Pods, init containers and sidecars
How to write a Pod fast, how command and args decide what a container runs, and how init containers and sidecars work beside your app.
Key ideas in this lesson · tap one for a simple explanation
What you will be able to do
- Make a Pod manifest with kubectl run --dry-run=client, then add command, args, env, ports and restartPolicy to it.
- Say what a container will run when you set command, args, or both.
- Build a Pod whose init container prepares a shared volume, and turn a helper container into a native sidecar.
- Read the logs of, and open a shell in, one chosen container of a Pod.
A is the smallest thing Kubernetes runs. It holds one or more that share one network address and can share folders. Every , and in this module ends up as Pods, so you need to write one quickly.
Exam tasks rarely want a plain Pod with one container. They want a second container, a setup step, a shared folder or an exact command. The grader then reads each container's name and image, and also where it sits in the spec. If the task wanted initContainers and you used containers, you score zero, even if the Pod runs.
Start from kubectl run, never from a blank file
Typing from memory is slow and easy to get wrong. Let kubectl run write the first version for you, then edit it.
# A manifest to edit, not a running Pod
kubectl run web --image=nginx:1.27 --port=80 --labels=app=web,tier=front --env=MODE=production --dry-run=client -o yaml > web.yaml
# A one-shot Pod that is not restarted when its command exits
kubectl run once --image=busybox:1.36 --restart=Never -- sh -c 'echo hello; sleep 5'
# Everything after -- becomes args ...
kubectl run box1 --image=busybox:1.36 -- sleep 3600
# ... unless you add --command, then it becomes command
kubectl run box2 --image=busybox:1.36 --command -- sleep 3600kubectl run always makes exactly one Pod. For other objects, use kubectl create deployment, kubectl create job or kubectl create cronjob. It has no --requests or --limits flag either. You add CPU and memory settings in the YAML, as the requests and limits lesson shows.
The generated file is only a start. These are the fields you will most often add by hand:
apiVersion: v1
kind: Pod
metadata:
name: web
namespace: wl-pods
labels:
app: web
spec:
restartPolicy: Always # Always (default) | OnFailure | Never; applies to the whole Pod
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80 # documents the port and lets probes/Services use the name
name: http
env:
- name: MODE
value: production
- name: clock
image: busybox:1.36
command: ["sh", "-c"] # replaces the image ENTRYPOINT
args: ["while true; do date; sleep 10; done"] # replaces the image CMDcommand and args versus ENTRYPOINT and CMD
Every container image has a default program to run. The image's Dockerfile names that program in ENTRYPOINT and its default arguments in CMD. A Pod can replace either one. The names differ, so keep the pairs apart:
So command replaces ENTRYPOINT, and args replaces CMD. Here is what runs in each case:
- busybox has no ENTRYPOINT, and its CMD is
sh. That is whyargsalone happens to work with busybox. - Many app images are different: their ENTRYPOINT is the app itself. Say your Django image has ENTRYPOINT
gunicorn. With onlyargs: ["sleep", "3600"], the container runsgunicorn sleep 3600, which is not what you wanted. - So when a task says the container must run something, set
command. - Both fields are lists. To run a shell one-liner, use
["sh", "-c", "…"].
Init containers
An is a setup step. It runs to the end and exits before your app starts. For example, before nginx starts, an init container can write the web page that nginx will serve.
- Init containers run one at a time, in the order you list them.
- Each one must succeed (exit with code 0) before the next one starts.
- Your app containers start only after the last init container succeeds.
- If one fails, the retries it according to the Pod's
restartPolicy. WithNever, the whole Pod fails instead.
While they run, the STATUS column of kubectl get pods shows their progress. Init:0/2 means none of two has finished yet. Init:Error and Init:CrashLoopBackOff mean one of them keeps failing.
How does an init container hand its work to the app? Through a shared volume. An emptyDir is an empty folder that Kubernetes creates when the Pod starts. Every container in the Pod can mount it, each at its own path, and they all see the same files. It lives as long as the Pod: it survives a container restart, but it is deleted with the Pod.
Init container
runs first, writes index.html, exits
emptyDir volume
a folder the Pod's containers share
nginx container
starts next and serves the file
The init container runs first and exits. Its file stays in the emptyDir, where nginx finds it and serves it.
apiVersion: v1
kind: Pod
metadata:
name: site
namespace: wl-pods
spec:
initContainers:
- name: render
image: busybox:1.36
command: ["sh", "-c", "echo \"built on $(hostname) at $(date)\" > /data/index.html"]
volumeMounts:
- name: html
mountPath: /data
containers:
- name: nginx
image: nginx:1.27
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
emptyDir: {}Native sidecar containers
A is a helper container that runs next to your app for the app's whole life. Examples are a log shipper, a proxy, or a tool that reloads config files.
The old way was to add the helper as a second entry under containers. That causes two problems:
- There is no start order, so the helper may start after your app.
- In a Job, the helper never exits. So the Pod never finishes, and the Job never completes.
A native sidecar fixes both. It is an entry under initContainers with one extra line: restartPolicy: Always. The kubelet then handles it like this:
- It starts the sidecar in init order, but it waits only for the sidecar to start, not to exit.
- It keeps the sidecar running beside your app, and restarts it if it stops.
- It stops the sidecar after your app containers finish.
1. Sidecar starts
listed under initContainers
2. App runs
the sidecar runs beside it
3. App exits
its work is done
4. Sidecar is stopped
so the Pod can finish
This matters most in a Job, because a Job's Pod must finish before the Job can complete.
apiVersion: v1
kind: Pod
metadata:
name: app
namespace: wl-pods
spec:
initContainers:
- name: log-tail
image: busybox:1.36
restartPolicy: Always # this one line makes it a sidecar
command: ["sh", "-c", "touch /var/log/app/app.log; tail -F /var/log/app/app.log"]
volumeMounts:
- name: logs
mountPath: /var/log/app
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "while true; do date >> /var/log/app/app.log; sleep 2; done"]
volumeMounts:
- name: logs
mountPath: /var/log/app
volumes:
- name: logs
emptyDir: {}Task 2 below puts a sidecar inside a Job, an object that runs a Pod until its work is done. Jobs have their own lesson: DaemonSets, Jobs and CronJobs.
Working with one container
In a Pod with several containers, needs to know which one you mean. Give the container's name with -c, for example -c log-tail.
kubectl -n wl-pods get pod app # READY 2/2: the sidecar is counted
kubectl -n wl-pods logs app -c log-tail # one container
kubectl -n wl-pods exec app -c app -- cat /var/log/app/app.log
kubectl -n wl-pods exec -it app -c app -- sh
kubectl -n wl-pods describe pod app | sed -n '/Init Containers/,/Conditions/p'Without -c, kubectl picks a container for you and prints a Defaulted container note. It picks the one named in the Pod annotation kubectl.kubernetes.io/default-container, or else the first entry under containers. Read that note. It is how people end up reading the wrong container's logs.
More ways to read logs, such as the logs of a container that crashed, are in Logs, events and container output streams.
In the official docs· open on exam day too
- Init Containerssearch: init containers
- Sidecar Containerssearch: sidecar containers
- Define a Command and Arguments for a Containersearch: command and arguments
- Communicate Between Containers in the Same Pod Using a Shared Volumesearch: shared volume
Serve a file prepared by an init container
wl-pods create a Pod named web. An init container named prepare (image busybox:1.36) writes the single line prepared by init to a file index.html on an emptyDir volume named content. A container named nginx (image nginx:1.27) mounts the same volume at /usr/share/nginx/html and serves that file on port 80.Cluster:kubectl config use-context kind-cka
- 1
Set up the scenario
curl -fsSL https://codewithmuh.com/labs/cka/t/wl-pods-1/setup | bash - 2
Solve it in your terminal
Use kubectl and the Kubernetes docs, like on exam day. Save any answer files under
/tmp/cka/. - 3
Check your work
curl -fsSL https://codewithmuh.com/labs/cka/t/wl-pods-1/verify | bashGot FAIL? Read which check failed, fix it, run it again. - 4
Clean up (optional)
curl -fsSL https://codewithmuh.com/labs/cka/t/wl-pods-1/cleanup | bash
Let a Job finish despite its log shipper
report in namespace wl-pods never completes. Its main container finishes its work, but the log-shipper container keeps the Pod running. Change the Job so that log-shipper runs as a native sidecar: it starts before main, runs while main works, and is stopped automatically when main exits. Keep both containers' names, images and commands and the shared volume. The Job must reach the Complete condition.Cluster:kubectl config use-context kind-cka
- 1
Set up the scenario
curl -fsSL https://codewithmuh.com/labs/cka/t/wl-pods-2/setup | bash - 2
Solve it in your terminal
Use kubectl and the Kubernetes docs, like on exam day. Save any answer files under
/tmp/cka/. - 3
Check your work
curl -fsSL https://codewithmuh.com/labs/cka/t/wl-pods-2/verify | bashGot FAIL? Read which check failed, fix it, run it again. - 4
Clean up (optional)
curl -fsSL https://codewithmuh.com/labs/cka/t/wl-pods-2/cleanup | bash
Finished this lesson?
0 of 2 tasks passed. Your readiness counts tasks, so run them before you move on.