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
740 lines
37 KiB
Python
740 lines
37 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
|
|
""",
|
|
}
|
|
|
|
# ---- 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]
|
|
|
|
|
|
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())
|