"""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, 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 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 """, # ---- 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 """, 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 """, # ---- 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 " 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 """, 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 """, 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 """, 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 """, 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 """, 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 """, 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 """, 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 """, 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 """, 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 """, 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 </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 """, 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 """, 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 """, 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 """, } # ---- 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, 21, 40, 55, 69, 75, 85, 98, 104, 109, # bank_course.py: written to run as they stand 133] 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())