 Autor: [Adam Nadolny](/autorzy/adam-nadolny) Ekspert DevOps i infrastruktury · Zweryfikowano Kwiecień 2026

1.  [Strona główna](/) ›
2.  [Baza wiedzy](/baza-wiedzy/) ›
3.  Kubernetes StatefulSet — bazy danych

# Kubernetes StatefulSet — wdrażanie baz danych w klastrze

Opublikowano: 10 kwietnia 2026 · Kategoria: VPS / Kubernetes

Deployment to domyślny wybór dla bezstanowych serwisów w Kubernetes, ale bazy danych mają wymagania, których Deployment nie spełnia: stała tożsamość poda, dedykowany wolumen per replika i uporządkowane uruchamianie. StatefulSet powstał właśnie dla takich scenariuszy. Ten artykuł pokazuje jak wdrożyć MySQL i PostgreSQL w K8s, zarządzać PersistentVolumes i tworzyć backupy przez CronJob oraz VolumeSnapshot.

## StatefulSet vs Deployment — kluczowe różnice

Cecha

Deployment

StatefulSet

Tożsamość poda

Losowy hash (app-7d4f9c)

Stabilna ordynalna (db-0, db-1)

PersistentVolume

Współdzielony lub brak

Dedykowany PVC per pod

Kolejność startu

Równoległa (dowolna)

Sekwencyjna (db-0 → db-1 → db-2)

Headless Service

Opcjonalny

Wymagany (stable DNS per pod)

Rolling update

Równoległy

Odwrócona kolejność (N → 0)

Zastosowanie

Bezstanowe API, web serwerzy

Bazy danych, kolejki, ZooKeeper

## Headless Service i DNS per pod

StatefulSet wymaga headless Service (clusterIP: None) — bez wirtualnego IP. Kubernetes tworzy wtedy rekordy DNS dla każdego poda osobno: `pod-0.service.namespace.svc.cluster.local`. Replika może więc zawsze połączyć się z masterem po stałym adresie, niezależnie od restartów.

\# headless-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: mysql
  labels:
    app: mysql
spec:
  clusterIP: None        # headless — brak wirtualnego IP
  selector:
    app: mysql
  ports:
    - port: 3306
      name: mysql
---
# Zwykly Service do dostepu aplikacji (tylko odczyt przez repliki)
apiVersion: v1
kind: Service
metadata:
  name: mysql-read
spec:
  selector:
    app: mysql
  ports:
    - port: 3306

## StatefulSet dla MySQL — manifest krok po kroku

\# mysql-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  serviceName: "mysql"       # headless Service name
  replicas: 1
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
        - name: mysql
          image: mysql:8.0
          env:
            - name: MYSQL\_ROOT\_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: mysql-secret
                  key: root-password
            - name: MYSQL\_DATABASE
              value: "appdb"
          ports:
            - containerPort: 3306
              name: mysql
          volumeMounts:
            - name: data
              mountPath: /var/lib/mysql
          resources:
            requests:
              memory: "512Mi"
              cpu: "250m"
            limits:
              memory: "1Gi"
              cpu: "1"
          readinessProbe:
            exec:
              command: \["mysqladmin", "ping", "-h", "127.0.0.1"\]
            initialDelaySeconds: 30
            periodSeconds: 10
  volumeClaimTemplates:       # PVC tworzony per pod automatycznie
    - metadata:
        name: data
      spec:
        accessModes: \["ReadWriteOnce"\]
        storageClassName: "fast-ssd"
        resources:
          requests:
            storage: 20Gi

\# Secret z haslem (base64)
kubectl create secret generic mysql-secret \\
  --from-literal=root-password=SuperSecretPass123 \\
  -n default

# Aplikacja manifestow
kubectl apply -f headless-service.yaml
kubectl apply -f mysql-statefulset.yaml

# Sprawdzenie
kubectl get statefulset mysql
kubectl get pvc          # data-mysql-0 = 20Gi Bound
kubectl get pods         # mysql-0 Running

# DNS dla poda (z dowolnego poda w klastrze)
nslookup mysql-0.mysql.default.svc.cluster.local

## PostgreSQL w StatefulSet

Schemat dla PostgreSQL jest analogiczny — zmienia się obraz, zmienne środowiskowe i ścieżka montowania. Warto dodać `fsGroup` w securityContext, bo PostgreSQL wymaga konkretnych uprawnień na katalogu danych.

spec:
  securityContext:
    fsGroup: 999       # GID grupy postgres w kontenerze
  containers:
    - name: postgres
      image: postgres:16
      env:
        - name: POSTGRES\_PASSWORD
          valueFrom:
            secretKeyRef:
              name: pg-secret
              key: password
        - name: POSTGRES\_DB
          value: "appdb"
        - name: PGDATA
          value: /var/lib/postgresql/data/pgdata
      volumeMounts:
        - name: data
          mountPath: /var/lib/postgresql/data
      readinessProbe:
        exec:
          command: \["pg\_isready", "-U", "postgres"\]
        initialDelaySeconds: 15
        periodSeconds: 5

## Backup — CronJob z mysqldump / pg\_dump

\# mysql-backup-cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: mysql-backup
spec:
  schedule: "0 3 \* \* \*"       # co noc o 3:00 UTC
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 1
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: backup
              image: mysql:8.0
              env:
                - name: MYSQL\_ROOT\_PASSWORD
                  valueFrom:
                    secretKeyRef:
                      name: mysql-secret
                      key: root-password
              command:
                - /bin/sh
                - -c
                - |
                  DATE=$(date +%Y%m%d-%H%M)
                  mysqldump -h mysql-0.mysql.default.svc.cluster.local \\
                    -u root -p$MYSQL\_ROOT\_PASSWORD --all-databases \\
                    | gzip > /backup/full-$DATE.sql.gz
                  echo "Backup done: /backup/full-$DATE.sql.gz"
                  ls -lh /backup/ | tail -10
              volumeMounts:
                - name: backup-storage
                  mountPath: /backup
          volumes:
            - name: backup-storage
              persistentVolumeClaim:
                claimName: backup-pvc

## VolumeSnapshot — błyskawiczne migawki

VolumeSnapshot to migawka na poziomie storage drivera (CSI) — działa w sekundach niezależnie od rozmiaru bazy. Wymaga VolumeSnapshotClass od twojego CSI providera (np. Longhorn, AWS EBS CSI, GCE PD CSI).

\# VolumeSnapshotClass (np. dla Longhorn)
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
  name: longhorn-snap
driver: driver.longhorn.io
deletionPolicy: Delete
---
# Snapshot konkretnego PVC
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: mysql-snap-20260410
spec:
  volumeSnapshotClassName: longhorn-snap
  source:
    persistentVolumeClaimName: data-mysql-0
---
# Odtworzenie PVC ze snapshotu
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-restore-pvc
spec:
  dataSource:
    name: mysql-snap-20260410
    kind: VolumeSnapshot
    apiGroup: snapshot.storage.k8s.io
  accessModes: \["ReadWriteOnce"\]
  resources:
    requests:
      storage: 20Gi

## StorageClass — dobór dla baz danych

-   **Local storage** — najszybsze (brak sieciowego I/O), ale pod jest przywiązany do węzła. Brak automatycznej replikacji — jeśli węzeł padnie, baza jest niedostępna do czasu jego powrotu. Dobry dla dev lub gdy sam zarządzasz replikacją bazy.
-   **NFS PV** — działa z RWX, ale latency I/O może być problemem dla baz danych o wysokim obciążeniu zapisu. Przydatny jako backend backupów.
-   **Ceph / Rook** — distributed storage z replikacją, snapshots i resizem PVC. Wymaga minimum 3 węzłów OSD. Najlepszy wybór dla on-prem produkcji.
-   **Longhorn** — lżejszy od Ceph, łatwiejszy w instalacji, dobry dla małych klastrów (3-10 węzłów). Wbudowane snapshots i offsite backup do S3.
-   **Chmurowe CSI** — AWS EBS gp3, GCE pd-ssd, Azure Premium Disk. Najłatwiejsze w zarządzaniu, dobre IOPS, snapshots w jednej linii. Droższe niż on-prem.

**Wskazówka:** Dla dużych produkcyjnych baz danych (100 GB+, high-write) rozważ managed bazę (RDS, Cloud SQL, PlanetScale) lub dedykowanego operatora K8s (Percona Operator for MySQL, CloudNative PG dla PostgreSQL). Operator zarządza replikacją, failoverem i backupami automatycznie, co jest trudne do poprawnego zrobienia samodzielnie.

## Najczęstsze pytania

Czym różni się StatefulSet od Deployment w Kubernetes? +

Deployment tworzy bezstanowe pody — każdy pod jest identyczny, może być zastąpiony w dowolnej kolejności i nie ma stałej tożsamości. StatefulSet gwarantuje stabilne nazwy podów (np. mysql-0, mysql-1), stałe PersistentVolumes przypisane do konkretnego poda, oraz uporządkowane uruchamianie i skalowanie (jeden pod naraz, od 0 do N). Jest to kluczowe dla baz danych, gdzie mysql-0 jest masterem a mysql-1 repliką, i zamiana kolejności spowodowałaby utratę danych.

Czy warto uruchamiać bazy danych w Kubernetes? +

To zależy od kontekstu. Dla małych aplikacji i środowisk developerskich K8s StatefulSet to dobre rozwiązanie — jeden manifest zarządza bazą i aplikacją. Dla dużej produkcji (100+ GB danych, wysokie IOPS) wiele firm wybiera managed bazy (RDS, Cloud SQL) lub bare metal, bo K8s dodaje warstwę abstrakcji, która może zwiększać latency I/O. Środek drogi to operator bazy danych (Percona Operator, CloudNative PG), który zarządza replikacją i backupami automatycznie.

Co to są volumeClaimTemplates w StatefulSet? +

volumeClaimTemplates to sekcja w specyfikacji StatefulSet, która automatycznie tworzy PersistentVolumeClaim dla każdego poda. Jeśli masz StatefulSet z 3 replikami i jednym volumeClaimTemplate (np. 10Gi), Kubernetes stworzy 3 osobne PVC: data-mysql-0, data-mysql-1, data-mysql-2. Każdy pod dostaje swój dedykowany wolumen. Po usunięciu poda wolumen NIE jest usuwany — dopiero po manualnym usunięciu PVC lub usunięciu StatefulSet z --cascade=foreground.

Jak zrobić backup bazy danych działającej w Kubernetes? +

Są dwie popularne strategie: (1) CronJob uruchamiający mysqldump/pg\_dump wewnątrz poda (lekki, nie wymaga dodatkowych narzędzi, ale backup jest logiczny i powolny dla dużych baz). (2) VolumeSnapshot — migawka na poziomie storage drivera (CSI), tworzona przez Kubernetes VolumeSnapshotClass. Snapshot jest błyskawiczny, ale wymaga wspierającego CSI drivera (np. AWS EBS, GCE PD, Longhorn). Do zarządzania backupami zewnętrznie polecany jest Velero — snapshot PV + backup manifestów K8s.

Jak dobrać StorageClass dla bazy danych w K8s? +

Kluczowe parametry to: access mode (bazy potrzebują RWO — ReadWriteOnce, nie RWX), provisioner (lokal czy chmura), reclaim policy (Retain dla produkcji, Delete dla dev), oraz volumeBindingMode (WaitForFirstConsumer wymusza scheduling na węźle z dyskiem — ważne dla local storage). W chmurze wybierz StorageClass z SSD gp3 lub premium (niższe latency). Dla on-prem rozważ Longhorn lub Rook-Ceph jako backend CSI.

## Sprawdź oferty pasujące do tego scenariusza

Poniżej masz szybkie przejścia do ofert i stron z kodami rabatowymi tam, gdzie są dostępne.

Contabo

VPS z dużym RAM i NVMe — idealny do uruchomienia K8s z bazą danych

K8s + DB

[Aktywuj rabat →](/out/contabo)

#Reklama · link partnerski

[Zobacz kod rabatowy →](/kody-rabatowe/contabo)

ProSerwer.pl

Polski VPS do Kubernetes StatefulSet z zgodą RODO

Polski VPS

[Aktywuj rabat →](/out/proserwer-pl)

#Reklama · link partnerski

[Zobacz kod rabatowy →](/kody-rabatowe/proserwer)

Mikr.us

Budżetowy VPS do nauki i testowania K8s StatefulSet

Dev/Test

[Aktywuj rabat →](/out/mikrus)

#Reklama · link partnerski

[Zobacz kod rabatowy →](/kody-rabatowe/mikrus)

## Powiązane strony

-   [k3s — lekki Kubernetes na VPS](/baza-wiedzy/kubernetes-k3s-vps)
-   [Kubernetes PersistentVolume — zarządzanie storage](/baza-wiedzy/kubernetes-persistent-volumes)
-   [PostgreSQL na VPS — instalacja i konfiguracja](/baza-wiedzy/postgresql-vps-instalacja-konfiguracja)
-   [Wszystkie artykuły](/baza-wiedzy/)