Skip to main content
mastering ckad certified kubernetes application developer

Solutions: Tasks 9–15

13 min read Chapter 81 of 87
Summary

Complete solutions for tasks 9-15: multi-container sidecar Pod,...

Complete solutions for tasks 9-15: multi-container sidecar Pod, Secret from file, three probe types, Helm install/upgrade/rollback, nodeSelector with toleration, Job with completions and parallelism, and debugging CrashLoopBackOff.

Solutions: Tasks 9–15

Solution: Task 9 — Multi-Container Pod with Sidecar (7%)

Imperative Approach

kubectl config set-context --current --namespace=app-team1

Generate a single-container Pod scaffold:

k run app-with-sidecar --image=busybox:1.36 \
  $do --command -- sh -c "while true; do echo \$(date) - Application log entry >> /var/log/app/app.log; sleep 5; done" > app-sidecar.yaml

Edit the YAML to add the second container and the shared volume.

Final YAML

apiVersion: v1
kind: Pod
metadata:
  name: app-with-sidecar
  namespace: app-team1
spec:
  containers:
  - name: app
    image: busybox:1.36
    command:
    - sh
    - -c
    - "while true; do echo $(date) - Application log entry >> /var/log/app/app.log; sleep 5; done"
    volumeMounts:
    - name: log-volume
      mountPath: /var/log/app
  - name: log-sidecar
    image: busybox:1.36
    command:
    - sh
    - -c
    - "tail -f /var/log/app/app.log"
    volumeMounts:
    - name: log-volume
      mountPath: /var/log/app
  volumes:
  - name: log-volume
    emptyDir: {}

Apply:

k apply -f app-sidecar.yaml

Verification

k get pod app-with-sidecar -n app-team1
NAME               READY   STATUS    RESTARTS   AGE
app-with-sidecar   2/2     Running   0          15s
k logs app-with-sidecar -c log-sidecar -n app-team1
Sat Mar  1 10:00:05 UTC 2026 - Application log entry
Sat Mar  1 10:00:10 UTC 2026 - Application log entry
Sat Mar  1 10:00:15 UTC 2026 - Application log entry
k exec app-with-sidecar -c app -n app-team1 -- cat /var/log/app/app.log

This confirms the main container is writing to the shared volume and the sidecar is reading from it.

Key Points

  • The sidecar pattern uses an emptyDir volume shared between containers. Both containers mount the same volume at the same or different paths.
  • emptyDir volumes are created when the Pod starts and deleted when the Pod is removed. They exist for the lifetime of the Pod.
  • The READY column shows 2/2 — both containers must be running. If you see 1/2, one container crashed. Check logs for both containers with -c <container-name>.
  • When generating the scaffold imperatively, kubectl run creates a single-container Pod. You must add the second container manually in the YAML.
  • Container names must be unique within a Pod. Using the same name for both containers causes a validation error.
  • Time target: 5–6 minutes.

Solution: Task 10 — Secret from File (4%)

Imperative Approach

kubectl config set-context --current --namespace=app-team1

Create the credentials file:

cat <<EOF > /tmp/db-credentials.txt
DB_HOST=mysql.database.svc
DB_PORT=3306
DB_USER=app_user
DB_PASS=Kub3rn3t3s!
EOF

Create the Secret from the file:

k create secret generic db-secret --from-file=db-credentials.txt=/tmp/db-credentials.txt -n app-team1

Generate the Pod scaffold:

k run db-client --image=busybox:1.36 \
  $do --command -- sh -c "cat /etc/db/db-credentials.txt && sleep 3600" > db-client.yaml

Edit to add the Secret volume mount:

Final Pod YAML

apiVersion: v1
kind: Pod
metadata:
  name: db-client
  namespace: app-team1
spec:
  containers:
  - name: db-client
    image: busybox:1.36
    command:
    - sh
    - -c
    - "cat /etc/db/db-credentials.txt && sleep 3600"
    volumeMounts:
    - name: db-secret-vol
      mountPath: /etc/db
      readOnly: true
  volumes:
  - name: db-secret-vol
    secret:
      secretName: db-secret

Apply:

k apply -f db-client.yaml

Verification

k get secret db-secret -n app-team1
NAME        TYPE     DATA   AGE
db-secret   Opaque   1      30s
k exec db-client -n app-team1 -- cat /etc/db/db-credentials.txt
DB_HOST=mysql.database.svc
DB_PORT=3306
DB_USER=app_user
DB_PASS=Kub3rn3t3s!

Key Points

  • --from-file=db-credentials.txt=/tmp/db-credentials.txt creates a Secret with key db-credentials.txt and value as the file content. Without the key= prefix, the key defaults to the filename.
  • readOnly: true on the volume mount is explicitly required by the task. While Secret volumes are read-only by default, specifying it ensures clarity and matches the requirement.
  • The Secret is of type Opaque (the default for generic secrets). The data is base64-encoded in the Secret object but appears as plaintext when mounted as a file.
  • Time target: 2–3 minutes.

Solution: Task 11 — Pod with All Three Probes (7%)

Imperative Approach

kubectl config set-context --current --namespace=monitoring

Generate the scaffold:

k run probed-app --image=nginx:1.25 --port=80 $do > probed-app.yaml

Edit to add all three probes:

Final YAML

apiVersion: v1
kind: Pod
metadata:
  name: probed-app
  namespace: monitoring
spec:
  containers:
  - name: probed-app
    image: nginx:1.25
    ports:
    - containerPort: 80
    startupProbe:
      httpGet:
        path: /
        port: 80
      failureThreshold: 30
      periodSeconds: 10
    livenessProbe:
      httpGet:
        path: /
        port: 80
      initialDelaySeconds: 0
      periodSeconds: 10
      failureThreshold: 3
    readinessProbe:
      httpGet:
        path: /
        port: 80
      initialDelaySeconds: 0
      periodSeconds: 5
      failureThreshold: 3

Apply:

k apply -f probed-app.yaml

Verification

k get pod probed-app -n monitoring
NAME         READY   STATUS    RESTARTS   AGE
probed-app   1/1     Running   0          20s
k describe pod probed-app -n monitoring | grep -A4 "Startup\|Liveness\|Readiness"
    Startup:        http-get http://:80/ delay=0s timeout=1s period=10s #success=1 #failure=30
    Liveness:       http-get http://:80/ delay=0s timeout=1s period=10s #success=1 #failure=3
    Readiness:      http-get http://:80/ delay=0s timeout=1s period=5s #success=1 #failure=3

Key Points

  • The startup probe gives the container up to 300 seconds to start (30 failures × 10 seconds). During this window, the liveness and readiness probes are disabled. This prevents the liveness probe from killing a slow-starting container.
  • Once the startup probe succeeds, it never runs again. The liveness and readiness probes take over from that point.
  • All three probes use httpGet in this example, but they can independently use httpGet, tcpSocket, or exec — mixing probe types within the same Pod is valid.
  • Setting initialDelaySeconds: 0 on the liveness and readiness probes means they start checking immediately after the startup probe succeeds. Since the startup probe already confirmed the container is up, no additional delay is needed.
  • Time target: 4–5 minutes.

Solution: Task 12 — Helm Chart Management (7%)

Step 1: Add Repository

helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
"bitnami" has been added to your repositories
...Successfully got an update from the "bitnami" chart repository

Step 2: Install

helm install web-release bitnami/nginx \
  --namespace helm-ns \
  --set replicaCount=2 \
  --set service.type=ClusterIP
NAME: web-release
LAST DEPLOYED: ...
NAMESPACE: helm-ns
STATUS: deployed
REVISION: 1

Step 3: Verify Installation

helm list -n helm-ns
NAME          NAMESPACE  REVISION  ...  STATUS    CHART         APP VERSION
web-release   helm-ns    1              deployed  nginx-x.x.x  x.x.x
k get pods -n helm-ns
NAME                                    READY   STATUS    RESTARTS   AGE
web-release-nginx-xxxx-xxxxx            1/1     Running   0          30s
web-release-nginx-xxxx-yyyyy            1/1     Running   0          30s

Two Pods running — matches replicaCount=2.

Step 4: Upgrade

helm upgrade web-release bitnami/nginx \
  --namespace helm-ns \
  --set replicaCount=4 \
  --set service.type=ClusterIP
Release "web-release" has been upgraded. Happy Helming!
REVISION: 2

Step 5: Verify Upgrade

k get pods -n helm-ns

Four Pods should be running.

Step 6: Rollback

helm rollback web-release 1 -n helm-ns
Rollback was a success! Happy Helming!

Step 7: Verify Rollback

helm list -n helm-ns
NAME          NAMESPACE  REVISION  ...  STATUS    CHART         APP VERSION
web-release   helm-ns    3              deployed  nginx-x.x.x  x.x.x

Revision is 3 (install=1, upgrade=2, rollback=3). A rollback creates a new revision.

k get pods -n helm-ns

Two Pods running — the original replica count is restored.

Key Points

  • When upgrading with helm upgrade, you must re-specify all --set values you want to keep. Values not specified revert to chart defaults. To preserve previous values, use --reuse-values:
    helm upgrade web-release bitnami/nginx -n helm-ns --set replicaCount=4 --reuse-values
  • helm rollback web-release 1 rolls back to revision 1 but creates revision 3. The revision number always increments.
  • Helm operations are namespace-scoped. Always include -n <namespace> or --namespace <namespace>.
  • If helm install fails because the release already exists, use helm upgrade --install to perform an install-or-upgrade operation.
  • Time target: 5–7 minutes.

Solution: Task 13 — Node Scheduling with nodeSelector and Toleration (7%)

Imperative Approach

kubectl config set-context --current --namespace=scheduling

Generate the scaffold:

k run ssd-worker --image=nginx:1.25 $do > ssd-worker.yaml

Edit to add nodeSelector and tolerations:

Final YAML

apiVersion: v1
kind: Pod
metadata:
  name: ssd-worker
  namespace: scheduling
spec:
  nodeSelector:
    disktype: ssd
  tolerations:
  - key: dedicated
    value: special
    effect: NoSchedule
    operator: Equal
  containers:
  - name: ssd-worker
    image: nginx:1.25

Apply:

k apply -f ssd-worker.yaml

Verification

k get pod ssd-worker -n scheduling
NAME         READY   STATUS    RESTARTS   AGE
ssd-worker   1/1     Running   0          10s
k get pod ssd-worker -n scheduling -o jsonpath='{.spec.nodeSelector}'
{"disktype":"ssd"}
k describe pod ssd-worker -n scheduling | grep -A3 Tolerations
Tolerations:     dedicated=special:NoSchedule
                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                 node.kubernetes.io/unreachable:NoExecute op=Exists for 300s

The first toleration is the one you created. The other two are default tolerations added by Kubernetes.

k get pod ssd-worker -n scheduling -o jsonpath='{.spec.nodeName}'

This confirms the Pod was scheduled on the node with the disktype=ssd label.

Key Points

  • nodeSelector is the straightforward way to constrain a Pod to nodes with specific labels. It goes under spec, at the same level as containers.
  • tolerations allow a Pod to be scheduled on nodes with matching taints. Without the toleration, the taint dedicated=special:NoSchedule prevents scheduling.
  • The operator: Equal means the key, value, and effect must all match exactly. The alternative operator: Exists matches any value for the given key and effect.
  • Both nodeSelector and tolerations are required in this task. nodeSelector alone would fail because the node is tainted. tolerations alone would not guarantee scheduling on the labeled node — they only permit it.
  • If the Pod stays in Pending, check: Does the node have the label? Does the taint match the toleration exactly? Use k describe node <name> to inspect labels and taints.
  • Time target: 3–4 minutes.

Solution: Task 14 — Job with Completions and Parallelism (7%)

Imperative Approach

kubectl config set-context --current --namespace=batch-ns

Generate the scaffold:

k create job data-processor --image=busybox:1.36 \
  $do -- sh -c "echo Processing batch item \$RANDOM && sleep 5" > data-processor.yaml

Edit to add completions, parallelism, backoff limit, active deadline, and restart policy:

Final YAML

apiVersion: batch/v1
kind: Job
metadata:
  name: data-processor
  namespace: batch-ns
spec:
  completions: 5
  parallelism: 2
  backoffLimit: 4
  activeDeadlineSeconds: 120
  template:
    spec:
      restartPolicy: Never
      containers:
      - name: data-processor
        image: busybox:1.36
        command:
        - sh
        - -c
        - "echo Processing batch item $RANDOM && sleep 5"

Apply:

k apply -f data-processor.yaml

Verification

Watch the Job progress:

k get job data-processor -n batch-ns -w
NAME             COMPLETIONS   DURATION   AGE
data-processor   0/5           5s         5s
data-processor   1/5           10s        10s
data-processor   2/5           11s        11s
data-processor   3/5           16s        16s
data-processor   4/5           17s        17s
data-processor   5/5           22s        22s

Notice the pattern: two completions arrive close together (parallel execution), then the next pair starts. This confirms parallelism=2.

k get pods -n batch-ns -l job-name=data-processor
NAME                     READY   STATUS      RESTARTS   AGE
data-processor-abc12     0/1     Completed   0          30s
data-processor-def34     0/1     Completed   0          30s
data-processor-ghi56     0/1     Completed   0          25s
data-processor-jkl78     0/1     Completed   0          25s
data-processor-mno90     0/1     Completed   0          20s

Five Completed Pods. At no point were more than 2 running simultaneously.

k get job data-processor -n batch-ns -o jsonpath='{.spec.completions} {.spec.parallelism} {.spec.backoffLimit}'
5 2 4

Key Points

  • completions: 5 means the Job creates 5 Pods total and considers itself complete when all 5 succeed.
  • parallelism: 2 means at most 2 Pods run at any given time. Kubernetes starts 2 Pods, waits for one to complete, then starts the next.
  • backoffLimit: 4 allows up to 4 failures before the Job is marked as failed. Each Pod failure counts toward this limit.
  • activeDeadlineSeconds: 120 sets a hard deadline. If the Job has not completed within 120 seconds, Kubernetes terminates all running Pods and marks the Job as failed.
  • restartPolicy: Never is required for Jobs. With Never, failed Pods are not restarted — new Pods are created instead. With OnFailure, the same Pod is restarted in place. Both are valid for Jobs; Always is not.
  • Time target: 4–5 minutes.

Solution: Task 15 — Debug CrashLoopBackOff (7%)

Investigation

kubectl config set-context --current --namespace=app-team1
k get pod crash-app -n app-team1
NAME        READY   STATUS                  RESTARTS   AGE
crash-app   0/1     CreateContainerError    0          10s

or, after a few moments:

NAME        READY   STATUS             RESTARTS   AGE
crash-app   0/1     CrashLoopBackOff   3          60s

Check the events:

k describe pod crash-app -n app-team1 | tail -15
Events:
  Type     Reason       Age    From               Message
  ----     ------       ----   ----               -------
  Warning  FailedMount  10s    kubelet            MountVolume.SetUp failed for volume "config-vol":
                                                   configmap "app-settings" not found

The root cause is clear: the ConfigMap app-settings does not exist. The Pod cannot mount a volume from a missing ConfigMap.

Additionally, even after creating the ConfigMap, the command cat /config/settings.conf will exit immediately, causing the container to complete and enter CrashLoopBackOff. The command needs modification to keep the container running.

Fix

Step 1: Create the missing ConfigMap:

k create configmap app-settings -n app-team1 \
  --from-literal=settings.conf="log_level=info
max_connections=100
timeout=30"

Alternatively, create a file and use --from-file:

cat <<EOF > /tmp/settings.conf
log_level=info
max_connections=100
timeout=30
EOF

k create configmap app-settings -n app-team1 --from-file=settings.conf=/tmp/settings.conf

Step 2: Delete the broken Pod:

k delete pod crash-app -n app-team1

Step 3: Recreate with a fixed command:

apiVersion: v1
kind: Pod
metadata:
  name: crash-app
  namespace: app-team1
spec:
  containers:
  - name: app
    image: busybox:1.36
    command:
    - sh
    - -c
    - "cat /config/settings.conf && sleep 3600"
    volumeMounts:
    - name: config-vol
      mountPath: /config
  volumes:
  - name: config-vol
    configMap:
      name: app-settings

Apply:

k apply -f crash-app.yaml

Verification

k get pod crash-app -n app-team1
NAME        READY   STATUS    RESTARTS   AGE
crash-app   1/1     Running   0          10s
k logs crash-app -n app-team1
log_level=info
max_connections=100
timeout=30

Key Points

  • CrashLoopBackOff means the container starts, exits, and Kubernetes keeps restarting it with exponential backoff. The root cause can be: wrong command, missing dependencies, missing volumes, or application errors.
  • When debugging, always check events first (k describe pod), then logs (k logs). Events reveal infrastructure problems (missing ConfigMaps, unschedulable, image pull failures). Logs reveal application problems.
  • A container that runs a command that exits (like cat) will complete immediately. If the restart policy is Always (the default for Pods), Kubernetes restarts it, causing CrashLoopBackOff. Fix by appending && sleep 3600 or using a long-running process.
  • The Pod must be deleted and recreated because the original command is part of the immutable Pod spec. You cannot kubectl edit the command of a running Pod.
  • When creating a ConfigMap with --from-literal, the key name becomes the filename when mounted as a volume. Using --from-literal=settings.conf="..." creates a file called settings.conf inside the mount path.
  • Time target: 5–6 minutes.

Mock Exam 1 is complete. Calculate your score by adding the weights of all tasks you completed successfully. A passing score is 66% (approximately 10 out of 15 tasks, depending on the weight distribution). If you scored above 80%, your preparation is solid. If you scored below 66%, revisit the chapters covering the topics where you lost points and retake the exam after additional practice.