cka-sim/tracks/cka/solutions.py
2026-09-06 19:06:10 -06:00

907 lines
35 KiB
Python

"""Reference solutions, keyed by task id. Rendered with the same %{param} / {WS} / {W1} substitution
as the task text. Shown only after a task has been graded (drills) or the session submitted (exams).
These are one correct way, written the way you'd type them under time pressure: imperative first,
YAML only where imperative can't express it."""
S = {}
# ================================================================= ARCH ====
S[1] = """
k -n rbac-app create sa deploy-bot
k -n rbac-app create role deploy-manager --verb=get,list,watch,create,update,patch,delete --resource=deployments.apps
k -n rbac-app create rolebinding deploy-bot-binding --role=deploy-manager --serviceaccount=rbac-app:deploy-bot
k auth can-i create deployments -n rbac-app --as=system:serviceaccount:rbac-app:deploy-bot # yes
k auth can-i delete pods -n rbac-app --as=system:serviceaccount:rbac-app:deploy-bot # no
"""
S[2] = """
{SHELL_CP}
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
etcdutl snapshot status /opt/cka/etcd-snapshot.db -w table
# If etcdctl is only inside the pod: run the same command via
# kubectl -n kube-system exec etcd-{CP} -- etcdctl ... snapshot save /var/lib/etcd/snap.db
# then on the node: mv /var/lib/etcd/snap.db /opt/cka/etcd-snapshot.db (the pod's /var/lib/etcd is the node's /var/lib/etcd)
"""
S[3] = """
mkdir -p {WS}/q03/overlays/prod && cd {WS}/q03/overlays/prod
cat > namespace.yaml <<EOF
apiVersion: v1
kind: Namespace
metadata: {name: store-prod}
EOF
cat > kustomization.yaml <<EOF
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: store-prod
resources: [../../base, namespace.yaml]
labels:
- pairs: {env: prod}
includeSelectors: false
replicas:
- name: store-api
count: 3
EOF
kubectl apply -k .
"""
S[4] = """
cat <<EOF | k apply -f -
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata: {name: backups.data.example.com}
spec:
group: data.example.com
scope: Namespaced
names: {kind: Backup, plural: backups, singular: backup, shortNames: [bk]}
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
schedule: {type: string}
target: {type: string}
EOF
cat <<EOF | k apply -f -
apiVersion: data.example.com/v1
kind: Backup
metadata: {name: nightly, namespace: default}
spec: {schedule: "0 2 * * *", target: "s3://backups/prod"}
EOF
k get bk
"""
S[5] = """
{SHELL_W3}
grep staticPodPath /var/lib/kubelet/config.yaml # /etc/kubernetes/manifests
cat > /etc/kubernetes/manifests/static-web.yaml <<EOF
apiVersion: v1
kind: Pod
metadata: {name: static-web, namespace: default}
spec:
containers: [{name: web, image: nginx:1.27}]
EOF
# kubelet picks it up within seconds; mirror pod is static-web-{W3}
"""
S[6] = """
# Part A
{SHELL_CP}
kubeadm certs check-expiration | grep '^apiserver ' > (copy the line into {WS}/q06/apiserver-expiry.txt)
# Part B
openssl genrsa -out jane.key 2048
openssl req -new -key jane.key -subj "/CN=jane" -out jane.csr
cat <<EOF | k apply -f -
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata: {name: jane}
spec:
request: $(base64 -w0 jane.csr)
signerName: kubernetes.io/kube-apiserver-client
usages: [client auth]
EOF
k certificate approve jane
k get csr jane -o jsonpath='{.status.certificate}' | base64 -d > jane.crt
k -n default create rolebinding jane-view --clusterrole=view --user=jane
"""
S[21] = """
k create ns %{ns}
helm install %{rel} {WS}/q21/chart -n %{ns} --set replicaCount=%{n}
helm upgrade %{rel} {WS}/q21/chart -n %{ns} --reuse-values --set service.type=NodePort
helm -n %{ns} history %{rel} # revision 2
"""
S[33] = """
cat <<EOF | k apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata: {name: %{cr}}
aggregationRule:
clusterRoleSelectors:
- matchLabels: {rbac.example.com/aggregate-to-%{cr}: "true"}
rules: []
EOF
k create clusterrole %{cr}-pods --verb=get,list,watch --resource=pods
k create clusterrole %{cr}-services --verb=get,list,watch --resource=services
k label clusterrole %{cr}-pods %{cr}-services rbac.example.com/aggregate-to-%{cr}=true
k create clusterrolebinding %{cr}-binding --clusterrole=%{cr} --user=%{user}
k get clusterrole %{cr} -o yaml # rules now populated by the controller
"""
S[34] = """
k explain %{kind}.spec --recursive # find the field, then:
k explain %{kind}.spec.tuning.%{field} # confirm the description
echo spec.tuning.%{field} > {WS}/q34/path.txt
cat <<EOF | k apply -f -
apiVersion: demo.io/v1
kind: %{kind}
metadata: {name: sample, namespace: default}
spec:
tuning: {%{field}: %{n}}
EOF
"""
S[40] = """
{SHELL_CP}
kubeadm token create --ttl %{ttl} --print-join-command
# copy the printed line into {WS}/q40/join.sh
kubeadm token list
"""
S[41] = """
k version # Server Version -> apiserver=
k get node %{node} -o jsonpath='{.status.nodeInfo.kubeletVersion}{"\\n"}{.status.nodeInfo.containerRuntimeVersion}{"\\n"}'
k -n kube-system get pod -l component=etcd -o jsonpath='{.items[0].spec.containers[0].image}' # tag after the colon
"""
# --------------------------------------------------------------- hard ARCH
S[101] = """
k get clusterrolebinding -o wide | grep %{sa} # find the cluster-admin binding -> delete it
k delete clusterrolebinding %{sa}-legacy-admin
k -n %{ns} create role %{sa}-role --verb=get,list,watch,patch --resource=deployments.apps,replicasets.apps
k -n %{ns} patch role %{sa}-role --type=json -p='[{"op":"add","path":"/rules/-","value":{"apiGroups":[""],"resources":["pods/exec"],"verbs":["create"]}}]'
k -n %{ns} create rolebinding %{sa}-rb --role=%{sa}-role --serviceaccount=%{ns}:%{sa}
k create clusterrole %{sa}-nodes --verb=list --resource=nodes
k create clusterrolebinding %{sa}-crb --clusterrole=%{sa}-nodes --serviceaccount=%{ns}:%{sa}
k auth can-i --list -n %{ns} --as=system:serviceaccount:%{ns}:%{sa}
"""
S[102] = """
{SHELL_CP}
etcdutl snapshot restore /opt/cka/backup.db --data-dir /var/lib/etcd-restore
# point the etcd static pod at the new dir: the hostPath volume, NOT just --data-dir
vi /etc/kubernetes/manifests/etcd.yaml # volumes: - hostPath: path: /var/lib/etcd -> /var/lib/etcd-restore
# kubelet restarts etcd; apiserver reconnects in ~30-60s
watch crictl ps # etcd + kube-apiserver come back
kubectl -n default get cm %{cm} # restored
"""
S[103] = """
k get nodes -l tier=edge # -> the node
{SHELL_W3}
grep staticPodPath /var/lib/kubelet/config.yaml # NOT /etc/kubernetes/manifests here
cat > <staticPodPath>/%{pod}.yaml <<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
"""
S[104] = """
openssl genrsa -out %{user}.key 2048
openssl req -new -key %{user}.key -subj "/CN=%{user}/O=developers" -out %{user}.csr
k apply -f - <<EOF
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata: {name: %{user}}
spec: {request: $(base64 -w0 %{user}.csr), signerName: kubernetes.io/kube-apiserver-client, usages: [client auth]}
EOF
k certificate approve %{user}
k get csr %{user} -o jsonpath='{.status.certificate}' | base64 -d > %{user}.crt
k -n %{ns} create rolebinding %{user}-edit --clusterrole=edit --user=%{user}
# self-contained kubeconfig (--embed-certs)
KC={WS}/q104/%{user}.kubeconfig
k config view --minify --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}' | base64 -d > ca.crt
SERVER=$(k config view --minify -o jsonpath='{.clusters[0].cluster.server}')
kubectl --kubeconfig $KC config set-cluster lab --server=$SERVER --certificate-authority=ca.crt --embed-certs
kubectl --kubeconfig $KC config set-credentials %{user} --client-certificate=%{user}.crt --client-key=%{user}.key --embed-certs
kubectl --kubeconfig $KC config set-context %{user}@lab --cluster=lab --user=%{user} --namespace=%{ns}
kubectl --kubeconfig $KC config use-context %{user}@lab
kubectl --kubeconfig $KC get pods
"""
S[105] = """
mkdir -p {WS}/q105/overlays/prod && cd {WS}/q105/overlays/prod
cat > replicas.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata: {name: web}
spec: {replicas: %{rep}}
EOF
cat > envfrom.yaml <<EOF
- op: add
path: /spec/template/spec/containers/0/envFrom
value: [{configMapRef: {name: web-config}}]
EOF
cat > kustomization.yaml <<EOF
namespace: %{ns}
resources: [../../base]
labels:
- pairs: {tier: prod}
includeSelectors: false
configMapGenerator:
- name: web-config
literals: [LOG_LEVEL=%{lvl}]
patches:
- path: replicas.yaml
- path: envfrom.yaml
target: {kind: Deployment, name: web}
EOF
k create ns %{ns}; k apply -k . # kustomize rewrites web-config -> web-config-<hash> in the envFrom too
"""
S[106] = """
cat <<EOF | k apply -f -
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata: {name: $(echo %{kind} | tr A-Z a-z)s.deploy.example.com}
spec:
group: deploy.example.com
scope: Namespaced
names: {kind: %{kind}, plural: $(echo %{kind} | tr A-Z a-z)s, singular: $(echo %{kind} | tr A-Z a-z)}
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
required: [replicas, image]
properties:
replicas: {type: integer, minimum: 1, maximum: 10}
environment: {type: string, enum: [prod, stage, dev], default: dev}
image: {type: string}
EOF
k apply -f - <<EOF
apiVersion: deploy.example.com/v1
kind: %{kind}
metadata: {name: good}
spec: {replicas: 3, image: nginx:1.27, environment: %{env}}
EOF
printf 'apiVersion: deploy.example.com/v1\\nkind: %{kind}\\nmetadata: {name: bad}\\nspec: {replicas: 0, image: x}\\n' | k apply -f - 2> {WS}/q106/rejected.txt
printf 'apiVersion: deploy.example.com/v1\\nkind: %{kind}\\nmetadata: {name: defaulted}\\nspec: {replicas: 2, image: nginx:1.27}\\n' | k apply -f -
"""
S[122] = """
{SHELL_CP}
kubeadm certs renew all
# static pods do NOT reload certs by themselves: bounce them
mv /etc/kubernetes/manifests/*.yaml /tmp/ && sleep 20 && mv /tmp/*.yaml /etc/kubernetes/manifests/
# admin.conf was regenerated by `renew all`; use it:
cp /etc/kubernetes/admin.conf ~/.kube/config
kubeadm certs check-expiration | grep '^apiserver ' # -> {WS}/q122/expiry.txt
"""
# ================================================================= WORK ====
S[7] = """
k -n apps set image deploy web-frontend nginx=nginx:1.27
k -n apps scale deploy web-frontend --replicas=4
k -n apps patch deploy web-frontend -p '{"spec":{"strategy":{"rollingUpdate":{"maxSurge":1,"maxUnavailable":0}}}}'
k -n apps rollout status deploy web-frontend
"""
S[8] = """
cat <<EOF | k apply -f -
apiVersion: v1
kind: Pod
metadata: {name: log-shipper, namespace: apps}
spec:
volumes: [{name: logs, emptyDir: {}}]
containers:
- name: app
image: busybox:1.36
command: [sh, -c, "while true; do date >> /var/log/app.log; sleep 2; done"]
volumeMounts: [{name: logs, mountPath: /var/log}]
- name: shipper
image: busybox:1.36
command: [sh, -c, "tail -f /var/log/app.log"]
volumeMounts: [{name: logs, mountPath: /var/log}]
EOF
"""
S[9] = """
k -n apps create deploy cache --image=redis:7-alpine --replicas=2 $do > cache.yaml
# edit: add under spec.template.spec
# affinity:
# nodeAffinity:
# requiredDuringSchedulingIgnoredDuringExecution:
# nodeSelectorTerms:
# - matchExpressions: [{key: disktype, operator: In, values: [ssd]}]
# and under the container: resources: {requests: {cpu: 100m, memory: 64Mi}, limits: {cpu: 200m, memory: 128Mi}}
k apply -f cache.yaml
"""
S[10] = """
k -n apps create cm app-config --from-literal=APP_MODE=production --from-literal=LOG_LEVEL=debug
k -n apps create secret generic db-creds --from-literal=username=admin --from-literal='password=S3cr3t!'
cat <<EOF | k apply -f -
apiVersion: v1
kind: Pod
metadata: {name: config-pod, namespace: apps}
spec:
volumes: [{name: cfg, configMap: {name: app-config}}]
containers:
- name: app
image: busybox:1.36
command: [sleep, "3600"]
env: [{name: APP_MODE, valueFrom: {configMapKeyRef: {name: app-config, key: APP_MODE}}}]
envFrom: [{secretRef: {name: db-creds}}]
volumeMounts: [{name: cfg, mountPath: /etc/config}]
EOF
"""
S[23] = """
k -n kube-system edit deploy metrics-server # args: add - --kubelet-insecure-tls
k top nodes # ~30s later
k -n %{ns} autoscale deploy %{dep} --min=2 --max=%{max} --cpu-percent=%{cpu} --name=%{dep}
"""
S[24] = """
cat <<EOF | k apply -f -
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata: {name: %{pc}}
value: %{val}
globalDefault: false
description: "%{pc} workloads"
EOF
k -n %{ns} patch deploy payments -p '{"spec":{"template":{"spec":{"priorityClassName":"%{pc}"}}}}'
k -n %{ns} rollout status deploy payments
"""
S[25] = """
k taint node {W1} %{key}=%{val}:NoSchedule
k -n %{ns} create deploy %{dep} --image=busybox:1.36 --replicas=2 $do -- sleep 3600 > d.yaml
# add under spec.template.spec:
# nodeSelector: {kubernetes.io/hostname: {W1}}
# tolerations: [{key: %{key}, operator: Equal, value: %{val}, effect: NoSchedule}]
k apply -f d.yaml
"""
S[26] = """
cat <<EOF | k apply -f -
apiVersion: apps/v1
kind: DaemonSet
metadata: {name: %{ds}, namespace: %{ns}}
spec:
selector: {matchLabels: {app: %{ds}}}
template:
metadata: {labels: {app: %{ds}}}
spec:
tolerations: [{key: node-role.kubernetes.io/control-plane, operator: Exists, effect: NoSchedule}]
containers:
- name: agent
image: busybox:1.36
command: [sleep, "3600"]
resources: {requests: {cpu: 10m, memory: 16Mi}}
EOF
"""
S[27] = """
k -n %{ns} create cj %{cj} --image=busybox:1.36 --schedule='*/%{every} * * * *' $do -- date > cj.yaml
# set: spec.concurrencyPolicy: Forbid, successfulJobsHistoryLimit: 3, failedJobsHistoryLimit: 1,
# spec.jobTemplate.spec.activeDeadlineSeconds: 30, ...template.spec.restartPolicy: Never
k apply -f cj.yaml
k -n %{ns} create job %{cj}-manual --from=cronjob/%{cj}
"""
S[28] = """
cat <<EOF | k apply -f -
apiVersion: v1
kind: Pod
metadata: {name: %{pod}, namespace: %{ns}}
spec:
volumes: [{name: content, emptyDir: {}}]
initContainers:
- name: setup
image: busybox:1.36
command: [sh, -c, "echo '%{text}' > /work/index.html"]
volumeMounts: [{name: content, mountPath: /work}]
containers:
- name: web
image: nginx:1.25
volumeMounts: [{name: content, mountPath: /usr/share/nginx/html}]
EOF
"""
S[29] = """
k -n %{ns} create cm %{cm}-v2 --from-literal=mode=new
k -n %{ns} patch cm %{cm}-v2 -p '{"immutable":true}' # optional, keeps the pattern
k -n %{ns} set volume deploy %{dep} --add=false 2>/dev/null; \\
k -n %{ns} patch deploy %{dep} --type=json -p='[{"op":"replace","path":"/spec/template/spec/volumes/0/configMap/name","value":"%{cm}-v2"}]'
k -n %{ns} rollout status deploy %{dep}
"""
S[31] = """
k -n %{ns} create job %{job} --image=busybox:1.36 $do -- sh -c "echo done; sleep 2" > job.yaml
# spec: completions: %{c}, parallelism: %{p}, backoffLimit: 2 (restartPolicy Never is the default from create job)
k apply -f job.yaml && k -n %{ns} wait --for=condition=complete job/%{job} --timeout=120s
"""
S[32] = """
k -n %{ns} create quota %{ns}-quota --hard=pods=%{pods},requests.cpu=1,limits.memory=2Gi
cat <<EOF | k apply -f -
apiVersion: v1
kind: LimitRange
metadata: {name: %{ns}-defaults, namespace: %{ns}}
spec:
limits:
- type: Container
defaultRequest: {cpu: %{dcpu}, memory: 64Mi}
default: {cpu: 500m, memory: 256Mi}
EOF
k -n %{ns} create deploy worker --image=nginx:1.25 --replicas=2
"""
# --------------------------------------------------------------- hard WORK
S[107] = """
k -n %{ns} patch quota %{ns}-quota -p '{"spec":{"hard":{"pods":"3","requests.cpu":"400m"}}}' # keep a cap, make room
cat <<EOF | k apply -f -
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata: {name: %{dep}-prio}
value: 50000
EOF
k -n %{ns} create deploy %{dep} --image=nginx:1.27 --replicas=3 $do > d.yaml
# spec.template.spec:
# priorityClassName: %{dep}-prio
# tolerations: [{key: %{key}, operator: Equal, value: %{val}, effect: NoSchedule}]
# affinity:
# podAntiAffinity:
# requiredDuringSchedulingIgnoredDuringExecution:
# - labelSelector: {matchLabels: {app: %{dep}}}
# topologyKey: kubernetes.io/hostname
# container resources: {requests: {cpu: 100m, memory: 64Mi}}
k apply -f d.yaml
"""
S[108] = """
k -n %{ns} rollout history deploy %{dep} # find the revision whose image is nginx:1.25
k -n %{ns} rollout history deploy %{dep} --revision=1 # confirm
k -n %{ns} rollout undo deploy %{dep} --to-revision=1
k -n %{ns} scale deploy %{dep} --replicas=3 && k -n %{ns} rollout status deploy %{dep}
k -n %{ns} rollout pause deploy %{dep}
k -n %{ns} set image deploy %{dep} nginx=nginx:1.27
k -n %{ns} set env deploy %{dep} RELEASE=next
k -n %{ns} get rs # no new RS while paused
"""
S[109] = """
cat <<EOF | k apply -f -
apiVersion: v1
kind: Pod
metadata: {name: %{pod}, namespace: %{ns}}
spec:
terminationGracePeriodSeconds: 5
volumes: [{name: logs, emptyDir: {}}]
initContainers:
- name: agent
image: busybox:1.36
restartPolicy: Always # <- native sidecar
command: [sh, -c, "while true; do date >> /var/log/agent.log; sleep 1; done"]
volumeMounts: [{name: logs, mountPath: /var/log}]
containers:
- name: app
image: nginx:1.27
volumeMounts: [{name: logs, mountPath: /var/log}]
readinessProbe: {httpGet: {path: /, port: 80}, periodSeconds: 5}
livenessProbe: {tcpSocket: {port: 80}}
EOF
k -n %{ns} get pod %{pod} -o jsonpath='{.spec.initContainers[*].name} {.spec.containers[*].name}' | tr ' ' '\\n' | sort > {WS}/q109/containers.txt
k -n %{ns} get pod %{pod} -o jsonpath='{.status.podIP}' > {WS}/q109/ip.txt
"""
S[110] = """
k get pods -A -o json | jq -r '.items[] | select(.metadata.namespace!="kube-system") | select(.spec.containers[0].readinessProbe==null) | "\\(.metadata.namespace)/\\(.metadata.name)"' | sort > {WS}/q110/no-readiness.txt
k get pods -A --field-selector=status.phase=Running -o jsonpath='{range .items[*]}{.spec.nodeName}{"\\n"}{end}' | sort | uniq -c | awk '{print $2, $1}' > {WS}/q110/per-node.txt
k -n kube-system get pods -o jsonpath='{.items[*].spec.containers[*].image}' | tr ' ' '\\n' | sort -u > {WS}/q110/images.txt
# no jq? use: k get pods -A -o custom-columns=NS:.metadata.namespace,NAME:.metadata.name,RP:.spec.containers[0].readinessProbe --no-headers | awk '$3=="<none>" && $1!="kube-system"{print $1"/"$2}' | sort
"""
S[121] = """
k config use-context k8s-c2-AC
k config view --minify -o jsonpath='{..namespace}' # -> %{ns}, write it to {WS}/q121/ns.txt
k create deploy %{dep} --image=busybox:1.36 --replicas=2 -- sleep 3600
k label deploy %{dep} owner=ac && k patch deploy %{dep} -p '{"spec":{"template":{"metadata":{"labels":{"owner":"ac"}}}}}'
k expose deploy %{dep} --port=9000 --target-port=9000 --name=%{dep}
"""
# ================================================================== NET ====
S[11] = """
cat <<EOF | k apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: backend-policy, namespace: netpol}
spec:
podSelector: {matchLabels: {app: backend}}
policyTypes: [Ingress]
ingress:
- from: [{podSelector: {matchLabels: {app: frontend}}}]
ports: [{protocol: TCP, port: 8080}]
EOF
"""
S[12] = """
k -n svc expose deploy shop --name=shop-svc --type=NodePort --port=80 --target-port=80 $do > svc.yaml
# set spec.ports[0].nodePort: 30080
k apply -f svc.yaml && k -n svc get endpointslices
"""
S[13] = """
k -n ingress-app create ingress app-ingress --class=nginx \\
--rule="shop.example.com/api*=api:8080" --rule="shop.example.com/*=web:80"
# `*` suffix -> pathType Prefix. Verify: k -n ingress-app get ingress app-ingress -o yaml
"""
S[14] = """
k -n dns-check run dns-test --image=busybox:1.36 -- sleep 3600
k -n dns-check exec dns-test -- nslookup hello.dns-check.svc.cluster.local | tee {WS}/q14/nslookup.txt
echo hello.dns-check.svc.cluster.local > {WS}/q14/svc-fqdn.txt
"""
S[22] = """
k apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.1/standard-install.yaml
cat <<EOF | k apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata: {name: %{gc}}
spec: {controllerName: example.com/gateway}
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata: {name: %{gw}, namespace: %{ns}}
spec:
gatewayClassName: %{gc}
listeners: [{name: http, protocol: HTTP, port: 80, hostname: %{host}.example.com}]
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata: {name: %{host}-route, namespace: %{ns}}
spec:
parentRefs: [{name: %{gw}}]
rules:
- matches: [{path: {type: PathPrefix, value: /api}}]
backendRefs: [{name: api, port: 8080}]
- backendRefs: [{name: web, port: 80}]
EOF
"""
S[30] = """
k -n %{ns} create deploy %{dep} --image=nginx:1.25 $do > d.yaml
# container: ports: [{name: %{pname}, containerPort: 80}]
# readinessProbe: {httpGet: {path: /, port: %{pname}}}
k apply -f d.yaml
k -n %{ns} expose deploy %{dep} --port=8080 --target-port=%{pname}
"""
S[42] = """
cat <<EOF | k apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: %{app}-egress, namespace: %{ns}}
spec:
podSelector: {matchLabels: {app: %{app}}}
policyTypes: [Egress]
egress:
- to:
- namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: kube-system}}
podSelector: {matchLabels: {k8s-app: kube-dns}}
ports: [{protocol: UDP, port: 53}, {protocol: TCP, port: 53}]
- to: [{ipBlock: {cidr: %{cidr}}}]
ports: [{protocol: TCP, port: 443}]
EOF
"""
S[43] = """
k -n %{ns} create svc clusterip %{sts} --clusterip=None --tcp=5432:5432 $do > h.yaml # fix selector: app: %{sts}
k apply -f h.yaml
k -n %{ns} create svc externalname legacy --external-name=%{ext}
k -n %{ns} exec %{sts}-0 -- nslookup %{sts}-0.%{sts} > {WS}/q43/pod0.txt
"""
# --------------------------------------------------------------- hard NET
S[111] = """
k -n %{ns} delete netpol temp-allow-all
k label ns %{from} team=%{from}
cat <<EOF | k apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: default-deny, namespace: %{ns}}
spec: {podSelector: {}, policyTypes: [Ingress, Egress]}
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: allow-dns, namespace: %{ns}}
spec:
podSelector: {}
policyTypes: [Egress]
egress:
- to: [{namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: kube-system}}}]
ports: [{protocol: UDP, port: 53}, {protocol: TCP, port: 53}]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: api-ingress, namespace: %{ns}}
spec:
podSelector: {matchLabels: {app: api}}
policyTypes: [Ingress]
ingress:
- from: [{namespaceSelector: {matchLabels: {team: %{from}}}}]
ports: [{port: 8080}]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: api-to-db, namespace: %{ns}}
spec:
podSelector: {matchLabels: {app: api}}
policyTypes: [Egress]
egress:
- to: [{podSelector: {matchLabels: {app: db}}}]
ports: [{port: 5432}]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: db-ingress, namespace: %{ns}}
spec:
podSelector: {matchLabels: {app: db}}
policyTypes: [Ingress]
ingress:
- from: [{podSelector: {matchLabels: {app: api}}}]
ports: [{port: 5432}]
EOF
"""
S[112] = """
openssl req -x509 -newkey rsa:2048 -nodes -days 365 -subj "/CN=%{host}" -keyout tls.key -out tls.crt
k -n %{ns} create secret tls %{host}-tls --cert=tls.crt --key=tls.key
k -n %{ns} create ingress web --class=nginx --rule="%{host}/*=web:80,tls=%{host}-tls" \\
--annotation nginx.ingress.kubernetes.io/ssl-redirect=true
openssl x509 -in tls.crt -noout -fingerprint -sha256 | cut -d= -f2 > {WS}/q112/fp.txt
"""
S[113] = """
cat <<EOF | k apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata: {name: %{host}-gc}
spec: {controllerName: example.com/gw}
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata: {name: %{host}-gw, namespace: %{ns}}
spec:
gatewayClassName: %{host}-gc
listeners:
- {name: web, protocol: HTTP, port: 80, hostname: %{host}.example.com}
- {name: api, protocol: HTTP, port: 80, hostname: api.%{host}.example.com}
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata: {name: %{host}-route, namespace: %{ns}}
spec:
parentRefs: [{name: %{host}-gw, sectionName: web}]
rules:
- matches: [{headers: [{name: X-Canary, value: always}]}]
backendRefs: [{name: v2, port: 80}]
- backendRefs:
- {name: v1, port: 80, weight: %{w2}}
- {name: v2, port: 80, weight: %{w}}
EOF
"""
S[114] = """
k -n kube-system get netpol && k -n kube-system delete netpol maintenance-lockdown
k -n kube-system get deploy coredns # 0/0 -> scale it
k -n kube-system scale deploy coredns --replicas=2
k -n kube-system edit cm coredns # forward . 10.255.255.254 -> forward . /etc/resolv.conf
k -n kube-system rollout restart deploy coredns
k run t --image=busybox:1.36 --rm -it --restart=Never -- nslookup kubernetes.default
"""
# ================================================================= STOR ====
S[15] = """
cat <<EOF | k apply -f -
apiVersion: v1
kind: PersistentVolume
metadata: {name: pv-data}
spec:
capacity: {storage: 1Gi}
accessModes: [ReadWriteOnce]
persistentVolumeReclaimPolicy: Retain
storageClassName: manual
hostPath: {path: /mnt/data}
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata: {name: pvc-data, namespace: storage}
spec: {accessModes: [ReadWriteOnce], storageClassName: manual, resources: {requests: {storage: 500Mi}}}
---
apiVersion: v1
kind: Pod
metadata: {name: data-pod, namespace: storage}
spec:
volumes: [{name: d, persistentVolumeClaim: {claimName: pvc-data}}]
containers: [{name: web, image: nginx:1.25, volumeMounts: [{name: d, mountPath: /usr/share/data}]}]
EOF
"""
S[16] = """
cat <<EOF | k apply -f -
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-local
annotations: {storageclass.kubernetes.io/is-default-class: "true"}
provisioner: rancher.io/local-path
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Delete
EOF
k annotate sc standard storageclass.kubernetes.io/is-default-class-
cat <<EOF | k apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata: {name: fast-claim, namespace: storage}
spec: {accessModes: [ReadWriteOnce], resources: {requests: {storage: 200Mi}}}
---
apiVersion: v1
kind: Pod
metadata: {name: fast-pod, namespace: storage}
spec:
volumes: [{name: d, persistentVolumeClaim: {claimName: fast-claim}}]
containers: [{name: web, image: nginx:1.25, volumeMounts: [{name: d, mountPath: /data}]}]
EOF
"""
S[35] = """
k get pv %{pv} -o yaml | grep -A5 claimRef # points at the deleted PVC -> remove it
k patch pv %{pv} --type=json -p='[{"op":"remove","path":"/spec/claimRef"}]'
k get pv %{pv} # Available
cat <<EOF | k apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata: {name: %{pvc}, namespace: %{ns}}
spec: {accessModes: [ReadWriteOnce], storageClassName: manual, volumeName: %{pv}, resources: {requests: {storage: 500Mi}}}
EOF
"""
S[36] = """
cat <<EOF | k apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata: {name: %{pvc}, namespace: %{ns}}
spec: {accessModes: [ReadWriteOnce], resources: {requests: {storage: 200Mi}}}
---
apiVersion: v1
kind: Pod
metadata: {name: writer, namespace: %{ns}}
spec:
restartPolicy: Never
volumes: [{name: d, persistentVolumeClaim: {claimName: %{pvc}}}]
containers: [{name: w, image: busybox:1.36, command: [sh, -c, "echo %{text} > /data/marker.txt"], volumeMounts: [{name: d, mountPath: /data}]}]
EOF
k -n %{ns} wait --for=jsonpath='{.status.phase}'=Succeeded pod/writer
cat <<EOF | k apply -f -
apiVersion: v1
kind: Pod
metadata: {name: reader, namespace: %{ns}}
spec:
volumes: [{name: d, persistentVolumeClaim: {claimName: %{pvc}}}]
containers: [{name: r, image: busybox:1.36, command: [sleep, "3600"], volumeMounts: [{name: d, mountPath: /data}]}]
EOF
"""
# --------------------------------------------------------------- hard STOR
S[115] = """
k get pv -l media --show-labels; k get node -l disk=nvme # lv-nvme is on {W2}
cat <<EOF | k apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata: {name: %{pvc}, namespace: %{ns}}
spec:
accessModes: [ReadWriteOnce]
storageClassName: local-fast
selector: {matchLabels: {media: nvme}}
resources: {requests: {storage: 1Gi}}
---
apiVersion: v1
kind: Pod
metadata: {name: consumer, namespace: %{ns}}
spec:
volumes: [{name: d, persistentVolumeClaim: {claimName: %{pvc}}}]
containers: [{name: c, image: busybox:1.36, command: [sleep, "3600"], volumeMounts: [{name: d, mountPath: /data}]}]
EOF
# the PV's nodeAffinity pulls the pod to {W2} - no nodeSelector needed
"""
S[116] = """
k -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
# RWO: the Job must land on the same node as `legacy` if the PV is node-bound; local-path is, so if it Pending -> add nodeName
k -n %{ns} wait --for=condition=complete job/migrate --timeout=120s
k -n %{ns} get pod legacy -o yaml > legacy.yaml # change claimName old-data -> new-data, strip status/uid/resourceVersion
k -n %{ns} delete pod legacy --now && k apply -f legacy.yaml
k get pv $(k -n %{ns} get pvc old-data -o jsonpath='{.spec.volumeName}') -o jsonpath='{.spec.persistentVolumeReclaimPolicy}' # Retain
k -n %{ns} delete pvc old-data
"""
# =============================================================== TSHOOT ====
S[17] = """
k get node {W2} # NotReady
{SHELL_W2}
systemctl status kubelet # inactive (dead), disabled
systemctl enable --now kubelet
journalctl -u kubelet -f # until "Successfully registered node"
"""
S[18] = """
k -n troubleshoot describe deploy payments | grep -i image # nginx:1.99-nope -> ErrImagePull
k -n troubleshoot set image deploy payments payments=nginx:1.27
k -n troubleshoot get svc payments -o yaml # selector app: payment (typo), targetPort 8080 (container is 80)
k -n troubleshoot patch svc payments -p '{"spec":{"selector":{"app":"payments"},"ports":[{"port":80,"targetPort":80}]}}'
k -n troubleshoot get endpointslices
"""
S[19] = """
k -n troubleshoot logs crasher --previous | tee {WS}/q19/crasher.log # FATAL: ... DB_HOST is not set
k -n troubleshoot get pod crasher -o yaml > crasher.yaml
# add under the container: env: [{name: DB_HOST, value: db.internal}] (strip status/uid/resourceVersion)
k -n troubleshoot delete pod crasher --now && k apply -f crasher.yaml
"""
S[20] = """
k -n kube-system get pods # kube-scheduler-... CrashLoopBackOff / Error
k -n kube-system logs kube-scheduler-{CP} # exec: "kube-schedulerr": not found
{SHELL_CP}
vi /etc/kubernetes/manifests/kube-scheduler.yaml # command: - kube-schedulerr -> - kube-scheduler
# kubelet restarts it; k -n kube-system get pods -w
"""
S[37] = """
k -n kube-system get pods -l k8s-app=kube-dns; k -n kube-system logs -l k8s-app=kube-dns --tail=20
k -n kube-system get cm coredns -o yaml # forward . 10.255.255.1 -> unreachable upstream
k -n kube-system edit cm coredns # forward . /etc/resolv.conf
k -n kube-system rollout restart deploy coredns
k run t --image=busybox:1.36 --rm -it --restart=Never -- nslookup kubernetes.default
"""
S[38] = """
k -n kube-system get pods # kube-controller-manager Error/CrashLoop
k -n kube-system logs kube-controller-manager-{CP} # flag error: lease duration ... invalid
{SHELL_CP}
vi /etc/kubernetes/manifests/kube-controller-manager.yaml # remove the bogus --leader-elect-lease-duration=1s line
k create deploy probe --image=busybox:1.36 -- sleep 60 && k get rs
"""
S[39] = """
{SHELL_W1}
systemctl status kubelet; journalctl -u kubelet -n 30 --no-pager # "open /etc/kubernetes/pki/ca-bundle.crt: no such file"
grep -n clientCAFile /var/lib/kubelet/config.yaml # -> /etc/kubernetes/pki/ca.crt
systemctl restart kubelet && systemctl enable kubelet
"""
# -------------------------------------------------------------- hard TSHOOT
S[117] = """
k get node {W2} # NotReady,SchedulingDisabled
{SHELL_W2}
systemctl status kubelet # disabled + dead
journalctl -u kubelet -n 30 --no-pager # after enable: "ca-wrong.crt: no such file"
sed -i 's|ca-wrong.crt|ca.crt|' /var/lib/kubelet/config.yaml
systemctl enable --now kubelet
exit
k uncordon {W2} # the third thing
k run t --image=busybox:1.36 --restart=Never --overrides='{"spec":{"nodeName":"{W2}"}}' -- sleep 60
"""
S[118] = """
k -n kube-system get pods # scheduler CrashLoop, controller-manager ImagePullBackOff
{SHELL_CP}
vi /etc/kubernetes/manifests/kube-scheduler.yaml # kube-schedulerr -> kube-scheduler
vi /etc/kubernetes/manifests/kube-controller-manager.yaml # image tag ...-broken -> remove suffix (match the others: crictl images)
k -n kube-system get pods -w
k create deploy probe --image=busybox:1.36 -- sleep 60 && k get deploy probe -w
"""
S[119] = """
k -n %{ns} get all; k -n %{ns} describe deploy %{dep}; k -n %{ns} describe rs -l app=%{dep} # quota: pods 1
k -n %{ns} patch quota tight -p '{"spec":{"hard":{"pods":"3"}}}'
k -n %{ns} set image deploy %{dep} web=nginx:1.27 # image tag typo
k -n %{ns} patch secret %{dep}-env -p '{"stringData":{"DATABASE_URL":"postgres://db"}}' # missing key referenced by env
k -n %{ns} rollout restart deploy %{dep} # picks up the secret key
k -n %{ns} patch svc %{dep} -p '{"spec":{"selector":{"app":"%{dep}"},"ports":[{"port":80,"targetPort":80}]}}'
"""
S[120] = """
k -n kube-system get pod -l component=kube-controller-manager -o yaml | grep cluster-cidr
k -n kube-system get pod -l component=kube-apiserver -o yaml | grep service-cluster-ip-range
k -n kube-system get svc kube-dns -o jsonpath='{.spec.clusterIP}'
k -n kube-system get cm kube-proxy -o yaml | grep '^ *mode:' # "" -> iptables
k get node %{node} -o jsonpath='{.spec.podCIDR}'
"""