"""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 cka 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 < {WS}/q40/join.sh # Verify active tokens podman exec {CP} 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[44] = """# 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. For a SUBRESOURCE use --subresource: `can-i create # pods/exec` means "create the pod named exec" and says no even when the Role is right. k auth can-i --list -n %{ns} --as=system:serviceaccount:%{ns}:%{sa} k auth can-i create pods --subresource=exec -n %{ns} --as=system:serviceaccount:%{ns}:%{sa} # yes """ S[45] = """# 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[46] = """# 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}/q47/%{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[48] = """# Step 1: Create directory for prod overlay mkdir -p {WS}/q48/overlays/prod && cd {WS}/q48/overlays/prod # Step 2: Create replica patch manifest cat > replicas.yaml < envfrom.yaml < kustomization.yaml <, not >, or the file stays empty printf 'apiVersion: deploy.example.com/v1\\nkind: %{kind}\\nmetadata: {name: bad}\\nspec: {replicas: 0, image: x}\\n' | k -n default apply -f - 2> {WS}/q49/rejected.txt cat {WS}/q49/rejected.txt # ...spec.replicas in body should be greater than or equal to 1 # Step 4: Omit environment and let the API server fill in the default printf 'apiVersion: deploy.example.com/v1\\nkind: %{kind}\\nmetadata: {name: defaulted}\\nspec: {replicas: 2, image: nginx:1.27}\\n' | k -n default apply -f - k -n default get $(echo %{kind} | tr A-Z a-z) defaulted -o jsonpath='{.spec.environment}{"\\n"}' # dev """ S[65] = """# 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 exit # Step 5: Back on your own machine now - {WS} is a path on your machine, not the node, # so capture the expiry report from out here and redirect it into the required file podman exec {CP} kubeadm certs check-expiration | grep '^apiserver ' > {WS}/q65/expiry.txt """ S[67] = """# Step 1: On the control-plane node, create a fresh bootstrap token {SHELL_CP} kubeadm token create --ttl 2h # -> prints a token like abcdef.0123456789abcdef # Step 2: Compute the CA discovery hash yourself. This is exactly what # `kubeadm join --discovery-token-ca-cert-hash` verifies against, so you should never have to # trust a value someone else hands you without being able to check it independently. openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | \\ openssl rsa -pubin -outform der 2>/dev/null | \\ openssl dgst -sha256 -hex | sed 's/^.* //' # Step 3: Find the real in-cluster API endpoint -- NOT what your local kubeconfig uses. # kind maps the API server to 127.0.0.1: for YOUR machine only; a node # joining over the cluster network needs the address the apiserver actually advertises. exit kubectl get endpointslices -l kubernetes.io/service-name=kubernetes -o jsonpath='{.items[0].endpoints[0].addresses[0]}:{.items[0].ports[0].port}{"\\n"}' # Step 4: Assemble the handoff file cat > {WS}/q67/node-join.env < CA_HASH=sha256: API_ENDPOINT= EOF """ # ================================================================= 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 <> /var/log/agent/agent.log; sleep 1; done"] volumeMounts: - {name: logs, mountPath: /var/log/agent} containers: - name: app image: nginx:1.27 readinessProbe: httpGet: {path: /, port: 80} periodSeconds: 5 livenessProbe: tcpSocket: {port: 80} volumeMounts: - {name: logs, mountPath: /var/log/agent} EOF # Step 2: Once Running, write the exact report files k -n %{ns} get pod %{pod} -o jsonpath='{.spec.initContainers[*].name} {.spec.containers[*].name}' \\ | tr ' ' '\\n' | sort > {WS}/q52/containers.txt k -n %{ns} get pod %{pod} -o jsonpath='{.status.podIP}' > {WS}/q52/ip.txt """ S[53] = r"""# Step 0: look before you script - which pods, which phases, which nodes k -n %{ns} get pods -o wide # Step 1: FIRST container without a readinessProbe, any phase (the Completed pod counts too). # jsonpath prints nothing for a missing field, so emit " " and keep the bare names. k -n %{ns} get pods -o jsonpath='{range .items[*]}{.metadata.name} {.spec.containers[0].readinessProbe}{"\n"}{end}' \ | awk 'NF==1{print $1}' | sort > {WS}/q53/no-readiness.txt # Step 2: Running pods per node, " ". uniq -c prints "count node", so swap the columns. k -n %{ns} get pods --field-selector=status.phase=Running -o jsonpath='{range .items[*]}{.spec.nodeName}{"\n"}{end}' \ | sort | uniq -c | awk '{print $2, $1}' | sort > {WS}/q53/per-node.txt # Step 3: distinct images in kube-system k -n kube-system get pods -o jsonpath='{.items[*].spec.containers[*].image}' | tr ' ' '\n' | sort -u > {WS}/q53/images.txt # Traps: cache-a has a readiness probe - on its SECOND container, so it belongs in file 1; # once-a is Completed, so it is in file 1 but not in file 2. `cat -A` shows stray spaces or \r. """ S[64] = """# Step 1: Switch context and find out which namespace it defaults to k config use-context %{ctx} k config view --minify -o jsonpath='{..namespace}{"\\n"}' # -> %{ns} # Step 2: Everything WITHOUT -n, so it lands in the context's namespace k create deploy %{dep} --image=busybox:1.36 --replicas=2 -- sleep 3600 k label deploy %{dep} owner=ac k expose deploy %{dep} --port=9000 --target-port=9000 # ClusterIP is the default type # Step 3: Record the namespace exactly as kubectl reports it k config view --minify -o jsonpath='{..namespace}' > {WS}/q64/ns.txt # Step 4: Verify it all landed there - and nothing in default k get deploy,svc %{dep} k -n default get deploy %{dep} # NotFound is the right answer """ # ================================================================= NET ===== # Domain 3: Services & Networking (20%) # =========================================================================== S[11] = """# Step 1: Create NetworkPolicy in namespace 'netpol' # Allow Ingress on port 8080 only from pods labeled 'app: frontend', no egress restriction cat < svc.yaml # Step 2: Set specific nodePort 30080 in spec.ports[0].nodePort cat < workspace/q14/svc-fqdn.txt # Step 3: Resolve it from inside the pod and save the full output k -n dns-check exec dns-test -- nslookup hello.dns-check.svc.cluster.local > workspace/q14/nslookup.txt """ S[22] = """# Step 1: Install the Gateway API standard-channel CRDs k apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.1/standard-install.yaml # Step 2: Apply GatewayClass, Gateway (with the listener hostname) and HTTPRoute cat <...svc.cluster.local k -n %{ns} get sts %{sts} -o jsonpath='{.spec.serviceName}{"\\n"}' # -> %{sts} cat < {WS}/q43/pod0.txt cat {WS}/q43/pod0.txt # Address: k -n %{ns} get pod %{sts}-0 -o wide # same IP k -n %{ns} get svc legacy # EXTERNAL-IP column shows %{ext} - it is only a CNAME """ # Hard Networking Tasks S[54] = """# Step 1: Label the source namespace for ingress matching k label ns %{from} team=%{from} # Step 2: Apply default-deny (ingress + egress), allow-dns egress, and tiered service policies cat < {WS}/q55/fp.txt """ S[56] = """# 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 # busybox's nslookup applies no search domains, so use FULL names (a short one fails even when DNS works) k run dns-check --image=busybox:1.36 --rm -it --restart=Never -- nslookup kubernetes.default.svc.cluster.local k run dns-ext --image=busybox:1.36 --rm -it --restart=Never -- nslookup kubernetes.io """ # ================================================================= 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: Delete the old claim. The Job's finished pod still references old-data, and a pod that # references a claim - even a Completed one - holds it in pvc-protection: `delete pvc` just sits # there (describe pvc -> "Used By: migrate-xxxxx"). Delete that pod first; the Job itself stays, # still reporting succeeded: 1. k -n %{ns} delete pod -l job-name=migrate k -n %{ns} delete pvc old-data k get pv | grep old-data # STATUS Released - Retain kept the 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 # busybox's nslookup applies no search domains, so use FULL names (a short one fails even when DNS works) k run test-dns --image=busybox:1.36 --rm -it --restart=Never -- nslookup kubernetes.default.svc.cluster.local k run test-ext --image=busybox:1.36 --rm -it --restart=Never -- nslookup kubernetes.io """ 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[60] = """# 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[61] = """# 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[62] = """# 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 the quota - with room for the rolling update, not just the end state. The default # strategy (maxSurge 25% -> 1, maxUnavailable 25% of 3 -> 0) needs a 4th pod mid-rollout, and with # maxUnavailable 0 the controller will not remove an old pod first either: at pods=3 the rollout # deadlocks at "0 out of 3 new replicas have been updated". k -n %{ns} patch quota tight -p '{"spec":{"hard":{"pods":"4"}}}' # 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 endpointslices -l kubernetes.io/service-name=%{dep} """ S[63] = r"""# Gather each fact and append it directly to the report file, one key=value line at a time pc=$(k -n kube-system get pod -l component=kube-controller-manager -o jsonpath='{.items[0].spec.containers[0].command}' | grep -o 'cluster-cidr=[^,"]*' | cut -d= -f2) echo "pod-cidr=$pc" > {WS}/q63/facts.txt sc=$(k -n kube-system get pod -l component=kube-apiserver -o jsonpath='{.items[0].spec.containers[0].command}' | grep -o 'service-cluster-ip-range=[^,"]*' | cut -d= -f2) echo "service-cidr=$sc" >> {WS}/q63/facts.txt d=$(k -n kube-system get svc kube-dns -o jsonpath='{.spec.clusterIP}') echo "dns-ip=$d" >> {WS}/q63/facts.txt # Empty mode in the ConfigMap means kube-proxy defaults to iptables m=$(k -n kube-system get cm kube-proxy -o jsonpath='{.data.config\.conf}' | grep '^mode:' | awk '{print $2}' | tr -d '"') echo "proxy-mode=${m:-iptables}" >> {WS}/q63/facts.txt echo "%{node}-pod-cidr=$(k get node %{node} -o jsonpath='{.spec.podCIDR}')" >> {WS}/q63/facts.txt """ S[66] = """# Step 1: Confirm nothing works through kubectl at all kubectl get nodes # connection refused / timeout -- the API server itself is down # Step 2: Access the control-plane node directly (kubectl can't help you here) {SHELL_CP} # Step 3: Check what containerd/kubelet is trying (and failing) to run crictl ps -a | grep apiserver journalctl -u kubelet -n 50 --no-pager | grep -i apiserver # Step 4: Fix the static pod manifest vi /etc/kubernetes/manifests/kube-apiserver.yaml # kube-apiserverr -> kube-apiserver # Step 5: Confirm recovery from your own terminal exit kubectl get nodes kubectl -n kube-system get pods -l component=kube-apiserver """