TT Lab
Get started
Learn Learning paths Courses

CKA — Kubernetes Administrator

CKA Mock Exam A

Continue in TT Lab

This is a practice exam

The real CKA has you solve 15–20 tasks in 120 minutes, and you pass if you exceed 66%. This set is also matched to 17 tasks, 120 minutes, and a 66% passing score. It uses partial credit, so you do not need to get everything right. Passing 12 of the 17 counts as complete.

Do not look at the hints; work through to the end first. The real exam has no hints. It is better for your score to skip a task you are stuck on and come back to it 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 site

Domain allocation

Domain Actual weight Tasks in this set
Cluster Architecture, Installation and Configuration 25% Tasks 1–4
Workloads and Scheduling 15% Tasks 5–7
Services and Networking 20% Tasks 8–10
Storage 10% Tasks 11–12
Troubleshooting 30% Tasks 13–17

Tasks 13 through 17

In the real exam, the broken resources are prepared in advance. This environment has no such mechanism, so the broken manifest is included in each task's instructions. Apply it as it is first, then diagnose the symptom and fix it. Even if you skip applying it and create the right thing from the start, grading passes, but then it does not serve as diagnostic practice.

What differs in this environment

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

Before you start, check with kubectl get nodes that the 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 of each task, the grader recomputes the values from the live cluster and compares them. It looks not at what you wrote in a file but at what state the cluster is in. Several paths to the correct answer are allowed.

Deployment permissions in the ops namespace

Create the namespace ops and a service account deployer.

The Role deployer allows get, list, watch, create, and update on deployments in the apps group, and get and list on pods in the core group. There must be no other permissions. Bind it to that service account with the RoleBinding deployer.

The core group is written as an empty string in apiGroups. A mistake that widens permissions does not show up if you check only what is allowed, so use kubectl auth can-i ... --as=system:serviceaccount:ops:deployer to also ask about the verbs that must not work.

Aggregated ClusterRole

Create the ClusterRole monitoring-view. Do not write the rules directly; use an aggregationRule so that it gathers the ClusterRoles that carry the label rbac.example.com/aggregate-to-monitoring: "true".

The ClusterRole monitoring-metrics that carries that label allows get, list, and watch on pods and nodes in the core group.

The aggregation is done by a controller. If you create monitoring-view with its rules left empty, the rules are filled in a moment later. If they are not filled in, the selector's label differs from the actual label by even one character.

Backup CRD and a custom resource

Create the CRD backups.ops.example.com. The group is ops.example.com, the version is v1 (served and storage), the scope is Namespaced, the kind is Backup, the plural is backups, and the short name is bk. In the schema, spec.schedule is a required string and spec.retention is an integer.

Then, in the namespace ops, create a Backup nightly with the schedule set to 0 2 * * * and the retention set to 7.

Right after a CRD is registered, it is not yet being served. Wait with kubectl wait --for=condition=Established crd/<이름> (where the placeholder is the CRD name) and then create the custom resource. If you do not write types in the schema, any value is accepted.

team-a quota and default resource values

In the namespace team-a, create the ResourceQuota team-a-quota. The limits are pods 10, requests.cpu 2, requests.memory 4Gi, limits.cpu 4, and limits.memory 8Gi.

In the same namespace, create the LimitRange team-a-limits so that containers that do not specify resources get limits cpu 500m and memory 512Mi, and requests cpu 100m and memory 128Mi filled in.

A LimitRange's default fills in limits, and defaultRequest fills in requests. To see whether they are actually filled in, you can ask the server with kubectl run ... --dry-run=server -o yaml.

frontend Deployment and rolling strategy

In the namespace web, create the Deployment frontend. The image is nginx:1.27, there are 4 replicas, and the Pod labels are app=frontend and tier=web.

The strategy is RollingUpdate with maxSurge 1 and maxUnavailable 0, and only 3 entries of revision history are kept.

maxUnavailable 0 means 'never reduce the number of ready Pods even during an update.' You can write it as a number or as a percentage, but the grader expects the numbers 1 and 0.

A batch-only node and a toleration

Create a new node lab-node-batch. Attach the annotation kwok.x-k8s.io/node: fake so that kwok manages it, and put on the label workload=batch and the taint workload=batch:NoSchedule.

Then, in the namespace batch, create the Deployment cruncher to run busybox:1.36 with 3 replicas, and make all three Pods run only on that node.

A toleration says only 'it may go.' It is a nodeSelector or a required nodeAffinity that says 'go there.' You need both for the placement to be decided.

An agent that runs on every node

In the namespace web, create the DaemonSet node-agent. The image is busybox:1.36, and it must run on every node in the cluster, including the tainted node.

A DaemonSet also goes through the scheduler. To run on a tainted node, it needs a toleration that tolerates that taint. To make it tolerate any taint, leave out the key and set only the operator to Exists.

The api Service using a named port

In the namespace web, create the Deployment api. The image is nginx:1.27, there are 2 replicas, and the container port is 8080 and the name of that port is http.

The Service api must accept port 80 as a ClusterIP and forward it by the port name http, not the number.

A Service is created fine even if the selector is wrong, and only the endpoints are empty. Check with kubectl get endpointslice -n web -l kubernetes.io/service-name=api whether addresses are attached.

The shop.example.com Ingress

Create the IngressClass nginx. The controller is k8s.io/ingress-nginx.

In the namespace web, create the Service frontend (port 80) that points to the frontend Deployment, and with the Ingress shop, for the host shop.example.com, send / to frontend:80 and /api to api:80. Both paths have the pathType Prefix.

An Ingress is created even without backend Services. No error appears even if you write the Service name wrongly, so check it yourself. In v1, the backend is written with service.name and service.port.number.

Network policies for the web namespace

For all Pods in the namespace web, create the NetworkPolicy default-deny-ingress that blocks incoming traffic by default.

Then, with the NetworkPolicy api-allow, for only the app=api Pods, allow only TCP 8080 coming from the app=frontend Pods in the same namespace and from the namespace monitoring.

A full block is leaving the podSelector as an empty object and writing no ingress rules at all. If you write the items of the from list separately, it is OR, and if you write a podSelector and a namespaceSelector together in one item, it is AND. This problem is OR.

Static volume binding

Create the StorageClass local-fast. The provisioner is kubernetes.io/no-provisioner, the reclaimPolicy is Retain, and the volumeBindingMode is Immediate.

The PV pv-fast-1 is 2Gi, ReadWriteOnce, local-fast, hostPath /mnt/fast1. In the namespace storage, create the PVC data as 1Gi, ReadWriteOnce, local-fast, and have it bind to that PV.

If the PVC does not leave Pending, one of the class name, access mode, or capacity does not match the PV. The requested capacity may be smaller than the PV capacity, but the access mode must be exactly included.

A workload that uses a PVC and a temporary volume together

In the namespace storage, create the Deployment writer. The image is nginx:1.27, with 1 replica.

Mount the PVC data with the volume name data at /data, and mount an emptyDir volume cache at /cache.

You declare volumes in the Pod spec's volumes and use them by name in the container's volumeMounts. If only one of the two is present, the Pod is not created or the mount is missing.

A Service with empty endpoints

First apply the manifest below as it is.

apiVersion: apps/v1
kind: Deployment
metadata: {name: shop, namespace: broken}
spec:
  replicas: 3
  selector: {matchLabels: {app: shop}}
  template:
    metadata: {labels: {app: shop}}
    spec:
      containers:
        - name: shop
          image: nginx:1.27
          ports: [{containerPort: 8080}]
---
apiVersion: v1
kind: Service
metadata: {name: shop, namespace: broken}
spec:
  selector: {app: shop-svc}
  ports: [{port: 80, targetPort: 80, protocol: TCP}]

All 3 Pods come up, but the Service shop gets no endpoints at all. Do not touch the Deployment; fix only the Service so that the 3 Pods attach on port 8080.

There are two places to fix. The reason the endpoints are completely empty and the reason the port goes to the wrong place are in different fields. Put kubectl get svc shop -n broken -o yaml and the Pod labels side by side and look at them.

A workload that cannot leave Pending

First apply the manifest below as it is.

apiVersion: apps/v1
kind: Deployment
metadata: {name: report, namespace: broken}
spec:
  replicas: 2
  selector: {matchLabels: {app: report}}
  template:
    metadata: {labels: {app: report}}
    spec:
      containers:
        - name: report
          image: nginx:1.27
          resources:
            requests: {cpu: "16", memory: 64Mi}

The Pods cannot leave Pending. Leave the replica count and the image as they are, remove the cause, and get both Pods to Running.

The scheduler writes the reason in the Events of kubectl describe pod or in the Pod's PodScheduled condition. The amount a single node can give is in Allocatable of kubectl describe node.

Reducing overly broad permissions

First apply the manifest below as it is.

apiVersion: v1
kind: ServiceAccount
metadata: {name: ci, namespace: broken}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata: {name: ci-admin}
roleRef: {apiGroup: rbac.authorization.k8s.io, kind: ClusterRole, name: cluster-admin}
subjects:
  - {kind: ServiceAccount, name: ci, namespace: broken}

The audit flagged this binding. Do not delete the service account ci; only reduce its permissions. ci must be able to get, list, and watch pods and get, list, watch, create, and update deployments inside the namespace broken, and must not be able to do anything else anywhere in the cluster.

Deleting the cluster-admin binding is not the end. After you delete it, ci has no permissions, so you have to grant what it needs again at namespace scope. You check with kubectl auth can-i --as=system:serviceaccount:broken:ci.

A workload stuck on a quota

First apply the manifest below as it is.

apiVersion: v1
kind: ResourceQuota
metadata: {name: tight, namespace: quota-broken}
spec:
  hard:
    requests.cpu: 500m
    limits.memory: 512Mi
---
apiVersion: apps/v1
kind: Deployment
metadata: {name: worker, namespace: quota-broken}
spec:
  replicas: 3
  selector: {matchLabels: {app: worker}}
  template:
    metadata: {labels: {app: worker}}
    spec:
      containers:
        - name: worker
          image: nginx:1.27

Not a single Pod comes up. Do not touch the quota; fix only the workload side so that all 3 replicas are Running.

The reason admission rejected it is written as is in the ReplicaSet's status conditions (kubectl describe rs -n quota-broken). If a quota lists a resource, every Pod in that namespace must declare that resource.

A drain that never finishes

First apply the manifest below as it is.

apiVersion: apps/v1
kind: Deployment
metadata: {name: edge, namespace: broken}
spec:
  replicas: 2
  selector: {matchLabels: {app: edge}}
  template:
    metadata: {labels: {app: edge}}
    spec:
      nodeSelector: {kubernetes.io/hostname: lab-node-0}
      containers: [{name: edge, image: nginx:1.27}]
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata: {name: edge-pdb, namespace: broken}
spec:
  minAvailable: 2
  selector: {matchLabels: {app: edge}}

For maintenance you must drain the node lab-node-0. But the drain does not finish. Do not delete the PodDisruptionBudget; keep 2 replicas and finish the drain. After it finishes, lab-node-0 must have no edge Pods, and the two Pods must be Running on other nodes.

There are two things blocking it. One makes the Pods unable to be evicted, and the other leaves them nowhere to go even when evicted. Look at the ALLOWED DISRUPTIONS column of kubectl get pdb -n broken and the Pod's nodeSelector together.