"""Reference solutions, keyed by task id. Rendered with the same %{param} / {WS} / {W1} substitution as the task text. Shown only after a task has been graded (drills) or the session submitted (exams). These solutions provide step-by-step instructions, Kubernetes concept explanations, imperative commands, and verification checks for exam success. """ S = {} # ================================================================= ARCH ==== # Domain 1: Cluster Architecture, Installation & Configuration (25%) # =========================================================================== S[1] = """# Step 1: Create the ServiceAccount in the target namespace k -n rbac-app create sa deploy-bot # Step 2: Create a Role granting specified verbs on deployments.apps # Verbs: get, list, watch, create, update, patch, delete k -n rbac-app create role deploy-manager \\ --verb=get,list,watch,create,update,patch,delete \\ --resource=deployments.apps # Step 3: Bind the Role to the ServiceAccount k -n rbac-app create rolebinding deploy-bot-binding \\ --role=deploy-manager \\ --serviceaccount=rbac-app:deploy-bot # Step 4: Verify permissions using 'kubectl auth can-i' k auth can-i create deployments -n rbac-app --as=system:serviceaccount:rbac-app:deploy-bot # Expected: yes k auth can-i delete pods -n rbac-app --as=system:serviceaccount:rbac-app:deploy-bot # Expected: no """ S[2] = """# Step 1: Access the control plane node {SHELL_CP} # Step 2: Take the etcd snapshot with TLS certificates and save to the target path # Certificates are located in /etc/kubernetes/pki/etcd/ ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 \\ --cacert=/etc/kubernetes/pki/etcd/ca.crt \\ --cert=/etc/kubernetes/pki/etcd/server.crt \\ --key=/etc/kubernetes/pki/etcd/server.key \\ snapshot save /opt/cka/etcd-snapshot.db # Step 3: Verify snapshot status and integrity etcdutl snapshot status /opt/cka/etcd-snapshot.db -w table # Note: on a cluster where etcdctl is only inside the static pod, run it there. The etcd image # is distroless (no sh, no cat, no tar), so kubectl cp cannot get the file out - write it into # /var/lib/etcd instead, which is a hostPath, and move it on the node: # kubectl -n kube-system exec etcd-{CP} -- etcdctl ... snapshot save /var/lib/etcd/snap.db # then on the node: mv /var/lib/etcd/snap.db /opt/cka/etcd-snapshot.db # ./etcd-tools.sh installs etcdctl/etcdutl on the control-plane node (setup.sh already does). """ S[3] = """# Step 1: Create the overlay directory structure mkdir -p {WS}/q03/overlays/prod && cd {WS}/q03/overlays/prod # Step 2: Define the target namespace manifest cat > namespace.yaml < kustomization.yaml < /etc/kubernetes/manifests/static-web.yaml < {WS}/q06/apiserver-expiry.txt # Part B: Issue and approve CertificateSigningRequest (CSR) for user Jane # Step 3: Generate private key and CSR openssl genrsa -out jane.key 2048 openssl req -new -key jane.key -subj "/CN=jane" -out jane.csr # Step 4: Submit Kubernetes CSR resource cat < jane.crt # Step 6: Bind user Jane to the default cluster role 'view' k -n default create rolebinding jane-view --clusterrole=view --user=jane """ S[21] = """# Step 1: Create the target namespace k create ns %{ns} # Step 2: Install the Helm chart with overridden replicaCount helm install %{rel} {WS}/q21/chart -n %{ns} --set replicaCount=%{n} # Step 3: Upgrade the release to NodePort while preserving existing values helm upgrade %{rel} {WS}/q21/chart -n %{ns} --reuse-values --set service.type=NodePort # Step 4: Verify the release history shows revision 2 helm -n %{ns} history %{rel} """ S[33] = """# Step 1: Create the aggregating ClusterRole with aggregationRule selector cat < {WS}/q34/path.txt # Step 4: Create a sample Custom Resource using the discovered field cat <:6443 --token --discovery-token-ca-cert-hash sha256:) # Copy the printed command into {WS}/q40/join.sh # Step 4: Verify active tokens kubeadm token list """ S[41] = """# Step 1: Check Kubernetes Server version (API server) k version # Step 2: Extract Kubelet and Container Runtime versions for the target node k get node %{node} -o jsonpath='{.status.nodeInfo.kubeletVersion}{"\\n"}{.status.nodeInfo.containerRuntimeVersion}{"\\n"}' # Step 3: Extract etcd image tag from the static pod spec k -n kube-system get pod -l component=etcd -o jsonpath='{.items[0].spec.containers[0].image}' # Parse the image tag (everything after the colon) """ # Hard Architecture Tasks S[101] = """# Step 1: Inspect bindings for the ServiceAccount to find and remove over-privileged bindings k get clusterrolebinding -o wide | grep %{sa} k delete clusterrolebinding %{sa}-legacy-admin # Step 2: Create a restricted Role for deployments and replicasets k -n %{ns} create role %{sa}-role --verb=get,list,watch,patch --resource=deployments.apps,replicasets.apps # Step 3: Add pods/exec permission rule to the Role k -n %{ns} patch role %{sa}-role --type=json -p='[{"op":"add","path":"/rules/-","value":{"apiGroups":[""],"resources":["pods/exec"],"verbs":["create"]}}]' # Step 4: Bind the Role to the ServiceAccount k -n %{ns} create rolebinding %{sa}-rb --role=%{sa}-role --serviceaccount=%{ns}:%{sa} # Step 5: Create a ClusterRole with node list privileges and bind it k create clusterrole %{sa}-nodes --verb=list --resource=nodes k create clusterrolebinding %{sa}-crb --clusterrole=%{sa}-nodes --serviceaccount=%{ns}:%{sa} # Step 6: Verify effective permissions k auth can-i --list -n %{ns} --as=system:serviceaccount:%{ns}:%{sa} """ S[102] = """# Step 1: Access the control plane node {SHELL_CP} # Step 2: Restore snapshot into a new data directory etcdutl snapshot restore /opt/cka/backup.db --data-dir /var/lib/etcd-restore # Step 3: Update etcd static pod hostPath volume to point to the restored directory # Edit /etc/kubernetes/manifests/etcd.yaml: # Change: # volumes: # - hostPath: # path: /var/lib/etcd-restore # name: etcd-data vi /etc/kubernetes/manifests/etcd.yaml # Step 4: Monitor static pod recreation and verify restored objects watch crictl ps kubectl -n default get cm %{cm} """ S[103] = """# Step 1: Identify the edge node matching the label selector k get nodes -l tier=edge # Step 2: Access the target node {SHELL_W3} # Step 3: Check kubelet configuration for custom staticPodPath grep staticPodPath /var/lib/kubelet/config.yaml # Step 4: Place the pod manifest directly into the configured staticPodPath cat > /%{pod}.yaml < %{user}.crt # Step 3: Grant permissions via RoleBinding k -n %{ns} create rolebinding %{user}-edit --clusterrole=edit --user=%{user} # Step 4: Construct self-contained kubeconfig with embedded certificates KC={WS}/q104/%{user}.kubeconfig k config view --minify --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}' | base64 -d > ca.crt SERVER=$(k config view --minify -o jsonpath='{.clusters[0].cluster.server}') kubectl --kubeconfig $KC config set-cluster lab --server=$SERVER --certificate-authority=ca.crt --embed-certs kubectl --kubeconfig $KC config set-credentials %{user} --client-certificate=%{user}.crt --client-key=%{user}.key --embed-certs kubectl --kubeconfig $KC config set-context %{user}@lab --cluster=lab --user=%{user} --namespace=%{ns} kubectl --kubeconfig $KC config use-context %{user}@lab # Step 5: Test the kubeconfig kubectl --kubeconfig $KC get pods """ S[105] = """# Step 1: Create directory for prod overlay mkdir -p {WS}/q105/overlays/prod && cd {WS}/q105/overlays/prod # Step 2: Create replica patch manifest cat > replicas.yaml < envfrom.yaml < kustomization.yaml < {WS}/q106/rejected.txt # Step 4: Verify default values on omitted fields printf 'apiVersion: deploy.example.com/v1\\nkind: %{kind}\\nmetadata: {name: defaulted}\\nspec: {replicas: 2, image: nginx:1.27}\\n' | k apply -f - """ S[122] = """# Step 1: Access control plane node {SHELL_CP} # Step 2: Renew all cluster certificates via kubeadm kubeadm certs renew all # Step 3: Restart static pods to load renewed certificates mv /etc/kubernetes/manifests/*.yaml /tmp/ && sleep 20 && mv /tmp/*.yaml /etc/kubernetes/manifests/ # Step 4: Update admin kubeconfig from newly generated credentials cp /etc/kubernetes/admin.conf ~/.kube/config # Step 5: Confirm new expiration dates kubeadm certs check-expiration | grep '^apiserver ' """ # ================================================================= WORK ==== # Domain 2: Workloads & Scheduling (15%) # =========================================================================== S[7] = """# Step 1: Update container image to nginx:1.27 k -n apps set image deploy web-frontend nginx=nginx:1.27 # Step 2: Scale deployment to 4 replicas k -n apps scale deploy web-frontend --replicas=4 # Step 3: Patch rolling update strategy (maxSurge=1, maxUnavailable=0) k -n apps patch deploy web-frontend -p '{"spec":{"strategy":{"rollingUpdate":{"maxSurge":1,"maxUnavailable":0}}}}' # Step 4: Monitor rollout until complete k -n apps rollout status deploy web-frontend """ S[8] = """# Step 1: Create pod manifest with shared emptyDir volume and sidecar pattern cat <> /var/log/app.log; sleep 2; done"] volumeMounts: [{name: logs, mountPath: /var/log}] - name: shipper image: busybox:1.36 command: [sh, -c, "tail -f /var/log/app.log"] volumeMounts: [{name: logs, mountPath: /var/log}] EOF # Step 2: Verify sidecar container reads the log k -n apps logs log-shipper -c shipper """ S[9] = """# Step 1: Generate initial deployment manifest with dry-run k -n apps create deploy cache --image=redis:7-alpine --replicas=2 $do > cache.yaml # Step 2: Add nodeAffinity (disktype=ssd) and container resources (requests/limits) # Under spec.template.spec: # affinity: # nodeAffinity: # requiredDuringSchedulingIgnoredDuringExecution: # nodeSelectorTerms: # - matchExpressions: [{key: disktype, operator: In, values: [ssd]}] # Under containers[0]: # resources: # requests: {cpu: 100m, memory: 64Mi} # limits: {cpu: 200m, memory: 128Mi} cat < cj.yaml # Step 2: Add concurrencyPolicy, activeDeadlineSeconds, and history limits: # spec: # concurrencyPolicy: Forbid # successfulJobsHistoryLimit: 3 # failedJobsHistoryLimit: 1 # jobTemplate: # spec: # activeDeadlineSeconds: 30 # template: # spec: # restartPolicy: Never cat < /work/index.html"] volumeMounts: [{name: content, mountPath: /work}] containers: - name: web image: nginx:1.25 volumeMounts: [{name: content, mountPath: /usr/share/nginx/html}] EOF # Step 2: Verify web server serves the file created by init container k -n %{ns} exec %{pod} -c web -- cat /usr/share/nginx/html/index.html """ S[29] = """# Step 1: Create the new version of the immutable ConfigMap k -n %{ns} create cm %{cm}-v2 --from-literal=mode=new k -n %{ns} patch cm %{cm}-v2 -p '{"immutable":true}' # Step 2: Update the Deployment volume reference to the new ConfigMap k -n %{ns} patch deploy %{dep} --type=json -p='[{"op":"replace","path":"/spec/template/spec/volumes/0/configMap/name","value":"%{cm}-v2"}]' # Step 3: Verify rolling update finishes k -n %{ns} rollout status deploy %{dep} """ S[31] = """# Step 1: Create Job with completions, parallelism, and backoffLimit cat < {WS}/q110/always.txt # Step 2: Count Running pods per node k get pods -A --field-selector=status.phase=Running -o jsonpath='{range .items[*]}{.spec.nodeName}{"\n"}{end}' | sort | uniq -c > {WS}/q110/per-node.txt # Step 3: Extract sorted unique container images used across kube-system k -n kube-system get pods -o jsonpath='{.items[*].spec.containers[*].image}' | tr ' ' '\n' | sort -u > {WS}/q110/images.txt """ S[121] = """# Step 1: Confirm current context namespace k config view --minify -o jsonpath='{.contexts[0].context.namespace}' # Step 2: Perform all required operations without specifying -n, relying on context namespace k create deploy %{dep} --image=nginx:1.27 --replicas=2 k set env deploy/%{dep} APP_ENV=production k expose deploy %{dep} --port=80 --target-port=80 """ # ================================================================= NET ===== # Domain 3: Services & Networking (20%) # =========================================================================== S[11] = """# Step 1: Create NetworkPolicy in namespace 'marketing' # Allow Ingress on port 80 only from pods labeled 'role: frontend' cat < svc.yaml # Step 2: Set specific nodePort 30080 in spec.ports[0].nodePort cat <..svc.cluster.local k -n default run dns-test --image=busybox:1.36 --rm -it --restart=Never -- \\ nslookup web-service.apps.svc.cluster.local # Step 2: Query Pod DNS (IP with dashes): ..pod.cluster.local k -n default run dns-test --image=busybox:1.36 --rm -it --restart=Never -- \\ nslookup 10-244-1-5.apps.pod.cluster.local """ S[22] = """# Step 1: Apply GatewayClass and Gateway resources cat < {WS}/q112/fp.txt """ S[113] = """# Step 1: Create GatewayClass, Gateway with multi-host listeners, and canary HTTPRoute cat < forward . /etc/resolv.conf # Step 4: Restart CoreDNS deployment and test DNS resolution k -n kube-system rollout restart deploy coredns k run dns-check --image=busybox:1.36 --rm -it --restart=Never -- nslookup kubernetes.default """ # ================================================================= STOR ==== # Domain 4: Storage (10%) # =========================================================================== S[15] = """# Step 1: Create PersistentVolume with hostPath, Retain policy, and manual storageClass cat < /data/marker.txt"], volumeMounts: [{name: d, mountPath: /data}]}] EOF # Step 3: Wait for writer pod completion k -n %{ns} wait --for=jsonpath='{.status.phase}'=Succeeded pod/writer # Step 4: Create reader Pod mounting the same claim to verify data persists cat < legacy.yaml # Edit legacy.yaml: replace claimName: old-data with claimName: new-data k -n %{ns} delete pod legacy --now && k apply -f legacy.yaml # Step 5: Clean up old PVC (PV remains Retained) k -n %{ns} delete pvc old-data """ # =============================================================== TSHOOT ==== # Domain 5: Troubleshooting (30%) # =========================================================================== S[17] = """# Step 1: Check node status k get node {W2} # Status: NotReady # Step 2: SSH into the broken worker node {SHELL_W2} # Step 3: Check kubelet service status and journal logs systemctl status kubelet # Service inactive/disabled systemctl enable --now kubelet # Step 4: Verify kubelet successfully connects and registers node journalctl -u kubelet -f --no-pager # Look for: "Successfully registered node" # Step 5: Exit node shell and verify Ready state from cluster # k get nodes """ S[18] = """# Step 1: Diagnose Deployment failure (ImagePullBackOff) k -n troubleshoot describe deploy payments | grep -i image # Notice invalid image tag: nginx:1.99-nope # Step 2: Fix image on the deployment k -n troubleshoot set image deploy payments payments=nginx:1.27 # Step 3: Inspect Service definition for mismatched selector and targetPort k -n troubleshoot get svc payments -o yaml # Discovered issues: selector is 'app: payment' (typo) and targetPort is 8080 (container port is 80) # Step 4: Patch the Service selector and targetPort k -n troubleshoot patch svc payments -p '{"spec":{"selector":{"app":"payments"},"ports":[{"port":80,"targetPort":80}]}}' # Step 5: Verify Service endpoints k -n troubleshoot get endpointslices -l kubernetes.io/service-name=payments """ S[19] = """# Step 1: Capture previous container logs to locate crash reason k -n troubleshoot logs crasher --previous | tee {WS}/q19/crasher.log # Log output: "FATAL: required environment variable DB_HOST is not set" # Step 2: Export pod manifest k -n troubleshoot get pod crasher -o yaml > crasher.yaml # Step 3: Edit crasher.yaml: # Add under spec.containers[0]: # env: # - name: DB_HOST # value: db.internal # (Remove system-managed fields: status, uid, resourceVersion, creationTimestamp) # Step 4: Recreate pod and verify Running status k -n troubleshoot delete pod crasher --now && k apply -f crasher.yaml k -n troubleshoot get pod crasher """ S[20] = """# Step 1: Check control-plane pods in kube-system k -n kube-system get pods # kube-scheduler static pod is in Error / CrashLoopBackOff # Step 2: Inspect scheduler pod logs k -n kube-system logs kube-scheduler-{CP} # Error: exec: "kube-schedulerr": executable file not found # Step 3: SSH into control plane node {SHELL_CP} # Step 4: Edit scheduler static pod manifest # In /etc/kubernetes/manifests/kube-scheduler.yaml: # Change: command: [kube-schedulerr] -> command: [kube-scheduler] vi /etc/kubernetes/manifests/kube-scheduler.yaml # Step 5: Wait for kubelet to reload the static pod and verify scheduling k -n kube-system get pods -l component=kube-scheduler """ S[37] = """# Step 1: Inspect CoreDNS pods and logs k -n kube-system get pods -l k8s-app=kube-dns k -n kube-system logs -l k8s-app=kube-dns --tail=20 # Step 2: Check CoreDNS ConfigMap k -n kube-system get cm coredns -o yaml # Fault: forward . 10.255.255.1 points to an invalid upstream DNS # Step 3: Edit ConfigMap to use host resolv.conf k -n kube-system edit cm coredns # Change: forward . 10.255.255.1 -> forward . /etc/resolv.conf # Step 4: Restart CoreDNS deployment to apply changes k -n kube-system rollout restart deploy coredns # Step 5: Verify DNS resolution from a test pod k run test-dns --image=busybox:1.36 --rm -it --restart=Never -- nslookup kubernetes.default """ S[38] = """# Step 1: Check control-plane pod status k -n kube-system get pods # kube-controller-manager is crashing # Step 2: Check controller-manager logs k -n kube-system logs kube-controller-manager-{CP} # Error: invalid flag: --leader-elect-lease-duration=1s # Step 3: Access control plane node {SHELL_CP} # Step 4: Edit static pod manifest to remove the invalid flag vi /etc/kubernetes/manifests/kube-controller-manager.yaml # Step 5: Verify controller-manager restarts and reconciles deployments/replicasets k create deploy probe --image=busybox:1.36 -- sleep 60 k get rs k delete deploy probe """ S[39] = """# Step 1: Access the failing worker node {SHELL_W1} # Step 2: Inspect kubelet status and journal logs systemctl status kubelet journalctl -u kubelet -n 30 --no-pager # Error: "open /etc/kubernetes/pki/ca-bundle.crt: no such file or directory" # Step 3: Check /var/lib/kubelet/config.yaml for incorrect certificate path grep -n clientCAFile /var/lib/kubelet/config.yaml # Fix path: replace /etc/kubernetes/pki/ca-bundle.crt with /etc/kubernetes/pki/ca.crt sed -i 's|ca-bundle.crt|ca.crt|' /var/lib/kubelet/config.yaml # Step 4: Restart and enable kubelet service systemctl restart kubelet && systemctl enable kubelet systemctl status kubelet """ # Hard Troubleshooting Tasks S[117] = """# Step 1: Check node status in cluster k get node {W2} # NotReady,SchedulingDisabled # Step 2: Access the node {SHELL_W2} # Step 3: Fix Fault 1 (kubelet service disabled) and Fault 2 (wrong CA certificate file) systemctl status kubelet sed -i 's|ca-wrong.crt|ca.crt|' /var/lib/kubelet/config.yaml systemctl enable --now kubelet systemctl status kubelet exit # Step 4: Fix Fault 3 (node was cordoned in cluster) k uncordon {W2} # Step 5: Verify scheduling on the repaired node k run test-pod --image=busybox:1.36 --restart=Never --overrides='{"spec":{"nodeName":"{W2}"}}' -- sleep 60 k get pod test-pod -o wide k delete pod test-pod --now """ S[118] = """# Step 1: Check control plane pods in kube-system k -n kube-system get pods # scheduler CrashLoop, controller-manager ImagePullBackOff # Step 2: Access control plane node {SHELL_CP} # Step 3: Fix kube-scheduler manifest command typo vi /etc/kubernetes/manifests/kube-scheduler.yaml # Replace: kube-schedulerr -> kube-scheduler # Step 4: Fix kube-controller-manager image tag vi /etc/kubernetes/manifests/kube-controller-manager.yaml # Remove broken image tag suffix to match cluster version # Step 5: Verify both static pods return to Running k -n kube-system get pods -w k create deploy probe --image=busybox:1.36 -- sleep 60 k get deploy probe k delete deploy probe """ S[119] = """# Step 1: Diagnose namespace quota and deployment events k -n %{ns} get all k -n %{ns} describe deploy %{dep} k -n %{ns} describe rs -l app=%{dep} # Quota exceeded: pods limit is 1 # Step 2: Fix quota to accommodate required replicas k -n %{ns} patch quota tight -p '{"spec":{"hard":{"pods":"3"}}}' # Step 3: Fix deployment image tag k -n %{ns} set image deploy %{dep} web=nginx:1.27 # Step 4: Fix Secret missing key referenced by container environment variables k -n %{ns} patch secret %{dep}-env -p '{"stringData":{"DATABASE_URL":"postgres://db"}}' k -n %{ns} rollout restart deploy %{dep} # Step 5: Fix Service selector and targetPort k -n %{ns} patch svc %{dep} -p '{"spec":{"selector":{"app":"%{dep}"},"ports":[{"port":80,"targetPort":80}]}}' # Step 6: Verify all deployment replicas are Available with endpoints k -n %{ns} get deploy %{dep} k -n %{ns} get endpoints %{dep} """ S[120] = """# Step 1: Query cluster CIDR from kube-controller-manager pod spec k -n kube-system get pod -l component=kube-controller-manager -o yaml | grep cluster-cidr # Step 2: Query service CIDR from kube-apiserver pod spec k -n kube-system get pod -l component=kube-apiserver -o yaml | grep service-cluster-ip-range # Step 3: Query CoreDNS ClusterIP k -n kube-system get svc kube-dns -o jsonpath='{.spec.clusterIP}' # Step 4: Query kube-proxy operating mode from ConfigMap k -n kube-system get cm kube-proxy -o yaml | grep '^ *mode:' # Empty string indicates default: iptables # Step 5: Query individual node Pod CIDR k get node %{node} -o jsonpath='{.spec.podCIDR}' """