Solutions: Tasks 1–8
SummaryComplete solutions for tasks 1-8: Pod with labels...
Complete solutions for tasks 1-8: Pod with labels...
Complete solutions for tasks 1-8: Pod with labels and resources, Deployment with rolling update, CronJob with concurrency, ConfigMap as env vars, NetworkPolicy, PVC with Pod, debugging a broken Pod, and Service with Ingress.
Solutions: Tasks 1–8
Solution: Task 1 — Pod with Labels and Resource Requests (4%)
Imperative Approach
kubectl config set-context --current --namespace=app-team1
Generate the Pod scaffold:
k run web-frontend --image=nginx:1.25-alpine --port=80 \
--labels="app=frontend,tier=web,version=v1" \
--requests="cpu=100m,memory=128Mi" \
--limits="cpu=250m,memory=256Mi" \
$do > web-frontend.yaml
Review the generated YAML and apply:
k apply -f web-frontend.yaml
Final YAML
apiVersion: v1
kind: Pod
metadata:
name: web-frontend
namespace: app-team1
labels:
app: frontend
tier: web
version: v1
spec:
containers:
- name: web-frontend
image: nginx:1.25-alpine
ports:
- containerPort: 80
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 250m
memory: 256Mi
Verification
k get pod web-frontend -n app-team1
NAME READY STATUS RESTARTS AGE
web-frontend 1/1 Running 0 10s
k get pod web-frontend -n app-team1 --show-labels
NAME READY STATUS RESTARTS AGE LABELS
web-frontend 1/1 Running 0 15s app=frontend,tier=web,version=v1
k describe pod web-frontend -n app-team1 | grep -A6 "Limits\|Requests"
Limits:
cpu: 250m
memory: 256Mi
Requests:
cpu: 100m
memory: 128Mi
Key Points
- This is a quick-win task. The entire Pod can be generated imperatively in one command — no YAML editing required.
- The
--labelsflag accepts comma-separated key=value pairs. No spaces after commas. - Verify all three labels are present. Missing one label means partial credit at best.
- Time target: under 2 minutes.
Solution: Task 2 — Deployment with Rolling Update (7%)
Imperative Approach
kubectl config set-context --current --namespace=app-team1
Generate the Deployment:
k create deployment api-server --image=nginx:1.24 --replicas=3 --port=8080 $do > api-server.yaml
Edit the YAML to add the rolling update strategy and the app: api label:
vim api-server.yaml
Final YAML (Step 1)
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
namespace: app-team1
labels:
app: api
spec:
replicas: 3
selector:
matchLabels:
app: api
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: api
spec:
containers:
- name: nginx
image: nginx:1.24
ports:
- containerPort: 8080
Apply the Deployment:
k apply -f api-server.yaml
Wait for all replicas:
k rollout status deployment/api-server -n app-team1
Step 2: Rolling Update
k set image deployment/api-server nginx=nginx:1.25 -n app-team1
Step 3: Verify
k rollout status deployment/api-server -n app-team1
deployment "api-server" successfully rolled out
k get pods -n app-team1 -l app=api -o jsonpath='{.items[*].spec.containers[0].image}'
nginx:1.25 nginx:1.25 nginx:1.25
Step 4: Rollout History
k rollout history deployment/api-server -n app-team1
deployment.apps/api-server
REVISION CHANGE-CAUSE
1 <none>
2 <none>
Key Points
- The
k create deploymentcommand generates a Deployment with default labels matching the deployment name (e.g.,app: api-server). The task requiresapp: api, so you must edit the selector, template labels, and Deployment labels to match. - When you change the label selector, update it in three places:
metadata.labels,spec.selector.matchLabels, andspec.template.metadata.labels. They must all be consistent. maxUnavailable: 0means Kubernetes creates new Pods before terminating old ones, ensuring zero downtime during the update.- The container name generated by
kubectl create deploymentdefaults to the image name (e.g.,nginx). Use that name in thek set imagecommand. - Time target: 5–6 minutes.
Solution: Task 3 — CronJob with Concurrency Settings (7%)
Imperative Approach
kubectl config set-context --current --namespace=batch-ns
Generate the CronJob scaffold:
k create cronjob report-generator --image=busybox:1.36 \
--schedule="*/5 * * * *" \
$do -- sh -c "echo Generating report at \$(date) && sleep 20" > report-generator.yaml
Edit the YAML to add concurrency policy, history limits, active deadline, and restart policy:
vim report-generator.yaml
Final YAML
apiVersion: batch/v1
kind: CronJob
metadata:
name: report-generator
namespace: batch-ns
spec:
schedule: "*/5 * * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
jobTemplate:
spec:
activeDeadlineSeconds: 30
template:
spec:
restartPolicy: Never
containers:
- name: report-generator
image: busybox:1.36
command:
- sh
- -c
- "echo Generating report at $(date) && sleep 20"
Apply:
k apply -f report-generator.yaml
Verification
k get cronjob report-generator -n batch-ns
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
report-generator */5 * * * * False 0 <none> 10s
k get cronjob report-generator -n batch-ns -o jsonpath='{.spec.concurrencyPolicy}'
Forbid
k get cronjob report-generator -n batch-ns -o jsonpath='{.spec.successfulJobsHistoryLimit}'
3
k get cronjob report-generator -n batch-ns -o jsonpath='{.spec.jobTemplate.spec.activeDeadlineSeconds}'
30
Key Points
activeDeadlineSecondsgoes underspec.jobTemplate.spec, not underspecof the CronJob. This is a common mistake — it applies to the Job, not the CronJob.concurrencyPolicy: Forbidprevents a new Job from starting if the previous Job is still running. This matters for tasks that should not overlap.successfulJobsHistoryLimitandfailedJobsHistoryLimitgo underspecof the CronJob (not the Job template).restartPolicymust beNeverorOnFailurefor Jobs. Pods in a Job cannot haverestartPolicy: Always.- Time target: 4–5 minutes.
Solution: Task 4 — ConfigMap as Environment Variables (4%)
Imperative Approach
kubectl config set-context --current --namespace=app-team1
Create the ConfigMap:
k create configmap app-config \
--from-literal=APP_ENV=production \
--from-literal=APP_LOG_LEVEL=info \
--from-literal=APP_PORT=8080 \
-n app-team1
Generate the Pod scaffold:
k run config-reader --image=busybox:1.36 \
$do --command -- sh -c "echo \$APP_ENV \$APP_LOG_LEVEL \$APP_PORT && sleep 3600" > config-reader.yaml
Edit the YAML to add envFrom:
vim config-reader.yaml
Final YAML (Pod only — ConfigMap was created imperatively)
apiVersion: v1
kind: Pod
metadata:
name: config-reader
namespace: app-team1
spec:
containers:
- name: config-reader
image: busybox:1.36
command:
- sh
- -c
- "echo $APP_ENV $APP_LOG_LEVEL $APP_PORT && sleep 3600"
envFrom:
- configMapRef:
name: app-config
Apply:
k apply -f config-reader.yaml
Verification
k exec config-reader -n app-team1 -- env | grep APP_
APP_ENV=production
APP_LOG_LEVEL=info
APP_PORT=8080
Key Points
envFromwithconfigMapRefloads all keys from the ConfigMap as environment variables. This is faster to write than individualenventries withvalueFrom.- The ConfigMap can be created entirely imperatively — no YAML needed. Only the Pod requires YAML editing to add
envFrom. - Time target: 2–3 minutes.
Solution: Task 5 — NetworkPolicy (7%)
Imperative Approach
There is no imperative command for NetworkPolicies. You must write YAML from scratch or copy from the Kubernetes documentation.
kubectl config set-context --current --namespace=network-ns
Navigate to kubernetes.io/docs/concepts/services-networking/network-policies/ and copy the example NetworkPolicy YAML. Edit it to match the task requirements.
Final YAML
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-netpol
namespace: network-ns
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
role: frontend
ports:
- protocol: TCP
port: 80
Apply:
k apply -f backend-netpol.yaml
Verification
k get networkpolicy backend-netpol -n network-ns
NAME POD-SELECTOR AGE
backend-netpol app=backend 10s
k describe networkpolicy backend-netpol -n network-ns
Name: backend-netpol
Namespace: network-ns
Created on: ...
Labels: <none>
Annotations: <none>
Spec:
PodSelector: app=backend
Allowing ingress traffic:
To Port: 80/TCP
From:
PodSelector: role=frontend
Not affecting egress traffic
Policy Types: Ingress
Key Points
- NetworkPolicy is the one common resource type that has no imperative command. You must write or copy YAML.
- The
podSelectorunderspecselects which Pods this policy applies to (app: backend). - The
podSelectorunderingress[0].from[0]selects which Pods are allowed to send traffic (role: frontend). - Specifying
policyTypes: [Ingress]without any egress rules means egress is unrestricted. If you addEgresstopolicyTypeswithout egress rules, you block all outbound traffic — a common mistake. - The
portsfield underingressrestricts which ports traffic is allowed on. Placing it at the wrong indentation level changes the meaning. - Pay close attention to YAML indentation:
fromandportsare both items under the sameingresslist entry. Thefromandportsarrays are peers, not nested. - Time target: 5–6 minutes.
Solution: Task 6 — PersistentVolumeClaim and Pod (7%)
Imperative Approach
kubectl config set-context --current --namespace=storage-ns
There is no imperative command for PVC creation. Write the YAML:
PVC YAML
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data-pvc
namespace: storage-ns
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
Apply:
k apply -f data-pvc.yaml
Generate the Pod scaffold:
k run data-writer --image=busybox:1.36 \
$do --command -- sh -c "echo 'persistent data test' > /data/output.txt && sleep 3600" > data-writer.yaml
Edit to add volume and volume mount:
Final Pod YAML
apiVersion: v1
kind: Pod
metadata:
name: data-writer
namespace: storage-ns
spec:
containers:
- name: data-writer
image: busybox:1.36
command:
- sh
- -c
- "echo 'persistent data test' > /data/output.txt && sleep 3600"
volumeMounts:
- name: data-vol
mountPath: /data
volumes:
- name: data-vol
persistentVolumeClaim:
claimName: data-pvc
Apply:
k apply -f data-writer.yaml
Verification
k get pvc data-pvc -n storage-ns
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
data-pvc Bound pv-xxxx 1Gi RWO standard 30s
k get pod data-writer -n storage-ns
NAME READY STATUS RESTARTS AGE
data-writer 1/1 Running 0 15s
k exec data-writer -n storage-ns -- cat /data/output.txt
persistent data test
Key Points
- Do not specify
storageClassNamein the PVC if the task says to use the default storage class. Omitting it causes Kubernetes to use the default class automatically. - The volume name (
data-vol) must match betweenvolumes[].nameandvolumeMounts[].name. A mismatch causes a validation error. - The PVC must be
Boundbefore the Pod can start. If the PVC stays inPending, check whether a suitable PersistentVolume or dynamic provisioner exists. - Time target: 4–5 minutes.
Solution: Task 7 — Debug a Broken Pod (7%)
Investigation
kubectl config set-context --current --namespace=monitoring
k get pod health-checker -n monitoring
NAME READY STATUS RESTARTS AGE
health-checker 0/1 ImagePullBackOff 0 30s
The status ImagePullBackOff indicates the image cannot be pulled. Check the events:
k describe pod health-checker -n monitoring | grep -A5 Events
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning Failed 10s kubelet Failed to pull image "ngnix:latest":
rpc error: image not found
Warning Failed 10s kubelet Error: ImagePullBackOff
The image name is ngnix — missing the i. It should be nginx.
Additionally, the probes target port 8080 but the container listens on port 80, and the probe paths /healthz and /ready do not exist on the default nginx image.
Fix
Delete the broken Pod:
k delete pod health-checker -n monitoring
Create the corrected YAML:
apiVersion: v1
kind: Pod
metadata:
name: health-checker
namespace: monitoring
spec:
containers:
- name: checker
image: nginx:latest
ports:
- containerPort: 80
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 3
periodSeconds: 5
Apply:
k apply -f health-checker.yaml
Verification
k get pod health-checker -n monitoring
NAME READY STATUS RESTARTS AGE
health-checker 1/1 Running 0 15s
k describe pod health-checker -n monitoring | grep -A3 "Liveness\|Readiness"
Liveness: http-get http://:80/ delay=5s timeout=1s period=10s #success=1 #failure=3
Readiness: http-get http://:80/ delay=3s timeout=1s period=5s #success=1 #failure=3
Key Points
- Debugging tasks require a systematic approach: check status, check events, check logs, identify root cause, fix.
- Pod spec fields are immutable. You cannot change the image or probe configuration of a running Pod. Delete and recreate.
- The default nginx image serves on port
80and returns200 OKon path/. Using/healthzor/readyreturns404, which the probe interprets as failure. - Typos in image names (
ngnixvsnginx) are a common exam trap. Always check the image name first when debuggingImagePullBackOff. - Time target: 4–5 minutes.
Solution: Task 8 — Service and Ingress (7%)
Imperative Approach
kubectl config set-context --current --namespace=web-prod
Create the Deployment:
k create deployment shop-frontend --image=nginx:1.25 --replicas=2 --port=80 -n web-prod
The default labels from kubectl create deployment will be app: shop-frontend. The task requires app: shop on the Pods. Update the Deployment:
k get deployment shop-frontend -n web-prod -o yaml > shop-deploy.yaml
Edit to set Pod labels to app: shop and update the selector:
apiVersion: apps/v1
kind: Deployment
metadata:
name: shop-frontend
namespace: web-prod
labels:
app: shop
spec:
replicas: 2
selector:
matchLabels:
app: shop
template:
metadata:
labels:
app: shop
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
Apply:
k apply -f shop-deploy.yaml
Create the Service:
k expose deployment shop-frontend --name=shop-svc --port=80 --target-port=80 -n web-prod
Since we changed the labels, the expose command automatically picks up the selector from the Deployment. Alternatively, create the Service imperatively and it will use the Deployment’s selector:
Verify the Service targets the correct Pods:
k get endpoints shop-svc -n web-prod
Create the Ingress. There is no fully-featured imperative command for Ingress with host-based routing, so write YAML:
Ingress YAML
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: shop-ingress
namespace: web-prod
spec:
ingressClassName: nginx
rules:
- host: shop.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: shop-svc
port:
number: 80
Apply:
k apply -f shop-ingress.yaml
Verification
k get svc shop-svc -n web-prod
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
shop-svc ClusterIP 10.96.45.123 <none> 80/TCP 20s
k get ingress shop-ingress -n web-prod
NAME CLASS HOSTS ADDRESS PORTS AGE
shop-ingress nginx shop.example.com 80 10s
k describe ingress shop-ingress -n web-prod
Name: shop-ingress
Namespace: web-prod
...
Rules:
Host Path Backends
---- ---- --------
shop.example.com
/ shop-svc:80 (10.244.0.15:80,10.244.0.16:80)
Key Points
- The Ingress API uses
networking.k8s.io/v1. Using the oldextensions/v1beta1API results in an error on modern Kubernetes versions. ingressClassName: nginxreplaces the oldkubernetes.io/ingress.classannotation. Use the field, not the annotation.pathTypeis required. The two common values arePrefix(matches the path and its sub-paths) andExact(matches only the exact path).- The Service selector must match the Pod labels. If you changed the Deployment labels from
app: shop-frontendtoapp: shop, verify the Service selector was updated accordingly. - Time target: 6–7 minutes.