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 menjadiReleased(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 #
- 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.
- 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.
- Kerentanan Keamanan Serius: Jika kontainer kita berhasil dieksploitasi oleh hacker, hacker tersebut dapat membaca atau memodifikasi seluruh file sensitif sistem operasi host (seperti
/etc/shadowatau/root/.ssh/) melalui mounthostPathyang 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-podmenulis 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.
- 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. - Gagal Deployment Total: Jika kluster baru tidak dikonfigurasi dengan default StorageClass sama sekali, PVC kita akan berada di status
Pendingselamanya, 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
emptyDiruntuk cache.- Retain policy mengamankan data dari human error: Setel reclaim policy StorageClass database produksi ke
Retainagar 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
volumeClaimTemplatesuntuk 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 →