Skip to content
Closed
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
1 change: 1 addition & 0 deletions docs/SKILL-INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
- [`addon-test-runner-portability-guide.md`](addon-test-runner-portability-guide.md) — 跨平台兼容坑 8 条:macOS bash 3.2 + `set -euo pipefail` 下 7 个 + GNU `seq` vs BSD `seq` zero-count 1 个(空数组、env-default 时机、`local x=$(cmd)`、`seq 1 0` 等)
- [`addon-test-runner-cadence-discipline-guide.md`](addon-test-runner-cadence-discipline-guide.md) — 长时 runner 操作期间的固定节奏汇报纪律;cadence 是操作者义务,触发器必须独立于 runner 进程
- [`addon-test-runner-write-after-bounded-role-gate-guide.md`](addon-test-runner-write-after-bounded-role-gate-guide.md) — runner 任何写步骤必须在 bounded、多视角 role-publish gate 之后;gate 通过后下游 primary 必须从 gate 重锚;gate 写探针只允许在 K8s role-label primary 上执行,secondary 不允许写复制表(避免内部 admin 用户穿透 read_only 后撞 1062);含 alpha.58 preload-after-gate 与 alpha.59 secondary write probe 两条 case
- [`addon-runner-openapi-schema-fetch-brittleness-guide.md`](addon-runner-openapi-schema-fetch-brittleness-guide.md) — `kubectl apply` 默认会先拉 OpenAPI schema 做 client-side 校验,远端 API 抖动会让 apply 在拉 body 时 cancel;只在 brittle apply 点用 `--validate=false` helper(不是全局 alias)关 client-side validate,server-side admission 仍生效;改动归 runner-harness 通道硬化,**不**作为产品 / addon / KB 稳定证据;apply 失败 3 类分类(server admission / RBAC / network transient);附 OceanBase enterprise addon T-PITR.4 撞 OpenAPI fetch 瞬时失败 + helper 落地案例(仅作 motivation,不外推 release-ready)

### 6. 协作 / GitHub 提交纪律

Expand Down
222 changes: 222 additions & 0 deletions docs/addon-runner-openapi-schema-fetch-brittleness-guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
# Addon Test Runner — `kubectl apply` Client-Side OpenAPI Fetch Brittleness and Safe `--validate=false` Use

> **Audience**: addon test engineer / TL / runner harness owner
> **Status**: stable methodology
> **Applies to**: any test harness that issues `kubectl apply -f -` (or `kubectl apply -f <file>`) against a remote / unstable k8s API entry where the client may hang on OpenAPI schema download.
> **Applies to KB version**: any.

属于:方法论主题文档(不绑定单一引擎)。

## 1. 这篇要解决的问题

`kubectl apply` 默认在执行前会先从 API server 拉一份 **OpenAPI schema** 做客户端校验(client-side validate)。这条路径有两个隐藏风险:

1. **OpenAPI schema 拉取走的是独立的 HTTP body 流**,远端 API 一旦慢/抖动,body 读到一半 `Client.Timeout exceeded while awaiting headers` / `request canceled while waiting for connection` / `context deadline exceeded`,**整个 `kubectl apply` 失败**,但实际 manifest 没有问题,apiserver 也没有 admission 拒绝。
2. **测试 runner 在这个时刻没有信号能区分**「manifest 错」/「server 真的拒了」/「client 拉 schema 网络抖动」。三类都表现为 `kubectl apply` 退出 rc != 0。

实战表现(参考附录 A):在一个 PITR runtime 测试的 T-PITR.4 阶段,`kubectl apply -f -` 撞 `failed to download openapi: ... net/http: request canceled (Client.Timeout or context cancellation while reading body)`,OpsRequest 没有被创建;client retry 后正常。**这不是 manifest 错,也不是 server 拒**——是 client validate 路径的网络抖动。

**目标**:把 client-side schema fetch 这条 brittle path 从 runner critical path 中剥离;但**前提是不放松实际的 server-side admission validation**。

> **边界 framing**:这一篇只讲 `kubectl apply` 的 client-side validation 关闭条件;不是说"所有 validation 都可以关"。server-side admission(CR 字段约束、webhook、defaulting)必须仍然生效。

## 2. 硬规则

### 规则 A — Server-side admission 不可放松;client-side validate 可在 brittle 路径上关闭

```
client side:kubectl pull OpenAPI -> validate manifest fields against schema
↓ pass
server side:apiserver admission -> CR validation webhook -> defaulting -> persist
```

`--validate=false` 关掉的是上面**第一层**(client side)。第二层(server side)由 apiserver 决定,关不掉。

**判断「safe to disable client validate」的两个充分条件**:

1. 这个 apply 的 manifest **CR kind 在 cluster 上有 server-side validation**(CRD `openAPIV3Schema` 已下发,或 admission webhook 起作用)。
2. 该 manifest 是 **runner 自己模板化生成**(不是用户手写),错的可能性只来自 runner 模板 bug,不是 typo / 字段名错。

两个条件都成立时,client validate 关掉的语义损失 ≈ 0;网络稳定性收益 = 消除 OpenAPI fetch 这一类瞬时失败。

### 规则 B — 关 client validate 的写法只对 brittle 路径上的 apply 用,不全局关

```bash
# 反例:全局 alias
alias kubectl='kubectl --validate=false'
```

```bash
# OK:只在被识别的 brittle apply 点用
kubectl apply --validate=false -f - <<YAML
...
YAML
```

- 全局关 = 把语义损失扩大到所有 apply 调用,可能把别的真错也忽略。
- 局部关 = 只在已经撞过 OpenAPI fetch 失败的 apply 点用,scope 收敛。

### 规则 C — runner / harness 把这个改动当作 **runner-side hardening**,**不能**写成产品 / addon / KB 修复证据

`--validate=false` 是 test harness 的代码改动;它**不**改变:
- addon ActionSet
- ComponentDefinition
- ComponentVersion
- KubeBlocks controller
- 引擎本身

closeout 模板里这个改动归到 **runner-harness 通道硬化**,与 runtime 结果(T1 / postReady / readback)解耦,**不**作为「产品稳定 / addon 稳定 / KB 稳定」的证据。

### 规则 D — apply 失败的分类必须区分三种 brittle 路径

```bash
if ! kubectl apply --validate=false -f - <<YAML
...
YAML
then
rc=$?
# rc=1 通常意味着 server side 拒了(admission 错 / 字段错)
# 如果带 client-side network error 关键字(unlikely with --validate=false 但仍可能拉 server health),分类到 env
# 如果带 RBAC denied,分类到 env / RBAC
...
fi
```

`--validate=false` 后,apply 失败的可能来源缩减到:

1. **server admission 拒绝**(manifest 真的违反 schema / webhook 拒)→ runner-口径 first-blocker(manifest 是 runner 写的,错责归 runner)。
2. **RBAC denied** → env / 权限 first-blocker。
3. **apiserver entry transient timeout(罕见,因 schema fetch 已绕过)** → env / network first-blocker。

分类清晰才能 closeout 写得清。

### 规则 E — 把所有 brittle apply 点收敛到一个 helper

```bash
# lib/common.sh
addon_apply_brittle() {
# for manifests that are runner-templated and may run against unstable api entry
kubectl apply --validate=false -f - "$@"
}
```

后续凡是这一类 apply 都走 helper:

```bash
addon_apply_brittle <<YAML
apiVersion: operations.kubeblocks.io/v1alpha1
kind: OpsRequest
...
YAML
```

好处:

- 局部关 client validate 的所有点 grep `addon_apply_brittle` 一次扫描。
- 后续要改回 strict(比如换稳定 apiserver entry),改一处。
- closeout patch-gate 可以 grep helper 出现次数确认覆盖。

## 3. 测试侧 patch-gate

新加 / 改 brittle apply 点后维护:

```
brittle_apply_helper_in_use=<int> # grep -c 'addon_apply_brittle' tests/*.sh
brittle_apply_raw_kubectl=<int> # grep -cE 'kubectl apply ' tests/*.sh - 减去用 helper 的;期望 0 或可知列表
brittle_apply_uses_validate_false=<bool> # helper 内部含 --validate=false
```

patch-gate 仅证明 helper 在位与覆盖度,**不**证明产品 / addon 稳定。

## 4. PR 评审 checklist

5 条:

1. `--validate=false` 是否只在 helper / 已识别的 brittle apply 点用,不是全局 alias?
2. 同一 PR 中是否声明 server-side validation 仍生效(webhook / CRD schema 存在)?
3. 是否给 apply 失败的退出码 / reason 字符串分类(server admission / RBAC / network),不是 `exit 1` 一把抓?
4. closeout 模板有没有把这个改动当 runner-harness(不是产品修复)?
5. 旧的「曾撞过 OpenAPI fetch 失败」的样本有没有作为该改动的 motivation 单独记录(不外推 / 不当 release 证据)?

## 5. 反例 vs 正例

### 反例 1(全局 alias)

```bash
alias kubectl='kubectl --validate=false'
./tests/all.sh
```

任何 apply 都没有 client-side validation,未来手写 manifest 字段错误时只能等 server 拒——增加调试成本。

### 正例(helper + 局部)

```bash
# lib/common.sh
addon_apply_brittle() {
kubectl apply --validate=false -f - "$@"
}

# tests/some-test.sh
addon_apply_brittle <<YAML
apiVersion: operations.kubeblocks.io/v1alpha1
kind: OpsRequest
metadata:
name: $OPS
namespace: $NS
spec:
...
YAML
```

### 反例 2(把 helper 引入也当作产品修复)

closeout 写:「修了 PITR brittle apply,addon 稳定性增强」。这是把 runner-harness 改动外推成产品结论,违反 §2 规则 C。

修:closeout 写「helper 在位证明 (patch-gate);runtime 结果是否 PASS / FAIL 与 helper 是否使用解耦」。

## 6. 与其他 skill 的关系

- `addon-runner-incluster-vcluster-access-pattern-guide.md` — runner pod 在 host k8s 内的方案 (3) 把 workstation 网络踢出 critical path,结构性消除大部分 brittle apply 场景;本 doc 是局部补丁,二者并行有效。
- `addon-test-acceptance-and-first-blocker-guide.md` — §2 规则 D 的 3 类分类与该 doc 的 first-blocker 5 层兼容。
- `addon-evidence-discipline-guide.md` — runner-harness 改动不外推为产品稳定,与该 doc 同源。
- `addon-postready-bounded-timeout-failure-classification-guide.md` — 长操作的 bounded timeout 与 apply 的 brittle path 是两条正交规则;本 doc 不替代 postReady 时序问题的修复。

## Appendix A — OceanBase enterprise addon 案例

### Case A.1 — T-PITR.4 撞 OpenAPI fetch 瞬时失败

在 OceanBase enterprise addon PITR runtime 测试早期(RUN_ID `pitr-runtime-postreadyfix-N2-...`)从 workstation Mac 跑 `kubectl apply -f -` 创建 Restore OpsRequest 时撞:

```
error: error validating "STDIN": error validating data: failed to download
openapi: unexpected error when reading response body. Please retry.
Original error: net/http: request canceled (Client.Timeout or context
cancellation while reading body); if you choose to ignore these errors,
turn validation off with --validate=false
```

后续 5 次 workstation→idc4 host k8s API `/version` 探测全部成功,确认 apiserver 已稳定;manifest 本身无误。**这次 apply 失败的原因仅是 client 拉 OpenAPI schema 时 body 读 cancel**。

按 §2 规则 D 分类 = `apiserver entry transient → env / network first-blocker`,**不是** OpenAPI schema 内容错、**不是** server admission 拒绝、**不是** runner manifest typo。

### Case A.2 — runner-side helper landing

修法:把 `tests/pitr.sh` 的两处 `kubectl apply -f -`(T-PITR.1 Backup + T-PITR.4 Restore OpsRequest)改成 `kubectl apply --validate=false -f -`:

- `tests/pitr.sh` sha: `6edf74fce835863b59226056b326d49cd2f5a67fdaeb88aa9d475569d415ef3e` → `f4b3375e36064b47150a8f82a6598c59b7809a6b1ae2b969ca4b4d78f147b51b`
- `bash -n` PASS
- server-side admission 仍跑(KB OpsRequest CRD `openAPIV3Schema` 在 v1.0.2 已下发;webhook 仍生效)

后续 N=2 candidate (RUN_ID `pitr-runtime-runner-hardening-N2-...`) 撞 OpenAPI fetch 之处即变成 `T-PITR.4 PASS phase=Succeed`。**单样本仅证明本次 apply 没踩到 schema fetch;不证明 helper 消除全部网络抖动风险**。

### Case A.3 — 切换路径后 helper 收益与残留

OceanBase enterprise addon 之后又切到 `addon-runner-incluster-vcluster-access-pattern-guide.md` 的方案 (3)(runner pod 在 host k8s 内 + in-cluster service DNS),方案 (3) 把这一类 workstation-side brittle path 结构性消除。helper 仍保留作为兜底;二者**不冗余**:

- 路径 (3) 收的是「workstation → host k8s API entry 」这条 hop。
- helper 收的是「pod 内 client → vcluster API server schema fetch」这一类(如果 vcluster apiserver 本身也抖)。

边界声明:3-sample 在 in-cluster DNS 路径下 helper 没再被实际触发;不能反过来说 helper 不再需要。

附录到此为止。其它引擎或其它 brittle apply 场景请另起 appendix。