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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fqcio9TXkEXH35UHebTgoL
This commit is contained in:
vumast3r 2026-09-14 19:23:54 -06:00
parent fb8cb2a649
commit 595003e377
2 changed files with 113 additions and 82 deletions

View File

@ -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 $K delete clusterrolebinding %{cr}-binding --ignore-not-found >/dev/null
""", """,
"checks": [ "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("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} 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"""), C("%{user} cannot delete pods", 1, r"""eq "$($K auth can-i delete pods -A --as=%{user} 2>/dev/null || true)" no"""),

View File

@ -139,11 +139,9 @@ EOF
""" """
S[6] = """# Part A: Check certificate expiration on control plane S[6] = """# Part A: Check certificate expiration on control plane
# Step 1: Access control plane # {WS} is a path on your own machine, so run the remote check and redirect its
{SHELL_CP} # 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
# Step 2: Inspect kubeadm cert expiration and extract the apiserver expiry line
kubeadm certs check-expiration | grep '^apiserver ' > {WS}/q06/apiserver-expiry.txt
# Part B: Issue and approve CertificateSigningRequest (CSR) for user Jane # Part B: Issue and approve CertificateSigningRequest (CSR) for user Jane
# Step 3: Generate private key and CSR # Step 3: Generate private key and CSR
@ -226,18 +224,13 @@ spec:
EOF EOF
""" """
S[40] = """# Step 1: Access the control plane node S[40] = """# Generate a new kubeadm join token with the specified TTL and print-join-command,
{SHELL_CP} # 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 # Verify active tokens
kubeadm token create --ttl %{ttl} --print-join-command podman exec cka-sim-control-plane kubeadm token list
# Step 3: Save the join command into the required workspace script
# (e.g., kubeadm join <control-plane-ip>:6443 --token <token> --discovery-token-ca-cert-hash sha256:<hash>)
# 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) 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 # Step 4: Update admin kubeconfig from newly generated credentials
cp /etc/kubernetes/admin.conf ~/.kube/config cp /etc/kubernetes/admin.conf ~/.kube/config
exit
# Step 5: Confirm new expiration dates # Step 5: Back on your own machine now - {WS} is a path on your machine, not the node,
kubeadm certs check-expiration | grep '^apiserver ' # 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 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 <<EOF | k apply -f - cat <<EOF | k apply -f -
apiVersion: v1 apiVersion: v1
kind: LimitRange kind: LimitRange
metadata: {name: %{ns}-limitrange, namespace: %{ns}} metadata: {name: %{ns}-defaults, namespace: %{ns}}
spec: spec:
limits: limits:
- type: Container - type: Container
default: {cpu: 200m, memory: 256Mi} default: {cpu: 500m, memory: 256Mi}
defaultRequest: {cpu: 100m, memory: 128Mi} defaultRequest: {cpu: %{dcpu}, memory: 64Mi}
max: {cpu: 500m, memory: 512Mi}
min: {cpu: 50m, memory: 64Mi}
EOF EOF
# Step 3: Verify quota and limits # Step 3: Create the Deployment without specifying resources - it picks up the LimitRange defaults
k -n %{ns} create deploy worker --image=nginx:1.25 --replicas=2
# Step 4: Verify quota, limits and the defaulted pod resources
k -n %{ns} describe quota %{ns}-quota k -n %{ns} describe quota %{ns}-quota
k -n %{ns} describe limitrange %{ns}-limitrange k -n %{ns} describe limitrange %{ns}-defaults
k -n %{ns} get pod -l app=worker -o jsonpath='{.items[0].spec.containers[0].resources}'
""" """
# Hard Workload Tasks # Hard Workload Tasks
@ -834,40 +831,49 @@ k -n %{ns} rollout pause deploy %{dep}
k -n %{ns} get deploy %{dep} k -n %{ns} get deploy %{dep}
""" """
S[109] = """# Step 1: Deploy Pod with native sidecar (restartPolicy: Always inside initContainers) S[109] = """# Step 1: Deploy Pod with a native sidecar (restartPolicy: Always inside initContainers),
# a shared emptyDir the sidecar writes into and the main container reads from
cat <<EOF | k apply -f - cat <<EOF | k apply -f -
apiVersion: v1 apiVersion: v1
kind: Pod kind: Pod
metadata: {name: %{pod}, namespace: %{ns}} metadata: {name: %{pod}, namespace: %{ns}}
spec: spec:
terminationGracePeriodSeconds: 5
volumes:
- name: logs
emptyDir: {}
initContainers: initContainers:
- name: proxy - name: agent
image: busybox:1.36 image: busybox:1.36
restartPolicy: Always restartPolicy: Always
command: [sh, -c, "while true; do sleep 5; done"] command: [sh, -c, "while true; do date >> /var/log/agent.log; sleep 1; done"]
readinessProbe: volumeMounts:
exec: {command: [true]} - {name: logs, mountPath: /var/log}
initialDelaySeconds: 1
containers: containers:
- name: main - name: app
image: nginx:1.27 image: nginx:1.27
livenessProbe: readinessProbe:
httpGet: {path: /, port: 80} httpGet: {path: /, port: 80}
initialDelaySeconds: 5 periodSeconds: 5
periodSeconds: 10 livenessProbe:
tcpSocket: {port: 80}
volumeMounts:
- {name: logs, mountPath: /var/log}
EOF EOF
# Step 2: Verify both containers are running # Step 2: Once Running, write the exact report files
k -n %{ns} get pod %{pod} 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 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.restartPolicy=="Always") | "\(.metadata.namespace) \(.metadata.name)"' > {WS}/q110/always.txt 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 # Step 2: count of Running pods per node ("<node> <count>", node first)
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 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 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%) # Domain 3: Services & Networking (20%)
# =========================================================================== # ===========================================================================
S[11] = """# Step 1: Create NetworkPolicy in namespace 'marketing' S[11] = """# Step 1: Create NetworkPolicy in namespace 'netpol'
# Allow Ingress on port 80 only from pods labeled 'role: frontend' # Allow Ingress on port 8080 only from pods labeled 'app: frontend', no egress restriction
cat <<EOF | k apply -f - cat <<EOF | k apply -f -
apiVersion: networking.k8s.io/v1 apiVersion: networking.k8s.io/v1
kind: NetworkPolicy kind: NetworkPolicy
metadata: {name: allow-frontend, namespace: marketing} metadata: {name: backend-policy, namespace: netpol}
spec: spec:
podSelector: {matchLabels: {app: api}} podSelector: {matchLabels: {app: backend}}
policyTypes: [Ingress] policyTypes: [Ingress]
ingress: ingress:
- from: - from:
- podSelector: {matchLabels: {role: frontend}} - podSelector: {matchLabels: {app: frontend}}
ports: ports:
- protocol: TCP - protocol: TCP
port: 80 port: 8080
EOF EOF
# Step 2: Verify NetworkPolicy configuration # Step 2: Verify NetworkPolicy configuration
k -n marketing describe netpol allow-frontend k -n netpol describe netpol backend-policy
""" """
S[12] = """# Step 1: Expose deployment as NodePort service using dry-run S[12] = """# Step 1: Expose deployment as NodePort service using dry-run
k -n web expose deploy web-server --type=NodePort --port=80 --target-port=80 $do > 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 # Step 2: Set specific nodePort 30080 in spec.ports[0].nodePort
cat <<EOF | k apply -f - cat <<EOF | k apply -f -
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
metadata: {name: web-server, namespace: web} metadata: {name: shop-svc, namespace: svc}
spec: spec:
type: NodePort type: NodePort
selector: {app: web-server} selector: {app: shop}
ports: ports:
- port: 80 - port: 80
targetPort: 80 targetPort: 80
@ -924,35 +930,56 @@ spec:
EOF EOF
# Step 3: Verify NodePort service and endpoints # Step 3: Verify NodePort service and endpoints
k -n web get svc web-server k -n svc get svc shop-svc
k -n web get endpoints web-server k -n svc get endpointslices -l kubernetes.io/service-name=shop-svc
""" """
S[13] = """# Step 1: Create Ingress resource routing host to backend service S[13] = """# Step 1: Create the Ingress with both path rules under one host
k -n web create ingress web-ingress \\ cat <<EOF | k apply -f -
--class=nginx \\ apiVersion: networking.k8s.io/v1
--rule="order.example.com/api*=order-service:8080" kind: Ingress
metadata:
name: app-ingress
namespace: ingress-app
spec:
ingressClassName: nginx
rules:
- host: shop.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service: {name: api, port: {number: 8080}}
- path: /
pathType: Prefix
backend:
service: {name: web, port: {number: 80}}
EOF
# Step 2: Verify Ingress rules # Step 2: Verify Ingress rules
k -n web describe ingress web-ingress k -n ingress-app describe ingress app-ingress
""" """
S[14] = """# Step 1: Run temporary debug pod to query DNS S[14] = """# Step 1: Create a long-running pod (not --rm) so it still exists for the check
# Query A-record for a Service: <service>.<namespace>.svc.cluster.local k -n dns-check run dns-test --image=busybox:1.36 --restart=Never -- sleep 3600
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): <ip-with-dashes>.<namespace>.pod.cluster.local # Step 2: Save the FQDN you're resolving
k -n default run dns-test --image=busybox:1.36 --rm -it --restart=Never -- \\ echo "hello.dns-check.svc.cluster.local" > workspace/q14/svc-fqdn.txt
nslookup 10-244-1-5.apps.pod.cluster.local
# 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 <<EOF | k apply -f - cat <<EOF | k apply -f -
apiVersion: gateway.networking.k8s.io/v1 apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass kind: GatewayClass
metadata: {name: %{gc}} metadata: {name: %{gc}}
spec: {controllerName: example.com/gateway-controller} spec: {controllerName: example.com/gateway}
--- ---
apiVersion: gateway.networking.k8s.io/v1 apiVersion: gateway.networking.k8s.io/v1
kind: Gateway kind: Gateway
@ -963,18 +990,21 @@ spec:
- name: http - name: http
protocol: HTTP protocol: HTTP
port: 80 port: 80
hostname: %{host}.example.com
--- ---
apiVersion: gateway.networking.k8s.io/v1 apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute kind: HTTPRoute
metadata: {name: %{route}, namespace: %{ns}} metadata: {name: %{host}-route, namespace: %{ns}}
spec: spec:
parentRefs: [{name: %{gw}}] parentRefs: [{name: %{gw}}]
rules: rules:
- matches: [{path: {type: PathPrefix, value: /api}}] - matches: [{path: {type: PathPrefix, value: /api}}]
backendRefs: [{name: %{svc}, port: 8080}] backendRefs: [{name: api, port: 8080}]
- matches: [{path: {type: PathPrefix, value: /}}]
backendRefs: [{name: web, port: 80}]
EOF EOF
# Step 2: Verify Gateway and HTTPRoute status # Step 3: Verify Gateway and HTTPRoute status
k -n %{ns} get gateway,httproute k -n %{ns} get gateway,httproute
""" """
@ -1554,20 +1584,21 @@ k -n %{ns} get deploy %{dep}
k -n %{ns} get endpoints %{dep} k -n %{ns} get endpoints %{dep}
""" """
S[120] = """# Step 1: Query cluster CIDR from kube-controller-manager pod spec S[120] = r"""# Gather each fact and append it directly to the report file, one key=value line at a time
k -n kube-system get pod -l component=kube-controller-manager -o yaml | grep cluster-cidr 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}/q120/facts.txt
# Step 2: Query service CIDR from kube-apiserver pod spec 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)
k -n kube-system get pod -l component=kube-apiserver -o yaml | grep service-cluster-ip-range echo "service-cidr=$sc" >> {WS}/q120/facts.txt
# Step 3: Query CoreDNS ClusterIP d=$(k -n kube-system get svc kube-dns -o jsonpath='{.spec.clusterIP}')
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 # Empty mode in the ConfigMap means kube-proxy defaults to iptables
k -n kube-system get cm kube-proxy -o yaml | grep '^ *mode:' # Empty string indicates default: 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 echo "%{node}-pod-cidr=$(k get node %{node} -o jsonpath='{.spec.podCIDR}')" >> {WS}/q120/facts.txt
k get node %{node} -o jsonpath='{.spec.podCIDR}'
""" """
S[123] = """# Step 1: Confirm nothing works through kubectl at all S[123] = """# Step 1: Confirm nothing works through kubectl at all