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.