From One CR to Child Resources and Status
Goal
Take a single CR as input, create child resources, and write the results back into status, completing by hand a structure in which one kubectl get line tells you the deployment state.
Why it matters
There are four disciplines to follow when you use a CR as a deployment interface. First, the user writes spec and the controller writes status. If the controller touches spec, the Git repository and the cluster diverge and the next sync erases the change. Second, status must always be written through a separate path. If the subresource is enabled and you write with an ordinary update, status is silently ignored, which shows up as the symptom "I definitely wrote it, but it isn't reflected." Third, attach owner references to children. When the parent is deleted, the garbage collector cleans up the children automatically, and the controller only has to manage "what I created," so the cleanup logic gets simpler. The link here is the uid, not the name — if you delete a parent and recreate one with the same name, the uid changes and children that point to the old uid are immediately garbage-collected. Fourth, expose lag with observedGeneration. If this value is smaller than metadata.generation, it means "status is still based on the old spec," and without this pair, users cannot know whether they can trust status.
Steps
Before you start: lab Pods start fresh for every lab, so the cluster state from the previous lab is not there. If kubectl get crd webservices.apps.labhub.io returns nothing, rewrite and apply the CRD and also run kubectl create ns crd-lab. This lab needs all of the following: v1's additionalPrinterColumns (Image/Replicas/Tier/Age), subresources.status, subresources.scale (.spec.replicas/.status.replicas/.status.selector), and the definitions of replicas, selector, observedGeneration, and conditions under properties.status. Without a status schema, even a patch is pruned away.
- In
/root/crd/deploy/minimal.yaml, write a WebService withmetadata.name: minimal(namespacecrd-lab) and onlyspec.image: nginx:1.27, and apply it. Do not writespec.replicas. The stored object'sspec.replicasmust be 1. - In
/root/crd/deploy/storefront.yaml, write a WebService withmetadata.name: storefront,metadata.labels.tier: prod,spec.image: nginx:1.27,spec.replicas: 4, andspec.tier: prod, and apply it. - Create a ConfigMap
storefront-configincrd-lab, and inmetadata.ownerReferences[0]putapiVersion: apps.labhub.io/v1,kind: WebService,name: storefront,controller: true, and foruidthe actual value you looked up withkubectl get webservice storefront -n crd-lab -o jsonpath='{.metadata.uid}'. - Write
replicas: 4andselector: app=storefrontto the status ofstorefront. You must write them with a patch that has--subresource=status, and save the entire command line you used to/root/crd/deploy/out/status-patch.txt. - Add
conditionsto the same status. Usetype: Ready,status: "True",reason: AllReplicasReady, a free-form sentence formessage, and forlastTransitionTimean RFC3339 timestamp in the format ofdate -u +%Y-%m-%dT%H:%M:%SZ. Also writestatus.observedGenerationwith the same value asmetadata.generation. - Apply
/opt/lab/fixtures/crd/sample-cr.yamltocrd-labto addsample(this makes three WebServices). Then attach a label withkubectl label webservice minimal -n crd-lab tier=dev, and save the output ofkubectl get webservice -n crd-lab -l tier=prodto/root/crd/deploy/out/selected.txt. This file must containstorefrontand notminimal, and exactly one must matchtier=prod. - Save the output of
kubectl get webservice -n crd-lab -o custom-columns=NAME:.metadata.name,IMAGE:.spec.image,TIER:.spec.tierto/root/crd/deploy/out/columns.txt. It must have one header line and at least 3 resource lines, for at least 4 lines in total. - Create a ConfigMap
storefront-desiredincrd-lab. The values of the three keysdata.image,data.replicas, anddata.tiermust be exactly equal, as strings, to the values read from thespecofstorefront, and designatestorefrontas the owner in the same way as in step 3. After this step,status.observedGenerationmust still equalmetadata.generation.
Notes
- Lab Pods start fresh for every lab, so the cluster state from the previous lab is not there. Still, if you leave your declarations as files, you can rebuild the same state on any Pod — this is the practical advantage of the declarative approach.
- Example of a status write:
kubectl patch webservice storefront -n crd-lab --subresource=status --type=merge -p '{"status":{"replicas":4}}' - The
datavalues of a ConfigMap are always strings. You must write the number 4 as"4", or the apply is rejected. - For an object that includes an owner reference, it is easier to create a file and apply it than to use
kubectl applydirectly. Capture the uid in a shell variable and splice it into the manifest. - Common mistake 1: patching without
--subresource=statusin step 4. If the subresource is enabled, status is silently ignored, so no error appears and the value is not stored. - Common mistake 2: matching only the name instead of the uid in step 3. Even if the names are the same, if the uid differs, the garbage collector sees that child as an orphan and deletes it immediately.
- Common mistake 3: modifying spec again in step 8, so that generation rises, and then not updating observedGeneration.
Create a CR with a minimal spec
In /root/crd/deploy/minimal.yaml, write a WebService with metadata.name: minimal (namespace crd-lab) and only spec.image: nginx:1.27, and apply it. Do not write spec.replicas. The stored object's spec.replicas must be 1.
Write only the one required field. If you write the rest, you cannot confirm whether the defaults were filled in, so leave them empty.
Create a CR with the full spec filled in
In /root/crd/deploy/storefront.yaml, write a WebService with metadata.name: storefront, metadata.labels.tier: prod, spec.image: nginx:1.27, spec.replicas: 4, and spec.tier: prod, and apply it.
A value in spec and a label in metadata are in different places. The former is the intent the controller reads, and the latter is an index for selecting by selector. You need both.
Bind a child with an owner reference
Create a ConfigMap storefront-config in crd-lab, and in metadata.ownerReferences[0] put apiVersion: apps.labhub.io/v1, kind: WebService, name: storefront, controller: true, and for uid the actual value you looked up with kubectl get webservice storefront -n crd-lab -o jsonpath='{.metadata.uid}'.
An owner reference links by uid, not by name. Look up the parent's uid first and put that value in. For apiVersion, write the group and version together, and you also need the boolean field that marks it as the managing controller.
Write observed values to the status subresource
Write replicas: 4 and selector: app=storefront to the status of storefront. You must write them with a patch that has --subresource=status, and save the entire command line you used to /root/crd/deploy/out/status-patch.txt.
With an ordinary patch, status is ignored. There is an option that specifies the status-only path, and the command that used that option must itself be left in the file. The selector is a string in 키=값 form (key=value).
Fill in standard conditions and observedGeneration
Add conditions to the same status. Use type: Ready, status: "True", reason: AllReplicasReady, a free-form sentence for message, and for lastTransitionTime an RFC3339 timestamp in the format of date -u +%Y-%m-%dT%H:%M:%SZ. Also write status.observedGeneration with the same value as metadata.generation.
A Ready condition needs both a machine-readable reason code and a human-readable explanation. The timestamp must be in RFC3339 format, and the processed generation number must equal the value in metadata.
Pick CRs with a label selector
Apply /opt/lab/fixtures/crd/sample-cr.yaml to crd-lab to add sample (this makes three WebServices). Then attach a label with kubectl label webservice minimal -n crd-lab tier=dev, and save the output of kubectl get webservice -n crd-lab -l tier=prod to /root/crd/deploy/out/selected.txt. This file must contain storefront and not minimal, and exactly one must match tier=prod.
It matches on the label in metadata, not the value in spec. Give the other CRs a different value so that exactly one matches prod.
Pick only the fields you want with custom-columns
Save the output of kubectl get webservice -n crd-lab -o custom-columns=NAME:.metadata.name,IMAGE:.spec.image,TIER:.spec.tier to /root/crd/deploy/out/columns.txt. It must have one header line and at least 3 resource lines, for at least 4 lines in total.
Separately from the columns defined in the CRD, you can specify the columns yourself at query time. Join entries of the form 머리글:JSON경로 (header:JSON path) with commas.
Create a desired-state object from the CR spec
Create a ConfigMap storefront-desired in crd-lab. The values of the three keys data.image, data.replicas, and data.tier must be exactly equal, as strings, to the values read from the spec of storefront, and designate storefront as the owner in the same way as in step 3. After this step, status.observedGeneration must still equal metadata.generation.
Do not copy the values by hand; read them from the CR and put them in as they are. None of the three values may differ from the CR, and you also need the link to the parent. If you changed spec, match the processed generation number again too.