kubestronaut-sim/cka/solve.py
vumas3r 0e5f7686ee feat: add guided lessons; move CKA and CKS to v1.37 on Ubuntu hosts
Add lessons: untimed guided labs, one per curriculum objective, on a
host of their own, in the browser and in the terminal. Each step has
a check, a hint and its commands; a lesson writes no history or
scores, only a done mark on its objective. CKAD 2.1-2.4 (24 steps)
and all 27 CKA objectives (135 steps) have one, and `./ksim lesson
audit` proves that every step fails, is done by its own commands,
then passes.

Add CKA tasks 132-147 (cka/bank_course.py) on what a full CKA course
teaches beyond the bank: a real upgrade from v1.36, a drain under a
PodDisruptionBudget, QoS and eviction, in-place resize, kube-proxy's
chains, encryption at rest, an operator, and CSI expansion, snapshots
and clones (ensure_csi_hostpath).

Move CKA and CKS to Kubernetes v1.37 and rebuild the hosts on Ubuntu
24.04, with kubelet, kubeadm and kubectl as held packages from
pkgs.k8s.io; the desktop runs Ubuntu too. Hosts gain man pages,
hexdump and xxd.

Fix CKS Q4, which gave 3 of 12 points untouched, and CKA Q4 and Q49,
whose solutions raced their new CRD.

Audited on v1.37: CKA 147/147, CKAD 105/105, CKS 109/110 (Q103, a
Falco task, failed to seed once and awaits a re-audit); lessons: 135
of 135 CKA steps and 24 of 24 CKAD steps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 20:15:17 -06:00

774 lines
39 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.
A command that reads stdin (ssh, kubectl run -i) would swallow the rest of the script: ssh -n,
</dev/null.
SOLVE: task id -> bash
"""
# cpwait COMPONENT: the control-plane component's static Pod on this host is ready (again)
CPWAIT = r"""
cpwait() {
local i
for i in $(seq 1 80); do
[ "$(kubectl -n kube-system get pod $1-{CP} -o jsonpath='{.status.containerStatuses[0].ready}' 2>/dev/null)" = true ] && return 0
sleep 3
done
return 1
}
"""
SOLVE = {
# ---- 114-124: bank_storage.py
114: r"""
cat <<EOF | kubectl apply -f -
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata: {name: %{sc}}
provisioner: rancher.io/local-path
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata: {name: %{pvc}, namespace: %{ns}}
spec:
storageClassName: %{sc}
accessModes: [ReadWriteOnce]
resources: {requests: {storage: 300Mi}}
---
apiVersion: v1
kind: Pod
metadata: {name: %{pod}, namespace: %{ns}}
spec:
terminationGracePeriodSeconds: 2
volumes: [{name: data, persistentVolumeClaim: {claimName: %{pvc}}}]
containers:
- name: writer
image: busybox:1.36
command: [sh, -c, "echo %{text} > /data/keep.txt; sleep 86400"]
volumeMounts: [{name: data, mountPath: /data}]
EOF
kubectl -n %{ns} wait --for=condition=Ready pod/%{pod} --timeout=240s
kubectl -n %{ns} get pvc %{pvc} -o jsonpath='{.spec.volumeName}' > {WS}/q114/pv.txt
kubectl -n %{ns} delete pod %{pod}
kubectl -n %{ns} delete pvc %{pvc}
sleep 4
kubectl get pv $(cat {WS}/q114/pv.txt) -o jsonpath='{.status.phase}' > {WS}/q114/phase.txt
""",
115: r"""
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata: {name: %{pod}, namespace: %{ns}}
spec:
volumes:
- name: scratch
ephemeral:
volumeClaimTemplate:
spec:
storageClassName: standard
accessModes: [ReadWriteOnce]
resources: {requests: {storage: 100Mi}}
- name: cache
emptyDir: {medium: Memory, sizeLimit: 32Mi}
containers:
- name: encoder
image: busybox:1.36
command: [sleep, "86400"]
volumeMounts:
- {name: scratch, mountPath: /scratch}
- {name: cache, mountPath: /cache}
EOF
kubectl -n %{ns} wait --for=condition=Ready pod/%{pod} --timeout=240s
kubectl -n %{ns} get pvc -o jsonpath='{.items[0].metadata.name}' > {WS}/q115/claim.txt
kubectl -n %{ns} get pvc %{pod}-scratch -o jsonpath='{.metadata.ownerReferences[0].kind}/{.metadata.ownerReferences[0].name}' > {WS}/q115/owner.txt
""",
116: r"""
kubectl -n %{ns} patch sts %{sts} -p '{"spec":{"persistentVolumeClaimRetentionPolicy":{"whenScaled":"Delete","whenDeleted":"Retain"}}}'
kubectl -n %{ns} scale sts %{sts} --replicas=1
for i in $(seq 1 40); do [ "$(kubectl -n %{ns} get pvc --no-headers | wc -l)" = 1 ] && break; sleep 3; done
kubectl -n %{ns} get pvc -o name | cut -d/ -f2 > {WS}/q116/claims.txt
""",
117: r"""
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolume
metadata: {name: %{pv}}
spec:
capacity: {storage: 1Gi}
accessModes: [ReadWriteOnce]
storageClassName: manual
persistentVolumeReclaimPolicy: Retain
hostPath: {path: /mnt/%{pv}}
claimRef: {namespace: %{ns}, name: %{pvc}}
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata: {name: %{pvc}, namespace: %{ns}}
spec:
storageClassName: manual
accessModes: [ReadWriteOnce]
resources: {requests: {storage: 1Gi}}
EOF
sleep 5
""",
118: r"""
kubectl -n %{ns} get pvc %{pvc} -o jsonpath='{.spec.volumeName}' > {WS}/q118/wrong-pv.txt
kubectl -n %{ns} scale deploy %{dep} --replicas=0
kubectl -n %{ns} wait --for=delete pod -l app=%{dep} --timeout=120s
kubectl -n %{ns} delete pvc %{pvc}
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata: {name: %{pvc}, namespace: %{ns}}
spec:
storageClassName: ""
volumeName: %{pv}
accessModes: [ReadWriteOnce]
resources: {requests: {storage: 500Mi}}
EOF
kubectl -n %{ns} scale deploy %{dep} --replicas=1
kubectl -n %{ns} rollout status deploy %{dep} --timeout=240s
""",
119: r"""
printf 'reports capacity\narchive accessmode\nmedia class\n' > {WS}/q119/reasons.txt
kubectl -n %{ns} delete pvc reports archive media
for c in "reports 1Gi" "archive 2Gi" "media 1Gi"; do set -- $c; cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata: {name: $1, namespace: %{ns}}
spec:
storageClassName: manual
accessModes: [ReadWriteOnce]
selector: {matchLabels: {use: $1}}
resources: {requests: {storage: $2}}
EOF
done
sleep 5
""",
120: r"""
for i in $(seq 1 40); do
M=$(kubectl -n %{ns} get events --field-selector reason=FailedScheduling -o jsonpath='{.items[-1:].message}')
[ -n "$M" ] && break; sleep 3
done
echo "$M" > {WS}/q120/event.txt
kubectl -n %{ns} patch deploy %{dep} --type=json -p '[{"op":"remove","path":"/spec/template/spec/nodeSelector"}]'
kubectl -n %{ns} rollout status deploy %{dep} --timeout=240s
""",
121: r"""
kubectl -n %{ns} get pvc %{pvc} -o jsonpath='{.metadata.finalizers[0]}' > {WS}/q121/finalizer.txt
echo deployment/%{dep} > {WS}/q121/used-by.txt
kubectl -n %{ns} patch deploy %{dep} --type=json -p '[{"op":"replace","path":"/spec/template/spec/volumes/0","value":{"name":"files","emptyDir":{}}}]'
kubectl -n %{ns} rollout status deploy %{dep} --timeout=240s
""",
122: r"""
kubectl -n local-path-storage scale deploy local-path-provisioner --replicas=1
kubectl get sc %{sc} -o json | jq 'del(.metadata.uid, .metadata.resourceVersion, .metadata.creationTimestamp, .metadata.managedFields, .metadata.annotations) | .provisioner = "rancher.io/local-path"' > /tmp/sc.json
kubectl delete sc %{sc}
kubectl apply -f /tmp/sc.json
for i in $(seq 1 40); do [ "$(kubectl -n %{ns} get pvc %{pvc} -o jsonpath='{.status.phase}')" = Bound ] && break; sleep 3; done
kubectl -n %{ns} wait --for=condition=Ready pod/%{pod} --timeout=240s
echo rancher.io/local-path > {WS}/q122/provisioner.txt
""",
123: r"""
kubectl annotate sc %{sc} storageclass.kubernetes.io/is-default-class=true
for i in $(seq 1 40); do [ "$(kubectl -n %{ns} get pvc %{pvc} -o jsonpath='{.status.phase}')" = Bound ] && break; sleep 3; done
kubectl -n %{ns} wait --for=condition=Ready pod/%{pod} --timeout=240s
kubectl -n %{ns} get pvc %{pvc} -o jsonpath='{.spec.storageClassName}' > {WS}/q123/class.txt
""",
124: r"""
cat <<EOF | kubectl apply -f -
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata: {name: %{sc}}
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: v1
kind: PersistentVolume
metadata: {name: %{pv}}
spec:
capacity: {storage: 1Gi}
accessModes: [ReadWriteOnce]
storageClassName: %{sc}
persistentVolumeReclaimPolicy: Retain
local: {path: /mnt/disks/%{pv}}
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions: [{key: kubernetes.io/hostname, operator: In, values: [{W2}]}]
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata: {name: %{pvc}, namespace: %{ns}}
spec:
storageClassName: %{sc}
accessModes: [ReadWriteOnce]
resources: {requests: {storage: 1Gi}}
---
apiVersion: v1
kind: Pod
metadata: {name: %{pod}, namespace: %{ns}}
spec:
volumes: [{name: disk, persistentVolumeClaim: {claimName: %{pvc}}}]
containers:
- name: prom
image: busybox:1.36
command: [sleep, "86400"]
volumeMounts: [{name: disk, mountPath: /disk}]
EOF
kubectl -n %{ns} wait --for=condition=Ready pod/%{pod} --timeout=240s
""",
# ---- 125-128: bank_net.py
125: r"""
kubectl -n kube-system get cm coredns -o yaml > {WS}/q125/coredns-backup.yaml
ADD=$' hosts {\n %{ip} %{host}.dc.internal\n fallthrough\n }\n rewrite name %{svc}.dc.internal %{svc}.%{ns}.svc.cluster.local\n'
kubectl -n kube-system get cm coredns -o json | jq --arg add "$ADD" '.data.Corefile |= sub(" kubernetes "; $add + " kubernetes ")' | kubectl apply -f -
kubectl -n kube-system rollout restart deploy coredns
kubectl -n kube-system rollout status deploy coredns --timeout=240s
""",
126: r"""
cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata: {name: %{gw}, namespace: %{ns}}
spec:
gatewayClassName: %{gc}
listeners:
- name: https
protocol: HTTPS
port: 443
hostname: %{host}.example.com
tls:
mode: Terminate
certificateRefs: [{kind: Secret, name: %{host}-tls}]
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata: {name: %{host}, namespace: %{ns}}
spec:
parentRefs: [{name: %{gw}}]
hostnames: [%{host}.example.com]
rules:
- matches: [{path: {type: PathPrefix, value: /api}}]
backendRefs: [{name: api, port: 8080}]
- matches: [{path: {type: PathPrefix, value: /}}]
backendRefs: [{name: web, port: 80}]
EOF
kubectl -n %{ns} delete ingress %{host}
""",
127: r"""
kubectl annotate ingressclass %{old} ingressclass.kubernetes.io/is-default-class-
kubectl annotate ingressclass %{cls} ingressclass.kubernetes.io/is-default-class=true
kubectl -n %{ns} patch ingress %{ing} --type=json -p '[
{"op": "add", "path": "/spec/ingressClassName", "value": "%{cls}"},
{"op": "replace", "path": "/spec/rules/0/http/paths/0/backend/service/port", "value": {"name": "http"}},
{"op": "replace", "path": "/spec/rules/0/http/paths/1/pathType", "value": "Prefix"}]'
kubectl apply -f {WS}/q127/blog-ingress.yaml
""",
128: r"""
kubectl -n %{ns} patch ds %{ds} --type=json -p '[{"op": "add", "path": "/spec/template/spec/containers/0/ports/0/hostPort", "value": %{hp}}]'
kubectl -n %{ns} rollout status ds %{ds} --timeout=240s
yq -i '.spec.dnsPolicy = "ClusterFirstWithHostNet"' {WS}/q128/collector.yaml
kubectl -n %{ns} delete pod %{pod} --grace-period=1
kubectl apply -f {WS}/q128/collector.yaml
kubectl -n %{ns} wait --for=condition=Ready pod/%{pod} --timeout=180s
kubectl -n %{ns} get pod %{pod} -o jsonpath='{.status.podIP} {.status.hostIP}' > {WS}/q128/ips.txt
sleep 8
""",
# ---- 129-131: bank_tshoot.py
129: r"""
ssh -n {W1} 'sudo systemctl enable --now containerd; echo "runtime-endpoint: unix:///run/containerd/containerd.sock" | sudo tee /etc/crictl.yaml >/dev/null'
echo containerd > {WS}/q129/unit.txt
for i in $(seq 1 40); do [ "$(kubectl get node {W1} -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}')" = True ] && break; sleep 3; done
""",
130: r"""
for i in $(seq 1 60); do
U=$(kubectl -n %{ns} top pod query --no-headers 2>/dev/null | awk '{gsub("Mi","",$3); print $3}')
[ "${U:-0}" -ge 50 ] 2>/dev/null && [ "$(kubectl -n %{ns} top pod --no-headers 2>/dev/null | wc -l)" -ge 3 ] && break
sleep 5
done
kubectl -n %{ns} top pod query --containers --no-headers | awk '{gsub("Mi","",$4); print $4, $2}' | sort -rn | head -1 | awk '{print $2}' > {WS}/q130/container.txt
for p in ingest index query; do
use=$(kubectl -n %{ns} top pod $p --no-headers | awk '{gsub("Mi","",$3); print $3}')
req=$(kubectl -n %{ns} get pod $p -o json | jq '[.spec.containers[].resources.requests.memory | sub("Mi"; "") | tonumber] | add')
[ "$use" -gt "$req" ] && echo $p
done > {WS}/q130/over.txt
echo 'kubectl -n %{ns} top pod --containers' > {WS}/q130/top.sh
""",
131: r"""
kubectl -n %{ns} logs %{pod} -c api --previous > {WS}/q131/previous.log
U=$(kubectl -n %{ns} get pod %{pod} -o jsonpath='{.metadata.uid}')
N=$(kubectl -n %{ns} get pod %{pod} -o jsonpath='{.status.containerStatuses[?(@.name=="api")].restartCount}')
NODE=$(kubectl -n %{ns} get pod %{pod} -o jsonpath='{.spec.nodeName}')
F=/var/log/pods/%{ns}_%{pod}_$U/api/$N.log
echo $F > {WS}/q131/logfile.txt
sleep 20
ssh -n $NODE "sudo grep ' stderr F ' $F" | cut -d' ' -f4- > {WS}/q131/stderr.log
echo 'kubectl -n %{ns} logs %{pod} --all-containers --prefix --timestamps' > {WS}/q131/all.sh
""",
# ---- 1-113, where the reference solution is not a script as it stands: it opens a shell on a
# ---- node, edits in vi, or leaves a value to be read off the screen. The same steps, scripted.
# ---- (The host is the control-plane node: what a solution does after "ssh <host>" is sudo here.)
2: r"""
sudo mkdir -p /opt/cka
sudo ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key snapshot save /opt/cka/etcd-snapshot.db
""",
5: r"""
ssh -n {W3} 'sudo tee /etc/kubernetes/manifests/static-web.yaml >/dev/null <<EOF
apiVersion: v1
kind: Pod
metadata: {name: static-web, namespace: default}
spec:
containers: [{name: web, image: "nginx:1.27"}]
EOF'
for i in $(seq 1 60); do [ "$(kubectl get pod static-web-{W3} -o jsonpath='{.status.phase}' 2>/dev/null)" = Running ] && break; sleep 3; done
""",
17: r"""
ssh -n {W2} 'sudo systemctl enable --now kubelet'
for i in $(seq 1 40); do [ "$(kubectl get node {W2} -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}')" = True ] && break; sleep 3; done
""",
18: r"""
kubectl -n troubleshoot set image deploy payments payments=nginx:1.27
kubectl -n troubleshoot patch svc payments -p '{"spec":{"selector":{"app":"payments"},"ports":[{"port":80,"targetPort":80}]}}'
kubectl -n troubleshoot rollout status deploy payments --timeout=240s
""",
20: CPWAIT + r"""
sudo sed -i 's/kube-schedulerr/kube-scheduler/' /etc/kubernetes/manifests/kube-scheduler.yaml
cpwait kube-scheduler
""",
23: r"""
kubectl -n kube-system patch deploy metrics-server --type=json -p '[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--kubelet-insecure-tls"}]'
kubectl -n kube-system rollout status deploy metrics-server --timeout=240s
for i in $(seq 1 40); do kubectl top nodes >/dev/null 2>&1 && break; sleep 5; done
kubectl -n %{ns} autoscale deploy %{dep} --min=2 --max=%{max} --cpu-percent=%{cpu} --name=%{dep}
""",
37: r"""
kubectl -n kube-system get cm coredns -o json | jq '.data.Corefile |= sub("forward \\. [0-9.]+"; "forward . /etc/resolv.conf")' | kubectl apply -f -
kubectl -n kube-system rollout restart deploy coredns
kubectl -n kube-system rollout status deploy coredns --timeout=240s
""",
38: CPWAIT + r"""
sudo sed -i '/--leader-elect-lease-duration=1s/d' /etc/kubernetes/manifests/kube-controller-manager.yaml
cpwait kube-controller-manager
""",
39: r"""
ssh -n {W1} "sudo sed -i 's|ca-bundle.crt|ca.crt|' /var/lib/kubelet/config.yaml; sudo systemctl restart kubelet; sudo systemctl enable kubelet"
for i in $(seq 1 40); do [ "$(kubectl get node {W1} -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}')" = True ] && break; sleep 3; done
""",
45: CPWAIT + r"""
sudo etcdutl snapshot restore /opt/cka/backup.db --data-dir /var/lib/etcd-restore
sudo sed -i 's|path: /var/lib/etcd$|path: /var/lib/etcd-restore|' /etc/kubernetes/manifests/etcd.yaml
sleep 30
for i in $(seq 1 80); do kubectl -n default get cm %{cm} >/dev/null 2>&1 && break; sleep 3; done
for c in etcd kube-apiserver kube-scheduler kube-controller-manager; do cpwait $c; done
""",
46: r"""
N=$(kubectl get nodes -l tier=edge -o jsonpath='{.items[0].metadata.name}')
D=$(ssh -n $N "sudo awk '/^staticPodPath:/ {print \$2}' /var/lib/kubelet/config.yaml")
kubectl create ns %{ns} 2>/dev/null
ssh -n $N "sudo tee $D/%{pod}.yaml >/dev/null <<EOF
apiVersion: v1
kind: Pod
metadata: {name: %{pod}, namespace: %{ns}, labels: {role: edge}}
spec:
containers:
- name: web
image: nginx:1.27
resources: {requests: {cpu: 50m, memory: 32Mi}}
EOF"
for i in $(seq 1 60); do [ "$(kubectl -n %{ns} get pod %{pod}-$N -o jsonpath='{.status.phase}' 2>/dev/null)" = Running ] && break; sleep 3; done
""",
51: r"""
for r in $(kubectl -n %{ns} rollout history deploy %{dep} | awk '/^[0-9]/{print $1}'); do
kubectl -n %{ns} rollout history deploy %{dep} --revision=$r | grep -q 'nginx:1.25' && N=$r
done
kubectl -n %{ns} rollout undo deploy %{dep} --to-revision=$N
kubectl -n %{ns} scale deploy %{dep} --replicas=3
kubectl -n %{ns} rollout status deploy %{dep} --timeout=240s
kubectl -n %{ns} rollout pause deploy %{dep}
kubectl -n %{ns} set image deploy/%{dep} $(kubectl -n %{ns} get deploy %{dep} -o jsonpath='{.spec.template.spec.containers[0].name}')=nginx:1.27
kubectl -n %{ns} set env deploy/%{dep} RELEASE=next
""",
57: r"""
kubectl -n kube-system delete netpol maintenance-lockdown
kubectl -n kube-system scale deploy coredns --replicas=2
kubectl -n kube-system get cm coredns -o json | jq '.data.Corefile |= sub("forward \\. [0-9.]+"; "forward . /etc/resolv.conf")' | kubectl apply -f -
kubectl -n kube-system rollout restart deploy coredns
kubectl -n kube-system rollout status deploy coredns --timeout=240s
""",
60: r"""
ssh -n {W2} "sudo sed -i 's|ca-wrong.crt|ca.crt|' /var/lib/kubelet/config.yaml; sudo systemctl enable --now kubelet"
kubectl uncordon {W2}
for i in $(seq 1 40); do [ "$(kubectl get node {W2} -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}')" = True ] && break; sleep 3; done
""",
61: CPWAIT + r"""
sudo sed -i 's/kube-schedulerr/kube-scheduler/' /etc/kubernetes/manifests/kube-scheduler.yaml
sudo sed -i 's|\(image: registry.k8s.io/kube-controller-manager:v[0-9.]*\)-broken|\1|' /etc/kubernetes/manifests/kube-controller-manager.yaml
cpwait kube-scheduler; cpwait kube-controller-manager
""",
65: CPWAIT + r"""
sudo kubeadm certs renew all
sudo sh -c 'mkdir -p /tmp/manifests && mv /etc/kubernetes/manifests/*.yaml /tmp/manifests/ && sleep 25 && mv /tmp/manifests/*.yaml /etc/kubernetes/manifests/'
for i in $(seq 1 80); do kubectl get nodes >/dev/null 2>&1 && break; sleep 3; done
for c in etcd kube-apiserver kube-scheduler kube-controller-manager; do cpwait $c; done
sudo kubeadm certs check-expiration 2>/dev/null | grep '^apiserver ' > {WS}/q65/expiry.txt
""",
66: CPWAIT + r"""
sudo sed -i 's/kube-apiserverr/kube-apiserver/' /etc/kubernetes/manifests/kube-apiserver.yaml
for i in $(seq 1 80); do kubectl get nodes >/dev/null 2>&1 && break; sleep 3; done
for c in etcd kube-apiserver kube-scheduler kube-controller-manager; do cpwait $c; done
""",
67: r"""
T=$(sudo kubeadm token create --ttl 2h)
H=$(sudo cat /etc/kubernetes/pki/ca.crt | openssl x509 -pubkey | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //')
E=$(kubectl get endpointslices -l kubernetes.io/service-name=kubernetes -o jsonpath='{.items[0].endpoints[0].addresses[0]}:{.items[0].ports[0].port}')
printf 'JOIN_TOKEN=%s\nCA_HASH=sha256:%s\nAPI_ENDPOINT=%s\n' "$T" "$H" "$E" > {WS}/q67/node-join.env
""",
70: r"""
kubectl -n driver-onboard set image deploy/onboard-app onboard=nginx:1.25
kubectl -n driver-onboard patch deploy onboard-app --type=json -p '[
{"op": "replace", "path": "/spec/template/spec/containers/0/livenessProbe/httpGet/port", "value": 80},
{"op": "replace", "path": "/spec/template/spec/containers/0/readinessProbe/httpGet/path", "value": "/"}]'
kubectl -n driver-onboard rollout status deploy onboard-app --timeout=240s
""",
71: r"""
kubectl -n dispatch-hub expose deploy dispatch-api --name=dispatch-service --port=80 --target-port=80
kubectl -n dispatch-hub rollout status deploy dispatch-api --timeout=240s
for i in 1 2 3 4 5; do
kubectl -n dispatch-hub run probe --image=busybox:1.36 --restart=Never -i --rm -- wget -qO- http://dispatch-service.dispatch-hub.svc:80 > {WS}/q71/test-result.txt </dev/null 2>/dev/null
grep -qi nginx {WS}/q71/test-result.txt && break; kubectl -n dispatch-hub delete pod probe --ignore-not-found >/dev/null 2>&1; sleep 3
done
kubectl -n dispatch-hub get svc dispatch-service -o jsonpath='{.spec.clusterIP}' > {WS}/q71/service-ip.txt
""",
76: r"""
kubectl -n trip-tracker get deploy trip-logger -o json | jq '
.spec.template.spec.volumes += [{"name": "config-volume", "emptyDir": {}}]
| .spec.template.spec.initContainers += [{"name": "config-init", "image": "busybox:1.36",
"command": ["sh", "-c", "echo '\''{\"region\":\"us-east\",\"mode\":\"production\"}'\'' > /config/app-config.json"],
"volumeMounts": [{"name": "config-volume", "mountPath": "/config"}]}]
| .spec.template.spec.containers[0].volumeMounts += [{"name": "config-volume", "mountPath": "/config"}]' | kubectl apply -f -
kubectl -n trip-tracker rollout status deploy trip-logger --timeout=240s
""",
79: r"""
kubectl -n surge-pricing create cm surge-config --from-literal=PRICING_MODE=dynamic --from-literal=MAX_MULTIPLIER=3.5 --from-literal=REGION=us-east-1
kubectl -n surge-pricing create secret generic surge-db-secret --from-literal=DB_HOST=pricing-db.surge-pricing.svc.cluster.local \
--from-literal=DB_USER=surge_admin --from-literal=DB_PASSWORD='S3cur3Pr1c1ng!'
kubectl -n surge-pricing get deploy surge-engine -o json | jq '
.spec.template.spec.containers[0].envFrom += [{"configMapRef": {"name": "surge-config"}}, {"secretRef": {"name": "surge-db-secret"}}]
| .spec.template.spec.containers[0].volumeMounts += [{"name": "config-vol", "mountPath": "/etc/surge-config"}]
| .spec.template.spec.volumes += [{"name": "config-vol", "configMap": {"name": "surge-config"}}]' | kubectl apply -f -
kubectl -n surge-pricing rollout status deploy surge-engine --timeout=240s
""",
81: r"""
kubectl -n fare-engine delete pvc fare-data-pvc
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata: {name: fare-data-pvc, namespace: fare-engine}
spec:
accessModes: [ReadWriteOnce]
resources: {requests: {storage: 100Mi}}
storageClassName: standard
EOF
kubectl label node {W1} disk=nvme
kubectl -n fare-engine rollout status deploy fare-calculator --timeout=240s
""",
84: r"""
kubectl -n booking-tls get cm booking-nginx-config -o json | jq '.data |= with_entries(.value |= sub("ssl_protocols TLSv1.2 TLSv1.3;"; "ssl_protocols TLSv1.3;"))' | kubectl apply -f -
kubectl -n booking-tls rollout restart deploy booking-nginx
kubectl -n booking-tls rollout status deploy booking-nginx --timeout=240s
echo 'ssl_protocols TLSv1.3;' > {WS}/q84/ssl-line.txt
""",
86: r"""
cat > {WS}/q86/controlplane.txt <<'EOF'
kubelet: process
kube-apiserver: static-pod
kube-scheduler: static-pod
kube-controller-manager: static-pod
etcd: static-pod
dns: deployment coredns
EOF
""",
87: r"""
C=$(kubectl get nodes --no-headers -l node-role.kubernetes.io/control-plane | wc -l)
W=$(kubectl get nodes --no-headers -l '!node-role.kubernetes.io/control-plane' | wc -l)
CIDR=$(kubectl -n kube-system get pod -l component=kube-apiserver -o yaml | grep -o 'service-cluster-ip-range=[^ ]*' | head -1 | cut -d= -f2)
F=$(ssh -n {W1} "sudo sh -c 'ls /etc/cni/net.d/*.conflist | sort | head -1'")
printf '1: %s\n2: %s\n3: %s\n4: kindnet, %s\n5: -{W3}\n' "$C" "$W" "$CIDR" "$F" > {WS}/q87/cluster-info.txt
""",
88: r"""
kubectl -n oslo-web run gatekeeper --image=busybox:1.36 --dry-run=client -o json -- sleep 86400 | jq '.spec.containers[0].readinessProbe = {"exec": {"command": ["sh", "-c", "wget -T2 -qO- http://ledger-api:8080"]}, "initialDelaySeconds": 5, "periodSeconds": 5}' | kubectl apply -f -
sleep 12
kubectl -n oslo-web expose deploy ledger --name=ledger-api --port=8080 --target-port=80
kubectl -n oslo-web wait --for=condition=Ready pod/gatekeeper --timeout=180s
""",
90: r"""
cat > {WS}/q90/cluster-events.sh <<'EOF'
kubectl get events -A --sort-by=.metadata.creationTimestamp
EOF
chmod +x {WS}/q90/cluster-events.sh
P=$(kubectl -n kube-system get pod -l k8s-app=kube-proxy --field-selector spec.nodeName={W2} -o jsonpath='{.items[0].metadata.name}')
kubectl -n kube-system delete pod $P
for i in $(seq 1 40); do
[ "$(kubectl -n kube-system get pod -l k8s-app=kube-proxy --field-selector spec.nodeName={W2} -o jsonpath='{.items[0].status.phase}' 2>/dev/null)" = Running ] && break; sleep 3
done
sleep 5
kubectl -n kube-system get events --sort-by=.metadata.creationTimestamp | grep kube-proxy | tail -8 > {WS}/q90/pod-deleted.log
ssh -n {W2} 'sudo crictl stop $(sudo crictl ps --name kube-proxy -q)'
sleep 25
kubectl -n kube-system get events --sort-by=.metadata.creationTimestamp | grep kube-proxy | tail -4 > {WS}/q90/container-killed.log
echo container > {WS}/q90/restarts.txt
""",
92: r"""
ssh -n {W1} "sudo sed -i 's|^ExecStart=/usr/local/bin/kubelet |ExecStart=/usr/bin/kubelet |' /etc/systemd/system/kubelet.service.d/10-kubeadm.conf; sudo systemctl daemon-reload; sudo systemctl restart kubelet"
echo /etc/systemd/system/kubelet.service.d/10-kubeadm.conf > {WS}/q92/fixed-file.txt
for i in $(seq 1 40); do [ "$(kubectl get node {W1} -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}')" = True ] && break; sleep 3; done
""",
94: r"""
kubectl run ops-beacon -n default --image=nginx:1.27 --labels=app=ops-beacon --dry-run=client -o yaml \
| yq '.spec.containers[0].resources = {"requests": {"cpu": "10m", "memory": "20Mi"}}' | sudo tee /etc/kubernetes/manifests/ops-beacon.yaml >/dev/null
for i in $(seq 1 60); do [ "$(kubectl -n default get pod ops-beacon-{CP} -o jsonpath='{.status.phase}' 2>/dev/null)" = Running ] && break; sleep 3; done
kubectl -n default expose pod ops-beacon-{CP} --name=ops-beacon-svc --type=NodePort --port=80 --target-port=80
kubectl -n default get svc ops-beacon-svc -o jsonpath='{.spec.ports[0].nodePort}' > {WS}/q94/nodeport.txt
sleep 5
""",
95: r"""
CI=$(ssh -n {W2} 'sudo openssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -noout -issuer' | sed 's/^issuer= *//')
SI=$(ssh -n {W2} 'sudo openssl x509 -in /var/lib/kubelet/pki/kubelet.crt -noout -issuer' | sed 's/^issuer= *//')
printf 'client-issuer: %s\nclient-eku: TLS Web Client Authentication\nserver-issuer: %s\nserver-eku: TLS Web Server Authentication\n' "$CI" "$SI" > {WS}/q95/kubelet-certs.txt
""",
96: r"""
kubectl -n default run cp-watch --image=httpd:2-alpine --dry-run=client -o json | jq '.spec.containers[0].name = "watch"
| .spec.nodeSelector = {"node-role.kubernetes.io/control-plane": ""}
| .spec.tolerations = [{"key": "node-role.kubernetes.io/control-plane", "operator": "Exists", "effect": "NoSchedule"}]' | kubectl apply -f -
kubectl -n default wait --for=condition=Ready pod/cp-watch --timeout=180s
""",
97: CPWAIT + r"""
sudo mv /etc/kubernetes/manifests/kube-scheduler.yaml /etc/kubernetes/
for i in $(seq 1 40); do [ -z "$(kubectl -n kube-system get pod -l component=kube-scheduler -o name 2>/dev/null)" ] && break; sleep 3; done
kubectl -n default run manual-1 --image=busybox:1.36 -- sleep 86400
sleep 5
kubectl -n default get pod manual-1 -o json | jq 'del(.metadata.resourceVersion, .metadata.uid, .metadata.creationTimestamp, .status) | .spec.nodeName = "{CP}"' | kubectl replace --force -f -
kubectl -n default wait --for=condition=Ready pod/manual-1 --timeout=180s
sudo mv /etc/kubernetes/kube-scheduler.yaml /etc/kubernetes/manifests/
cpwait kube-scheduler
kubectl -n default run manual-2 --image=busybox:1.36 -- sleep 86400
kubectl -n default wait --for=condition=Ready pod/manual-2 --timeout=180s
""",
105: r"""
kubectl -n %{ns} set resources deploy %{dep} -c web --requests=cpu=100m
cat <<EOF | kubectl apply -f -
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata: {name: %{dep}, namespace: %{ns}}
spec:
scaleTargetRef: {apiVersion: apps/v1, kind: Deployment, name: %{dep}}
minReplicas: %{min}
maxReplicas: %{max}
metrics:
- type: Resource
resource: {name: cpu, target: {type: Utilization, averageUtilization: %{cpu}}}
behavior: {scaleDown: {stabilizationWindowSeconds: %{win}, policies: [{type: Pods, value: 1, periodSeconds: 60}]}}
EOF
for i in $(seq 1 40); do [ "$(kubectl -n %{ns} get deploy %{dep} -o jsonpath='{.status.readyReplicas}')" -ge %{min} ] 2>/dev/null && break; sleep 3; done
""",
106: r"""
echo Unhealthy > {WS}/q106/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
""",
107: r"""
ssh -n {W3} "printf 'overlay\nbr_netfilter\n' | sudo tee /etc/modules-load.d/%{name}.conf >/dev/null
printf 'net.bridge.bridge-nf-call-iptables = 1\nnet.bridge.bridge-nf-call-ip6tables = 1\nnet.ipv4.ip_forward = 1\n' | sudo tee /etc/sysctl.d/%{name}.conf >/dev/null"
IPF=$(ssh -n {W3} 'sudo sysctl -n net.ipv4.ip_forward')
SW=off; [ "$(ssh -n {W3} 'sed 1d /proc/swaps | grep -c .')" -gt 0 ] && SW=on
FS=$(ssh -n {W3} "sudo awk '/^failSwapOn:/ {print \$2}' /var/lib/kubelet/config.yaml"); [ -n "$FS" ] || FS=true
CG=$(ssh -n {W3} "sudo awk '/^cgroupDriver:/ {print \$2}' /var/lib/kubelet/config.yaml")
printf 'ip_forward=%s\nswap=%s\nfail-swap-on=%s\ncgroup-driver=%s\n' "$IPF" "$SW" "$FS" "$CG" > {WS}/q107/node.txt
""",
108: r"""
V=$(ssh -n {W2} 'sudo crictl version 2>/dev/null')
RN=$(echo "$V" | awk '/^RuntimeName:/ {print $2}'); RV=$(echo "$V" | awk '/^RuntimeVersion:/ {print $2}')
SOCK=$(ssh -n {W2} "sudo awk '/^containerRuntimeEndpoint:/ {print \$2}' /var/lib/kubelet/config.yaml" | tr -d '"')
CID=$(kubectl -n %{ns} get pod %{pod} -o jsonpath='{.status.containerStatuses[0].containerID}' | sed 's#^[a-z-]*://##')
PID=$(ssh -n {W2} "sudo crictl inspect -o go-template --template '{{.info.pid}}' $CID 2>/dev/null")
LOG=$(ssh -n {W2} "sudo crictl inspect -o go-template --template '{{.status.logPath}}' $CID 2>/dev/null")
printf 'runtime=%s %s\nsocket=%s\ncontainer-id=%s\npid=%s\nlog=%s\n' "$RN" "$RV" "$SOCK" "$CID" "$PID" "$LOG" > {WS}/q108/runtime.txt
""",
110: r"""
kubectl -n kube-system get cm kubeadm-config -o jsonpath='{.data.ClusterConfiguration}' | awk '/^controlPlaneEndpoint:/{print $2}' | tr -d '"' > {WS}/q110/endpoint.txt
KEY=$(sudo kubeadm init phase upload-certs --upload-certs 2>/dev/null | tail -1)
sudo kubeadm token create --print-join-command --certificate-key $KEY 2>/dev/null | grep 'kubeadm join' > {WS}/q110/join-control-plane.sh
""",
111: r"""
E='sudo etcdctl --endpoints=https://127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key'
N=$(kubectl get nodes -l node-role.kubernetes.io/control-plane --no-headers | wc -l)
M=$($E member list | wc -l)
L=$($E member list | awk -F', ' 'NR==1 {print $3}')
S=$(kubectl -n kube-system get lease kube-scheduler -o jsonpath='{.spec.holderIdentity}' | cut -d_ -f1)
C=$(kubectl -n kube-system get lease kube-controller-manager -o jsonpath='{.spec.holderIdentity}' | cut -d_ -f1)
printf 'control-plane-nodes=%s\netcd-topology=stacked\netcd-members=%s\netcd-leader=%s\nscheduler-leader=%s\ncontroller-manager-leader=%s\n' "$N" "$M" "$L" "$S" "$C" > {WS}/q111/ha.txt
""",
112: r"""
CONF=$(ssh -n {W1} "sudo sh -c 'ls /etc/cni/net.d/*.conflist | sort | head -1'")
PL=$(ssh -n {W1} "sudo cat $CONF" | jq -r '[.plugins[].type] | join(",")')
IPAM=$(ssh -n {W1} "sudo cat $CONF" | jq -r '[.plugins[] | select(.ipam) | .ipam.type][0]')
SOCK=$(ssh -n {W1} "sudo awk '/^containerRuntimeEndpoint:/ {print \$2}' /var/lib/kubelet/config.yaml" | tr -d '"')
N=$(kubectl get csidrivers --no-headers 2>/dev/null | wc -l)
PROV=$(kubectl get sc -o json | jq -r '.items[] | select(.metadata.annotations["storageclass.kubernetes.io/is-default-class"] == "true") | .provisioner')
printf 'cni-config=%s\ncni-plugins=%s\ncni-ipam=%s\ncri-socket=%s\ncsi-drivers=%s\ndefault-provisioner=%s\n' "$CONF" "$PL" "$IPAM" "$SOCK" "$N" "$PROV" > {WS}/q112/interfaces.txt
""",
19: r"""
for i in $(seq 1 40); do kubectl -n troubleshoot logs crasher --previous 2>/dev/null | grep -q DB_HOST && break; sleep 3; done
kubectl -n troubleshoot logs crasher --previous > {WS}/q19/crasher.log
kubectl -n troubleshoot get pod crasher -o json | jq 'del(.metadata.resourceVersion, .metadata.uid, .metadata.creationTimestamp, .status)
| .spec.containers[0].env = [{"name": "DB_HOST", "value": "db.internal"}]' > /tmp/crasher.json
kubectl -n troubleshoot delete pod crasher --now
kubectl apply -f /tmp/crasher.json
kubectl -n troubleshoot wait --for=condition=Ready pod/crasher --timeout=180s
""",
41: r"""
A=$(kubectl version -o json | jq -r .serverVersion.gitVersion)
E=$(kubectl -n kube-system get pod -l component=etcd -o jsonpath='{.items[0].spec.containers[0].image}' | sed 's/.*://')
KV=$(kubectl get node %{node} -o jsonpath='{.status.nodeInfo.kubeletVersion}')
R=$(kubectl get node %{node} -o jsonpath='{.status.nodeInfo.containerRuntimeVersion}')
printf 'apiserver=%s\netcd=%s\nkubelet=%s\nruntime=%s\n' "$A" "$E" "$KV" "$R" > {WS}/q41/versions.txt
""",
59: r"""
kubectl -n %{ns} apply -f - <<EOF
apiVersion: v1
kind: PersistentVolumeClaim
metadata: {name: new-data, namespace: %{ns}}
spec: {accessModes: [ReadWriteOnce], resources: {requests: {storage: 1Gi}}}
---
apiVersion: batch/v1
kind: Job
metadata: {name: migrate, namespace: %{ns}}
spec:
template:
spec:
restartPolicy: Never
volumes:
- {name: src, persistentVolumeClaim: {claimName: old-data}}
- {name: dst, persistentVolumeClaim: {claimName: new-data}}
containers:
- name: cp
image: busybox:1.36
command: [sh, -c, "cp -a /src/. /dst/ && ls /dst"]
volumeMounts: [{name: src, mountPath: /src}, {name: dst, mountPath: /dst}]
EOF
kubectl -n %{ns} wait --for=condition=complete job/migrate --timeout=240s
kubectl -n %{ns} get pod legacy -o json | jq 'del(.metadata.resourceVersion, .metadata.uid, .metadata.creationTimestamp, .status)
| (.spec.volumes[] | select(.persistentVolumeClaim.claimName == "old-data") | .persistentVolumeClaim.claimName) = "new-data"' > /tmp/legacy.json
kubectl -n %{ns} delete pod legacy --now
kubectl apply -f /tmp/legacy.json
kubectl -n %{ns} delete pod -l job-name=migrate
kubectl -n %{ns} delete pvc old-data
kubectl -n %{ns} wait --for=condition=Ready pod/legacy --timeout=180s
""",
77: r"""
kubectl -n kube-system get ds -o name | sed 's|.*/||' | grep -i kindnet > {WS}/q77/cni.txt
kubectl -n zero-trust run srv --image=nginx:1.27 --labels=app=srv
kubectl -n zero-trust expose pod srv --port 80
kubectl -n zero-trust run cli --image=busybox:1.36 -- sleep 3600
kubectl -n zero-trust wait --for=condition=Ready pod/srv pod/cli --timeout=180s
kubectl -n zero-trust exec cli -- wget -qO- -T3 http://srv >/dev/null && echo reachable
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: default-deny-all, namespace: zero-trust}
spec:
podSelector: {}
policyTypes: [Ingress, Egress]
EOF
sleep 6
if kubectl -n zero-trust exec cli -- wget -qO- -T3 http://srv >/dev/null 2>&1; then echo no; else echo yes; fi > {WS}/q77/enforced.txt
kubectl -n zero-trust delete pod srv cli --now
kubectl -n zero-trust delete svc srv
""",
91: r"""
kubectl api-resources --namespaced=true --no-headers | awk '{print $1}' > {WS}/q91/namespaced.txt
for ns in census-north census-south census-east census-west; do echo "$ns $(kubectl -n $ns get role --no-headers | wc -l)"; done | sort -k2 -n | tail -1 > {WS}/q91/most-roles.txt
""",
102: r"""
kubectl -n oslo-files get pvc shared-files -o json | jq 'del(.metadata.resourceVersion, .metadata.uid, .metadata.creationTimestamp, .metadata.annotations, .metadata.finalizers, .status)
| .spec.accessModes = ["ReadWriteOnce"]' > /tmp/pvc.json
kubectl -n oslo-files delete pvc shared-files
kubectl apply -f /tmp/pvc.json
kubectl -n oslo-files rollout restart deploy uploader
kubectl -n oslo-files rollout status deploy uploader --timeout=240s
echo 'PVC requested ReadWriteMany, which the local-path provisioner cannot provide (RWO/RWOP only)' > {WS}/q102/reason.txt
""",
93: r"""
kubectl -n oslo-secure create secret generic bundle --from-file={WS}/q93/tls-bundle.pem
kubectl -n oslo-secure create secret generic db-login --from-literal=user=svc-ledger --from-literal=pass='%{pw}'
kubectl -n oslo-secure run vault-reader --image=busybox:1.36 --dry-run=client -o json -- sleep 86400 | jq '
.spec.volumes = [{"name": "bundle", "secret": {"secretName": "bundle"}}]
| .spec.containers[0].volumeMounts = [{"name": "bundle", "mountPath": "/etc/bundle", "readOnly": true}]
| .spec.containers[0].env = [{"name": "APP_USER", "valueFrom": {"secretKeyRef": {"name": "db-login", "key": "user"}}},
{"name": "APP_PASS", "valueFrom": {"secretKeyRef": {"name": "db-login", "key": "pass"}}}]' | kubectl apply -f -
kubectl -n oslo-secure wait --for=condition=Ready pod/vault-reader --timeout=180s
""",
# ---- 132-145: bank_course.py (the others are solved by their solution as written, below)
132: r"""
apt_get() { sudo DEBIAN_FRONTEND=noninteractive apt-get -qq -y -o Dpkg::Options::=--force-confold "$@" </dev/null >/dev/null; }
sudo sed -i 's|/v1.36/|/v1.37/|' /etc/apt/sources.list.d/kubernetes.list
apt_get update
sudo apt-mark unhold kubeadm >/dev/null && apt_get install kubeadm=1.37.1-1.1 && sudo apt-mark hold kubeadm >/dev/null
sudo kubeadm upgrade apply v1.37.1 -y </dev/null | tail -2
kubectl drain {CP} --ignore-daemonsets --delete-emptydir-data
sudo apt-mark unhold kubelet kubectl >/dev/null && apt_get install kubelet=1.37.1-1.1 kubectl=1.37.1-1.1 && sudo apt-mark hold kubelet kubectl >/dev/null
sudo systemctl daemon-reload && sudo systemctl restart kubelet
for i in $(seq 1 30); do kubectl uncordon {CP} >/dev/null 2>&1 && break; sleep 3; done
kubectl drain {W1} --ignore-daemonsets --delete-emptydir-data
ssh {W1} sudo bash <<'EOF'
export DEBIAN_FRONTEND=noninteractive
sed -i 's|/v1.36/|/v1.37/|' /etc/apt/sources.list.d/kubernetes.list
apt-get update -qq </dev/null
apt-mark unhold kubeadm kubelet kubectl >/dev/null
apt-get install -y -qq kubeadm=1.37.1-1.1 </dev/null >/dev/null
kubeadm upgrade node </dev/null | tail -1
apt-get install -y -qq kubelet=1.37.1-1.1 kubectl=1.37.1-1.1 </dev/null >/dev/null
apt-mark hold kubeadm kubelet kubectl >/dev/null
systemctl daemon-reload && systemctl restart kubelet
EOF
kubectl uncordon {W1}
""",
139: r"""
ssh {W1} sudo bash <<'EOF'
yq -i '.evictionHard."memory.available" = "%{mem}Mi" | .evictionHard."nodefs.available" = "%{fs}%" | .evictionSoft."memory.available" = "%{soft}Mi" | .evictionSoftGracePeriod."memory.available" = "1m30s"' /var/lib/kubelet/config.yaml
systemctl restart kubelet
EOF
for i in $(seq 1 30); do [ "$(kubectl get node {W1} -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}')" = True ] && break; sleep 3; done
""",
}
# ---- 1-113: the banks carried over from cka-sim.
# For these tasks the reference solution is a script as it stands, so the audit runs exactly the
# text the candidate is shown - and a solution that stops working is found the next time it runs.
AS_WRITTEN = [1, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 21, 22, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
40, 42, 43, 44, 47, 48, 49, 50, 52, 53, 54, 55, 56, 58, 62, 63, 64, 68, 69, 72, 73, 74, 75, 78, 80, 82, 83,
85, 89, 98, 99, 100, 101, 103, 104, 109, 113,
# bank_course.py: written to run as they stand
133, 134, 135, 136, 137, 138, 140, 141, 142, 143, 144, 145, 146, 147]
def _as_written():
from cka.questions import QUESTIONS
text = {q["id"]: q["solution"] for q in QUESTIONS}
# $do is the shorthand the solutions use for kubectl's dry run (export it yourself in the exam)
return {i: 'export do="--dry-run=client -o yaml"\n' + text[i] for i in AS_WRITTEN if i not in SOLVE}
SOLVE.update(_as_written())