Exam Prep · Common preparation · The assumed floor

The Kubernetes Baseline Both Exams Assume

Neither the CNPE nor the CNPA teaches you Kubernetes. Both simply assume it, the way a driving test assumes you know what a steering wheel does. Every curriculum item on either blueprint — reconciliation, GitOps, CRDs, admission policy, observability pipelines — is written on top of a Kubernetes vocabulary that is never defined and never examined directly. This page is the honest floor: the objects, the API machinery, the kubectl reflexes, the scheduling, RBAC, networking and storage you must already own before exam study is worth starting. It ends with a scored self-test, so you find your gaps here rather than at minute 74 of a 120-minute practical.

☺ Explain it like I’m 10

Imagine signing up for a class called “How to design a great skatepark.” The class talks about ramps, flow, safety rails and how to make skaters happy. What the class never covers is how to skate. It just assumes you can already roll, turn and stop without thinking about it. Kubernetes is the skating. Both of these exams are the skatepark-design class. If you’re still thinking hard about how to stand on the board, every single lesson will feel twice as difficult as it really is — so this page is a quick lap around the park to check that you can already roll.

🦫🤖Your hosts for this topic: Benny the Beaver & Recon the Robot — Benny drills the reflexes that save you minutes under the clock, and Recon explains the one idea (the reconciliation loop) that every other page on this site quietly stands on.

Why neither exam teaches Kubernetes

☺ Like you’re 10: Both tests are about building a helpful workshop for other people. They assume you already know how to use the tools in it.

Read either curriculum closely and you notice something: Kubernetes is everywhere in it and nowhere in it. The CNPA lists “Kubernetes Reconciliation Loop,” “APIs for Self-Service Platforms (CRDs),” “Infrastructure Provisioning with Kubernetes” and “Kubernetes Security Essentials” as competencies — but every one of those phrases starts from the assumption that you know what an API object is, what a controller does, and what a ServiceAccount is for. The CNPE goes further: it hands you a live cluster and a task list, and the clock does not pause while you remember whether a Deployment is apps/v1.

That is not an oversight. Both credentials sit above the Kubernetes line on the CNCF’s ladder — the Kubernetes line is KCNA, CKA, CKAD and CKS. Platform engineering is what you do with Kubernetes once you can already drive it. Our companion page Kubernetes as the Platform Substrate explains why the platform world settled on it as the universal control plane; this page is the narrower, more selfish question — are you personally ready?

Two exams, two different fluencies

The distinction matters more than anything else on this page, because it decides how you spend the next fortnight. The CNPA is knowledge-based and multiple-choice across six weighted domains (36 / 20 / 16 / 12 / 8 / 8). It tests recognition: shown four plausible sentences about what a ClusterRole can do, can you pick the true one? You never type anything. The CNPE is performance-based — 120 minutes, 15–20 hands-on tasks, 64% to pass — and it tests speed: not “do you know what a ClusterRole is,” but “can you create, bind and verify one in ninety seconds while four other tasks wait.” (As always, confirm current format, duration and cut score on the official CNCF and Linux Foundation pages before you register — those figures get revised.)

◆ Key idea

Same vocabulary, two entirely different thresholds. For CNPA, a topic is “known” when you can explain it in a sentence and spot a wrong statement about it. For CNPE, a topic is “known” when you can execute it without opening documentation and without a typo. Recognition is a reading standard; speed is a stopwatch standard. Never let yourself mark a CNPE topic complete using the CNPA test.

Where each section of this page matters most

Every section below carries a one-line ⚖ CNPA vs CNPE marker telling you which exam leans on it and how hard. Here is the whole map at a glance, so you can triage before you read:

Baseline areaCNPA — recognitionCNPE — speed
The object modelHigh. Named directly in “Declarative Resource Management”High. You author these objects all day
API machinery (spec/status, labels, owners)High. Underpins the reconciliation competencyCritical. Selector and apiVersion mistakes eat whole tasks
Reconciliation loopCritical. Explicitly examined, and reappears in three domainsHigh. It is GitOps, CRDs and operators
kubectl fluencyLow. No terminal, and it is closed-book anywayCritical. The single largest time lever in the exam
Scheduling & resourcesMedium. Concepts, QoS, why limits existHigh. Tasks specify requests, tolerations, spread
RBACMedium. Part of “Kubernetes Security Essentials”Critical. Multi-tenancy tasks are RBAC tasks
Networking & DNSMedium. Service types, default-deny as a conceptHigh. Mesh, ingress and policy tasks assume it
StorageLow–medium. Vocabulary onlyMedium. Shows up around stateful add-ons
⚠ The most expensive mistake in exam prep

Starting platform-engineering study while your Kubernetes is shaky feels productive and is not. You spend your reading time silently translating — “wait, what’s an owner reference again?” — and absorb about a third of what you should. Worse, on the CNPE you discover the gap under a clock, where every lookup costs a task. Spend a week on this page’s gaps first. It is the highest-return week in the whole plan, and how to study assumes you have done it.

The object model — the nouns you must never look up

☺ Like you’re 10: Kubernetes is a box of Lego pieces with names. You need to know each piece by sight, without turning it over to read the label.

⚖ CNPA vs CNPE — High for both. CNPA names it as “Declarative Resource Management”; CNPE assumes you can author any of these from a blank file.

Eight nouns carry almost everything. If any of them makes you hesitate, stop and fix that before reading further on this site.

Pod → ReplicaSet → Deployment

A Pod is the smallest deployable unit: one or more containers that share a network namespace (same IP, same localhost) and can share volumes. Pods are mortal — they are never repaired, only replaced. A ReplicaSet keeps exactly N copies of a Pod template alive. A Deployment owns ReplicaSets and gives you rollouts: change the template, and it creates a new ReplicaSet, scaling the new one up and the old one down according to maxSurge and maxUnavailable, keeping the old ones around for rollback.

You almost never create a ReplicaSet by hand — but you must know it exists, because it is what makes kubectl get rs the fastest way to see whether a rollout actually progressed. The sibling controllers matter too: StatefulSet (stable identity and stable storage per replica), DaemonSet (one Pod per node — how agents like log shippers and CNI plugins ship), Job and CronJob.

Service & Ingress — how traffic finds a Pod

Pod IPs change constantly, so nothing should ever address one directly. A Service is a stable virtual IP plus a DNS name in front of a label-selected set of Pods. An Ingress is an HTTP(S) routing rule — host and path to Service — that does nothing at all unless an ingress controller is installed to implement it. (Its successor, the Gateway API, is the direction of travel; see networking.)

ConfigMap, Secret and Namespace

A ConfigMap holds non-confidential key/value configuration; a Secret holds confidential data, base64-encoded in the API — encoding is not encryption, which is exactly why secrets management is its own discipline. Both mount two ways, and knowing which to reach for is a genuine exam reflex: as environment variables (simple, but the Pod must restart to see a change) or as a projected volume (files, and the kubelet updates them in place over time). A Namespace is the scope boundary for names, RBAC, quotas and most policy — the unit of tenancy that every multi-tenant platform pattern builds on.

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-cfg
  namespace: shop
data:
  LOG_LEVEL: debug
  feature.flags: |
    checkout_v2=true
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  namespace: shop
  labels:
    app.kubernetes.io/name: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web            # MUST match .spec.template.metadata.labels
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: web
          image: nginx:1.27
          ports:
            - containerPort: 8080
          envFrom:
            - configMapRef:
                name: app-cfg      # every key becomes an env var
          volumeMounts:
            - name: cfg
              mountPath: /etc/app  # every key becomes a file
      volumes:
        - name: cfg
          configMap:
            name: app-cfg
🦆 Dot’s-eye view

“Nobody told me the Deployment’s selector and the Pod template’s labels had to match, so my first Deployment reported zero ready replicas forever and I blamed the image. Now it’s the first thing I check when a rollout looks stuck — and I learned the hard way that spec.selector is immutable, so you can’t just fix it in place. You delete and recreate.”

The API machinery — how every object is shaped

☺ Like you’re 10: Every Kubernetes thing is filled in like the same form: what type am I, what do I want, and what’s actually true right now.

⚖ CNPA vs CNPE — High for CNPA (it is the substrate under the reconciliation competency), critical for CNPE (an apiVersion or selector slip silently costs you a whole task).

apiVersion, kind and the resource map

Every object declares apiVersion (a group and a version: apps/v1, networking.k8s.io/v1, rbac.authorization.k8s.io/v1) and a kind. The core group has an empty name, which is why Pods, Services, ConfigMaps and Secrets are just v1. Getting this wrong is the single most common source of “error validating data” under time pressure, and the fix is a command, not memory:

# every kind the cluster serves, with its group, short name and scope
kubectl api-resources
# just one answer: which apiVersion do I write for an Ingress?
kubectl api-resources | grep -i ingress
# what versions does a group serve (useful for CRDs)?
kubectl api-versions | grep networking

spec vs status — the two halves of every object

spec is desired state — what you (or a controller) asked for. status is observed state — what a controller reports is actually true. You write spec; controllers write status. That single split is the whole declarative model in one sentence, and it is why kubectl get deploy showing 1/3 READY is not a bug report — it is a controller honestly telling you it hasn’t finished. Two related fields are worth knowing by name: metadata.generation increments when you change spec, and a well-written controller copies it into status.observedGeneration when it has processed that change. When those two disagree, the controller hasn’t caught up yet.

Labels vs annotations, and selectors

Both are key/value maps on metadata, and confusing them is a classic multiple-choice trap. Labels are identifying and selectable — Services, ReplicaSets, NetworkPolicies and kubectl -l all query on them, so keep them short and structured. Annotations are non-identifying and not selectable — arbitrary metadata for humans and tools (ingress controller tuning, kubectl.kubernetes.io/last-applied-configuration, checksums that force a rollout). Selectors come in two shapes: matchLabels (equality) and matchExpressions (In, NotIn, Exists, DoesNotExist).

# equality selector
kubectl get pods -l app=web,tier=frontend
# set-based selector
kubectl get pods -l 'env in (staging,prod)'
# existence
kubectl get pods -l '!canary'
# label an existing object (overwrite needs the flag)
kubectl label pod web-0 tier=frontend --overwrite
◆ Key idea

One sentence that answers a whole family of exam questions: if something has to find the object, it must be a label; if something merely has to read the object, it can be an annotation. Services find Pods → labels. An ingress controller reads a timeout setting → annotation.

resourceVersion, UIDs and owner references

metadata.resourceVersion is an opaque string the API server uses for optimistic concurrency and for resuming watches. It is never a number to compare or reason about — its only jobs are “has this changed?” and “resume my watch from here.” If your update is rejected with a conflict, that is resourceVersion doing exactly its job: someone else wrote first, so re-read and retry.

metadata.ownerReferences is how the object graph is wired. A ReplicaSet carries an owner reference to its Deployment; a Pod carries one to its ReplicaSet. This drives cascading deletion by the garbage collector: delete the Deployment and its ReplicaSets and Pods go with it. You can change that with --cascade=orphan, and — importantly for operators and CRDs — you set owner references yourself on the resources your own controller creates, so cleanup is automatic.

# see the ownership chain for yourself
kubectl get rs -l app=web -o jsonpath='{.items[*].metadata.ownerReferences[*].kind}'
# delete a Deployment but keep the Pods running (rare, but know it exists)
kubectl delete deploy web --cascade=orphan

The controller idea everything else rests on

☺ Like you’re 10: A tireless helper compares “what you asked for” with “what’s really there,” fixes the difference, then does it again. Forever. That’s the whole trick.

⚖ CNPA vs CNPE — Critical for CNPA (explicitly examined, and it recurs in three of the six domains), high for CNPE (it is the mechanism behind GitOps, CRDs and every operator you touch).

If you take one idea from this page, take this one. A controller is a program that watches objects of some kind and runs a loop: observe the desired state in spec, observe the actual state in the world, diff them, act to close the gap, write what it saw into status, and repeat. Kubernetes ships dozens — the Deployment controller, the ReplicaSet controller, the node controller, the endpoints controller — and they all run this same loop.

Crucially, the loop is level-triggered, not edge-triggered. It does not react to a one-off event and hope; it repeatedly compares the current level against the target and drives toward it. That is why Kubernetes is self-healing at all, and why deleting a Pod under a Deployment simply produces a new Pod rather than an outage.

spec desired state 🤖 controller the world actual state observe observe act write status Built-in controllers · your CRDs & operators · Argo CD and Flux — all three are this one loop, at different altitudes

Why this one idea unlocks three exam domains

Understand this loop properly and three separate blueprint areas collapse into one. GitOps is the loop with Git as the source of spec. A CRD plus a controller — an operator — is the loop applied to a noun you invented, like Database or Environment. Crossplane is the loop pointed at a cloud provider’s API. Every self-service platform capability on this site is, mechanically, someone running this loop on your behalf. That is why CNPA lists it under Platform APIs and leans on it in Core Fundamentals, and why the 36% domain keeps circling back to it.

🦫 Benny’s workshop · 10 min

Prove the loop to yourself in one minute on any throwaway cluster. Run kubectl create deploy demo --image=nginx --replicas=3, then kubectl get pods -w in one pane and kubectl delete pod <one-of-them> in another. Watch the replacement appear before your deletion command has finished printing. Now scale the ReplicaSet directly with kubectl scale rs <name> --replicas=5 and watch the Deployment controller drag it back to 3. Nobody sent an event to fix that — a loop simply noticed.

kubectl fluency that buys you minutes

☺ Like you’re 10: There’s a slow way and a fast way to do everything here. Under a clock, only the fast way counts.

⚖ CNPA vs CNPE — Low for CNPA (there is no terminal, and it is closed-book anyway), critical for CNPE. This is the section where CNPE candidates win or lose the exam.

The CNPE gives you 15–20 tasks in 120 minutes: roughly six to eight minutes each, including reading. Hand-writing YAML from memory is not a strategy at that pace. Six habits carry the whole exam; the exhaustive version lives in the speed reference.

Generate, never type

--dry-run=client -o yaml is the highest-leverage flag in Kubernetes. It gives you a valid skeleton with the correct apiVersion, correct nesting and correct indentation — the three things that cost the most time by hand. Set the shorthand up before task one:

alias k=kubectl
export do='--dry-run=client -o yaml'
export now='--force --grace-period=0'
source <(kubectl completion bash)
complete -o default -F __start_kubectl k
k create deploy web --image=nginx:1.27 --replicas=3 $do > web.yaml
k create service clusterip web --tcp=80:8080 $do > svc.yaml
k create configmap app-cfg --from-literal=LOG_LEVEL=debug $do > cm.yaml
k create secret generic db --from-literal=password=s3cr3t $do > sec.yaml
k create role reader --verb=get,list,watch --resource=pods $do > role.yaml
k create sa builder $do > sa.yaml
k create ns team-a $do > ns.yaml

Read exactly one field — jsonpath and custom-columns

Scrolling through -o yaml to find one value is a minute you don’t have. Ask for the field:

# the image a Deployment is currently running
k get deploy web -o jsonpath='{.spec.template.spec.containers[0].image}'
# every pod name in the namespace, space separated
k get pods -o jsonpath='{.items[*].metadata.name}'
# node name per pod, as a table
k get pods -o custom-columns=NAME:.metadata.name,NODE:.spec.nodeName
# is this thing actually Ready? read the condition, not the colour
k get deploy web -o jsonpath='{.status.conditions[?(@.type=="Available")].status}'
# sorted output, which describe will never give you
k get events --sort-by=.lastTimestamp

describe vs logs vs events — the triage order

These three answer different questions and candidates routinely reach for the wrong one. kubectl describe answers “why won’t this thing start?” — it shows the resolved spec plus the object’s recent events (image pull failures, failed scheduling, failed mounts, probe failures). kubectl logs answers “why did the process inside fail?” — it needs a container that actually started, and for a crash loop you need --previous to see the run that died. kubectl get events answers “what happened in this namespace recently?” — broader, and note events are namespaced and short-lived (roughly an hour by default), so a silent event log is not proof of health.

# 1. the object's own story, including its events
k describe pod web-0
# 2. the dead container's last words
k logs web-0 -c app --previous
# 3. the neighbourhood, newest last
k get events -n shop --sort-by=.lastTimestamp
# narrow to one object
k get events -n shop --field-selector involvedObject.name=web-0

explain, diff and watch — the three most underused commands

kubectl explain is documentation that ships with the cluster, which matters because it works even when you don’t want to burn thirty seconds in a browser tab. kubectl diff shows exactly what an apply would change — the safest habit in the exam and in production. kubectl get -w turns “did it work?” into a live answer instead of a loop of re-running get.

# which field was it again?
k explain deployment.spec.strategy
k explain pod.spec.containers.resources --recursive
# what would this change?
k diff -f web.yaml
k diff -k overlays/prod
# watch it converge, don't poll it
k get pods -l app=web -w
k rollout status deploy/web --timeout=90s
⚠ Verify, don’t assume

The CNPE grades cluster state, not your intent. A task is complete when you have proved it: rollout status came back, auth can-i said yes, get -o jsonpath printed the value the task asked for. Budget the last twenty seconds of every task for that check. Applying a manifest and moving on is how people score 55% while believing they scored 80%.

Scheduling, resources & placement

☺ Like you’re 10: Every Pod says how much room it needs, and the cluster plays a matching game to find a machine with space. Some machines have “keep out” signs; some Pods carry a pass.

⚖ CNPA vs CNPE — Medium for CNPA (concepts and QoS), high for CNPE (tasks name requests, tolerations and spread explicitly).

requests vs limits — they do completely different jobs

This distinction is examined constantly and misunderstood constantly. A request is what the scheduler uses: it reserves that much capacity on a node and will not place the Pod where it doesn’t fit. A limit is what the kubelet and kernel enforce at runtime. And the two resources behave differently at the limit: exceeding a CPU limit gets you throttled (slow, alive), while exceeding a memory limit gets you OOM-killed (dead, restarted). That asymmetry is why experienced teams always set memory limits and are far more cautious about CPU limits.

The three QoS classes

The kubelet derives a Pod’s Quality-of-Service class from its requests and limits, and uses it to decide who dies first when a node runs out of memory:

ClassConditionEvicted…
GuaranteedEvery container sets both requests and limits, for both CPU and memory, and request equals limitLast
BurstableAt least one container has a request or limit, but the Pod doesn’t qualify as GuaranteedSecond — and first if it is over its request
BestEffortNo container sets any request or limit at allFirst. Always first
spec:
  containers:
    - name: app
      image: shop/api:2.1
      resources:
        requests:
          cpu: "250m"        # scheduler reserves a quarter core
          memory: "256Mi"
        limits:
          cpu: "500m"        # throttled above this
          memory: "256Mi"    # OOM-killed above this

Read that block carefully: memory request equals limit but CPU request does not, so this Pod is Burstable, not Guaranteed. That is exactly the kind of one-field distinction a multiple-choice question is built from.

Taints, tolerations and affinity

Three placement mechanisms, and knowing which one a task is describing is half the work. A taint is on the node and repels Pods (NoSchedule, PreferNoSchedule, or NoExecute, which also evicts Pods already running). A toleration is on the Pod and lets it ignore a matching taint. Note the direction of the logic: a toleration grants permission, it does not attract — a tolerating Pod may still land elsewhere. To attract, you need node affinity (requiredDuringSchedulingIgnoredDuringExecution for a hard rule, preferredDuringScheduling… for a soft one). Pod affinity/anti-affinity place Pods relative to other Pods across a topologyKey like kubernetes.io/hostname or a zone label, and topology spread constraints are the modern, more predictable way to say “spread these evenly across zones.”

spec:
  tolerations:
    - key: "workload"
      operator: "Equal"
      value: "gpu"
      effect: "NoSchedule"      # permission to land on tainted GPU nodes
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: accelerator
                operator: In
                values: ["nvidia-a100"]   # and this is what attracts it
  topologySpreadConstraints:
    - maxSkew: 1
      topologyKey: topology.kubernetes.io/zone
      whenUnsatisfiable: DoNotSchedule
      labelSelector:
        matchLabels:
          app: web

The deeper treatment — autoscaling, node pools, Karpenter, bin-packing economics — is in scaling & scheduling. For the baseline, you only need to read these blocks fluently and know which lever does what.

RBAC — who may do what, and how to prove it

☺ Like you’re 10: Four pieces: a list of allowed actions, and a note saying which person gets that list. Two of each — one for a single room, one for the whole building.

⚖ CNPA vs CNPE — Medium for CNPA (part of “Kubernetes Security Essentials”), critical for CNPE — every multi-tenancy and self-service task ends in an RBAC grant.

Role vs ClusterRole, and the binding that matters

A Role is a namespaced list of permitted verbs on resources. A ClusterRole is the same list but cluster-scoped — and you need one for three things: cluster-scoped resources (Nodes, PersistentVolumes, Namespaces themselves), non-resource URLs like /healthz, and any permission you want to grant across all namespaces. A RoleBinding grants a role inside one namespace; a ClusterRoleBinding grants it everywhere.

The combination candidates forget is the useful one: a RoleBinding may reference a ClusterRole. That grants the ClusterRole’s permissions but only within the binding’s namespace — which is exactly how you reuse the built-in view, edit and admin ClusterRoles per tenant without writing a Role for every team. Two more facts worth carrying in: RBAC is purely additive (there are no deny rules — the union of everything bound to you is what you can do), and roleRef is immutable once created, so a wrong binding is deleted and recreated, not patched.

Subjects — bind the ServiceAccount, not a person

A binding’s subjects can be a User, a Group or a ServiceAccount. On a platform, it is nearly always the ServiceAccount, because that is what your workloads and your controllers authenticate as. Its full identity string — system:serviceaccount:<namespace>:<name> — is worth memorising, because it is what you pass to --as when you verify.

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: config-reader
  namespace: shop
rules:
  - apiGroups: [""]                        # "" is the core group
    resources: ["configmaps", "secrets"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: config-reader-binding
  namespace: shop
subjects:
  - kind: ServiceAccount
    name: builder
    namespace: shop
roleRef:
  kind: Role
  name: config-reader
  apiGroup: rbac.authorization.k8s.io

kubectl auth can-i — the five-second proof

Never hand in an RBAC task unverified. Impersonation answers the question directly, and it is the difference between “I applied a Role” and “the grant works”:

# as yourself
k auth can-i create deployments -n shop
# as the service account you just bound — this is the real test
k auth can-i list secrets -n shop --as=system:serviceaccount:shop:builder
# the whole permission surface for that identity
k auth can-i --list -n shop --as=system:serviceaccount:shop:builder
# who is the current kubeconfig, anyway?
k auth whoami
◆ Key idea

Least privilege is a verb list, not a vibe. When a task says “the pipeline should be able to read config but not secrets,” you are being asked for exactly one thing: which strings go in verbs and resources. Write the Role, bind the ServiceAccount, then prove it with can-i twice — once for the thing that should work, once for the thing that must not.

Networking & DNS — the paths traffic takes

☺ Like you’re 10: Pods get new addresses all the time, so we give groups of them a permanent nickname. Then we decide who’s allowed to phone whom.

⚖ CNPA vs CNPE — Medium for CNPA (Service types and default-deny as ideas), high for CNPE — mesh, ingress and policy tasks all assume you can already reason about the path.

The Service types, as a ladder

The three main types are nested, not alternatives — each is a superset of the one before, which is the cleanest way to remember them:

TypeWhat you getReachable from
ClusterIPA stable virtual IP and DNS name (the default)Inside the cluster only
NodePortAll of ClusterIP, plus a port (default range 30000–32767) opened on every nodeAnything that can reach a node’s IP
LoadBalancerAll of NodePort, plus an external load balancer provisioned by the cloud controllerThe internet, or your VPC
ExternalNameJust a DNS CNAME to an outside host — no proxying, no selectorn/a — a naming trick
Headless (clusterIP: None)No virtual IP; DNS returns the Pod IPs directlyInside — used by StatefulSets and client-side load balancing

DNS names you should be able to write blind

Cluster DNS is formulaic, and being able to type a name without checking is a real time saver:

web                                  # same namespace, short form
web.shop                             # cross-namespace
web.shop.svc                         # explicit service segment
web.shop.svc.cluster.local           # fully qualified (default cluster domain)

web-0.web.shop.svc.cluster.local     # a StatefulSet pod via its headless Service:
                                     # pod-name . service . namespace . svc . cluster.local
# resolve a Service from inside the cluster, with a throwaway pod
k run dnstest --rm -it --restart=Never --image=busybox:1.36 -- nslookup web.shop
# does the Service actually have endpoints? (empty = selector mismatch)
k get endpointslices -l kubernetes.io/service-name=web -n shop
⚠ “The Service isn’t working”

Nine times out of ten it is not the Service. It is a selector that matches nothing, or a targetPort that doesn’t match the container’s actual port. Check for endpoints first — an EndpointSlice with no addresses tells you instantly which of the two it is, and saves you from debugging DNS that was never broken.

NetworkPolicy and the default-deny flip

Two facts decide most NetworkPolicy questions. First, the default is allow-all: with no policies, every Pod can talk to every Pod, in every namespace. Second, policies are additive and there are no deny rules. A Pod becomes “isolated” for a direction the moment any policy selects it for that direction — and from then on only the union of matching allow rules gets through. So you don’t write a deny; you write an empty policy that selects everything, which isolates everything, and then you allow back what you need.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: shop
spec:
  podSelector: {}            # every pod in this namespace
  policyTypes: ["Ingress", "Egress"]
# no ingress or egress rules at all = nothing is allowed
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-web-to-api
  namespace: shop
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes: ["Ingress"]
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: web
      ports:
        - protocol: TCP
          port: 8080

One trap worth naming: a NetworkPolicy is an API object, and the API server will happily accept it whether or not anything enforces it. Enforcement is the CNI plugin’s job — Cilium, Calico and friends. On a cluster whose CNI doesn’t implement policy, your default-deny is a very convincing-looking no-op. The full story is in networking & service mesh.

Storage — claims, volumes and classes

☺ Like you’re 10: The Pod asks for “a locker, this big, that I can write to.” Something goes and finds or builds one, then hands over the key.

⚖ CNPA vs CNPE — Low-to-medium for CNPA (vocabulary), medium for CNPE — it appears around stateful add-ons like Prometheus and databases rather than as a topic in its own right.

The claim/volume handshake

Three nouns, one handshake. A PersistentVolume (PV) is a piece of storage in the cluster — cluster-scoped, and either pre-created by an admin or produced on demand. A PersistentVolumeClaim (PVC) is a namespaced request for storage of a given size and access mode; a Pod mounts the PVC, never the PV. A StorageClass names a provisioner and its parameters, so a PVC that references one gets its PV created dynamically. That is the modern default: you almost never hand-write PVs any more, you write a PVC and let a CSI driver do the rest.

One field is worth knowing by name because it explains a confusing behaviour: a StorageClass’s volumeBindingMode. With Immediate, a volume is provisioned as soon as the PVC is created — which can pin it to the wrong zone. With WaitForFirstConsumer, provisioning waits until a Pod actually needs it, so the volume lands where the Pod is scheduled. A PVC sitting in Pending with no Pod is usually this, not a fault.

Access modes and reclaim policy

Access modeShortMeans
ReadWriteOnceRWORead-write by a single node — note: node, not Pod. Several Pods on that same node can share it
ReadOnlyManyROXRead-only by many nodes
ReadWriteManyRWXRead-write by many nodes — needs a filesystem that supports it (NFS, CephFS); most block storage does not
ReadWriteOncePodRWOPRead-write by exactly one Pod — the strict one, for single-writer databases

The PV’s persistentVolumeReclaimPolicy decides what happens when the claim goes away: Delete (the default for most dynamic provisioners — the volume and its data go too) or Retain (the PV survives, holding the data, and needs manual cleanup). Getting that backwards is how people lose data, so on anything that matters, set Retain deliberately.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data
  namespace: shop
spec:
  accessModes: ["ReadWriteOnce"]
  storageClassName: standard
  resources:
    requests:
      storage: 10Gi

Finally, StatefulSets don’t use a PVC — they use volumeClaimTemplates, minting one PVC per replica (data-web-0, data-web-1, …) so each Pod keeps its own disk across restarts and rescheduling. And by default those PVCs outlive the StatefulSet when you delete it, which is a safety feature that surprises people the first time. More depth in storage & state.

Score yourself — the baseline self-test

☺ Like you’re 10: Tick the things you can genuinely do. Be honest — the boxes you leave empty are the ones that would have cost you marks.

Two tiers, deliberately. Tier 1 is the recognition floor — if you can’t tick these, CNPA study will be uphill and CNPE study is premature. Tier 2 is the speed floor, and it is CNPE-specific: each item has an implied stopwatch, because on the exam everything does. Read each item as a literal instruction and only tick it if you could do it right now, without documentation.

Tier 1 — recognition (the CNPA floor)

Tier 2 — speed (the CNPE floor)

What your score means

Count your unticked boxes in each tier separately — the gaps are the signal, not the total.

UntickedTier 1 (recognition)Tier 2 (speed)
0–2You are ready. Start CNPA or CNPE study todayYou are ready for timed CNPE practice — go to the task bank
3–6One focused week. Re-read the matching sections here, then the substrate page, and re-take this testTwo weeks of reps. Work the speed reference and know it cold daily until the commands are automatic
7+Do Kubernetes properly first — KCNA for concepts, CKA/CKAD for hands. Platform study on this foundation will not stickDon’t book the CNPE yet. Build a cluster, run the lab track end to end, then re-test
◆ Key idea

The two tiers can legitimately diverge, and that tells you something useful. Strong Tier 1, weak Tier 2 is the classic architect profile: sit the CNPA now, and spend your CNPE runway entirely on keyboard reps. Strong Tier 2, weak Tier 1 is the classic operator profile: you can do it but can’t always explain it — which is fine for CNPE and dangerous for CNPA, where every question is a sentence about a concept.

Closing the gaps you found

☺ Like you’re 10: Now you know which bits are wobbly. Fix those bits — don’t start over from the beginning.

Resist the urge to re-read everything. You now have a specific list, and specific lists are fixed with specific drills.

For recognition gaps

Explain the item out loud, from memory, in one paragraph — then check it against the section above and mark what you got thin. That retrieval loop is the whole method, and it is exactly what how to study builds a schedule around. Kubernetes as the platform substrate is the right second read for the API-machinery and control-loop items; CNPA Core Fundamentals shows how the same ideas are actually examined. The glossary and flashcards are for the vocabulary items you keep dropping.

For speed gaps

Speed is only ever built one way: repetition against a clock. Take the specific unticked item, do it ten times on a throwaway kind cluster, and time the tenth. Then keep going with real work — the lab track puts every one of these in service of something you are actually building, which is far more durable than drilling in the abstract. The speed reference is your lookup for the commands themselves, know it cold is the set of manifests you should be able to type blind, and the troubleshooting playbook turns the describe/logs/events reflex into a decision tree.

Then, and only then, start the exam material

Once this page’s boxes are ticked, everything downstream gets easier — because GitOps is now “the loop, with Git as spec,” CRDs and operators are “the loop, with a noun I invented,” and policy is “admission control on objects I can already read.” That is the whole return on this week: not new knowledge, but the disappearance of the constant, quiet translation cost that makes platform study feel harder than it is.

🎬 At the Platform Guild
🦊

Foxy: I booked the CNPE for three weeks’ time. I’ve read all the GitOps pages twice, so I reckon I’m about ready.

🦫

Benny: Great. Quick one — generate a Deployment manifest for me. Fifteen seconds, no browser.

🦊

Foxy: Sure, so it’s apiVersion: apps/v1, then metadata, then… hang on, does selector go before or after replicas? Let me just check the docs —

🦫

Benny: That’s the exam, Foxy. Twenty tasks, and you just spent your first minute on the shape of a file. kubectl create deploy web --image=nginx $do. That’s it. That’s the whole answer.

🤖

Recon: BEEP. And note what Foxy did understand: reconciliation, drift, prune. His concepts are fine. It is his fingers that are three weeks behind.

👺

Gizmo: Or — hear me out — sit the CNPA instead, it’s multiple choice, you can basically guess. Same badge energy, half the work. 😏

🐢

Timmy: They’re not the same badge, Gizmo, and it’s closed-book. Guessing is a strategy for people who enjoy paying twice.

🦆

Dot: Honestly the self-test was the useful bit for me. I ticked every concept box and about four of the speed ones. Turns out I understand Kubernetes and can’t actually drive it.

🦫

Benny: That’s not a failure, Dot — that’s a study plan. You just found it in twenty minutes instead of in minute seventy-four of the exam.

🐢 Timmy’s checkpoint

1. Why does neither exam teach Kubernetes, and what does that mean for how you plan? 2. What is the difference between a request and a limit — and what happens when each is exceeded? 3. Name the three QoS classes in eviction order. 4. When do you need a ClusterRole instead of a Role, and what does a RoleBinding that references a ClusterRole grant? 5. Write the fully-qualified DNS name for Service api in namespace shop. 6. How do you express “deny all traffic” in a NetworkPolicy, given there is no deny rule? 7. Which single kubectl flag saves you the most time in a hands-on exam, and why? 8. Which sections of this page matter most for CNPA, and which for CNPE?

Check your answers
  1. Both sit above the Kubernetes line on the CNCF ladder — Kubernetes itself is covered by KCNA, CKA, CKAD and CKS. So the blueprints assume the vocabulary and never define it, which means your Kubernetes gaps are invisible in the syllabus and very visible in your score. Fix them before exam study, not during.
  2. A request is used by the scheduler to reserve capacity and pick a node; a limit is enforced at runtime by the kubelet and kernel. Exceeding a CPU limit means throttling; exceeding a memory limit means the container is OOM-killed and restarted.
  3. BestEffort (no requests or limits) is evicted first, then Burstable, and Guaranteed (every container sets requests equal to limits for both CPU and memory) last.
  4. You need a ClusterRole for cluster-scoped resources (Nodes, PersistentVolumes, Namespaces), for non-resource URLs such as /healthz, and to grant a permission across all namespaces. A RoleBinding referencing a ClusterRole grants that ClusterRole’s permissions only within the RoleBinding’s namespace — the standard way to reuse the built-in view/edit/admin roles per tenant.
  5. api.shop.svc.cluster.local (with the default cluster domain; api.shop is enough in practice).
  6. There is no deny rule, so you isolate and then allow back: a policy with podSelector: {} and policyTypes: ["Ingress","Egress"] but no rules selects every Pod, isolates both directions, and permits nothing. Subsequent policies are additive allows. Remember enforcement is the CNI’s job, not the API server’s.
  7. --dry-run=client -o yaml. It generates a valid skeleton with the right apiVersion, nesting and indentation, so you edit two lines instead of authoring a file — the three things that cost the most time by hand.
  8. For CNPA: the object model, the API machinery and above all the reconciliation loop, which recurs across three domains. For CNPE: kubectl fluency (the largest single time lever), RBAC, and verification habits. Storage is the lightest for both; kubectl speed is worth almost nothing on CNPA and almost everything on CNPE.