CKAD — Kubernetes Application Developer
Multi-Container Pods — init, Sidecar, Ambassador, Adapter
Goal
You distinguish, pattern by pattern, why several containers belong in one Pod, and write init containers, native sidecars, ambassadors, and adapters as manifests yourself.
Why it matters
Putting two containers in a Pod is not a free decision. The two containers are scheduled together, restarted together, and deleted together. So you put them in one Pod only when both "they must be on the same node" and "they must share a lifecycle" hold. A log collector has to read files the app wrote, so it must be on the same node, and when the app goes away there is nothing to read, so the lifecycle is the same too. A cache server, on the other hand, is shared by many apps, so it gets its own Pod.
The criterion that separates an init container from a sidecar is whether it ends. Work that must end (migration, preparing configuration, waiting for a prerequisite service) belongs in an init container, and work that must not end (shipping logs, a proxy) belongs in a sidecar. In the past you had no choice but to put a sidecar in containers, so startup order was not guaranteed and Jobs would not finish. Now, if you put it in initContainers and give it restartPolicy: Always, it becomes a container that "starts first, keeps running, and ends together with the main container."
An ambassador and an adapter differ in direction. An ambassador handles the outgoing traffic on the app's behalf, and an adapter handles the outgoing data format. Both aim to absorb environment differences without touching the app code.
Steps
- Create the namespace
ckad-multi. - Create a Pod
app-init. An init containerprep(imagebusybox:1.36,command: ["/bin/sh","-c","echo ready > /work/state"]) and a main containerapp(imagenginx:1.27) both mount theemptyDirvolumeworkdirat/work. - Create a Pod
ordered-init. Add two init containers in order,wait-configandmigrate(bothbusybox:1.36,commandis["/bin/sh","-c","true"]), and a main containerappwithnginx:1.27. - Create a Pod
log-shipper. Put a native sidecarloggerininitContainersand give itrestartPolicy: Always(imagebusybox:1.36,command: ["/bin/sh","-c","tail -F /var/log/nginx/access.log"]). The main containerwebusesnginx:1.27. Mount theemptyDirvolumevarlogat/var/log/nginxin both containers. - Create a Pod
ambassador-pod. Give the main containerapp(busybox:1.36,command: ["/bin/sh","-c","sleep 3600"]) the environment variableREDIS_ADDR=localhost:6379, and for the second containerproxy(nginx:1.27) specifycontainerPort: 6379andname: redis-proxy. - Create a Pod
adapter-pod. The main containerapp(busybox:1.36,command: ["/bin/sh","-c","sleep 3600"]) and the adapter containermetrics-adapter(nginx:1.27) mount theemptyDirvolumeapplogboth at/var/log/app. For the adapter, specifycontainerPort: 9113andname: metrics. - Create a Pod
graceful. Include a native sidecardrainer(insideinitContainers,restartPolicy: Always,busybox:1.36,command: ["/bin/sh","-c","sleep 3600"]) and a main containerweb(nginx:1.27), and set the Pod'sterminationGracePeriodSecondsto45. On the main containerweb, attach apreStophook that runs theexeccommand["/bin/sh","-c","sleep 10"].
Notes
- Declare a volume once in
spec.volumesand use the same name in each container'svolumeMounts[].name. If the names differ, the mount does not attach. - You can check the native sidecar field with
kubectl explain pod.spec.initContainers.restartPolicy. - Common mistake 1: writing
restartPolicy: Alwaysonly at the Pod level and not inside the sidecar container. It has to be inside the container to make it a native sidecar. - Common mistake 2: having the adapter/sidecar mount the same volume at a different path. To exchange files, the paths must match as well.
- You can check the order with
kubectl get pod ordered-init -n ckad-multi -o jsonpath='{.spec.initContainers[*].name}'.
Create the lab namespace
Create the namespace ckad-multi.
A single kubectl create namespace line is enough. Create every later resource inside this namespace.
Init container and shared emptyDir
Create a Pod app-init. An init container prep (image busybox:1.36, command: ["/bin/sh","-c","echo ready > /work/state"]) and a main container app (image nginx:1.27) both mount the emptyDir volume workdir at /work.
spec.initContainers is an array at the same level as spec.containers. For two containers to see the same volume, declare it once in spec.volumes and reference the same volume name from each container's volumeMounts.
Execution order of two init containers
Create a Pod ordered-init. Add two init containers in order, wait-config and migrate (both busybox:1.36, command is ["/bin/sh","-c","true"]), and a main container app with nginx:1.27.
Init containers run one at a time in the order written in the array, and the next one starts only after the previous one succeeds. The order is graded, so check the array indexes.
Native sidecar (restartPolicy: Always)
Create a Pod log-shipper. Put a native sidecar logger in initContainers and give it restartPolicy: Always (image busybox:1.36, command: ["/bin/sh","-c","tail -F /var/log/nginx/access.log"]). The main container web uses nginx:1.27. Mount the emptyDir volume varlog at /var/log/nginx in both containers.
Put a native sidecar in the initContainers list and give only that container restartPolicy: Always. A container-level restartPolicy is used only for this purpose.
Ambassador pattern — opening the way outward on the app's behalf
Create a Pod ambassador-pod. Give the main container app (busybox:1.36, command: ["/bin/sh","-c","sleep 3600"]) the environment variable REDIS_ADDR=localhost:6379, and for the second container proxy (nginx:1.27) specify containerPort: 6379 and name: redis-proxy.
The main container always connects only to a fixed port on localhost, and the container next to it knows the real destination. Containers in a Pod share a network namespace, so localhost reaches each other.
Adapter pattern — matching the output format to the outside
Create a Pod adapter-pod. The main container app (busybox:1.36, command: ["/bin/sh","-c","sleep 3600"]) and the adapter container metrics-adapter (nginx:1.27) mount the emptyDir volume applog both at /var/log/app. For the adapter, specify containerPort: 9113 and name: metrics.
The adapter reads the file the main container writes and exposes it in a different format. Both containers must mount the same volume at the same path, and for the adapter you specify the exposed port together with a name.
Termination time budget and preStop (comprehensive)
Create a Pod graceful. Include a native sidecar drainer (inside initContainers, restartPolicy: Always, busybox:1.36, command: ["/bin/sh","-c","sleep 3600"]) and a main container web (nginx:1.27), and set the Pod's terminationGracePeriodSeconds to 45. On the main container web, attach a preStop hook that runs the exec command ["/bin/sh","-c","sleep 10"].
terminationGracePeriodSeconds is a Pod-level field: the number of seconds from SIGTERM to SIGKILL. The preStop hook lives under the container's lifecycle, and the time this hook takes is also consumed from that budget.