Pods, init containers and sidecars

The Pod spec fields the exam keeps returning to, how command and args override an image, and the multi-container shapes built on a shared volume: init containers and native sidecars.

Workloads · 15%35 min2 tasks

After this lesson

  • 01You can produce a Pod manifest with kubectl run --dry-run=client and add command, args, env, ports and restartPolicy to it without looking anything up.
  • 02You can predict what a container runs when command and args are combined with an image's ENTRYPOINT and CMD.
  • 03You can build a Pod whose init container prepares a shared volume, and a Pod or Job with a native sidecar declared as an init container with restartPolicy: Always.
  • 04You can read logs from, and open a shell in, one specific container of a multi-container Pod.

Every workload ends up as a Pod, so every other lesson in this module assumes you can write one fast. The exam rarely wants a bare single-container Pod. It wants a second container, an init step, a shared volume or an exact command, and the grader then reads container names, images and where each container sits in the spec. Getting a container into containers when the question wanted initContainers scores zero even if the Pod runs.

Start from kubectl run, never from a blank file

kubectl run patterns worth memorizingbash
# 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 3600

# A throwaway client, deleted when it exits (interactive, fine in the exam terminal)
kubectl run tmp --rm -it --image=busybox:1.36 --restart=Never -- wget -qO- http://web

kubectl run creates exactly one Pod. Old guides mention --generator values that produced Deployments or Jobs; those were removed years ago, and there are no --requests or --limits flags either. Use kubectl create deployment|job|cronjob for controllers, and add resources in the YAML or with kubectl set resources.

The fields you will add by handyaml
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 CMD

command and args versus ENTRYPOINT and CMD

You setThe container runsExample with nginx:1.27
NeitherImage ENTRYPOINT + image CMD/docker-entrypoint.sh nginx -g 'daemon off;'
args onlyImage ENTRYPOINT + your args/docker-entrypoint.sh sleep 3600
command onlyYour command; the image CMD is ignoredsleep 3600
BothYour command + your argssh -c 'nginx -t'

busybox has no ENTRYPOINT (its CMD is sh), so args alone happen to work there. With an image whose ENTRYPOINT is the application binary, args alone become flags to that binary. When a question says the container must run something, set command. Both fields are arrays; a shell pipeline needs ["sh", "-c", "..."].

Init containers

Init containers run one at a time in the order listed, and each must exit 0 before the next starts. The regular containers start only after the last one succeeds. A failed init container is retried according to the Pod's restartPolicy; with Never the Pod fails. While they run, kubectl get pods shows Init:0/2, Init:Error or Init:CrashLoopBackOff. Regular init containers cannot have liveness, readiness or startup probes.

An init container rendering content into a shared emptyDiryaml
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: {}

The volume is the contract between containers. An emptyDir lives as long as the Pod (it survives container restarts, not Pod deletion), and each container may mount it at a different path. The same pattern carries a rendered config file, a downloaded artifact or a Unix socket.

Native sidecar containers

A sidecar is a helper that must live as long as the main container: a log shipper, a proxy, a config reloader. Listed as a second entry under containers it has no start order, and in a Job it keeps the Pod alive forever because it never exits. A native sidecar is an entry under initContainers with restartPolicy: Always. The kubelet starts it in init order but only waits for it to start (or for its startupProbe to pass), keeps it running beside the app containers, restarts it if it dies, and stops it after the main containers finish. It has been on by default since v1.29 and is GA since v1.33.

A log tailer as a native sidecaryaml
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: {}
Working with one container at a timebash
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 logs app --all-containers --prefix
kubectl -n wl-pods logs app -c app --previous       # the instance that crashed last
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 uses the container named in the Pod annotation kubectl.kubernetes.io/default-container, or else the first entry in containers, and prints a Defaulted container note. Read that note; it is how people end up grading the wrong container's logs.

In the docs · the tab you get on exam day

Taskwl-pods-1
Workloads4%~6 min

context · kubectl config use-context kind-cka

Serve a file prepared by an init container

In namespace 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.
  1. 01

    Set up the scenario

    curl -fsSL https://codewithmuh.com/labs/cka/t/wl-pods-1/setup | bash
  2. 02

    Solve it in your terminal

    Work the way the exam makes you: kubectl, the docs, nothing else. Put any answer files under /tmp/cka/.

  3. 03

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/wl-pods-1/verify | bash
    Counts toward your readiness score.
  4. 04

    Clean up

    curl -fsSL https://codewithmuh.com/labs/cka/t/wl-pods-1/cleanup | bash
Taskwl-pods-2
Workloads6%~8 min

context · kubectl config use-context kind-cka

Let a Job finish despite its log shipper

The Job 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.
  1. 01

    Set up the scenario

    curl -fsSL https://codewithmuh.com/labs/cka/t/wl-pods-2/setup | bash
  2. 02

    Solve it in your terminal

    Work the way the exam makes you: kubectl, the docs, nothing else. Put any answer files under /tmp/cka/.

  3. 03

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/wl-pods-2/verify | bash
    Counts toward your readiness score.
  4. 04

    Clean up

    curl -fsSL https://codewithmuh.com/labs/cka/t/wl-pods-2/cleanup | bash

Finished reading?

0 of 2 tasks passed. Readiness counts tasks, so run them before you move on.