From 595003e377d3839a58aac695eb28d0ea88bc58ca Mon Sep 17 00:00:00 2001 From: vumast3r Date: Mon, 14 Sep 2026 19:23:54 -0600 Subject: [PATCH] fix: repair stale reference solutions and Q33's grading check tracks/cka/solutions.py had drifted out of sync with the actual current task text for several questions (Q06, 11, 12, 13, 14, 22, 32, 40, 109, 110, 120, 122) - wrong namespaces/resource names, missing required steps, or output files that were never actually written. Rewrote each to match its task's real requirements. tracks/cka/bank_extra.py's Q33 grading check queried `.spec.aggregationRule` on a ClusterRole, but ClusterRole has no `.spec` field - `aggregationRule` and `rules` are top-level. Fixed the jsonpath so a correct answer actually passes. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Fqcio9TXkEXH35UHebTgoL --- tracks/cka/bank_extra.py | 2 +- tracks/cka/solutions.py | 193 +++++++++++++++++++++++---------------- 2 files changed, 113 insertions(+), 82 deletions(-) diff --git a/tracks/cka/bank_extra.py b/tracks/cka/bank_extra.py index ff8a1f1..ced4a24 100644 --- a/tracks/cka/bank_extra.py +++ b/tracks/cka/bank_extra.py @@ -96,7 +96,7 @@ for x in %{cr} %{cr}-pods %{cr}-services; do $K delete clusterrole $x --ignore-n $K delete clusterrolebinding %{cr}-binding --ignore-not-found >/dev/null """, "checks": [ - C("%{cr} has an aggregationRule with the right label", 1, r"""j clusterrole %{cr} '{.spec.aggregationRule.clusterRoleSelectors[*].matchLabels}' | grep -q 'aggregate-to-%{cr}":"true'"""), + C("%{cr} has an aggregationRule with the right label", 1, r"""j clusterrole %{cr} '{.aggregationRule.clusterRoleSelectors[*].matchLabels}' | grep -q 'aggregate-to-%{cr}":"true'"""), C("aggregation populated rules from the two labelled roles", 2, r"""j clusterrole %{cr} '{.rules[*].resources}' | grep -q pods && j clusterrole %{cr} '{.rules[*].resources}' | grep -q services"""), C("%{user} can list pods and services cluster-wide", 1, r"""eq "$($K auth can-i list pods -A --as=%{user} 2>/dev/null)" yes && eq "$($K auth can-i list services -A --as=%{user} 2>/dev/null)" yes"""), C("%{user} cannot delete pods", 1, r"""eq "$($K auth can-i delete pods -A --as=%{user} 2>/dev/null || true)" no"""), diff --git a/tracks/cka/solutions.py b/tracks/cka/solutions.py index 9a5265c..5b5b6ce 100644 --- a/tracks/cka/solutions.py +++ b/tracks/cka/solutions.py @@ -139,11 +139,9 @@ EOF """ S[6] = """# Part A: Check certificate expiration on control plane -# Step 1: Access control plane -{SHELL_CP} - -# Step 2: Inspect kubeadm cert expiration and extract the apiserver expiry line -kubeadm certs check-expiration | grep '^apiserver ' > {WS}/q06/apiserver-expiry.txt +# {WS} is a path on your own machine, so run the remote check and redirect its +# output locally - don't try to write {WS} from inside the node's own shell. +podman exec cka-sim-control-plane kubeadm certs check-expiration | grep '^apiserver ' > {WS}/q06/apiserver-expiry.txt # Part B: Issue and approve CertificateSigningRequest (CSR) for user Jane # Step 3: Generate private key and CSR @@ -226,18 +224,13 @@ spec: EOF """ -S[40] = """# Step 1: Access the control plane node -{SHELL_CP} +S[40] = """# Generate a new kubeadm join token with the specified TTL and print-join-command, +# writing the output straight into the required workspace file (run from the host, not the node, +# since {WS} is a path on your machine) +podman exec cka-sim-control-plane kubeadm token create --ttl %{ttl} --print-join-command > {WS}/q40/join.sh -# Step 2: Generate a new kubeadm join token with specified TTL and print join command -kubeadm token create --ttl %{ttl} --print-join-command - -# Step 3: Save the join command into the required workspace script -# (e.g., kubeadm join :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 +# Verify active tokens +podman exec cka-sim-control-plane kubeadm token list """ S[41] = """# Step 1: Check Kubernetes Server version (API server) @@ -440,9 +433,11 @@ mv /etc/kubernetes/manifests/*.yaml /tmp/ && sleep 20 && mv /tmp/*.yaml /etc/kub # Step 4: Update admin kubeconfig from newly generated credentials cp /etc/kubernetes/admin.conf ~/.kube/config +exit -# Step 5: Confirm new expiration dates -kubeadm certs check-expiration | grep '^apiserver ' +# 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 cka-sim-control-plane kubeadm certs check-expiration | grep '^apiserver ' > {WS}/q122/expiry.txt """ S[124] = """# Step 1: On the control-plane node, create a fresh bootstrap token @@ -769,19 +764,21 @@ k -n %{ns} create quota %{ns}-quota --hard=pods=%{pods},requests.cpu=1,limits.me cat <> /var/log/agent.log; sleep 1; done"] + volumeMounts: + - {name: logs, mountPath: /var/log} containers: - - name: main + - name: app image: nginx:1.27 - livenessProbe: + readinessProbe: httpGet: {path: /, port: 80} - initialDelaySeconds: 5 - periodSeconds: 10 + periodSeconds: 5 + livenessProbe: + tcpSocket: {port: 80} + volumeMounts: + - {name: logs, mountPath: /var/log} EOF -# Step 2: Verify both containers are running -k -n %{ns} get pod %{pod} +# 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}/q109/containers.txt +k -n %{ns} get pod %{pod} -o jsonpath='{.status.podIP}' > {WS}/q109/ip.txt """ -S[110] = r"""# Step 1: Filter pods outside kube-system with custom output -k get pods -A -o json | jq -r '.items[] | select(.metadata.namespace!="kube-system") | select(.spec.restartPolicy=="Always") | "\(.metadata.namespace) \(.metadata.name)"' > {WS}/q110/always.txt +S[110] = r"""# Step 1: pods outside kube-system whose FIRST container has no readiness probe +k get pods -A -o json | jq -r '.items[] | select(.metadata.namespace!="kube-system") | select(.spec.containers[0].readinessProbe == null) | "\(.metadata.namespace)/\(.metadata.name)"' | sort > {WS}/q110/no-readiness.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 2: count of Running pods per node (" ", node first) +k get pods -A --field-selector=status.phase=Running -o jsonpath='{range .items[*]}{.spec.nodeName}{"\n"}{end}' | sort | uniq -c | awk '{print $2, $1}' | sort > {WS}/q110/per-node.txt -# Step 3: Extract sorted unique container images used across kube-system +# Step 3: 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 """ @@ -885,38 +891,38 @@ k expose deploy %{dep} --port=80 --target-port=80 # 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' +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 +k -n svc expose deploy shop --name=shop-svc --type=NodePort --port=80 --target-port=80 $do > 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 +S[14] = """# Step 1: Create a long-running pod (not --rm) so it still exists for the check +k -n dns-check run dns-test --image=busybox:1.36 --restart=Never -- sleep 3600 -# 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 +# Step 2: Save the FQDN you're resolving +echo "hello.dns-check.svc.cluster.local" > 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: Apply GatewayClass and Gateway resources +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 < {WS}/q120/facts.txt -# 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 +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}/q120/facts.txt -# Step 3: Query CoreDNS ClusterIP -k -n kube-system get svc kube-dns -o jsonpath='{.spec.clusterIP}' +d=$(k -n kube-system get svc kube-dns -o jsonpath='{.spec.clusterIP}') +echo "dns-ip=$d" >> {WS}/q120/facts.txt -# 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 +# 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}/q120/facts.txt -# Step 5: Query individual node Pod CIDR -k get node %{node} -o jsonpath='{.spec.podCIDR}' +echo "%{node}-pod-cidr=$(k get node %{node} -o jsonpath='{.spec.podCIDR}')" >> {WS}/q120/facts.txt """ S[123] = """# Step 1: Confirm nothing works through kubectl at all