CKA — Mock Exam Part 1

9 minute read

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:

  1. Create a namespace called team-blue
  2. Create a Role named deploy-manager in namespace team-blue that allows create, get, and list on Deployments
  3. Create a RoleBinding named deploy-manager-binding in namespace team-blue that binds the Role deploy-manager to user dev-user
  4. Verify that dev-user can create Deployments in team-blue but 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 --as flag with kubectl auth can-i to test permissions
  • Don’t forget -n team-blue on 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 (not kubeadm 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 --cacert is 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:

  1. Create a default-deny-all ingress NetworkPolicy named deny-all-ingress
  2. Create a second NetworkPolicy named allow-frontend that allows ingress to Pods with label app=backend only from Pods with label app=frontend on 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 kubelet first on NotReady nodes
  • Use journalctl -u kubelet for detailed error logs
  • Common fixes: start kubelet, fix config path, fix certificate paths
  • After starting, also enable it so it survives reboot

Question 6 (4%) — PV, PVC, and Pod

Context: kubectl config use-context cluster1

  1. Create a PersistentVolume named data-pv with:
    • Capacity: 10Gi
    • Access mode: ReadWriteOnce
    • Host path: /opt/data
    • No storageClassName (set to empty string)
  2. Create a PersistentVolumeClaim named data-pvc in namespace default with:
    • Request: 5Gi
    • Access mode: ReadWriteOnce
    • No storageClassName (set to empty string)
  3. Create a Pod named data-pod that 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 volumes must match the one referenced in volumeMounts

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
  • nodeSelector goes under spec.template.spec (same level as containers)
  • readinessProbe goes 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-labels to 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.

Next: Mock Exam — Part 2 →

Updated: