CNPE for Kindergarteners
The whole exam in one picture: you run the theme park.
The dev teams are families who come to ride. They should never have to know how a rollercoaster is bolted together — they buy a ticket, they ride. Your job is to build the park so that's true.
- GitOps is the master plan pinned to the wall of the office. Argo CD is the crew that walks the park every few minutes and rebuilds anything that doesn't match the plan.
- A CRD is a new kind of ticket you invent — "birthday party room." A controller (Kyverno, an operator, Crossplane) is the staff who see the ticket and set the room up.
- Quotas are the wristbands: each family gets so many rides.
- Prometheus is the thermometer on every ride; an alert is the bell that rings when one gets too hot.
- Policy (Kyverno / Gatekeeper) is the height sign at the gate — "you must be this tall."
- CI/CD is the workshop that builds and inspects a ride before it opens.
The curriculum says it out loud: you are not tested on knowing every
tool. You're tested on knowing which kind of thing a task is, and reading
the manual for whatever tool is in front of you (kubectl explain,
--help). Every section below ends with "how to read the manual."
1. GitOps and Continuous Delivery (25%) — "the plan on the wall"
The crew that fixes drift (Argo CD Application)
Analogy: you pin a plan to the wall. The crew reads it, builds it, and keeps checking. If someone moves a bench, the crew moves it back.
What to look for: "deploy from repo X path Y," "keep it in sync," "prune," "self-heal."
kind: Application
metadata: { name: guestbook, namespace: argocd } # Applications live in the crew's office
spec:
source: { repoURL: https://…/repo.git, path: guestbook, targetRevision: HEAD }
destination: { server: https://kubernetes.default.svc, namespace: gitops }
syncPolicy:
automated: { prune: true, selfHeal: true } # rebuild what changed; remove what left the plan; undo tampering
syncOptions: [CreateNamespace=true]
Check it: Synced / Healthy. Then move a bench (k scale) and
watch the crew move it back within a few minutes.
Kindergarten rules:
prune: false= things removed from the plan stay in the park forever.CreateNamespace=trueis a sync option, not underautomated.- The crew's own paperwork (ApplicationSet CRD) is too big for normal apply:
kubectl apply --server-side.
The workshop (CI pipeline)
Analogy: build the ride, inspect it, only then wheel it out. If inspection fails, it never leaves the workshop.
Simplest version — a Job whose init containers are the stages, in order:
initContainers:
- { name: checkout, …clone into /workspace… }
- { name: scan, …exit 1 if unsafe… } # the inspector
containers:
- { name: publish, …only runs if scan passed… }
volumes: [{ name: workspace, emptyDir: {} }] # the workbench they all share
Plus safety for the workshop itself: runAsNonRoot, seccompProfile, and a
ServiceAccount with automountServiceAccountToken: false — the workshop
doesn't need keys to the park.
Read the manual: Tekton (k explain task.spec.steps), Argo Workflows
(k explain workflow.spec.templates). Same shape: stages, shared bench,
a failing stage stops the line.
Open the new ride to a few guests first (progressive delivery)
Plain Kubernetes: two Deployments, one signpost — see CKAD. Mesh: weights —
see ICA. Flagger / Argo Rollouts: a Canary / Rollout object that does the
"10%, wait, 50%, wait, 100%" for you.
2. Platform APIs and Self-Service (25%) — "invent a ticket, staff the booth"
A new kind of ticket (CRD)
Analogy: families used to email you for a party room. Now there's a
ticket: fill in owner and tier. The ticket refuses nonsense — you can't
write tier: platinum if only bronze/silver/gold exist.
kind: CustomResourceDefinition
metadata: { name: tenantspaces.platform.cnpe.io } # plural.group
spec:
group: platform.cnpe.io
scope: Cluster
names: { kind: TenantSpace, plural: tenantspaces, shortNames: [ts] }
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
required: [owner, tier]
properties:
owner: { type: string }
tier: { type: string, enum: [bronze, silver, gold] } # nonsense refused at the window
Check it: k apply --dry-run=server with tier: platinum is rejected.
The staff who set up the room (automation)
Analogy: a ticket comes in; without anyone being asked, a room appears with the right sign on the door, a wristband limit, and a locked back door.
Kyverno generate is the simplest staff to hire:
kind: ClusterPolicy
spec:
rules:
- name: gen-room
match: { any: [{ resources: { kinds: ["platform.cnpe.io/v1/TenantSpace"] } }] }
generate:
apiVersion: v1
kind: Namespace
name: "tenant-{{request.object.metadata.name}}"
synchronize: true # a living room, not a one-time copy
data: { metadata: { labels: { owner: "{{request.object.spec.owner}}" } } }
…and the same shape again for a ResourceQuota and a NetworkPolicy.
Kindergarten rule — the one that will get you: the staff need keys to the rooms they set up. Kyverno gets keys through labelled ClusterRoles, and it needs them for admission, background and reports:
labels:
rbac.kyverno.io/aggregate-to-admission-controller: "true"
rbac.kyverno.io/aggregate-to-background-controller: "true"
rbac.kyverno.io/aggregate-to-reports-controller: "true"
Give only background keys and the policy says Ready: True, throws no
error, and does absolutely nothing. Check for an UpdateRequest; none
means the staff never saw the ticket.
Read the manual: this is exactly what Crossplane does with different
words — CompositeResourceDefinition is the ticket, Composition is the
staff. k explain composition.spec. An operator is the same idea with a
Deployment doing the reconciling. (Note: kyverno.io/v1 ClusterPolicy is
deprecated; policies.kyverno.io is its successor.)
Use someone else's booth (an existing operator)
k get crd | grep thing; k api-resources --api-group=…
k explain Thing.spec --recursive | head -60
k get pods -A -l control-plane=controller-manager # is anyone actually staffing it?
A ticket with no staff behind the booth just sits there.
3. Observability and Operations (20%) — "thermometers and bells"
Put a thermometer on the ride (Prometheus scraping)
Analogy: Prometheus walks the park and reads every thermometer that has a sign saying "read me." The sign goes on the ride, not on the ride's blueprint.
template: # the POD template — not the Deployment
metadata:
annotations: { prometheus.io/scrape: "true", prometheus.io/port: "8080", prometheus.io/path: "/metrics" }
(Operator-flavoured parks use a ServiceMonitor instead — same idea.)
Check it — through Prometheus's own window, not by hoping:
curl -s --get --data-urlencode 'query=up{namespace="obs"}' http://prometheus/api/v1/query | jq
Kindergarten rule: "no data" is three different things. The sign is on
the wrong object (not discovered), the thermometer is broken (up == 0), or
the ride simply hasn't had a rider yet (no series yet). Check up first.
The bell (alerting rule)
groups:
- name: rides
rules:
- alert: RideDown
expr: up{job="kubernetes-pods"} == 0
for: 1m # ring only if it STAYS hot — no false alarms
labels: { severity: critical } # a LABEL, so the bell knows who to call
Check it: curl http://prometheus/api/v1/rules | jq '.data.groups[].rules[].name'
— editing the config file is not the same as the bell being wired.
Logs and traces (know the shape)
Logs: a collector on every stand → one big book. Traces: a coloured string
tied to each guest so you can follow their whole day — but only if every
ride passes the string along (traceparent header).
4. Platform Architecture (15%) — "wristbands"
Wristbands (ResourceQuota + LimitRange)
Analogy: a quota is how many rides the whole family gets. A LimitRange is "if a child doesn't say, they get 3 rides" — and "no child may take more than 10."
kind: ResourceQuota
spec: { hard: { requests.cpu: "2", requests.memory: 2Gi, limits.cpu: "4", limits.memory: 4Gi, pods: "10" } }
---
kind: LimitRange
spec:
limits:
- type: Container
default: { cpu: 200m, memory: 256Mi }
defaultRequest: { cpu: 100m, memory: 128Mi }
max: { cpu: "1", memory: 1Gi }
Check it: a pod that says nothing gets the defaults on the pod; a pod asking for 2 CPU is refused.
Kindergarten rule: a quota without a LimitRange refuses every child who forgot to ask — and the complaint lands on the ReplicaSet where nobody looks.
Front of the queue (PriorityClass)
value: 100000, globalDefault: false. Make a high one the default and
everyone's at the front — which is the same as nobody.
Are we wasting rides? (right-sizing)
k top pods (what they used) vs requests (what they reserved). The
gap is money. OpenCost turns the gap into dollars.
5. Security and Policy Enforcement (15%) — "the height sign at the gate"
You must be this tall (validate)
kind: ClusterPolicy
spec:
validationFailureAction: Enforce # Audit = just write it down; Enforce = turn them away
rules:
- name: need-a-team
match: { any: [{ resources: { kinds: [Pod], namespaceSelector: { matchLabels: { tenant: "true" } } } }] }
validate:
message: "every pod needs a team label"
pattern: { metadata: { labels: { team: "?*" } } }
Check it: a pod with no team label is refused with your message.
Hand everyone a helmet on the way in (mutate)
mutate:
patchStrategicMerge:
spec:
securityContext:
+(runAsNonRoot): true # +() = only if they didn't bring their own
Check it on the running pod — it's wearing a helmet you never gave it.
Kindergarten rule: Kyverno autogen — a rule you write for pods is quietly copied to Deployments too. So the Deployment wears the helmet as well. That's expected, not a mistake.
The inspector's report (compliance evidence)
k get policyreport -A — who's not tall enough. Plus: trivy for the
delivery boxes, kube-bench for the building, the audit log for the visitor
book. The task is usually "produce the report," not "fix everything."
Whispering and guest lists
mTLS and AuthorizationPolicy — see the ICA doc. Default-deny NetworkPolicy — see the CKA doc.
Kindergarten rules for the whole exam
- Which job is this? Ticket (CRD), staff (controller), plan (GitOps), height sign (policy), thermometer (observability), wristband (quota).
- Read the manual on the spot —
k explain X --recursive,tool --help. Nobody expects you to know the tool by heart. - Did the staff actually do it? One ticket → the room appeared. Move a bench → the crew moved it back. Ring the bell → it's in the rules API.
- Prove the "no" — a refused pod, a rejected ticket. An accepted one proves nothing.
- Staff need keys — a policy that's
Readybut silent has no RBAC.