CKA — Mock Exam Part 1
Mock Exam Instructions
Welcome to Mock Exam Part 1. This simulates a real CKA exam experience.
Rules:
- 8 questions — try to complete all of them in 60 minutes
- Use only kubernetes.io/docs as a reference (no other tabs)
- Set up your aliases first, just like in the real exam
- For each question, switch to the specified context before starting
- Verify your work after each question
- Don’t peek at the answers until you’ve attempted the question
Total points: 50% (this is half of a full exam)
Setup before starting:
alias k=kubectl
export do="--dry-run=client -o yaml"
export now="--force --grace-period=0"
source <(kubectl completion bash)
complete -o default -F __start_kubectl k
Start your timer now.
Question 1 (7%) — RBAC Setup
Context: kubectl config use-context cluster1
Create the following RBAC configuration:
- Create a namespace called
team-blue - Create a Role named
deploy-managerin namespaceteam-bluethat allowscreate,get, andlistonDeployments - Create a RoleBinding named
deploy-manager-bindingin namespaceteam-bluethat binds the Roledeploy-managerto userdev-user - Verify that
dev-usercan create Deployments inteam-bluebut cannot delete them
Show Answer
Solution
Step 1: Switch context
kubectl config use-context cluster1
Step 2: Create the namespace
k create namespace team-blue
Step 3: Create the Role
k create role deploy-manager \
--verb=create,get,list \
--resource=deployments \
-n team-blue
Step 4: Create the RoleBinding
k create rolebinding deploy-manager-binding \
--role=deploy-manager \
--user=dev-user \
-n team-blue
Step 5: Verify
# Should return "yes"
k auth can-i create deployments --as dev-user -n team-blue
# Should return "yes"
k auth can-i get deployments --as dev-user -n team-blue
# Should return "yes"
k auth can-i list deployments --as dev-user -n team-blue
# Should return "no"
k auth can-i delete deployments --as dev-user -n team-blue
Key points:
- Role and RoleBinding must be in the same namespace
- Use
--asflag withkubectl auth can-ito test permissions - Don’t forget
-n team-blueon every command
Question 2 (7%) — Cluster Upgrade
Context: kubectl config use-context cluster3
Worker node cluster3-worker1 is currently running Kubernetes v1.30.0. Upgrade it to v1.31.0.
- Drain the node properly before upgrading
- Upgrade kubeadm, then kubelet and kubectl
- Uncordon the node after the upgrade
- Verify the node shows v1.31.0
Show Answer
Solution
Step 1: Switch context
kubectl config use-context cluster3
Step 2: Drain the worker node (from controlplane)
k drain cluster3-worker1 --ignore-daemonsets --delete-emptydir-data
Step 3: SSH to the worker node
ssh cluster3-worker1
Step 4: Upgrade kubeadm
apt-get update
apt-get install -y kubeadm=1.31.0-1.1
Step 5: Run kubeadm upgrade on the worker
kubeadm upgrade node
Step 6: Upgrade kubelet and kubectl
apt-get install -y kubelet=1.31.0-1.1 kubectl=1.31.0-1.1
Step 7: Restart kubelet
systemctl daemon-reload
systemctl restart kubelet
Step 8: Exit back to controlplane
exit
Step 9: Uncordon the node
k uncordon cluster3-worker1
Step 10: Verify
k get nodes
The node should show Ready status and version v1.31.0.
Key points:
- Always drain before upgrading
kubeadm upgrade node(notkubeadm upgrade apply) on worker nodes- Restart kubelet after package upgrade
- Uncordon from the controlplane, not from the worker node
Question 3 (7%) — etcd Backup
Context: kubectl config use-context cluster2
Take a snapshot of the etcd database and save it to /opt/etcd-backup.db.
Use the certificates from the etcd Pod running on the controlplane.
Show Answer
Solution
Step 1: Switch context
kubectl config use-context cluster2
Step 2: Find the etcd certificate paths
k describe pod etcd-controlplane -n kube-system
Look for these flags in the command section:
--cert-file→ used as--cert--key-file→ used as--key--trusted-ca-file→ used as--cacert--listen-client-urls→ used as--endpoints
Typical paths:
--cert-file=/etc/kubernetes/pki/etcd/server.crt
--key-file=/etc/kubernetes/pki/etcd/server.key
--trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
--listen-client-urls=https://127.0.0.1:2379
Step 3: Take the snapshot
ETCDCTL_API=3 etcdctl snapshot save /opt/etcd-backup.db \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
--endpoints=https://127.0.0.1:2379
Step 4: Verify the snapshot
ETCDCTL_API=3 etcdctl snapshot status /opt/etcd-backup.db --write-table
You should see a table with hash, revision, total keys, and total size.
Key points:
- Always use
ETCDCTL_API=3 - Get exact cert paths from the etcd Pod spec — don’t guess
- The
--cacertis the etcd CA, not the Kubernetes API CA - Verify the snapshot was created successfully
Question 4 (4%) — NetworkPolicy
Context: kubectl config use-context cluster1
In namespace secure:
- Create a default-deny-all ingress NetworkPolicy named
deny-all-ingress - Create a second NetworkPolicy named
allow-frontendthat allows ingress to Pods with labelapp=backendonly from Pods with labelapp=frontendon port 80
Show Answer
Solution
Step 1: Switch context
kubectl config use-context cluster1
Step 2: Ensure namespace exists
k create namespace secure --dry-run=client -o yaml | k apply -f -
Step 3: Create default deny-all ingress NetworkPolicy
cat <<EOF | k apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-ingress
namespace: secure
spec:
podSelector: {}
policyTypes:
- Ingress
EOF
This selects ALL Pods in the namespace (empty podSelector) and defines Ingress policy type with no ingress rules — effectively denying all ingress.
Step 4: Create allow-frontend NetworkPolicy
cat <<EOF | k apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend
namespace: secure
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 80
EOF
Step 5: Verify
k get networkpolicy -n secure
k describe networkpolicy allow-frontend -n secure
Key points:
- Empty
podSelector: {}means “all Pods in the namespace” spec.podSelector= which Pods this policy APPLIES TO (the target)ingress.from.podSelector= which Pods are ALLOWED to send traffic (the source)- Don’t confuse these two selectors — it’s a common mistake
Question 5 (7%) — Troubleshoot Broken Node
Context: kubectl config use-context cluster3
Node cluster3-worker2 is showing NotReady status. Investigate and fix the issue so that the node becomes Ready again.
Show Answer
Solution
Step 1: Switch context
kubectl config use-context cluster3
Step 2: Confirm node status
k get nodes
You should see cluster3-worker2 in NotReady state.
Step 3: SSH to the node
ssh cluster3-worker2
Step 4: Check kubelet status
systemctl status kubelet
If the output shows inactive (dead) or failed, kubelet is not running.
Step 5: Check kubelet logs for errors
journalctl -u kubelet --no-pager -l | tail -50
Look for error messages. Common issues:
- kubelet service not started
- Wrong configuration file path
- Certificate errors
Step 6: Start kubelet
systemctl start kubelet
systemctl enable kubelet
Step 7: Verify kubelet is running
systemctl status kubelet
Status should show active (running).
Step 8: Exit and verify from controlplane
exit
k get nodes
cluster3-worker2 should now show Ready.
Key points:
- Always check
systemctl status kubeletfirst on NotReady nodes - Use
journalctl -u kubeletfor detailed error logs - Common fixes: start kubelet, fix config path, fix certificate paths
- After starting, also
enableit so it survives reboot
Question 6 (4%) — PV, PVC, and Pod
Context: kubectl config use-context cluster1
- Create a PersistentVolume named
data-pvwith:- Capacity: 10Gi
- Access mode: ReadWriteOnce
- Host path:
/opt/data - No storageClassName (set to empty string)
- Create a PersistentVolumeClaim named
data-pvcin namespacedefaultwith:- Request: 5Gi
- Access mode: ReadWriteOnce
- No storageClassName (set to empty string)
- Create a Pod named
data-podthat mounts the PVC at/usr/share/data
Show Answer
Solution
Step 1: Switch context
kubectl config use-context cluster1
Step 2: Create the PersistentVolume
cat <<EOF | k apply -f -
apiVersion: v1
kind: PersistentVolume
metadata:
name: data-pv
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /opt/data
storageClassName: ""
EOF
Step 3: Create the PersistentVolumeClaim
cat <<EOF | k apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: ""
EOF
Step 4: Verify PVC is Bound
k get pv
k get pvc
data-pvc should be Bound to data-pv.
Step 5: Create the Pod
cat <<EOF | k apply -f -
apiVersion: v1
kind: Pod
metadata:
name: data-pod
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: data-volume
mountPath: /usr/share/data
volumes:
- name: data-volume
persistentVolumeClaim:
claimName: data-pvc
EOF
Step 6: Verify
k get pod data-pod
k describe pod data-pod | grep -A5 Mounts
Key points:
- PV and PVC must have matching
accessModes - PVC storage request must be ≤ PV capacity
- Setting
storageClassName: ""ensures no dynamic provisioning — the PVC binds to a manually created PV - The volume name in
volumesmust match the one referenced involumeMounts
Question 7 (7%) — Deployment with Advanced Specs
Context: kubectl config use-context cluster2
Create a Deployment named web-app in namespace production with the following specifications:
- Image:
nginx:1.27 - Replicas: 3
- Container port: 80
- Resource requests: CPU 100m, Memory 128Mi
- Resource limits: CPU 200m, Memory 256Mi
- Node selector:
disk=ssd - Readiness probe: HTTP GET on path
/port 80, initial delay 5s, period 10s
Show Answer
Solution
Step 1: Switch context
kubectl config use-context cluster2
Step 2: Create the namespace
k create namespace production --dry-run=client -o yaml | k apply -f -
Step 3: Generate base YAML
k create deployment web-app --image=nginx:1.27 --replicas=3 --port=80 \
-n production $do > /tmp/web-app.yaml
Step 4: Edit the YAML to add resource limits, nodeSelector, and readiness probe
vi /tmp/web-app.yaml
The final YAML should look like this:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
nodeSelector:
disk: ssd
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "200m"
memory: "256Mi"
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 10
Step 5: Apply
k apply -f /tmp/web-app.yaml
Step 6: Verify
k get deployment web-app -n production
k describe deployment web-app -n production
k get pods -n production -l app=web-app
Check that:
- 3 replicas are created
- Resource limits are set
- Readiness probe is configured
- nodeSelector is present
Key points:
- Generate base YAML with imperative command, then add complex fields manually
nodeSelectorgoes underspec.template.spec(same level ascontainers)readinessProbegoes inside the container spec- Resource requests and limits go inside the container spec under
resources
Question 8 (7%) — Fix a Broken Service
Context: kubectl config use-context cluster1
A Deployment named api-server with 2 replicas exists in namespace apps. A Service named api-service also exists in namespace apps, but it has no endpoints. Users report they cannot connect to the API.
Diagnose the issue and fix it so that the Service correctly routes traffic to the api-server Pods.
Show Answer
Solution
Step 1: Switch context
kubectl config use-context cluster1
Step 2: Check the Service
k get svc api-service -n apps
k describe svc api-service -n apps
Note the selector on the Service. For example, it might show:
Selector: app=api-svc
Step 3: Check the endpoints
k get endpoints api-service -n apps
If endpoints show <none>, the selector isn’t matching any Pods.
Step 4: Check the Pod labels
k get pods -n apps --show-labels
You might see the Pods have labels like:
app=api-server
Step 5: Compare
- Service selector:
app=api-svc(wrong) - Pod labels:
app=api-server(correct)
The Service selector doesn’t match the Pod labels.
Step 6: Fix the Service selector
k edit svc api-service -n apps
In the editor, find the selector section and change:
selector:
app: api-svc
to:
selector:
app: api-server
Save and exit (:wq).
Step 7: Verify
# Endpoints should now show Pod IPs
k get endpoints api-service -n apps
# Test connectivity
k run test-pod --image=busybox:1.36 --rm -it --restart=Never -n apps \
-- wget -qO- http://api-service
Key points:
- “No endpoints” almost always means selector mismatch
- Always compare Service selector with Pod labels
- Use
--show-labelsto quickly see all Pod labels - After fixing, verify endpoints are populated before moving on
Scoring
Grade yourself honestly:
| Question | Points | Your Score |
|---|---|---|
| Q1 — RBAC Setup | 7% | ___ |
| Q2 — Cluster Upgrade | 7% | ___ |
| Q3 — etcd Backup | 7% | ___ |
| Q4 — NetworkPolicy | 4% | ___ |
| Q5 — Troubleshoot Node | 7% | ___ |
| Q6 — PV, PVC, Pod | 4% | ___ |
| Q7 — Advanced Deployment | 7% | ___ |
| Q8 — Fix Service | 7% | ___ |
| Total | 50% | ___ |
Scoring guide:
- Full marks — Completed correctly within the time limit
- Partial marks — Partially correct (e.g., Role created but RoleBinding wrong)
- Zero — Not attempted or fundamentally wrong
Benchmark:
- 40–50%: You’re on track. Keep practicing.
- 30–40%: Need more practice. Review weak areas.
- Below 30%: Go back and review the relevant chapters.