目标
典型的应用镜像为了跑一个进程而装了一整个 Linux 发行版:包管理器、shell、各种工具,全是应用从未用到的攻击面与 CVE 噪声。distroless 镜像只含运行时与应用本身。迁移通常只是 Dockerfile 两行改动,真正的工作是保住“没法再 sh 进去”之后的排障能力。
步骤要点
- 记录基线:当前镜像体积与 trivy HIGH/CRITICAL 数量(如 950MB / 212 个 HIGH)。
- 最终阶段换成 gcr.io/distroless/nodejs22 并以 nonroot 运行,多阶段构建只拷应用产物。
- 同步构建 :debug 变体(内含 busybox)发布到仓库,事故时仍可 exec 排障。
- 对新镜像跑冒烟套件,证明没有系统包也能正常服务。
- 复测体积与 CVE(约 120MB / 个位数 HIGH),并把这两个数字设成 CI 门禁防回潮。
排错提示
容器立刻崩溃并报难懂的错误,多半是应用 shell 调了已不存在的系统工具,从日志找到子进程调用改用库实现;没法调试运行中容器正是 distroless 的本意,请用 debug 标签或 kubectl debug 临时容器;健康检查依赖 wget/curl 会失效,改成语言级端点探活或 TCP 探针。
适用边界
移除 shell 动了你的调试习惯,debug 标签必须是流程的一部分而非事后补救;依赖原生扩展时需选匹配运行时的 distroless 变体,否则退回 slim。
代码示例
FROM gcr.io/distroless/nodejs22
WORKDIR /app
COPY --from=build /app /app
USER nonroot
CMD ["server.js"]
# Incidents still need a shell: build a second tag
FROM gcr.io/distroless/nodejs22:debug