Files
admin 68c0a320ed refactor(k8s): 删除 K8s 命名的死接口 LivePod;修正 K8s 文档横幅重复
1) src/supervisor/spawner.ts:LivePod —— 注释此前已中性化,但**类型名来自 K8s 的
   Pod**;全仓只有声明那一处出现(0 引用 = 死代码)⇒ 整块删除。属 cf8b7b1 的漏项。

2) docs/k8s-deploy.md · docs/k8s-deployment.md:cf8b7b1 里那行状态横幅被**重复
   插入 2 次并挤在同一行**(生成脚本当时不幂等,每跑一次多插一条)⇒ 收敛为 1 条。
   脚本已补「H1 后已有横幅就不再插」的负向断言 + 一条把重复收敛成 1 条的自愈规则。

验证:tsc --noEmit exit 0;npm test 36 测试 / 35 通过 / 0 失败 / 1 跳过;
LivePod 全仓(含 lib/)0 命中;两份文档横幅各 1 条。
2026-09-15 06:51:48 +08:00

398 lines
21 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# K8s 踩坑记录(模式 B)— 部署流程 + 根因排查
> ⚠️ **2026-09-15:模式 B 的 K8s 后端代码已从本仓下线**(`k8s-spawner` / `leader` / `reconcile` / `k8s-user-fs` / `tcp-bridge` / `web/file-service` 六个模块 + 2 个测试 + 1 个脚本)。备份:`D:\github\_dsh_shenxian_K8s后端备份_20260915\`。本文按「**历史设计稿 + 未来可选路线**」保留;文中的 `dshs file-service` / `dshs tcp-bridge` 两条子命令**现已不存在**,照着敲会 `command not found`。
> 🧭 [← 返回 README](../README.md) · 分步教程:[K8s 部署教程](k8s-deployment.md)
> 记录模式 B 实机部署踩到的坑与根因:先在**阿里云 2C2G 单机 k3s** 上 PoC 验证,后在 **ACK 智能托管**上完整落地(§5–§8 含部署流程实录)。
> 想直接照着部署,从 [k8s-deployment.md](k8s-deployment.md) 开始;本文按「症状 → 根因 → 修复」组织,用于卡住时对查。
---
## 1. 环境 / k3s 安装
### 1.1 cgroup v1 导致 k3s v1.36+ kubelet 拒启
- **症状**:k3s v1.36 启动几秒后退出,`systemctl` 反复 auto-restart。日志:
```
Shutdown request received: "kubelet exited: ... kubelet is configured to not run on a host using cgroup v1 ..."
```
- **根因**:K8s 1.36 移除了 cgroup v1 支持;阿里云 Linux 3(RHEL8 系)默认跑 cgroup v1。
- **修复(二选一)**:
- 启用 cgroup v2(改内核参数 + **重启**):
```bash
grubby --update-kernel=ALL --args="systemd.unified_cgroup_hierarchy=1"
grubby --info=ALL | grep args # 确认参数已写入
reboot
# 重启后验证
stat -fc %T /sys/fs/cgroup/ # 输出 cgroup2fs 才对
```
- 或降级 k3s v1.31(还支持 cgroup v1)。
### 1.2 swap 未关
- **症状**:kubelet 起不来(`fail-swap-on` 默认 true)。
- **修复**:
```bash
swapoff -a
# 持久化(可选):注释 /etc/fstab 里的 swap 行
sed -i '/[[:space:]]swap[[:space:]]/s/^/#/' /etc/fstab
```
### 1.3 SELinux 依赖缺失(RHEL8 系)
- **症状**:k3s 安装脚本报:
```
nothing provides container-selinux >= 3:2.191.0-1 needed by k3s-selinux-...
```
- **修复**:跳过 SELinux RPM(PoC 不需要):
```bash
curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh | \
INSTALL_K3S_MIRROR=cn INSTALL_K3S_SKIP_SELINUX_RPM=true sh -s - --disable traefik --disable metrics-server --disable servicelb --disable local-storage
```
---
## 2. 镜像源(中国区)
### 2.1 docker hub 被墙
- **症状**:`registry-1.docker.io` 403 / 超时,busybox/node/socat 镜像拉不动。
- **修复**:给 k3s 的 containerd 配国内镜像 `/etc/rancher/k3s/registries.yaml`:
```yaml
mirrors:
docker.io:
endpoint:
- "https://docker.m.daocloud.io"
- "https://docker.1ms.run"
```
然后 `systemctl restart k3s`。
- **注意**:镜像拉取**极慢**(一个 29MB 镜像拉了 13 分钟),批量拉取要有耐心或换更快的镜像源。
### 2.2 raw.githubusercontent 被墙
- **症状**:`kubectl apply -f https://raw.githubusercontent.com/longhorn/...` 拉不到 manifest。
- **修复**:加 ghproxy 前缀:
```bash
curl -sL "https://ghfast.top/https://raw.githubusercontent.com/longhorn/longhorn/v1.7.2/deploy/longhorn.yaml" -o /tmp/longhorn.yaml
```
### 2.3 alpine/socat tag 写错
- **症状**:`alpine/socat:1.8.0.0-r0` 拉取 403。
- **根因**:该 tag 不存在。
- **修复**:用 `alpine/socat:1.8.0.0`(或 `latest`)。
⚠️ 内部设计稿 §4.3 里写的 `1.8.0.0-r0` 需按此改正;本项目已改用自带的 Node `tcp-bridge`(见 §7.5),不再依赖 socat 镜像。
### 2.4 ghcr.io 直连被墙(CNPG/Longhorn 镜像)
- **症状**:CloudNativePG operator 镜像 `ghcr.io/cloudnative-pg/cloudnative-pg` 卡 "Pulling" 十几分钟不动。
- **修复**:`registries.yaml` 里给 `ghcr.io` 也配镜像:
```yaml
ghcr.io:
endpoint:
- "https://ghcr.m.daocloud.io"
```
### 2.5 CloudNativePG `poolers` CRD apply 报 annotation 超长
- **症状**:`kubectl apply -f cnpg.yaml` 报 `CRD poolers.postgresql.cnpg.io is invalid: metadata.annotations: Too long`,operator 崩溃 `no matches for kind "Pooler"`。
- **根因**:client-side apply 写的 `last-applied-configuration` annotation 超过 256KB。
- **修复**:`kubectl apply --server-side --force-conflicts -f cnpg.yaml`(server-side 不写那个超长 annotation)。
---
## 3. Longhorn
### 3.1 磁盘保留比例过高
- **症状**:卷副本创建失败 `No available disk candidates`,卷状态 `faulted`。
- **根因**:Longhorn 默认**保留 30% 磁盘**;磁盘 88% 满时「可用 < 保留量」,拒绝调度副本。
- **修复**:把保留/最小可用降到 5%:
```bash
kubectl -n longhorn-system patch settings.longhorn.io storage-reserved-percentage-for-default-disk --type=merge -p '{"value":"5"}'
kubectl -n longhorn-system patch settings.longhorn.io storage-minimal-available-percentage --type=merge -p '{"value":"5"}'
```
### 3.2 iscsid 未启动
- **症状**:卷卡在 `attaching`,消费 Pod 一直 `ContainerCreating`。
- **修复**(装完 `iscsi-initiator-utils` 后记得启动):
```bash
dnf install -y iscsi-initiator-utils
systemctl enable --now iscsid
```
### 3.3 单节点 3 副本调度不了
- **症状**:2 个副本 `Failed to schedule replica`,卷卡 `attaching`。
- **根因**:Longhorn 默认 **hard anti-affinity**,同一卷的副本必须在不同节点;单节点只能调度 1 个。
- **修复**:副本降到 1:
```bash
kubectl -n longhorn-system patch volumes.longhorn.io <volume-name> --type=merge -p '{"spec":{"numberOfReplicas":1}}'
kubectl -n longhorn-system patch settings.longhorn.io default-replica-count --type=merge -p '{"value":"1"}'
```
### 3.4 RWX 卷需 nfs-utils
- **症状**:RWX 卷挂载失败(RWX 走 share-manager / NFS ganesha)。
- **修复**:装 `nfs-utils`(提供 `mount.nfs`):
```bash
dnf install -y nfs-utils
```
### 3.5 内存预算(重要)
- Longhorn 自身 ~600M。**2C2G 上 k3s + Longhorn 已占 ~1.4G**,无法再跑 CloudNativePG 3 实例(需 ~1.5G)。
- **PoC item 4(CNPG + pgbench)需 ≥4G 机器**,2G 必 OOM。
---
## 4. 方案修正(PoC 实测推翻/修正的结论)
### 4.1 flannel 实际强制 NetworkPolicy(§3.5 修正)
- 实测 k3s v1.31 flannel 下 NetworkPolicy **开箱即用**(k3s 内嵌 kube-router netpol 控制器,不再以 DaemonSet 形式;attacker 被拒,删策略后立刻恢复连通)。
- **§3.5「k3s 默认 flannel 不强制」不成立**。
- Cilium 的选型理由应改为:**L7 策略 / eBPF 性能 / Hubble 可观测**(这些 kube-router 没有),而不是「基础强制」。
### 4.2 gid ≠ fsGroup(§6.0 措辞)
- 实测 init Job 建目录 `0700` 属主 uid 正确,但 **gid=0**(不是 fsGroup=100001)——因 §4.3 的 Pod 只设 `runAsUser`+`fsGroup`、没设 `runAsGroup`,进程默认 gid=0。
- **不影响 0700 安全边界**(0700 已把 group 权限关成 `---`)。若要对齐 gid,需加 `runAsGroup:<uid>`。
### 4.3 socat tag(§4.3 修正)
- `alpine/socat:1.8.0.0-r0` → `alpine/socat:1.8.0.0`。见 §2.3。
---
## 5. 完整部署流程(ACK 实跑记录,2026-08-23)
> 在阿里云 **ACK 智能托管模式** 上跑通控制面 Phase 2 的完整步骤。k8s 方案里的
> 「3 server HA / kube-vip / MetalLB / Cilium」在 ACK 上由托管能力替代(§5.5 映射)。
> 每用户 DSH Pod(Phase 3)、cert-manager、Helm chart 尚未落地,仍待补。
### 5.1 集群与基础组件
1. 建 ACK 集群(智能托管模式):**网络插件 DataPath V2(eBPF)+ 勾选 NetworkPolicy**;服务转发模式 IPVS。
2. 装 CNPG operator:manifest 用 ghproxy 下载(§2.2),`kubectl apply --server-side --force-conflicts`(§2.5 annotation 超长坑)。
3. **ghcr.io 换源**:CNPG operator 与 Postgres 镜像在阿里云拉不动(§2.4),换 `ghcr.m.daocloud.io`。
### 5.2 部署步骤
1. `kubectl apply -f deploy/00-namespace.yaml`
2. `kubectl apply -f deploy/01-dsh-pg.yaml`(Postgres 集群;storageClass 用拓扑感知 `alicloud-disk-topology-alltype`,size ≥20GiB)
3. 等 `dsh-pg` 进入 `Cluster in healthy state`;读连接串:
`kubectl -n dsh get secret dsh-pg-app -o jsonpath='{.data.uri}' | base64 -d`
4. 推镜像到 ACR:CI master push 自动推(需 `ACR_USERNAME`/`ACR_PASSWORD` secret),或 workflow_dispatch。
5. 建三个 secret(不在 deploy/ YAML 里,因含动态值):
- `dsh-acr-pull`(`docker-registry` 类型,ACR 凭证)
- `dsh-secret`(共享加密密钥,`key`)
- `dsh-pg`(`url` = 上面读到的 URI)
6. `kubectl apply -f deploy/02-control-plane.yaml`
7. bootstrap admin:`kubectl -n dsh exec deploy/dsh-orchestrator -- node lib/cli.js bootstrap-admin --username admin --password '<p>'`
8. 按 §5.3 验收。
### 5.3 验收清单(Phase 2)
- 注册 → 审核 → 登录 → 管理台/域名 API 全通。
- 访问控制:普通用户访问管理台 403、域名 API 200。
- kill 一个控制面副本 → Deployment 自动重建(3/3)、Service 不中断。
- 数据在 Postgres,删副本/重启不丢。
### 5.4 踩坑记录(ACK 实测新增)
| 坑 | 修复 |
|---|---|
| ghcr.io 被墙:CNPG operator / Postgres 镜像拉不动 | 换 `ghcr.m.daocloud.io`(§2.4) |
| ESSD 最小 20GiB:`size: 10Gi` 报 `less than minimum 20GiB` | storage `size: 20Gi` |
| CNPG 重建集群密码漂移:`dsh-pg-app` 重新生成密码 | 读最新 URI 重建 `dsh-pg` secret;生产用固定密码 |
| 控制面启动依赖 Postgres 就绪:ECONNREFUSED 崩 | 等 Postgres healthy 再部署,或接受 CrashLoopBackOff 重试 |
| Auto Mode 节点有 taint,CNPG pod 调度失败 | GOATScaler 自动扩出无 taint 节点 |
### 5.5 ACK 与 k8s 方案的映射
| k8s.md 定案 | ACK 等价 |
|---|---|
| 3 server HA + kube-vip | 托管控制面(免费) |
| MetalLB(L2 ARP 云上不生效) | SLB / ALB |
| Cilium | Terway DataPath V2(eBPF + NetworkPolicy) |
| Longhorn RWX / RWO | NAS / ESSD 云盘 |
| CloudNativePG | CloudNativePG(自建)或 RDS PG 高可用版 |
---
## 6. PoC 实测基线(item 4:CNPG on Longhorn)
**机器**:阿里云 4C16G,Ubuntu 24.04,SSD 40G。k3s v1.31 + Longhorn v1.7.2(副本 1)+ CNPG v1.24.1,3 实例 healthy。
**pgbench(scale 5,30s,4 clients / 2 threads)**:
| 负载 | tps | latency average | 说明 |
|---|---|---|---|
| select-only(`-S`) | 19164.7 | 0.209 ms | 纯读,数据 ~70MB 全进内存缓冲,未打到磁盘 |
| TPC-B 混合(含写) | 705.3 | 5.671 ms | 写落到 Longhorn 磁盘,**这才是 I/O 基线** |
**结论**:CNPG on Longhorn 跑通(3 实例、pgbench 正常)。写路径 latency ~5.6ms / tps ~705,是 Longhorn 单副本的 I/O 成本。要对照「节点本地 NVMe + PG 主备」判断是否可接受,需换 NVMe 盘 + 更大 scale(让数据集超过内存)再压一次。
**另:pgbench 手动跑法**(`kubectl run --rm -i` 在后台/非 tty 会话会卡 stdin,别用):
```bash
kubectl run pgbench-init --restart=Never --image=postgres:16 -n <ns> \
--env PGHOST=<cluster>-rw --env PGUSER=dsh --env PGPASSWORD=<pw> --env PGDATABASE=dsh \
-- pgbench -i -s 5
# 等 pod phase=Succeeded 后 kubectl logs;跑完 kubectl delete pod
```
---
## 7. Phase 3 集群联调踩坑(2026-08-23,第二阶段)
> 控制面 `deployMode=k8s` + leader election + file sidecar + 每用户 Pod 在 ACK
> 上联调的实跑记录。前一个阶段(§5)只把控制面 3 副本 + CNPG 跑通了。
### 7.1 部署顺序(缺一步就挂,按序执行)
1. `deploy/00-namespace.yaml` → `01-dsh-pg.yaml`(Postgres healthy)→ 读 `dsh-pg-app` URI 建 `dsh-pg`/`dsh-secret`/`dsh-acr-pull` secret。
2. **`deploy/03-rbac.yaml`**(SA + Role + lease 权限)。漏了它,控制面 Deployment 报
`FailedCreate: serviceaccount "dsh-orchestrator" not found`,滚动更新卡死。
3. `deploy/02-control-plane.yaml`(含 `imagePullPolicy: Always`,同名 tag 否则节点
缓存旧镜像)。
4. NAS:控制台建 **CNFS**(容器网络文件系统)→ 应用 `deploy/05-storage.yaml` 引 CNFS
→ `04-pvc.yaml` → `08-bootstrap.yaml`(chmod 1777 PVC 根)→ **最后** `07-psa.yaml`。
5. 每用户 Pod 依赖 `dsh-acr-pull`(§7.3)。
### 7.2 NAS:用 CNFS,别手写 `server` 参数
- 手写 `alicloud-nas` StorageClass 的 `server` 必须是**挂载目标域 + `:/`**,且
**不含 FileSystemId 前缀**。我给错成 `<FSID>.<NAS_MOUNT_TARGET>`
(带 FSID 前缀)→ provisioner 报 `CreateDir: IllegalCharacters`;去掉 FSID 前缀
后 PVC 才 Bound。
- 但挂载目标还会变(重建 NAS 后后缀漂移),正确姿势是控制台建 **CNFS**
(`storage.alibabacloud.com/v1beta1 ContainerNetworkFileSystem`),StorageClass 用
`containerNetworkFileSystem: nas` 参数引用,provisioner 自动拿到当前挂载目标。
- bootstrap Job 第一次卡 `ContainerCreating` 无事件 = NFS 挂载挂起(挂载目标错/VPC 不通);
目标对了会报 `mount.nfs: Connection reset by peer`(通常是安全组/访问组或目标刚就绪)。
### 7.3 每用户 Pod 镜像拉取与资源
- 控制面镜像在**私有 ACR**,生成的 files Pod / DSH Pod / watchdog Job **必须带
`imagePullSecrets: [dsh-acr-pull]`**——控制面 Deployment 有,但生成的 Pod 不继承。
漏了就是 `Init:ImagePullBackOff insufficient_scope`。代码里 config `imagePullSecret`
默认 `dsh-acr-pull`,已在 K8sSpawner 生成的三类 Pod/Job 全部带上。
- bootstrap Job 也需 `imagePullSecrets` + 用控制面镜像(busybox 从 docker.io 拉不动)。
- files sidecar 内存 64Mi 跑不起 node:22-slim + Fastify(OOMKilled → Pod 不 Ready →
Headless DNS 无 A 记录 → 控制面 ENOTFOUND),提到 256Mi。
- files sidecar 以用户 uid 跑且无 home,`homedir()` 落到 `/`,`resolveConfig` 会尝试
`mkdir /.dshs` → EACCES 崩。`runFileService` 已钉 `dataRoot=/tmp` + 占位
`encryptionSecret` 跳过写文件。
### 7.5 docker.io 被墙 → socat sidecar 换成 Node tcp-bridge
- ACK 节点拉不动 docker.io(busybox/alpine/socat 都超时),而 `alpine/socat` 正是
每用户 DSH Pod 的 sidecar 镜像。改为控制面镜像里的 `tcp-bridge` 子命令(`net`
纯转发 8081→8080),DSH Pod 只依赖 ACR 里已有的 `dsh` + `dshs`
两个镜像 + `imagePullSecret`,彻底去掉 docker.io 依赖。
- 测鉴权/带 cookie 的接口用 `--resolve` 走域名,别用 `kubectl port-forward`:设了
`cookieDomain=.dsh.example.com` 后 cookie 不会发到 `127.0.0.1`。
### 7.6 每用户 Pod 的 uid 与就绪探针
- **uid 错位**:注册/建 admin 若先 `initUserRoot`(建 files Pod)再 `createUser`,此时
用户不存在,`resolveUid` 回退 hash uid,而 `createUser` 给的是 `baseUid+row_id`——
files Pod 用 hash uid 建 0700 目录,DSH Pod 用 row_id uid 访问 → EACCES。已改成先
建用户再 initUserRoot。
- **就绪探针**:DSH 只监听 loopback,`tcpSocket 8080` 探的是 Pod IP、永远不 Ready →
Headless 无 A 记录 → 子域 502。改成容器内 `node -e http.get(127.0.0.1:8080)` exec 探针。
- **子域代理端口**:Headless Service(clusterIP: None)没有 kube-proxy 做 DNAT,代理
必须直连 Pod 的 targetPort(sidecar 8081),不能连 Service port 80——否则 502。
`endpointFor` 返回 8081。
- **cookie 跨子域**:`dsh.<domain>` 登录后跳 `<user>.dsh.<domain>`,`SameSite=Lax` 实测
不带 cookie → 401;改 `SameSite=None; Secure`(App 已 HTTPS-only)+ `secureCookies=true`。
- **代理 502**:控制面 keep-alive 池缓存旧 DSH Pod IP,重建 Pod 后命中旧 IP 副本 → 502;
连接出错时 `agent.destroy()` + `agent:false` 重试一次重新 DNS。
### 7.4 域名入口被阿里云备案拦截
- 腾讯云入口机 nginx 反代到 ACK 公网 SLB(`<SLB_IP>`)时,`Host: dsh.example.com`
被阿里云返回 `403 Non-compliance ICP Filing`(`Server: Beaver`)——域名在腾讯云备案、
未在阿里云备案,走阿里云公网 SLB 的 80/443 被备案校验拦。直连 SLB IP(不带域名 Host)
则 200 正常。
- 解法:给域名在阿里云做 ICP 备案,或入口走 NodePort/专线绕开公网 SLB 备案层,或
控制面域名解析子域改用「SLB 直连 + 腾讯云 nginx 透传 Host」之外的方案(待定)。
---
## 8. Phase 4 加固 / 可观测(2026-08-23)
### 8.1 代码加固(已落地,随镜像生效)
- `readOnlyRootFilesystem: true` + `emptyDir` `/tmp`:DSH dsh/sidecar、file sidecar、
watchdog、控制面容器全部只读 rootfs,`/tmp` 走 emptyDir(PSA restricted 纵深)。
- 空闲回收:reconcile(仅 leader)对每个期望 main 检查 `hasActiveSession`,会话全过期
→ `stop` Pod + 删期望态,不自动拉起(复用 `sessionTtlSeconds`,默认 7 天)。
- egress 收敛:`DSHS_EGRESS_CIDRS`(逗号分隔 CIDR)非空时,每用户 DSH Pod 的
443 egress 从 `0.0.0.0/0` 收敛为白名单(仍 except 内网)。默认空 = 保持现状。
⚠️ DeepSeek API 走 CDN,IP 会漂移,收敛前先 `dig +short api.deepseek.com` 核对并定期重查。
### 8.2 etcd encryption-at-rest(ACK 托管,控制台操作)
1. ACK 控制台 → 集群 → 托管控制面 → 开启 **etcd 加密**(encryption-at-rest)。
2. 开启后**必须全量重写存量 Secret**,否则旧 Secret 仍明文:
```bash
kubectl get secrets -A -o json | kubectl replace -f -
```
3. 验证(应只剩 `k8s:enc:aescbc:v1:` 前缀的密文):
```bash
kubectl get secrets --all-namespaces -o json | grep -v 'k8s:enc:aescbc' || echo "all encrypted"
```
覆盖 `dsh-key-*`(每用户 API key)、`dsh-pg`(DB DSN)、`dsh-secret`(共享加密密钥)。
### 8.3 可观测(Prometheus + Loki + 告警)
- **指标**:控制面已可暴露 `/metrics`(未接入则用 prom-client 加一个);ACK 托管 Prometheus
(ARMS)在控制台开通后,用 ServiceMonitor 抓 `dsh-orchestrator` 的 3080。
关键指标:控制面副本数、Postgres 连接数、每用户 Pod 数、崩溃计数。
- **日志**:Loki + Promtail(或 ACK SLS)接入控制面与每用户 Pod 日志。
- **告警规则**(Prometheus):控制面副本 < 3、Postgres 不可写、DSH Pod 崩溃率、NAS 容量水位。
示例(自建 Prometheus 时用):
```yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata: { name: dsh-alerts, namespace: dsh }
spec:
groups:
- name: dsh
rules:
- alert: DshControlPlaneDown
expr: count(up{app="dsh-orchestrator"}) < 3
for: 5m
- alert: DshPostgresUnavailable
expr: pg_up == 0
for: 2m
```
注:托管 Prometheus/Loki 实际接入需在 ACK 控制台开通,本轮交付清单与步骤。
### 8.4 Web 安全审计(代码审计结论,2026-08-23)
- **XSS(已修)**:`desktop.html`/`admin.html` 把文件名 `e.name`、插件名/描述
`plugin.name`/`plugin.description`、密钥名 `k.name`、用户名 `u.username` 直接拼进
`innerHTML`/`insertAdjacentHTML`。文件名与插件描述不受字符集约束 → 存储型 XSS。
已加 `esc()` 转义 `<>&"'` 后再拼接。
- **输入校验(已确认安全)**:用户名 `^[a-zA-Z0-9_-]+$`、域名 `isValidDomain`
(小写字母数字+连字符,防 nginx 注入)、密钥名 `^[A-Za-z0-9\-_ .]{1,32}$`、路径
`resolveWithinRoot`/`safeFilename`。
- **CSRF(残余低风险,未修)**:会话 cookie 为 `SameSite=None` 后跨站请求也会带
cookie,但所有状态变更端点都是 `application/json` + 无 CORS 头,跨站 fetch 会触发
preflight 被浏览器拦;真正暴露的是无 body 的 `POST /api/auth/logout`(logout CSRF,
低危)。后续若要加固,给状态变更端点加 CSRF token / 自定义头校验。
- **OWASP ZAP 基线扫描**:未在本轮执行(需 ZAP 工具/容器)。已定位的 XSS 已人工修,
ZAP 可作为 CI 门禁后续接入。
### 8.5 联调后期修的 4 个根因 bug(2026-08-23)
| 症状 | 根因 | 修复 |
|---|---|---|
| leader 不接管,lease 挂旧 pod 数小时 | client-node `patchNamespacedLease` 走 JSON Patch(要数组),传对象被 Go 拒 400 被静默吞 | 改 `replaceNamespacedLease` 全量 PUT + RV 乐观并发 |
| admin DSH 反复 502 | `endpointFor` 返回 Service DNS,A 记录 ~30s TTL,Pod 重启后这 30s 内仍解析旧 IP | endpointFor 直接返回 `podIP`,每次请求实时读 Pod |
| 模型无法请求(EAI_AGAIN) | ACK DNS 是 `node-local-dns`(label `k8s-app=node-local-dns`),NP 规则却选 `k8s-app=kube-dns` → DNS 全被拦 | NP DNS egress 改 `0.0.0.0/0`(可移植) |
| keepAlive 池销毁后不可复用 | `agent.destroy()` 只销毁不替换,后续请求首跳全失败 | destroy 后 `new Agent()` 替换,再短连接重试一次 |