Anti-Pattern Storage #

Di dalam arsitektur aplikasi microservices modern, data adalah aset yang paling berharga. Kita sering kali dengan mudah mengelola aplikasi tanpa status (stateless); jika kontainer web crash atau mati, kita cukup membiarkan Kubernetes menghidupkan kembali replika baru dari gambar image yang bersih. Namun, ketika kita berurusan dengan beban kerja yang memiliki status (stateful workload) seperti database transaksional, sistem antrian pesan (message brokers), atau media penyimpanan berkas, toleransi kesalahan kita menjadi sangat ketat.

Storage adalah area di mana kesalahan konfigurasi paling sering tidak terdeteksi sejak awal. Semuanya mungkin terlihat berjalan lancar saat pertama kali di-deploy. Namun, saat kluster mengalami kegagalan hardware, proses pemeliharaan node, atau pembaruan versi, konfigurasi yang salah dapat memicu kehilangan data permanen (data loss).

Artikel ini mengulas secara komprehensif berbagai anti-pattern storage yang paling sering ditemui di lingkungan produksi kluster Kubernetes, konsekuensi buruknya terhadap bisnis, serta contoh kode solusi konkrit untuk menghindarinya.


Anti-Pattern 1: Menyimpan State di Filesystem Kontainer (Ephemeral Storage) #

Ini adalah kesalahan paling klasik dan mendasar yang sering kita jumpai ketika tim developer baru melakukan migrasi aplikasi monolitik tradisional ke dalam Kubernetes. Mereka terbiasa menulis file gambar unggahan pengguna, file log, session state, atau bahkan file database SQLite langsung ke dalam folder lokal aplikasi (misalnya /app/data).

Konsekuensi Buruk #

Kontainer di Kubernetes dirancang bersifat ephemeral (sementara). Setiap kali kontainer mengalami crash, di-restart oleh kubelet, atau dipindahkan ke node lain akibat pemeliharaan rutin (rolling update), Kubernetes akan membuang kontainer lama beserta seluruh layer filesystem writable-nya, lalu membuat kontainer baru dari image dasar yang bersih. Akibatnya, seluruh berkas unggahan pengguna atau data transaksi database lokal kita akan musnah selamanya.

Perbandingan Kode #

Kode Manifest Salah (Menulis Langsung ke Layer Kontainer) #

# JANGAN LAKUKAN INI: Menulis data tanpa volume mount
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-without-storage
  namespace: production
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web-app
  template:
    metadata:
      labels:
        app: web-app
    spec:
      containers:
        - name: web-container
          image: my-app:v1.0.0
          # Aplikasi menulis file unggahan ke /var/www/uploads secara langsung

Kode Solusi (Menggunakan Volume untuk Persistensi Data) #

# SOLUSI: Pisahkan data persisten menggunakan PVC dan data sementara dengan emptyDir
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-with-safe-storage
  namespace: production
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web-app
  template:
    metadata:
      labels:
        app: web-app
    spec:
      containers:
        - name: web-container
          image: my-app:v1.0.0
          volumeMounts:
            - name: persistent-uploads-volume
              mountPath: /var/www/uploads
            - name: temporary-cache-volume
              mountPath: /tmp/cache
      volumes:
        - name: persistent-uploads-volume
          persistentVolumeClaim:
            claimName: user-uploads-pvc # Menunjuk ke PVC eksternal
        - name: temporary-cache-volume
          emptyDir: {} # Cache boleh hilang saat Pod mati, tapi tidak mengotori container filesystem

Anti-Pattern 2: Menggunakan Reclaim Policy Delete untuk Database Produksi #

Secara default, banyak cloud provider yang mengonfigurasi default StorageClass mereka dengan kebijakan pemulihan volume (reclaimPolicy) bernilai Delete. Artinya, jika resource objek PersistentVolumeClaim (PVC) di Kubernetes dihapus, maka volume disk fisik yang ada di cloud provider (seperti AWS EBS atau Google Cloud Persistent Disk) juga akan dihapus seketika.

Konsekuensi Buruk #

Manusia tidak luput dari kesalahan. Dalam skenario darurat di mana database kita mengalami kendala, seorang engineer mungkin terburu-buru menghapus StatefulSet atau PVC database dengan maksud untuk melakukan re-create ulang manifes. Jika StorageClass yang digunakan menggunakan kebijakan Delete, begitu objek PVC terhapus dari API Server, driver CSI akan langsung mengirimkan perintah penghapusan disk fisik ke cloud API. Data database produksi berukuran terabyte bisa lenyap dalam hitungan detik tanpa ada kesempatan untuk di-restore dari Recycle Bin cloud.

Perbandingan Kode #

Kode Manifest Salah (Reclaim Policy Delete pada StorageClass DB) #

# JANGAN GUNAKAN INI UNTUK DATA PENTING PRODUKSI
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: DB-storage-class
provisioner: ebs.csi.aws.com
reclaimPolicy: Delete # Sangat berbahaya: Hapus PVC = Hapus Disk Fisik Cloud
parameters:
  type: gp3

Kode Solusi (Menerapkan Reclaim Policy Retain) #

# SOLUSI: Mengamankan volume fisik dengan kebijakan Retain
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: secure-database-sc
provisioner: ebs.csi.aws.com
reclaimPolicy: Retain # Sangat aman: Jika PVC terhapus, volume cloud fisik TETAP bertahan
volumeBindingMode: WaitForFirstConsumer
parameters:
  type: gp3
  • Dengan kebijakan Retain, jika PVC database terhapus secara tidak sengaja, status PersistentVolume (PV) fisik di kluster Kubernetes akan berubah menjadi Released (terlepas). Data di cloud tetap aman, dan administrator kluster dapat merebut kembali data tersebut dengan membuat manifest PV statis baru yang menunjuk ke ID disk fisik yang sama.

Anti-Pattern 3: Berbagi Volume Block Storage (ReadWriteOnce) ke Banyak Pod #

Banyak developer mengira bahwa mereka dapat menghemat biaya penyimpanan dengan membuat satu PVC berkapasitas besar berkinerja SSD, kemudian menempelkannya (mount) ke banyak replica Pod aplikasi secara bersamaan menggunakan pola Deployment.

Konsekuensi Buruk #

Sebagian besar penyimpanan berbasis blok (block storage) di cloud (seperti AWS EBS atau GCP Persistent Disk) hanya mendukung access mode ReadWriteOnce (RWO). Artinya, volume tersebut hanya dapat ditempelkan secara eksklusif ke satu node worker VM dalam satu waktu.

Jika kita memiliki Deployment dengan 3 replika Pod yang berjalan di 3 node worker fisik yang berbeda, dan semuanya merujuk pada satu PVC RWO yang sama, maka hanya Pod pertama yang berhasil berjalan. Dua Pod lainnya akan stuck selamanya di status ContainerCreating atau Pending dengan pesan error Multi-Attach error for volume.

Mari kita lihat visualisasi skenario kegagalan ini melalui diagram berikut:

flowchart TD
    PVC["Shared PVC (ReadWriteOnce)"] -- Mount Sukses --> Pod1["Pod A (Node 1)"]
    PVC -. Multi-Attach Error .-> Pod2["Pod B (Node 2)"]
    PVC -. Multi-Attach Error .-> Pod3["Pod C (Node 3)"]
    
    style Pod2 fill:#ffcccc,stroke:#ff0000,stroke-width:2px
    style Pod3 fill:#ffcccc,stroke:#ff0000,stroke-width:2px

Perbandingan Kode #

Kode Manifest Salah (Shared PVC RWO di Seluruh Replikasi Pod) #

# JANGAN LAKUKAN INI: Satu PVC dipakai ramai-ramai oleh banyak replica Pod
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-shared-rwo-failure
  namespace: production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-server
  template:
    metadata:
      labels:
        app: web-server
    spec:
      containers:
        - name: app-container
          image: nginx:alpine
          volumeMounts:
            - name: shared-data
              mountPath: /usr/share/nginx/html
      volumes:
        - name: shared-data
          persistentVolumeClaim:
            claimName: shared-html-pvc # RWO PVC tunggal

Kode Solusi (Menggunakan volumeClaimTemplates pada StatefulSet) #

Untuk aplikasi stateful (seperti database), kita tidak boleh menggunakan Deployment. Kita harus menggunakan StatefulSet dan memanfaatkan fitur volumeClaimTemplates agar Kubernetes membuatkan PVC unik secara otomatis untuk setiap replika Pod yang berjalan (misalnya data-db-0, data-db-1, dst).

# SOLUSI: Menggunakan StatefulSet dengan template PVC unik per Pod
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres-db
  namespace: production
spec:
  serviceName: postgres-service
  replicas: 3
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres-container
          image: postgres:15
          volumeMounts:
            - name: db-data
              mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
    - metadata:
        name: db-data
      spec:
        accessModes:
          - ReadWriteOnce
        storageClassName: secure-database-sc
        resources:
          requests:
            storage: 50Gi

Anti-Pattern 4: Menyalahgunakan hostPath untuk Workload Produksi #

Volume tipe hostPath me-mount direktori fisik langsung dari sistem operasi node worker tempat Pod berjalan ke dalam container. Sering kali tim developer merasa ini sangat praktis di awal pengembangan karena tidak membutuhkan konfigurasi driver CSI atau StorageClass yang rumit.

Konsekuensi Buruk #

  1. Inkonsistensi Data: Jika Pod kita mengalami crash dan dijadwalkan ulang oleh Kubernetes scheduler ke node worker lain, Pod tersebut akan me-mount direktori dari node worker baru yang masih kosong atau memiliki data yang berbeda. Data kita menjadi terpecah-pecah antar node fisik.
  2. Kehilangan Data Total: Jika node worker mengalami kegagalan hardware, seluruh data yang disimpan di disk lokal node tersebut akan lenyap. Kita kehilangan fitur ketersediaan tinggi dan portabilitas volume Kubernetes.
  3. Kerentanan Keamanan Serius: Jika kontainer kita berhasil dieksploitasi oleh hacker, hacker tersebut dapat membaca atau memodifikasi seluruh file sensitif sistem operasi host (seperti /etc/shadow atau /root/.ssh/) melalui mount hostPath yang berjalan dengan hak akses penuh.

Perbandingan Kode #

Kode Manifest Salah (Mengekspos Filesystem Node Fisik) #

# JANGAN LAKUKAN INI DI PRODUKSI: Data terkunci pada satu node fisik
apiVersion: v1
kind: Pod
metadata:
  name: insecure-app-pod
  namespace: production
spec:
  containers:
    - name: app
      image: node:18
      volumeMounts:
        - name: local-storage
          mountPath: /app/data
  volumes:
    - name: local-storage
      hostPath:
        path: /var/lib/my-app-data # Bergantung penuh pada filesystem host node worker
        type: DirectoryOrCreate

Kode Solusi (Menggunakan Abstraksi Dynamic PVC Bawaan CSI) #

# SOLUSI: Gunakan PVC agar volume data dapat berpindah mengikuti Pod ke node manapun
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: app-portable-data-pvc
  namespace: production
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: secure-database-sc
  resources:
    requests:
      storage: 20Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: portable-app-deployment
  namespace: production
spec:
  replicas: 1
  selector:
    matchLabels:
      app: portable-app
  template:
    metadata:
      labels:
        app: portable-app
    spec:
      containers:
        - name: app
          image: node:18
          volumeMounts:
            - name: portable-volume
              mountPath: /app/data
      volumes:
        - name: portable-volume
          persistentVolumeClaim:
            claimName: app-portable-data-pvc

Anti-Pattern 5: Mengabaikan Limit Ephemeral Storage pada Pod #

Saat kita mendeploy aplikasi, kita terbiasa membatasi penggunaan CPU dan memori RAM (resource requests and limits). Namun, kita sering mengabaikan pembatasan untuk penggunaan disk kontainer lokal (ephemeral storage). Direktori seperti /tmp atau file logs kontainer menggunakan kapasitas disk sistem utama node worker tempat Kubernetes berjalan.

Konsekuensi Buruk #

Jika aplikasi kita memiliki bug (misalnya menulis log tanpa batas/rotasi, atau mengunduh file temporary yang sangat besar ke direktori /tmp), disk utama node worker (/var/lib/kubelet) akan terisi penuh hingga 100%.

Ketika kapasitas disk node kritis, Kubelet lokal akan memicu status DiskPressure. Kubelet kemudian akan mengambil tindakan evakuasi paksa (eviction) dengan mematikan Pod-Pod yang berjalan di node tersebut secara acak demi menyelamatkan kestabilan sistem operasi host. Akibatnya, Pod aplikasi produksi kita yang sehat dan tidak bersalah akan ikut mati terkena dampak.

Mari kita lihat alur kejadian ini:

flowchart TD
    AppBug["Aplikasi Menulis Log Tanpa Batas"] -. Memenuhi disk .-> NodeDisk["Worker Node Disk (100% Full)"]
    NodeDisk -. Memicu status .-> DiskPressure["Kubelet DiskPressure Event"]
    DiskPressure -. Eksekusi .-> Eviction["Evakuasi Paksa (Evict Pods)"]
    Eviction -. Mematikan .-> ProdPods["Pod Produksi Lain (Downtime)"]

Perbandingan Kode #

Kode Manifest Salah (Tanpa Limit Ephemeral Storage) #

# JANGAN LAKUKAN INI: Rentan memenuhi disk node worker
apiVersion: v1
kind: Pod
metadata:
  name: risky-app-pod
  namespace: production
spec:
  containers:
    - name: log-writer
      image: alpine
      command: ["/bin/sh", "-c", "while true; do echo 'unlimited logs' >> /var/log/app.log; done"]
      resources:
        limits:
          cpu: "500m"
          memory: "512Mi"
          # ephemeral-storage limits dikosongkan!

Kode Solusi (Menerapkan Limits Ephemeral Storage) #

# SOLUSI: Batasi kapasitas disk kontainer agar tidak mengganggu tetangga
apiVersion: v1
kind: Pod
metadata:
  name: safe-app-pod
  namespace: production
spec:
  containers:
    - name: safe-log-writer
      image: alpine
      command: ["/bin/sh", "-c", "while true; do echo 'safe logs' >> /var/log/app.log; done"]
      resources:
        requests:
          cpu: "500m"
          memory: "512Mi"
          ephemeral-storage: "500Mi" # Request kapasitas disk awal
        limits:
          cpu: "500m"
          memory: "512Mi"
          ephemeral-storage: "2Gi" # Batas maksimal penggunaan disk kontainer
  • Jika Pod safe-app-pod menulis data ephemeral melebihi batas 2Gi, Kubelet akan langsung mematikan Pod ini secara eksklusif tanpa perlu mengorbankan kestabilan node worker fisik maupun Pod produksi lainnya.

Anti-Pattern 6: Tidak Menentukan storageClassName Secara Eksplisit pada PVC #

Ketika membuat manifest PVC, developer sering mengosongkan properti storageClassName, dengan asumsi Kubernetes akan selalu memilih StorageClass bawaan kluster yang sudah dikonfigurasi oleh tim SysOps.

Konsekuensi Buruk #

Mengandalkan nilai implisit di produksi adalah tindakan berisiko tinggi.

  1. Degradasi Performa Tiba-tiba: Jika tim platform melakukan migrasi atau upgrade kluster dan secara tidak sengaja mengubah default StorageClass kluster dari tipe SSD super cepat (premium-gp3) menjadi HDD murah lambat (standard-hdd), PVC kita yang baru dibuat akan otomatis menggunakan HDD. Akibatnya, database kita mengalami degradasi IOPS drastis tanpa adanya perubahan kode aplikasi.
  2. Gagal Deployment Total: Jika kluster baru tidak dikonfigurasi dengan default StorageClass sama sekali, PVC kita akan berada di status Pending selamanya, membingungkan tim deployment karena tidak ada pesan error yang jelas pada manifes aplikasi.

Perbandingan Kode #

Kode Manifest Salah (Mengosongkan Properti storageClassName) #

# JANGAN LAKUKAN INI: Perilaku PVC bergantung pada setelan luar kluster
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: app-database-pvc
  namespace: production
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 100Gi
  # storageClassName dihilangkan!

Kode Solusi (Menuliskan storageClassName Secara Eksplisit) #

# SOLUSI: Tentukan kelas penyimpanan yang kita inginkan secara deklaratif
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: app-database-pvc
  namespace: production
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: secure-database-sc # Selalu eksplisit untuk menjamin performa
  resources:
    requests:
      storage: 100Gi

Kesimpulan & Checklist Audit Keamanan Storage Kluster #

Mengelola stateful workload di Kubernetes menuntut kehati-hatian yang tinggi. Kita harus memperlakukan data sebagai aset penting dan merancang infrastruktur penyimpanan kita dengan mengantisipasi segala kemungkinan terburuk (fail-safe design).

Sebelum kita merilis aplikasi ke produksi, mari kita gunakan checklist audit penyimpanan berikut sebagai panduan evaluasi kluster kita:

No Aspek Evaluasi Keamanan & Stabilitas Storage Status Tindakan Koreksi
1 Apakah seluruh file unggahan pengguna dan database sudah terhubung ke PVC (bukan disk ephemeral kontainer)? [ ] Yes / No Pindahkan direktori tulis aplikasi ke volume mount eksternal.
2 Apakah StorageClass untuk database penting sudah disetel ke reclaimPolicy: Retain? [ ] Yes / No Buat StorageClass baru dengan kebijakan Retain untuk mencegah data loss tidak sengaja.
3 Apakah binding mode StorageClass cloud multi-zona sudah disetel ke volumeBindingMode: WaitForFirstConsumer? [ ] Yes / No Ubah mode binding kelas penyimpanan di cloud untuk mencegah volume attachment error lintas zona.
4 Apakah kita telah menghindari penggunaan volume hostPath untuk aplikasi produksi? [ ] Yes / No Ganti dengan PVC yang didukung oleh CSI driver standar.
5 Apakah seluruh Pod yang memiliki potensi memproses data/logs besar sudah disetel batas ephemeral-storage? [ ] Yes / No Tambahkan konfigurasi limits ephemeral-storage pada manifes resource container spec.
6 Apakah seluruh manifest PVC telah mendefinisikan storageClassName secara tertulis dan eksplisit? [ ] Yes / No Tulis ulang manifes PVC lama agar tidak bergantung pada setelan default kluster.

Ringkasan #

  • Wajib mount volume untuk stateful data: Jangan biarkan data ditulis langsung ke filesystem container karena akan lenyap saat container restart. Gunakan PVC untuk file persisten dan emptyDir untuk cache.
  • Retain policy mengamankan data dari human error: Setel reclaim policy StorageClass database produksi ke Retain agar data fisik cloud tidak ikut musnah jika PVC tidak sengaja terhapus.
  • Hindari shared PVC ReadWriteOnce pada Deployment: Block storage hanya bisa terikat ke satu node worker. Gunakan StatefulSet dengan volumeClaimTemplates untuk mengalokasikan PVC unik per Pod.
  • Tinggalkan hostPath di produksi: hostPath merusak portabilitas Pod dan membuka celah keamanan sistem operasi host yang fatal. Selalu andalkan driver CSI standar.
  • Batasi ephemeral-storage kontainer: Cegah insiden DiskPressure pada worker node akibat kebocoran log/data container dengan menerapkan limits ephemeral storage pada manifest Pod.
  • Eksplisit terhadap storageClassName: Selalu tentukan target StorageClass secara tertulis di manifest PVC kita guna mencegah perubahan performa tak terduga saat kluster mengalami migrasi.

← Sebelumnya: Dynamic Provisioning   Berikutnya: Storage Performance →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact