TT Lab
Get started
Learn Learning paths Courses

CRDs and Operators

From One CR to Child Resources and Status

Continue in TT Lab

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.

  1. 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.
  2. 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.
  3. 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}'.
  4. 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.
  5. 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.
  6. 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.
  7. 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.
  8. 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.

Notes

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.