- 开发工具
- 代码编辑器
- 数据科学
【免费下载链接】positron
Positron, a next-generation data science IDE
Positron 的 CI 测试环境依赖一组预构建的 Docker 镜像(ubuntu24_04、rocky_8、rocky_9、debian、openSUSE15_6、SLES15_6 与 postgres),当 Node.js 需要升级或镜像需要随依赖打上统一的新 tag 时,逐个手工触发构建、盯日志、修错误代价高昂。update-ci-images是一个面向长期编排的 Agent Skill,它把"新建分支 → 改 Node 版本 → 以受限并发 dispatch 构建工作流 → 监控 run → 自动修复失败并重试 → 全部变绿后留下 PR 供人工审查"这条完整链路固化成了可重复执行的流程。读完本文,你将掌握这套 7 镜像批量重建的输入参数、阶段化执行模型、失败分类与重试预算、以及仓库中每个脚本的真实行为。
背景:7 个 CI 镜像与两条构建工作流
镜像构建的实际执行者是两个workflow_dispatch工作流,Skill 只是它们的"指挥者"。构建矩阵共 7 个构建任务:
| 工作流 | 变体 | 数量 |
|---|---|---|
| ci-images-build-os.yml | os∈ {ubuntu24_04, rocky_8, rocky_9, debian, openSUSE15_6, SLES15_6} | 6 |
| ci-images-build-postgres.yml | postgres | 1 |
每个 run 同时产出 amd64 与 arm64 的 manifest list
每次 dispatch 只触发一个 run,但该 run 内部是两个架构的矩阵构建加一个合并任务:
build矩阵在原生 runner上分别构建linux/amd64与linux/arm64(ci-images-build-os.yml 中fail-fast: false,一个架构失败不会取消另一个),各自推送单架构 tag(如positron-ubuntu24-amd64:<tag>、positron-ubuntu24-arm64:<tag>);- 随后的
merge任务用docker buildx imagetools create将两个单架构 tag 合并为多架构 manifest list,即ghcr.io/<owner>/positron-ubuntu24:<tag>; - 单架构 tag 会保留在 GHCR 上,作为过渡期存量消费方的兼容输出。
postgres 镜像在 Dockerfile.postgres 中只是postgres:latest加初始化脚本(COPY init-scripts/与COPY sql/),没有任何 Node,因此它不受NODE_VERSION变更影响。
镜像里到底装了什么
从 Dockerfile.ubuntu24_04 可以看到这类 OS 测试镜像的内容:系统依赖与 VNC/窗口管理器(Fluxbox + x11vnc 用于本地观察测试)、AWS CLI v2、Node.js 与全局 npm 依赖、Quarto、Miniforge Conda、rig 管理的多个 R 版本、TinyTeX(LaTeX)、uv 管理的 Python 3.10.12 venv 与 e2e 测试内容依赖、pyenv、隐藏 Python/R、以及 Positron license server。镜像还暴露8080、5900、9323端口,与 docker/images/README.md 中"本地开发复刻 CI 环境"的用途一致。
输入参数:一次升级只需要三个信息
Skill 从用户的调用中解析输入(缺失时才询问):
- tag(必填)——所有镜像统一使用的 Docker tag,例如
2025.06.0; - node version(必填)——完整 Node 版本号,例如
22.22.1,且必须同时存在于https://nodejs.org/dist/v<version>/的 x64 与 arm64 路径下(预检会用 HEAD 请求验证); - concurrency N(可选,默认 4)——同时最多有多少个构建任务在飞行中。
五个执行阶段
Phase 0 — Preflight:动手前先确认基线
在改动任何东西之前,依次确认以下前置条件全部通过:
gh auth status # 已认证,且有 workflow + repo scope git -C "$REPO" status --porcelain # 必须为空(工作树干净) git -C "$REPO" rev-parse --abbrev-ref HEAD # 汇报当前基础分支(任意分支均可) gh repo view --json nameWithOwner -q .nameWithOwner # 期望 posit-dev/positron- 用 HEAD 请求验证 Node 版本在双架构都存在:
curl -sfI https://nodejs.org/dist/v<node>/node-v<node>-linux-x64.tar.xz与...-linux-arm64.tar.xz,任一返回 404 就停止并告知用户版本无效; - 工作树必须干净,否则停止并询问如何处理;
- 基础分支不必是
main:update-images/<tag>分支从当前所在位置 fork 即可,这对从功能分支上测试很有用。只需在 PR body 中注明 fork 自哪个基础分支。
Phase 1 — 分支、改版本、开 PR
git -C "$REPO" switch -c update-images/<tag> # 非字母数字字符会被清洗为 '-' bash <dir>/scripts/bump-node-version.sh <node> # 编辑全部 12 个 compose 文件并打印证明 git -C "$REPO" add -A git -C "$REPO" commit -m "Bump Node to <node> and rebuild all images at <tag>" git -C "$REPO" push -u origin HEAD gh pr create --base main --head update-images/<tag> \ --title "Rebuild all CI images at <tag> (Node <node>)" \ --body "<see template below>"PR body 模板(构建逐个变绿时勾选):
Rebuilds every image at tag `<tag>` with Node `<node>`. Driven by the `update-ci-images` skill. Builds are dispatched on this branch via workflow_dispatch with max <N> jobs in flight. ### Builds (each is a multi-arch amd64+arm64 manifest) - [ ] ubuntu24_04 - [ ] rocky_8 - [ ] rocky_9 - [ ] debian - [ ] openSUSE15_6 - [ ] SLES15_6 - [ ] postgres更新 PR body/checklist 时务必用 REST API 而不是
gh pr edit:gh pr edit --body可能触发 Projects-classic GraphQL 弃用错误并静默地不更新 body,因此统一用不受影响的 REST PATCH:gh api -X PATCH repos/<owner>/<repo>/pulls/<n> -f body="$NEW_BODY"每次勾选时都基于最新内容重建完整 body 再 PATCH。
bump-node-version.sh 到底改了哪些文件
scripts/bump-node-version.sh 维护一个 12 个文件的显式列表(6 个 OS × amd64/arm64 各一),用sed -i.bak只替换NODE_VERSION:的值并保留缩进。它的防御性设计值得一提:任一预期文件缺失、或文件中没有NODE_VERSION:行,脚本都会报错退出,从而杜绝"静默部分更新"。postgres 没有 Node,刻意排除在列表之外。执行后打印:
Updated NODE_VERSION to <node> in 12 files:当前仓库中这些文件的实际值,例如 ubuntu24_04 的 amd64 compose 里NODE_VERSION: 24.18.0,对应的 Dockerfile 在 Dockerfile.ubuntu24_04 中用curl -fsSL https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-${NODE_ARCH}.tar.xz安装 Node 并用node -v && npm -v验证。
PPM_SNAPSHOT 是独立的旋钮,普通 Node 重建不动它
R 的 dev-deps 安装还有一个独立的PPM_SNAPSHOTbuild arg(由 scripts/bump-ppm-snapshot.sh 负责修改),它在正常的 Node 重建中不会被触碰——除非遇到 Phase 4 描述的 PPM 发布窗口竞争。bump-ppm-snapshot.sh 同样以显式文件列表 + 缺失即报错的方式工作,并且把日期用引号包裹(sed写成"${NEW}"),避免 YAML 把日期解析成时间戳(buildx bake 会拒绝time.Time);debian 被有意排除(冻结在2026-03-01,见 debian compose,原因是 terra/GDAL 问题)。
Phase 2 — 建队列与状态
在 scratchpad 里创建一个状态文件以支撑长时间运行。每个任务的状态沿queued→running(记录 run id)→done/failed迁移。每个任务维护两个相互独立的计数器(原因见 Phase 4):
fix_attempts——做了真正的代码修复并重新 dispatch 的次数;transient_retries——因基础设施抖动(无代码改动)重新 dispatch 的次数。
7 个任务按 checklist 行命名。每个任务调用dispatch-job.sh <branch> <tag> [os]:6 个 OS 任务传os,postgres 任务省略;每次 dispatch 只产生一个 run(双架构构建 + manifest 合并)。
Phase 3 — Dispatch + 监控循环(长时间运行部分)
维护in_flight(run id → 任务)与queued,循环直到queued与in_flight都为空:
补位:当
len(in_flight) < N且queued非空时,弹出一个任务并执行:id=$(bash <dir>/scripts/dispatch-job.sh <branch> <tag> [os])记录
id、标记任务running、递增其attempts、更新状态文件。等待:把 waiter 放到后台启动(不阻塞会话),传入所有在飞 run id(每 60s 轮询一次)。macOS 上包一层
caffeinate -i,防止主机空闲休眠导致轮询停滞:WAIT="bash <dir>/scripts/wait-for-runs.sh 60 <id1> <id2> ..." if [ "$(uname)" = "Darwin" ]; then caffeinate -i $WAIT; else $WAIT; fi(注意
caffeinate -i只防空闲休眠,不防电池状态下的合盖休眠;Linux 上可换systemd-inhibit --what=idle。)只要有任一在飞 run 完成,脚本就以<id> <conclusion>格式打印并退出;harness 会携带该输出重新调用 Agent。处理完成事件:对每行输出——
success→ 标记done、移出in_flight、用gh api -X PATCH勾选 PR checklist 与 TodoWrite 项,回到步骤 1 补位;其他结论(failure/cancelled/timed_out/startup_failure)→ 进入 Phase 4。当
queued与in_flight都为空 → 进入 Phase 5。
不要用前台的
sleep轮询;用步骤 2 的后台 waiter,harness 会在其退出时重新调用你。等待期间也可以使用 Monitor 工具。
脚本实现细节
scripts/dispatch-job.sh 用run-name(displayTitle)来匹配刚触发的 run:有os参数时工作流是ci-images-build-os.yml、标题<tag>-<os>;无os时是ci-images-build-postgres.yml、标题postgres-<tag>。它先查同分支同标题已有 run 的最大databaseId,再 dispatch,然后最多 3 次尝试、每次 20 次 × 3s 轮询去定位databaseId更大的新 run——这样即使 dispatch 命令报错但实际已落地,也不会重复触发。run id 输出到 stdout,其余日志走 stderr,便于调用方干净地捕获 id。
scripts/wait-for-runs.sh 阻塞直到至少一个给定 run 达到status=completed,然后逐行打印已完成 run 的<id> <conclusion>(进行中的不打印),success | failure | cancelled | timed_out | ...,一旦有完成即退出 0。后续用仍在飞的 id 重新调用即可继续等待。
Phase 4 — 诊断、修复、重试失败的构建
对失败的 run id:
gh run view <id> --log-failed # 失败步骤 + 错误每个 run 有两个build矩阵腿(amd64、arm64)加一个merge任务。--log-failed会指出哪条腿(哪个架构)失败——据此选择要编辑的文件(按架构区分的docker/images/<os>/docker-compose.<arch>.yml或共享的docker/images/<os>/Dockerfile.*)。merge任务失败说明某条腿没推上它的架构 tag(真正的失败在腿,修腿),或者是一次瞬时的 GHCR/imagetools抖动(重新 dispatch)。重新 dispatch 会重建两个架构并重新合并,这是预期行为。
按失败特征(失败步骤 + 错误)分类,而不是只看任务名:
- 瞬时失败(runner 丢失、网络抖动、GHCR 5xx、"no space left"、
setup-qemu抖动、CTAN/mirror 拉取失败、甚至一次脆弱的gh调用):无代码改动,重新 dispatch 同一任务(无需 push),放回in_flight,递增transient_retries(上限5——抖动不是任务的错); - 真实构建错误(包/仓库找不到、某架构 Node 404、Dockerfile/compose 问题、基础镜像或上游包变更):编辑对应文件(
docker/images/<os>/Dockerfile.*、docker/images/<os>/docker-compose.<arch>.yml或docker/images/postgres/Dockerfile.postgres),保持改动最小且匹配周围风格,然后:git -C "$REPO" add -A git -C "$REPO" commit -m "Fix <os>/<arch> build: <what>" git -C "$REPO" push在(已更新的)分支上重新 dispatch,放回
in_flight,递增fix_attempts; - 某架构 Node 版本 404 通常意味着输入错误——停止并上报,不要浪费重试。
计数按根因、不按任务:一个任务可能连续命中多个不同的失败(先真实错误、再独立的基础设施抖动)。要把相应计数器重置/限定到当前失败特征,避免一个问题的预算被另一个问题消耗。
停止条件:当某个任务满足以下任一条件时暂停循环并升级——同一失败特征下fix_attempts达到3(真实修复不生效)、或transient_retries达到5(持续抖动)、或失败不明确/修复会改变镜像内容而应由所有者决策(例如固定包版本)。暂停时报告:任务、失败步骤、已尝试内容、gh run view <id> --log-failed摘录。其他在飞/排队任务继续,只有卡住的任务阻塞最终成功。不要无限循环。
推送到分支的修复只影响推送之后dispatch 的构建;已在飞的 run 沿用旧代码,让它们自然结束并独立评判即可。
仓库内已证实的三类失败模式
1. R 源码包需要比发行版更新的系统库(例如terra1.9 需要 GDAL ≥ 3.7,而 Debian bookworm 只有 3.6.2,报gdal_multidimensional.cpp ... no matching function for ... AsClassicDataset)。修复:把 dev-deps 安装固定到破坏性包版本之前的某个 PPM 快照日期,作用于该 OS 的R_REPO/RSPM:https://packagemanager.posit.co/cran/<DATE>(源码)与https://packagemanager.posit.co/cran/__linux__/<distro>/<DATE>(二进制)。破坏日期可从https://cran.r-project.org/src/contrib/Archive/<pkg>/查得。(rocky_8走不同路线,从源码构建更新的 GEOS/GDAL/libgit2,所以这只影响 Debian 系;rocky_9两者都不需要:EL9 + EPEL9 已自带 GEOS 3.13 / GDAL 3.10 / PROJ 9.6 / libgit2 1.7 和支持 C++20 的 GCC,那里应优先用固定日期的 PPM 快照而不是重新引入源码构建。)
2. PPMlatest发布窗口竞争(报! Failed to download <pkg> from https://packagemanager.posit.co/cran/.../latest/.../<pkg>_<ver>.tar.gz,发生在pak::local_install_dev_deps期间,前面很多包已正常下载)。根因:滚动latest通道上,pak 从PACKAGES元数据解析出版本,但对应的二进制/源码文件可能已在发布中途轮出latest→ 间歇性 404。它看起来像瞬时问题但会反复出现。修复:把构建期dev-deps 安装固定到有日期快照(源码https://packagemanager.posit.co/cran/<DATE>、二进制.../cran/__linux__/<distro>/<DATE>),保证内部一致;各镜像运行期的.Rprofile/ENV RSPM保持latest。日期就是PPM_SNAPSHOTbuild arg,按 OS 设置在docker/images/<os>/docker-compose.*.yml中,由各Dockerfile.*消费(参见 Dockerfile.ubuntu24_04 中options(repos=c(RSPM='.../${PPM_SNAPSHOT}'), ...)的用法)。四个滚动 OS 用scripts/bump-ppm-snapshot.sh <DATE>提升;debian 有意排除(因上述 terra/GDAL 原因冻结在2026-03-01,不要随其他 OS 一起动)。改变镜像内容(包版本)属于所有者决策——重新固定前要与用户确认日期。
3. TinyTeX /tlmgr间歇性失败(could not get texlive.tlpdb,或从随机镜像拉取后安装失败)。根因:tlmgr option repository https://mirror.ctan.org/...是轮询式分配,每次调用可能落在过期/不可达的镜像上。修复:固定到单个完整镜像(https://ctan.math.illinois.edu/systems/texlive/tlnet),并把网络依赖的tlmgr update/install包进一个小重试循环。6 个 OS 的 Dockerfile 中都存在该模式(Dockerfile.ubuntu24_04 即是固定 CTAN 镜像 +retry函数包裹tlmgr步骤的实例)。
Phase 5 — 收尾
全部 7 个任务done(success)后:
- 用
gh api -X PATCH(见 Phase 1 备注)勾选全部 PR checklist 框;用gh pr comment <n>添加总结评论,列出每个推送的 manifest:tag,例如ghcr.io/posit-dev/positron-ubuntu24:<tag>…positron-postgres:<tag>(单架构-amd64/-arm64tag 也作为构建产物推上去了); - PR 保持打开供人工审查——不要合并;
- 向用户汇报:PR URL、tag、Node 版本,以及 7 个多架构镜像全部构建并推送成功的确认;注明任何需要修复的内容(附上对应 commit 链接)。
如果因卡住的任务提前停止,则精确报告哪些已绿、哪些被阻塞、诊断结果,并把分支/PR 留在原地供用户接管。
一次完整重建的端到端视图
把整个流程串起来看:预检确认认证与干净工作树 → 在update-images/<tag>分支上由 bump-node-version.sh 批量更新 12 个 compose 文件的NODE_VERSION并开 PR → 用 dispatch-job.sh 以默认并发 4 逐批 dispatch 7 个构建 → 由 wait-for-runs.sh 后台轮询、按成功/失败分流 → 失败按特征归类,瞬时抖动最多重试 5 次,真实错误修复后最多 3 次 → 全绿后勾选 checklist、留总结评论,PR 交给人工审查合并。每个 OS 的构建最终产出ghcr.io/<owner>/positron-<os-short>:<tag>多架构 manifest,同时保留-amd64/-arm64单架构 tag 供存量流程过渡。
这套编排的关键设计都沉淀在 SKILL.md 与其 scripts 目录中:双计数器分离"代码修复"与"基础设施抖动"的重试预算、run-name + databaseId 双保险防重复 dispatch、后台 waiter 避免长轮询阻塞会话,以及三类已证实的失败模式及其修复方案——它们共同保证了在数小时的批量重建中,Agent 能以受控并发稳定地把全部 7 个镜像推向同一个新 tag。
- 开发工具
- 代码编辑器
- 数据科学
【免费下载链接】positron
Positron, a next-generation data science IDE
相关推荐
osquery CI 多架构 Docker 构建镜像实战:基于 buildx 的 x86/arm64 Linux 工具链镜像生成指南
osquery CI 多架构 Docker 构建镜像实战:基于 buildx 的 x86/arm64 Linux 工具链镜像生成指南 导读 osquery 作为
观测代理网络安全5分钟上手多架构Docker镜像:GitLab CI+buildx实战指南
5分钟上手多架构Docker镜像:GitLab CI+buildx实战指南 在容器化技术快速发展的今天,构建支持多架构的Docker镜像已成为开发者的必备技能。
云原生DevOps构建 Earthly CI 镜像:为容器化 CI 定制内含 Earthly 的 Docker 镜像完整指南
构建 Earthly CI 镜像:为容器化 CI 定制内含 Earthly 的 Docker 镜像完整指南 导读 本指南面向需要在容器化 CI(如 Jenkin
CI/CDDevOps构建工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考