Di episode ini kita akan coba bahas multi-container Pod di Kubernetes dan common design pattern. Kita akan mempelajari sidecar, ambassador, adapter pattern, dan bagaimana container share resource dalam Pod.

Catatan
Untuk kalian yang ingin membaca episode sebelumnya, bisa click thumbnail episode 19.1 di bawah ini
Di episode sebelumnya kita sudah belajar tentang Ingress dan Gateway API untuk sophisticated HTTP/HTTPS, gRPC, dan TCP/UDP routing. Selanjutnya di episode 20 kali ini, kita akan coba bahas Multi-Container Pod, sebuah architectural pattern penting dimana multiple container run together dalam single Pod.
Catatan: Disini saya akan menggunakan Kubernetes Cluster yang di install melalui K3s.
Sementara kebanyakan Pod run single container, Kubernetes support running multiple container dalam satu Pod. Container-container ini share network namespace yang sama, storage volume, dan lifecycle, enable powerful design pattern untuk building modular, maintainable application.
Multi-Container Pod adalah Pod yang run dua atau lebih container yang work together sebagai single unit. Semua container di Pod share network namespace yang sama (IP address dan port), bisa communicate via localhost, dan bisa share storage volume.
Bayangkan Pod seperti logical host - sama seperti multiple process bisa run di server yang sama dan communicate via localhost, multiple container dalam Pod bisa communicate efficiently sambil tetap isolated dari container di Pod lain.
Karakteristik kunci Multi-Container Pod:
Multi-container Pod solve beberapa architectural challenge:
Tanpa multi-container Pod, kalian harus:
Container dalam Pod bisa communicate dengan beberapa cara:
Container share network namespace:
apiVersion: v1
kind: Pod
metadata:
name: multi-container-pod
spec:
containers:
- name: web-app
image: nginx:1.25
ports:
- containerPort: 80
- name: log-agent
image: busybox:1.36
command:
- sh
- -c
- while true; do wget -q -O- http://localhost:80; sleep 5; doneContainer log-agent access web-app via localhost:80.
Container bisa share file melalui volume:
apiVersion: v1
kind: Pod
metadata:
name: shared-volume-pod
spec:
containers:
- name: writer
image: busybox:1.36
command:
- sh
- -c
- while true; do date >> /data/log.txt; sleep 5; done
volumeMounts:
- name: shared-data
mountPath: /data
- name: reader
image: busybox:1.36
command:
- sh
- -c
- tail -f /data/log.txt
volumeMounts:
- name: shared-data
mountPath: /data
volumes:
- name: shared-data
emptyDir: {}Kedua container read/write ke volume yang sama.
Share configuration melalui environment:
apiVersion: v1
kind: Pod
metadata:
name: env-pod
spec:
containers:
- name: app
image: nginx:1.25
env:
- name: APP_PORT
value: "80"
- name: sidecar
image: busybox:1.36
env:
- name: APP_PORT
value: "80"Ada tiga common design pattern untuk multi-container Pod:
Multi Container or Sidecar PatternsSidecar container enhance atau extend functionality main container.
Use case:
Contoh: Log Sidecar
apiVersion: v1
kind: Pod
metadata:
name: web-with-logging
spec:
containers:
# Main application container
- name: web-app
image: nginx:1.25
ports:
- containerPort: 80
volumeMounts:
- name: logs
mountPath: /var/log/nginx
# Sidecar container untuk log shipping
- name: log-shipper
image: fluent/fluentd:v1.16
volumeMounts:
- name: logs
mountPath: /var/log/nginx
env:
- name: FLUENTD_CONF
value: fluent.conf
volumes:
- name: logs
emptyDir: {}Log-shipper sidecar read log dari shared volume dan ship ke central logging system.
Contoh: Metrics Sidecar
apiVersion: v1
kind: Pod
metadata:
name: app-with-metrics
spec:
containers:
# Main application
- name: application
image: myapp:latest
ports:
- containerPort: 8080
# Metrics exporter sidecar
- name: metrics-exporter
image: prom/nginx-exporter:latest
ports:
- containerPort: 9113
args:
- -nginx.scrape-uri=http://localhost:8080/metricsAmbassador container proxy network connection untuk main container.
Use case:
Contoh: Database Ambassador
apiVersion: v1
kind: Pod
metadata:
name: app-with-db-ambassador
spec:
containers:
# Main application
- name: application
image: myapp:latest
env:
- name: DATABASE_HOST
value: "localhost"
- name: DATABASE_PORT
value: "5432"
# Ambassador untuk database connection
- name: db-ambassador
image: haproxy:2.8
ports:
- containerPort: 5432
volumeMounts:
- name: config
mountPath: /usr/local/etc/haproxy
volumes:
- name: config
configMap:
name: haproxy-configApplication connect ke localhost:5432, dan ambassador proxy ke actual database dengan connection pooling dan retry logic.
Contoh: Service Mesh Ambassador
apiVersion: v1
kind: Pod
metadata:
name: app-with-proxy
spec:
containers:
# Main application
- name: application
image: myapp:latest
ports:
- containerPort: 8080
# Envoy proxy ambassador
- name: envoy-proxy
image: envoyproxy/envoy:v1.28
ports:
- containerPort: 9901
volumeMounts:
- name: envoy-config
mountPath: /etc/envoy
volumes:
- name: envoy-config
configMap:
name: envoy-configurationAdapter container transform output main container untuk match external requirement.
Use case:
Contoh: Log Adapter
apiVersion: v1
kind: Pod
metadata:
name: app-with-log-adapter
spec:
containers:
# Main application (write custom log format)
- name: application
image: legacy-app:latest
volumeMounts:
- name: logs
mountPath: /var/log/app
# Adapter untuk convert log ke standard format
- name: log-adapter
image: log-transformer:latest
volumeMounts:
- name: logs
mountPath: /var/log/app
- name: transformed-logs
mountPath: /var/log/transformed
command:
- sh
- -c
- |
while true; do
if [ -f /var/log/app/app.log ]; then
tail -f /var/log/app/app.log | \
awk '{print "{\"timestamp\":\""$1"\",\"level\":\""$2"\",\"message\":\""$3"\"}"}' \
> /var/log/transformed/app.json
fi
sleep 1
done
volumes:
- name: logs
emptyDir: {}
- name: transformed-logs
emptyDir: {}Contoh: Metrics Adapter
apiVersion: v1
kind: Pod
metadata:
name: app-with-metrics-adapter
spec:
containers:
# Application expose custom metrics
- name: application
image: myapp:latest
ports:
- containerPort: 8080
# Adapter untuk convert ke Prometheus format
- name: metrics-adapter
image: metrics-converter:latest
ports:
- containerPort: 9090
env:
- name: SOURCE_METRICS_URL
value: "http://localhost:8080/stats"
- name: TARGET_FORMAT
value: "prometheus"Init container run sebelum main container dan harus complete successfully.
apiVersion: v1
kind: Pod
metadata:
name: pod-with-init
spec:
initContainers:
- name: init-setup
image: busybox:1.36
command:
- sh
- -c
- |
echo "Initializing..."
sleep 5
echo "Setup complete" > /work-dir/ready.txt
volumeMounts:
- name: workdir
mountPath: /work-dir
containers:
- name: main-app
image: nginx:1.25
volumeMounts:
- name: workdir
mountPath: /work-dir
volumes:
- name: workdir
emptyDir: {}Init container run sequentially:
apiVersion: v1
kind: Pod
metadata:
name: multi-init-pod
spec:
initContainers:
# First init container
- name: download-config
image: curlimages/curl:latest
command:
- sh
- -c
- curl -o /config/app.conf https://config-server/app.conf
volumeMounts:
- name: config
mountPath: /config
# Second init container (run setelah first complete)
- name: validate-config
image: busybox:1.36
command:
- sh
- -c
- |
if [ -f /config/app.conf ]; then
echo "Config valid"
else
echo "Config missing" && exit 1
fi
volumeMounts:
- name: config
mountPath: /config
containers:
- name: application
image: myapp:latest
volumeMounts:
- name: config
mountPath: /etc/app
volumes:
- name: config
emptyDir: {}apiVersion: v1
kind: Pod
metadata:
name: web-app-complete
labels:
app: web
spec:
containers:
# Main web application
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
volumeMounts:
- name: logs
mountPath: /var/log/nginx
- name: html
mountPath: /usr/share/nginx/html
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
# Log shipping sidecar
- name: log-shipper
image: fluent/fluentd:v1.16
volumeMounts:
- name: logs
mountPath: /var/log/nginx
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "100m"
# Metrics exporter sidecar
- name: nginx-exporter
image: nginx/nginx-prometheus-exporter:latest
ports:
- containerPort: 9113
args:
- -nginx.scrape-uri=http://localhost:80/stub_status
resources:
requests:
memory: "32Mi"
cpu: "25m"
limits:
memory: "64Mi"
cpu: "50m"
volumes:
- name: logs
emptyDir: {}
- name: html
emptyDir: {}apiVersion: v1
kind: Pod
metadata:
name: app-with-db-proxy
spec:
containers:
# Main application
- name: application
image: myapp:latest
env:
- name: DB_HOST
value: "127.0.0.1"
- name: DB_PORT
value: "5432"
ports:
- containerPort: 8080
# Cloud SQL Proxy sidecar
- name: cloud-sql-proxy
image: gcr.io/cloudsql-docker/gce-proxy:latest
command:
- /cloud_sql_proxy
- -instances=project:region:instance=tcp:5432
securityContext:
runAsNonRoot: trueapiVersion: v1
kind: Pod
metadata:
name: web-with-git-sync
spec:
initContainers:
# Clone repository initially
- name: git-clone
image: alpine/git:latest
command:
- git
- clone
- https://github.com/user/repo.git
- /git
volumeMounts:
- name: git-repo
mountPath: /git
containers:
# Web server serving git content
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
volumeMounts:
- name: git-repo
mountPath: /usr/share/nginx/html
# Git sync sidecar
- name: git-sync
image: k8s.gcr.io/git-sync:v3.6.3
env:
- name: GIT_SYNC_REPO
value: "https://github.com/user/repo.git"
- name: GIT_SYNC_DEST
value: "repo"
- name: GIT_SYNC_WAIT
value: "60"
volumeMounts:
- name: git-repo
mountPath: /tmp/git
volumes:
- name: git-repo
emptyDir: {}Memahami container startup order dan dependency:
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-pod
spec:
containers:
- name: main-app
image: nginx:1.25
lifecycle:
postStart:
exec:
command:
- sh
- -c
- echo "Container started" > /usr/share/message
preStop:
exec:
command:
- sh
- -c
- nginx -s quit; sleep 10
- name: sidecar
image: busybox:1.36
command:
- sh
- -c
- while true; do sleep 3600; doneSet resource untuk setiap container:
apiVersion: v1
kind: Pod
metadata:
name: resource-managed-pod
spec:
containers:
- name: main-app
image: myapp:latest
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
- name: sidecar
image: sidecar:latest
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"Total Pod resource = sum dari semua container resource.
Problem: Multiple container trying gunakan port yang sama.
Solusi: Gunakan different port untuk setiap container:
containers:
- name: app1
ports:
- containerPort: 8080
- name: app2
ports:
- containerPort: 8081 # Different portProblem: Container tidak bisa access shared data.
Solusi: Mount volume yang sama di kedua container:
volumeMounts:
- name: shared-data
mountPath: /dataProblem: Container terlalu dependent on each other.
Solusi: Gunakan multi-container Pod hanya ketika container truly need co-located.
Problem: Satu container consuming semua resource.
Solusi: Set resource limit untuk setiap container.
Problem: Hard troubleshoot multi-container issue.
Solusi: Check log untuk setiap container:
sudo kubectl logs <pod-name> -c <container-name>Hanya ketika container harus co-located:
# Bagus: Tightly coupled (app + log shipper)
# Buruk: Loosely coupled (frontend + backend)Selalu define resource untuk setiap container:
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"Separate initialization dari runtime:
initContainers:
- name: setup
# Setup task
containers:
- name: app
# Main applicationAdd probe untuk setiap container:
livenessProbe:
httpGet:
path: /health
port: 8080
readinessProbe:
httpGet:
path: /ready
port: 8080Pilih descriptive name:
# Bagus
- name: web-server
- name: log-shipper
- name: metrics-exporter
# Hindari
- name: container1
- name: sidecar
- name: c2sudo kubectl get pod <pod-name> -o jsonpath='{.spec.containers[*].name}'sudo kubectl logs <pod-name> -c <container-name>sudo kubectl exec -it <pod-name> -c <container-name> -- shsudo kubectl describe pod <pod-name>Show semua container, init container, dan state mereka.
Pada episode 20 ini, kita telah membahas Multi-Container Pod di Kubernetes. Kita sudah belajar tentang common design pattern, container communication, dan best practice untuk building modular application.
Key takeaway:
Multi-container Pod enable powerful architectural pattern di Kubernetes. Dengan memahami pattern ini, kalian bisa build modular, maintainable application dengan clear separation of concern sambil maintain tight integration dimana needed.
Bagaimana, makin jelas kan tentang Multi-Container Pod di Kubernetes? Jadi, pastikan tetap semangat belajar dan nantikan episode selanjutnya!