Volume #

Di dalam kontainerisasi Linux tradisional (seperti Docker polos), konsep volume digunakan untuk memetakan direktori dari host ke kontainer agar data tidak hilang saat kontainer berhenti. Namun, di dalam ekosistem orkestrasi Kubernetes yang bersifat dinamis dan terdistribusi, pengelolaan volume membutuhkan abstraksi yang jauh lebih matang. Pod di Kubernetes bersifat transien; mereka dapat dihancurkan dan dijadwalkan ulang di node worker fisik yang berbeda kapan saja oleh control plane.

Untuk mengatasi tantangan ini, Kubernetes menyediakan objek Volume. Volume di Kubernetes didefinisikan sebagai bagian dari siklus hidup Pod, bukan kontainer. Ini berarti volume akan bertahan selama Pod tersebut masih hidup, terlepas dari berapa kali kontainer di dalamnya mengalami crash atau restart. Selain itu, Kubernetes mendukung puluhan driver penyimpanan—mulai dari memori lokal (RAM) hingga penyimpanan cloud terkelola, sistem file jaringan (NFS), hingga injeksi metadata kluster. Artikel ini akan membedah secara mendalam bagaimana volume bekerja di Kubernetes, jenis-jenis volume lokal dan konfigurasi, evolusi menuju CSI (Container Storage Interface), pola penataan tingkat lanjut, serta kesalahan konfigurasi (anti-pattern) yang sering terjadi di produksi.


Bagaimana Volume Bekerja di Pod: Arsitektur Mounting #

Di Kubernetes, volume didefinisikan pada tingkat spesifikasi Pod (spec.volumes) sebagai deklarasi penyediaan penyimpanan. Setelah penyedia penyimpanan didefinisikan, kontainer-kontainer di dalam Pod tersebut harus me-mount volume tersebut secara eksplisit ke dalam sistem berkas lokal mereka menggunakan blok spec.containers[*].volumeMounts.

Mekanisme ini memungkinkan kita berbagi folder penyimpanan yang sama secara langsung antar kontainer yang berada di dalam satu Pod yang sama.

Berikut adalah diagram arsitektur bind-mount volume dari sistem host fisik ke dalam ruang nama (namespace) kontainer:

flowchart TD
    subgraph PodSpec ["Spesifikasi Pod (Pod Spec)"]
        PV_Def["volumes:\n- name: shared-vol\n  emptyDir: {}"]
    end

    subgraph Containers ["Kontainer-Kontainer di dalam Pod"]
        C1["Kontainer Utama (web-app)\nvolumeMounts:\n- name: shared-vol\n  mountPath: /usr/share/nginx/html"]
        C2["Kontainer Sidecar (log-watcher)\nvolumeMounts:\n- name: shared-vol\n  mountPath: /var/log/nginx"]
    end

    subgraph NodeHost ["Sistem Berkas Node Worker (Host Filesystem)"]
        HostDir["/var/lib/kubelet/pods/{pod-id}/volumes/kubernetes.io~emptyDir/shared-vol"]
    end

    PV_Def -. Menyediakan direktori lokal .-> HostDir
    C1 -. Bind mount path kontainer ke host .-> HostDir
    C2 -. Bind mount path kontainer ke host .-> HostDir

Contoh Manifest YAML Produksi Berbagi Volume: #

apiVersion: v1
kind: Pod
metadata:
  name: multi-container-volume-pod
  namespace: production
spec:
  volumes:
  - name: shared-data-vol
    emptyDir: {}                     # Menyediakan ruang kosong di node
  containers:
  - name: web-server
    image: nginx:1.25
    volumeMounts:
    - name: shared-data-vol
      mountPath: /usr/share/nginx/html # Mount ke direktori HTML Nginx
  - name: asset-downloader
    image: alpine:3.18
    command: ["sh", "-c", "wget -O /app/index.html http://example.com/index.html && sleep 3600"]
    volumeMounts:
    - name: shared-data-vol
      mountPath: /app                  # Mount ke direktori download

Di bawah kap, Kubelet mengelola proses ini dengan membuat direktori fisik di filesystem node worker (di bawah /var/lib/kubelet/pods/{pod-uid}/volumes/). Ketika kontainer dijalankan, runtime kontainer melakukan operasi Linux bind mount dari direktori host tersebut ke mount namespace kontainer.


Jenis-Jenis Volume Ephemeral Lokal #

Volume lokal hanya memanfaatkan kapasitas perangkat keras (disk atau RAM) yang melekat secara fisik pada node worker tempat Pod berjalan. Data di dalam volume ini dijamin aman selama Pod tidak berpindah node.

1. emptyDir: Ruang Kosong Sementara #

Seperti yang dibahas sekilas pada artikel sebelumnya, emptyDir adalah direktori kosong yang dibuat saat Pod mulai. Secara default, emptyDir dibuat di atas media penyimpanan node worker (SSD atau HDD root node).

Kita dapat mengubah perilakunya dengan mengonfigurasi medium penyimpanan:

  • medium: Memory: Membuat direktori di dalam RAM (tmpfs). Sangat berguna untuk aplikasi yang membutuhkan caching data tingkat tinggi dengan latensi rendah.
  • sizeLimit: Batasan keras ukuran volume. Kubelet secara berkala akan memantau ukuran folder ini. Jika kontainer menulis data melebihi sizeLimit, Kubelet akan mengusir (evict) Pod tersebut untuk menyelamatkan memori node worker.

2. hostPath: Akses Langsung ke Filesystem Node Worker #

hostPath memetakan file atau direktori dari sistem berkas OS node worker worker langsung ke dalam kontainer. Ini adalah tipe volume yang sangat berkuasa sekaligus sangat berbahaya.

Kasus Penggunaan Valid hostPath di Produksi: #

  • Kontainer DaemonSet monitoring (seperti Prometheus Node Exporter) yang butuh membaca status perangkat keras host di /proc atau /sys.
  • Kontainer pengumpul log (seperti Fluent Bit) yang butuh membaca log sistem operasi node worker di /var/log.
  • Menghubungkan socket runtime kontainer (/var/run/docker.sock atau /run/containerd/containerd.sock) ke kontainer pembangun image (CI/CD runner).

Tipe Valid hostPath: #

DirectoryOrCreate  ➔ Membuat direktori di host jika belum ada.
Directory          ➔ Direktori di host wajib sudah ada (gagal jika tidak).
FileOrCreate       ➔ Membuat file kosong di host jika belum ada.
File               ➔ File di host wajib sudah ada.
Socket             ➔ Unix socket yang sudah ada di host.

[!CAUTION] Jangan pernah menggunakan hostPath untuk database atau aplikasi bisnis stateless biasa. Jika kita mendeploy aplikasi dengan 3 replika Deployment menggunakan hostPath: /data, dan Pod berpindah node worker karena kegagalan hardware, Pod baru di node worker B tidak akan dapat mengakses data yang ditulis oleh Pod lama di node worker A. Selain itu, hostPath melanggar isolasi keamanan karena kontainer jahat dapat memodifikasi sistem operasi host worker secara tidak sah.


Jenis-Jenis Volume Konfigurasi dan Metadata #

Kubernetes memiliki kemampuan unik untuk menyajikan konfigurasi sistem, rahasia, atau data internal kluster sebagai berkas teks biasa di dalam kontainer.

1. configMap dan secret Volumes #

Daripada memasukkan file konfigurasi (seperti nginx.conf atau config.yaml) langsung ke dalam docker image, kita dapat menyimpannya sebagai objek ConfigMap/Secret di Kubernetes, lalu me-mount-nya sebagai volume file.

apiVersion: v1
kind: Pod
metadata:
  name: config-mounted-pod
spec:
  volumes:
  - name: config-vol
    configMap:
      name: web-nginx-config         # Mengambil data dari objek ConfigMap
      defaultMode: 0640              # Set permission file (read-write owner, read group)
      items:
      - key: custom-nginx.conf
        path: nginx.conf             # Disajikan sebagai file nginx.conf di container
  containers:
  - name: web
    image: nginx:1.25
    volumeMounts:
    - name: config-vol
      mountPath: /etc/nginx/conf.d

Mekanisme Hot Update Reload: #

Ketika objek ConfigMap atau Secret diperbarui di Kubernetes API, Kubelet di node worker akan mendeteksi perubahan tersebut. Dalam waktu sekitar 60–120 detik, Kubelet akan memperbarui isi file di dalam kontainer secara otomatis melalui pembaruan tautan simbolis (symlink update). Kontainer tidak perlu di-restart untuk mendapatkan file baru, asalkan aplikasi kita memiliki kemampuan untuk mendeteksi perubahan file (hot reload) seperti Nginx dengan sinyal SIGHUP.

2. downwardAPI Volumes #

Downward API memungkinkan kontainer membaca metadata dirinya sendiri tanpa harus melakukan query API Server Kubernetes. Kubelet akan menulis informasi tersebut ke dalam file teks statis di dalam kontainer.

volumes:
- name: pod-metadata-vol
  downwardAPI:
    items:
    - path: "pod_name"
      fieldRef:
        fieldPath: metadata.name
    - path: "pod_namespace"
      fieldRef:
        fieldPath: metadata.namespace
    - path: "cpu_limit"
      resourceFieldRef:
        containerName: web
        resource: limits.cpu

Ini sangat berguna untuk aplikasi clustering terdistribusi (seperti Elasticsearch) yang butuh mengetahui nama Pod fisiknya sendiri untuk bergabung ke dalam kluster master.

3. projected Volumes #

projected volume bertindak sebagai konsolidator yang menyatukan beberapa sumber volume (seperti Secrets, ConfigMaps, DownwardAPI, dan Token Service Account) ke dalam satu direktori tunggal di dalam kontainer.

volumes:
- name: all-in-one-projected
  projected:
    sources:
    - secret:
        name: tls-certs
    - configMap:
        name: app-properties
    - downwardAPI:
        items:
        - path: "labels"
          fieldRef:
            fieldPath: metadata.labels

Hal ini membuat struktur manifes YAML kita jauh lebih bersih karena kontainer hanya perlu menuliskan satu baris volumeMounts untuk mengakses berbagai jenis data tersebut.


Evolusi Penyimpanan: Dekopling via CSI (Container Storage Interface) #

Pada masa-masa awal Kubernetes, kode untuk berkomunikasi dengan storage provider cloud (seperti AWS EBS, GCP PD, atau VMware disk) ditulis langsung di dalam kode biner inti Kubernetes (in-tree drivers).

  • Masalah: Jika AWS merilis fitur storage baru, tim Kubernetes harus merilis update Kubernetes untuk menyertakan driver tersebut. Begitu pula jika ada bug keamanan pada driver Azure Disk, seluruh kontroler Kubernetes harus di-patch.

Lahirnya Standar CSI (Container Storage Interface) #

Untuk memisahkan siklus hidup pengembangan core Kubernetes dengan driver storage, Kubernetes mengadopsi standar CSI (Container Storage Interface).

[In-Tree Storage (Cara Lama - Deprecated)]
  Kubernetes Core Biner ➔ [ Driver AWS EBS ] ➔ API Cloud

[CSI Architecture (Cara Modern)]
  Kubernetes Core Biner ➔ [ CSI Standard API ] ➔ [ CSI Driver (AWS/GCP/Ceph) ] ➔ API Cloud

CSI adalah spesifikasi standar industri yang memungkinkan vendor storage pihak ketiga untuk mengembangkan driver penyimpanan mereka sendiri secara independen sebagai kontainer terpisah yang di-deploy di dalam kluster.

  • Bagaimana cara kerjanya? Vendor storage men-deploy CSI Controller di control plane dan CSI Node plugin sebagai DaemonSet di setiap node worker.
  • Penggunaan Modern: Kita tidak lagi mendefinisikan detail driver cloud di tingkat Pod spec. Kita cukup menunjuk nama driver melalui StorageClass:
# Contoh memanggil CSI Driver secara langsung (hanya untuk static PV)
volumes:
- name: cloud-disk
  csi:
    driver: ebs.csi.aws.com          # Memanggil driver CSI AWS EBS
    volumeHandle: vol-09218bc109f    # ID volume fisik di cloud

Pola Penataan Volume Tingkat Lanjut #

1. Menggunakan subPath untuk Berbagi Volume #

Terkadang kita hanya ingin me-mount sub-direktori tertentu dari sebuah volume, bukan seluruh isi volume tersebut. subPath memungkinkan kita mengisolasi mount path ke direktori spesifik di dalam volume.

spec:
  containers:
  - name: mysql
    image: mysql:8.0
    volumeMounts:
    - name: database-storage-vol
      mountPath: /var/lib/mysql
      subPath: mysql-data            # Hanya me-mount folder 'mysql-data' dari volume
  - name: pgsql
    image: postgres:15
    volumeMounts:
    - name: database-storage-vol
      mountPath: /var/lib/postgresql/data
      subPath: postgres-data         # Berbagi volume yang sama di folder terpisah
  volumes:
  - name: database-storage-vol
    persistentVolumeClaim:
      claimName: shared-disk-pvc

[!CAUTION] Di lingkungan produksi, berbagi satu disk fisik (RWO) untuk dua database terpisah (MySQL & PostgreSQL) menggunakan subPath sangat tidak disarankan karena akan memicu kemacetan transaksi I/O (I/O bottlenecks) dan memperbesar risiko kehilangan data total jika disk tersebut rusak. Gunakan subPath hanya untuk folder log atau aset non-kritis.

2. Menggunakan subPathExpr untuk Direktori Dinamis #

Jika kita menjalankan banyak replika Pod di bawah StatefulSet dan ingin memisahkan direktori penyimpanan berdasarkan nama Pod secara otomatis, kita dapat menggunakan ekspresi lingkungan melalui subPathExpr:

spec:
  containers:
  - name: log-writer
    image: alpine
    command: ["sh", "-c", "while true; do echo log >> /logs/app.log; sleep 5; done"]
    env:
    - name: POD_NAME
      valueFrom:
        fieldRef:
          fieldPath: metadata.name   # Menyuntikkan nama Pod ke env var
    volumeMounts:
    - name: logs-vol
      mountPath: /logs
      subPathExpr: $(POD_NAME)       # Nama folder otomatis disamakan dengan nama Pod

Anti-Pattern dalam Penggunaan Volume & Solusinya #

Berikut adalah tiga kesalahan konfigurasi volume yang paling sering menimbulkan masalah performa atau celah keamanan di produksi:

Anti-Pattern 1: Menggunakan hostPath untuk Menyimpan Database pada Deployment Biasa #

Mengasumsikan direktori lokal host node worker aman untuk menyimpan data database jangka panjang.

ANTI-PATTERN: hostPath: { path: "/data/db" } untuk Database Postgres
// KITA MELAKUKAN:
- Mendeploy PostgreSQL menggunakan Deployment (replicas: 1) dengan volume hostPath menunjuk ke /data/db.

// KONSEKUENSI DI PRODUKSI:
- Saat node worker-1 tempat Pod berjalan mengalami restart pemeliharaan, scheduler memindahkan Pod ke node worker-2.
- Pod PostgreSQL menyala di node worker-2, mencari folder /data/db di node tersebut. 
- Karena folder tersebut kosong (atau berisi data lama dari pengujian lain), database kita akan menyalakan 
  proses baru dari nol (*fresh database*). Data transaksi produksi yang tersimpan di node worker-1 hilang/stuck di sana.
✓ SOLUSI YANG BENAR:
- Gunakan tipe volume `persistentVolumeClaim` yang didukung oleh StorageClass bertipe network storage 
  atau cloud storage (seperti AWS EBS, GCP PD) agar disk dapat dipasang kembali secara dinamis ke node worker baru.

Anti-Pattern 2: Mengabaikan Pengaturan defaultMode pada Secret Volume #

Membiarkan file enkripsi sensitif atau kunci privat SSH di-mount ke kontainer menggunakan hak akses (permission) default yang terlalu terbuka.

ANTI-PATTERN: Mount Secret SSH Key Tanpa defaultMode
// KITA MELAKUKAN:
- Mendeploy Pod yang memproses pembayaran dengan me-mount Secret berisi SSH private key.
- Kita mengabaikan pengaturan `defaultMode` di volume spec.

// KONSEKUENSI DI PRODUKSI:
- Keamanan Longgar: File kunci privat di-mount dengan permission default `0644` (dapat dibaca oleh siapa saja).
- Penolakan Aplikasi: Banyak library kriptografi modern (seperti OpenSSH) akan menolak memproses kunci 
  privat jika file tersebut memiliki permission yang terlalu longgar, memicu error: 
  `Permissions 0644 for id_rsa are too open. It is required that your private key files are NOT accessible by others`.
✓ SOLUSI YANG BENAR:
- Selalu setel hak akses yang paling ketat (`0400` - read-only untuk owner) pada file kredensial sensitif:
  volumes:
  - name: ssh-key-vol
    secret:
      secretName: payment-ssh-key
      defaultMode: 0400              # Hanya pemilik proses yang boleh membaca

Anti-Pattern 3: Menggunakan subPath pada ConfigMap/Secret Volume, Mematikan Hot Update #

Menggunakan properti subPath untuk menempatkan file konfigurasi tunggal ke dalam folder kontainer yang sudah berisi file lain.

ANTI-PATTERN: volumeMounts: [ { name: "config", mountPath: "/etc/nginx/nginx.conf", subPath: "nginx.conf" } ]
// KITA MELAKUKAN:
- Kita me-mount file `nginx.conf` dari ConfigMap ke direktori `/etc/nginx/` menggunakan `subPath` 
  agar file lain di direktori tersebut tidak tertimpa.

// KONSEKUENSI DI PRODUKSI:
- Matinya Fitur Hot Reload: Ketika kita memperbarui ConfigMap di API Server, Kubelet tidak akan 
  pernah memperbarui isi file `nginx.conf` di dalam kontainer. 
- Mengapa? Karena Linux bind mount untuk file tunggal bersifat statis dan mengunci inode file tersebut 
  sejak startup. Kubelet tidak dapat menukar symlink direktori untuk memperbarui data, 
  memaksa kita melakukan restart Pod secara manual untuk setiap perubahan konfigurasi.
✓ SOLUSI YANG BENAR:
- Jangan gunakan `subPath` jika konfigurasi kita membutuhkan perubahan dinamis (*hot reload*).
- Mount seluruh ConfigMap ke direktori tersendiri, lalu buat symlink dari dalam kontainer, 
  atau gunakan sub-direktori khusus untuk konfigurasi tambahan (seperti `/etc/nginx/conf.d/`).

Ringkasan #

  • Lifecycle Terikat Pod — Volume Kubernetes hidup selama Pod hidup; data kontainer di dalam volume tidak akan hilang meskipun kontainer mengalami restart berulang kali.
  • Berbagi Volume Antar Kontainer — Dapatkan keuntungan sidecar pattern dengan me-mount volume yang sama (emptyDir) ke beberapa kontainer berbeda di dalam satu Pod.
  • hostPath Khusus System Agent — Hindari hostPath untuk aplikasi bisnis; batasi penggunaannya hanya untuk DaemonSet log forwarder atau monitoring node.
  • Injeksi ConfigMap & Secret — Sajikan konfigurasi terpusat sebagai file di kontainer; manfaatkan pembaruan otomatis symlink tanpa perlu restart Pod.
  • Downward API untuk Identitas — Gunakan Downward API untuk menyuntikkan metadata seperti nama Pod dan namespace ke kontainer aplikasi untuk keperluan clustering.
  • Standar Modern CSI — Seluruh interaksi penyimpanan cloud dikelola secara eksternal melalui Container Storage Interface (CSI), membebaskan kluster dari dependensi driver bawaan.
  • Gunakan subPath dengan Hati-Hati — Manfaatkan subPath untuk memisahkan direktori penyimpanan di volume bersama, namun sadari hilangnya kemampuan hot update ConfigMap.
  • Kunci Kredensial via defaultMode — Lindungi kredensial sensitif di volume Secret dengan menyetel hak akses ketat 0400 guna mencegah eksploitasi keamanan.

← Sebelumnya: Ephemeral vs Persistent Storage   Berikutnya: Storage Problem di Distributed Systems →

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