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
""",
"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"""),

View File

@ -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 <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
# 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 <<EOF | k apply -f -
apiVersion: v1
kind: LimitRange
metadata: {name: %{ns}-limitrange, namespace: %{ns}}
metadata: {name: %{ns}-defaults, namespace: %{ns}}
spec:
limits:
- type: Container
default: {cpu: 200m, memory: 256Mi}
defaultRequest: {cpu: 100m, memory: 128Mi}
max: {cpu: 500m, memory: 512Mi}
min: {cpu: 50m, memory: 64Mi}
default: {cpu: 500m, memory: 256Mi}
defaultRequest: {cpu: %{dcpu}, memory: 64Mi}
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 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
@ -834,40 +831,49 @@ k -n %{ns} rollout pause 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 -
apiVersion: v1
kind: Pod
metadata: {name: %{pod}, namespace: %{ns}}
spec:
terminationGracePeriodSeconds: 5
volumes:
- name: logs
emptyDir: {}
initContainers:
- name: proxy
- name: agent
image: busybox:1.36
restartPolicy: Always
command: [sh, -c, "while true; do sleep 5; done"]
readinessProbe:
exec: {command: [true]}
initialDelaySeconds: 1
command: [sh, -c, "while true; do date >> /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> <count>", 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 <<EOF | k apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: allow-frontend, namespace: marketing}
metadata: {name: backend-policy, namespace: netpol}
spec:
podSelector: {matchLabels: {app: api}}
podSelector: {matchLabels: {app: backend}}
policyTypes: [Ingress]
ingress:
- from:
- podSelector: {matchLabels: {role: frontend}}
- podSelector: {matchLabels: {app: frontend}}
ports:
- protocol: TCP
port: 80
port: 8080
EOF
# 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
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
cat <<EOF | k apply -f -
apiVersion: v1
kind: Service
metadata: {name: web-server, namespace: web}
metadata: {name: shop-svc, namespace: svc}
spec:
type: NodePort
selector: {app: web-server}
selector: {app: shop}
ports:
- port: 80
targetPort: 80
@ -924,35 +930,56 @@ spec:
EOF
# Step 3: Verify NodePort service and endpoints
k -n web get svc web-server
k -n web get endpoints web-server
k -n svc get svc shop-svc
k -n svc get endpointslices -l kubernetes.io/service-name=shop-svc
"""
S[13] = """# Step 1: Create Ingress resource routing host to backend service
k -n web create ingress web-ingress \\
--class=nginx \\
--rule="order.example.com/api*=order-service:8080"
S[13] = """# Step 1: Create the Ingress with both path rules under one host
cat <<EOF | k apply -f -
apiVersion: networking.k8s.io/v1
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
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
# Query A-record for a Service: <service>.<namespace>.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): <ip-with-dashes>.<namespace>.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 <<EOF | k apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata: {name: %{gc}}
spec: {controllerName: example.com/gateway-controller}
spec: {controllerName: example.com/gateway}
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
@ -963,18 +990,21 @@ spec:
- name: http
protocol: HTTP
port: 80
hostname: %{host}.example.com
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata: {name: %{route}, namespace: %{ns}}
metadata: {name: %{host}-route, namespace: %{ns}}
spec:
parentRefs: [{name: %{gw}}]
rules:
- 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
# Step 2: Verify Gateway and HTTPRoute status
# Step 3: Verify Gateway and HTTPRoute status
k -n %{ns} get gateway,httproute
"""
@ -1554,20 +1584,21 @@ 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
S[120] = 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}/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