在 Docker Engine 29.8.2 里把一个 8 条指令的镜像 docker save 出来,逐层读 OCI 结构:5 个 blob(1 manifest + 1 config + 3 层)文件名都等于内容的 sha256;config 里 history 9 条、6 条没有层;每层的 digest(压缩数据)与 config.rootfs.diff_ids(解压 tar)是两个不同的 sha256,都能重新算出来。
在 kind 集群里创建一个带 openAPIV3Schema 的 CRD 并逐项实测:必填、范围、枚举、类型校验各自的报错;未知字段在 kubectl 严格校验下被拒绝、用 --validate=false 提交时被剪掉;etcd 里存的 JSON 只含写入时补的默认值,CRD 事后新增的默认值是读出来时才补;开了 status 子资源之后主资源和 /status 各自忽略对方的字段,generation 只跟着 spec 变化。
在 kind 集群里为同一个 CRD 配两个版本(v1beta1 的 spec.replicas、v1 的 spec.size)并实测转换:strategy None 时用 v1beta1 写入的字段被剪掉、etcd 里只剩 {};Webhook 转换时 etcd 存存储版本 v1;写一次就会触发 v1→v1beta1 转换,GET 按需转换而 list 不再调用;webhook 停掉后存储版本照常读写,非存储版本读写 list 全部失败;改存储版本后 storedVersions 变成 ["v1","v1beta1"],旧对象仍是旧版本,删除旧版本被拒绝。
在 kind 集群里实测同一个 HPA 用 autoscaling/v1 与 v2 读出来的样子:v1 里只有 targetCPUUtilizationPercentage,内存指标和 behavior 被塞进 autoscaling.alpha.kubernetes.io 的注解;etcd 里不管用哪个版本写都存成 autoscaling/v2 的 protobuf;读已弃用的 v1 Endpoints 和 CRD 里标记 deprecated 的版本会得到响应头 Warning: 299,apiserver_requested_deprecated_apis 是值为 1 的 gauge;kubectl get hpa 默认请求 preferredVersion v2,短名加版本时版本被忽略。
在 kind 集群里实测 resourceVersion:两个写入者读到同一版本后先后 replace,后者得到 409 Conflict;merge patch 带旧 resourceVersion 会冲突、不带就覆盖,kubectl apply 不带 resourceVersion;resourceVersion 等于 etcd 里该 key 的 mod_revision,重复的无实际改动不会让它变化;resourceVersionMatch=Exact 能读回创建时的旧内容,太旧报 Expired,比当前大 100 万的版本等 43.1 秒后报 Too large resource version。
在 kind 集群里实测 watch 语义:从某个 resourceVersion 开始只会收到它之后的事件,当前版本 0 个;不带版本或 rv=0 先把现有对象当 ADDED 发一遍,被删除的对象不会补发;sendInitialEvents 用带 initial-events-end 注解的 BOOKMARK 标出初始事件结束;etcd 压缩并不会让旧版本失效,写到约 80 多秒、6000 多次之后 watch 缓存装不下才返回 410 too old resource version;带标签选择器时标签变化表现为 ADDED 与 DELETED。
在 kind 集群里实测 watch bookmark:对从不变化的对象 quiet 开两个 watcher,同时在别的对象上写 7000 次;不带 allowWatchBookmarks 的收到 0 个事件、记住的版本停在 41857,带的在第 59.7 秒收到一个只含 resourceVersion 的 BOOKMARK;85 秒时从 quiet 最后一次变化的版本重连得到 410 too old resource version,而从 27 秒前收到的 BOOKMARK 版本重连成功。
在 kind 集群里实测 Server-Side Apply 的字段所有权:team-a 与 team-b 申请同一字段得到 Apply failed with 1 conflict,对象不变;--force-conflicts 抢走所有权,被抢的一方原样再申请又冲突;把值改成与现值相同则共同拥有;共有字段被一方去掉仍保留,独占字段去掉就被删除;kubectl scale 这样的 Update 也会占有 replicas,再用 apply 申请不同的值会冲突。
在 kind 集群里用同一个修改对比三种 patch:merge patch 把 containers 列表整个替换(丢掉容器 b 和 a 的 command),strategic merge patch 按 name 合并只改 image,JSON patch 按下标精确修改;null、$patch: delete、JSON patch 的 remove 与 test 各自怎么删;schema 里的 x-kubernetes-patch-merge-key 决定了 containers、env、ports 按 key 合并而 tolerations、command 整个替换;自定义资源不支持 strategic merge patch。
在 kind 集群里实测 kubectl apply(client-side)的三方合并:last-applied 注解、这次的 manifest、集群里现在的对象三者比较,上次有这次没有的字段被删除、从没 apply 过的字段(手工添加的 c)被保留;kubectl create 出来的对象没有注解,apply 会警告且不会删字段;server-side apply 不写注解;data 约 270000 字节的 ConfigMap 能 create 却因注解超过 262144 字节而 apply 失败。
在 kind 集群里实测 finalizer:带 finalizer 的 ConfigMap 被 delete 后 39 毫秒返回但对象仍在,只多了 deletionTimestamp;正在删除的对象能改 data、不能新增 finalizer、不能撤销删除、不能同名重建;kubectl delete 默认等到最后一个 finalizer 被摘掉(这次等了 4.1 秒);内置的 kubernetes.io/pvc-protection 让被 Pod 使用的 PVC 保持 Bound 直到 Pod 删除。
在 kind 集群里用 ConfigMap 实测 ownerReferences 与垃圾回收:--cascade=background 所有者立刻消失、从属约 40 毫秒后被回收;foreground 时所有者留着 foregroundDeletion 的 finalizer,等 blockOwnerDeletion=true 的从属删完才消失,blockOwnerDeletion=false 的不阻塞;orphan 会摘掉从属的 ownerReferences;指向不存在的 uid 或跨命名空间的所有者会让从属被直接删除,并产生 OwnerRefInvalidNamespace 事件。