Anurag's Notes

Mock Exams

← 15 Other Topics · Index

Contents


16.1 Mock Exam 1 Step by Step Solutions

Source: KodeKloud notes

12 questions, all run from controlplane. Each question shows a weight (Q1 = 8) and the grader's checks (Q1: "Is the pod environment variable correctly set?", "Does the sidecar display logs?").

QTaskResource / tool
13-container pod sharing an emptyDirPod, Downward API env
2Install cri-dockerd on node01dpkg, systemctl
3Save VPA CRD names to a filekubectl get crd
4ClusterIP service for a podkubectl expose
5Deployment, 2 replicaskubectl create deployment
6Fix init container typokubectl replace --force
7NodePort service on 30082Service YAML
8hostPath PV, 100Mi, RWXPersistentVolume
9CPU HPA with scale-down stabilisationHPA autoscaling/v2
10VPA in Auto modeVerticalPodAutoscaler
11Gateway with HTTP listenerGateway API
12Helm repo update + upgrade to chart 18.1.5helm upgrade --version

Q1 — Pod mc-pod: 3 containers + shared non-persistent volume

Task: mc-pod-1 = nginx:1-alpine with env NODE_NAME = node name; mc-pod-2 = busybox:1 appends date to /var/log/shared/date.log every second; mc-pod-3 = busybox:1 prints that file to stdout. Shared emptyDir at /var/log/shared. Background: §5.7 (env), §5.13/§5.14 (multi-container), §8.6 (volumes).

apiVersion: v1
kind: Pod
metadata:
  name: mc-pod
  namespace: mc                  # use the namespace the task names (the task screenshot shows mc-namespace)
spec:
  volumes:
  - name: shared-volume
    emptyDir: {}                 # non-persistent; lives as long as the pod
  containers:
  - name: mc-pod-1
    image: nginx:1-alpine
    env:
    - name: NODE_NAME
      valueFrom:
        fieldRef:
          fieldPath: spec.nodeName   # Downward API
  - name: mc-pod-2
    image: busybox:1
    command: ["sh", "-c", "while true; do date >> /var/log/shared/date.log; sleep 1; done"]
    volumeMounts:
    - name: shared-volume
      mountPath: /var/log/shared
  - name: mc-pod-3
    image: busybox:1
    command: ["sh", "-c", "until [ -f /var/log/shared/date.log ]; do sleep 1; done; tail -f /var/log/shared/date.log"]
    volumeMounts:
    - name: shared-volume
      mountPath: /var/log/shared
  restartPolicy: Always
kubectl apply -f question1.yaml
kubectl logs mc-pod -n mc -c mc-pod-3 -f      # timestamps written by mc-pod-2 appear continuously
kubectl exec mc-pod -n mc -c mc-pod-1 -- env | grep NODE_NAME   # Beyond the course: check the env var
  • emptyDir is for sharing between containers of one pod and is gone when the pod goes. It survives container restarts within the same Pod, but is removed with the Pod; use suitable persistent storage for Pod replacement/node failure (§8.7).

[!WARNING] The course notes use namespace mc, but the task screenshot says mc-namespace. Always copy names from the task text. Create the namespace first if it does not exist.

Q2 — Install cri-dockerd on node01

Task: SSH to node01 as bob, become root, install the cri-dockerd .deb in /root, then start and enable the service.

ssh bob@node01
sudo su -                                   # become root
cd /root && ls -l                           # find the package (the file name varies)
dpkg -i ./cri-dockerd_0.3.16.3-0.debian.deb # output: package configured, unit symlinks created
systemctl start cri-docker.service
systemctl enable cri-docker.service
systemctl status cri-docker.service                # expect: Active: active (running)
systemctl is-enabled cri-docker.service            # expect: enabled

[!NOTE] Beyond the course: systemctl enable --now cri-docker.service starts and enables in one command. Background on cri-dockerd (the Docker shim that lives outside Kubernetes): §2.3/§2.4.

Q3 — Save the VPA CRD names to /root/vpa-crds.txt

kubectl get crd | grep -i vertical | awk '{print $1}' > /root/vpa-crds.txt
cat /root/vpa-crds.txt
# verticalpodautoscalercheckpoints.autoscaling.k8s.io
# verticalpodautoscalers.autoscaling.k8s.io
  • awk '{print $1}' keeps only the NAME column, so no CREATED AT column ends up in the file.

Q4 — ClusterIP service messaging-service for pod messaging on 6379 (internal only)

kubectl expose pod messaging --port=6379 --name=messaging-service   # type defaults to ClusterIP
kubectl get svc messaging-service
kubectl describe svc messaging-service      # Endpoints must show <messaging pod IP>:6379
  • expose builds the service selector from the pod's labels. Selector and labels must match, otherwise the service has no endpoints.

[!CAUTION] Correction: the course notes say a pod with no labels gives a service with no selector. In fact kubectl expose fails with "the pod has no labels and cannot be exposed". Label the pod first (kubectl label pod messaging app=messaging) or pass --selector. Pods made with kubectl run already have the run=<name> label.

Q5 — Deployment hr-web-app, image kodekloud/webapp-color, 2 replicas

kubectl create deployment hr-web-app --image=kodekloud/webapp-color --replicas=2
kubectl get deployment hr-web-app
kubectl get pods -l app=hr-web-app          # create deployment sets the label app=<name>
kubectl get deployment hr-web-app -o yaml > hr-web-app.yaml   # optional: keep a manifest

Q6 — Fix pod orange (Init:CrashLoopBackOff)

Symptom: init container init-myservice exits with code 127. Its logs show sh: sleeeep: not found (a typo; 127 means command not found).

kubectl logs orange -c init-myservice       # shows the error
kubectl get pod orange -o yaml > question6.yaml
# edit: initContainers[init-myservice].command: sh -c "sleeeep 2;"  ->  "sleep 2;"
kubectl replace -f question6.yaml --force   # delete + recreate
kubectl get pod orange                      # Init completes, then Running
initContainers:
- name: init-myservice
  image: busybox
  command: ["sh", "-c", "sleep 2;"]
  • Use --force replace because a bare pod has no controller to recreate it, and the pod's command cannot be edited in place.

[!TIP] kubectl edit pod orange rejects the change but saves a copy in /tmp/kubectl-edit-*.yaml. Run kubectl replace --force -f /tmp/kubectl-edit-….yaml.

Q7 — NodePort service hr-web-app-service on 30082

Spec: type NodePort, port 8080, nodePort 30082, selector app: hr-web-app.

apiVersion: v1
kind: Service
metadata:
  name: hr-web-app-service
  labels:
    app: hr-web-app
spec:
  type: NodePort
  selector:
    app: hr-web-app          # must match the deployment's pod labels
  ports:
  - port: 8080
    targetPort: 8080         # webapp-color listens on 8080
    nodePort: 30082
    protocol: TCP
kubectl apply -f question7.yaml
kubectl describe svc hr-web-app-service     # NodePort: 30082/TCP, Endpoints = the 2 pod IPs

[!WARNING]

  • nodePort must be in the cluster's NodePort range (default 30000-32767) and not already used by another service.
  • Beyond the course: kubectl create service nodeport hr-web-app-service --tcp=8080:8080 --node-port=30082 sets the selector app=hr-web-app-service, which is wrong here. Use kubectl expose deployment hr-web-app --name=hr-web-app-service --type=NodePort --port=8080 --dry-run=client -o yaml, add nodePort: 30082, then apply.

Q8 — PersistentVolume pv-analytics

Spec: 100Mi, ReadWriteMany, hostPath /pv/data-analytics, volumeMode Filesystem. Background: §8.7.

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-analytics
spec:
  capacity:
    storage: 100Mi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteMany
  hostPath:
    path: /pv/data-analytics
kubectl apply -f question8.yaml
kubectl get pv pv-analytics                 # STATUS Available until a PVC binds to it
  • hostPath is local to one node, so RWX on hostPath does not give real shared access across nodes. Use distributed storage for that.

Q9 — HPA webapp-hpa: CPU 50%, min 2, max 10, scale-down stabilisation 300s

Target: Deployment kk-app-deploy in default. The behavior block needs autoscaling/v2. Background: §5.17.

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: webapp-hpa
  namespace: default
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: kk-app-deploy
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 50
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300
kubectl apply -f webapp-hpa.yaml
kubectl get hpa webapp-hpa
kubectl describe hpa webapp-hpa
  • The HPA needs a metrics provider (metrics-server). Without one, the targets show <unknown>.

[!NOTE] Beyond the course: kubectl autoscale deployment kk-app-deploy --name=webapp-hpa --min=2 --max=10 --cpu-percent=50, then kubectl edit hpa webapp-hpa to add behavior. autoscale has no flag for behavior.

Q10 — VPA analytics-vpa, Auto mode

Target: Deployment analytics-deployment in default. Background: §5.19.

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: analytics-vpa
  namespace: default
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: analytics-deployment
  updatePolicy:
    updateMode: "Auto"        # VPA may evict and recreate pods to apply new requests
kubectl apply -f question10.yaml
kubectl get vpa
kubectl describe vpa analytics-vpa
  • What actually happens depends on the VPA admission controller and updater being installed. Auto can restart pods.

Q11 — Gateway web-gateway in nginx-gateway

Spec: gatewayClassName: nginx, one listener http, HTTP, port 80. Background: §9.21.

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: web-gateway
  namespace: nginx-gateway
spec:
  gatewayClassName: nginx
  listeners:
  - name: http
    protocol: HTTP
    port: 80
kubectl apply -f question11.yaml
kubectl get gateway -n nginx-gateway
kubectl describe gateway web-gateway -n nginx-gateway   # status gets filled in once a controller programs it

Q12 — Helm: update repo, upgrade release kk-mock1 (ns kk-ns) from chart 18.1.0 to 18.1.5

Background: §13.6–§13.8.

helm list -n kk-ns                               # find the release and its current chart version
helm repo update
helm search repo kk-mock1 --versions             # optional: list available versions
helm search repo kk-mock1/nginx --versions       # when you know <repo>/<chart>
helm upgrade kk-mock1 kk-mock1/nginx --version 18.1.5 -n kk-ns
helm list -n kk-ns                               # CHART shows nginx-18.1.5
helm status kk-mock1 -n kk-ns

[!WARNING] Exam traps / remember:

  • Pass -n <ns> on namespace-scoped Helm release commands (list, get, upgrade, uninstall). helm repo / helm search repo operate on client repositories, not a release namespace.
  • --version is the chart version. It is not the app version.
  • Use -c <container> for logs in multi-container pods. Use --force replace for pod fields you cannot change in place.

[!CAUTION] Correction: the sidecar examples wait for the shared log file before tail -f; the course version races the writer and can exit if the file does not yet exist. emptyDir survives container restarts, not Pod replacement. See emptyDir. The upstream cri-dockerd package installs cri-docker.service / cri-docker.socket, despite the executable being named cri-dockerd. Inspect dpkg -L <installed-package-name> or systemctl list-unit-files "cri-docker*"; use the units supplied by the lab package if they differ. See upstream service.

[!NOTE] Beyond the course: CPU/memory Utilization HPA targets depend on container resource requests as well as metrics-server. Verify kubectl describe hpa and target-Pod requests if the metric is unknown.

16.2 Mock Exam 2 Step by Step Solutions

Source: KodeKloud notes

QTaskPrimary resource(s)Key command
1Default local StorageClassStorageClasskubectl apply -f question1.yaml
2Deployment with sidecar log agentDeployment, Namespacekubectl apply -f question2.yaml
3Ingress to a serviceIngress, Namespacekubectl apply -f question3.yaml
4Rolling image updateDeploymentkubectl set image deployment/nginx-deploy nginx=nginx:1.17
5User john via CSR + RoleCSR, Role, RoleBindingkubectl certificate approve john-developer
6Pod + ClusterIP + DNS lookupsPod, Servicekubectl run nginx-resolver --image=nginx --restart=Never
7Static pod on node01Static pod filemanifest in /etc/kubernetes/manifests/ on the node
8Memory-based HPAHPAkubectl apply -f webapp-hpa.yaml
9kubectl fails (kubelet inactive)systemd / kubeletsudo systemctl start kubelet
10Gateway to HTTPS/TLSGateway APIkubectl apply -f question10.yaml
11Uninstall the Helm release that uses a vulnerable imageHelmhelm get manifest <rel> -n <ns> | grep -i "kodekloud/webapp-color:v1"
12Pick the most restrictive NetworkPolicyNetworkPolicykubectl apply -f net-pol-3.yaml
13Deployment blocked by ResourceQuotaDeployment, ResourceQuotakubectl edit deployment backend-api
14Install Calico, pod CIDR 172.17.0.0/16Calico Installation/IPPoolkubectl apply -f custom-resources.yaml

Q1 — Default StorageClass local-sc (on cluster1-controlplane)

Spec: no-provisioner (static/local), default class, allowVolumeExpansion: true, WaitForFirstConsumer. Background: §8.10.

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: local-sc
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"   # this annotation makes it the default class
provisioner: kubernetes.io/no-provisioner                 # manual/static provisioning
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
parameters: {}
kubectl apply -f question1.yaml     # storageclass.storage.k8s.io/local-sc created
kubectl get sc                      # Beyond the course: local-sc shows "(default)"

[!WARNING] The annotation value must be the string "true". Beyond the course: if another class is already marked default, remove its annotation (or set it to "false") so only one default remains.

Q2 — Deployment logging-deployment (ns logging-ns) with a sidecar log agent

Spec: 1 replica. app-container (busybox:1.28) writes to /var/log/app/app.log. Sidecar log-agent tails the file. Both share an emptyDir.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: logging-deployment
  namespace: logging-ns
spec:
  replicas: 1
  selector:
    matchLabels:
      app: app-logger
  template:
    metadata:
      labels:
        app: app-logger
    spec:
      volumes:
        - name: log-volume
          emptyDir: {}
      containers:
        - name: app-container
          image: busybox:1.28
          command:
            - sh
            - -c
            - |
              mkdir -p /var/log/app
              while true; do
                echo "Log entry" >> /var/log/app/app.log
                sleep 5
              done
          volumeMounts:
            - name: log-volume
              mountPath: /var/log/app
        - name: log-agent
          image: busybox:1.28
          command: ["sh", "-c", "until [ -f /var/log/app/app.log ]; do sleep 1; done; tail -f /var/log/app/app.log"]
          volumeMounts:
            - name: log-volume
              mountPath: /var/log/app
kubectl create namespace logging-ns          # only if it does not exist
kubectl apply -f question2.yaml
kubectl get pods -n logging-ns
kubectl logs <pod-name> -c log-agent -n logging-ns -f   # "Log entry" lines

[!NOTE] Beyond the course: native sidecars (v1.29+) are an initContainers entry with restartPolicy: Always. Use them only if the task asks for that. Otherwise use the regular second-container structure specified in the task, as above.

Q3 — Ingress webapp-ingress (ns ingress-ns)

Spec: IngressClass nginx, host kodekloud-ingress.app, path / with Prefix, backend webapp-svc:80. Background: §9.18.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: webapp-ingress
  namespace: ingress-ns
spec:
  ingressClassName: nginx
  rules:
    - host: kodekloud-ingress.app
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: webapp-svc
                port:
                  number: 80
kubectl create namespace ingress-ns   # only if absent
kubectl apply -f question3.yaml
kubectl get ingress -n ingress-ns
curl -s http://kodekloud-ingress.app/        # the client must resolve the host to the ingress IP (DNS or /etc/hosts)

[!NOTE] Beyond the course: kubectl create ingress webapp-ingress -n ingress-ns --class=nginx --rule="kodekloud-ingress.app/*=webapp-svc:80" (a trailing * gives pathType: Prefix).

Q4 — Deployment nginx-deploy nginx:1.16, rolling update to nginx:1.17

kubectl create deployment nginx-deploy --image=nginx:1.16 --dry-run=client -o yaml > question4.yaml
kubectl apply -f question4.yaml
kubectl get deploy nginx-deploy
kubectl rollout history deployment nginx-deploy
kubectl set image deployment/nginx-deploy nginx=nginx:1.17     # container name "nginx" comes from the image name
kubectl rollout status deployment/nginx-deploy
kubectl rollout history deployment nginx-deploy                 # now 2 revisions
kubectl rollout history deployment nginx-deploy --revision=2    # shows image nginx:1.17
  • RollingUpdate is the default strategy, so there is nothing to configure. Background: §5.2.

Q5 — User john: CSR john-developer + Role developer in development

Background: §7.10 (CSR API), §7.16 (RBAC).

base64 -w0 john.csr        # single-line base64 -> goes into spec.request
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: john-developer
spec:
  signerName: kubernetes.io/kube-apiserver-client   # required in v1
  request: <BASE64_CSR>
  usages:
    - digital signature
    - key encipherment
    - client auth
kubectl apply -f question5-csr.yaml
kubectl get csr john-developer                     # Pending
kubectl certificate approve john-developer         # Approved,Issued
kubectl get csr john-developer -o jsonpath='{.status.certificate}' | base64 -d > john.crt
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: developer
  namespace: development
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["create", "list", "get", "update", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: john-developer-role-binding
  namespace: development
subjects:
  - kind: User
    name: john                    # = CN in the CSR
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: developer
  apiGroup: rbac.authorization.k8s.io
kubectl apply -f question5-rbac.yaml               # create the namespace first if it is missing
kubectl auth can-i create pods --as=john -n development   # yes
kubectl auth can-i create pods --as=john -n default       # no
  • The username in the RoleBinding is the CN of the CSR subject.
  • The CSR does not contain the private key. Keep john.key safe and pair it with john.crt in the kubeconfig for john (§7.12).

[!NOTE] Beyond the course, imperative RBAC: kubectl create role developer -n development --verb=create,list,get,update,delete --resource=pods and kubectl create rolebinding john-developer-role-binding -n development --role=developer --user=john.

Q6 — Pod nginx-resolver, ClusterIP service, DNS lookups saved to files

Background: §9.15.

kubectl run nginx-resolver --image=nginx --restart=Never
kubectl expose pod nginx-resolver --name=nginx-resolver-service --port=80 --target-port=80 --type=ClusterIP
kubectl get svc nginx-resolver-service

# Service lookup from a throwaway busybox pod; the output is saved locally
kubectl run test-nslookup --image=busybox:1.28 --rm -it --restart=Never -- nslookup nginx-resolver-service > nginx.svc

# Pod lookup: pod DNS name = <IP with dashes>.<ns>.pod.cluster.local
POD_IP=$(kubectl get pod nginx-resolver -o jsonpath='{.status.podIP}')
IP_DASH=$(echo $POD_IP | tr '.' '-')          # 172.17.1.18 -> 172-17-1-18
kubectl run test-nslookup2 --image=busybox:1.28 --rm -it --restart=Never -- nslookup ${IP_DASH}.default.pod.cluster.local > nginx.pod
  • Service FQDN: nginx-resolver-service.default.svc.cluster.local.
  • Pod FQDN: <ip-with-dashes>.<namespace>.pod.cluster.local.
  • The example pins busybox:1.28; DNS utility behavior can differ by image. Confirm the lookup exits successfully and inspect the saved output.

Q7 — Static pod nginx-critical on cluster1-node01

The kubelet watches /etc/kubernetes/manifests. Background: §3.16.

kubectl run nginx-critical --image=nginx --restart=Never --dry-run=client -o yaml > static.yaml
# copy the manifest to the node (scp, or paste it into a file over ssh), then on cluster1-node01:
sudo mv static.yaml /etc/kubernetes/manifests/static.yaml
# back on the control plane:
kubectl get pod -o wide        # nginx-critical-cluster1-node01, NODE = cluster1-node01
  • The mirror pod's name has the node name appended.
  • The file must be on the node. A YAML file left on the control plane does nothing.

[!NOTE] Beyond the course: confirm the path on the node with grep staticPodPath /var/lib/kubelet/config.yaml.

Q8 — HPA backend-hpa on memory, 65%, min 3, max 15 (ns backend)

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: backend-hpa
  namespace: backend
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: backend-deployment
  minReplicas: 3
  maxReplicas: 15
  metrics:
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 65
kubectl create namespace backend        # only if it does not exist
kubectl apply -f webapp-hpa.yaml
kubectl describe hpa backend-hpa -n backend
  • metrics-server must be running. Beyond the course: memory Utilization is measured against the containers' memory requests, so the target pods need requests set.

Q9 — kubectl gets "connection refused" (kubelet is inactive)

Symptom: dial tcp <api-server-ip>:6443: connect: connection refused. Background: §12.4.

kubectl get nodes                       # connection refused
sudo systemctl status kubelet           # inactive (dead)
sudo systemctl start kubelet
sudo systemctl enable kubelet
crictl ps -a | grep kube-apiserver      # the API server container comes back
kubectl get pods -n kube-system
kubectl get nodes
  • The lab’s stopped kubelet prevents the missing API-server static Pod from being started/recovered. Stopping kubelet does not automatically stop containers already running under the runtime; diagnose the actual missing/exited component with crictl.

Q10 — Change Gateway web-gateway (ns cka5673) to HTTPS

Spec: listener on port 443, protocol HTTPS, hostname kodekloud.com, certificate from Secret kodekloud-tls. Background: §9.21.

kubectl get gateway -n cka5673 -o yaml > question10.yaml   # or: kubectl edit gateway web-gateway -n cka5673
spec:
  gatewayClassName: kodekloud        # keep the existing class
  listeners:
    - name: https
      port: 443
      protocol: HTTPS
      hostname: kodekloud.com
      tls:
        certificateRefs:             # Secret in the Gateway's namespace (mode defaults to Terminate)
          - name: kodekloud-tls
      allowedRoutes:
        namespaces:
          from: Same
kubectl apply -f question10.yaml
kubectl get gateway -n cka5673

[!CAUTION] Correction:

  • The course runs kubectl create namespace cka5673 before applying. The namespace already exists because the Gateway lives in it, so that command just errors. Skip it.
  • The course also says the TLS field could be tls.certificateRefs or tls.secretRef depending on version. In Gateway API v1 it is tls.certificateRefs (kind Secret by default). There is no secretRef. Check with kubectl explain gateway.spec.listeners.tls.

Q11 — Find and uninstall the Helm release that uses kodekloud/webapp-color:v1

helm list -A                                                        # every release, every namespace
helm get manifest <release-name> -n <namespace> | grep -i "kodekloud/webapp-color:v1"
helm uninstall <release-name> -n <namespace>                        # e.g. helm uninstall atlanta-page-apd -n atlanta-page-04
  • helm uninstall removes the release’s managed resources; kept objects, CRDs and separately managed persistent data may remain.

[!NOTE] Beyond the course, check every release in one go:

helm list -A --no-headers | while read n ns _; do helm get manifest $n -n $ns | grep -q "webapp-color:v1" && echo "$n $ns"; done

Q12 — Pick the most restrictive NetworkPolicy: only frontend → backend

Requirement: allow ingress to the backend pods only from the frontend namespace and block the database namespace. Do not delete existing policies. Choose and apply the most restrictive candidate that meets the requirement.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: net-policy-3
  namespace: backend
spec:
  podSelector: {}                 # every pod in backend
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              name: frontend      # only namespaces labelled name=frontend
      ports:
        - protocol: TCP
          port: 80                # and only TCP/80
kubectl apply -f net-pol-3.yaml
  • This candidate permits TCP/80 from namespaces labelled name=frontend. Background: §7.27/§7.28.

[!CAUTION] Correction: NetworkPolicies are additive. This candidate cannot revoke traffic allowed by another policy selecting the backend Pods. Inspect every applicable policy (kubectl get netpol -n backend -o yaml) and verify connectivity; obey the task’s instruction to preserve existing policies. See NetworkPolicy semantics.

  • Beyond the course: check the label actually on the namespace with kubectl get ns --show-labels. If it is missing, use kubernetes.io/metadata.name: frontend, which every namespace has automatically.

Q13 — Deployment backend-api stuck below 3 replicas (ResourceQuota)

Symptom: the ReplicaSet events show exceeded quota: cpu-mem-quota on requests.memory (e.g. used 256Mi, hard 300Mi).

kubectl describe deployment backend-api
kubectl describe rs backend-api-<rs-suffix>        # FailedCreate ... exceeded quota
kubectl describe resourcequota cpu-mem-quota       # Used vs Hard
kubectl edit deployment backend-api                # lower spec.template.spec.containers[].resources.requests.memory, e.g. 90Mi
kubectl delete rs backend-api-6b6454f5cf           # optional: drop the old RS so its pods release quota
kubectl get deployment backend-api                 # READY 3/3
  • Root cause: the namespace quota is exceeded. Budget replicas × request + requests of other counted Pods ≤ quota, including temporary rollout surge Pods.
  • If you are not allowed to change the requests, ask for a higher quota (when that is permitted).
  • Course rationale: the updated template must produce a new ReplicaSet with lower requests. More precisely, editing the Deployment template already triggers that rollout; deleting an old ReplicaSet is not normally required, but its old Pods can consume quota and stall progress. Delete only the old lab ReplicaSet if the task permits it. Background: §2.25 (ResourceQuota), §3.12.

Q14 — Install Calico with pod CIDR 172.17.0.0/16 and test pod-to-pod traffic (cluster4)

curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.29.2/manifests/custom-resources.yaml
# edit: Installation spec.calicoNetwork.ipPools[0].cidr: 172.17.0.0/16
kubectl apply -f custom-resources.yaml

kubectl run web-app --image=nginx --restart=Never
kubectl get pod web-app -o wide
POD_IP=$(kubectl get pod web-app -o jsonpath='{.status.podIP}')
kubectl run test --rm -it --image=jrecord/nettools --restart=Never -- curl http://${POD_IP}/   # nginx welcome HTML
  • Set the IPPool CIDR before pods get IPs. Pods that already have IPs keep them.

[!CAUTION] Correction: custom-resources.yaml only contains Installation/APIServer custom resources. The Tigera operator must be installed first, or the apply fails with "no matches for kind Installation":

kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.29.2/manifests/tigera-operator.yaml

Use create, not apply: the CRDs are too large for the last-applied annotation. Then edit and apply custom-resources.yaml, and watch kubectl get pods -n calico-system until everything is Running.

[!WARNING] Exam traps / remember:

  • Default StorageClass: annotation storageclass.kubernetes.io/is-default-class: "true". Static/local provisioner: kubernetes.io/no-provisioner.
  • CSR: signerName is required, usages includes client auth, decode .status.certificate. RBAC subject: kind: User, name = CN.
  • Static pod: the file goes in /etc/kubernetes/manifests on the target node.
  • "connection refused :6443" on a kubeadm control plane: check the kubelet first, then the apiserver static pod (crictl ps -a).
  • Do not delete or edit policies, PVs or other objects the task says to keep. Only change what is asked.

[!NOTE] Beyond the course: allowVolumeExpansion: true does not give kubernetes.io/no-provisioner a resize controller. Actual expansion requires backend/driver support. Helm uninstall also has exceptions (e.g. kept resources, CRDs and separately managed PVC/data); inspect the release rather than assuming all persistent data was deleted.

16.3 Mock Exam 3 Step by Step Solutions

Source: KodeKloud notes

QTaskQTask
1sysctl settings kubeadm needs8PVC Pending: accessMode mismatch
2SA + ClusterRole + ClusterRoleBinding + Pod9Fix a broken kubeconfig
3StorageClass rancher-sc10Scale a deployment; fix the controller-manager
4ConfigMap → env vars via envFrom11HPA on a custom Pods metric
5PriorityClass, assign it to a pod12HTTPRoute 80/20 traffic split
6NetworkPolicy allowing port 8013Helm lint/install/uninstall
7Taint + toleration14Write the Pod CIDR to a file

Q1 — Persistent sysctl settings for kubeadm (all nodes)

Needed: net.ipv4.ip_forward = 1 and net.bridge.bridge-nf-call-iptables = 1. The docs page to use is Creating a cluster with kubeadm / container runtime prerequisites (kubernetes.io docs are allowed in the exam). Background: §11.3.

cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sudo sysctl --system                         # apply now and on every boot
sysctl net.ipv4.ip_forward                   # = 1
sysctl net.bridge.bridge-nf-call-iptables    # = 1

# if net.bridge.* keys did not exist, load the module and APPLY AGAIN:
sudo modprobe br_netfilter
echo "br_netfilter" | sudo tee /etc/modules-load.d/br_netfilter.conf
sudo sysctl --system
sysctl net.bridge.bridge-nf-call-iptables
  • sysctl --system may print "permission denied" for unrelated keys. That is fine as long as the two k8s keys are set.
  • A file in /etc/sysctl.d/ makes the setting persistent. sysctl -w alone is lost on reboot.

Q2 — ServiceAccount pvviewer that can list PVs, and a pod that uses it

Background: §7.18 (cluster roles), §7.20 (service accounts).

kubectl create serviceaccount pvviewer          # verify: kubectl get sa
kubectl create clusterrole pvviewer-role --resource=persistentvolumes --verb=list
kubectl create clusterrolebinding pvviewer-role-binding --clusterrole=pvviewer-role --serviceaccount=default:pvviewer
kubectl describe clusterrole pvviewer-role
kubectl describe clusterrolebinding pvviewer-role-binding
apiVersion: v1
kind: Pod
metadata:
  name: pvviewer
spec:
  serviceAccountName: pvviewer
  containers:
  - name: pvviewer
    image: redis
kubectl apply -f question2.yaml
kubectl get pod pvviewer                                       # Running
kubectl describe pod pvviewer                                  # Service Account: pvviewer
kubectl get pod pvviewer -o yaml | grep serviceAccountName    # pvviewer
kubectl auth can-i list persistentvolumes --as=system:serviceaccount:default:pvviewer   # Beyond the course: yes
  • PVs are cluster-scoped, so this needs a ClusterRole + ClusterRoleBinding. A Role cannot grant it.
  • --serviceaccount takes <namespace>:<name>.

[!NOTE] Beyond the course: kubectl run --serviceaccount was removed. Generate the pod with kubectl run pvviewer --image=redis --dry-run=client -o yaml and add serviceAccountName by hand.

Q3 — StorageClass rancher-sc

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: rancher-sc
provisioner: rancher.io/local-path
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer   # binding waits until a pod uses the PVC
kubectl apply -f question3.yaml
kubectl get storageclass rancher-sc
kubectl describe storageclass rancher-sc

Q4 — ConfigMap app-config (ns cm-namespace) → env vars of Deployment cm-webapp

Background: §5.8.

kubectl create configmap app-config -n cm-namespace \
  --from-literal=ENV=production \
  --from-literal=LOG_LEVEL=info
kubectl describe cm app-config -n cm-namespace
kubectl edit deploy cm-webapp -n cm-namespace       # add envFrom (below) under the container
spec:
  template:
    spec:
      containers:
      - name: <container-name>
        image: <image>
        envFrom:                 # imports every key as an env var
        - configMapRef:
            name: app-config
kubectl get pod -n cm-namespace                     # pods restart (new rollout)
kubectl describe pod -n cm-namespace -l app=cm-webapp
kubectl exec -n cm-namespace <pod-name> -- env | egrep 'ENV|LOG_LEVEL'

[!NOTE] Beyond the course: kubectl set env deployment/cm-webapp -n cm-namespace --from=configmap/app-config also works. It writes one env[].valueFrom.configMapKeyRef per key instead of envFrom. If the task says envFrom, edit the YAML.

Q5 — PriorityClass low-priority (value 50000), assign it to pod lp-pod (ns low-priority)

Background: §3.18.

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: low-priority
value: 50000
globalDefault: false
description: "Low priority class for test pods"
kubectl apply -f question5.yaml
kubectl get priorityclass
kubectl get pod lp-pod -n low-priority -o yaml > question5-pod.yaml
# edit: add spec.priorityClassName: low-priority ; delete the numeric spec.priority line
kubectl delete pod lp-pod -n low-priority
kubectl apply -f question5-pod.yaml          # or skip the delete: kubectl replace --force -f question5-pod.yaml
kubectl get pod -n low-priority

[!WARNING] The exported pod YAML already has priority: 0. If you keep it and add priorityClassName, the API server can reject the pod because the number no longer matches the class. Delete the numeric priority field and let the class set it.

Q6 — NetworkPolicy ingress-np-test: allow TCP 80 to pods run=np-test-1

kubectl get pod --show-labels        # np-test-1 has run=np-test-1
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: ingress-np-test
  namespace: default
spec:
  podSelector:
    matchLabels:
      run: np-test-1
  policyTypes:
  - Ingress
  ingress:
  - ports:
    - protocol: TCP
      port: 80
    from:
    - podSelector: {}     # any pod in the SAME namespace
kubectl apply -f question6.yaml
kubectl get networkpolicy -n default
kubectl describe networkpolicy ingress-np-test -n default

[!NOTE] Beyond the course: from: [podSelector: {}] limits sources to the policy's own namespace. To allow port 80 from any source, leave out from (just - ports: [...]). Test with kubectl run t --rm -it --image=busybox:1.28 --restart=Never -- wget -qO- -T2 <svc-or-pod-ip>.

Q7 — Taint node01, create dev-redis (no toleration) and prod-redis (with toleration)

Background: §3.6.

kubectl taint node node01 env_type=production:NoSchedule
kubectl describe node node01 | grep -i taint
kubectl run dev-redis --image=redis:alpine --restart=Never   # will not be scheduled on node01
apiVersion: v1
kind: Pod
metadata:
  name: prod-redis
spec:
  tolerations:
  - key: env_type
    operator: Equal
    value: production
    effect: NoSchedule
  containers:
  - name: prod-redis
    image: redis:alpine
kubectl apply -f question7.yaml
kubectl get pod -o wide        # prod-redis on node01, dev-redis on another node

[!WARNING] A toleration only allows scheduling on node01. It does not attract the pod there (§3.11). prod-redis ends up on node01 here only because the other nodes are not suitable (e.g. the tainted control plane). If the task says it must run on node01, also add nodeSelector/nodeName (Beyond the course).

Q8 — PVC app-pvc (ns storage-ns) Pending while PV app-pv is Available

Root cause: accessMode mismatch (PV ReadWriteOnce, PVC asks for ReadWriteMany). Fix the PVC. The PV must not be edited. Background: §8.8.

kubectl get pv
kubectl get pvc -n storage-ns
kubectl get pv app-pv -o yaml                       # compare accessModes, capacity, storageClassName
kubectl get pvc app-pvc -n storage-ns -o yaml > question8.yaml
# edit: spec.accessModes: [ReadWriteOnce]; delete status, resourceVersion, uid and other server-managed fields
kubectl delete pvc app-pvc -n storage-ns
kubectl apply -f question8.yaml
kubectl get pvc -n storage-ns                       # Bound to app-pv
  • Beyond the course: you have to delete and recreate because a PVC's accessModes cannot be changed after creation.
  • Beyond the course: binding also needs the PVC request ≤ PV capacity and the same storageClassName (and volumeMode).

Q9 — Fix /root/CKA/super.kubeconfig (wrong API server port)

kubectl get nodes --kubeconfig=/root/CKA/super.kubeconfig   # connection refused: controlplane:9999
cat /root/CKA/super.kubeconfig                              # look at clusters[].cluster.server
sudo netstat -tulnp | grep kube-apiserver                   # :::6443 LISTEN
# edit server: https://controlplane:9999  ->  https://controlplane:6443
kubectl get nodes --kubeconfig=/root/CKA/super.kubeconfig   # nodes listed
  • The kube-apiserver listens on 6443 by default. Background: §7.12.
  • Beyond the course: ss -tlnp | grep 6443 works where netstat is not installed. You can also compare against the server: line in ~/.kube/config.

Q10 — Scale nginx-deploy to 3; the ReplicaSet does not scale (controller-manager broken)

Background: §12.4/§12.5.

kubectl scale deploy nginx-deploy --replicas=3
kubectl get deploy nginx-deploy; kubectl get rs; kubectl get pods   # RS stuck at 1
kubectl get pod -n kube-system          # kube-controller-manager-* in ImagePullBackOff / CrashLoopBackOff
sudo vi /etc/kubernetes/manifests/kube-controller-manager.yaml      # fix the typo in image / command / volumes
kubectl get pod -n kube-system          # the kubelet recreates the static pod once the file is saved
kubectl get deploy nginx-deploy         # 3/3 once the controller reconciles
  • Deployment → ReplicaSet → Pods scaling is done by the controller-manager. If it is down, kubectl scale updates the spec but nothing creates the pods.
  • Beyond the course: for errors in args, read kubectl logs -n kube-system kube-controller-manager-controlplane or crictl logs <id>.

Q11 — HPA api-hpa (ns api) on the custom pods metric requests_per_second

Spec: target api-deployment, 1–20 replicas, AverageValue 1000.

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api-hpa
  namespace: api
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api-deployment
  minReplicas: 1
  maxReplicas: 20
  metrics:
    - type: Pods                    # per-pod custom metric (not Resource)
      pods:
        metric:
          name: requests_per_second
        target:
          type: AverageValue
          averageValue: "1000"
kubectl apply -f question11.yaml
kubectl describe hpa api-hpa -n api   # metric shows <unknown> if no custom-metrics adapter serves it
  • The HPA object is created even when no backend serves the metric.

Q12 — HTTPRoute web-route: 80% to web-service, 20% to web-service-v2, attached to web-gateway

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: web-route
  namespace: default
spec:
  parentRefs:
    - name: web-gateway
      namespace: default
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: web-service
          port: 80
          weight: 80
        - name: web-service-v2
          port: 80
          weight: 20
kubectl apply -f question12.yaml
kubectl get httproute -n default
kubectl describe httproute web-route -n default   # check that the parent shows Accepted
  • The split only happens if the Gateway implementation supports weighted backendRefs. Beyond the course: weights are relative, so 80/20 = 4/1.

Q13 — Helm: lint /root/new-version, install it, uninstall webpage-server-01

helm lint /root/new-version                          # must report 0 failures
helm install new-version-release /root/new-version   # install from the local chart directory
helm list
helm uninstall webpage-server-01
helm list
  • Beyond the course: if the old release is not in default, find it with helm list -A and add -n <ns> to install/uninstall.

Q14 — Write the Pod CIDR to /root/pod-cidr.txt

kubectl get node -o jsonpath='{.items[0].spec.podCIDR}' > /root/pod-cidr.txt          # first node
kubectl get node controlplane -o jsonpath='{.spec.podCIDR}' > /root/pod-cidr.txt       # a specific node
cat /root/pod-cidr.txt          # e.g. 172.17.0.0/24

[!NOTE] Beyond the course: node.spec.podCIDR is the per-node slice (here a /24). The cluster-wide range is in --cluster-cidr (grep cluster-cidr /etc/kubernetes/manifests/kube-controller-manager.yaml), in kubectl -n kube-system get cm kubeadm-config -o yaml | grep podSubnet, or in the kube-proxy ConfigMap clusterCIDR. Read the task wording to decide which one it wants. The course answer uses the node's podCIDR.

[!WARNING] Exam traps / remember:

  • Cluster-scoped resources (PVs, nodes, StorageClasses) need a ClusterRole + ClusterRoleBinding.
  • A pod's priority comes from priorityClassName. Delete the numeric priority field before reapplying.
  • Exported YAML (-o yaml) contains status, uid, resourceVersion, creationTimestamp. Strip them before recreating, or use kubectl replace --force.
  • Scaling does nothing: suspect the controller-manager. Pods stay Pending with no node assigned: suspect the scheduler. Fix both in /etc/kubernetes/manifests/.
  • kubeconfig problems: check server: (host and port 6443), the certificate paths/data, and the context → cluster/user mapping.

16.4 Whats Next

Source: KodeKloud notes

Introduction to KodeKloud's Ultimate CKA Mock Exam Series (10 mock exams). Take it after finishing the core course, its mock exams and the lightning labs. The mocks are harder than the course labs on purpose, are timed, and use several clusters.

Exam domains (approx. weight; the mocks follow the same split)

DomainWeight
Troubleshooting30% (largest)
Architecture, Installation & Maintenance25%
Services & Networking20%
Workload Scheduling15%
Storage10%

Mock lab environment

ClusterSetup
cluster13-node K8s (cluster1-controlplane, cluster1-node01, cluster1-node02)
cluster22-node K8s
cluster3single-node K3s (cluster3-controlplane)
cluster42-node K8s
  • You start on the student node, a client shell that has contexts for all clusters. Switch with kubectl contexts. SSH to a control-plane or worker node only when a task needs node-level access.
  • The lab takes ~30 s to load (sometimes 1–2 min) and has ~20 questions. Each question belongs to one domain. You can scroll and jump between questions.
  • When the timer reaches zero the exam is auto-submitted and graded. End Exam submits early and starts grading. The mock results screen shows the pass mark: 66%.

Workflow drill (mock 1, Q1): decode Secret beta-sec-cka14-arch (ns beta-ns-cka14-arch) on cluster3 and save it to /opt/beta-sec-cka14-arch

kubectl config get-clusters                      # cluster1..cluster4
kubectl config use-context cluster3              # switch FIRST, for every question
kubectl get nodes                                # confirm which cluster you are on
kubectl get ns                                   # target namespace exists
kubectl get secrets -n beta-ns-cka14-arch        # beta-sec-cka14-arch  Opaque  1
kubectl get secret beta-sec-cka14-arch -n beta-ns-cka14-arch -o yaml   # data.secret: VGhpcyBpcyB0aGUgc2VjcmV0IQo=
echo 'VGhpcyBpcyB0aGUgc2VjcmV0IQo=' | base64 -d                       # This is the secret!
echo 'VGhpcyBpcyB0aGUgc2VjcmV0IQo=' | base64 -d > /opt/beta-sec-cka14-arch   # the file goes on the student node

[!NOTE] Beyond the course, a one-liner with no copy/paste: kubectl get secret beta-sec-cka14-arch -n beta-ns-cka14-arch -o jsonpath='{.data.secret}' | base64 -d > /opt/beta-sec-cka14-arch (§15.1).

Working across clusters: --context per command

kubectl --context cluster1 create serviceaccount student -n kube-system
kubectl --context cluster1 create clusterrole student --verb=get,list,watch --resource=pods
kubectl --context cluster1 create clusterrolebinding student --clusterrole=student --serviceaccount=kube-system:student
kubectl --context cluster1 auth can-i get pods --as=system:serviceaccount:kube-system:student   # yes
  • Service account identity for --as: system:serviceaccount:<ns>:<name>.

[!WARNING] Exam traps / remember:

  • Read every task for the cluster, namespace and output path it names. Working in the wrong context means the requested cluster was not changed.
  • Pass --context <ctx> (or run use-context first) so you never change the wrong cluster.
  • Output files (e.g. /opt/...) go on the student node unless the task says otherwise. Leave any SSH session before writing them.
  • Practise the troubleshooting routine (logs, events, describe, node-level checks such as kubelet and static pods), since it is worth 30%.
  • Watch the timer. Unfinished work is graded as it stands when time runs out.

[!NOTE] Beyond the course: kubectl config get-clusters lists clusters, not contexts. kubectl config get-contexts (and current-context) shows the names use-context accepts. The real CKA is 2 hours with a 66% pass mark, and its exact setup can change (e.g. a per-task ssh <host> instead of contexts). Run whatever setup command the task gives before you start.


← 15 Other Topics · Index


Reference

Based on the KodeKloud Certified Kubernetes Administrator (CKA) course notes.

On this page

16.1 Mock Exam 1 Step by Step SolutionsQ1 — Pod mc-pod: 3 containers + shared non-persistent volumeQ2 — Install cri-dockerd on node01Q3 — Save the VPA CRD names to /root/vpa-crds.txtQ4 — ClusterIP service messaging-service for pod messaging on 6379 (internal only)Q5 — Deployment hr-web-app, image kodekloud/webapp-color, 2 replicasQ6 — Fix pod orange (Init:CrashLoopBackOff)Q7 — NodePort service hr-web-app-service on 30082Q8 — PersistentVolume pv-analyticsQ9 — HPA webapp-hpa: CPU 50%, min 2, max 10, scale-down stabilisation 300sQ10 — VPA analytics-vpa, Auto modeQ11 — Gateway web-gateway in nginx-gatewayQ12 — Helm: update repo, upgrade release kk-mock1 (ns kk-ns) from chart 18.1.0 to 18.1.516.2 Mock Exam 2 Step by Step SolutionsQ1 — Default StorageClass local-sc (on cluster1-controlplane)Q2 — Deployment logging-deployment (ns logging-ns) with a sidecar log agentQ3 — Ingress webapp-ingress (ns ingress-ns)Q4 — Deployment nginx-deploy nginx:1.16, rolling update to nginx:1.17Q5 — User john: CSR john-developer + Role developer in developmentQ6 — Pod nginx-resolver, ClusterIP service, DNS lookups saved to filesQ7 — Static pod nginx-critical on cluster1-node01Q8 — HPA backend-hpa on memory, 65%, min 3, max 15 (ns backend)Q9 — kubectl gets "connection refused" (kubelet is inactive)Q10 — Change Gateway web-gateway (ns cka5673) to HTTPSQ11 — Find and uninstall the Helm release that uses kodekloud/webapp-color:v1Q12 — Pick the most restrictive NetworkPolicy: only frontend → backendQ13 — Deployment backend-api stuck below 3 replicas (ResourceQuota)Q14 — Install Calico with pod CIDR 172.17.0.0/16 and test pod-to-pod traffic (cluster4)16.3 Mock Exam 3 Step by Step SolutionsQ1 — Persistent sysctl settings for kubeadm (all nodes)Q2 — ServiceAccount pvviewer that can list PVs, and a pod that uses itQ3 — StorageClass rancher-scQ4 — ConfigMap app-config (ns cm-namespace) → env vars of Deployment cm-webappQ5 — PriorityClass low-priority (value 50000), assign it to pod lp-pod (ns low-priority)Q6 — NetworkPolicy ingress-np-test: allow TCP 80 to pods run=np-test-1Q7 — Taint node01, create dev-redis (no toleration) and prod-redis (with toleration)Q8 — PVC app-pvc (ns storage-ns) Pending while PV app-pv is AvailableQ9 — Fix /root/CKA/super.kubeconfig (wrong API server port)Q10 — Scale nginx-deploy to 3; the ReplicaSet does not scale (controller-manager broken)Q11 — HPA api-hpa (ns api) on the custom pods metric requests_per_secondQ12 — HTTPRoute web-route: 80% to web-service, 20% to web-service-v2, attached to web-gatewayQ13 — Helm: lint /root/new-version, install it, uninstall webpage-server-01Q14 — Write the Pod CIDR to /root/pod-cidr.txt16.4 Whats NextExam domains (approx. weight; the mocks follow the same split)Mock lab environmentWorkflow drill (mock 1, Q1): decode Secret beta-sec-cka14-arch (ns beta-ns-cka14-arch) on cluster3 and save it to /opt/beta-sec-cka14-archWorking across clusters: --context per command