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:
parent
fb8cb2a649
commit
595003e377
@ -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"""),
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user