Know It Cold — the manifests you must write from memory
The CNPE is not a broadly open-book exam. While the clock runs you get kubernetes.io/docs, kubernetes.io/blog, whatever the exam links from its own Quick Reference box, and the documentation installed locally on the machine — and nothing else. Argo CD, Flux, Tekton, Argo Rollouts, Crossplane, Prometheus, OpenTelemetry, Kyverno, Gatekeeper, Istio, Helm and Backstage are all squarely inside the exam domains, and not one of their doc sites is on that list. So every manifest on this page has to come out of your head. That is the whole premise: this is the half of the exam kubernetes.io cannot carry for you. Drill it the way it will be tested — blank page, empty file, no browser, no notes — and then check yourself against the real docs while you still can. Re-reading this page feels like learning and is not; typing these skeletons from nothing is. If you can only do one exercise between now and exam day, make it the one at the bottom of this page.
Imagine a cooking test where you may bring one big book about ingredients and ovens — but none of the little recipe cards for the fancy dishes. The fancy dishes are still on the menu. So the recipes have to live in your head, because nobody is handing them to you on the day. This page is those recipes. Don’t read them like a story. Close the page, write one out on a blank sheet, then come back and see what you got wrong.
Rollout before you’ve found the tab; Mira knows what each of these APIs is actually for, so the shapes stick as meaning rather than as letters. Remy drills you; Mira makes sure you understand what you’re drilling.During the CNPE the only permitted resources are: ① https://kubernetes.io/docs including its translations, ② https://kubernetes.io/blog/, ③ task-specific documentation linked from the exam’s own “Quick Reference” box, and ④ documentation installed locally on the exam machine — man pages, /usr/share, distribution packages.
The doc sites for Argo CD, Flux, Tekton, Argo Rollouts, Crossplane, Prometheus, OpenTelemetry, Kyverno, Gatekeeper, Istio, Helm and Backstage are NOT available to you, even though every one of them sits inside a named exam domain. You may use the search box on a permitted site, but you may not open external search results and you may not follow outbound links off a permitted site — a kubernetes.io page linking to helm.sh does not make helm.sh permitted.
Allowlists get revised, and this page is not an authority. Verify the current list yourself on the official Linux Foundation Resources Allowed page and the Important Instructions: CNPE page in the days before your exam, and let those override anything written here. The Docs Map covers the rules, the links and the kubernetes.io half in full.
How to use this page
☺ Like you’re 10: You don’t need every field. You need the shape: the right first two lines, and the two or three fields that actually mean something.
You do not need to memorise every optional field of every CRD — nobody does that, and the exam does not reward it. You need the skeleton: the correct apiVersion and kind, the two or three fields that carry the meaning, and the nesting that holds them together. Get that much onto the page and the cluster itself will fill in the rest. Everything below is written to that standard: enough to be correct and applyable, with the annotations marking the exact spots where marks are usually lost.
Read each block once for shape, then close the page. Open an empty file. Type it. Compare. The gap you find is your actual study list — and it will be shorter and more specific than you fear. Anything you get wrong twice belongs on a flashcard, not on a re-read.
The three routes to a spec on exam day
There are exactly three, and you control only the first. Memory — this page. The Quick Reference box — if a task involves a tool whose docs are closed, the exam may link the specific page you need directly from the task; it is a permitted resource and it is targeted, so make reading it step zero of every task. kubectl explain — once a CRD is installed in the cluster its full OpenAPI schema is in the cluster, and the docs come to you with no browser at all.
kubectl explain <kind> --recursive is your safety net for every resource on this page, because installed CRDs register their full schema in the cluster. kubectl api-resources | grep -i rollout gives you the kind and its apiVersion; kubectl explain rollout.spec.strategy.canary --recursive gives you the field tree, version-correct by construction. Also check whether an example already exists in the cluster — kubectl get application -n argocd -o yaml on an existing app is a working reference manifest you were handed for free. Reading the cluster is always permitted. More of these in the command reference.
Delivery — Argo CD, Flux, Tekton and Argo Rollouts
☺ Like you’re 10: These are the robots that take what’s written in Git and make it real. Four tools, six shapes.
This is the exam’s largest domain and the one whose specs you are most likely to type unaided. Background reading lives in GitOps Workflows; what follows is the memory layer on top of it.
Argo CD — Application
The Argo CD Application is the single most likely thing you will write without a reference. Know source (repoURL, path, targetRevision), destination (server or name, plus namespace), project, and the syncPolicy.automated block — remembering that prune and selfHeal both default to false, so “auto-sync” alone does not delete removed resources or revert drift. CreateNamespace=true lives in syncOptions, not in automated.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: web
namespace: argocd # Applications live in the Argo CD namespace
spec:
project: default
source:
repoURL: https://github.com/org/repo.git
targetRevision: main # branch, tag or commit SHA
path: envs/prod
destination:
server: https://kubernetes.default.svc
namespace: prod
syncPolicy:
automated:
prune: true # default false — delete removed resources
selfHeal: true # default false — revert manual drift
syncOptions:
- CreateNamespace=true # NOT under automated:Argo CD — ApplicationSet
For ApplicationSet, memorise the wrapper and at least the List and Git generators; Matrix and Cluster are worth recognising. The shape is always spec.generators[] plus a spec.template that is an Application with {{ }} placeholders filled from the generator.
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata: {name: tenants, namespace: argocd}
spec:
generators:
- git: # one App per directory in the repo
repoURL: https://github.com/org/repo.git
revision: main
directories:
- path: tenants/*
# - list: {elements: [{cluster: dev, url: https://dev.example}]}
# - clusters: {} # one App per registered cluster
# - matrix: {generators: [ ... , ... ]} # cross-product of two
# goTemplate: true # then placeholders become '{{.path.basename}}'
template:
metadata: {name: '{{path.basename}}'}
spec:
project: default
source: {repoURL: https://github.com/org/repo.git, targetRevision: main, path: '{{path}}'}
destination: {server: https://kubernetes.default.svc, namespace: '{{path.basename}}'}Flux — GitRepository + Kustomization
For Flux, the pairing to know is GitRepository (the source) plus Kustomization (the applier), and the fact that they live in different API groups — source.toolkit.fluxcd.io and kustomize.toolkit.fluxcd.io. Both need an interval. The Kustomization’s prune and wait are the fields tasks care about.
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata: {name: app, namespace: flux-system}
spec:
interval: 1m
url: https://github.com/org/repo.git
ref: {branch: main} # or tag: / semver: / commit:
# secretRef: {name: git-creds} # private repos
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata: {name: app, namespace: flux-system}
spec:
interval: 10m
sourceRef: {kind: GitRepository, name: app}
path: ./envs/prod
prune: true # garbage-collect removed resources
wait: true # block until resources are Ready
targetNamespace: prod
# dependsOn: [{name: infra}]Tekton — Task, Pipeline, PipelineRun
For Tekton, know the three-object chain: a Task holds steps; a Pipeline references Tasks via taskRef and orders them with runAfter; a PipelineRun executes it and is where workspaces get bound to real storage. Workspaces are the classic stumble — they must be declared in the Task, declared and mapped in the Pipeline, and bound in the PipelineRun. Miss any of the three and it fails.
apiVersion: tekton.dev/v1
kind: Task
metadata: {name: build}
spec:
workspaces:
- name: source # ① declared in the Task
params:
- name: image
type: string
steps:
- name: build
image: gcr.io/kaniko-project/executor:latest
script: |
echo building $(params.image) from $(workspaces.source.path)
---
apiVersion: tekton.dev/v1
kind: Pipeline
metadata: {name: ci}
spec:
workspaces:
- name: shared # ② declared in the Pipeline
params:
- name: image
type: string
tasks:
- name: fetch
taskRef: {name: git-clone}
params:
- name: url # git-clone's url param has no default
value: https://github.com/org/repo.git
workspaces:
- name: output # name: = the workspace the TASK declares
workspace: shared # workspace: = the Pipeline's own name
- name: build
runAfter: [fetch] # ordering
taskRef: {name: build}
params:
- name: image
value: $(params.image)
workspaces:
- name: source
workspace: shared
---
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata: {generateName: ci-run-}
spec:
pipelineRef: {name: ci}
params:
- name: image
value: registry.example/app:v1
workspaces:
- name: shared # ③ BOUND to real storage here
persistentVolumeClaim: {claimName: ci-cache}
# or: volumeClaimTemplate: / emptyDir: {}Argo Rollouts — Rollout + AnalysisTemplate
For Argo Rollouts, the key mental model is that a Rollout replaces your Deployment — same spec.template, different kind — and that canary steps is an ordered list where a pause with no duration waits indefinitely for a manual promote. An AnalysisTemplate gates the rollout on a metric; the successCondition is what decides pass or fail. Do not confuse this with Flagger, which keeps your Deployment and wraps a Canary CR around it — copying the wrong tool’s example applies cleanly and does nothing useful.
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata: {name: web}
spec:
replicas: 5
selector: {matchLabels: {app: web}}
template: # identical to a Deployment's pod template
metadata:
labels: {app: web} # must match spec.selector.matchLabels
spec:
containers:
- name: web
image: registry.example/app:v1
strategy:
canary:
canaryService: web-canary
stableService: web-stable
steps:
- setWeight: 20
- pause: {duration: 60s} # bare `- pause: {}` waits for manual promote
- analysis:
templates:
- templateName: success-rate
- setWeight: 50
- pause: {duration: 60s}
---
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata: {name: success-rate}
spec:
metrics:
- name: success-rate
interval: 1m
count: 5
successCondition: result[0] >= 0.95
failureLimit: 2
provider:
prometheus:
address: http://prometheus.monitoring:9090
query: |
sum(rate(http_requests_total{status!~"5.."}[2m]))
/ sum(rate(http_requests_total[2m]))Platform APIs — Crossplane & CRDs
☺ Like you’re 10: Crossplane has two YAMLs that look nearly the same — the definition the platform team writes, and the claim the developer writes. Check which one the task wants.
The self-service domain, and the one candidates most often garble under pressure. Concepts in Platform APIs & CRDs and Self-Service; the shapes here.
Crossplane — XRD, Composition, and the claim
Crossplane’s three-object story: the XRD (CompositeResourceDefinition) defines the developer-facing API and its claimNames; the Composition says what real infrastructure that API maps to; the claim is the small namespaced YAML a developer writes, using the claim kind, not the X-prefixed composite kind. Know which of the three a task is asking for — that alone is half the marks. The XRD’s metadata.name must be plural.group, and referenceable: true on the served version is what lets a Composition bind to it. One version caveat worth carrying in: Crossplane has been moving composition logic out of the inline spec.resources patch-and-transform block below and into composition functions — spec.mode: Pipeline with a pipeline of functionRef steps. Both shapes appear in the wild, so let the cluster settle it: kubectl explain composition.spec tells you which fields the installed version actually serves, and kubectl api-resources tells you whether the claim kind exists.
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata: {name: xpostgresqlinstances.platform.example.org} # plural.group
spec:
group: platform.example.org
names: {kind: XPostgreSQLInstance, plural: xpostgresqlinstances}
claimNames: {kind: PostgreSQLInstance, plural: postgresqlinstances}
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
size: {type: string, enum: [small, large]}
required: [size]
---
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata: {name: postgres-aws}
spec:
compositeTypeRef: {apiVersion: platform.example.org/v1alpha1, kind: XPostgreSQLInstance}
# inline patch-and-transform; newer Crossplane prefers
# `mode: Pipeline` + a pipeline of functionRef steps
resources:
- name: rds
base:
apiVersion: rds.aws.upbound.io/v1beta1
kind: Instance
spec: {forProvider: {region: eu-west-1}}
patches:
- fromFieldPath: spec.size
toFieldPath: spec.forProvider.instanceClass
transforms:
- type: map
map: {small: db.t3.micro, large: db.m5.large}
---
# What the DEVELOPER writes — namespaced, uses the CLAIM kind:
apiVersion: platform.example.org/v1alpha1
kind: PostgreSQLInstance
metadata: {name: orders-db, namespace: team-a}
spec:
size: smallThe CRD — the one platform API whose docs you do get
Worth noting the exception on this page: a plain CustomResourceDefinition is Kubernetes-native, so it is documented on kubernetes.io under Tasks → Extend Kubernetes, and you may open that page during the exam. Even so, learn the shape — versions[].schema.openAPIV3Schema, subresources: {status: {}, scale: {}}, additionalPrinterColumns, scope and names.shortNames — because retrieving it still costs you thirty seconds you may not have, and because the XRD above is essentially a CRD wearing a Crossplane hat.
Observability — Prometheus & OpenTelemetry
☺ Like you’re 10: Two shapes: one that says “go collect numbers from this app,” and one that says “here’s where the numbers should flow.”
The collection half of observability tasks is nearly always a copyable manifest — except that here you have nothing to copy from. Concepts in Observability.
Prometheus — ServiceMonitor and PrometheusRule
The ServiceMonitor trips people on two details: endpoints[].port takes the named port from the Service (not a number), and the Prometheus instance only picks up ServiceMonitors matching its own serviceMonitorSelector — usually a release label. A PrometheusRule is the alerting-rules file wrapped in a CRD; the for duration and labels.severity are what alerting tasks check.
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: web
namespace: monitoring
labels: {release: kube-prometheus-stack} # must match serviceMonitorSelector
spec:
selector: {matchLabels: {app: web}} # selects the SERVICE, not pods
namespaceSelector: {matchNames: [prod]}
endpoints:
- port: metrics # the Service's NAMED port
path: /metrics
interval: 30s
---
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: web-alerts
namespace: monitoring
labels: {release: kube-prometheus-stack}
spec:
groups:
- name: web
rules:
- alert: HighErrorRate
expr: sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m])) > 0.05
for: 10m
labels: {severity: critical}
annotations: {summary: "Error rate above 5% for 10m"}OpenTelemetry — the Collector config
For the OpenTelemetry Collector, memorise the four-block config and — the part everyone forgets — that declaring receivers, processors and exporters does nothing until service.pipelines wires them together. A config with a perfect receivers block and no pipeline is valid YAML that moves zero telemetry. Know the OTLP ports cold: 4317 for gRPC, 4318 for HTTP.
receivers:
otlp:
protocols:
grpc: {endpoint: 0.0.0.0:4317} # OTLP/gRPC
http: {endpoint: 0.0.0.0:4318} # OTLP/HTTP
processors:
batch: {}
memory_limiter: {check_interval: 1s, limit_percentage: 80, spike_limit_percentage: 25}
exporters:
otlp: {endpoint: jaeger-collector:4317, tls: {insecure: true}}
debug: {verbosity: detailed}
service:
pipelines: # ← without this, nothing flows
traces:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [otlp]
metrics:
receivers: [otlp]
processors: [batch]
exporters: [debug]Security — Kyverno, Gatekeeper & Istio
☺ Like you’re 10: Rules that say no, and encryption between services. Both policy engines default to not blocking, which is the trap.
Both major policy engines ship enormous libraries of ready-made policies, and neither library is reachable during the exam — so the shapes have to be yours. Concepts in Security & Policy Enforcement.
Kyverno — ClusterPolicy
For Kyverno, know the ClusterPolicy skeleton and the one field that decides whether it does anything: validationFailureAction must be Enforce to block (it defaults to Audit, which logs the violation and lets the bad pod through). Know both rule shapes — validate with a pattern, and mutate with patchStrategicMerge. Newer Kyverno releases moved this switch down onto the rule as spec.rules[].validate.failureAction and treat the policy-level field as the legacy spelling, so confirm which one the installed version wants with kubectl explain clusterpolicy.spec.rules.validate before you commit to a shape.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata: {name: require-limits}
spec:
validationFailureAction: Enforce # default Audit = logs only, blocks nothing
# newer Kyverno: per-rule `validate: {failureAction: Enforce}` instead
background: true
rules:
- name: check-limits
match:
any:
- resources: {kinds: [Pod]}
validate:
message: "CPU and memory limits are required."
pattern:
spec:
containers:
- resources:
limits:
memory: "?*" # ?* = any non-empty value
cpu: "?*"
- name: add-team-label # mutate rule in the same policy
match:
any:
- resources: {kinds: [Pod]}
mutate:
patchStrategicMerge:
metadata:
labels:
team: "{{request.object.metadata.namespace}}"Gatekeeper — ConstraintTemplate + Constraint
For Gatekeeper, the pair is always ConstraintTemplate (the reusable Rego + parameter schema) plus a Constraint whose kind is the template’s crd.spec.names.kind. Two details worth burning in: the Rego rule head inside a ConstraintTemplate is violation[{"msg": msg}], not deny (that is standalone OPA); and enforcementAction defaults to deny but is frequently set to dryrun in examples you half-remember. Rego has since grown a stricter dialect in which the same head is written violation contains {"msg": msg} if { … }; if a cluster you are handed rejects the classic form, kubectl get constrainttemplate -o yaml on anything already installed shows you which dialect that Gatekeeper build expects.
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata: {name: k8srequiredlabels}
spec:
crd:
spec:
names: {kind: K8sRequiredLabels} # ← becomes the Constraint's kind
validation:
openAPIV3Schema:
type: object
properties:
labels: {type: array, items: {type: string}}
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg}] { # NOT deny[...]
required := input.parameters.labels[_]
not input.review.object.metadata.labels[required]
msg := sprintf("missing required label: %v", [required])
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels # the template's names.kind
metadata: {name: ns-must-have-owner}
spec:
enforcementAction: deny # dryrun = logs only
match:
kinds:
- apiGroups: [""]
kinds: [Namespace]
parameters:
labels: [owner]Istio — PeerAuthentication and AuthorizationPolicy
For Istio, two resources cover most of the security domain. PeerAuthentication with mtls.mode: STRICT enforces mTLS — mesh-wide if it lives in the Istio root namespace with no selector, namespace-scoped otherwise. AuthorizationPolicy controls who may call what; remember that an empty rule with action: DENY is the deny-all idiom, and that principals take SPIFFE identities of the form cluster.local/ns/<ns>/sa/<serviceaccount>.
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: default
namespace: prod # istio-system + no selector = mesh-wide
spec:
mtls:
mode: STRICT # PERMISSIVE accepts plaintext too
---
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata: {name: allow-frontend, namespace: prod}
spec:
selector: {matchLabels: {app: orders}}
action: ALLOW
rules:
- from:
- source:
principals: ["cluster.local/ns/prod/sa/frontend"]
to:
- operation:
methods: [GET, POST]
paths: ["/api/*"]
---
# Deny-all idiom: empty spec.rules with action DENY
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata: {name: deny-all, namespace: prod}
spec:
action: DENY
rules:
- {}The three fields people forget
☺ Like you’re 10: Most lost marks aren’t a whole missing recipe. They’re one missing pinch of salt. Here are the usual three per dish.
If you only revise one thing on this page in the last hour before the exam, revise this table. Every row is a manifest that applies cleanly and scores nothing when the named fields are missing or wrong.
| Resource | Which tool | The 3 fields people forget |
|---|---|---|
Application | Argo CD | syncPolicy.automated.prune and .selfHeal (both default false); syncOptions: [CreateNamespace=true] — a sibling of automated, not a child; metadata.namespace: argocd |
ApplicationSet | Argo CD | spec.template (the generator alone produces nothing); the {{ }} placeholders that consume the generator’s output; generators[].git.directories[].path globbing |
GitRepository + Kustomization | Flux | Two different API groups — source.toolkit vs kustomize.toolkit; interval on both; the Kustomization’s prune and wait |
Task / Pipeline / PipelineRun | Tekton | Workspaces wired in all three places — declared in the Task, declared and mapped in the Pipeline, bound to storage in the PipelineRun; runAfter for ordering; taskRef vs inline taskSpec |
Rollout | Argo Rollouts | canaryService and stableService (both, named separately); a bare - pause: {} waits forever vs {duration: 60s}; it replaces the Deployment — do not leave both |
AnalysisTemplate | Argo Rollouts | successCondition (no condition = no gate); failureLimit; the - analysis: step that actually references the template |
XRD / Composition / claim | Crossplane | XRD metadata.name must be plural.group; referenceable: true on the served version; the claim uses the claimNames kind, not the X-prefixed one |
ServiceMonitor | Prometheus Operator | The release label matching the Prometheus serviceMonitorSelector; endpoints[].port is the Service’s named port, not a number; namespaceSelector when the Service is elsewhere |
PrometheusRule | Prometheus Operator | for: (fires instantly without it); labels.severity for routing; the same release label as above |
| Collector config | OpenTelemetry | service.pipelines — nothing flows without it; ports 4317 gRPC / 4318 HTTP; every component named in a pipeline must also be defined above |
ClusterPolicy | Kyverno | validationFailureAction: Enforce (Audit, the default, blocks nothing — newer Kyverno spells it per-rule as validate.failureAction); match.any.resources.kinds; the "?*" anchor meaning “any non-empty value” |
ConstraintTemplate + Constraint | Gatekeeper | Rego head is violation[{"msg": msg}], not deny; the Constraint’s kind is the template’s crd.spec.names.kind; enforcementAction: deny vs dryrun |
PeerAuthentication | Istio | mtls.mode: STRICT vs PERMISSIVE; scope comes from namespace + selector; root namespace with no selector = mesh-wide |
AuthorizationPolicy | Istio | action (defaults to ALLOW); the deny-all idiom rules: [{}] with action: DENY; SPIFFE principals of the form cluster.local/ns/<ns>/sa/<sa> |
Drill it — blank page, no notes
☺ Like you’re 10: Reading the recipes again feels like studying. Writing one out with the book shut is studying. Do the second one.
Recognition and recall are different skills, and only one of them is tested. You will recognise every block on this page after one read; that feeling of fluency is exactly the illusion that sinks candidates at minute twenty. The only honest measure is an empty file.
The blank-page drill, and it is the most important exercise on this site. Open an empty file with no browser and no notes. Write, from memory: an Argo CD Application with automated prune and self-heal; a Flux GitRepository + Kustomization pair; a Tekton Task + Pipeline + PipelineRun sharing one workspace; an Argo Rollouts Rollout with two canary steps and an AnalysisTemplate; a Crossplane XRD, Composition and claim; a ServiceMonitor and a PrometheusRule; an OTel Collector config with a complete traces pipeline; a Kyverno ClusterPolicy that enforces; a Gatekeeper ConstraintTemplate + Constraint; and an Istio PeerAuthentication STRICT plus an AuthorizationPolicy. Then check each against the real project docs — now, while you still can. Anything you got wrong twice goes on a flashcard. Then build them for real in the practice tasks, and when one won’t come up healthy, work the troubleshooting playbook.
Run the drill three times across your final fortnight, not once. The first pass tells you what you don’t know; the second tells you what didn’t stick; by the third the skeletons come out without deliberation, which is the state you actually need — because on the day the manifest is the easy part of the task, and every second you spend recalling it is a second not spent reading the requirement properly, adjusting the name and namespace, and verifying the result.
Remy: Blank file. Ninety seconds. Argo CD Application, prune and self-heal on. Go.
Gizmo: Easy. syncPolicy.automated.prune, selfHeal, and createNamespace right underneath. Ship it! 🤑
Mira: Two of three. CreateNamespace=true is a syncOption, not something automated knows about. Ask what each field is for: automated answers “when do I sync,” syncOptions answers “how do I apply.” Different questions, different homes.
Gizmo: Fine, I’ll just look it up on the day.
Foxy: On what? argo-cd.readthedocs.io isn’t on the allowlist. You get kubernetes.io, the blog, the Quick Reference box and man pages. That’s the list.
Recon: BEEP. Partial recovery available. kubectl explain application.spec.syncPolicy --recursive. The schema is in the cluster. The cluster is permitted.
Timmy: Which only helps if you remember the kind is called Application and it lives in argocd. explain fills in fields. It does not fill in names.
Dot: At my job nobody remembers YAML, we remember which tab. Turns out the exam is stricter than my job.
Remy: Then we drill. Blank file. Again. This time the Tekton three-place workspace — and Gizmo, all three places.
That is the page. Eleven skeletons, a table of the fields that quietly cost marks, and one drill that turns reading into recall. Pair it with the Docs Map for the kubernetes.io half you can look up, the exam guide for the logistics and the rules, the field notes for what people say it actually feels like, the command reference for the muscle memory around these manifests, and the glossary when a kind’s name is the bit that won’t come. Then close all of them and open an empty file.
1. In an Argo CD Application, where does CreateNamespace=true live — and what do prune and selfHeal default to? 2. Name the three places a Tekton workspace must appear before a PipelineRun will work. 3. Which Crossplane object does a developer write, and which kind does it use? 4. You wrote a perfect OTel Collector with receivers, processors and exporters, and no telemetry moves. What is missing, and which two ports should the OTLP receiver bind? 5. Name the one field on a Kyverno ClusterPolicy and the one on a Gatekeeper Constraint that decide whether either actually blocks anything. 6. What is the Rego rule head inside a Gatekeeper ConstraintTemplate, and what is the Istio deny-all idiom?
Check your answers
- Under
spec.syncPolicy.syncOptionsas the list itemCreateNamespace=true— a sibling ofautomated, not a child of it. BothpruneandselfHealdefault to false, so enabling automated sync alone neither deletes removed resources nor reverts manual drift. - ① declared in the
Taskunderspec.workspaces; ② declared in thePipelineunderspec.workspacesand mapped per-task (tasks[].workspaces[].workspace); ③ bound to real storage in thePipelineRun(persistentVolumeClaim,volumeClaimTemplateoremptyDir). Miss any one and it fails. - The developer writes the claim — a small namespaced manifest using the kind from the XRD’s
claimNames(e.g.PostgreSQLInstance), not theX-prefixed composite kind (XPostgreSQLInstance). The XRD and Composition are platform-team plumbing behind it. service.pipelines— defining components does nothing until a pipeline wires receivers → processors → exporters together. OTLP ports: 4317 for gRPC and 4318 for HTTP.- Kyverno:
spec.validationFailureAction: Enforce(Auditlogs only). Gatekeeper: the Constraint’sspec.enforcementAction: deny(dryrunlogs only). Then prove it by trying to create a violating pod and seeing the rejection. - The rule head is
violation[{"msg": msg}] { … }— notdeny[msg], which is standalone OPA. The Istio deny-all is anAuthorizationPolicywithaction: DENYandrules: [{}]— an empty rule matches everything.