Solutions: Tasks 9–15
SummaryComplete solutions for tasks 9-15: multi-container sidecar Pod,...
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
emptyDirvolume shared between containers. Both containers mount the same volume at the same or different paths. emptyDirvolumes are created when the Pod starts and deleted when the Pod is removed. They exist for the lifetime of the Pod.- The
READYcolumn shows2/2— both containers must be running. If you see1/2, one container crashed. Check logs for both containers with-c <container-name>. - When generating the scaffold imperatively,
kubectl runcreates 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.txtcreates a Secret with keydb-credentials.txtand value as the file content. Without thekey=prefix, the key defaults to the filename.readOnly: trueon 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 forgenericsecrets). 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
httpGetin this example, but they can independently usehttpGet,tcpSocket, orexec— mixing probe types within the same Pod is valid. - Setting
initialDelaySeconds: 0on 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--setvalues 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 1rolls 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 installfails because the release already exists, usehelm upgrade --installto 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
nodeSelectoris the straightforward way to constrain a Pod to nodes with specific labels. It goes underspec, at the same level ascontainers.tolerationsallow a Pod to be scheduled on nodes with matching taints. Without the toleration, the taintdedicated=special:NoScheduleprevents scheduling.- The
operator: Equalmeans the key, value, and effect must all match exactly. The alternativeoperator: Existsmatches any value for the given key and effect. - Both
nodeSelectorandtolerationsare required in this task.nodeSelectoralone would fail because the node is tainted.tolerationsalone 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? Usek 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: 5means the Job creates 5 Pods total and considers itself complete when all 5 succeed.parallelism: 2means at most 2 Pods run at any given time. Kubernetes starts 2 Pods, waits for one to complete, then starts the next.backoffLimit: 4allows up to 4 failures before the Job is marked as failed. Each Pod failure counts toward this limit.activeDeadlineSeconds: 120sets a hard deadline. If the Job has not completed within 120 seconds, Kubernetes terminates all running Pods and marks the Job as failed.restartPolicy: Neveris required for Jobs. WithNever, failed Pods are not restarted — new Pods are created instead. WithOnFailure, the same Pod is restarted in place. Both are valid for Jobs;Alwaysis 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
CrashLoopBackOffmeans 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 isAlways(the default for Pods), Kubernetes restarts it, causingCrashLoopBackOff. Fix by appending&& sleep 3600or 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 editthe 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 calledsettings.confinside 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.