diff --git a/AUDIT-2026-09-22.md b/AUDIT-2026-09-22.md index 0df35da..92e4d6d 100644 --- a/AUDIT-2026-09-22.md +++ b/AUDIT-2026-09-22.md @@ -1,6 +1,6 @@ # Felis 生产就绪审计 — 2026-09-22(真机 E2E + 混沌) -分支:`audit-fixes-20260922`(已推送)。环境:CentOS Stream 9 / aarch64 / k3s v1.36.4, +分支:全部修复**逐 commit 直接推 `main`**(不再用功能分支)。环境:CentOS Stream 9 / aarch64 / k3s v1.36.4, IPv6-only 接入(`ssh -6 -i ~/.ssh/id_ed25519 root@fdb2:2c26:f4e4:0:21c:42ff:fede:69ec`), 面板经 `socat TCP6:443 → 127.0.0.1:30443` 中继(手动启动,重启 VM 后需重开)。 @@ -24,14 +24,16 @@ IPv6-only 接入(`ssh -6 -i ~/.ssh/id_ed25519 root@fdb2:2c26:f4e4:0:21c:42ff:f ## 待决策台账(未修) -| # | 主题 | 说明 | +前两日台账的 #7、#11–#15 已全部修复(本批核销,证据见下),不再挂在"未修"里: + +| # | 主题 | 状态 | |---|------|------| -| 7 | 异步失败不可感知 | backup/restore 失败后无状态出口:restore 行不变、backup 无行;只有集群侧 Job/日志可查。建议状态字段或 `?failed` 查询 | -| 11 | PG 断连表现 | 会话查询失败报 401 而非 503(fail-closed 但误导;用户以为没登录) | -| 12 | ready 门滞后 | 容器 Ready 后 6~10s 内 API 仍 409 not_running | -| 13 | setup token 截断 | 43 字符 token + 长域名,80 列终端下 TUI 截断显示(复现:tmux 80 列) | -| 14 | 日志噪音 | controller-runtime 未 SetLogger,首用打印整段堆栈;TLS handshake EOF 噪音(kubelet 探针) | -| 15 | reaper 启用未演练 | **已演练完毕(2026-09-22)**:绑定挂载演练 world → 一次 pass 归档+删 PVC+保留 servers 行/CRD;过期归档驱逐(行转 deleted、文件删除、合法归档未动)。启用仍受 #6 三旗标约束;另注意 `--worlds-host-path` 的 `/` 布局在 stock local-path 下不成立(编排责任),且 VM 上演练用的 CronJob 已 **suspend** 防误删(真实世界目录不在 /srv/worlds-root)| +| 7 | 异步失败不可感知 | ✅ 修复:`GET /api/v1/servers/{name}/jobs`(`ff7c57c`,含 RBAC `jobs:list` 与 OpenAPI);真机 drill 用它看到 running→succeeded | +| 11 | PG 断连报 401 | ✅ 修复:会话存储故障改为 503(`2a8f897`) | +| 12 | ready 门滞后 | ✅ 修复:operator 启动期内每 2s 重探 RCON(`a2df2f2`) | +| 13 | setup URL 截断 | ✅ 修复:TUI 折行(`abce381`) | +| 14 | controller-runtime 日志噪音 | ✅ 修复:SetLogger 接 slog(`a415246`) | +| 15 | reaper 启用未演练 | ✅ 已演练(见"第二日"节;CronJob 仍 `suspend=true` 防误删) | ## 已验证事实(正向清单) @@ -53,16 +55,25 @@ IPv6-only 接入(`ssh -6 -i ~/.ssh/id_ed25519 root@fdb2:2c26:f4e4:0:21c:42ff:f - **回收闭环(真实 k3s 布局 + 权限)**:resolvecheck(20d idle)→ CronJob 派 Job:`evaluated=2 reaped=1`;归档含 marker、PVC+宿主目录回收、`world_backups` 记 `inactive_15d`;期间先经历 `lstat /worlds/…: permission denied`(k3s storage root 0700)→ 修 ACL/root 权限后通过 ✅ - **磁盘压力三连 drill**:①1e6 自定义类:游戏 pod 先走、控制面随后仍被逐(kubelet ranked/evicted 日志)→ 镜像 GC → ErrImagePull;②改内建 `system-cluster-critical` 后:`cannot evict a critical pod` × felis-api/operator/registry,全部保持 Running;③恢复阶段实测 `DiskPressure` 条件 ~5min(eviction-pressure-transition-period)转 False,被 GC 的游戏镜像按 runbook 重导入后 25s 恢复 ✅ - **混沌**:SIGKILL k3s 主进程 → kubectl 与 felis-api ready 均在 **10s** 内恢复(api pod 本身未被重启)✅;内存压力(2.6G tmpfs 满写):无 OOM kill、无人被逐(swap 5.9G 吸收 + 内核回收),控制面不受影响 ✅ -- **构建链路定位(未修,见下一节)**:Kaniko/Trivy 默认镜像是外部的 → 已加 `[registry] kaniko_image/trivy_image/build_cpu_limit/build_mem_limit` 覆写;上下文跨 ns 不可挂载(PVC 不能跨 ns)是设计级缺口,s3 lane 也缺凭据注入 ✅(证据:`internal/submit/blobstore.go:40`、`cmd/felis/api.go` contextBase 分支、jobspec 无 volumes/env) +- **构建链路定位(当时未修)**:Kaniko/Trivy 默认镜像是外部的 → 已加 `[registry] kaniko_image/trivy_image/build_cpu_limit/build_mem_limit` 覆写;上下文跨 ns 不可挂载(PVC 不能跨 ns)是设计级缺口,s3 lane 也缺凭据注入 ✅(证据:`internal/submit/blobstore.go:40`、`cmd/felis/api.go` contextBase 分支、jobspec 无 volumes/env)→ **第三批已修复,见下** + +### 本轮新增真机证据(第三批:探针 + 构建链路全通) + +- **控制面探针(`0c8e29b`)**:felis-api / felis-operator / registry 三个 Deployment 此前**完全没有探针**。修复后真机验证:api、operator `/readyz`+`/healthz`(internal 8081),registry `/v2/`(含命名端口解析);三个 Pod `Ready=true`、`restartCount=0`、无 `Unhealthy` 事件;operator 新增 `--health-probe-bind-address`(8081,与 metrics 8080 分离,零检查也 404 → 已注册 ping) +- **构建链路全通(`f79e5eb` + `02fd2de`)**: + - 传输:context_ref 改为 internal-face URL;`felis fetch-context` initContainer 走 service token(对象来自 `felis-build` 里按 `bootstrap`/`felis setup` 同款 Secret 复制机制落地的 `felis-service-token`;netpol 只放行控制 ns:8081)+ zip-slip 安全解包到限容 emptyDir;Kaniko `--context=/context` 只读挂载 + - **真机 E2E**:玩家 OTP 登录 → 建 submission → 上传 gzip context(marker `felis-e2e-build-marker`)→ Owner approve → Job:fetch ✅ → Kaniko 构建并 push `registry.felis.svc:5000/user-uploads/:latest` ✅ → Trivy 扫描(内建镜像 DB)✅ → Job `Complete`;**从 registry 拉回镜像校验 `/hello.txt` 内容 = 上传的 marker** ✅ + - 演练中真机抓到并修复 3 个单测看不到的缺陷:① Job requests=limits(2C/4Gi)在 4C/5.5G starter 上**永远 Pending**(`FailedScheduling/Insufficient memory`)→ requests 改为地板值(250m/512Mi,不超上限);② Kaniko 重拷 Dockerfile 时 chown/chmod 到源文件 owner(distroless uid 65532)在 drop-ALL 能力下失败(`copying dockerfile: chown … operation not permitted`)→ fetch 容器以 root 解包(= Kaniko 自身 uid);③ Trivy DB 默认 `mirror.gcr.io` 正被 egress 锁拒绝(fail-closed)→ 新配置 `[registry] trivy_db_repository` + 文档 §8e 固化镜像配方(`docker pull/tag/push aquasec/trivy-db:2` 进内建 registry;`--insecure` 已覆盖明文 HTTP) ## 结论:离"生产可用"还差什么(按优先级) -1. **构建链路的上下文通道(唯一的功能级缺口)**。用户模组提交 → 审批 → Kaniko 构建这条 lane 需要跨命名空间传递 context 的机制;PVC 不能跨 ns,s3 lane 又缺 build pod 侧凭据注入。两条候选:a) 官方走对象存储(在 bundle 里加 MinIO 或让 operator 配外部 S3 + 为每次构建挂 per-build Secret + netpol 放行 endpoint);b) 在 felis-build 里做 context handoff(一个控制面任务把 blob 落到 build ns 的 PVC)。做 a) 时顺带把 kaniko/trivy 镜像推入内建 registry。 +1. ~~构建链路的上下文通道~~ ✅ **已修**(`f79e5eb`/`02fd2de`,真机全链路含拉回校验;Trivy DB 需按 §8e 镜像一次)。 2. **镜像耐久**:kubelet 仍可能 GC 掉"当前无人使用"的镜像(游戏镜像已实测发生)。把控制面与游戏镜像推入内建 registry 并让 Deployment/StatefulSet 引用 registry 路径,可免去"事故后人工重导入"。本轮已用 runbook(troubleshooting §13b)兜住。 3. **PG 级契约测试**:PGRepo 与接口/fake 漂移已抓到 3 例(#16/#17/#18)且单测全绿也发现不了;建议对"注释承诺了字段/错误码/生命周期"的方法加 Postgres 集成测试。 4. **面板把 /jobs 显示出来**:API 已有出口(#7),`ServerBackups` 页加一块"最近操作"即可让普通用户看见失败原因。 5. **多节点回收**:reaper CronJob 无 nodeSelector,多节点需按卷所在节点加 selector(代码与文档均已标注,未实现调度约束)。 6. **告警**:felis_* 指标已全有生产者,但无非告警;磁盘/内存阈值告警建议随部署一并给出。 +7. **玩家可见的构建结果(新发现)**:构建失败目前只有 admin 路由(`/images/build/{id}`)能看到;`/me/submissions` 只回 `approved`,玩家看不到 failed/succeeded。建议在 submission 视图附上 `build_status`(或在面板里链到构建详情)。 ## 系统性观察 @@ -74,5 +85,6 @@ IPv6-only 接入(`ssh -6 -i ~/.ssh/id_ed25519 root@fdb2:2c26:f4e4:0:21c:42ff:f - 面板会话 cookie:`/tmp/felis-cookies.json`;API 助手:`/tmp/fcurl.sh` - 测试服:`test-one`(minecraft ns,stopped);合法备份 `bk-47ee2e7e96e5a4ca9d0e51b805518bac` - CDP 调试口:Mac `127.0.0.1:9333`(独立 Chrome,profile `/tmp/felis-chrome2`);WebAuthn 虚拟认证器需在**同一 CDP 会话**内完成仪式,且先 `Page.bringToFront`(否则 NotAllowedError: page does not have focus) -- 分支已部署到 VM:`felis-api` 镜像 = `felis:auditfix7`(含全部修复) +- 分支已部署到 VM:`felis-api` 镜像 = `felis:auditfix16`(含全部修复;探针可直接 `kubectl get deploy -o jsonpath=…readinessProbe` 复核) +- 构建链路 drill 现成条件:`felis-build` 里有 `felis-service-token`(Secret 复制);`felis-config` 里 `kaniko_image/trivy_image` 指向 k3s containerd 已导入的 pin tag、`trivy_db_repository = registry.felis.svc:5000/mirror/trivy-db:2`(镜像配方 §8e;VM 上 docker daemon 的 `insecure-registries` 已含 registry ClusterIP) - VM 内部面:`k3s kubectl -n felis port-forward svc/felis-api-internal 18081:8081`(Pod 重建后转发会悬死,需重启);reaper CronJob(minecraft ns)已应用但 `suspend=true`