TT Lab
Get started
Learn Learning paths Courses

CKAD — Kubernetes Application Developer

CKAD Mock Exam A

Continue in TT Lab

This is a practice exam

In the real CKAD, you solve 15–20 tasks in 120 minutes and pass if you score above 66%. This set is also matched to 17 tasks, 120 minutes, and a 66% passing score. Because scoring is partial, you do not need to get everything right. Passing 12 of the 17 counts as complete.

Do not look at the hints; solve everything first. The real exam has no hints. For a task you are stuck on, it is better for your score to skip it and come back if time remains. Use the hints and answers for review after you have finished the exam once.

Things that cost you time if you first learn them at the real exam

Domain distribution

Domain Actual weight Tasks in this set
Application Design and Build 20% tasks 1–3
Application Deployment 20% tasks 4–7
Application Observability and Maintenance 15% tasks 8–10
Application Environment, Configuration and Security 25% tasks 11–14
Services and Networking 20% tasks 15–17

About time allocation

Task 3 (CronJob) takes up to 1 minute until the controller actually runs it once. Create it first, solve other tasks, and check afterward. Tasks 4 and 5 have to wait until the rollout finishes, so it is faster to use kubectl rollout status.

What is different in this environment

The cluster inside the Pod is a single-user cluster started by kwokctl. The control plane is real, so manifests, RBAC, scheduling, quotas, CRDs, PV binding, and drain all actually work. However, workload containers do not run, so kubectl exec, logs, and port-forward cannot be used. The tasks do not require them either.

Before you start, check with kubectl get nodes that all 3 nodes are Ready. If they are not yet, the cluster is still coming up (it takes about 2 minutes).

Grading

When you press the check button on each task, the grader recomputes the values from the live cluster and compares them. It looks at what state the cluster is in, not at what you wrote in a file. Several paths to the correct answer are accepted.

Init container and sidecar

In the namespace app, create a Pod logger.

Write the init container separately under spec.initContainers. Declare the volume once on the Pod, and each of the three containers uses it through its own volumeMounts.

A Job with a specified completion count

In the namespace app, create a Job migrate. The image is busybox:1.36 and it runs echo done.

The completions are 4, parallelism 2, backoffLimit 2, activeDeadlineSeconds 120, and restartPolicy is Never. The Job must end as Complete.

A Job's completions cannot be changed once it is created. If you wrote the wrong value, delete it and create it again. Write restartPolicy on the Pod template side.

CronJob

In the namespace app, create a CronJob report. The image is busybox:1.36, running date, and restartPolicy is OnFailure.

The schedule is */1 * * * *, concurrencyPolicy is Forbid, startingDeadlineSeconds is 30, and keep only 2 successful histories and 1 failed history. It must actually run at least once.

The period is 1 minute, so right after creating it there is no run record yet. Wait until a time appears in the LAST SCHEDULE column of kubectl get cronjob report -n app, then grade.

Updating the image with a rolling update

In the namespace deploy, create a Deployment api with nginx:1.27 and 3 replicas. The strategy is RollingUpdate with maxSurge 2 and maxUnavailable 0.

Then update the image to nginx:1.28. You must update it rather than deleting and recreating.

If you update with kubectl set image deploy/api <컨테이너>=nginx:1.28 (the placeholder is the container name), the previous ReplicaSet remains as history. Wait until it finishes with kubectl rollout status.

Rollback

In the namespace deploy, create a Deployment web with nginx:1.27 and 2 replicas.

Then push the image to nginx:1.99-broken, confirm it is a bad deployment, and roll back to the first revision. Even after the rollback, the problem revision must remain in the history.

After checking the revision number with kubectl rollout history deploy/web, use kubectl rollout undo deploy/web --to-revision=1. If you delete and recreate it, the history is lost.

A canary split by labels

In the namespace deploy, create Deployments shop-stable (4 replicas, Pod labels app=shop and version=stable, image nginx:1.27) and shop-canary (1 replica, Pod labels app=shop and version=canary, image nginx:1.28). The container port of both Pods is 8080.

The Service shop takes port 80 and forwards it to 8080, and must point to the Pods of both Deployments.

If you put version in the Service selector, only one side is caught. Use only one label that both Deployments share as the selector. You only need to check that there are 5 endpoints.

A kustomize overlay

In /root/exam/kustomize/base, put a Deployment cart (1 replica, image nginx:1.27, Pod label app=cart) and a kustomization.

In /root/exam/kustomize/overlay, create an overlay that adds prod- in front of the name, changes the namespace to deploy-prod, changes the replicas to 3, changes the image tag to 1.28, and adds the label env=prod (do not put it in the selector).

Apply that overlay to the cluster.

First check the result by eye with kubectl kustomize <디렉터리> (the placeholder is the directory), then apply with kubectl apply -k. A selector cannot be changed once created, so if you put the common label in the selector, the next apply is rejected.

The three probes

In the namespace obs, create a Deployment checkout. The image is nginx:1.27, 2 replicas, and the container port is 8080 (named http).

Write the three probes side by side inside the container spec. If you leave out even one value, the default gets in, but the grader expects the values written in the question exactly.

Extracting a Pod list into a file

In the namespace obs, sort the Pods with the label app=checkout by name, and save them to /root/exam/09-pods.txt in the format <파드이름> <노드이름> <상태> on each line (the placeholders are the Pod name, the node name, and the status). Do not include a header.

The grader compares this file against the cluster at grading time. If you recreated the Pods later, recreate the file as well.

Extract only the fields you need with the range of kubectl get pods -o jsonpath and sort with sort. The node name is .spec.nodeName, and the status is .status.phase.

Moving off a vanished API version

The manifest below is written with API versions the cluster does not serve now. Move it to the current versions and apply it in the namespace obs. If required fields have increased, you must fill them in.

apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata: {name: obs-pdb}
spec:
  minAvailable: 1
---
apiVersion: batch/v1beta1
kind: CronJob
metadata: {name: obs-cron}
spec:
  schedule: "0 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers: [{name: obs, image: busybox:1.36, command: ["sh","-c","date"]}]
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata: {name: obs-ing}
spec:
  rules:
    - host: obs.example.com
      http:
        paths:
          - path: /
            backend:
              serviceName: checkout
              servicePort: 80

Also create the Service checkout that the Ingress will point to (port 80 → container port http) and an IngressClass nginx (controller k8s.io/ingress-nginx), and specify that class on the Ingress. The PDB's selector is app=checkout.

You can check the field names of the current version with kubectl explain <리소스> --recursive (the placeholder is the resource). The PDB in policy/v1 requires a selector, and the Ingress in networking.k8s.io/v1 requires pathType and the backend.service structure.

Injecting configuration and secret values

In the namespace cfg, create a ConfigMap app-config. The keys are APP_MODE=production, LOG_LEVEL=info, and a file key app.properties (the content is up to you).

In a Secret app-secret (Opaque), put the key API_TOKEN. Choose the value yourself.

The Deployment svc (image nginx:1.27, 1 replica) uses all three methods.

With subPath, only a single file, not the directory, is placed at that path. Write the file path in mountPath and the key name in subPath. If you create the value with --from-literal, kubectl does the base64 encoding for you.

Tightening with securityContext

In the namespace cfg, create a Deployment hardened. The image is nginx:1.27 and it has 1 replica.

Pod level: runAsUser 10001, runAsGroup 10001, fsGroup 10001, runAsNonRoot true, seccompProfile is RuntimeDefault.

Container level: allowPrivilegeEscalation false, readOnlyRootFilesystem true, drop all capabilities and add only NET_BIND_SERVICE.

The Pod level and the container level are in different places. capabilities, allowPrivilegeEscalation, and readOnlyRootFilesystem exist only on the container side.

An application-specific service account

In the namespace cfg, create a service account app-sa so that its token is not mounted automatically.

The Role cm-reader allows only get and list on configmaps in the core group. Bind it with a RoleBinding cm-reader.

Make the Deployment reader (image nginx:1.27, 1 replica) run with that service account.

automountServiceAccountToken is a top-level field of the service account object (not inside spec). In a Deployment, specify it with serviceAccountName in the Pod spec.

A workload that runs within the limits

In the namespace cfg-quota, put a LimitRange cfg-limits. Per container, max is cpu 500m and memory 512Mi, min is cpu 50m and memory 64Mi, default is cpu 250m and memory 256Mi, and defaultRequest is cpu 100m and memory 128Mi.

The limits of the ResourceQuota cfg-quota are pods 10, requests.cpu 1, requests.memory 1Gi, limits.cpu 2, and limits.memory 2Gi.

The Deployment sized (image nginx:1.27, 2 replicas) has requests of cpu 100m and memory 128Mi and limits of cpu 250m and memory 256Mi, and both Pods must come up.

Values above the LimitRange's max are rejected at admission. Check whether the quota is actually counting with the Used column of kubectl describe quota cfg-quota -n cfg-quota.

ClusterIP and NodePort

In the namespace net, create a Deployment web. The image is nginx:1.27, 3 replicas, the container port is 8080, and its name is http.

The Service web is ClusterIP, takes port 80, and forwards it to the port name http. The Service web-nodeport is NodePort, points to the same Pods, takes port 80, and its node port is 30080.

For both Services, you must write the name, not a number, in targetPort. You can specify nodePort directly only within the range 30000–32767.

An Ingress that splits two paths

In the namespace net, create a Deployment admin (image nginx:1.27, 1 replica, container port 8080 named http) and a Service admin (port 80 → http).

Create an IngressClass nginx (controller k8s.io/ingress-nginx), and with an Ingress net-ing, send / of the host app.example.com to web:80 and /admin to admin:80. The pathType of both paths is Prefix.

When paths overlap, the more specific one is matched first, but grading only checks that the two rules are written exactly. Also check that the backend Services actually exist.

Three policies: block and allow

In the namespace net, create three NetworkPolicies.

If you block all egress, DNS is blocked too and Pods cannot resolve any name. DNS does not use only UDP; when a response is large it falls over to TCP, so you have to open both. You can pick the namespace selector with the kubernetes.io/metadata.name label.