Solutions: Tasks 1–10
SummaryFull solutions for Tasks 1–10 of Mock Exam...
Full solutions for Tasks 1–10 of Mock Exam...
Full solutions for Tasks 1–10 of Mock Exam 2: namespace ResourceQuota creation and testing, Pod SecurityContext with writable volume workarounds, debugging ImagePullBackOff and readiness probe misconfiguration, canary deployment with shared Service selector, CronJob creation with manual Job extraction, combined Secret env and ConfigMap volume consumption, deny-all-then-allow NetworkPolicy pattern, StatefulSet with headless Service and PVC templates, Service port mismatch diagnosis, and Helm install-upgrade-rollback lifecycle.
Solutions: Tasks 1–10
Each solution below follows a consistent structure: the fastest approach (imperative commands when available), the complete declarative YAML, verification commands with expected output, and a list of common mistakes that cost points on this task.
Solution 1 — Namespace with ResourceQuota
Time target: 4 minutes
Step 1: Create the namespace
kubectl create namespace quota-lab
Step 2: Create the ResourceQuota
The imperative approach does not cover all ResourceQuota fields. Use a YAML manifest:
apiVersion: v1
kind: ResourceQuota
metadata:
name: pod-memory-quota
namespace: quota-lab
spec:
hard:
pods: "2"
requests.memory: 1Gi
limits.memory: 1Gi
Apply it:
kubectl apply -f quota.yaml
Step 3: Verify the quota
kubectl describe resourcequota pod-memory-quota -n quota-lab
Expected output:
Name: pod-memory-quota
Namespace: quota-lab
Resource Used Hard
-------- ---- ----
limits.memory 0 1Gi
pods 0 2
requests.memory 0 1Gi
Step 4: Test the quota enforcement
Create two Pods that fit within the quota:
kubectl run test-pod-1 --image=nginx:1.25 -n quota-lab \
--overrides='{"spec":{"containers":[{"name":"test-pod-1","image":"nginx:1.25","resources":{"requests":{"memory":"256Mi"},"limits":{"memory":"256Mi"}}}]}}'
kubectl run test-pod-2 --image=nginx:1.25 -n quota-lab \
--overrides='{"spec":{"containers":[{"name":"test-pod-2","image":"nginx:1.25","resources":{"requests":{"memory":"256Mi"},"limits":{"memory":"256Mi"}}}]}}'
Attempt a third Pod:
kubectl run test-pod-3 --image=nginx:1.25 -n quota-lab \
--overrides='{"spec":{"containers":[{"name":"test-pod-3","image":"nginx:1.25","resources":{"requests":{"memory":"256Mi"},"limits":{"memory":"256Mi"}}}]}}'
The third Pod is rejected with: Error from server (Forbidden): pods "test-pod-3" is forbidden: exceeded quota: pod-memory-quota, requested: pods=1, used: pods=2, limited: pods=2.
Common Pitfalls
- Forgetting resource requests/limits on test Pods. When a ResourceQuota specifies
requests.memoryorlimits.memory, every Pod in the namespace must declare those fields. A Pod without memory requests is rejected — not because of the quota limit, but because the quota requires the field to be set. - String vs integer for the
podsfield. Thepodsvalue must be a string ("2") in YAML, though Kubernetes accepts integers in most cases. Use quotes to be safe. - Wrong namespace. Applying the quota to
defaultinstead ofquota-labis a silent error — the quota works, but the test Pods go to the wrong namespace.
Solution 2 — Pod with Strict SecurityContext
Time target: 5 minutes
Step 1: Create the namespace
kubectl create namespace secure-app
Step 2: Create the Pod
This task requires a declarative manifest because the security context fields are too complex for kubectl run overrides:
apiVersion: v1
kind: Pod
metadata:
name: locked-pod
namespace: secure-app
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 3000
containers:
- name: nginx
image: nginx:1.25
securityContext:
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
volumeMounts:
- name: cache-vol
mountPath: /var/cache/nginx
- name: run-vol
mountPath: /var/run
volumes:
- name: cache-vol
emptyDir: {}
- name: run-vol
emptyDir: {}
Apply:
kubectl apply -f locked-pod.yaml
Step 3: Verify the Pod is running
kubectl get pod locked-pod -n secure-app
Expected: STATUS is Running.
kubectl exec locked-pod -n secure-app -- id
Expected output: uid=1000 gid=3000 groups=3000
kubectl exec locked-pod -n secure-app -- touch /test-file 2>&1
Expected: touch: /test-file: Read-only file system — confirming readOnlyRootFilesystem is active.
Common Pitfalls
- Missing emptyDir volumes. Without writable mounts for
/var/cache/nginxand/var/run, nginx fails to start because it cannot write to its cache directory or PID file. The Pod entersCrashLoopBackOff. - Placing
runAsNonRootat the container level instead of Pod level. The task specifies Pod-levelrunAsNonRootand container-levelreadOnlyRootFilesystem. Mixing up the levels is a common error. - Forgetting
allowPrivilegeEscalation: false. This field defaults totruewhen not specified. The task requires it explicitly set tofalse. - Using
drop: ["all"]instead ofdrop: ["ALL"]. The capability name is case-sensitive and must be uppercaseALL.
Solution 3 — Fix Broken Deployment
Time target: 4 minutes
Step 1: Create the namespace and apply the broken manifest
kubectl create namespace debug-deploy
Apply the broken Deployment from the task.
Step 2: Diagnose the errors
kubectl get pods -n debug-deploy
All Pods show ImagePullBackOff or ErrImagePull. The image name ngnix:1.25 is misspelled — it should be nginx:1.25.
Step 3: Fix Error 1 — Image name
kubectl set image deployment/broken-web web=nginx:1.25 -n debug-deploy
Step 4: Fix Error 2 — Readiness probe
After fixing the image, Pods start but remain NotReady because the readiness probe targets port 8080 path /healthz. Nginx listens on port 80 and serves its default page at /.
Edit the Deployment:
kubectl edit deployment broken-web -n debug-deploy
Change the readiness probe:
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 5
Alternatively, patch it:
kubectl patch deployment broken-web -n debug-deploy --type='json' -p='[
{"op": "replace", "path": "/spec/template/spec/containers/0/readinessProbe/httpGet/port", "value": 80},
{"op": "replace", "path": "/spec/template/spec/containers/0/readinessProbe/httpGet/path", "value": "/"}
]'
Step 5: Verify
kubectl get pods -n debug-deploy
Expected: 2 Pods in Running state, READY column shows 1/1.
kubectl rollout status deployment/broken-web -n debug-deploy
Expected: deployment "broken-web" successfully rolled out.
Common Pitfalls
- Fixing only one error. The task has two distinct problems. Candidates who fix the image but skip the probe end up with running-but-not-ready Pods, which still fail the task.
- Using
kubectl editfor the image fix. While functional,kubectl set imageis faster and less error-prone. Savekubectl editfor fields that lack imperative equivalents. - Changing the readiness probe to a TCP probe. The task says to fix the HTTP probe, not replace it with a different type. Changing to
tcpSocketworks functionally but does not match the requirement.
Solution 4 — Canary Deployment
Time target: 5 minutes
Step 1: Create the namespace
kubectl create namespace canary-ns
Step 2: Create the stable Deployment
kubectl create deployment web-stable --image=nginx:1.24 --replicas=4 -n canary-ns --dry-run=client -o yaml > web-stable.yaml
Edit web-stable.yaml to ensure the Pod template labels include both app: web and track: stable:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-stable
namespace: canary-ns
spec:
replicas: 4
selector:
matchLabels:
app: web
track: stable
template:
metadata:
labels:
app: web
track: stable
spec:
containers:
- name: nginx
image: nginx:1.24
ports:
- containerPort: 80
kubectl apply -f web-stable.yaml
Step 3: Create the Service
The Service selector must match only app: web — not track. This is what allows both stable and canary Pods to receive traffic:
kubectl expose deployment web-stable --name=web-svc --port=80 --target-port=80 -n canary-ns --dry-run=client -o yaml > web-svc.yaml
Edit web-svc.yaml to remove track: stable from the selector (keep only app: web):
apiVersion: v1
kind: Service
metadata:
name: web-svc
namespace: canary-ns
spec:
selector:
app: web
ports:
- port: 80
targetPort: 80
kubectl apply -f web-svc.yaml
Step 4: Create the canary Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-canary
namespace: canary-ns
spec:
replicas: 1
selector:
matchLabels:
app: web
track: canary
template:
metadata:
labels:
app: web
track: canary
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
kubectl apply -f web-canary.yaml
Step 5: Verify
kubectl get endpoints web-svc -n canary-ns
The endpoint list must contain 5 IP addresses (4 from web-stable + 1 from web-canary).
kubectl get pods -n canary-ns --show-labels
Four Pods show track=stable, one shows track=canary, and all five show app=web.
Common Pitfalls
- Including
trackin the Service selector. If the Service selector isapp: web, track: stable, the canary Pods are excluded. The canary pattern requires the Service to select on a common label shared by both Deployments. - Using
kubectl exposewithout modifying the selector.kubectl expose deployment web-stablecopies the Deployment’s labels into the Service selector, includingtrack: stable. You must manually edit the selector to removetrack. - Mismatched labels between the Deployment selector and Pod template. The Deployment’s
selector.matchLabelsmust be a subset of the Pod template’s labels.
Solution 5 — CronJob and Job Extraction
Time target: 4 minutes
Step 1: Create the namespace
kubectl create namespace batch-ns
Step 2: Create the CronJob
Imperative approach:
kubectl create cronjob log-timestamp \
--image=busybox:1.36 \
--schedule="*/5 * * * *" \
--restart=Never \
-n batch-ns \
-- sh -c "echo Timestamp: \$(date) && sleep 5"
Then patch for history limits — or use a declarative manifest from the start:
apiVersion: batch/v1
kind: CronJob
metadata:
name: log-timestamp
namespace: batch-ns
spec:
schedule: "*/5 * * * *"
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
jobTemplate:
spec:
template:
spec:
containers:
- name: logger
image: busybox:1.36
command: ["sh", "-c", "echo Timestamp: $(date) && sleep 5"]
restartPolicy: Never
kubectl apply -f cronjob.yaml
Step 3: Create a Job from the CronJob
kubectl create job log-now --from=cronjob/log-timestamp -n batch-ns
This creates a one-off Job using the CronJob’s template without waiting for the next scheduled trigger.
Step 4: Verify the Job
kubectl wait --for=condition=complete job/log-now -n batch-ns --timeout=60s
kubectl logs job/log-now -n batch-ns
Expected output: Timestamp: <current date/time>
Step 5: Confirm the CronJob still exists
kubectl get cronjob log-timestamp -n batch-ns
The CronJob must show its schedule and remain active.
Common Pitfalls
- Forgetting
restartPolicy: Never. CronJob templates require eitherNeverorOnFailure. If you omit it, the default isAlways, which is invalid for Jobs and produces an API error. - Using
\$(date)vs$(date). In a YAML manifest,$(date)inside a command string is interpreted by the container’s shell at runtime, which is correct. In akubectlimperative command, you must escape the$to prevent your local shell from expanding it before the command reaches Kubernetes. - Not specifying history limits. While the CronJob works without explicit history limits, the task requires
successfulJobsHistoryLimit: 3andfailedJobsHistoryLimit: 1. Missing them loses partial credit.
Solution 6 — Pod with Secret (env) and ConfigMap (volume)
Time target: 5 minutes
Step 1: Create the namespace
kubectl create namespace config-ns
Step 2: Create the Secret
kubectl create secret generic db-credentials \
--from-literal=DB_USER=admin \
--from-literal=DB_PASS=exam-secret-2026 \
-n config-ns
Step 3: Create the ConfigMap
kubectl create configmap app-config \
--from-literal=app.properties="log.level=INFO
cache.ttl=300
feature.flag=true" \
-n config-ns
Alternatively, create a file first and use --from-file:
cat <<EOF > app.properties
log.level=INFO
cache.ttl=300
feature.flag=true
EOF
kubectl create configmap app-config --from-file=app.properties -n config-ns
The --from-file approach is cleaner because it preserves the key name as the filename (app.properties).
Step 4: Create the Pod
apiVersion: v1
kind: Pod
metadata:
name: config-consumer
namespace: config-ns
spec:
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "echo DB_USER=$DB_USER DB_PASS=$DB_PASS && cat /config/app.properties && sleep 3600"]
env:
- name: DB_USER
valueFrom:
secretKeyRef:
name: db-credentials
key: DB_USER
- name: DB_PASS
valueFrom:
secretKeyRef:
name: db-credentials
key: DB_PASS
volumeMounts:
- name: config-volume
mountPath: /config
volumes:
- name: config-volume
configMap:
name: app-config
kubectl apply -f config-consumer.yaml
Step 5: Verify
kubectl exec config-consumer -n config-ns -- env | grep DB_
Expected:
DB_USER=admin
DB_PASS=exam-secret-2026
kubectl exec config-consumer -n config-ns -- cat /config/app.properties
Expected:
log.level=INFO
cache.ttl=300
feature.flag=true
Common Pitfalls
- Using
envFromwithsecretRefinstead of individualsecretKeyRef. The task specifies injecting each key individually withsecretKeyRef. UsingenvFromworks functionally but does not match the requirement. - ConfigMap key mismatch. If you create the ConfigMap with
--from-literal=config=..., the mounted file is namedconfig, notapp.properties. Thecat /config/app.propertiesverification command fails. - Forgetting the volume mount. Defining the volume in
spec.volumesbut not mounting it inspec.containers[].volumeMountsmeans the ConfigMap data is never accessible inside the container.
Solution 7 — NetworkPolicy: Deny All Then Allow by Namespace
Time target: 5 minutes
Step 1: Create namespaces and label
kubectl create namespace netpol-ns
kubectl create namespace trusted-ns
kubectl label namespace trusted-ns purpose=trusted
Step 2: Create the target Pod
kubectl run web-server --image=nginx:1.25 -n netpol-ns --labels=app=web-server
Step 3: Create the deny-all ingress NetworkPolicy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-ingress
namespace: netpol-ns
spec:
podSelector: {}
policyTypes:
- Ingress
ingress: []
kubectl apply -f deny-all.yaml
The empty ingress: [] list means no ingress traffic is allowed to any Pod in netpol-ns.
Step 4: Create the allow-from-trusted NetworkPolicy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-from-trusted
namespace: netpol-ns
spec:
podSelector:
matchLabels:
app: web-server
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
purpose: trusted
ports:
- protocol: TCP
port: 80
kubectl apply -f allow-trusted.yaml
Step 5: Verify (conceptual)
The deny-all policy blocks all ingress. The allow-from-trusted policy adds an exception for Pods in namespaces labeled purpose: trusted to reach web-server on port 80. Both policies coexist — NetworkPolicies are additive. If any policy allows traffic, it passes.
kubectl describe networkpolicy -n netpol-ns
Two policies must appear: deny-all-ingress and allow-from-trusted.
Common Pitfalls
- Omitting
policyTypes: ["Ingress"]on the deny-all policy. WithoutpolicyTypes, the policy type is inferred from the presence ofingressoregressrules. An emptyingress: []with nopolicyTypesfield creates ambiguity. Always specifypolicyTypesexplicitly. - Using
podSelectorinstead ofnamespaceSelectorin the allow rule. The task requires allowing traffic from a specific namespace, not from specific Pods. UsingpodSelectoralone (withoutnamespaceSelector) matches Pods in the same namespace as the policy. - Placing the allow policy in
trusted-nsinstead ofnetpol-ns. NetworkPolicies apply to the namespace they are created in. The allow policy must be innetpol-nsbecause it governs ingress to Pods in that namespace. - Combining
namespaceSelectorandpodSelectorin the samefromentry without understanding the AND/OR semantics. A singlefromentry with both selectors means AND (both must match). Separatefromentries mean OR. The task requires onlynamespaceSelector.
Solution 8 — StatefulSet with Headless Service and volumeClaimTemplates
Time target: 6 minutes
Step 1: Create the namespace
kubectl create namespace stateful-ns
Step 2: Create the headless Service
apiVersion: v1
kind: Service
metadata:
name: db-headless
namespace: stateful-ns
spec:
clusterIP: None
selector:
app: db
ports:
- port: 5432
targetPort: 5432
kubectl apply -f db-headless.yaml
Step 3: Create the StatefulSet
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: db
namespace: stateful-ns
spec:
serviceName: db-headless
replicas: 3
selector:
matchLabels:
app: db
template:
metadata:
labels:
app: db
spec:
containers:
- name: postgres
image: postgres:16
ports:
- containerPort: 5432
env:
- name: POSTGRES_PASSWORD
value: exam-pass
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi
kubectl apply -f db-statefulset.yaml
Step 4: Wait for all Pods
kubectl rollout status statefulset/db -n stateful-ns --timeout=120s
Or watch directly:
kubectl get pods -n stateful-ns -w
Pods appear in order: db-0 first, then db-1, then db-2. Each Pod must be Running before the next one starts.
Step 5: Verify DNS
kubectl exec db-0 -n stateful-ns -- nslookup db-headless.stateful-ns.svc.cluster.local
This resolves to the IP addresses of all three Pods. Individual Pods are addressable as db-0.db-headless.stateful-ns.svc.cluster.local.
Step 6: Verify PVCs
kubectl get pvc -n stateful-ns
Expected:
NAME STATUS VOLUME CAPACITY ACCESS MODES
data-db-0 Bound ... 1Gi RWO
data-db-1 Bound ... 1Gi RWO
data-db-2 Bound ... 1Gi RWO
Common Pitfalls
- Forgetting to create the headless Service before the StatefulSet. The StatefulSet’s
serviceNamereferences the headless Service. While the StatefulSet still creates Pods without the Service, DNS resolution does not work until the Service exists. - Setting
clusterIPto a value other thanNone. A headless Service requiresclusterIP: None. Any other value creates a regular ClusterIP Service, breaking the StatefulSet DNS pattern. - Missing
POSTGRES_PASSWORDenvironment variable. Thepostgres:16image requires this variable. Without it, the container exits with an error message about missing authentication configuration. - Mismatched
volumeClaimTemplatesname andvolumeMountsname. Thenamefield involumeClaimTemplates[].metadata.namemust exactly match thevolumeMounts[].namein the container spec.
Solution 9 — Debug: Pod Returns 503
Time target: 4 minutes
Step 1: Create namespace and apply the broken manifest
kubectl create namespace debug-svc
Apply the Pod and Service from the task.
Step 2: Diagnose the problem
Check the Pod status:
kubectl get pod api-server -n debug-svc
The Pod is Running and Ready. The issue is with the Service, not the Pod.
Check the Service endpoints:
kubectl get endpoints api-svc -n debug-svc
If the Pod is Ready, the endpoint should show the Pod IP — but with the wrong targetPort. The http-echo container listens on port 5678, but the Service targetPort is 8080.
Step 3: Fix the Service targetPort
kubectl patch service api-svc -n debug-svc --type='json' -p='[{"op":"replace","path":"/spec/ports/0/targetPort","value":5678}]'
Or edit directly:
kubectl edit service api-svc -n debug-svc
Change targetPort: 8080 to targetPort: 5678.
Step 4: Verify the fix
kubectl get endpoints api-svc -n debug-svc
The endpoint must show the Pod IP with port 5678.
kubectl run curl-test --image=curlimages/curl --rm -it --restart=Never -n debug-svc -- curl http://api-svc.debug-svc.svc.cluster.local
Expected output: healthy
Common Pitfalls
- Assuming the readiness probe is the problem. The
http-echoimage returns 200 on all paths, so the/readypath in the readiness probe actually works. The Pod becomes Ready. The real issue is the Service’stargetPortmismatch. - Changing the Service
portinstead oftargetPort. Theportis the Service’s external port (what clients connect to). ThetargetPortis what the Service forwards traffic to on the Pod. The container listens on5678, sotargetPortmust be5678. - Forgetting to verify with an actual curl test. Checking endpoints alone confirms connectivity plumbing, but a curl test confirms end-to-end data flow.
Solution 10 — Helm: Install, Upgrade, Rollback
Time target: 5 minutes
Step 1: Create the namespace
kubectl create namespace helm-ns
Step 2: Add the Bitnami repository
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
If the repo is already added, the add command prints a warning. That is fine — helm repo update ensures you have the latest index.
Step 3: Install the release
helm install my-nginx bitnami/nginx -n helm-ns --set replicaCount=1
Step 4: Verify installation
helm list -n helm-ns
Expected: one entry for my-nginx with status deployed and revision 1.
kubectl get deployment -n helm-ns
The nginx Deployment should show 1/1 ready replicas.
Step 5: Upgrade to 3 replicas
helm upgrade my-nginx bitnami/nginx -n helm-ns --set replicaCount=3
Step 6: Verify upgrade
kubectl get deployment -n helm-ns
Expected: 3/3 ready replicas.
helm history my-nginx -n helm-ns
Expected: two revisions.
REVISION STATUS DESCRIPTION
1 superseded Install complete
2 deployed Upgrade complete
Step 7: Rollback to revision 1
helm rollback my-nginx 1 -n helm-ns
Step 8: Verify rollback
kubectl get deployment -n helm-ns
Expected: 1/1 ready replicas (back to revision 1 settings).
helm history my-nginx -n helm-ns
Expected: three revisions.
REVISION STATUS DESCRIPTION
1 superseded Install complete
2 superseded Upgrade complete
3 deployed Rollback to 1
Common Pitfalls
- Forgetting
-n helm-ns. Without the namespace flag, Helm installs the release in thedefaultnamespace. Every Helm command must include-n helm-ns. - Using
helm upgradewithout--setor-f. If you runhelm upgrade my-nginx bitnami/nginx -n helm-nswithout specifying values, the chart’s default values override your previous--setoverrides. Always re-specify values you want to keep, or use--reuse-values(though--reuse-valueshas limitations with chart version changes). - Confusing revision numbers. A rollback creates a new revision, not a revert. After rollback, the history shows 3 entries, and the current state matches revision 1 but carries revision number 3. The
helm historyoutput confirms this. - Not running
helm repo updatebefore install. Without an updated index, Helm may fail to find the chart or use a stale version. Always runhelm repo updateafter adding a repository.