Single & Multi Container #
Salah satu keputusan arsitektural awal yang paling penting saat memindahkan aplikasi ke Kubernetes adalah menentukan bagaimana kita membungkus kontainer-kontainer kita ke dalam Pod. Secara default, sebagian besar Pod di Kubernetes hanya berisi satu kontainer tunggal (single container Pod), dan ini adalah pola standar yang paling direkomendasikan untuk sebagian besar skenario beban kerja.
Namun, Kubernetes dirancang untuk mendukung penempatan beberapa kontainer secara bersamaan di dalam satu Pod (multi-container Pod). Pola ini sangat berguna jika diimplementasikan dengan pemahaman yang tepat, namun dapat menjadi bencana performa jika disalahgunakan untuk menggabungkan komponen-komponen yang seharusnya terpisah. Memahami batas-batas arsitektural antara model single dan multi-container membantu kita membangun kluster yang skalabel, mudah dipelihara, serta terhindar dari overhead jaringan dan penyimpanan yang tidak perlu.
Prinsip Dasar: Pod Sebagai Unit Deployment Tunggal #
Sebelum melangkah lebih jauh ke dalam implementasi teknis, ada satu prinsip dasar yang harus selalu kita pegang teguh:
Seluruh kontainer di dalam satu Pod adalah satu kesatuan daur hidup (lifecycle) dan deployment.
Artinya, kontainer-kontainer tersebut:
- Selalu dijadwalkan secara bersamaan ke Worker Node yang sama oleh Scheduler.
- Selalu dimulai (startup) dan dihentikan (termination) secara bersamaan sebagai satu kesatuan.
- Tidak dapat di-scale secara independen. Jika kita menskalakan Deployment menjadi 5 replika, Kubernetes akan menduplikasi seluruh isi Pod tersebut (termasuk kontainer utama dan kontainer pembantunya) menjadi 5 pasang.
Berikut adalah pertanyaan panduan untuk membantu kita mengambil keputusan:
Apakah komponen A dan komponen B harus selalu berjalan di server host yang sama?
├── TIDAK ➔ Pisahkan ke Pod dan Deployment yang berbeda (misal: Frontend dan Backend).
└── YA
└── Apakah komponen A dan B harus di-scale bersamaan dan mati bersamaan?
├── TIDAK ➔ Pisahkan ke Pod yang berbeda (hubungkan via Service IP).
└── YA ➔ Valid untuk digabungkan menjadi Multi-Container Pod.
Single Container Pod: Standar Emas Workload Modern #
Menerapkan pola satu Pod, satu kontainer adalah cara paling aman dan efisien dalam microservices. Pola ini mematuhi prinsip tanggung jawab tunggal (single responsibility principle) di level infrastruktur.
Keunggulan utama Single Container Pod meliputi:
- Isolasi Kesalahan (Fault Isolation): Jika kontainer aplikasi kita crash, Kubelet dapat dengan mudah melacak kegagalan tersebut tanpa memengaruhi kontainer lain yang tidak bersalah.
- Skalabilitas Presisi: Kita dapat menskalakan komponen yang sibuk saja (misal: menskalakan API Gateway menjadi 10 replika sementara worker background tetap 2 replika), menghemat alokasi RAM dan CPU kluster secara signifikan.
- Observabilitas Jelas: Metrik CPU, RAM, dan log keluaran (stdout/stderr) murni merepresentasikan satu proses aplikasi, mempermudah debugging dan pembuatan alarm pemantauan.
Sumber Daya yang Berbagi dalam Satu Pod #
Ketika beberapa kontainer diletakkan di dalam satu Pod yang sama, Kubernetes secara otomatis mengonfigurasi sistem operasi Linux agar kontainer-kontainer tersebut dapat berbagi resource secara transparan melalui penggunaan Linux Namespaces.
1. Shared Network Namespace (Komunikasi Localhost) #
Seluruh kontainer di dalam Pod berbagi satu alamat IP yang sama, tabel perutean (routing table) yang sama, serta antarmuka jaringan loopback (lo) yang sama.
- Komunikasi Latensi Rendah: Kontainer A dapat berkomunikasi dengan Kontainer B di dalam Pod yang sama secara langsung melalui protokol TCP/UDP pada alamat IP
localhostatau127.0.0.1. - Konsekuensi Bentrokan Port: Karena berbagi network namespace yang sama, dua kontainer di dalam satu Pod tidak boleh mendengarkan (listen) pada port yang sama. Jika Kontainer A menggunakan port
8080, Kontainer B akan gagal menyala karena terjadi bentrokan Address Already in Use.
Berikut visualisasi komunikasi inter-kontainer di dalam satu network namespace Pod:
sequenceDiagram
participant Client as Klien Luar Kluster
participant PodIP as IP Pod (10.244.1.42)
box rgb(240, 240, 240) Multi-Container Pod
participant Main as Kontainer Utama (Port 8080)
participant Sidecar as Kontainer Sidecar (Port 9090)
end
Client->>PodIP: GET 10.244.1.42:8080
PodIP->>Main: Meneruskan ke Port 8080
Main-->>PodIP: HTTP 200 OK
PodIP-->>Client: HTTP 200 OK
Note over Main, Sidecar: Komunikasi internal via Localhost
Main->>Sidecar: POST localhost:9090 (Kirim Log/Metrik)
Sidecar-->>Main: ACK (Sangat Cepat & Tanpa Latensi Jaringan Fisik)
2. Shared Storage Volumes (Berbagi Berkas) #
Kontainer-kontainer di dalam Pod dapat mengakses direktori penyimpanan yang sama dengan me-mount volume yang sama di dalam spesifikasi manifest. Pola ini sangat berguna untuk memproses berkas secara kolaboratif.
Sebagai contoh, bayangkan sebuah aplikasi web statis Nginx (kontainer utama) yang perlu menyajikan konten yang secara dinamis diunduh dari repositori Git oleh kontainer pembantu (sidecar). Kita dapat menghubungkannya menggunakan volume emptyDir:
apiVersion: v1
kind: Pod
metadata:
name: dynamic-web-pod
spec:
volumes:
- name: web-content
emptyDir:
medium: Memory # Menggunakan RAM disk (tmpfs) agar I/O sangat cepat
containers:
- name: web-server
image: nginx:1.25
volumeMounts:
- name: web-content
mountPath: /usr/share/nginx/html # Nginx membaca file HTML dari sini
readOnly: true
- name: git-sync-sidecar
image: k8s.gcr.io/git-sync/git-sync:v3.6.0
env:
- name: GIT_SYNC_REPO
value: "https://github.com/example/static-assets.git"
- name: GIT_SYNC_DEST
value: "html"
volumeMounts:
- name: web-content
mountPath: /tmp/git # Sidecar menulis berkas hasil clone ke sini
3. Shared IPC & PID Namespace #
Secara default, proses di dalam satu kontainer terisolasi dari proses kontainer lain. Namun, Kubernetes mendukung fitur pembagian PID namespace sehingga satu kontainer dapat mendeteksi dan mengelola proses di kontainer lain dalam satu Pod dengan mengaktifkan flag shareProcessNamespace: true.
Ketika flag ini diaktifkan:
- Proses-proses dari seluruh kontainer di dalam Pod akan saling terlihat saat kita menjalankan perintah
ps axatau melihat direktori/proc. - PID 1 tidak lagi dipegang oleh aplikasi utama kita, melainkan dipegang oleh proses pause container (kontainer infrastruktur minimal Kubernetes).
- Kontainer pembantu dapat mengirimkan sinyal sistem (seperti
SIGTERMatauSIGHUP) ke kontainer utama. Misalnya, kontainer debugger dapat menjalankan perintahkill -HUP 12untuk menyuruh proses Nginx melakukan reload konfigurasi tanpa mematikan kontainer.
apiVersion: v1
kind: Pod
metadata:
name: debug-target-pod
spec:
shareProcessNamespace: true # Mengizinkan interaksi sinyal proses antarkontainer
containers:
- name: app
image: my-app:v1
- name: debugger
image: alpine
command: ["sh", "-c", "sleep 3600"] # debugger dapat mendeteksi PID app via 'ps ax'
Tiga Pola Desain Multi-Container yang Valid #
Komunitas Kubernetes mengakui tiga pola utama (design patterns) di mana penggunaan multi-container Pod dinyatakan valid dan sangat direkomendasikan di lingkungan produksi:
1. Sidecar Pattern (Pola Sespan) #
Pola Sidecar menempatkan kontainer tambahan (sidecar) untuk mendampingi dan meningkatkan fungsionalitas kontainer utama tanpa perlu memodifikasi kode program di kontainer utama tersebut.
- Use Case Nyata: Logging agent, Cloud SQL Proxy (untuk mengamankan koneksi database GCP/AWS), Prometheus Metrics Exporter, dan Service Mesh proxy (Envoy).
spec:
containers:
- name: main-api
image: payment-api:v2.0
- name: log-forwarder
image: fluent-bit:3.0
# Sidecar ini bertugas membaca file log dari volume bersama
# dan mengirimkannya ke Elasticsearch/Loki secara asinkron.
2. Ambassador Pattern (Pola Duta Besar) #
Pola Ambassador menempatkan kontainer tambahan yang bertindak sebagai proxy perwakilan jaringan untuk menangani seluruh koneksi keluar (outbound) dari kontainer utama ke dunia luar.
flowchart LR
subgraph Pod ["Multi-Container Pod"]
MainApp["Kontainer Utama\n(Hanya tahu localhost:5432)"] -->|"Koneksi Lokal"| Ambassador["Kontainer Ambassador\n(pgbouncer)"]
end
subgraph DBCluster ["External Infrastructure"]
Ambassador -->|"Connection Pooling\n& TLS Encryption"| DB1["PostgreSQL Primary"]
Ambassador -->|"Load Balancing"| DB2["PostgreSQL Replica"]
end
- Keuntungan: Kontainer utama cukup menembak alamat
localhost:5432. Urutan koneksi pool, rotasi password database, algoritma failover, dan enkripsi mTLS keluar sepenuhnya ditangani oleh kontainer Ambassador.
3. Adapter Pattern (Pola Adaptor) #
Pola Adapter digunakan ketika kontainer utama menghasilkan data output dengan format yang tidak standar (misal aplikasi legacy dengan format log mentah), dan kontainer Adapter bertugas menstandarisasi output tersebut agar dapat dibaca oleh sistem monitoring terpusat kluster.
Sebagai contoh, bayangkan sebuah aplikasi lama yang menuliskan log audit ke dalam format teks mentah Apache Common Log. Kita dapat menempatkan adapter Fluent Bit untuk mengubah teks mentah tersebut menjadi JSON terstruktur sebelum dikirim ke sistem log terpusat:
apiVersion: v1
kind: Pod
metadata:
name: legacy-app-pod
spec:
volumes:
- name: log-dir
emptyDir: {}
containers:
- name: legacy-app
image: legacy-app:v1.0
volumeMounts:
- name: log-dir
mountPath: /var/log/legacy
- name: log-adapter
image: fluent-bit:3.0
volumeMounts:
- name: log-dir
mountPath: /var/log/input
# Adapter membaca file teks mentah, melakukan regex parsing,
# dan mengeksposnya kembali ke sistem monitoring
Urutan Inisialisasi dan Native Sidecar Lifecycle #
Pada versi Kubernetes lawas (sebelum v1.28), tidak ada jaminan urutan start-up antar kontainer biasa. Hal ini sering kali memicu error startup aplikasi: jika kontainer utama menyala lebih cepat daripada kontainer sidecar proxy jaringan (seperti Istio/Envoy), kontainer utama akan crash karena gagal menghubungi database eksternal saat inisialisasi awal.
Solusi Modern: Native Sidecar (K8s 1.29+) #
Kubernetes memperkenalkan dukungan Native Sidecar dengan memanfaatkan blok initContainers yang ditandai dengan parameter restartPolicy: Always.
Berikut adalah perbandingan daur hidup kontainer pembantu sebelum dan sesudah adanya dukungan Native Sidecar:
| Dimensi Perbandingan | Model Lama (Kontainer Biasa) | Model Baru (Native Sidecar K8s 1.29+) |
|---|---|---|
| Deklarasi Manifest | Di bawah array spec.containers |
Di bawah array spec.initContainers dengan restartPolicy: Always |
| Urutan Startup | Dijalankan paralel tanpa jaminan urutan | Dijalankan paling awal, kontainer utama menunggu sidecar ready |
| Pengecekan Kesehatan | Main container bisa start saat sidecar masih booting | Main container ditahan sampai readiness probe sidecar sukses |
| Siklus Terminiasi | Dimatikan acak (sidecar bisa mati duluan) | Main container dimatikan dulu, sidecar dimatikan paling akhir |
Manifes penerapan Native Sidecar:
spec:
initContainers:
- name: network-proxy
image: envoy:v1.28
restartPolicy: Always # Kunci penanda bahwa kontainer init ini bertindak sebagai Sidecar Abadi
readinessProbe:
httpGet:
path: /ready
port: 15021
containers:
- name: main-api
image: payment-api:v2.0
Anti-Pattern dalam Penggunaan Multi-Container #
Berikut adalah dua kesalahan fatal penempatan kontainer dalam kluster produksi:
Anti-Pattern 1: Menggabungkan Dua Microservice Independen ke dalam Satu Pod #
Mencoba menyatukan dua service bisnis mandiri karena alasan kemudahan komunikasi lokal.
ANTI-PATTERN: Menaruh user-service dan order-service di dalam Satu Pod Spec
// KITA MELAKUKAN:
- Menggabungkan kode program user-service dan order-service ke dalam satu manifest Pod.
// KONSEKUENSI DI PRODUKSI:
- Skalabilitas Buruk: Jika traffic order-service melonjak tajam sedangkan user-service sepi,
kita terpaksa menduplikasi RAM dan CPU untuk kedua service tersebut secara boros.
- Kerusakan Beruntun: Jika user-service mengalami kebocoran memori (memory leak) dan memicu OOMKilled,
Kubelet akan merestart seluruh Pod, yang mengakibatkan downtime tidak perlu pada order-service yang sehat.
✓ SOLUSI YANG BENAR:
- Pisahkan masing-masing service ke dalam Deployment terpisah.
- Gunakan objek `Service` Kubernetes untuk memfasilitasi komunikasi antar service melalui DNS internal kluster:
http://user-service.default.svc.cluster.local
Anti-Pattern 2: Menyatukan Aplikasi Stateless dengan Database Stateful dalam Satu Pod #
Mencoba menghindari overhead perutean jaringan untuk akses database.
ANTI-PATTERN: Menaruh nginx-app dan postgres-database di Satu Pod
// KITA MELAKUKAN:
- Menulis nginx container dan postgres container di satu manifest Pod yang sama.
// KONSEKUENSI DI PRODUKSI:
- Kehilangan Data: Database Postgres membutuhkan persistent volume. Jika kita melakukan scaling Deployment nginx menjadi 3 replika,
Ketiga Pod tersebut akan mencoba memperebutkan dan mengunci volume penyimpanan database yang sama secara bersamaan, memicu corrupt data.
- Siklus Restart Lambat: Nginx web app yang dinamis sering kali dideploy ulang (misal 5 kali sehari).
Tindakan ini memaksa database Postgres ikut mati-hidup berulang kali, merusak performa caching database.
✓ SOLUSI YANG BENAR:
- Tempatkan aplikasi web di dalam objek `Deployment` (stateless).
- Tempatkan database PostgreSQL di dalam objek `StatefulSet` terpisah yang memiliki `volumeClaimTemplates` mandiri.
Ringkasan #
- Prinsip Unit Tunggal — Seluruh kontainer di dalam Pod berbagi daur hidup, penjadwalan node, dan kapasitas scaling yang sama tanpa bisa dipisahkan.
- Gunakan Single Container — Pola satu kontainer per Pod adalah standar terbaik untuk microservices demi menjaga isolasi fault dan presisi alokasi resource.
- Komunikasi Localhost — Kontainer di dalam satu Pod berbagi network namespace yang sama, memfasilitasi komunikasi inter-kontainer berlatensi sangat rendah di
127.0.0.1.- Port Sharing Policy — Waspadai bentrokan port jaringan di dalam satu Pod; dua kontainer tidak boleh menggunakan port TCP/UDP yang sama.
- Pola Desain Valid — Gunakan multi-container hanya untuk tiga pola arsitektur yang valid: Sidecar (sespan pembantu), Ambassador (duta besar proxy), dan Adapter (adaptor format).
- Gunakan Native Sidecar — Manfaatkan fitur
initContainersdenganrestartPolicy: Always(K8s 1.29+) untuk menjamin urutan start-up sidecar sebelum aplikasi utama.- Isolasi Aplikasi vs Database — Jangan pernah menggabungkan aplikasi stateless dan database stateful ke dalam satu Pod guna menghindari konflik penguncian volume penyimpanan.