kubestronaut-sim/ckad/solve.py
vumas3r f3d0e536b6 Exam simulator for CKA, CKAD and CKS with per-task hosts and a remote desktop
kubestronaut-sim runs each exam task on a host of its own - a kind (kubeadm)
cluster in rootless podman, or a QEMU/KVM Ubuntu VM for the CKS tasks that
need their own kernel (Falco, AppArmor) - and puts a browser desktop in front
of them, the way killer.sh and sailor.sh do.

- ksim, sim/: the engine (sessions, drills, study mode, grading, coverage,
  audit), host and VM lifecycle, the desktop
- images/: exam host images (common, CKS variant with its tools, VM guest)
- cka/ (131 tasks), ckad/ (105), cks/ (110): task banks, curricula with
  study notes, reference solutions and scripted solutions

Every domain of every track has at least 17 pool tasks. `./ksim audit` proves
a task three ways: it seeds cleanly on its own host, scores nothing before
anyone has worked on it, and reaches full marks when its scripted solution is
run on the host as `candidate`. All 346 tasks pass (CKA audited 2026-10-13,
CKAD and CKS 2026-10-12).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZPd3GGWyxTgAkL6a4gAJz
2026-10-13 16:29:33 -06:00

1270 lines
50 KiB
Python

"""Scripted solutions: what a candidate would type on the task's host, as a script.
`./ksim audit` runs a task's script as `candidate` in an interactive shell on its host (so the
host's aliases apply) after grading the task untouched, and grades it again: full marks prove the
task can be solved as its text describes, on a cluster of its own. %{param}, {WS} and {CP}.. are
substituted as in the task. A task without a script is only audited for a clean seed.
SOLVE: task id -> bash
"""
SOLVE = {
# ---- 1-37: the first banks (questions.py, bank_extra.py); 13 is further down
1: r"""
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata: {name: %{pod}, namespace: %{ns}}
spec:
volumes: [{name: spool, emptyDir: {}}]
containers:
- name: writer
image: busybox:1.36
command: [sh, -c, "while true; do echo 'metric 1' >> /spool/raw.txt; sleep 2; done"]
volumeMounts: [{name: spool, mountPath: /spool}]
- name: adapter
image: busybox:1.36
command: [sh, -c, "tail -f /spool/raw.txt"]
volumeMounts: [{name: spool, mountPath: /spool}]
EOF
kubectl -n %{ns} wait --for=condition=Ready pod/%{pod} --timeout=180s
sleep 6
""",
2: r"""
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata: {name: %{pod}, namespace: %{ns}}
spec:
initContainers:
- name: wait-for-backend
image: busybox:1.36
command: [sh, -c, "until wget -T 2 -q -O- http://backend:80 >/dev/null 2>&1; do echo waiting; sleep 2; done"]
containers: [{name: app, image: "nginx:1.27"}]
EOF
kubectl -n %{ns} create deploy backend --image=nginx:1.27
kubectl -n %{ns} rollout status deploy/backend --timeout=180s
kubectl -n %{ns} wait --for=condition=Ready pod/%{pod} --timeout=180s
""",
3: r"""
cat <<EOF | kubectl apply -f -
apiVersion: batch/v1
kind: CronJob
metadata: {name: %{cj}, namespace: %{ns}}
spec:
schedule: "*/%{m} * * * *"
startingDeadlineSeconds: 10
successfulJobsHistoryLimit: 2
failedJobsHistoryLimit: 2
jobTemplate:
spec:
activeDeadlineSeconds: 20
template:
spec:
restartPolicy: Never
containers:
- {name: digest, image: "busybox:1.36", command: [sh, -c, "echo digest"]}
EOF
""",
4: r"""
kubectl -n %{ns} set image deploy %{dep} nginx=nginx:1.27
kubectl -n %{ns} annotate deploy %{dep} kubernetes.io/change-cause='pick 1.27' --overwrite
kubectl -n %{ns} rollout status deploy %{dep} --timeout=240s
kubectl -n %{ns} set image deploy %{dep} nginx=nginx:9.99-nope
sleep 10
kubectl -n %{ns} rollout undo deploy %{dep}
kubectl -n %{ns} rollout status deploy %{dep} --timeout=240s
""",
5: r"""
kubectl -n %{ns} get deploy %{svc}-blue -o json | jq 'del(.status, .metadata.uid, .metadata.resourceVersion, .metadata.creationTimestamp, .metadata.generation, .metadata.annotations, .metadata.managedFields) | .metadata.name = "%{svc}-green" | .spec.replicas = 2 | .spec.selector.matchLabels.version = "green" | .spec.template.metadata.labels.version = "green" | .spec.template.spec.containers[0].image = "nginx:1.27"' | kubectl apply -f -
kubectl -n %{ns} rollout status deploy %{svc}-green --timeout=240s
kubectl -n %{ns} patch svc %{svc} -p '{"spec":{"selector":{"app":"%{svc}","version":"green"}}}'
sleep 5
""",
6: r"""
kubectl create ns %{ns} 2>/dev/null
helm -n %{ns} install %{rel} {WS}/q6/chart --set replicaCount=2 >/dev/null
helm -n %{ns} upgrade %{rel} {WS}/q6/chart --set replicaCount=4 >/dev/null
helm -n %{ns} rollback %{rel} 1 >/dev/null
helm -n %{ns} history %{rel} -o json | jq 'map(.revision) | max' > {WS}/q6/revision.txt
""",
7: r"""
kubectl -n %{ns} patch deploy %{dep} --type=json -p '[
{"op": "add", "path": "/spec/template/spec/containers/0/startupProbe", "value": {"httpGet": {"path": "/", "port": 80}, "periodSeconds": 5, "failureThreshold": 12}},
{"op": "add", "path": "/spec/template/spec/containers/0/readinessProbe", "value": {"httpGet": {"path": "/", "port": 80}, "periodSeconds": 5, "failureThreshold": 1}},
{"op": "add", "path": "/spec/template/spec/containers/0/livenessProbe", "value": {"tcpSocket": {"port": 80}, "periodSeconds": 10, "initialDelaySeconds": 15}}]'
kubectl -n %{ns} rollout status deploy %{dep} --timeout=240s
""",
8: r"""
for i in $(seq 1 40); do R=$(kubectl -n %{ns} get pod %{pod} -o jsonpath='{.status.containerStatuses[0].state.waiting.reason}'); case "$R" in *ImagePull*) break;; esac; sleep 3; done
echo "$R" > {WS}/q8/reason.txt
kubectl -n %{ns} set image pod/%{pod} worker=busybox:1.36
for i in $(seq 1 60); do [ "$(kubectl -n %{ns} get pod %{pod} -o jsonpath='{.status.phase}')" = Running ] && [ "$(kubectl -n %{ns} get pod %{pod} -o jsonpath='{.status.containerStatuses[0].ready}')" = true ] && break; sleep 3; done
""",
9: r"""
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata: {name: %{pod}, namespace: %{ns}}
spec:
securityContext: {runAsUser: %{uid}, runAsGroup: %{uid}, runAsNonRoot: true}
containers:
- name: c
image: busybox:1.36
command: [sleep, "86400"]
securityContext:
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities: {drop: [ALL], add: [NET_BIND_SERVICE]}
EOF
kubectl -n %{ns} wait --for=condition=Ready pod/%{pod} --timeout=180s
""",
10: r"""
kubectl -n %{ns} create cm app-cfg --from-literal=mode=live
kubectl -n %{ns} create secret generic app-sec --from-literal=apikey=xyz789
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata: {name: %{pod}, namespace: %{ns}}
spec:
volumes:
- name: bundle
projected:
sources:
- configMap: {name: app-cfg, items: [{key: mode, path: mode}]}
- secret: {name: app-sec, items: [{key: apikey, path: apikey}]}
- serviceAccountToken: {audience: vault, expirationSeconds: 3600, path: token}
containers:
- name: c
image: busybox:1.36
command: [sleep, "86400"]
volumeMounts: [{name: bundle, mountPath: /etc/bundle}]
EOF
kubectl -n %{ns} wait --for=condition=Ready pod/%{pod} --timeout=180s
""",
11: r"""
kubectl -n %{ns} expose deploy %{dep} --port=8080 --target-port=80
kubectl -n %{ns} expose deploy %{dep} --name=%{dep}-ext --type=NodePort --port=80 --target-port=80 --dry-run=client -o json | jq '.spec.ports[0].nodePort = %{np}' | kubectl apply -f -
kubectl -n %{ns} create ingress %{dep} --class=nginx --rule="%{dep}.example.com/*=%{dep}:8080"
kubectl -n %{ns} rollout status deploy %{dep} --timeout=240s
""",
12: r"""
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: db-allow, namespace: %{ns}}
spec:
podSelector: {matchLabels: {app: db}}
policyTypes: [Ingress]
ingress:
- from: [{podSelector: {matchLabels: {app: %{client}}}}]
ports: [{protocol: TCP, port: 5432}]
EOF
""",
14: r"""
cat <<EOF | kubectl apply -f -
apiVersion: batch/v1
kind: Job
metadata: {name: %{job}, namespace: %{ns}}
spec:
completions: %{n}
parallelism: %{p}
backoffLimit: 2
activeDeadlineSeconds: 300
template:
metadata: {labels: {batch: %{job}}}
spec:
restartPolicy: Never
containers:
- {name: render, image: "busybox:1.36", command: [sh, -c, "echo frame done; sleep 3"]}
EOF
kubectl -n %{ns} wait --for=condition=Complete job/%{job} --timeout=240s
""",
15: r"""
cat <<'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: DaemonSet
metadata: {name: %{ds}, namespace: %{ns}}
spec:
selector: {matchLabels: {app: %{ds}}}
template:
metadata: {labels: {app: %{ds}}}
spec:
tolerations: [{key: node-role.kubernetes.io/control-plane, operator: Exists, effect: NoSchedule}]
containers:
- name: agent
image: busybox:1.36
command: ["sh", "-c", "while true; do echo \"$(date) $NODE_NAME ok\"; sleep 30; done"]
env: [{name: NODE_NAME, valueFrom: {fieldRef: {fieldPath: spec.nodeName}}}]
resources: {requests: {cpu: 10m, memory: 16Mi}}
EOF
kubectl -n %{ns} rollout status ds %{ds} --timeout=240s
sleep 3
""",
16: r"""
cd {WS}/q16
yq -i '.spec.template.spec.initContainers = [(.spec.template.spec.containers[] | select(.name == "shipper")) * {"restartPolicy": "Always"}] | .spec.template.spec.containers |= map(select(.name == "worker"))' job.yaml
kubectl -n %{ns} delete job %{job}
kubectl apply -f job.yaml
kubectl -n %{ns} wait --for=condition=Complete job/%{job} --timeout=240s
""",
17: r"""
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata: {name: %{pvc}, namespace: %{ns}}
spec: {accessModes: [ReadWriteOnce], storageClassName: standard, resources: {requests: {storage: 100Mi}}}
---
apiVersion: apps/v1
kind: Deployment
metadata: {name: %{dep}, namespace: %{ns}, labels: {app: %{dep}}}
spec:
replicas: 1
selector: {matchLabels: {app: %{dep}}}
template:
metadata: {labels: {app: %{dep}}}
spec:
terminationGracePeriodSeconds: 2
volumes:
- {name: data, persistentVolumeClaim: {claimName: %{pvc}}}
- {name: scratch, emptyDir: {medium: Memory, sizeLimit: 32Mi}}
containers:
- name: busybox
image: busybox:1.36
command: ["sh", "-c", "date >> /data/boot.log; sleep 86400"]
volumeMounts: [{name: data, mountPath: /data}, {name: scratch, mountPath: /scratch}]
EOF
kubectl -n %{ns} rollout status deploy %{dep} --timeout=240s
kubectl -n %{ns} delete pod -l app=%{dep}
kubectl -n %{ns} rollout status deploy %{dep} --timeout=240s
sleep 5
""",
18: r"""
kubectl -n %{ns} get deploy %{app}-stable -o json | jq 'del(.status, .metadata.uid, .metadata.resourceVersion, .metadata.creationTimestamp, .metadata.generation, .metadata.annotations, .metadata.managedFields) | .metadata.name = "%{app}-canary" | .spec.replicas = 1 | .spec.selector.matchLabels.track = "canary" | .spec.template.metadata.labels.track = "canary" | .spec.template.spec.containers[0].image = "nginx:1.27"' | kubectl apply -f -
kubectl -n %{ns} scale deploy %{app}-stable --replicas=4
kubectl -n %{ns} rollout status deploy %{app}-canary --timeout=240s
kubectl -n %{ns} rollout status deploy %{app}-stable --timeout=240s
sleep 5
""",
19: r"""
kubectl -n %{ns} patch deploy %{dep} -p '{"spec":{"minReadySeconds":10,"revisionHistoryLimit":3,"strategy":{"type":"RollingUpdate","rollingUpdate":{"maxSurge":1,"maxUnavailable":0}}}}'
kubectl -n %{ns} set image deploy %{dep} nginx=nginx:1.27
kubectl -n %{ns} rollout status deploy %{dep} --timeout=300s
""",
20: r"""
mkdir -p {WS}/q20/overlays/prod
cat > {WS}/q20/overlays/prod/kustomization.yaml <<EOF
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources: [../../base]
namespace: %{ns}
namePrefix: prod-
labels:
- pairs: {env: prod}
includeSelectors: false
replicas:
- {name: web, count: %{n}}
images:
- {name: nginx, newTag: "1.27"}
EOF
kubectl apply -k {WS}/q20/overlays/prod
kubectl -n %{ns} rollout status deploy prod-web --timeout=240s
""",
21: r"""
helm show values {WS}/q21/charts/webapp-1.3.0.tgz > {WS}/q21/defaults-1.3.0.yaml
printf 'replicaCount: 2\nservice:\n type: NodePort\n' > {WS}/q21/%{rel}-values.yaml
helm -n %{ns} install %{rel} {WS}/q21/charts/webapp-1.3.0.tgz -f {WS}/q21/%{rel}-values.yaml >/dev/null
helm -n %{ns} upgrade %{old} {WS}/q21/charts/webapp-1.3.0.tgz --reset-then-reuse-values >/dev/null
kubectl -n %{ns} rollout status deploy %{old}-webapp --timeout=240s
kubectl -n %{ns} rollout status deploy %{rel}-webapp --timeout=240s
""",
22: r"""
cd {WS}/q22
printf '%s\n' extensions/v1beta1 batch/v1beta1 policy/v1beta1 autoscaling/v2beta2 > removed.txt
cat > fixed.yaml <<EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata: {name: %{app}, namespace: %{ns}}
spec:
rules:
- host: %{app}.example.com
http:
paths:
- path: /
pathType: Prefix
backend: {service: {name: %{app}, port: {number: 80}}}
---
apiVersion: batch/v1
kind: CronJob
metadata: {name: %{app}-report, namespace: %{ns}}
spec:
schedule: "15 2 * * *"
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers: [{name: report, image: "busybox:1.36", command: [sh, -c, "echo report"]}]
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata: {name: %{app}, namespace: %{ns}}
spec:
minAvailable: 1
selector: {matchLabels: {app: %{app}}}
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata: {name: %{app}, namespace: %{ns}}
spec:
scaleTargetRef: {apiVersion: apps/v1, kind: Deployment, name: %{app}}
minReplicas: 2
maxReplicas: 5
metrics:
- type: Resource
resource: {name: cpu, target: {type: Utilization, averageUtilization: 70}}
EOF
kubectl apply -f fixed.yaml
""",
23: r"""
for i in $(seq 1 60); do [ "$(kubectl top pod -n %{ns} --no-headers 2>/dev/null | wc -l)" -ge 3 ] && break; sleep 5; done
sleep 20
kubectl top pod -n %{ns} --sort-by=cpu --no-headers | head -1 | awk '{print $1}' > {WS}/q23/cpu.txt
kubectl top pod -n %{ns} --sort-by=memory --no-headers | head -1 | awk '{print $1}' > {WS}/q23/memory.txt
echo 'kubectl top node' > {WS}/q23/nodes.sh
echo 'kubectl top pod -A --sort-by=memory' > {WS}/q23/pods.sh
""",
24: r"""
for i in $(seq 1 40); do kubectl -n %{ns} logs %{pod} -c ingest --previous 2>/dev/null | grep -q FATAL && break; sleep 3; done
kubectl -n %{ns} logs %{pod} -c ingest --previous > {WS}/q24/crash.log
grep -o '/etc/ingest/[^ ]*\.yaml' {WS}/q24/crash.log | head -1 > {WS}/q24/missing.txt
kubectl -n %{ns} logs %{pod} -c helper --tail=3 --timestamps > {WS}/q24/helper.log
""",
25: r"""
kubectl -n %{ns} get deploy %{dep} -o json | jq '(.spec.template.spec.containers[0].env[] | select(.name == "LOG_LEVEL") | .valueFrom.configMapKeyRef.key) = "log_level" | del(.spec.template.spec.containers[0].command)' | kubectl apply -f -
kubectl -n %{ns} rollout status deploy %{dep} --timeout=240s
kubectl -n %{ns} exec deploy/%{dep} -- printenv LOG_LEVEL > {WS}/q25/log-level.txt
""",
26: r"""
for i in $(seq 1 40); do R=$(kubectl -n %{ns} get pod -l app=%{dep} -o jsonpath='{.items[0].status.containerStatuses[0].lastState.terminated.reason}' 2>/dev/null); [ -n "$R" ] && break; sleep 3; done
echo $R > {WS}/q26/reason.txt
kubectl -n %{ns} set resources deploy %{dep} -c resize --limits=memory=256Mi
kubectl -n %{ns} rollout status deploy %{dep} --timeout=240s
sleep 35
""",
27: r"""
kubectl get crd -o name | grep -i backup | cut -d/ -f2 > {WS}/q27/crd.txt
kubectl get backups.data.example.com -A --no-headers 2>/dev/null | wc -l > {WS}/q27/count.txt
cat <<EOF | kubectl apply -f -
apiVersion: data.example.com/v1
kind: Backup
metadata: {name: %{name}, namespace: %{ns}}
spec: {target: %{target}, schedule: "30 3 * * *", retentionDays: 14}
EOF
""",
28: r"""
kubectl -n %{ns} create sa %{sa}
cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: {name: %{sa}-role, namespace: %{ns}}
rules:
- {apiGroups: [""], resources: [pods, pods/log], verbs: [get, list, watch]}
- {apiGroups: [apps], resources: [deployments, deployments/scale], verbs: [get, list, update, patch]}
EOF
kubectl -n %{ns} create rolebinding %{sa}-binding --role=%{sa}-role --serviceaccount=%{ns}:%{sa}
""",
29: r"""
kubectl get ns %{ns} -o jsonpath='{.metadata.labels.pod-security\.kubernetes\.io/enforce}' > {WS}/q29/level.txt
kubectl -n %{ns} patch deploy %{dep} --type=json -p '[
{"op": "add", "path": "/spec/template/spec/securityContext", "value": {"runAsNonRoot": true, "runAsUser": %{uid}, "seccompProfile": {"type": "RuntimeDefault"}}},
{"op": "add", "path": "/spec/template/spec/containers/0/securityContext", "value": {"allowPrivilegeEscalation": false, "capabilities": {"drop": ["ALL"]}}}]'
kubectl -n %{ns} rollout status deploy %{dep} --timeout=240s
""",
30: r"""
kubectl -n %{ns} create quota compute-quota --hard=requests.cpu=1,requests.memory=1Gi,limits.cpu=2,limits.memory=2Gi,pods=6
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: LimitRange
metadata: {name: container-defaults, namespace: %{ns}}
spec:
limits:
- type: Container
defaultRequest: {cpu: 100m, memory: 128Mi}
default: {cpu: 250m, memory: 256Mi}
max: {cpu: 500m, memory: 512Mi}
EOF
kubectl -n %{ns} create deploy %{dep} --image=nginx:1.27 --replicas=3
kubectl -n %{ns} rollout status deploy %{dep} --timeout=240s
sleep 5
""",
31: r"""
kubectl -n %{ns} set resources deploy %{dep} -c app --requests=cpu=250m,memory=128Mi --limits=cpu=250m,memory=128Mi
kubectl -n %{ns} set resources deploy %{dep} -c metrics --requests=cpu=50m,memory=32Mi --limits=cpu=50m,memory=32Mi
kubectl -n %{ns} rollout status deploy %{dep} --timeout=240s
sleep 40
kubectl -n %{ns} get pod -l app=%{dep} --field-selector status.phase=Running --sort-by=.metadata.creationTimestamp -o jsonpath='{.items[-1:].status.qosClass}' > {WS}/q31/qos.txt
""",
32: r"""
kubectl -n %{ns} create cm %{cm} --from-file=app.properties={WS}/q32/app.properties --from-literal=TIER=backend
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata: {name: %{pod}, namespace: %{ns}}
spec:
volumes:
- name: cfg
configMap: {name: %{cm}, items: [{key: app.properties, path: app.properties}]}
containers:
- name: reader
image: busybox:1.36
command: [sleep, "86400"]
env: [{name: APP_TIER, valueFrom: {configMapKeyRef: {name: %{cm}, key: TIER}}}]
volumeMounts: [{name: cfg, mountPath: /etc/app}]
EOF
kubectl -n %{ns} wait --for=condition=Ready pod/%{pod} --timeout=180s
""",
33: r"""
kubectl -n %{ns} get secret %{old} -o jsonpath='{.data.password}' | base64 -d > {WS}/q33/old-password.txt
kubectl -n %{ns} create secret generic %{sec} --from-literal=username=%{user} --from-literal=password=%{pw}
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata: {name: %{pod}, namespace: %{ns}}
spec:
volumes:
- name: creds
secret: {secretName: %{sec}, defaultMode: 0400}
containers:
- name: client
image: busybox:1.36
command: [sleep, "86400"]
env: [{name: API_USER, valueFrom: {secretKeyRef: {name: %{sec}, key: username}}}]
volumeMounts: [{name: creds, mountPath: /etc/api-creds, readOnly: true}]
EOF
kubectl -n %{ns} wait --for=condition=Ready pod/%{pod} --timeout=180s
""",
34: r"""
kubectl -n %{ns} create sa %{sa}
kubectl -n %{ns} patch sa %{sa} -p '{"automountServiceAccountToken": false}'
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
name: %{sa}-token
namespace: %{ns}
annotations: {kubernetes.io/service-account.name: %{sa}}
type: kubernetes.io/service-account-token
EOF
sleep 4
kubectl -n %{ns} get secret %{sa}-token -o jsonpath='{.data.token}' | base64 -d > {WS}/q34/token.txt
kubectl -n %{ns} create deploy %{dep} --image=nginx:1.27 --dry-run=client -o json | jq '.spec.template.spec.serviceAccountName = "%{sa}"' | kubectl apply -f -
kubectl -n %{ns} rollout status deploy %{dep} --timeout=240s
""",
35: r"""
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: api-egress, namespace: %{ns}}
spec:
podSelector: {matchLabels: {app: api}}
policyTypes: [Egress]
egress:
- to: [{podSelector: {matchLabels: {app: db}}}]
ports: [{protocol: TCP, port: 80}]
- ports: [{protocol: UDP, port: 53}, {protocol: TCP, port: 53}]
EOF
sleep 6
""",
36: r"""
kubectl -n %{ns} patch svc %{app} --type=json -p '[{"op": "replace", "path": "/spec/selector/tier", "value": "web"}, {"op": "replace", "path": "/spec/ports/0/targetPort", "value": 80}]'
sleep 5
kubectl -n %{ns} get endpointslices -l kubernetes.io/service-name=%{app} -o jsonpath='{range .items[*].endpoints[*]}{.addresses[0]}{"\n"}{end}' > {WS}/q36/endpoints.txt
""",
37: r"""
kubectl -n %{ns} create secret tls %{dom}-tls --cert={WS}/q37/tls.crt --key={WS}/q37/tls.key
kubectl -n %{ns} create ingress %{dom} --class=nginx --rule="%{dom}.example.com/api*=api:8080,tls=%{dom}-tls" --rule="%{dom}.example.com/*=web:80,tls=%{dom}-tls"
""",
# ---- 38-57: the fixed set A (bank_sim_a.py); 41 is further down
38: r"""
kubectl -n danube run ferry --image=httpd:2-alpine --labels=tier=edge --dry-run=client -o json | jq '.spec.containers[0].name = "ferry-web"' | kubectl apply -f -
kubectl -n danube wait --for=condition=Ready pod/ferry --timeout=180s
mkdir -p {WS}/q38
echo "kubectl -n danube get pod ferry -o jsonpath='{.status.phase}'" > {WS}/q38/ferry-status.sh
""",
39: r"""
kubectl -n rhine create cronjob tide-report --image=busybox:1.36 --schedule='*/30 * * * *' --dry-run=client -o json -- sh -c 'echo "tide report $(date +%H:%M)"' | jq '.spec.suspend = true' | kubectl apply -f -
kubectl -n rhine create job tide-report-manual --from=cronjob/tide-report
kubectl -n rhine wait --for=condition=complete job/tide-report-manual --timeout=180s
mkdir -p {WS}/q39
kubectl -n rhine logs job/tide-report-manual > {WS}/q39/manual-run.log
""",
40: r"""
cat <<'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata: {name: harbor-web, namespace: volga, labels: {app: harbor-web}}
spec:
replicas: 2
selector: {matchLabels: {app: harbor-web}}
template:
metadata: {labels: {app: harbor-web}}
spec:
volumes: [{name: html, emptyDir: {}}]
initContainers:
- name: page-gen
image: busybox:1.36
env: [{name: POD_NAME, valueFrom: {fieldRef: {fieldPath: metadata.name}}}]
command: [sh, -c, 'echo "harbor online: $POD_NAME" > /work/index.html']
volumeMounts: [{name: html, mountPath: /work}]
containers:
- name: nginx
image: nginx:1.27
volumeMounts: [{name: html, mountPath: /usr/share/nginx/html}]
EOF
kubectl -n volga expose deploy harbor-web --port=80
kubectl -n volga rollout status deploy harbor-web --timeout=240s
sleep 5
""",
42: r"""
kubectl -n yukon get deploy dispatch-api -o jsonpath='{.spec.template.spec.containers[0].image}' > {WS}/q42/broken-image.txt
kubectl -n yukon rollout undo deploy dispatch-api
kubectl -n yukon rollout status deploy dispatch-api --timeout=240s
kubectl -n yukon get deploy dispatch-api -o jsonpath='{.metadata.annotations.deployment\.kubernetes\.io/revision}' > {WS}/q42/revision.txt
""",
43: r"""
helm -n mekong list --failed -q > {WS}/q43/failed.txt
helm -n mekong uninstall buoy-north >/dev/null
helm -n mekong upgrade buoy-west {WS}/q43/charts/buoy-0.2.0.tgz --reuse-values --set replicaCount=3 >/dev/null
helm -n mekong rollback buoy-east 1 >/dev/null
kubectl -n mekong rollout status deploy buoy-west --timeout=240s
kubectl -n mekong rollout status deploy buoy-east --timeout=240s
""",
44: r"""
C=$(kubectl create --dry-run=client -f {WS}/q44/gauge-pod.yaml -o json | jq -c '.spec.containers[0] | .securityContext = {allowPrivilegeEscalation: false, privileged: false}')
kubectl -n ganges create deploy gauge-sensor --image=busybox:1.36 --replicas=3 --dry-run=client -o json | jq --argjson c "$C" '.spec.template.spec.containers = [$c]' > {WS}/q44/gauge-deployment.yaml
kubectl apply -f {WS}/q44/gauge-deployment.yaml
kubectl -n ganges delete pod gauge-sensor --grace-period=1
kubectl -n ganges rollout status deploy gauge-sensor --timeout=240s
""",
45: r"""
cd {WS}/q45/overlays/staging
cat > kustomization.yaml <<EOF
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources: [../../base]
namespace: orinoco
configMapGenerator:
- name: river-api-settings
envs: [settings.env]
patches:
- path: patch.yaml
replicas:
- {name: river-api, count: 2}
EOF
cat > patch.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata: {name: river-api}
spec:
template:
spec:
containers:
- name: river-api
envFrom: [{configMapRef: {name: river-api-settings}}]
resources: {limits: {memory: 128Mi}}
EOF
kubectl apply -k .
kubectl -n orinoco rollout status deploy river-api --timeout=240s
""",
46: r"""
kubectl -n tagus patch deploy tide-api --type=json -p '[
{"op": "add", "path": "/spec/template/spec/containers/0/readinessProbe", "value": {"exec": {"command": ["test", "-f", "/tmp/warm"]}, "periodSeconds": 5}},
{"op": "add", "path": "/spec/template/spec/containers/0/livenessProbe", "value": {"httpGet": {"path": "/healthz", "port": 8080}, "initialDelaySeconds": 30, "periodSeconds": 10, "failureThreshold": 3}}]'
kubectl -n tagus rollout status deploy tide-api --timeout=300s
""",
47: r"""
kubectl -n elbe debug vault-shim --image=busybox:1.36 --target=main -c dbg -- sh -c "tr '\0' '\n' < /proc/1/environ | grep SHIM_TOKEN; sleep 3600"
for i in $(seq 1 40); do T=$(kubectl -n elbe logs vault-shim -c dbg 2>/dev/null | sed -n 's/^SHIM_TOKEN=//p'); [ -n "$T" ] && break; sleep 3; done
echo "$T" > {WS}/q47/token.txt
""",
48: r"""
for i in $(seq 1 60); do
best=$(for n in seine amur; do kubectl -n $n get pod -o json | jq -r --arg n $n '.items[] | "\(.status.containerStatuses[0].restartCount // 0) \($n)/\(.metadata.name)"'; done | sort -rn | head -1)
[ "${best%% *}" -ge 2 ] 2>/dev/null && kubectl -n seine get events --field-selector type=Warning --no-headers 2>/dev/null | grep -q . && break
sleep 3
done
P=${best#* }
echo "$P" > {WS}/q48/most-restarts.txt
kubectl -n ${P%%/*} get pod ${P#*/} -o jsonpath='{.status.containerStatuses[0].lastState.terminated.exitCode}' > {WS}/q48/exit-code.txt
echo 'kubectl -n seine get events --field-selector type=Warning --sort-by=.lastTimestamp' > {WS}/q48/warnings.sh
""",
49: r"""
kubectl -n nile create role configmap-reader --verb=get,list,watch --resource=configmaps
kubectl -n nile create rolebinding ledger-sync-configmaps --role=configmap-reader --serviceaccount=nile:ledger-sync
for i in $(seq 1 40); do kubectl -n nile logs deploy/ledger-sync --tail=1 2>/dev/null | grep -q 'can list configmaps' && break; sleep 3; done
kubectl -n nile logs deploy/ledger-sync --tail=1 > {WS}/q49/log.txt
""",
50: r"""
kubectl -n indus create secret generic gatekeeper-tls-settings --from-file=tls-settings.conf={WS}/q50/tls-settings.conf --dry-run=client -o json | jq '.immutable = true' | kubectl apply -f -
kubectl -n indus patch deploy gatekeeper --type=json -p '[
{"op": "add", "path": "/spec/template/spec/volumes", "value": [{"name": "tls", "secret": {"secretName": "gatekeeper-tls-settings"}}]},
{"op": "add", "path": "/spec/template/spec/containers/0/volumeMounts", "value": [{"name": "tls", "mountPath": "/etc/nginx/conf.d/tls-settings.conf", "subPath": "tls-settings.conf"}]}]'
kubectl -n indus rollout status deploy gatekeeper --timeout=240s
""",
51: r"""
cat > {WS}/q51/default.conf <<'EOF'
server {
listen 80;
location /status { return 200 "harbor v2 ok"; }
location / { return 200 "harbor v1"; }
}
EOF
kubectl -n niger create cm harbor-nginx --from-file=default.conf={WS}/q51/default.conf --dry-run=client -o yaml | kubectl apply -f -
kubectl -n niger rollout restart deploy harbor-proxy
kubectl -n niger rollout status deploy harbor-proxy --timeout=240s
for i in 1 2 3 4 5; do
kubectl -n niger run tmp --rm -i --restart=Never --image=nginx:1.27 -- curl -s http://harbor-proxy.niger.svc.cluster.local/status > {WS}/q51/status.txt </dev/null 2>/dev/null
grep -q 'harbor v2 ok' {WS}/q51/status.txt && break; kubectl -n niger delete pod tmp --ignore-not-found >/dev/null 2>&1; sleep 3
done
""",
52: r"""
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata: {name: ledger-pair, namespace: murray}
spec:
securityContext: {runAsNonRoot: true, fsGroup: 3000}
volumes: [{name: data, emptyDir: {}}]
containers:
- name: writer-a
image: busybox:1.36
securityContext: {runAsUser: 1000}
command: [sh, -c, "echo a > /data/a.txt; sleep 86400"]
volumeMounts: [{name: data, mountPath: /data}]
- name: writer-b
image: busybox:1.36
securityContext: {runAsUser: 2000}
command: [sh, -c, "echo b > /data/b.txt; sleep 86400"]
volumeMounts: [{name: data, mountPath: /data}]
EOF
kubectl -n murray wait --for=condition=Ready pod/ledger-pair --timeout=180s
sleep 3
""",
53: r"""
echo LimitRange/douro-limits > {WS}/q53/rejected-by.txt
kubectl -n douro set resources deploy batch-runner -c runner --limits=memory=256Mi --requests=memory=128Mi
kubectl -n douro rollout status deploy batch-runner --timeout=240s
""",
54: r"""
kubectl -n oder expose pod buoy-api --name=buoy-svc --port=8080 --target-port=80
sleep 3
for i in 1 2 3 4 5; do
kubectl -n oder run tmp --rm -i --restart=Never --image=nginx:1.27 -- curl -s http://buoy-svc:8080 > {WS}/q54/response.html </dev/null 2>/dev/null
grep -qi 'Welcome to nginx' {WS}/q54/response.html && break; kubectl -n oder delete pod tmp --ignore-not-found >/dev/null 2>&1; sleep 3
done
kubectl -n oder delete pod tmp --ignore-not-found >/dev/null 2>&1
kubectl -n oder logs buoy-api > {WS}/q54/buoy.log
""",
55: r"""
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: vault-ingress, namespace: zambezi}
spec:
podSelector: {matchLabels: {app: vault}}
policyTypes: [Ingress]
ingress:
- from:
- podSelector: {matchLabels: {app: teller}}
- namespaceSelector: {matchLabels: {purpose: monitoring}}
ports: [{protocol: TCP, port: 80}]
EOF
sleep 6
""",
56: r"""
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata: {name: lighthouse, namespace: ebro}
spec: {clusterIP: None, selector: {app: lighthouse}, ports: [{port: 80}]}
---
apiVersion: apps/v1
kind: StatefulSet
metadata: {name: lighthouse, namespace: ebro}
spec:
serviceName: lighthouse
replicas: 3
selector: {matchLabels: {app: lighthouse}}
template:
metadata: {labels: {app: lighthouse}}
spec: {containers: [{name: nginx, image: "nginx:1.27"}]}
EOF
kubectl -n ebro rollout status sts lighthouse --timeout=300s
F=lighthouse-1.lighthouse.ebro.svc.cluster.local
echo $F > {WS}/q56/fqdn.txt
for i in 1 2 3 4 5; do
IP=$(kubectl -n ebro run tmp --rm -i --restart=Never --image=busybox:1.36 -- nslookup $F </dev/null 2>/dev/null | awk '/^Name:/ {n=1} n && /^Address/ {print $NF; exit}')
[ -n "$IP" ] && break; kubectl -n ebro delete pod tmp --ignore-not-found >/dev/null 2>&1; sleep 3
done
echo "$IP" > {WS}/q56/ip.txt
""",
57: r"""
kubectl -n dnieper patch ingress portal --type=json -p '[
{"op": "replace", "path": "/spec/rules/0/http/paths/0/pathType", "value": "Prefix"},
{"op": "replace", "path": "/spec/rules/0/http/paths/0/backend/service/port/number", "value": 8080},
{"op": "replace", "path": "/spec/rules/0/http/paths/1/backend/service/name", "value": "portal-ui"}]'
""",
# ---- 58-64: bank_more.py; 60 is further down
58: r"""
cd {WS}/q58
sed -i 's#^apiVersion: extensions/v1beta1#apiVersion: apps/v1#' deployment.yaml
yq -i '.spec.selector.matchLabels.app = "%{app}"' deployment.yaml
kubectl apply -f deployment.yaml
kubectl apply -f flag.yaml --dry-run=server 2> warning.txt >/dev/null
sed -i 's#rollout.example.com/v1beta1#rollout.example.com/v1#' flag.yaml
kubectl apply -f flag.yaml
kubectl api-resources --no-headers | awk '{k=$NF; if (k=="CronJob" || k=="HorizontalPodAutoscaler" || k=="EndpointSlice" || k=="PriorityClass") print k, $(NF-2)}' > served.txt
kubectl -n %{ns} rollout status deploy %{app} --timeout=240s
""",
59: r"""
cd {WS}/q59
echo cc > shortname.txt
kubectl get cc -A --no-headers | awk '$NF == "Degraded" {print $1 "/" $2}' > degraded.txt
MAX=$(kubectl get crd cacheclusters.cache.example.com -o jsonpath='{.spec.versions[0].schema.openAPIV3Schema.properties.spec.properties.replicas.maximum}')
cat > cache.yaml <<EOF
apiVersion: cache.example.com/v1
kind: CacheCluster
metadata:
name: %{name}
namespace: %{ns}
spec:
engine: %{eng}
replicas: $MAX
memoryMb: %{mem}
EOF
kubectl apply -f cache.yaml
""",
61: r"""
cd {WS}/q61/app
sed -i 's/name: store-front/name: storefront/' replicas-patch.yaml
cat > kustomization.yaml <<EOF
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
patches:
- path: replicas-patch.yaml
namespace: %{ns}
nameSuffix: -%{sfx}
images:
- {name: nginx, newTag: "1.27"}
secretGenerator:
- name: storefront-token
literals: [token=%{tok}]
generatorOptions:
disableNameSuffixHash: true
EOF
kubectl apply -k .
kubectl -n %{ns} rollout status deploy storefront-%{sfx} --timeout=240s
""",
62: r"""
echo Unhealthy > {WS}/q62/reason.txt
kubectl -n %{ns} patch deploy %{dep} --type=json -p '[
{"op": "replace", "path": "/spec/template/spec/containers/0/livenessProbe/httpGet/port", "value": %{port}},
{"op": "replace", "path": "/spec/template/spec/containers/0/readinessProbe/httpGet/path", "value": "/%{ready}"}]'
kubectl -n %{ns} rollout status deploy %{dep} --timeout=240s
""",
63: r"""
echo "kubectl -n %{ns} get pods -o custom-columns=POD:.metadata.name,NODE:.spec.nodeName,IMAGE:.spec.containers[0].image" > {WS}/q63/inventory.sh
kubectl -n %{ns} get pods -l tier=%{tier} --field-selector='status.phase!=Running' -o name | cut -d/ -f2 > {WS}/q63/not-running.txt
kubectl -n %{ns} get pods -o jsonpath='{range .items[*]}{.spec.nodeName}{"\n"}{end}' | grep . | sort | uniq -c | sort -rn | awk 'NR==1 {print $2}' > {WS}/q63/busiest.txt
""",
64: r"""
for i in $(seq 1 30); do
B=$(kubectl -n %{ns} logs -l app=%{app} --prefix --tail=-1 2>/dev/null | grep ERROR | head -1 | sed 's#^\[pod/\([^/]*\)/.*#\1#')
[ -n "$B" ] && break; sleep 3
done
echo "$B" > {WS}/q64/bad-pod.txt
kubectl -n %{ns} logs "$B" | grep ERROR > {WS}/q64/errors.log
kubectl -n %{ns} logs %{app}-0 -c migrate | awk '/schema version/ {print $3}' > {WS}/q64/schema.txt
echo "kubectl -n %{ns} logs -l app=%{app} --prefix --tail=2" > {WS}/q64/tail.sh
""",
13: r"""
cd {WS}/q13/image
podman build -q -t localhost/%{img}:v1 . >/dev/null
sed -i 's/^ENV GREETING=hello/ENV GREETING=%{greet}\nENV APP_VERSION=v2/' Dockerfile
podman build -q -t localhost/%{img}:v2 . >/dev/null
podman save --format oci-archive -o {WS}/q13/%{img}-v2.tar localhost/%{img}:v2 >/dev/null 2>&1
podman run --rm localhost/%{img}:v2 > {WS}/q13/run.txt
""",
41: r"""
cd {WS}/q41/image
docker build -q --build-arg APP_VERSION=2.1 -t localhost/beacon-relay:2.1 -t localhost/beacon-relay:latest . >/dev/null
sudo podman run -d --name beacon-relay localhost/beacon-relay:2.1 >/dev/null
sleep 2; podman logs beacon-relay > {WS}/q41/relay.log
""",
60: r"""
cd {WS}/q60/image
printf 'FROM docker.io/library/busybox:1.36\nLABEL team=%{team}\nCOPY report.sh /app/report.sh\nRUN chmod 0755 /app/report.sh\nUSER 10001\nENTRYPOINT ["/app/report.sh"]\nCMD ["%{arg}"]\n' > Dockerfile
podman build -q -t localhost/%{img}:1.1 . >/dev/null
podman run --rm localhost/%{img}:1.1 > {WS}/q60/default.txt
podman run --rm localhost/%{img}:1.1 %{arg2} > {WS}/q60/%{arg2}.txt
podman rmi localhost/%{img}:1.0 >/dev/null
""",
# ---- 65-76: Services and Networking (bank_net.py). A policy needs a moment to reach the node, and
# ---- the audit grades the instant the script ends - hence the sleeps a person would not need.
65: r"""
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: default-deny-ingress, namespace: %{ns}}
spec:
podSelector: {}
policyTypes: [Ingress]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: api-from-web, namespace: %{ns}}
spec:
podSelector: {matchLabels: {app: api}}
policyTypes: [Ingress]
ingress:
- from: [{podSelector: {matchLabels: {app: web}}}]
ports: [{protocol: TCP, port: 80}]
EOF
sleep 5
""",
66: r"""
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: metrics-scrape, namespace: %{ns}}
spec:
podSelector: {matchLabels: {app: metrics}}
policyTypes: [Ingress]
ingress:
- from:
- namespaceSelector: {matchLabels: {purpose: monitoring}}
podSelector: {matchLabels: {role: scraper}}
ports: [{protocol: TCP, port: 80}]
EOF
sleep 5
""",
67: r"""
kubectl -n %{ns} patch netpol cache-allow --type=json -p '[{"op": "replace", "path": "/spec/ingress/0/from/0/podSelector/matchLabels", "value": {"app": "worker"}}, {"op": "replace", "path": "/spec/ingress/0/ports/0/port", "value": 80}]'
sleep 5
""",
68: r"""
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: same-namespace, namespace: %{ns}}
spec:
podSelector: {}
policyTypes: [Ingress]
ingress:
- from: [{podSelector: {}}]
EOF
sleep 5
""",
69: r"""
kubectl -n %{ns} label pod reporter access=db
sleep 5
""",
70: r"""
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata: {name: %{app}, namespace: %{ns}}
spec:
selector: {app: %{app}}
sessionAffinity: ClientIP
ports:
- name: web
port: %{port}
targetPort: http-main
EOF
sleep 3
""",
71: r"""
kubectl -n %{ns} create service externalname inventory --external-name warehouse.%{ns}-data.svc.cluster.local
echo warehouse.%{ns}-data.svc.cluster.local > {WS}/q71/target.txt
""",
72: r"""
kubectl -n %{ns} describe pod -l app=%{app} | grep -i 'readiness probe failed' | head -1
echo 404 > {WS}/q72/code.txt
kubectl -n %{ns} patch deploy %{app} --type=json -p '[{"op": "replace", "path": "/spec/template/spec/containers/0/readinessProbe/httpGet/path", "value": "/"}]'
kubectl -n %{ns} rollout status deploy %{app} --timeout=180s
""",
73: r"""
IP=$(kubectl -n %{ns}-old get pod legacy -o jsonpath='{.status.podIP}')
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata: {name: legacy-gw, namespace: %{ns}}
spec:
ports: [{port: 80, targetPort: 80}]
---
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: legacy-gw-1
namespace: %{ns}
labels: {kubernetes.io/service-name: legacy-gw}
addressType: IPv4
ports: [{port: 80, protocol: TCP}]
endpoints: [{addresses: ["$IP"]}]
EOF
sleep 3
""",
74: r"""
kubectl -n %{ns} set selector svc %{app} app=%{app},version=v2
sleep 4
kubectl -n %{ns} exec probe -- wget -T 3 -qO- http://%{app} > {WS}/q74/response.txt
""",
75: r"""
kubectl -n %{ns} create ingress %{dom} --class=nginx --rule="shop.%{dom}.test/*=shop:80" --rule="blog.%{dom}.test/*=blog:8080" --default-backend=fallback:80
""",
76: r"""
kubectl -n %{ns} create ingress %{dom}-routes --class=nginx --rule="%{dom}.example.com/healthz=status:8080" --rule="%{dom}.example.com/app*=app:80" --annotation nginx.ingress.kubernetes.io/rewrite-target=/
""",
# ---- 77-84: Application Design and Build (bank_design.py)
77: r"""
cd {WS}/q77/image
printf 'FROM busybox:1.36\nWORKDIR /app\nCOPY run.sh /app/run.sh\nRUN chmod +x /app/run.sh\nENTRYPOINT ["/app/run.sh"]\n' > Dockerfile
podman build -q -t localhost/%{img}:1.0 . >/dev/null
podman tag localhost/%{img}:1.0 localhost/%{img}:stable
podman run --rm localhost/%{img}:1.0 > {WS}/q77/out.txt
""",
78: r"""
podman image inspect localhost/%{img}:%{tag} --format 'user={{.Config.User}}
workdir={{.Config.WorkingDir}}
tier={{index .Config.Labels "tier"}}' > {WS}/q78/facts.txt
echo "port=$(podman image inspect localhost/%{img}:%{tag} --format '{{range $p, $v := .Config.ExposedPorts}}{{$p}}{{end}}' | cut -d/ -f1)" >> {WS}/q78/facts.txt
podman run --rm -e MODE=debug localhost/%{img}:%{tag} check > {WS}/q78/run.txt
""",
79: r"""
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata: {name: %{app}, namespace: %{ns}}
spec:
clusterIP: None
selector: {app: %{app}}
ports: [{port: 80}]
---
apiVersion: apps/v1
kind: StatefulSet
metadata: {name: %{app}, namespace: %{ns}}
spec:
serviceName: %{app}
replicas: 2
selector: {matchLabels: {app: %{app}}}
template:
metadata: {labels: {app: %{app}}}
spec:
containers:
- name: store
image: busybox:1.36
command: ["sh", "-c", "hostname >> /data/started; exec httpd -f -p 80 -h /data"]
volumeMounts: [{name: data, mountPath: /data}]
volumeClaimTemplates:
- metadata: {name: data}
spec:
accessModes: [ReadWriteOnce]
storageClassName: standard
resources: {requests: {storage: 50Mi}}
EOF
kubectl -n %{ns} rollout status sts %{app} --timeout=240s
kubectl -n %{ns} delete pod %{app}-0
sleep 3
kubectl -n %{ns} rollout status sts %{app} --timeout=240s
kubectl -n %{ns} wait --for=condition=Ready pod/%{app}-0 --timeout=120s
""",
80: r"""
cat <<'EOF' | kubectl apply -f -
apiVersion: batch/v1
kind: Job
metadata: {name: %{job}, namespace: %{ns}}
spec:
backoffLimit: 4
activeDeadlineSeconds: 120
ttlSecondsAfterFinished: 600
template:
spec:
restartPolicy: OnFailure
volumes: [{name: work, emptyDir: {}}]
containers:
- name: import
image: busybox:1.36
command: ["sh", "-c", "if [ -f /work/tried ]; then echo imported; else touch /work/tried; echo failing once; exit 1; fi"]
volumeMounts: [{name: work, mountPath: /work}]
EOF
kubectl -n %{ns} wait --for=condition=Complete job/%{job} --timeout=120s
""",
81: r"""
cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata: {name: %{pod}, namespace: %{ns}}
spec:
volumes:
- name: conf
configMap: {name: ambassador-conf}
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "while true; do wget -T 2 -qO- http://localhost:8080/ > /tmp/last 2>&1; sleep 5; done"]
- name: ambassador
image: nginx:1.27
volumeMounts: [{name: conf, mountPath: /etc/nginx/conf.d}]
EOF
kubectl -n %{ns} wait --for=condition=Ready pod/%{pod} --timeout=180s
sleep 8
""",
82: r"""
kubectl -n %{ns} get pod %{pod} -o json | jq 'del(.status, .metadata.uid, .metadata.resourceVersion, .metadata.creationTimestamp) | .spec.initContainers |= reverse | .spec.containers[0].volumeMounts += [{"name": "work", "mountPath": "/work"}]' > /tmp/pod.json
kubectl replace --force -f /tmp/pod.json
kubectl -n %{ns} wait --for=condition=Ready pod/%{pod} --timeout=180s
""",
83: r"""
kubectl -n %{ns} delete pvc %{pvc}
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata: {name: %{pvc}, namespace: %{ns}}
spec:
accessModes: [ReadWriteOnce]
storageClassName: manual
resources: {requests: {storage: 1Gi}}
EOF
kubectl -n %{ns} wait --for=condition=Ready pod/%{pod} --timeout=180s
""",
84: r"""
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata: {name: %{pod}, namespace: %{ns}}
spec:
volumes:
- name: status
configMap: {name: %{cm}}
containers:
- name: nginx
image: nginx:1.27
volumeMounts:
- name: status
mountPath: /etc/nginx/conf.d/status.conf
subPath: status.conf
readOnly: true
EOF
kubectl -n %{ns} wait --for=condition=Ready pod/%{pod} --timeout=180s
""",
# ---- 85-93: Application Deployment (bank_deploy.py)
85: r"""
kubectl -n %{ns} get deploy %{app}-canary -o jsonpath='{.spec.template.spec.containers[0].image}' > {WS}/q85/image.txt
kubectl -n %{ns} set image deploy/%{app}-stable web="$(cat {WS}/q85/image.txt)"
kubectl -n %{ns} rollout status deploy/%{app}-stable --timeout=240s
kubectl -n %{ns} delete deploy %{app}-canary
sleep 20
""",
86: r"""
kubectl -n %{ns} patch deploy %{dep} --type=json -p '[{"op": "replace", "path": "/spec/strategy", "value": {"type": "Recreate"}}]'
kubectl -n %{ns} set image deploy/%{dep} nginx=nginx:1.27
kubectl -n %{ns} rollout status deploy/%{dep} --timeout=240s
echo Recreate > {WS}/q86/strategy.txt
""",
87: r"""
kubectl -n %{ns} rollout pause deploy/%{dep}
kubectl -n %{ns} set image deploy/%{dep} nginx=nginx:1.27
kubectl -n %{ns} set env deploy/%{dep} LOG_LEVEL=debug
kubectl -n %{ns} set resources deploy/%{dep} --requests=cpu=50m,memory=64Mi
kubectl -n %{ns} rollout resume deploy/%{dep}
kubectl -n %{ns} rollout status deploy/%{dep} --timeout=240s
sleep 10
""",
88: r"""
for i in 1 2 3 4; do kubectl -n %{ns} rollout history deploy/%{dep} --revision=$i | grep -q 'busybox:%{tag}' && echo $i; done > {WS}/q88/revision.txt
kubectl -n %{ns} rollout undo deploy/%{dep} --to-revision=$(cat {WS}/q88/revision.txt)
kubectl -n %{ns} annotate deploy %{dep} kubernetes.io/change-cause="rollback to busybox:%{tag}" --overwrite
kubectl -n %{ns} rollout status deploy/%{dep} --timeout=240s
sleep 35
""",
89: r"""
kubectl -n %{ns} rollout restart deploy/%{app}
kubectl -n %{ns} rollout status deploy/%{app} --timeout=240s
kubectl -n %{ns} annotate deploy %{app} kubernetes.io/change-cause="secret rotation" --overwrite
sleep 35
kubectl -n %{ns} logs deploy/%{app} | head -1 > {WS}/q89/started.txt
""",
90: r"""
cd {WS}/q90
NS=$(helm list -A -o json | jq -r '.[] | select(.name == "%{rel}") | .namespace')
echo $NS > namespace.txt
helm list -A -o json | jq -r '.[] | select(.name == "%{rel}") | .chart' > chart.txt
helm -n $NS get values %{rel} -o yaml > values.yaml
helm -n $NS get manifest %{rel} > manifest.yaml
""",
91: r"""
helm repo add internal http://localhost:30080 >/dev/null
helm repo update >/dev/null
helm search repo internal/webapp --versions -o json | jq -r '.[].version' > {WS}/q91/versions.txt
helm install %{rel} internal/webapp --version 1.2.0 -n %{ns} --create-namespace --set replicaCount=2 >/dev/null
helm upgrade %{rel} internal/webapp -n %{ns} --reuse-values >/dev/null
""",
92: r"""
echo image.tag > {WS}/q92/key.txt
helm -n %{ns} upgrade %{rel} {WS}/q92/chart --reuse-values --set image.tag=1.27 --wait --timeout 180s >/dev/null
helm -n %{ns}-archive uninstall %{rel}-old >/dev/null
sleep 10
""",
93: r"""
cd {WS}/q93/app
printf 'apiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nnamespace: %{ns}\nresources:\n - deployment.yaml\nconfigMapGenerator:\n - name: %{app}-config\n envs:\n - settings.env\n' > kustomization.yaml
kubectl apply -k .
kubectl -n %{ns} rollout status deploy/%{app} --timeout=240s
sed -i 's/^MODE=blue/MODE=green/' settings.env
kubectl apply -k .
kubectl -n %{ns} rollout status deploy/%{app} --timeout=240s
sleep 35
""",
# ---- 94-99: Application Observability and Maintenance (bank_obs.py)
94: r"""
kubectl -n %{ns} patch deploy %{app} --type=json -p '[{"op": "add", "path": "/spec/template/spec/containers/0/readinessProbe", "value": {"httpGet": {"path": "/ready", "port": 80}, "periodSeconds": 3, "failureThreshold": 1}}]'
kubectl -n %{ns} rollout status deploy/%{app} --timeout=240s
sleep 35
POD=$(kubectl -n %{ns} get pod -l app=%{app} --field-selector status.phase=Running -o jsonpath='{.items[0].metadata.name}')
kubectl -n %{ns} exec $POD -- rm /www/ready
echo $POD > {WS}/q94/unready.txt
sleep 10
""",
95: r"""
kubectl -n %{ns} patch deploy %{dep} --type=json -p '[{"op": "add", "path": "/spec/template/spec/containers/0/startupProbe", "value": {"httpGet": {"path": "/", "port": 80}, "periodSeconds": 5, "failureThreshold": 18}}]'
kubectl -n %{ns} rollout status deploy/%{dep} --timeout=240s
""",
96: r"""
echo FailedScheduling > {WS}/q96/reason.txt
kubectl -n %{ns} get events --field-selector reason=FailedScheduling -o jsonpath='{.items[-1:].message}' > {WS}/q96/message.txt
kubectl -n %{ns} set resources deploy/%{dep} --requests=cpu=50m,memory=640Mi --limits=memory=640Mi
kubectl -n %{ns} rollout status deploy/%{dep} --timeout=240s
""",
97: r"""
kubectl -n %{ns} logs -l job-name=%{job} --tail=-1 | grep '^result' > {WS}/q97/results.txt
kubectl -n %{ns} logs job/%{job}-load | grep '^FATAL' > {WS}/q97/fatal.txt
kubectl -n %{ns} logs job/%{job}-load | head -1 > {WS}/q97/first.txt
""",
98: r"""
kubectl -n %{ns} logs deploy/%{dep} -c migrate > {WS}/q98/why.log 2>&1
kubectl -n %{ns} patch cm %{dep}-config -p '{"data": {"DB_HOST": "db.%{ns}.svc"}}'
kubectl -n %{ns} rollout restart deploy/%{dep}
kubectl -n %{ns} rollout status deploy/%{dep} --timeout=240s
sleep 5
""",
99: r"""
kubectl -n %{ns} debug %{pod} --image=busybox:1.36 --target=whoami --container=dbg -- sh -c 'tr "\0" "\n" < /proc/1/environ | grep WHOAMI_PORT_NUMBER; sleep 3600' >/dev/null 2>&1
for i in $(seq 1 20); do P=$(kubectl -n %{ns} logs %{pod} -c dbg 2>/dev/null | sed -n 's/^WHOAMI_PORT_NUMBER=//p'); [ -n "$P" ] && break; sleep 2; done
echo $P > {WS}/q99/port.txt
kubectl -n %{ns} patch svc %{pod} -p "{\"spec\": {\"ports\": [{\"port\": 80, \"targetPort\": $P}]}}"
sleep 5
""",
# ---- 100-105: Application Environment, Configuration and Security (bank_env.py)
100: r"""
kubectl -n %{ns} create rolebinding %{dev}-view --clusterrole=view --user=%{dev}
kubectl -n %{ns} create role pod-restarter --verb=delete --resource=pods
kubectl -n %{ns} create rolebinding %{dev}-restart --role=pod-restarter --user=%{dev}
kubectl -n %{ns} create rolebinding %{lead}-edit --clusterrole=edit --user=%{lead}
for q in "get pods --subresource=log --as=%{dev}" "get secrets --as=%{dev}" "create rolebindings --as=%{lead}"; do kubectl -n %{ns} auth can-i $q; done > {WS}/q100/answers.txt 2>/dev/null
""",
101: r"""
echo requests.cpu > {WS}/q101/resource.txt
kubectl -n %{ns} set resources deploy/%{dep} --requests=cpu=200m,memory=64Mi
kubectl -n %{ns} rollout status deploy/%{dep} --timeout=240s
sleep 5
""",
102: r"""
cd {WS}/q102
kubectl -n %{ns} create configmap %{cm}-env --from-env-file=logging.env
kubectl -n %{ns} create configmap %{cm}-files --from-file=application.properties=app.properties
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata: {name: %{pod}, namespace: %{ns}}
spec:
volumes:
- name: files
configMap:
name: %{cm}-files
defaultMode: 0440
items:
- {key: application.properties, path: conf/app.properties}
containers:
- name: sender
image: busybox:1.36
command: [sleep, "86400"]
envFrom:
- prefix: LOG_
configMapRef: {name: %{cm}-env}
volumeMounts: [{name: files, mountPath: /etc/app}]
EOF
kubectl -n %{ns} wait --for=condition=Ready pod/%{pod} --timeout=180s
""",
103: r"""
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata: {name: %{pod}, namespace: %{ns}}
spec:
securityContext:
runAsUser: %{uid}
runAsGroup: %{gid}
fsGroup: %{fsg}
volumes: [{name: work, emptyDir: {}}]
containers:
- name: scanner
image: busybox:1.36
command: [sleep, "86400"]
securityContext:
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities: {drop: [ALL]}
volumeMounts: [{name: work, mountPath: /work}]
EOF
kubectl -n %{ns} wait --for=condition=Ready pod/%{pod} --timeout=180s
kubectl -n %{ns} exec %{pod} -- id > {WS}/q103/id.txt
kubectl -n %{ns} exec %{pod} -- touch /work/proof
""",
104: r"""
kubectl -n %{ns} create sa %{dep}
kubectl -n %{ns} create role %{dep} --verb=get --resource=configmaps --resource-name=feature-flags
kubectl -n %{ns} create rolebinding %{dep} --role=%{dep} --serviceaccount=%{ns}:%{dep}
kubectl -n %{ns} set serviceaccount deploy/%{dep} %{dep}
kubectl -n %{ns} rollout status deploy/%{dep} --timeout=240s
sleep 12
""",
105: r"""
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata: {name: %{pod}, namespace: %{ns}}
spec:
volumes:
- name: ca
secret:
secretName: %{sec}-ca
items:
- {key: ca.crt, path: ca.crt}
containers:
- name: client
image: busybox:1.36
command: [sleep, "86400"]
envFrom:
- prefix: DB_
secretRef: {name: %{sec}}
volumeMounts: [{name: ca, mountPath: /etc/ssl/db, readOnly: true}]
EOF
kubectl -n %{ns} wait --for=condition=Ready pod/%{pod} --timeout=180s
""",
}