From e00e17c7fb2e5db57cb45e6990dcbbb80a8f5cc7 Mon Sep 17 00:00:00 2001 From: ReSerendipity Date: Wed, 23 Sep 2026 15:08:43 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(deploy):=20k8s=20=E6=B8=85=E5=8D=95?= =?UTF-8?q?=E5=A3=B0=E6=98=8E=20ghcr=20=E6=8B=89=E5=8F=96=E5=87=AD?= =?UTF-8?q?=E8=AF=81=E8=A6=81=E6=B1=82=EF=BC=8CREADME=20=E7=BB=99=E5=87=BA?= =?UTF-8?q?=E5=BB=BA=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #140 把镜像名修对了,但照着清单上线仍然会失败:ghcr 上那个 `tts_multimodel` 包**不是匿名可拉的** (今天实测:匿名 token 拿不到 grant、manifest GET 四条 tag 全 404;已登录但缺 `read:packages` 的 token 回 403),而 `deploy/kubernetes/` 下原先既没有 `imagePullSecrets`、README 也没有一句 凭证说明。最难查的是**缺这一步时 `kubectl apply` 不报错** —— 它会受理,然后 Pod 一直 `ImagePullBackOff`。 - `deployment.yaml`:Pod spec 里加 `imagePullSecrets: [{name: ghcr-pull}]`,注释写明为什么 不能拿 `GITHUB_TOKEN` 当密码(只对当次 run 有效,且 API 拒绝用它建 registry secret)。 - `deploy/kubernetes/README.md` 前置条件:给出现成的 `kubectl create namespace` + `create secret docker-registry` 两条命令,并点明"别把 apply 成功当成上线成功"。 没有真集群可验(本机没有 k8s,也没有注册 runner),所以这一条的强度只到"清单与文档自洽、 YAML 可解析、`spec.template.spec.imagePullSecrets` 确实解析成 `[{'name': 'ghcr-pull'}]`"; `kubectl apply` 到真集群拉通镜像那一步仍未取证,记在这里。 YAML 用 `yaml.safe_load_all` 复解析过(deployment 2 个文档、workflow 1 个)。 发版链路 3 个文件 16 passed、`check_release_readiness.py` 判"发版条件满足"(11 处 = 2.2.5)。 Signed-off-by: ReSerendipity --- deploy/kubernetes/README.md | 14 ++++++++++++++ deploy/kubernetes/deployment.yaml | 7 +++++++ 2 files changed, 21 insertions(+) diff --git a/deploy/kubernetes/README.md b/deploy/kubernetes/README.md index ce38594..e20386f 100644 --- a/deploy/kubernetes/README.md +++ b/deploy/kubernetes/README.md @@ -17,6 +17,20 @@ - 集群已安装 [NVIDIA GPU Operator](https://github.com/NVIDIA/gpu-operator) 或 `nvidia-container-toolkit`, 提供 `nvidia.com/gpu` 可分配资源。 - 镜像已推送到 `ghcr.io/reserendipity/tts_multimodel`(**下划线**:名字由 `${{ github.repository }}` 整体小写得到,`_` 原样保留,见 `.github/workflows/docker-publish.yml`;`tests/test_image_name_consistency.py` 就是把这条引用与工作流钉成同源的闸)。 +- **拉这个镜像要有凭证**:上面那个包不是匿名可拉的(实测匿名 token 无 grant、manifest GET 回 404)。 + 先建 `tts` 命名空间,再建 registry secret: + + ```bash + kubectl create namespace tts # 已存在就跳过 + kubectl -n tts create secret docker-registry ghcr-pull \ + --docker-server=ghcr.io \ + --docker-username=<你的 GitHub 用户名> \ + --docker-password=<带 read:packages 作用域的 PAT> + ``` + + 清单里 `imagePullSecrets: [{name: ghcr-pull}]` 指的就是它。**少了这一步,`kubectl apply` 不报错**, + Pod 只会一直 `ImagePullBackOff` —— 别把"apply 成功"当成"上线成功"。 + 用 PAT 而不是 `GITHUB_TOKEN`:后者只对当次 workflow run 有效,且 API 拒绝拿它建 registry secret。 - 模型权重通过**独立的大文件分发流程**提供(`.dockerignore` 已排除 `model/`), 运行时以 PVC(`tts-models`,见 pvc.yaml)只读挂载到 `/app/model`。 **部署前必须先把权重预填充进该 PVC**(如经临时 Pod / rsync / 对象存储同步), diff --git a/deploy/kubernetes/deployment.yaml b/deploy/kubernetes/deployment.yaml index 27ef782..49354e5 100644 --- a/deploy/kubernetes/deployment.yaml +++ b/deploy/kubernetes/deployment.yaml @@ -28,6 +28,13 @@ spec: labels: app: tts-multimodel spec: + # 这个 ghcr 包不是匿名可拉的(实测:匿名 token 无 grant、manifest GET 回 404; + # 已登录但缺 read:packages 的 token 回 403)。没有拉取凭证时 Pod 会一直 + # ImagePullBackOff,而 `kubectl apply` 本身不会报错 —— 所以这一步必须显式做。 + # 凭证要的是带 read:packages 的 PAT,**不是** GITHUB_TOKEN(只对当次 run 有效, + # 且 API 拒绝拿它建 registry secret)。建法见 deploy/kubernetes/README.md「前置条件」。 + imagePullSecrets: + - name: ghcr-pull terminationGracePeriodSeconds: 90 securityContext: runAsNonRoot: true From 3bef18da20d2b966486ba733848342cd73e41eb7 Mon Sep 17 00:00:00 2001 From: ReSerendipity Date: Wed, 23 Sep 2026 15:09:06 +0800 Subject: [PATCH 2/2] =?UTF-8?q?ci(docker):=20=E5=8A=A0=E5=B9=B6=E5=8F=91?= =?UTF-8?q?=E7=BB=84=EF=BC=9B=E5=B9=B6=E6=B8=85=E6=8E=89=E4=B8=80=E6=9D=A1?= =?UTF-8?q?=20RP=20=E6=A0=B9=E6=9C=AC=E4=B8=8D=E4=BC=9A=E6=B6=88=E8=B4=B9?= =?UTF-8?q?=E7=9A=84=20[Unreleased]=20=E6=89=8B=E5=86=99=E6=9D=A1=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **并发组**:`docker-publish.yml` 之前没有 `concurrency`,2026-09-22 实测到三条构建同日并行 (13:29 与 14:03 两次 main push,加 14:12 我为 v2.2.5 手工 dispatch 的那条),单次 20+ 分钟, 还同时读写同一份 Actions 层缓存 —— 而那份缓存的配额只有 10 GiB,历史上正是被这个工作流的 `mode=max` 吃满过(#133)。 我先想的是按 ref 分组(`docker-publish-${{ github.ref }}`),**自己否掉了**:今天真正重叠的 两条恰好来自不同 ref(`refs/heads/main` 与 `refs/tags/v2.2.5`),分组等于没管住,所以用常量组。 `cancel-in-progress: false`:中途取消会留下半份层缓存、也可能让 `:latest` 停在中间态, release 那条更不能事后被取消。 **`[Unreleased]` 这条是修我自己造成的假账**:#140 里我手写过一条 `## [Unreleased] → ### Bug Fixes` 的条目,以为发版时 RP 会把它折进新版本段。今天开 #141 才看清 它**不读这个段落** —— 它照提交信息另生成两条(真实提交 + merge commit 各一条),把我那段原文 原地留在 `[2.2.5]` 与 `[2.2.4]` 之间。结果就是"已经发出去的内容"永久挂在 Unreleased 底下。 现在删掉那条,并把机制写进 `docs/release-governance.md`:走自动路径时**别手写 `[Unreleased]`**, 要留的说明进提交信息与文档;§2 第 1 步"`[Unreleased]` 收敛进 `[<新版本>]`"那句标注成只对 手工路径成立。 同一条里也记下 RP 的计数形状:#137 与 #141 都把 PR 的真实提交**与 merge commit 各算一条**, 所以 2.2.6 的 CHANGELOG 里同一件事有两行。要治就是仓库侧改用 squash merge,属另一条决定, 这次不动。 Signed-off-by: ReSerendipity --- .github/workflows/docker-publish.yml | 10 ++++++++++ CHANGELOG.md | 4 ---- docs/release-governance.md | 11 ++++++++++- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 1bb70ae..9edd06d 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -14,6 +14,16 @@ permissions: contents: read packages: write +# 同一时刻只跑一条镜像构建(2026-09-22 实测的浪费):13:29 的 main push、14:03 的 main push +# 与 14:12 我为 v2.2.5 手工 dispatch 的那条在同一天里并行 —— 单次构建 20+ 分钟, +# 还要读写同一份 Actions 层缓存(配额只有 10 GiB,曾经就是被这里的缓存吃满的,见 #133)。 +# 按 ref 分组的方案我否掉了:实测重叠的两条恰好是**不同 ref**(refs/heads/main 与 +# refs/tags/v2.2.5),分组等于没管住。cancel-in-progress 关掉:被中途取消会留下半份层缓存, +# 还可能让 :latest 停在中间态,而 release 那条尤其不能事后取消。 +concurrency: + group: docker-publish + cancel-in-progress: false + env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 2905d99..0c4809e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,10 +18,6 @@ ## [Unreleased] -### Bug Fixes - -* **deploy:** k8s 清单写的镜像名与工作流真正推的不是一个:`ghcr.io/.../tts-multimodel`(连字符)从未存在,真实的是 `tts_multimodel`(下划线,`${{ github.repository }}` 整体小写、`_` 原样保留)。照着 `deploy/kubernetes/deployment.yaml` apply 的两个容器(含 init 容器)必然拉不到镜像;`docs/rollback_sop.md` 那条回滚命令除了名字还多带了个 `v` 前缀 —— 工作流的 semver 图案(`type=semver,pattern={{version}}`)产出的形状是 `2.2.0`,不带 `v`。名字与 tag 形状现在由 `tests/test_image_name_consistency.py` 从工作流推导钉住(原来那处正则把名字写死了,所以只核得出版本、核不出名字)。 - ## [2.2.4] - 2026-09-22 两处用户可见修复,其余是**发布与 CI 链路的收口**(这个版本存在的理由之一:v2.2.3 之后 main 上 diff --git a/docs/release-governance.md b/docs/release-governance.md index 455f8c2..a8446b9 100644 --- a/docs/release-governance.md +++ b/docs/release-governance.md @@ -110,10 +110,19 @@ config/manifest + 5 个 v4 不认的入参 → 每次 main push 输出 `found 0 possible releases` 后绿, v2.2.2 因此三次绿 run 都没 Release);已修,现在它真的会开 PR。挂在它下面的 `build-release`(sdist/wheel + SHA256SUMS)同样只在 `release_created == true` 时执行。 + > **`[Unreleased]` 不是中转站,RP 不读它**(2026-09-23 实测):#140 里我手写了一条 + > `## [Unreleased]` → `### Bug Fixes` 的条目,RP 开 #141 时**没把它折进 2.2.6** —— 它照提交信息 + > 另生成两条(真实提交 + merge commit 各一条),把我那段原文留在原地,位置在 `[2.2.5]` 与 + > `[2.2.4]` 之间。于是"已经随 2.2.6 发出去的内容"会永久挂在 Unreleased 底下,是一份自相矛盾的 + > 假账(已删)。**走自动路径时别手写 `[Unreleased]`**:要留的说明写进提交信息与本文, + > CHANGELOG 由提交信息生成。§2 第 1 步那句"`[Unreleased]` 收敛进 `[<新版本>]`"只对第 2 条 + > (手工发版)成立。 ## 2. 发布流程 -1. 确认本批内容已进 CHANGELOG(`[Unreleased]` 收敛进 `[<新版本>]` 并改日期); +1. 确认本批内容已进 CHANGELOG(`[Unreleased]` 收敛进 `[<新版本>]` 并改日期)—— + **这一步只属于手工路径**:走 §1 第 1 条时 CHANGELOG 由 release-please 按提交信息生成, + 手写 `[Unreleased]` 不会被消费,只会在 `[2.2.5]` 与 `[2.2.4]` 之间留一份永久假账(见 §1 末); `tests/test_version_consistency.py` 必须绿(它就是"§5 版本位全部同步"那格闸) 2. 同步 `config.yaml` 顶层 `version` 与 `deploy/kubernetes/deployment.yaml` 的镜像 tag (都不在 extra-files 里:前者因为 RP 的 YAML 写入器会整份重排并洗掉注释,见 §1;