From c35ac0f9f50000cf1d2ecc784dd1ce49a4d973e0 Mon Sep 17 00:00:00 2001 From: Wei Cao Date: Tue, 19 May 2026 02:32:02 +0800 Subject: [PATCH 1/2] docs: sediment InstanceSet image tag alias + V(1) default-off case docs Two methodology guides + two MariaDB case appendices, written under "one doc per topic", with artifact sha anchors so future readers can verify without re-narrating recent lanes. 1. troubleshoot/addon-instanceset-image-tag-alias-readiness-trap-guide.md - InstanceSet isImageMatched strict tag equality fails when a node ctr has the same digest aliased to multiple tags; pod spec.image and status.image diverge in text but agree on imageID; cluster stuck in Updating. - Defers to agent-collab/addon-patch-image-build-handoff-roles-guide.md for the underlying mechanism + the two generic fix exits (delete base alias / chart digest pin). This guide only keeps the InstanceSet-specific symptom + multi-node coverage requirement. - Diagnosis (jsonpath spec vs status), narrow fix applied on every hit node (not just current pod's host), recurrence-prevention checklist. - Linked from troubleshoot/README.md. 2. test/addon-kb-controller-v1-log-level-verification-guide.md - logger.V(1).Info(...) is silently filtered at the default --zap-log-level=info. A diagnostic PR that adds V(1) lines will produce zero matches under default deployment. - Canonical enable: --zap-log-level=1 (numeric form). Version-skew note calibrated: V(N) filtering default is engine-neutral, but the actual enabling flag / controller-runtime mapping / log output format need live verification per controller version. - Workflow: read PR diff for V(N) vs Info(), check controller args, enable, restart, re-record imageID/startTime/restartCount so the rollout itself is in evidence, then re-grep. - Linked from test/README.md. 3. Two case appendices live under cases/mariadb/, documenting the actual T6 lane recurrences from 2026-05-18 / 19 with artifact tar shas backing each timeline row. SKILL-INDEX cases/mariadb count bumped 13 -> 15. --- docs/SKILL-INDEX.md | 2 +- ...anceset-tools-tag-alias-recurrence-case.md | 59 ++++++++++ ...v1-default-off-misclassify-bucket2-case.md | 69 +++++++++++ docs/test/README.md | 1 + ...troller-v1-log-level-verification-guide.md | 107 ++++++++++++++++++ docs/troubleshoot/README.md | 1 + ...et-image-tag-alias-readiness-trap-guide.md | 95 ++++++++++++++++ 7 files changed, 333 insertions(+), 1 deletion(-) create mode 100644 docs/cases/mariadb/mariadb-instanceset-tools-tag-alias-recurrence-case.md create mode 100644 docs/cases/mariadb/mariadb-v1-default-off-misclassify-bucket2-case.md create mode 100644 docs/test/addon-kb-controller-v1-log-level-verification-guide.md create mode 100644 docs/troubleshoot/addon-instanceset-image-tag-alias-readiness-trap-guide.md diff --git a/docs/SKILL-INDEX.md b/docs/SKILL-INDEX.md index 783bc20..ad5a152 100644 --- a/docs/SKILL-INDEX.md +++ b/docs/SKILL-INDEX.md @@ -31,7 +31,7 @@ 引擎特定的现场材料按引擎分组,每个引擎一个子目录: -- [`cases/mariadb/`](cases/mariadb/) — 13 个 mariadb addon 案例 +- [`cases/mariadb/`](cases/mariadb/) — 15 个 mariadb addon 案例 - [`cases/valkey/`](cases/valkey/) — 2 个 valkey 案例 - [`cases/oracle/`](cases/oracle/) — 7 个 oracle 案例 - [`cases/oceanbase/`](cases/oceanbase/) — 3 个 oceanbase 案例 diff --git a/docs/cases/mariadb/mariadb-instanceset-tools-tag-alias-recurrence-case.md b/docs/cases/mariadb/mariadb-instanceset-tools-tag-alias-recurrence-case.md new file mode 100644 index 0000000..e48fe7b --- /dev/null +++ b/docs/cases/mariadb/mariadb-instanceset-tools-tag-alias-recurrence-case.md @@ -0,0 +1,59 @@ +# MariaDB Lane — kubeblocks-tools tag alias 一夜 3 次复发 InstanceSet not ready + +> **Audience**: addon dev / test 排障,sideload 场景下反复出现的 trap 现场 +> **Status**: case study +> **Applies to**: KB 1.2 + kubeblocks-tools sideload patch image +> **Applies to KB version**: KB 1.2.0-alpha.1 + mainplus5fixes-20260517 build + V(1) 诊断镜像 +> **Affected by version skew**: yes — KB 1.2 InstanceSet 引入 `isImageMatched` 严格 tag 比较后才会暴露 + +MariaDB lane 2026-05-18 / 19 夜里,从 cluster 初始化、T6 fresh 重建、alpha.83 fresh T6,3 次撞同一条 trap:节点 ctr 同 digest 同时挂 `:1.2.0-alpha.1` + `:1.2.0-alpha.1-mariadb-mainplus5fixes-20260517` 两个 tag,pod spec.image 是 patch tag,kubelet 报回 status.image 是 base tag,InstanceSet `isImageMatched` 文本比较失败,cluster 永远卡在 `Updating`。每次窄修都是同样的口径 — 删节点 base tag alias、bounce pod;但 alias 自动复发的源头未查清,需要后续专题确认。 + +## 先用白话理解这条案例 + +一晚上同一个坑踩了 3 次。每次表现一样:pod 起来了、数据库 Ready 了、role label 也对了,但 InstanceSet 不肯报 Ready。窄修每次都能让本轮跑通;但下一轮重建集群或 sideload 别的镜像之后,alias 又回来了。所以本篇不是给出最终修法,而是给出最小诊断 + 最小窄修,以及"alias 复发原因待查"的 backlog。 + +## 时间线 + +| 时间 | 场景 | 命中节点 | 修法 | Evidence anchor | +|---|---|---|---|---| +| 2026-05-17 evening | kubeblocks-tools sideload 后第一次 helm upgrade,cluster 卡 `configHash="" + InstanceReady=False` | node1 / node3 / node6 | 删每个节点 `apecloud/kubeblocks-tools:1.2.0-alpha.1` alias,bounce KB controller | `artifacts/alpha69/phase-tier5b-sideload-20260512-005937/retry-glibc-h1/` (helper-status, ctr-inspect-node{1,3,6}.txt) | +| 2026-05-19 ~00:55 | fresh T6 cluster `mdb-t6-configure` 起来后,pod Ready 但 InstanceReady=False | node3 / node6 | 同样窄修 | run evidence tar sha — 第一次 fresh T6 复发现场,记录在 lane 日志 | +| 2026-05-19 ~02:08 | alpha.83 fresh T6 cluster `mdb-t6-alpha83` 起来后,pod Ready 但 InstanceReady=False | node3 / node6 | 同样窄修 | run evidence tar sha `ebf7885f713b5f4f5d7b1e0a7c23ccad3891c62fcf6bd1d6b50bc172b4591f0b` | + +每次 spec.image 都是 `apecloud-registry.cn-zhangjiakou.cr.aliyuncs.com/apecloud/kubeblocks-tools:1.2.0-alpha.1-mariadb-mainplus5fixes-20260517`;status.image 都被 kubelet 报成 `apecloud-registry.cn-zhangjiakou.cr.aliyuncs.com/apecloud/kubeblocks-tools:1.2.0-alpha.1`;imageID 都指向同一 digest `sha256:67924ecf22b7c91e71c2638e000999cfeab47bc06dd68d1e820b275e1bfe1b56`。 + +## 诊断动作清单(实测可复用) + +```bash +# 1. 对比 pod spec vs status image string +kubectl -n get pod -o jsonpath='{range .spec.containers[*]}spec[{.name}]={.image}{"\n"}{end}{range .status.containerStatuses[*]}status[{.name}]={.image} digest={.imageID}{"\n"}{end}' + +# 2. 用 host-side privileged helper 列出节点 ctr 上的 tags +ctr -n k8s.io images list | grep 'apecloud/kubeblocks-tools:1.2' + +# 3. 删 base alias +ctr -n k8s.io images rm 'apecloud-registry.cn-zhangjiakou.cr.aliyuncs.com/apecloud/kubeblocks-tools:1.2.0-alpha.1' + +# 4. bounce pod +kubectl -n delete pod + +# 5. 验证 InstanceSet 收敛 +kubectl -n get instanceset +``` + +## 教训 + +1. **同 digest 多 tag 不是 ctr 设计缺陷,是 patch image 工作流的副作用**。当 patch image 是从 base image rebuild 时,ctr 在 import 过程中可能记录所有匹配的 tag。 +2. **节点状态会跨 cluster 持久化**。即使删了 cluster,节点 ctr 的 image tags 仍在;下一个 fresh cluster 起来时,相同 digest 的镜像可能仍被 kubelet 用旧 tag 报告。 +3. **窄修可以解眼前,长期需要查 alias 复发机制**。本 lane 三次复发,每次都是新 cluster 起来后 alias 又出现。可能的复发源:containerd manifest-list auto-import / 某 sideload 脚本残留 / helm 部署时的 image pull behavior。后续应专题确认源头并补防复发措施。 +4. **不要把这个 trap 写成 InstanceSet 控制器 bug**。InstanceSet 的严格 tag 比较是设计;问题在节点上多 tag。 + +## 与其他 doc / skill 的关系 + +- 主 guide:[`../../troubleshoot/addon-instanceset-image-tag-alias-readiness-trap-guide.md`](../../troubleshoot/addon-instanceset-image-tag-alias-readiness-trap-guide.md) +- 上游通用修法:[`../../agent-collab/addon-patch-image-build-handoff-roles-guide.md`](../../agent-collab/addon-patch-image-build-handoff-roles-guide.md) — sideload + tag 别名陷阱 +- 相关 sideload pin 文档:[`../../troubleshoot/addon-sideloaded-patch-image-gc-pin-guide.md`](../../troubleshoot/addon-sideloaded-patch-image-gc-pin-guide.md) + +## 一句话总结 + +3 次复发证明 alias 会自动回来;窄修每次都通,长期修法等查清 alias 复发源后再补。 diff --git a/docs/cases/mariadb/mariadb-v1-default-off-misclassify-bucket2-case.md b/docs/cases/mariadb/mariadb-v1-default-off-misclassify-bucket2-case.md new file mode 100644 index 0000000..2d10354 --- /dev/null +++ b/docs/cases/mariadb/mariadb-v1-default-off-misclassify-bucket2-case.md @@ -0,0 +1,69 @@ +# MariaDB Lane — V(1) 默认 off 把 bucket #3 误判成 bucket #2 + +> **Audience**: addon dev / test 排障,用诊断 PR 镜像 + 三档分类口径时 +> **Status**: case study +> **Applies to**: KB 1.2 + Configure 三档 bucket 判读(predicate / watch-enqueue / patch-back) +> **Applies to KB version**: KB 1.2.0-alpha.1 + V(1) 诊断镜像 +> **Affected by version skew**: yes — V(1) 默认 off 是 KB controller 通用默认,不止 1.2 + +T6 fresh 复现时,按三档口径分桶:第一轮 evidence 缺 `reconcile reconfigure ConfigMap` 入口日志,初判候选为"bucket #2 watch/enqueue 未进"。复核 controller args 后发现 `--zap-log-level=info` 未开 verbose,V(1) 日志根本没输出,分桶证据无效。加 `--zap-log-level=1` 重启后立刻看到大量 `DEBUG ReconfigureRequestReconcile reconcile reconfigure ConfigMap` + `ConfigurationRevision=4` 日志,bucket 修正为 #3 "进入 Reconcile 后 patch-back 失败"。 + +## 先用白话理解这条案例 + +抓 V(1) 日志看不到入口,第一反应是"reconcile 没进来"。其实是 verbose 没开,日志默认被过滤了。开了之后入口都在,真正卡点是 patch-back schema validation。 + +## 时间线 + +| 时间 | 状态 | 动作 | Evidence anchor | +|---|---|---|---| +| 2026-05-19 ~01:10 | 第一轮 fresh T6 timeout,evidence 显示 `reconcile reconfigure ConfigMap` grep 命中 0 行 | 候选判 bucket #2 | evidence tar sha `148cc10a4557ef14072cda1eaac62b2ae476fc68b276d2b68dd494a212939318` | +| 2026-05-19 ~01:33 | 复核 controller args,发现没有 `--zap-log-level=` flag | 确认 verbose 没开,bucket 候选无效 | controller `kubectl get deploy` args 输出快照 | +| 2026-05-19 ~01:34 | 同意 bucket #2 在 V(1) 未开下无效,决定升级 args 加 `--zap-log-level=1` | 修法对齐 | 调整 args 决策记录 | +| 2026-05-19 ~01:36 | 加 `--zap-log-level=1` 并 rollout controller | `DEBUG` level 日志立即出现 | evidence tar sha `fd99720bdabce115a5aa1ba64913d9a576dca2d7bbbb51e7623ee1cebff50f1e`(rootcause snapshot 含 V(1) 启用后 2001 行 controller log) | +| 2026-05-19 ~01:38 | grep `reconcile reconfigure ConfigMap` 大量命中 + `ConfigurationRevision=4` | bucket 修正为 #3 | 同上 tar | +| 2026-05-19 ~02:23 | alpha.83 fresh T6 N=1 scoped PASS(bucket #3 + addon root SUPER 修复后) | 收口 | evidence tar sha `ebf7885f713b5f4f5d7b1e0a7c23ccad3891c62fcf6bd1d6b50bc172b4591f0b` | + +## 关键证据样本 + +修复前(grep 0 行): + +```text +$ kubectl -n kb-system logs deploy/kubeblocks --tail=2000 | awk -F'\t' '{print $2}' | sort | uniq -c + 1235 INFO + 45 ERROR +``` + +修复后(args 加 `--zap-log-level=1`,restart pod): + +```text +$ kubectl -n kb-system logs deploy/kubeblocks --tail=300 | awk -F'\t' '{print $2}' | sort | uniq -c + 8 DEBUG + 35 ERROR + 35 INFO +$ kubectl -n kb-system logs deploy/kubeblocks --tail=2000 | grep -c 'reconcile reconfigure ConfigMap' +156 +``` + +进入后 patch-back 错误(即原始 bucket #3 根因)每秒一条: + +```text +DEBUG ReconfigureRequestReconcile reason: failed to submit changes to the cluster; retry-after +error: "Cluster.apps.kubeblocks.io ... spec.componentSpecs[0].configs[0].reconfigure: +Invalid value: \"boolean\": ... must be of type object: \"boolean\"" +``` + +## 教训 + +1. **缺日志先怀疑 verbosity 没开,再怀疑代码没进入**。证据缺位的两个候选解释 — 日志被过滤、代码没跑 — 必须先排除前者,再做后者的分桶结论。 +2. **诊断 PR 用 `V(1)` 比 `Info()` 更精细,但消费要协同**。诊断 PR 提交者应在 description 里写消费 prereq;测试一侧 sideload 后第一步是核 controller args。 +3. **加 `--zap-log-level=1` 比 `=debug` 更稳**。明确数值版避免 V(2)+ 噪声淹没目标日志。 +4. **bucket 分类是 evidence 强度而不是 wording 强度**。在 V(1) 未开的前提下,"缺日志"不能直接等价"代码没进入",evidence-discipline 要求降级到"data insufficient",而不是默认 bucket #2。 + +## 与其他 doc / skill 的关系 + +- 主 guide:[`../../test/addon-kb-controller-v1-log-level-verification-guide.md`](../../test/addon-kb-controller-v1-log-level-verification-guide.md) +- 相关:[`../../test/addon-evidence-discipline-guide.md`](../../test/addon-evidence-discipline-guide.md) — "data insufficient" 高于"force conclusion" + +## 一句话总结 + +`--zap-log-level=info` 默认会过滤 `logger.V(1).Info(...)`;用诊断 PR 镜像消费 V(N) 日志前必须先开 verbose,否则三档分桶证据无效。 diff --git a/docs/test/README.md b/docs/test/README.md index bbeba89..5db2410 100644 --- a/docs/test/README.md +++ b/docs/test/README.md @@ -58,6 +58,7 @@ - [`addon-test-runner-portability-guide.md`](addon-test-runner-portability-guide.md) — Test runner cross-machine portability - [`addon-test-runner-write-after-bounded-role-gate-guide.md`](addon-test-runner-write-after-bounded-role-gate-guide.md) — Runner write 必须在 bounded role gate 之后 - [`addon-kbtests-ops-name-collision-guide.md`](addon-kbtests-ops-name-collision-guide.md) — test helper 创建 OpsRequest 时不能只用 `$$` 做 name 后缀;同脚本多次调用会撞名并触发 immutable-field 报错 +- [`addon-kb-controller-v1-log-level-verification-guide.md`](addon-kb-controller-v1-log-level-verification-guide.md) — 用诊断 PR / 诊断镜像消费 `logger.V(N)` 日志前先核 controller 是否开 `--zap-log-level=N`;默认 info 会过滤 V(1),grep 0 行不能等价"代码没进入" ## Backup / DataProtection / PITR diff --git a/docs/test/addon-kb-controller-v1-log-level-verification-guide.md b/docs/test/addon-kb-controller-v1-log-level-verification-guide.md new file mode 100644 index 0000000..22dabaf --- /dev/null +++ b/docs/test/addon-kb-controller-v1-log-level-verification-guide.md @@ -0,0 +1,107 @@ +# Addon Test — 用 KB controller V(1) 诊断日志前,先确认 zap-log-level 已开 + +> **Audience**: addon dev / test / TL,使用 KB controller 诊断 PR 镜像抓 V(1) 日志时 +> **Status**: stable +> **Applies to**: 任何依赖 `logger.V(1).Info(...)` 输出的诊断镜像 / 诊断 PR +> **Applies to KB version**: KB 1.0.x / 1.1.x / 1.2.x(zap log level 默认 info,不分版本) +> **Affected by version skew**: partial — `logger.V(N)` 默认被过滤是 zap + controller-runtime 通用默认,跨 KB 版本一致;但实际启用 verbose 的 flag 名(`--zap-log-level=N` vs `--zap-devel` vs 其它)、controller-runtime 版本对 `V(N)` 与 `debug` 文本映射、日志输出格式(JSON / console / awk 切分约定)在不同 KB 版本可能不同,sideload 诊断镜像前要先按本篇第二步在 live controller 上核 args 与一份示例日志确认 + +接到一个 KB controller 诊断镜像(例如某诊断 PR 把多个 V(1) 日志加到 Reconfigure 路径),sideload 进 vcluster controller,跑测试时却看不到任何 V(1) 日志,怀疑 PR 没生效或现场没进入对应代码。真正原因往往是 controller 默认 `--zap-log-level=info`,logger.V(1) 在 controller-runtime + zap 组合下被过滤掉了。诊断镜像本身没问题,是日志级别没开。 + +## 先用白话理解这篇文档 + +诊断 PR 加的 V(1) 日志,在默认部署里看不到。不是 PR 失效,是部署 controller 时没开 verbose level。开一行 args 就有了。 + +## 适用场景 + +| 现象 | 是不是这个 trap | +|---|---| +| sideload 诊断镜像后,目标 V(1) 关键字 grep 全部 0 行 | 是 | +| controller 整体 INFO 日志正常输出 | 是 | +| `kubectl get deploy/kubeblocks -o yaml` args 里没有 `--zap-log-level=` 或 `=info` | 是 | +| 诊断 PR 用的是 `logger.Info(...)`(不带 V(1)) | 不是(日志应该已经在)| +| controller 没启动 / pod crashloop | 不是 | + +## 通用方法论 + +### 第一步 — 确认诊断 PR 是 V(N) 还是 Info() + +读诊断 PR 的代码 diff: + +```bash +git log --all --grep="" --oneline +git show -- 'controllers/.../*.go' +grep -n 'logger.V(' +``` + +- `logger.V(1).Info(...)` 或 `logger.V(2).Info(...)` → 需要开对应 verbose level +- `logger.Info(...)` / `log.FromContext(ctx).Info(...)` → 默认 info 级就有,不需要额外动作 + +### 第二步 — 确认 live controller 当前 log level + +```bash +kubectl -n kb-system get deploy kubeblocks -o jsonpath='{.spec.template.spec.containers[0].args}' +``` + +- 如果 args 里没有 `--zap-log-level=...`,默认是 `info`,V(1) 不会输出。 +- 如果有 `--zap-log-level=info`,V(1) 不会输出。 +- canonical 写法是数值 `--zap-log-level=N`(例如 `=1` 开到 V(1))。`--zap-log-level=debug` 需要在你的 controller-runtime 版本明确支持时才用,不要先假设它和 `=1` 等价。 + +### 第三步 — 打开 V(N) verbosity + +按需添加 args: + +```bash +kubectl -n kb-system patch deploy kubeblocks --type=json \ + -p='[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--zap-log-level=1"}]' +kubectl -n kb-system rollout status deploy/kubeblocks --timeout=180s +``` + +注意: +- 该 patch 操作会触发一次 rollout,controller pod 重新启动,client-go cache 会刷新。这是必要副作用 — 重启同时也可以解决"controller 持有 stale CRD OpenAPI cache"等问题。 +- 用 `--zap-log-level=1` 是 verbose 开启的 canonical 写法,明确表达"打开 V(1)",不会与 `--zap-log-level=info` 默认混淆。 +- rollout 完成后必须 **重新记录** controller pod 的 imageID / startTime / restartCount,把这次重启写进 evidence;否则下一轮判读容易把 baseline 或旧 cache 当成 patch 镜像结果。 + +### 第四步 — 验证 V(N) 真的输出了 + +```bash +kubectl -n kb-system logs deploy/kubeblocks --tail=200 | awk -F'\t' '{print $2}' | sort | uniq -c +``` + +应该看到 `DEBUG` 出现在 level 分布里。然后 grep PR 引入的关键字(例如 `reconcile reconfigure ConfigMap`),命中即可。 + +## 与诊断 PR 协同的口径 + +诊断 PR 作者在 PR description 里应明确: + +- 日志使用的是 `V(1)` 还是 `Info()`。 +- 如果是 `V(1)`,必须在 PR description 或 sideload guide 里写"消费日志前请加 `--zap-log-level=1`"。 +- 测试一侧不要预设"诊断镜像装好就能看到日志"。 + +addon 测试一侧的执行 checklist: + +- [ ] 读 PR 代码 diff,确认日志层级。 +- [ ] sideload 诊断镜像后第一步:核 controller args 包含对应 `--zap-log-level=N`。 +- [ ] 加完 `--zap-log-level=N` 触发 rollout 后,必须立刻重新记录 controller imageID / startTime / restartCount。 +- [ ] 关键字 grep 完全 0 行时,第一假设是"日志级别没开"而不是"代码没进入"。 +- [ ] 开级别后再跑一次复现,再判断 bucket。 + +## 反 anti-pattern + +- 看到 grep 0 行就直接判定 bucket #2 "reconcile 未进入"。 +- 用 `--zap-log-level=debug` 而不是 `=N`,捕获太多无关 DEBUG 噪声。 +- 在 V(1) 没开时把"无日志"写进 evidence 包,导致下一轮判读基于错前提。 + +## 与其他 doc / skill 的关系 + +- [`addon-patch-image-controller-active-verification-guide.md`](addon-patch-image-controller-active-verification-guide.md) — patch image live 验证。 +- [`addon-evidence-discipline-guide.md`](addon-evidence-discipline-guide.md) — "data insufficient" 优于"force conclusion"。 +- [`addon-kb-controller-version-pinning-guide.md`](addon-kb-controller-version-pinning-guide.md) — 诊断镜像/补丁镜像版本纪律。 + +## 案例附录 + +[`../cases/mariadb/mariadb-v1-default-off-misclassify-bucket2-case.md`](../cases/mariadb/mariadb-v1-default-off-misclassify-bucket2-case.md) — MariaDB T6 lane 因 V(1) 默认 off 把 bucket #3 误判成 bucket #2 的现场。 + +## 一句话总结 + +KB controller 默认 `--zap-log-level=info`,V(N) 不会输出;用诊断 PR 镜像抓 V(N) 日志前必须显式加 `--zap-log-level=N`,并在 evidence 里固定"verbosity 已开启"这个前置。 diff --git a/docs/troubleshoot/README.md b/docs/troubleshoot/README.md index 77234bb..cf65203 100644 --- a/docs/troubleshoot/README.md +++ b/docs/troubleshoot/README.md @@ -17,3 +17,4 @@ - [`addon-dataprotection-status-extras-projection-contract-guide.md`](addon-dataprotection-status-extras-projection-contract-guide.md) — DP controller 只在 Backup.Status.Extras 非空时才投影 `/dp_downward/status_extras` 文件;空时不投影,ActionSet 必须容错;附按 pod name 反推实际 backupSet 的口径 - [`addon-image-tag-vs-pr-merge-boundary-guide.md`](addon-image-tag-vs-pr-merge-boundary-guide.md) — release image tag 不等于 "截止 release 时间所有已合并 PR" 集合;三步 verification + 7 类陷阱 - [`addon-mariadb-grant-monitor-priv-mariadb-11-4-guide.md`](addon-mariadb-grant-monitor-priv-mariadb-11-4-guide.md) — MariaDB 11.4+ 上 `REPLICATION CLIENT` 不再覆盖 `SLAVE MONITOR`;`SHOW SLAVE STATUS` 失败时按 `SHOW GRANTS` 定位并补显式授权 +- [`addon-instanceset-image-tag-alias-readiness-trap-guide.md`](addon-instanceset-image-tag-alias-readiness-trap-guide.md) — sideload 后 pod 已 Ready 但 InstanceSet 仍 NotReady 这一具体表现的诊断 + 多节点收口;同 digest 多 tag 的成因和通用修法(删 base alias / chart digest pin)参考 [`../agent-collab/addon-patch-image-build-handoff-roles-guide.md`](../agent-collab/addon-patch-image-build-handoff-roles-guide.md) 的 "Sideload + tag 别名陷阱" 一节 diff --git a/docs/troubleshoot/addon-instanceset-image-tag-alias-readiness-trap-guide.md b/docs/troubleshoot/addon-instanceset-image-tag-alias-readiness-trap-guide.md new file mode 100644 index 0000000..2689e49 --- /dev/null +++ b/docs/troubleshoot/addon-instanceset-image-tag-alias-readiness-trap-guide.md @@ -0,0 +1,95 @@ +# Addon 排障 — InstanceSet readiness 卡在 image tag alias 字符串错配 + +> **Audience**: addon dev / test / TL,sideload patch image 后 InstanceSet 仍 NotReady 时排障 +> **Status**: stable +> **Applies to**: 任何用 ctr import 旁路加载 image 的 KB addon +> **Applies to KB version**: KB 1.2.x(InstanceSet 引入 `isImageMatched` 严格 tag 比较后才会触发) +> **Affected by version skew**: 主要受 KB controller 版本影响;release-1.0 / release-1.1 InstanceSet 未做严格 tag string 比较,不命中本 trap + +sideload 一个 patch image 后,pod 已经 Running、容器 Ready、roleProbe OK,但 InstanceSet 仍报 `readyReplicas=0` / `InstanceReady=False`,Cluster / Component 卡在 `Updating`。这通常不是 controller bug,也不是 cluster spec 错;是节点上同 digest 同时挂多个 tag,导致 pod `.spec.containers[*].image` 和 `.status.containerStatuses[*].image` 文本不一致。 + +## 先用白话理解这篇文档 + +InstanceSet 现在用严格字符串比较来判断 pod 镜像是否就位:`spec.image` 必须和 `status.image` 一模一样。但节点上的 container runtime 可能给同一个 digest 挂多个 tag — kubelet 报回的 tag 是节点上同 digest 镜像里的某一个,不一定是 spec 里指定的那个;选哪个不在 InstanceSet 控制范围内。结果是镜像内容完全对,但文本对不上,InstanceSet 永远等不到 Ready。 + +> **同 digest 多 tag 的成因和通用修法(删 base alias / chart digest pin)已经在 `agent-collab/addon-patch-image-build-handoff-roles-guide.md` 的 "Sideload + tag 别名陷阱" 一节里讲过;本篇只补 InstanceSet readiness 这一具体表现 + 多节点诊断细节。** + +## 适用场景 + +| 现象 | 是不是这个 trap | +|---|---| +| Pod 3/3 Running,role label 已发布 | 是 | +| `kubectl describe pod` 容器 image 与 spec image 字符串不同 | 是 | +| 两者 imageID 是同一个 sha256 digest | 是 | +| spec image 用了 patch tag,节点 ctr 同 digest 还挂着 base tag | 是 | +| Pod 整体没起 / ImagePullBackOff | 不是(是另一个 trap) | + +## 通用方法论 — InstanceSet 这一具体表现的诊断 + 收口 + +### 第一步 — 确认是字符串错配而不是真 not ready + +```bash +kubectl -n get pod -o jsonpath='{range .spec.containers[*]}spec[{.name}]={.image}{"\n"}{end}{range .status.containerStatuses[*]}status[{.name}]={.image} digest={.imageID}{"\n"}{end}' +``` + +- spec 与 status 的 `image` 字段任意一个容器不一致,且对应 `imageID` digest 一致 → 命中本 trap。 +- digest 也不一致 → kubelet 真的拉到了另一个镜像,不在本 trap 范围。 + +### 第二步 — 收口前先确认"所有可能落点节点"都清理 + +InstanceSet 的 bounce-and-reschedule 行为意味着窄修必须覆盖 cluster 可能调度到的全部节点,而不仅是当前 pod 所在节点: + +```bash +# 列 cluster 所有 pod 的当前节点 +kubectl -n get pod -l app.kubernetes.io/instance= -o wide + +# 列该 instanceset 可调度的节点(按 nodeSelector / tolerations / affinity 推断) +kubectl -n get instanceset -o yaml | yq '.spec.template.spec.{nodeSelector, tolerations, affinity}' +``` + +把这些节点都列进清理清单。只清当前 pod 节点会导致下一轮调度漂到别的节点后 trap 复发。 + +### 第三步 — 按 `agent-collab/addon-patch-image-build-handoff-roles-guide.md` 的两条出口任选其一 + +**出口 A — 节点侧删 base alias**:在第二步列出的 **每一个** 节点上执行 `ctr -n k8s.io images rm :` 删掉同 digest 的 base / 旧 tag,只保留 patch tag。digest 数据不受影响(只删一个别名)。 + +**出口 B — chart 改 digest pin**:把 chart image 引用从 `repo:tag` 改成 `repo@sha256:...`。controller 收到 digest pin 后会用 `status.imageID` 兜底比较,避开 tag 唯一性依赖。 + +### 第四步 — bounce pod,让 kubelet 重报告 + +```bash +kubectl -n delete pod +``` + +bounce 后等 pod 恢复 Ready。 + +### 第五步 — 验证 InstanceSet 收敛 + +```bash +kubectl -n get instanceset # READY=N AVAILABLE=N +kubectl -n get cluster # STATUS=Running +kubectl -n get component -l app.kubernetes.io/instance= # STATUS=Running +``` + +bounded retry 直到三条都收敛。 + +## 防复发 checklist + +- [ ] sideload 时不要顺手 `ctr tag `。`imagePullPolicy=Never` 只需要 patch tag 存在即可。 +- [ ] 如果 chart 默认配置依赖 base tag,先确认 chart 是否真的不能改 image override;如果能改,让 spec 指向 patch tag 并保持单 tag。 +- [ ] 多节点 sideload 时,每个节点上的 tag 集合保持一致。 +- [ ] sideload 完成后立刻跑第一步对比,确认 `imageID` 与 `image` 文本一致再继续后续验证。 + +## 与其他 doc / skill 的关系 + +- [`../agent-collab/addon-patch-image-build-handoff-roles-guide.md`](../agent-collab/addon-patch-image-build-handoff-roles-guide.md) — sideload + tag 别名陷阱的成因 + 两条通用出口,本篇的前置阅读。 +- [`../test/addon-patch-image-controller-active-verification-guide.md`](../test/addon-patch-image-controller-active-verification-guide.md) — sideload 后 controller live 验证口径。 +- [`addon-sideloaded-patch-image-gc-pin-guide.md`](addon-sideloaded-patch-image-gc-pin-guide.md) — sideload 镜像 GC pin。 + +## 案例附录 + +[`../cases/mariadb/mariadb-instanceset-tools-tag-alias-recurrence-case.md`](../cases/mariadb/mariadb-instanceset-tools-tag-alias-recurrence-case.md) — 同一晚 3 次复发的多节点窄修现场。 + +## 一句话总结 + +InstanceSet 用 `spec.image == status.image` 严格文本比较;节点上同 digest 多 tag 是默默触发这条等式失败的源头;定位用 jsonpath 对比,修法按 patch-image 出口 A 或 B 做,并且要覆盖 instanceset 可能调度到的**全部节点**而不只是当前 pod 节点。 From 78cb3596d9064b57904e6e18bab1cf697e15b8ae Mon Sep 17 00:00:00 2001 From: Wei Cao Date: Tue, 19 May 2026 14:00:19 +0800 Subject: [PATCH 2/2] docs: polish MariaDB sediment guides --- .../test/addon-kb-controller-v1-log-level-verification-guide.md | 2 +- .../addon-instanceset-image-tag-alias-readiness-trap-guide.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/test/addon-kb-controller-v1-log-level-verification-guide.md b/docs/test/addon-kb-controller-v1-log-level-verification-guide.md index 22dabaf..bca814c 100644 --- a/docs/test/addon-kb-controller-v1-log-level-verification-guide.md +++ b/docs/test/addon-kb-controller-v1-log-level-verification-guide.md @@ -4,7 +4,7 @@ > **Status**: stable > **Applies to**: 任何依赖 `logger.V(1).Info(...)` 输出的诊断镜像 / 诊断 PR > **Applies to KB version**: KB 1.0.x / 1.1.x / 1.2.x(zap log level 默认 info,不分版本) -> **Affected by version skew**: partial — `logger.V(N)` 默认被过滤是 zap + controller-runtime 通用默认,跨 KB 版本一致;但实际启用 verbose 的 flag 名(`--zap-log-level=N` vs `--zap-devel` vs 其它)、controller-runtime 版本对 `V(N)` 与 `debug` 文本映射、日志输出格式(JSON / console / awk 切分约定)在不同 KB 版本可能不同,sideload 诊断镜像前要先按本篇第二步在 live controller 上核 args 与一份示例日志确认 +> **Affected by version skew**: yes — `logger.V(N)` 默认被过滤是 zap + controller-runtime 通用默认;但实际启用 verbose 的 flag 名(`--zap-log-level=N` vs `--zap-devel` vs 其它)、controller-runtime 版本对 `V(N)` 与 `debug` 文本映射、日志输出格式(JSON / console / awk 切分约定)在不同 KB 版本可能不同,sideload 诊断镜像前要先按本篇第二步在 live controller 上核 args 与一份示例日志确认 接到一个 KB controller 诊断镜像(例如某诊断 PR 把多个 V(1) 日志加到 Reconfigure 路径),sideload 进 vcluster controller,跑测试时却看不到任何 V(1) 日志,怀疑 PR 没生效或现场没进入对应代码。真正原因往往是 controller 默认 `--zap-log-level=info`,logger.V(1) 在 controller-runtime + zap 组合下被过滤掉了。诊断镜像本身没问题,是日志级别没开。 diff --git a/docs/troubleshoot/addon-instanceset-image-tag-alias-readiness-trap-guide.md b/docs/troubleshoot/addon-instanceset-image-tag-alias-readiness-trap-guide.md index 2689e49..c039591 100644 --- a/docs/troubleshoot/addon-instanceset-image-tag-alias-readiness-trap-guide.md +++ b/docs/troubleshoot/addon-instanceset-image-tag-alias-readiness-trap-guide.md @@ -12,7 +12,7 @@ sideload 一个 patch image 后,pod 已经 Running、容器 Ready、roleProbe InstanceSet 现在用严格字符串比较来判断 pod 镜像是否就位:`spec.image` 必须和 `status.image` 一模一样。但节点上的 container runtime 可能给同一个 digest 挂多个 tag — kubelet 报回的 tag 是节点上同 digest 镜像里的某一个,不一定是 spec 里指定的那个;选哪个不在 InstanceSet 控制范围内。结果是镜像内容完全对,但文本对不上,InstanceSet 永远等不到 Ready。 -> **同 digest 多 tag 的成因和通用修法(删 base alias / chart digest pin)已经在 `agent-collab/addon-patch-image-build-handoff-roles-guide.md` 的 "Sideload + tag 别名陷阱" 一节里讲过;本篇只补 InstanceSet readiness 这一具体表现 + 多节点诊断细节。** +> **同 digest 多 tag 的成因和通用修法(删 base alias / chart digest pin)已经在 [`../agent-collab/addon-patch-image-build-handoff-roles-guide.md`](../agent-collab/addon-patch-image-build-handoff-roles-guide.md) 的 "Sideload + tag 别名陷阱" 一节里讲过;本篇只补 InstanceSet readiness 这一具体表现 + 多节点诊断细节。** ## 适用场景