Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/SKILL-INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 案例
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <ns> get pod <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 <ns> delete pod <pod>

# 5. 验证 InstanceSet 收敛
kubectl -n <ns> 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 复发源后再补。
Original file line number Diff line number Diff line change
@@ -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,否则三档分桶证据无效。
1 change: 1 addition & 0 deletions docs/test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
107 changes: 107 additions & 0 deletions docs/test/addon-kb-controller-v1-log-level-verification-guide.md
Original file line number Diff line number Diff line change
@@ -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**: 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 组合下被过滤掉了。诊断镜像本身没问题,是日志级别没开。

## 先用白话理解这篇文档

诊断 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="<PR description>" --oneline
git show <commit> -- 'controllers/.../*.go'
grep -n 'logger.V(' <changed-files>
```

- `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 已开启"这个前置。
1 change: 1 addition & 0 deletions docs/troubleshoot/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 别名陷阱" 一节
Loading