从一份 CR 生成下层资源与状态
目标
以一份 CR 作为输入创建下层资源,再把结果写回 status,亲手完成让一行 kubectl get 就能说明部署状态的结构。
为什么重要
把 CR 作为部署接口时,必须遵守四项纪律。**第一,spec 由用户写,status 由控制器写。**控制器修改 spec 会让 Git 仓库与集群产生差异,下一次同步会将其抹去。**第二,status 必须通过独立路径写入。**启用子资源后,普通 update 会静默忽略 status,表现为“明明写了却没有生效”。**第三,为子资源添加 owner reference。**删除父对象时,垃圾回收器会自动清理子对象;控制器只需管理“自己创建的对象”,清理逻辑更简单。连接依据不是名称,而是 uid:删除同名父对象再重建后,uid 会改变,指向旧 uid 的子对象会立即被回收。**第四,用 observedGeneration 显示时间差。**若它小于 metadata.generation,表示“status 仍基于旧 spec”。没有这组值,用户无法判断 status 是否可信。
步骤
开始前准备:每次实验都会创建新的实验 Pod,因此上一实验的集群状态不会保留。如果 kubectl get crd webservices.apps.labhub.io 为空,请重新编写并应用 CRD,同时执行 kubectl create ns crd-lab。本实验需要 v1 的 additionalPrinterColumns(Image/Replicas/Tier/Age)、subresources.status、subresources.scale(.spec.replicas/.status.replicas/.status.selector),以及 properties.status 下的 replicas、selector、observedGeneration、conditions 定义。缺少 status 模式时,即使 patch,内容也会被 pruning 裁掉。
- 在
/root/crd/deploy/minimal.yaml中编写 WebService:metadata.name: minimal,命名空间为crd-lab,且只包含spec.image: nginx:1.27,然后应用。不要填写spec.replicas。保存后的对象必须显示spec.replicas为 1。 - 在
/root/crd/deploy/storefront.yaml中编写并应用 WebService,包含metadata.name: storefront、metadata.labels.tier: prod、spec.image: nginx:1.27、spec.replicas: 4、spec.tier: prod。 - 在
crd-lab中创建 ConfigMapstorefront-config。在metadata.ownerReferences[0]中填入apiVersion: apps.labhub.io/v1、kind: WebService、name: storefront、controller: true;uid必须使用kubectl get webservice storefront -n crd-lab -o jsonpath='{.metadata.uid}'查询到的真实值。 - 在
storefront的 status 中写入replicas: 4与selector: app=storefront。必须使用带--subresource=status的 patch,并把完整命令行保存到/root/crd/deploy/out/status-patch.txt。 - 在同一 status 中加入
conditions:type: Ready、status: "True"、reason: AllReplicasReady,message可自由填写,lastTransitionTime使用date -u +%Y-%m-%dT%H:%M:%SZ格式的 RFC3339 时间。同时把status.observedGeneration写成与metadata.generation相同的值。 - 把
/opt/lab/fixtures/crd/sample-cr.yaml应用到crd-lab,增加sample(此时共有 3 个 WebService)。然后运行kubectl label webservice minimal -n crd-lab tier=dev添加标签,把kubectl get webservice -n crd-lab -l tier=prod的输出保存到/root/crd/deploy/out/selected.txt。文件中必须有storefront、没有minimal,且tier=prod精确匹配一个对象。 - 把
kubectl get webservice -n crd-lab -o custom-columns=NAME:.metadata.name,IMAGE:.spec.image,TIER:.spec.tier的输出保存到/root/crd/deploy/out/columns.txt。应包含一行表头与至少三行资源,总计至少四行。 - 在
crd-lab中创建 ConfigMapstorefront-desired。data.image、data.replicas、data.tier三个键的值,必须以字符串形式与从storefront的spec读取的值完全相同,并按第 3 步相同方式把storefront设为所有者。完成后,status.observedGeneration仍必须等于metadata.generation。
参考
- 每次实验都会创建新的实验 Pod,因此上一实验的集群状态不会保留。但只要把声明保存成文件,就能在任何 Pod 中重建相同状态——这正是声明式的实际优势。
- 写入 status 的示例:
kubectl patch webservice storefront -n crd-lab --subresource=status --type=merge -p '{"status":{"replicas":4}}' - ConfigMap 的
data值始终是字符串。数字 4 必须写成"4",否则应用会被拒绝。 - 对带 owner reference 的对象,创建文件后应用通常比直接使用
kubectl apply更方便。请把 uid 读入 shell 变量,再嵌入清单。 - 常见错误 1:第 4 步 patch 时缺少
--subresource=status。启用子资源后,status 会被静默忽略,不报错也不保存。 - 常见错误 2:第 3 步只匹配名称而未填写 uid。即使名称相同,uid 不同,垃圾回收器也会把子对象视为孤儿并立即删除。
- 常见错误 3:第 8 步再次修改 spec 导致 generation 增加,却没有更新 observedGeneration。
使用最小 spec 创建 CR
在 /root/crd/deploy/minimal.yaml 中编写 WebService:metadata.name: minimal,命名空间为 crd-lab,且只包含 spec.image: nginx:1.27,然后应用。不要填写 spec.replicas。保存后的对象必须显示 spec.replicas 为 1。
只填写一个必填字段。其他字段应留空,否则无法确认是否由默认值填充。
创建填写完整 spec 的 CR
在 /root/crd/deploy/storefront.yaml 中编写并应用 WebService,包含 metadata.name: storefront、metadata.labels.tier: prod、spec.image: nginx:1.27、spec.replicas: 4、spec.tier: prod。
spec 值与 metadata 标签位于不同位置。前者是控制器读取的意图,后者是供选择器检索的索引,两者都需要。
通过 owner reference 绑定子对象
在 crd-lab 中创建 ConfigMap storefront-config。在 metadata.ownerReferences[0] 中填入 apiVersion: apps.labhub.io/v1、kind: WebService、name: storefront、controller: true;uid 必须使用 kubectl get webservice storefront -n crd-lab -o jsonpath='{.metadata.uid}' 查询到的真实值。
owner reference 通过 uid 而不是名称连接。先查询父对象 uid,再填入该值。apiVersion 要同时包含组和版本,还需要表示主控制器的 bool 字段。
向 status 子资源写入观测值
在 storefront 的 status 中写入 replicas: 4 与 selector: app=storefront。必须使用带 --subresource=status 的 patch,并把完整命令行保存到 /root/crd/deploy/out/status-patch.txt。
普通 patch 会忽略 status。必须指定 status 专用路径,并把包含该选项的命令本身保存到文件。selector 是 키=값 形式的字符串。
填写标准 conditions 与 observedGeneration
在同一 status 中加入 conditions:type: Ready、status: "True"、reason: AllReplicasReady,message 可自由填写,lastTransitionTime 使用 date -u +%Y-%m-%dT%H:%M:%SZ 格式的 RFC3339 时间。同时把 status.observedGeneration 写成与 metadata.generation 相同的值。
Ready 条件同时需要机器读取的原因代码和人类读取的说明。时间必须使用 RFC3339 格式,已处理的 generation 必须与 metadata 中的值相同。
使用标签选择器筛选 CR
把 /opt/lab/fixtures/crd/sample-cr.yaml 应用到 crd-lab,增加 sample(此时共有 3 个 WebService)。然后运行 kubectl label webservice minimal -n crd-lab tier=dev 添加标签,把 kubectl get webservice -n crd-lab -l tier=prod 的输出保存到 /root/crd/deploy/out/selected.txt。文件中必须有 storefront、没有 minimal,且 tier=prod 精确匹配一个对象。
选择器匹配 metadata 标签,而不是 spec 值。请给其他 CR 添加不同值,确保 prod 精确匹配一个对象。
使用 custom-columns 提取所需字段
把 kubectl get webservice -n crd-lab -o custom-columns=NAME:.metadata.name,IMAGE:.spec.image,TIER:.spec.tier 的输出保存到 /root/crd/deploy/out/columns.txt。应包含一行表头与至少三行资源,总计至少四行。
除了 CRD 定义的列,也可以在查询时直接指定列。用逗号连接 머리글:JSON경로。
根据 CR spec 创建期望状态对象
在 crd-lab 中创建 ConfigMap storefront-desired。data.image、data.replicas、data.tier 三个键的值,必须以字符串形式与从 storefront 的 spec 读取的值完全相同,并按第 3 步相同方式把 storefront 设为所有者。完成后,status.observedGeneration 仍必须等于 metadata.generation。
不要手工抄写值,应从 CR 读取后原样填入。三个值都必须与 CR 完全一致,也要建立父对象连接。如果修改了 spec,还应重新对齐已处理的 generation。