StatefulSets & Penyimpanan Persisten dalam Kubernetes

Buat aplikasi stateful dengan skalasi terurut dan data yang bertahan lama

Kubernetes StatefulSets adalah solusi utama untuk mengelola aplikasi berstatus yang memerlukan identitas stabil, penyimpanan permanen, dan pola penyebaran terurut—penting untuk database, sistem terdistribusi, dan lapisan caching.

Jika Anda baru dengan Kubernetes atau sedang mengatur klaster, pertimbangkan untuk menjelajahi distribusi Kubernetes seperti k3s atau MicroK8s untuk pengembangan, atau instalasi Kubernetes dengan Kubespray untuk klaster berbasis produksi.

presentasi di kafe Gambar yang menarik ini dihasilkan oleh model AI Flux 1 dev.

Apa itu StatefulSets?

StatefulSets adalah objek API beban kerja Kubernetes yang dirancang khusus untuk mengelola aplikasi berstatus. Berbeda dengan Deployments yang memperlakukan semua pod sebagai saling menggantikan, StatefulSets mempertahankan identitas unik untuk setiap pod dengan jaminan tentang urutan dan keunikan.

Fitur Utama:

  • Identitas Jaringan Stabil: Setiap pod mendapatkan nama host yang dapat diprediksi yang bertahan selama restart
  • Penyimpanan Permanen: Klaim Volume Persisten yang ditujukan untuk mengikuti pod selama reschedule
  • Penyebaran Terurut: Pod dibuat secara berurutan (0, 1, 2…) dan dihentikan dalam urutan terbalik
  • Pembaruan Terurut: Pembaruan berjalan dalam urutan, memastikan stabilitas aplikasi

StatefulSets sangat penting untuk aplikasi seperti PostgreSQL, MySQL, MongoDB, Cassandra, Elasticsearch, Kafka, ZooKeeper, Redis, dan etcd—setiap beban kerja di mana identitas pod dan keberlanjutan data penting.

Memahami Penyimpanan Permanen dalam Kubernetes

Kubernetes menyediakan lapisan abstraksi penyimpanan yang canggih yang memisahkan manajemen penyimpanan dari siklus hidup pod:

Komponen Penyimpanan

PersistentVolume (PV): Sebuah bagian penyimpanan dalam klaster yang disediakan oleh administrator atau dibuat secara dinamis melalui StorageClass. PVs ada secara independen dari pod.

PersistentVolumeClaim (PVC): Permintaan penyimpanan oleh pod. PVCs mengikat ke PVs yang tersedia yang cocok dengan kebutuhan mereka (ukuran, mode akses, kelas penyimpanan).

StorageClass: Mendefinisikan berbagai “kelas” penyimpanan dengan berbagai penyedia (AWS EBS, GCE PD, Azure Disk, NFS, dll.) dan parameter seperti replikasi, tingkat kinerja, dan kebijakan cadangan.

Mode Akses

  • ReadWriteOnce (RWO): Volume dipasang sebagai baca-tulis oleh satu node
  • ReadOnlyMany (ROX): Volume dipasang sebagai baca-hanya oleh banyak node
  • ReadWriteMany (RWX): Volume dipasang sebagai baca-tulis oleh banyak node (memerlukan backend penyimpanan khusus)

Arsitektur Penyimpanan StatefulSet

StatefulSets menggunakan volumeClaimTemplates untuk secara otomatis menciptakan PersistentVolumeClaims untuk setiap replika pod. Ini secara fundamental berbeda dari Deployments:

Cara volumeClaimTemplates Bekerja

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql-service
  replicas: 3
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: password
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "fast-ssd"
      resources:
        requests:
          storage: 10Gi

Ketika Anda membuat StatefulSet ini:

  1. Kubernetes membuat pod mysql-0 dan PVC data-mysql-0
  2. Lalu membuat pod mysql-1 dan PVC data-mysql-1
  3. Akhirnya membuat pod mysql-2 dan PVC data-mysql-2

Setiap pod mendapatkan volume permanen 10GB sendiri. Jika mysql-1 dihapus atau dijadwalkan ulang, Kubernetes membuatnya kembali dan menghubungkan PVC data-mysql-1 yang sama, mempertahankan semua data.

Membuat Service Headless untuk StatefulSets

StatefulSets memerlukan Service Headless untuk menyediakan identitas jaringan stabil:

apiVersion: v1
kind: Service
metadata:
  name: mysql-service
spec:
  clusterIP: None  # Ini membuatnya menjadi service headless
  selector:
    app: mysql
  ports:
  - port: 3306
    name: mysql

Ini menciptakan entri DNS untuk setiap pod:

  • mysql-0.mysql-service.default.svc.cluster.local
  • mysql-1.mysql-service.default.svc.cluster.local
  • mysql-2.mysql-service.default.svc.cluster.local

Aplikasi dapat terhubung langsung ke instance pod tertentu menggunakan nama DNS stabil ini.

Pola Penyebaran StatefulSet

Untuk tim yang mengelola penyebaran Kubernetes yang kompleks, Helm Charts menyediakan cara yang kuat untuk mengemas dan mendeploy StatefulSets dengan templating, versioning, dan manajemen dependensi. Helm menyederhanakan pengelolaan konfigurasi StatefulSet di berbagai lingkungan.

Penyebaran Terurut

Ketika menyebar dari 3 ke 5 replika:

kubectl scale statefulset mysql --replicas=5

Kubernetes menciptakan pod dalam urutan: mysql-3 → menunggu siap → mysql-4

Ketika menyebar dari 5 ke 3:

kubectl scale statefulset mysql --replicas=3

Kubernetes menghentikan dalam urutan terbalik: mysql-4 → menunggu penghentian → mysql-3

Pembaruan Berjalan

StatefulSets mendukung dua strategi pembaruan:

OnDelete: Pembaruan manual—pod hanya diperbarui ketika Anda menghapusnya RollingUpdate: Pembaruan otomatis berurutan dalam urutan ordinal terbalik

spec:
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      partition: 2  # Hanya perbarui pod dengan ordinal >= 2

Parameter partition memungkinkan pengujian canary—Anda dapat memperbarui pod dengan nomor tinggi terlebih dahulu sebelum menyebar ke semua replika.

Praktik Terbaik Penyimpanan

Pemrosesan Dinamis

Selalu gunakan StorageClasses untuk pemrosesan volume dinamis:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp3
  iops: "3000"
  throughput: "125"
allowVolumeExpansion: true
reclaimPolicy: Retain

allowVolumeExpansion: Memungkinkan memperbesar PVC tanpa membuat ulang reclaimPolicy: Retain mempertahankan data PV setelah penghapusan PVC, Delete menghapusnya secara otomatis

Kebijakan Retensi PVC

Kubernetes 1.23+ mendukung persistentVolumeClaimRetentionPolicy:

spec:
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: Retain    # Pertahankan PVC ketika StatefulSet dihapus
    whenScaled: Delete     # Hapus PVC ketika menyebar ke bawah

Opsi:

  • Retain: Pertahankan PVC (perilaku default, paling aman)
  • Delete: Hapus PVC secara otomatis (berguna untuk lingkungan pengembangan)

Strategi Cadangan

Snapshots Volume: Gunakan sumber daya VolumeSnapshot untuk membuat cadangan titik dalam waktu

apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: mysql-snapshot-20251113
spec:
  volumeSnapshotClassName: csi-snapclass
  source:
    persistentVolumeClaimName: data-mysql-0

Cadangan Tingkat Aplikasi: Gunakan alat seperti mysqldump, pg_dump, atau Velero untuk cadangan database khusus

Replikasi Terdistribusi: Konfigurasikan replikasi tingkat aplikasi (replikasi MySQL, replikasi streaming PostgreSQL) sebagai pertahanan pertama

Kasus Penggunaan Nyata

Klaster Database (PostgreSQL)

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres
  replicas: 3
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:16
        ports:
        - containerPort: 5432
        env:
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: postgres-secret
              key: password
        - name: PGDATA
          value: /var/lib/postgresql/data/pgdata
        volumeMounts:
        - name: data
          mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: "standard"
      resources:
        requests:
          storage: 20Gi

Cache Terdistribusi (Redis)

Untuk klaster Redis, Anda memerlukan StatefulSet dan konfigurasi yang hati-hati:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
spec:
  serviceName: redis-service
  replicas: 6
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:7-alpine
        command:
        - redis-server
        - "--appendonly"
        - "yes"
        - "--appendfsync"
        - "everysec"
        ports:
        - containerPort: 6379
          name: redis
        volumeMounts:
        - name: data
          mountPath: /data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 5Gi

Antrian Pesan (Kafka)

Kafka memerlukan penyimpanan permanen untuk log dan identitas jaringan stabil untuk koordinasi broker:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: kafka
spec:
  serviceName: kafka-service
  replicas: 3
  selector:
    matchLabels:
      app: kafka
  template:
    metadata:
      labels:
        app: kafka
    spec:
      containers:
      - name: kafka
        image: confluentinc/cp-kafka:7.5.0
        ports:
        - containerPort: 9092
          name: kafka
        env:
        - name: KAFKA_BROKER_ID
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        volumeMounts:
        - name: data
          mountPath: /var/lib/kafka/data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 100Gi

Pemantauan dan Penyelesaian Masalah

Untuk referensi menyeluruh tentang perintah Kubernetes yang digunakan dalam bagian ini, lihat Kubernetes Cheatsheet.

Periksa Status StatefulSet

# Lihat detail StatefulSet
kubectl get statefulset mysql
kubectl describe statefulset mysql

# Periksa urutan pembuatan pod dan status
kubectl get pods -l app=mysql -w

# Lihat status PVC
kubectl get pvc
kubectl describe pvc data-mysql-0

Masalah Umum

Pod Tertahan dalam Pending: Periksa status PVC dan ketersediaan penyimpanan

kubectl describe pod mysql-0
kubectl get events --sort-by='.lastTimestamp'

Penyimpanan Penuh: Perluas PVC jika StorageClass memungkinkan

kubectl patch pvc data-mysql-0 -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'

Pod Tidak Dapat Dihentikan: Periksa kunci aplikasi atau masalah pemutusan volume

kubectl delete pod mysql-0 --grace-period=0 --force

Metrik yang Perlu Dipantau

  • Penggunaan Penyimpanan: Pantau kapasitas PVC dan persentase penggunaan
  • Kinerja I/O: Lacak IOPS, throughput, dan latensi
  • Restart Pod: Restart yang sering mungkin menunjukkan masalah penyimpanan
  • Waktu Pengikatan PVC: Waktu pengikatan yang lambat menunjukkan masalah pemrosesan

Strategi Migrasi

Ketika bermigrasi ke StatefulSets, pastikan klaster Kubernetes Anda dikonfigurasi dengan benar. Untuk pengaturan homelab atau klaster kecil, tinjau perbandingan menyeluruh dari distribusi Kubernetes untuk memilih platform yang tepat untuk kebutuhan beban kerja Anda.

Dari Deployment ke StatefulSet

  1. Buat StatefulSet dengan volumeClaimTemplates
  2. Skalakan turun Deployment secara halus
  3. Pulihkan data dari cadangan ke pod StatefulSet
  4. Perbarui referensi DNS/Service
  5. Hapus Deployment lama dan PVCs

Cadangkan Sebelum Migrasi

# Snapshot PVC yang ada
kubectl get pvc -o yaml > pvc-backup.yaml

# Buat snapshot volume
kubectl apply -f volume-snapshot.yaml

Pertimbangan Keamanan

Enkripsi Penyimpanan

Aktifkan enkripsi di tempat menggunakan parameter StorageClass:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: encrypted-storage
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp3
  encrypted: "true"
  kmsKeyId: arn:aws:kms:us-east-1:123456789012:key/abcd-1234

Kontrol Akses

Gunakan RBAC untuk membatasi siapa yang dapat menciptakan/merubah StatefulSets dan PVCs:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: statefulset-manager
rules:
- apiGroups: ["apps"]
  resources: ["statefulsets"]
  verbs: ["get", "list", "create", "update", "delete"]
- apiGroups: [""]
  resources: ["persistentvolumeclaims"]
  verbs: ["get", "list", "create", "delete"]

Kebijakan Jaringan

Batasi komunikasi pod ke pod:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: mysql-network-policy
spec:
  podSelector:
    matchLabels:
      app: mysql
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: backend
    ports:
    - protocol: TCP
      port: 3306

Optimisasi Kinerja

Tingkat Kinerja Penyimpanan

Pilih StorageClass yang sesuai berdasarkan beban kerja:

  • IOPS Tinggi: Database dengan baca/tulis acak berat (gp3, io2)
  • Throughput Tinggi: Agregasi log, analitik (st1, sc1)
  • Seimbang: Aplikasi umum (gp3)

Penyebaran Pod

Gunakan anti-affinity pod untuk menyebarkan pod StatefulSet di zona ketersediaan:

spec:
  template:
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchLabels:
                app: mysql
            topologyKey: topology.kubernetes.io/zone

Permintaan dan Batas Sumber Daya

Atur sumber daya yang sesuai untuk kinerja konsisten:

resources:
  requests:
    cpu: "2"
    memory: "4Gi"
    ephemeral-storage: "10Gi"
  limits:
    cpu: "4"
    memory: "8Gi"
    ephemeral-storage: "20Gi"

Pola Lanjutan

Aplikasi Berstatus dengan Init Containers

Gunakan init containers untuk inisialisasi database:

spec:
  template:
    spec:
      initContainers:
      - name: init-mysql
        image: mysql:8.0
        command:
        - bash
        - "-c"
        - |
          if [[ ! -d /var/lib/mysql/mysql ]]; then
            mysqld --initialize-insecure --datadir=/var/lib/mysql
          fi          
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql

Pod Multi-Container untuk Sidecars

Tambahkan sidecars cadangan atau agen pemantauan:

spec:
  template:
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        # ... konfigurasi mysql ...
      - name: backup-sidecar
        image: mysql-backup:latest
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          readOnly: true

ConfigMaps untuk Konfigurasi Dinamis

Pisahkan konfigurasi dari definisi StatefulSet:

apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-config
data:
  my.cnf: |
    [mysqld]
    max_connections=200
    innodb_buffer_pool_size=2G    
---
spec:
  template:
    spec:
      containers:
      - name: mysql
        volumeMounts:
        - name: config
          mountPath: /etc/mysql/conf.d
      volumes:
      - name: config
        configMap:
          name: mysql-config

Tautan Berguna