Solutions: Tasks 11–20
SummaryFull solutions for Tasks 11–20 of Mock Exam...
Full solutions for Tasks 11–20 of Mock Exam...
Full solutions for Tasks 11–20 of Mock Exam 2: init container data handoff, DaemonSet with control-plane tolerations, TLS Ingress with self-signed certificate, OOMKilled diagnosis and fix, ServiceAccount RBAC binding, Deployment-to-Ingress chain with rewrite annotation, nodeSelector mismatch resolution, Job backoffLimit failure observation and fix, blue-green deployment Service selector switchover, and a comprehensive full-stack aggregate task assembling namespace, quota, ConfigMap, Secret, Deployment, Service, and Ingress.
Solutions: Tasks 11–20
These solutions continue from Section 1. Each solution includes the fastest approach, complete YAML, verification, and a time-saving tip to help you shave minutes off your exam performance.
Solution 11 — Multi-Container Pod with Init Container
Time target: 4 minutes
Time-saving tip: Write init container YAML from memory — init containers follow the same structure as regular containers but appear under spec.initContainers instead of spec.containers. Start with kubectl run app-with-init --image=busybox:1.36 --dry-run=client -o yaml to get the base Pod structure, then add the init container block manually.
Step 1: Create the namespace
kubectl create namespace init-ns
Step 2: Create the Pod
apiVersion: v1
kind: Pod
metadata:
name: app-with-init
namespace: init-ns
spec:
initContainers:
- name: config-downloader
image: busybox:1.36
command: ["sh", "-c", "echo 'server.port=8080' > /shared/config.txt && echo 'Config downloaded'"]
volumeMounts:
- name: shared-data
mountPath: /shared
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "cat /config/config.txt && sleep 3600"]
volumeMounts:
- name: shared-data
mountPath: /config
volumes:
- name: shared-data
emptyDir: {}
kubectl apply -f app-with-init.yaml
Step 3: Verify
Wait for the Pod to reach Running:
kubectl get pod app-with-init -n init-ns
Check the main container logs:
kubectl logs app-with-init -c app -n init-ns
Expected output: server.port=8080
Confirm the init container completed:
kubectl get pod app-with-init -n init-ns -o jsonpath='{.status.initContainerStatuses[0].state}'
Expected: {"terminated":{"exitCode":0,...,"reason":"Completed",...}}
Common Pitfalls
- Mounting the volume at different paths but referencing the wrong path in the main container. The init container writes to
/shared/config.txt, and the volume is mounted at/configin the main container. The file appears as/config/config.txt. If the mount paths don’t align with the file access paths, the main container finds an empty directory. - Placing
initContainersinsidecontainers. They are sibling fields underspec, not nested.spec.initContainersis separate fromspec.containers. - Forgetting to share the same volume. Both the init container and the main container must reference the same volume name. If they use different volume names, they mount different (empty) volumes.
Solution 12 — DaemonSet with Control-Plane Tolerations
Time target: 4 minutes
Time-saving tip: There is no imperative command to create a DaemonSet. Start with a Deployment dry-run (kubectl create deployment log-agent --image=busybox:1.36 --dry-run=client -o yaml), then change kind: Deployment to kind: DaemonSet, remove the replicas field and the strategy field, and add tolerations. This is faster than writing a DaemonSet from scratch.
Step 1: Create the namespace
kubectl create namespace daemon-ns
Step 2: Create the DaemonSet
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: log-agent
namespace: daemon-ns
spec:
selector:
matchLabels:
app: log-agent
template:
metadata:
labels:
app: log-agent
spec:
tolerations:
- key: "node-role.kubernetes.io/control-plane"
operator: "Exists"
effect: "NoSchedule"
containers:
- name: log-agent
image: busybox:1.36
command: ["sh", "-c", "while true; do echo 'Collecting logs from $(hostname)'; sleep 60; done"]
kubectl apply -f log-agent.yaml
Step 3: Verify
kubectl get daemonset log-agent -n daemon-ns
Expected: DESIRED equals the total number of nodes (workers + control-plane), and READY matches.
kubectl get pods -n daemon-ns -o wide
At least one Pod must be running on a node with the control-plane role. Check the NODE column against:
kubectl get nodes
Common Pitfalls
- Using
operator: "Equal"without specifying avalue. TheExistsoperator matches any value for the given key. If you useEqual, you must provide the exact value of the taint, which varies by cluster.Existsis the reliable choice when the taint value is unknown. - Leaving the
replicasfield in the manifest. DaemonSets do not have areplicasfield. If you converted from a Deployment template and forgot to remove it, the API rejects the manifest. - Not checking that the DaemonSet Pod count matches the node count. If
DESIREDis less than the total node count, a taint you did not account for is blocking scheduling on some nodes.
Solution 13 — Ingress with TLS
Time target: 5 minutes
Time-saving tip: Generate the TLS cert and Secret with two quick commands. Do not overthink the certificate details — for the exam, any self-signed cert works. The key task is wiring the Secret into the Ingress correctly.
Step 1: Create the namespace
kubectl create namespace tls-ns
Step 2: Generate the TLS certificate
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout tls.key -out tls.crt \
-subj "/CN=secure-app.example.com"
Step 3: Create the TLS Secret
kubectl create secret tls tls-secret --cert=tls.crt --key=tls.key -n tls-ns
Step 4: Create the Deployment
kubectl create deployment secure-app --image=nginx:1.25 --replicas=2 -n tls-ns
Step 5: Create the Service
kubectl expose deployment secure-app --name=secure-app-svc --port=80 --target-port=80 -n tls-ns
Step 6: Create the Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: secure-app-ingress
namespace: tls-ns
spec:
ingressClassName: nginx
tls:
- hosts:
- secure-app.example.com
secretName: tls-secret
rules:
- host: secure-app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: secure-app-svc
port:
number: 80
kubectl apply -f secure-app-ingress.yaml
Step 7: Verify
kubectl describe ingress secure-app-ingress -n tls-ns
Expected: TLS section shows secure-app.example.com terminated with Secret tls-secret. Rules section shows the path / routing to secure-app-svc:80.
kubectl get secret tls-secret -n tls-ns -o yaml
The Secret type must be kubernetes.io/tls with tls.crt and tls.key data fields.
Common Pitfalls
- Creating the Secret with
kubectl create secret genericinstead ofkubectl create secret tls. A generic Secret does not have thekubernetes.io/tlstype and may not have the correct key names (tls.crtandtls.key). Usekubectl create secret tlsto get the right type automatically. - Mismatched host between TLS and rules. The hostname in the
tls[].hostslist must exactly match the hostname inrules[].host. A mismatch causes TLS termination to fail for that host. - Forgetting
ingressClassName. Without this field, the Ingress may not be picked up by any Ingress controller, depending on cluster configuration. - Swapping
--certand--keyarguments. Providing the key file as--certand the cert file as--keycreates a Secret with invalid TLS data.
Solution 14 — Pod with Ephemeral Volume and OOMKilled Behavior
Time target: 4 minutes
Time-saving tip: Use the polinux/stress image directly — no need to install stress inside a container at runtime. Set up the Pod with limits you know will trigger OOMKilled, observe it, then create the fixed version.
Step 1: Create the namespace
kubectl create namespace oom-ns
Step 2: Create the memory-hog Pod
apiVersion: v1
kind: Pod
metadata:
name: memory-hog
namespace: oom-ns
spec:
containers:
- name: stress
image: polinux/stress
command: ["stress"]
args: ["--vm", "1", "--vm-bytes", "128M", "--vm-hang", "0"]
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "100Mi"
cpu: "200m"
volumeMounts:
- name: tmp-data
mountPath: /tmp/data
volumes:
- name: tmp-data
emptyDir: {}
kubectl apply -f memory-hog.yaml
Step 3: Observe OOMKilled
The container requests 128Mi of memory but the limit is 100Mi. The kernel’s OOM killer terminates the process.
kubectl get pod memory-hog -n oom-ns
After a few seconds, the status shows OOMKilled or CrashLoopBackOff (with OOMKilled as the last termination reason).
kubectl get pod memory-hog -n oom-ns -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}'
Expected output: OOMKilled
Step 4: Create the fixed Pod
apiVersion: v1
kind: Pod
metadata:
name: memory-ok
namespace: oom-ns
spec:
containers:
- name: stress
image: polinux/stress
command: ["stress"]
args: ["--vm", "1", "--vm-bytes", "128M", "--vm-hang", "0"]
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
volumeMounts:
- name: tmp-data
mountPath: /tmp/data
volumes:
- name: tmp-data
emptyDir: {}
kubectl apply -f memory-ok.yaml
kubectl get pod memory-ok -n oom-ns
Expected: Running status that persists.
Common Pitfalls
- Setting limits equal to the stress allocation. If you set
memory: 128Mias the limit and the stress tool allocates exactly 128Mi, the container may still be OOMKilled due to the process’s own overhead (stack, heap, shared libraries). The limit must be comfortably above the allocation. - Forgetting the emptyDir volume. The task requires an ephemeral volume mount at
/tmp/data. Missing it loses partial credit even if the OOMKilled behavior is observed correctly. - Not checking
lastStatevsstate. The Pod restarts after OOMKilled, so the currentstatemay showRunning(during the next attempt) orWaiting(during backoff). The OOMKilled reason is inlastState.terminated.reason.
Solution 15 — ServiceAccount with Role Binding
Time target: 5 minutes
Time-saving tip: Use imperative commands for all three RBAC resources. The sequence is: kubectl create sa, kubectl create role, kubectl create rolebinding. These three commands take under 30 seconds combined.
Step 1: Create the namespace
kubectl create namespace rbac-ns
Step 2: Create the ServiceAccount
kubectl create serviceaccount pod-reader-sa -n rbac-ns
Step 3: Create the Role
kubectl create role pod-reader-role \
--verb=get,list,watch \
--resource=pods \
-n rbac-ns
Step 4: Create the RoleBinding
kubectl create rolebinding pod-reader-binding \
--role=pod-reader-role \
--serviceaccount=rbac-ns:pod-reader-sa \
-n rbac-ns
Note the --serviceaccount format: namespace:name.
Step 5: Create the Pod
apiVersion: v1
kind: Pod
metadata:
name: reader-pod
namespace: rbac-ns
spec:
serviceAccountName: pod-reader-sa
containers:
- name: kubectl
image: bitnami/kubectl:latest
command: ["sh", "-c", "kubectl get pods -n rbac-ns && sleep 3600"]
kubectl apply -f reader-pod.yaml
Step 6: Verify access
kubectl logs reader-pod -n rbac-ns
Expected: a list of Pods in rbac-ns (at minimum, reader-pod itself).
Step 7: Verify restricted access
kubectl exec reader-pod -n rbac-ns -- kubectl get pods -n default
Expected: Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:rbac-ns:pod-reader-sa" cannot list resource "pods" in API group "" in the namespace "default"
Common Pitfalls
- Using
ClusterRoleandClusterRoleBindinginstead ofRoleandRoleBinding. The task specifies namespace-scoped access. A ClusterRoleBinding would grant access across all namespaces, which violates the requirement that the ServiceAccount cannot list Pods in other namespaces. - Wrong
--serviceaccountformat. The format isnamespace:serviceAccountName. Omitting the namespace or reversing the order causes the binding to fail silently — the RoleBinding is created but binds to a non-existent ServiceAccount reference. - Forgetting
serviceAccountNamein the Pod spec. Without it, the Pod uses thedefaultServiceAccount, which does not have the pod-reader permissions. - Using
automountServiceAccountToken: false. If this is set (sometimes as a cluster default), the Pod cannot authenticate to the API server. Thekubectl get podscommand inside the container fails with a connection error instead of a Forbidden error.
Solution 16 — Deployment → Service → Ingress Chain
Time target: 4 minutes
Time-saving tip: Chain imperative commands. Create the Deployment, expose it, then write only the Ingress YAML. Two imperative commands plus one small YAML file — total time under 3 minutes.
Step 1: Create the namespace
kubectl create namespace app-stack
Step 2: Create the Deployment
kubectl create deployment frontend --image=nginx:1.25 --replicas=3 -n app-stack
Step 3: Expose with a Service
kubectl expose deployment frontend --name=frontend-svc --port=80 --target-port=80 -n app-stack
Step 4: Verify endpoints
kubectl get endpoints frontend-svc -n app-stack
Three IP addresses must appear in the endpoint list.
Step 5: Create the Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: frontend-ingress
namespace: app-stack
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: frontend.example.com
http:
paths:
- path: /app
pathType: Prefix
backend:
service:
name: frontend-svc
port:
number: 80
kubectl apply -f frontend-ingress.yaml
Step 6: Verify
kubectl describe ingress frontend-ingress -n app-stack
Expected: Rules section shows frontend.example.com → /app → frontend-svc:80. Annotations section shows the rewrite-target annotation.
Common Pitfalls
- Omitting the
rewrite-targetannotation. Without it, requests to/appare forwarded to the backend as/app, but nginx serves content at/. The response is a 404. Therewrite-target: /annotation strips the path prefix before forwarding. - Using
pathType: Exactinstead ofPrefix. Exact matches only/appliterally, not/app/or/app/anything. The task specifiesPrefix. - Forgetting
ingressClassName. Required in clusters with multiple Ingress controllers or where no default class is configured.
Solution 17 — Fix: Pod Stuck Pending (nodeSelector Mismatch)
Time target: 3 minutes
Time-saving tip: When a Pod is Pending, run kubectl describe pod and read the Events section first. The scheduler message tells you exactly what label or resource is missing. This avoids guessing.
Step 1: Create namespace and apply the Pod
kubectl create namespace schedule-ns
Apply the Pod manifest from the task.
Step 2: Diagnose
kubectl describe pod picky-pod -n schedule-ns
Events section shows:
Warning FailedScheduling ... 0/N nodes are available: N node(s) didn't match Pod's node affinity/selector. preemption: ...
The Pod requires a node with label disktype=ssd, but no node has that label.
Step 3: Find a node and apply the label
kubectl get nodes --show-labels
Pick any worker node (e.g., worker-1):
kubectl label node worker-1 disktype=ssd
Step 4: Wait for the Pod to schedule
kubectl get pod picky-pod -n schedule-ns -w
The Pod transitions from Pending to ContainerCreating to Running.
Step 5: Optional cleanup
kubectl label node worker-1 disktype-
The trailing - removes the label. The Pod continues running because nodeSelector is evaluated at scheduling time, not continuously.
Common Pitfalls
- Editing the Pod’s
nodeSelectorinstead of labeling the node. Both approaches work, but editing a running Pod’snodeSelectorrequires deleting and recreating the Pod (Pods are immutable for most spec fields). Labeling the node is faster and non-disruptive. - Labeling the wrong node. On multi-node clusters, label a worker node. Labeling a control-plane node that has taints may not help if the Pod lacks the corresponding tolerations.
- Typo in the label key or value. The label must be exactly
disktype=ssd(matching the Pod’snodeSelector).disk-type=ssdordisktype=SSDdo not match.
Solution 18 — Job with backoffLimit and Failure Handling
Time target: 4 minutes
Time-saving tip: Create the failing Job from a YAML manifest (copy from the task), watch it fail with kubectl get pods -w, then delete it and modify the command for the success version. The knowledge being tested is behavioral — understanding backoffLimit mechanics — not YAML complexity.
Step 1: Create the namespace
kubectl create namespace job-ns
Step 2: Create the failing Job
Apply the manifest from the task directly:
apiVersion: batch/v1
kind: Job
metadata:
name: failing-job
namespace: job-ns
spec:
backoffLimit: 3
template:
spec:
containers:
- name: worker
image: busybox:1.36
command: ["sh", "-c", "echo 'Attempting task...' && exit 1"]
restartPolicy: Never
kubectl apply -f failing-job.yaml
Step 3: Watch the failure behavior
kubectl get pods -n job-ns -w
Four Pods are created in sequence. Each one runs, prints “Attempting task…”, exits with code 1, and transitions to Error status. The back-off delay increases between each attempt.
Step 4: Verify the Job failed
kubectl describe job failing-job -n job-ns
The conditions section shows:
Type Status Reason
Failed True BackoffLimitExceeded
kubectl get job failing-job -n job-ns
The COMPLETIONS column shows 0/1 and no new Pods are being created.
Step 5: Delete and create the fixed Job
kubectl delete job failing-job -n job-ns
apiVersion: batch/v1
kind: Job
metadata:
name: success-job
namespace: job-ns
spec:
backoffLimit: 3
template:
spec:
containers:
- name: worker
image: busybox:1.36
command: ["sh", "-c", "echo 'Task completed' && exit 0"]
restartPolicy: Never
kubectl apply -f success-job.yaml
Step 6: Verify success
kubectl get job success-job -n job-ns
Expected: COMPLETIONS shows 1/1.
kubectl logs job/success-job -n job-ns
Expected output: Task completed
Common Pitfalls
- Confusing
backoffLimitwith total attempts.backoffLimit: 3means 3 retries after the first attempt, for a total of 4 Pod creations. The naming is misleading — it limits the number of back-offs (retries), not the total number of runs. - Using
restartPolicy: Always. Jobs require eitherNeverorOnFailure. UsingAlwayscauses an API validation error. WithNever, each failed attempt creates a new Pod. WithOnFailure, the same Pod restarts in-place (useful to avoid Pod proliferation). - Not deleting the failed Job before creating the success Job. If you try to apply a Job with the same name without deleting the old one, the API rejects it because Job names must be unique within a namespace.
Solution 19 — Blue-Green Deployment
Time target: 5 minutes
Time-saving tip: Create both Deployments and the Service before testing. The actual switchover is a single kubectl patch command. The whole task can be done in under 4 minutes if you write the YAML quickly.
Step 1: Create the namespace
kubectl create namespace bg-ns
Step 2: Create the blue Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-blue
namespace: bg-ns
spec:
replicas: 3
selector:
matchLabels:
app: webapp
version: blue
template:
metadata:
labels:
app: webapp
version: blue
spec:
containers:
- name: echo
image: hashicorp/http-echo:0.2.3
args: ["-listen=:5678", "-text=blue"]
ports:
- containerPort: 5678
kubectl apply -f app-blue.yaml
Step 3: Create the Service pointing to blue
apiVersion: v1
kind: Service
metadata:
name: webapp-svc
namespace: bg-ns
spec:
selector:
app: webapp
version: blue
ports:
- port: 80
targetPort: 5678
kubectl apply -f webapp-svc.yaml
Step 4: Verify blue
kubectl run test-blue --image=curlimages/curl --rm -it --restart=Never -n bg-ns -- curl http://webapp-svc.bg-ns.svc.cluster.local
Expected output: blue
Step 5: Create the green Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-green
namespace: bg-ns
spec:
replicas: 3
selector:
matchLabels:
app: webapp
version: green
template:
metadata:
labels:
app: webapp
version: green
spec:
containers:
- name: echo
image: hashicorp/http-echo:0.2.3
args: ["-listen=:5678", "-text=green"]
ports:
- containerPort: 5678
kubectl apply -f app-green.yaml
Wait for green Pods to be ready:
kubectl rollout status deployment/app-green -n bg-ns
Step 6: Switch the Service to green
kubectl patch service webapp-svc -n bg-ns -p '{"spec":{"selector":{"version":"green"}}}'
This changes only the version selector value. The app: webapp selector remains, so the Service now matches green Pods (which have both app: webapp and version: green).
Step 7: Verify green
kubectl run test-green --image=curlimages/curl --rm -it --restart=Never -n bg-ns -- curl http://webapp-svc.bg-ns.svc.cluster.local
Expected output: green
Step 8: Confirm both Deployments are running
kubectl get deployments -n bg-ns
Both app-blue and app-green must show 3/3 ready replicas. The blue Deployment remains available for instant rollback by patching the Service selector back to version: blue.
Common Pitfalls
- Removing the
app: webapplabel from the selector when patching. Thekubectl patchcommand with-p '{"spec":{"selector":{"version":"green"}}}'merges the new selector with the existing one. It does not replace the entire selector. The result isapp: webapp, version: green, which is correct. If you usedkubectl editand accidentally deleted theapp: webappline, no Pods match. - Using different labels on blue and green Deployments. Both must share the
app: webapplabel. Theversionlabel is what differentiates them. Without the commonapplabel, the Service cannot target both with a single selector pattern. - Not waiting for green Pods to be ready before switching. If you switch the Service while green Pods are still starting, the curl test may fail or return connection errors. Always confirm all green replicas are ready before patching the Service.
- Deleting the blue Deployment after switchover. The task requires both Deployments to remain running. Blue serves as the rollback target.
Solution 20 — Full-Stack Aggregate Task
Time target: 8 minutes
Time-saving tip: Work through this task in layers: namespace and quota first, then data resources (ConfigMap and Secret), then the Deployment (which consumes them), then the Service, then the Ingress. Each layer depends on the previous one. Use imperative commands wherever possible to save time — ConfigMap, Secret, Deployment, and Service all support imperative creation.
Step 1: Create namespace and ResourceQuota
kubectl create namespace fullstack-ns
apiVersion: v1
kind: ResourceQuota
metadata:
name: stack-quota
namespace: fullstack-ns
spec:
hard:
pods: "10"
requests.cpu: "2"
requests.memory: 2Gi
kubectl apply -f stack-quota.yaml
Verify:
kubectl describe resourcequota stack-quota -n fullstack-ns
Step 2: Create the ConfigMap
kubectl create configmap app-settings \
--from-literal=APP_ENV=production \
--from-literal=LOG_LEVEL=warn \
--from-literal=MAX_CONNECTIONS=100 \
-n fullstack-ns
Step 3: Create the Secret
kubectl create secret generic app-secrets \
--from-literal=API_KEY=super-secret-key-2026 \
-n fullstack-ns
Step 4: Create the Deployment
Because of the ResourceQuota on requests.cpu and requests.memory, every container must specify resource requests. The Deployment manifest:
apiVersion: apps/v1
kind: Deployment
metadata:
name: fullstack-app
namespace: fullstack-ns
spec:
replicas: 3
selector:
matchLabels:
app: fullstack
tier: frontend
template:
metadata:
labels:
app: fullstack
tier: frontend
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "200m"
memory: "256Mi"
envFrom:
- configMapRef:
name: app-settings
env:
- name: API_KEY
valueFrom:
secretKeyRef:
name: app-secrets
key: API_KEY
kubectl apply -f fullstack-app.yaml
Wait for the rollout:
kubectl rollout status deployment/fullstack-app -n fullstack-ns
Step 5: Create the Service
kubectl expose deployment fullstack-app --name=fullstack-svc --port=80 --target-port=80 -n fullstack-ns
The kubectl expose command automatically picks up the app: fullstack selector from the Deployment. Verify it also includes tier: frontend or confirm endpoints are populated:
kubectl get endpoints fullstack-svc -n fullstack-ns
Three IP addresses must appear.
Note: kubectl expose uses all the Deployment’s selector labels. If the generated Service selector is app: fullstack, tier: frontend, the Service still matches the correct Pods. However, the task only requires the selector to include app: fullstack. Both configurations are valid as long as the endpoints resolve correctly.
Step 6: Create the Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: fullstack-ingress
namespace: fullstack-ns
spec:
ingressClassName: nginx
rules:
- host: fullstack.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: fullstack-svc
port:
number: 80
kubectl apply -f fullstack-ingress.yaml
Step 7: Full verification
# All resources exist
kubectl get quota,configmap,secret,deployment,service,ingress -n fullstack-ns
Expected: stack-quota, app-settings, app-secrets, fullstack-app, fullstack-svc, and fullstack-ingress all present.
# Deployment has 3 ready replicas
kubectl get deployment fullstack-app -n fullstack-ns
Expected: 3/3 in the READY column.
# Environment variables are set
kubectl exec deploy/fullstack-app -n fullstack-ns -- env | grep -E "APP_ENV|LOG_LEVEL|MAX_CONNECTIONS|API_KEY"
Expected:
APP_ENV=production
LOG_LEVEL=warn
MAX_CONNECTIONS=100
API_KEY=super-secret-key-2026
# Service has 3 endpoints
kubectl get endpoints fullstack-svc -n fullstack-ns
Expected: 3 IP:port entries.
# Ingress is configured
kubectl describe ingress fullstack-ingress -n fullstack-ns
Expected: fullstack.example.com → / → fullstack-svc:80.
Common Pitfalls
- Forgetting resource requests because of the ResourceQuota. When a ResourceQuota specifies
requests.cpuorrequests.memory, every container in the namespace must declare those fields. Pods without resource requests are rejected by the admission controller. This catches candidates who usekubectl create deploymentimperatively without adding resources afterward. - Using
envFromfor the Secret. The task specifiesenvFromwithconfigMapReffor the ConfigMap but individualsecretKeyReffor the Secret. Mixing them up (usingenvFromwithsecretReffor the Secret) technically works but does not match the requirements. - Deploying resources in the wrong namespace. With 6 resources to create, forgetting
-n fullstack-nson one command puts that resource in the default namespace. The verification commands checkfullstack-nsexclusively. - Not verifying end-to-end after each layer. Check the Deployment status before creating the Service. Check the endpoints before creating the Ingress. Catching errors early prevents cascading debugging time.
- Exceeding the ResourceQuota. Three replicas at 100m CPU and 128Mi memory each total 300m CPU and 384Mi memory — well within the 2 CPU and 2Gi limits. But if you set higher values per container (e.g., 1 CPU each for 3 replicas = 3 CPU), the Deployment fails to create Pods because the quota caps total CPU requests at 2.