From b051bdb30b48173f6dc3c69a9a0a760ce07607f4 Mon Sep 17 00:00:00 2001 From: Wei Cao Date: Tue, 19 May 2026 13:39:00 +0800 Subject: [PATCH] docs(troubleshoot+cases): add reconfigure persist-race guide and case Add an engine-neutral troubleshooting guide for reconfigure actions that only apply runtime state, plus a MariaDB semisync case that shows how a process-level restart can make OpsRequest success revert to chart defaults. The guide documents trigger conditions, symptom checks, evidence collection, a two-layer defense with ParametersDefinition and persisted override files, and a verify gate. The MariaDB case keeps the engine-specific evidence table, timeline, chart fix path, boundaries, and artifact hashes. Update the troubleshoot README and MariaDB case count in SKILL-INDEX. --- docs/SKILL-INDEX.md | 2 +- ...re-set-global-without-persist-race-case.md | 80 ++++++++++ docs/troubleshoot/README.md | 1 + ...configure-set-global-persist-race-guide.md | 150 ++++++++++++++++++ 4 files changed, 232 insertions(+), 1 deletion(-) create mode 100644 docs/cases/mariadb/mariadb-reconfigure-set-global-without-persist-race-case.md create mode 100644 docs/troubleshoot/addon-reconfigure-set-global-persist-race-guide.md diff --git a/docs/SKILL-INDEX.md b/docs/SKILL-INDEX.md index ad5a152..87aee16 100644 --- a/docs/SKILL-INDEX.md +++ b/docs/SKILL-INDEX.md @@ -31,7 +31,7 @@ 引擎特定的现场材料按引擎分组,每个引擎一个子目录: -- [`cases/mariadb/`](cases/mariadb/) — 15 个 mariadb addon 案例 +- [`cases/mariadb/`](cases/mariadb/) — 16 个 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-reconfigure-set-global-without-persist-race-case.md b/docs/cases/mariadb/mariadb-reconfigure-set-global-without-persist-race-case.md new file mode 100644 index 0000000..808d7a4 --- /dev/null +++ b/docs/cases/mariadb/mariadb-reconfigure-set-global-without-persist-race-case.md @@ -0,0 +1,80 @@ +# MariaDB reconfigure SET GLOBAL × ConfigMap sync race 案例 + +> **Audience**: addon dev / test 在 reconfigure / Reconfiguring OpsRequest 场景,特别是涉及 SET GLOBAL + restart-prone 拓扑 +> **Status**: stable (root cause closed, alpha.84 chart fix landed) +> **Applies to**: MariaDB addon semisync 拓扑 +> **Applies to KB version**: KB 1.2.0-alpha.1(+ 5 fixes + V(1) 诊断 + kbagent pullPolicy fix) +> **Affected by version skew**: no — 由 addon 自身写法决定,跨 KB 版本一致 + +通用方法论见 [`../../troubleshoot/addon-reconfigure-set-global-persist-race-guide.md`](../../troubleshoot/addon-reconfigure-set-global-persist-race-guide.md);本案例只保留 MariaDB-specific 现场材料。 + +## MariaDB 特有的根因链 + +1. semisync 拓扑没有声明自己的 `ParametersDefinition`(chart 漏写)→ KB Configure controller 不知道 `slow_query_log` / `long_query_time` 是 dynamic → 默认走 rolling restart 路径 +2. rolling restart 在 semisync 拓扑里表现成 switchover / promote → 新主 mariadbd 进程级 restart +3. chart 的 `reconfigureAction` 只跑 `SET GLOBAL`(运行时生效),不落任何持久化文件 +4. mariadbd restart 时机点上 `/etc/mysql/conf.d/my.cnf` 还没收到 kubelet 的 ConfigMap volume 同步(典型几秒到约 60s 延迟)→ mariadbd 读旧 my.cnf → 加载 chart 默认值 +5. SET GLOBAL 应用的运行时状态被 restart 抹掉,最终运行时 = chart 默认 + +## 现场证据(alpha.83 clean R1) + +| 字段 | pod0(原 primary → 当前 secondary) | pod1(原 secondary → 当前 primary) | +|---|---|---| +| OpsRequest phase | Succeed | Succeed | +| ComponentParameter | Finished, succeedCount=2 / expectedCount=2, revision=4 | 同 | +| kbagent action stdout | reconfigure success | reconfigure success @ 20:42:11 | +| K8s container restartCount | 0 | 0 | +| K8s container startedAt | 未变 | 未变 | +| mariadbd PID | 未变 | **20:42:33 新 PID**(进程级 restart) | +| mariadb error log shutdown | 无 | `Normal shutdown` 20:42:29 | +| /etc/mysql/conf.d/my.cnf 内容 | slow_query_log=ON | slow_query_log=ON(**但更新时间晚于 mariadbd restart**) | +| `SHOW GLOBAL VARIABLES slow_query_log` | ON ✓ | **OFF** ✗ | +| `SHOW GLOBAL VARIABLES long_query_time` | 3 ✓ | **10** ✗ | + +## 时间线(pod1 的窗口) + +``` +T+0 reconfigure action runs SET GLOBAL slow_query_log=ON on both pods +T+10s ConfigMap update commits to etcd +T+15s KB triggers switchover/promote (PD 缺失 → 默认 rolling restart 路径) +T+15s pod1 mariadbd receives SIGTERM, normal shutdown +T+18s pod1 mariadbd new process starts + reads /etc/mysql/conf.d/my.cnf — STILL OLD (kubelet not synced yet) + loads slow_query_log=OFF (chart default) +T+45s kubelet ConfigMap volume sync completes + /etc/mysql/conf.d/my.cnf now has slow_query_log=ON + but mariadbd is already in-memory running with OFF +T+60s test checks SHOW GLOBAL VARIABLES → OFF ✗ +``` + +## 修法(alpha.84,4 处 chart 改动) + +1. 补 `mariadb-semisync-pd` 到 `paramsdef.yaml` — `componentDef: ^mariadb-semisync-`、`templateName: mariadb-semisync-config`、dynamic/static 复用同一份 `mariadb-config-effect-scope.yaml` +2. `mariadb-semisync.tpl` 末尾追加 `!includedir /var/lib/mysql/runtime-overrides.d/` +3. `cmpd-semisync.yaml` init-syncer 加 `mkdir -p {{ .Values.dataMountPath }}/runtime-overrides.d` +4. `mariadb.config.reconfigureAction` helper:SET GLOBAL 成功后写 `${OVERRIDES_DIR}/.cnf`(per-param file,temp + atomic rename,写入失败 WARN-only 不 fail action) + +L1 修法(补 PD)让常规路径不再 restart;L2 修法(持久化)兜底 OOMKill / 节点驱逐 / 手动维护等不可控 restart 路径。两层都做才闭环。 + +## 边界 + +- 适用拓扑:semisync(有 promote/switchover 路径 + SET GLOBAL reconfigureAction) +- replication / standalone 拓扑也写了 SET GLOBAL reconfigureAction,但 restart 路径较少;如果用户手动 `kill -TERM mariadbd` 也会丢值;建议同样落 L2 持久化(已含在 alpha.84 chart helper 修法里) +- alpha.85 / alpha.88 后续版本是同一修法路径的 chart-immutability 迭代,本质上不变 + +## 现场证据归档 + +- 失败样本 evidence tar sha:`9502a88ff4a89c17af87649f83c9662bab1b51a699c77e3541a3dd53115fec67` +- summary md sha:`4fa0f12f602bf5f3d19377987f83f1a27e92f9d4bdf0588bf99e32422f8e2af2` +- 修法 chart 版本:MariaDB addon `1.1.1-alpha.84+`(实际 land 版本含 alpha.85 + alpha.88 修复层) + +## 相关文档 + +- 通用方法论:[`../../troubleshoot/addon-reconfigure-set-global-persist-race-guide.md`](../../troubleshoot/addon-reconfigure-set-global-persist-race-guide.md) +- 同主题 MariaDB 案例:[`cm1-dynamic-reload-case.md`](cm1-dynamic-reload-case.md)(reloadAction 未落 live PD)+ [`cm4-bounded-window-helper-semantic-bug-case.md`](cm4-bounded-window-helper-semantic-bug-case.md)(CM4 bounded window) +- 设计入口:[`../../design/addon-reconfigure-guide.md`](../../design/addon-reconfigure-guide.md) +- evidence-discipline:[`../../test/addon-evidence-discipline-guide.md`](../../test/addon-evidence-discipline-guide.md) + +## 一句话总结 + +MariaDB semisync 这次不是 OpsRequest 计数错,而是 `SET GLOBAL` 未持久化撞上进程级 restart;PD + PVC override 两层修法一起落才闭合。 diff --git a/docs/troubleshoot/README.md b/docs/troubleshoot/README.md index cf65203..d468ce3 100644 --- a/docs/troubleshoot/README.md +++ b/docs/troubleshoot/README.md @@ -18,3 +18,4 @@ - [`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 别名陷阱" 一节 +- [`addon-reconfigure-set-global-persist-race-guide.md`](addon-reconfigure-set-global-persist-race-guide.md) — reconfigureAction 只跑 `SET GLOBAL` / `CONFIG SET` 等运行时语句不落持久化文件时,遇到引擎进程级 restart(switchover promote / OOMKill / 节点驱逐 / KB 默认 rolling restart)会让 OpsRequest phase=Succeed 但运行时回 chart 默认;防御两层:补 ParametersDefinition + reconfigureAction 同步写 PVC 持久化 override diff --git a/docs/troubleshoot/addon-reconfigure-set-global-persist-race-guide.md b/docs/troubleshoot/addon-reconfigure-set-global-persist-race-guide.md new file mode 100644 index 0000000..d6aa00d --- /dev/null +++ b/docs/troubleshoot/addon-reconfigure-set-global-persist-race-guide.md @@ -0,0 +1,150 @@ +# reconfigure 只跑 runtime apply 不持久化 × restart race + +> **Audience**: 任何写 addon `reconfigureAction` 的人;常用引擎 `SET GLOBAL` / `CONFIG SET` / `ALTER SYSTEM` / `redis-cli CONFIG SET` 等"运行时生效"语句 +> **Status**: stable methodology +> **Applies to**: 任何 addon 的 `reconfigureAction` / `reloadAction` 只用引擎 runtime-apply 语句改参数、不在动作里持久化到引擎重启可读路径的场景 +> **Applies to KB version**: KB 1.0+,与 KB 控制器版本无关;触发条件是 addon `reconfigureAction` 写法本身 +> **Affected by version skew**: no — addon `reconfigureAction` 写法决定,跨 KB 版本一致 +> **Engine-neutral**:引擎相关现场材料见各 case,如 [`../cases/mariadb/mariadb-reconfigure-set-global-without-persist-race-case.md`](../cases/mariadb/mariadb-reconfigure-set-global-without-persist-race-case.md) + +## 问题 + +OpsRequest Reconfigure 报 `phase=Succeed`,但被改的参数在引擎一次进程级 restart 之后悄悄回到 chart 默认值。这一类 false-success 出现的根因是:`reconfigureAction` 只用引擎的"运行时生效"语句把变更打到 in-memory state,并没有把变更落到引擎下一次启动时还能读到的持久化路径;任何 restart 路径(switchover / OOMKill / 节点驱逐 / KB 默认 rolling restart / 引擎自身 reload)都会把 in-memory state 抹掉,回到 chart 默认。 + +## 先用白话理解这篇文档 + +OpsRequest Reconfigure 报 `phase=Succeed` 不等于 invariant 持续。如果你的 `reconfigureAction` 只用引擎的"运行时生效"语句改参数(`SET GLOBAL` / `CONFIG SET` 等),但没把变更落到引擎重启后还能读到的文件里,那么遇到下面任何一个 restart 路径都会丢值: + +- 用户触发 switchover → 新主 promote 路径里可能重启引擎进程 +- 节点 OOMKill → kubelet 重建 container 或拉起新进程 +- 节点驱逐 / 维护 +- KB 自己因不知道这个参数是 dynamic 还是 static 而默认走 rolling restart +- 引擎自身的 SIGHUP / 重载策略 + +这篇 guide 给你一个统一的修法框架,让你的 reconfigureAction 通用地避开这一类 race。 + +## 触发条件 + +三件事同时成立才出问题: + +1. `reconfigureAction` 用引擎的"运行时生效"语句改参数,不落任何持久化文件 +2. 引擎进程在 reconfigure 窗口里发生 restart(任何路径) +3. 引擎 restart 时还没读到包含新值的 config 文件(典型 ConfigMap-as-volume kubelet sync 延迟) + +## 症状("长这样的 false-success") + +复合特征同时出现就大概率是这一类: + +- OpsRequest `phase=Succeed` +- ComponentParameter `phase=Finished`、`currentRevision == updateRevision`、`expectedCount == succeedCount` +- addon action attestation 所有 pod 都报 success +- 但业务层 `SHOW VARIABLES` / `CONFIG GET` / `SHOW LIKE` 至少一个 pod 还是 chart 默认值 +- K8s container `restartCount=0`,`containerStatuses[].state.startedAt` 没变 ← **这条非常容易误导** + +## 反例(不要这样判断) + +| 误判 | 反例为什么不成立 | +|---|---| +| "runner 抢答 / 测试时机不对" | bounded wait 后再次查仍是旧值 | +| "kbagent 没在所有 pod 都跑 action" | attestation 显式覆盖所有 pod 都 success | +| "controller succeedCount 算错" | 现场 pod kbagent action stdout 本来就 success;不是计数虚报 | +| "trap #4 image alias 还没清干净" | image identity 已 verify | +| "K8s container restartCount=0,所以没重启" | **容器没重启不等于引擎进程没重启**;进程级 restart 在容器视角是透明的 | + +## 区分这一类的关键证据 + +不能停在 k8s 层。必须进入 container 看进程层: + +1. **引擎 error log 的 shutdown / startup 序列**:reconfigure 窗口附近有没有 `shutdown` / `starting` / `ready` 序列? +2. **引擎主进程 PID 是否变化**:`ps -e | grep ` 在 reconfigure 前后是否同一个 PID? +3. **ConfigMap-mounted my.cnf / redis.conf 等的内容 vs mtime**:reconfigure 之后该文件内容是否反映新值?mtime 是什么时候? +4. **比较 config 文件更新时间 vs engine restart 时间**:如果 engine restart 在 config 文件更新之前,就是这一类 race +5. **switchover / role 变化**:reconfigure 窗口里有没有 promote/demote 事件?哪个 pod 变化了? + +如果这五条满足"engine 进程重启过 + config 文件落后于 engine restart" → race 成立。 + +## 修法(两个防御层都要做) + +### 防御层 L1:声明清楚 dynamic / static,避免 unnecessary restart + +- 给每个 topology / cmpd 写自己的 `ParametersDefinition`,不要共用一份不匹配的 +- `componentDef` 字段(regex 或精确)要真匹配你的 cmpd 名字 +- `templateName` 要匹配 cmpd 里 `configs[].name`,**不是** ConfigMap object 名(这是常见错位 — Galera 案例已经有这个 trap) +- `staticParameters` / `dynamicParameters` 列表完整覆盖你支持的参数 + +正确声明后,KB Configure controller 看到 dynamic 参数变更时不会触发 rolling restart,避免大多数 restart 路径。 + +### 防御层 L2:持久化 reconfigure 值 + +即使 PD 完美声明,仍有不可控的 restart 路径(OOMKill / 节点驱逐 / 手动维护)。reconfigureAction 必须把新值同时写到引擎重启后能读到的持久化路径。 + +通用模式: + +```sh +# 1. 引擎运行时生效(必做) +engine_cli "SET GLOBAL = " + +# 2. 持久化(必做,写到 PVC 等不依赖 ConfigMap sync 的路径) +OVERRIDES_DIR="/var/lib//runtime-overrides.d" +mkdir -p "$OVERRIDES_DIR" +override_tmp="$OVERRIDES_DIR/.cnf.tmp.$$" +{ + echo "# Written by reconfigureAction on $(date -u +%Y-%m-%dT%H:%M:%SZ)" + echo "[]" + echo " = " +} > "$override_tmp" && mv "$override_tmp" "$OVERRIDES_DIR/.cnf" +``` + +关键点: + +- **per-param 文件**,独立 reconfigure op 不互相覆盖 +- **temp + atomic rename**,被中断不会留半文件让 engine 启动失败 +- **WARN-only 失败模式**:写文件失败不让 action fail(SET GLOBAL 已成功,运行时已生效;持久化是 defense-in-depth) +- **写到 PVC 等持久化卷**,不要写到 ConfigMap 挂载的只读路径(写不进去),也不要写到 emptyDir(pod 重建丢失) + +### 让 engine 读到持久化文件 + +写完文件后还要让 engine 启动时读到。两条路径: + +**A. 通过 main config 文件 include**: + +```ini +# /etc//conf.d/main.cnf ← chart 的 ConfigMap 渲染 +[] +... chart defaults ... +!includedir /var/lib//runtime-overrides.d/ # ← include 在末尾,override 胜出 +``` + +注意 `!includedir` 的语义:包含目录里所有 .cnf 文件,silently 跳过不存在的目录。chart bootstrap 创建空目录以保证 directive 永远 safe。 + +**B. 通过 engine 启动参数 `--defaults-extra-file`**: + +如果引擎不支持 include 语法,可在 cmpd container args 加 `--defaults-extra-file=` 或等效。 + +## 测试 verify gate(任何 reconfigure fix 都应该过这 4 项) + +1. **PD 命中**:`kubectl get parametersdef ` Available;`dynamicParameters` / `staticParameters` 包含目标参数 +2. **不触发 rolling restart**:reconfigure 前后所有 pod UID + startTime + restartCount + role label 完全一致 +3. **OpsRequest Succeed 后所有 pod runtime 立刻反映新值**:bounded wait 不超过 5s +4. **强制 engine 进程 restart 后值仍保留**:手动 `kill -TERM` engine 进程,等被 kbagent / 容器 entrypoint 拉起,再次查 runtime 应该仍是新值(证明持久化文件被 engine 启动加载) + +**N≥3 fresh 集群** 4 项全过才算 axis closed。单次过不能写 release-ready。 + +## 给跨 addon 团队的 4 条可复用经验 + +1. **任何 runtime-apply 语句都要配持久化备份**。MariaDB `SET GLOBAL`、PostgreSQL `ALTER SYSTEM`(自带持久化但用法要对)、MySQL `SET PERSIST`(自带,对)、Valkey `CONFIG SET`(**不**自带,需要 `CONFIG REWRITE` 跟上)、Redis 同 +2. **PD 缺失会让 KB 默认 rolling restart**。每个 topology 上线时检查 PD componentDef regex 是否真的匹配 cmpd +3. **K8s container restartCount=0 不代表引擎进程没重启**。诊断要进 container 看进程层 +4. **OpsRequest phase=Succeed 不等于业务 invariant 持续**。测试必须 bounded wait + 再次查询确认未回退 + +## 相关文档 + +- [`../cases/mariadb/mariadb-reconfigure-set-global-without-persist-race-case.md`](../cases/mariadb/mariadb-reconfigure-set-global-without-persist-race-case.md) — MariaDB 现场材料 +- [`../cases/mariadb/cm1-dynamic-reload-case.md`](../cases/mariadb/cm1-dynamic-reload-case.md) — reloadAction 未落 live PD 的同主题相关案例 +- [`../design/addon-reconfigure-guide.md`](../design/addon-reconfigure-guide.md) — reconfigure 设计入口 +- [`../test/addon-evidence-discipline-guide.md`](../test/addon-evidence-discipline-guide.md) — "phase Succeed ≠ invariant 持续" 是 evidence-discipline Rule A 的具体形状 +- [`../test/addon-bounded-eventual-convergence-guide.md`](../test/addon-bounded-eventual-convergence-guide.md) — bounded wait 而非 single-snapshot 验证 + +## 一句话总结 + +`reconfigureAction` 只改运行时状态不落持久化文件时,OpsRequest 成功也可能在一次引擎进程 restart 后回到 chart 默认;修法是声明正确 PD 并把 reconfigure 值写进引擎启动会读取的持久化路径。