15 KiB
name, description
| name | description |
|---|---|
| runtime-image | 在具体项目中探测技术栈并按 runtime-base 镜像矩阵选择正确的基础运行时镜像、生成/修正项目 Dockerfile;也能对已存在的 Dockerfile 做评估审计并修正。凡用户要求「写 Dockerfile / 容器化这个项目 / 选基础镜像 / FROM 哪个镜像 / 帮我打包成镜像 / 这个项目部署成容器 / 评估一下这个 Dockerfile / 帮我看看 Dockerfile 有没有问题 / 优化 / 评审 / 精简镜像」,或出现 容器起不来 / exec format error / 镜像太大 / pip 安装编译失败 / latest 镜像行为变了 等症状,或提到 runtime-node / runtime-pm2 / runtime-base / livebook:8418 / code.innev.cn / 镜像源 时,务必使用本 skill——即使用户没说「运行时镜像」四个字。Use whenever the user needs to pick a base runtime image (runtime-<name> matrix), write a project Dockerfile, or audit/fix an existing Dockerfile. 边界:构建/推送业务镜像归项目 CI 或 xgent-image-push;修改 runtime 镜像本身(加镜像/改版本)归 runtime-base 仓库维护,不用本 skill。 |
runtime-image — 按项目语言与环境选运行时镜像
依据 runtime-base 仓库的镜像矩阵(10 个镜像:Linux 7 个 Alpine 系 + Wine 3 个),在项目里完成「探测栈 → 选型 → 写 Dockerfile → 真机验证」。两种工作模式:
- 新建模式:项目没有 Dockerfile,从零选型生成
- 评估修正模式:项目已有 Dockerfile(或用户要求"看看这个 Dockerfile"),先审计再修正
核心理念(推理链,所有约束都挂在这上面):runtime 系列只负责把编译好的代码跑起来——构建/编译由项目自己的 CI 完成,镜像内无编译器/SDK(Node 系唯一例外:容器启动时才 install+build,自带 node-gyp 链)。因此项目 Dockerfile 恒为单段:FROM runtime-<name> → COPY 产物 → CMD。
总流程
前置 镜像源探测(脚本自动判内网/外网)
→ 第 0 步 探测技术栈(同时探测已有 Dockerfile → 有则转评估修正模式)
→ 第 1 步 选型对照 → 第 2 步 定 tag
→ 第 3 步 写/改 Dockerfile → 第 4 步 真机验证 → 第 5 步 汇报
前置:镜像源探测(每次任务最先做,绝不可跳过)
镜像源有两路,由探测决定,不许凭经验猜:
| 源 | 前缀 | 适用 | 条件 |
|---|---|---|---|
| 内网 | livebook:8418/images |
内网机器,构建拉取最快 | ① daemon.json 已登记 insecure-registries: ["livebook:8418"](明文 HTTP,不登记 docker 拒拉)② http://livebook:8418/v2/ 可达(200/401 均算通) |
| 外网 | code.innev.cn/images |
外网/分发给同事使用 | 内网任一条件不满足时兜底 |
执行 scripts/detect-registry.sh,stdout 最后一行即镜像前缀;之后所有 Dockerfile 的 FROM 统一写成 <前缀>/runtime-<name>:<tag>。两路都不通(exit 1)→ die:停下报排查方向(内网机器确认同网段+登记+重启 docker;外网机器确认能访问 https://code.innev.cn),不硬写任何一个。
→ 验收:本次任务的镜像前缀有探测结论(内网/外网)背书,并记入第 5 步汇报。
第 0 步:探测技术栈(绝不可跳过)
不许凭用户一句话定镜像——读项目文件坐实,再听用户补充部署需求。
| 探测到什么 | 判定 |
|---|---|
package.json(无构建产物直出静态页) |
Node 后端 → 再问:单进程还是多进程/需要 cluster? |
package.json + 纯前端(react/vue/vite 静态产物) |
不走本系列,CI 出静态文件由项目自己的 nginx 镜像托管 |
requirements.txt / pyproject.toml |
Python |
go.mod / Cargo.toml |
Go/Rust → 静态二进制 → runtime-base |
pom.xml / build.gradle |
Java → jar |
*.csproj / *.sln |
.NET → 必须再问一句:现代 .NET(publish 产物)还是 Framework 4.x?两者走完全不同的镜像 |
CMakeLists / Makefile(C/C++) |
C/C++ → 必须确认 musl 动态链接(见约束⑤) |
| MQL4 / MT4 终端需求 | runtime-mt4 |
| 脚本/运维工具 | runtime-base |
同时探测已有 Dockerfile:glob 项目内所有 Dockerfile*(含子目录、.dockerignore)。存在即转入「评估修正模式」(见第 2 步之后);不存在走新建模式顺次执行。多个 Dockerfile 逐一评估。
→ 验收:判定结论有文件证据(路径),拿不准的在选型前问清,不猜;是否已有 Dockerfile 有明确结论。
第 1 步:选型对照
| 项目类型 | FROM | 项目提供什么 |
|---|---|---|
| Node 后端(单进程) | runtime-node |
源码(不含 node_modules);启动时 install+build |
| Node 后端(多进程) | runtime-pm2 |
同上 + pm2 cluster(缺省 max 核数) |
| Python | runtime-python |
源码 + venv/(CI 装好依赖) |
| Go / Rust 静态二进制 | runtime-base |
编译好的二进制 |
| C/C++(musl 动态链接) | runtime-cpp |
编译好的二进制 |
| Java jar | runtime-java |
target/*.jar |
| 现代 .NET publish 产物 | runtime-dotnet |
dotnet publish 输出目录 |
| .NET Framework 4.x | runtime-wine-dotnet |
bin/Release/ 目录 |
| MT4 终端 | runtime-mt4 |
不 COPY,运行时挂数据目录 |
| 脚本/工具 | runtime-base |
脚本文件 |
镜像全名:<前缀>/runtime-<name>:<tag>——前缀由前置探测给出(内网 livebook:8418/images / 外网 code.innev.cn/images),也可被 INNEV_IMAGE_PREFIX 显式覆盖(CI 场景)。矩阵详情(预装内容/环境变量/Wine 说明)读 references/runtime-matrix.md,按需读取。
die:探测结果不在矩阵内(如 Ruby/PHP)→ 停下报"无匹配运行时镜像",给两个去向(项目自建完整镜像 / 去 runtime-base 仓库加新镜像),不硬塞一个近似的。
→ 验收:选出的镜像在对照表中有明确一行,且该行的特殊前提已逐条确认(musl 还是 glibc / 现代 .NET 还是 4.x / 单进程还是多进程)。
第 2 步:定 tag
三类 tag:latest(每周一随上游滚动重建)/ 短sha / 版本标签(如 v0.5.0)。
- 生产/交付项目 → 锁
版本标签或短sha(warn:用户坚持用 latest 时提醒滚动重建风险,不拦) - 本地试跑 → latest 可接受
→ 验收:Dockerfile 里 FROM 的 tag 是显式选择的结果,不是顺手写的。
评估修正模式(第 0 步发现已有 Dockerfile 时走此分支)
先做完第 1、2 步得出"应该长什么样"(正确镜像 + tag),再拿现状逐项对照审计。只读审计,审计报告出来之前不改任何文件。
审计清单(每条挂着硬约束编号,级别定义见下)
| # | 检查项 | 违反 | 级别 |
|---|---|---|---|
| A1 | 多段构建 / 装了 SDK / 镜像内编译(RUN mvn package、dotnet build、pip install 源码编译) |
① | 必须修 |
| A2 | Node 系 COPY 了 node_modules / 缺 .dockerignore 排除 / 把 dist 产物当通用模式 COPY(Node 系模型是启动时 install+build) |
② | 必须修 |
| A3 | ARG 传令牌 / 密钥 COPY 进层 / .npmrc 含 token 落镜像层 |
③ | 必须修(安全项,优先处理) |
| A4 | FROM ...:latest 且是生产/交付项目 |
④ | 建议修 |
| A5 | glibc 环境(debian/ubuntu CI)构建的 C/C++ 产物 FROM alpine 系 | ⑤ | 必须修 |
| A6 | Go/Rust 二进制 FROM golang/rust 全镜像或其他带语言环境的镜像 |
⑥ | 建议修(换 runtime-base,体积常减一个数量级) |
| A7 | 前端静态产物用 node 镜像 serve/http-server 跑 |
⑦ | 建议修(换 nginx 托管) |
| A8 | 栈与 FROM 错配(Framework 4.x 用 runtime-dotnet、多进程需求用 runtime-node 单进程跑等) |
选型表 | 必须修 |
| A9 | 缺 CMD/ENTRYPOINT、EXPOSE、入口路径与 CI 产物路径对不上 |
— | 提示 |
| A10 | FROM 前缀与前置探测结论不符(外网机器 FROM livebook:8418 拉不动;内网机器 FROM code.innev.cn 白白绕远) |
⑧ | 必须修 |
级别定义:必须修 = 会挂(起不来/行为错)或泄密;建议修 = 能跑但体积/稳定性/可维护性受损;提示 = 瑕疵。只发现"建议修/提示"且用户只要评估时,不改文件,出报告即止。
审计报告格式(固定)
- 结论一行:当前 FROM 什么 → 应该 FROM 什么(一致则明说"选型正确")
- 问题表:级别 | 行号 | 问题 | 依据(约束编号) | 修法
- 没问题的项也要说一句"已过"(A1-A9 逐条过,不许只报坏不报好——漏报和误报一样有害)
报告摊开、用户确认要修 → 转第 3 步按模板改写(给 diff),再进第 4 步验证。用户说"只评估不动手"→ 报告即交付物。
第 3 步:写/改 Dockerfile
默认动作:先把选型结论 + 拟写的 Dockerfile 全文摊给用户确认,再落盘(改已有 Dockerfile 时给 diff)。
模板(通用模式,python/java/dotnet/cpp/base/wine 同构):
FROM livebook:8418/images/runtime-<name>:<tag>
COPY <CI 构建好的产物> <目标路径>
CMD [...]
Node 系模式(注意与通用模式不同——COPY 的是源码不是产物):
FROM livebook:8418/images/runtime-node:<tag>
COPY . . # .dockerignore 必须排除 node_modules
Node 系运行参数在 docker run 时经环境变量注入(APP_ENTRY/BUILD_CMD/NPM_REGISTRY/PM2_APP_NAME/PM2_INSTANCES 等,全表见 references),不落镜像层。已有 Dockerfile 若用了多段构建/装了 SDK/ARG 传令牌,按红线逐条指出并改写为单段。
→ 验收:Dockerfile 单段、无编译步骤、无密钥;Node 系配套 .dockerignore 排除 node_modules。
第 4 步:真机验证
完成定义 = 真机构建并起容器冒烟,不是"Dockerfile 语法没问题":
docker build -t <app>:test .成功docker run起容器,进程活着(Node 系首启会 install+build,启动慢是正常的,看日志等到 listening)- 有端口的验证端口可达 / 健康检查通过
环境不允许构建验证(宿主机没登记 livebook:8418 为 insecure-registry、无 docker)→ 显式声明"未验证",并把"待人工补"列进汇报。
第 5 步:汇报
固定结构:
- 模式:新建 / 评估修正
- 镜像源:内网 / 外网(探测结论,脚本输出佐证)
- 探测结论:语言/栈 + 证据文件路径
- 选型:
runtime-<name>:<tag>+ 一句话理由 - 【评估修正模式】审计结果:必须修 N 条 / 建议修 N 条 / 提示 N 条(附问题表),用户是否授权修正
- 写了什么:Dockerfile(新建/修改 diff)、
.dockerignore - 被跳过/未做的:如多进程需求未确认、CI 构建步骤缺失、用户只让评估没让修
- 验证做到哪一步:build/run 结果,没做就说没做
- 待人工补:daemon.json 登记、CI 构建产物步骤、secret 配置
硬约束
- ① 编译不进 runtime 镜像。为什么:镜像内无编译器/SDK(dotnet 无 SDK、java 无 JDK/Maven、python 无 musl 编译链),装了即失去"最小运行基座"意义;症状是构建期
mvn/dotnet SDK not found、pip 源码编译失败。唯一例外:Node 系启动时 install+build(自带 node-gyp 链,这是设计如此,不是漏洞)。 - ② Node 系不 COPY node_modules。为什么:entrypoint 启动时
pnpm install(有 lockfile 走--frozen-lockfile),COPY 进去会覆盖冲突且镜像膨胀;原生模块在容器内编译才匹配运行环境。 - ③ 令牌不用 ARG 传、不落镜像层。为什么:ARG 值留在
docker history;NPM_TOKEN走 env 会出现在docker inspect输出。正确姿势:NPM_TOKEN_FILE指 secret 文件(docker secret / k8s 挂载卷),令牌只写进容器内~/.npmrc。 - ④ 生产不用 latest。为什么:
latest每周一 02:30 随上游滚动重建,同一 tag 下面内容会变——上次能跑不代表下次能跑。锁版本标签或短 sha。 - ⑤ C/C++ 仅 musl 动态链接适用
runtime-cpp。为什么:Alpine 是 musl 系统,glibc 产物跑不起来,症状exec format error或not found(二进制存在但加载器缺失)。glibc 产物请换基座或静态编译。 - ⑥ Go/Rust 没有独立镜像。静态原生二进制运行时不需要语言环境,
FROM runtime-base即可——不要去找runtime-go。 - ⑦ 前端不走本系列。React/Vue 等由 CI 构建静态文件,项目自己的 nginx 镜像托管。
- ⑧ 镜像源由前置探测决定,不猜不写死。为什么:内网
livebook:8418是 LAN 明文 HTTP,外网根本够不着;外网code.innev.cn在内网绕远。FROM 写死任何一路,换个网络环境就拉不动。每次任务先跑scripts/detect-registry.sh;CI 等需要固定的场景用INNEV_IMAGE_PREFIX显式指定,并在汇报里说明。
排障对照表
| 症状 | 原因 | 怎么办 |
|---|---|---|
exec format error |
glibc 二进制进 musl 镜像 / 架构不符 | 构建阶段改 musl 或静态编译;确认 buildx 架构 |
拉镜像 http: server gave HTTP response to HTTPS client |
宿主未登记 insecure-registries: ["livebook:8418"] |
改 /etc/docker/daemon.json 并重启 docker(人工);或按前置探测改走外网前缀 |
外网机器 FROM livebook:8418 超时/拒连 |
内网前缀只在内网可达 | FROM 换 code.innev.cn/images/...(重跑前置探测确认) |
| 外网拉取 401 unauthorized | code.innev.cn 仓库要求认证 | docker login code.innev.cn(人工,用 Gitea 账号) |
| pip 安装编译失败 | runtime-python 无编译链 | 构建阶段(CI)编译好 wheel/venv 再 COPY |
| .NET 程序在 runtime-dotnet 起不来 | Framework 4.x 程序走了现代 .NET 镜像 | 换 runtime-wine-dotnet,xvfb-run -a wine 启动 |
令牌出现在 docker inspect |
NPM_TOKEN 走了 env | 改 NPM_TOKEN_FILE secret 挂载 |
| 上周能跑这周不能 | 用了 latest,周一滚动重建 | 锁版本标签/短 sha |
| Node 容器启动很慢 | 正常:启动时 install+build | 看日志等到 listening;别当挂起杀掉 |
红线(任何情况下不违反)
- 不凭猜测选型——第 0 步探测不到证据就问,不硬塞近似镜像
- 不给项目 Dockerfile 加编译段/SDK(Node 系启动时构建除外)
- 不在 Dockerfile/镜像层里写任何令牌密钥;用户贴过来的令牌不落进任何文件,并提醒轮换
- 不静默截断——跳过的需求(多进程/私有源/健康检查)必须出现在汇报里
- 改已有 Dockerfile 前先给结论和 diff,确认后再写