拉取时间与扩容速度:最先感受到的收益
拉取时间与扩容速度:最先感受到的收益
我们有个 Java 服务,镜像原本 1.1GB(openjdk:8 基础镜像 + 全量依赖 + 构建工具残留),改成多阶段构建加 jre-slim 后是 210MB。在 100Mbps 带宽的节点上,冷拉取从 90 秒降到 18 秒,HPA 扩容时 Pod 从 Pending 到 Ready 的 P99 从 2 分 10 秒降到 40 秒左右。
更隐蔽的是 imagefs 压力。节点本地盘 100GB,kubelet 默认在 imagefs 使用率到 85% 时触发镜像回收。1.1GB 的镜像让单节点只能缓存五六十个实例,业务高峰扩容时反复出现“刚拉下来又被 GC 删掉”的抖动,日志里能看到连续的 ImageGCFailed 和重新拉取。镜像降到 200MB 后,同节点可缓存量翻了几倍,这种抖动基本消失。
分发与存储成本:对 registry 是复利的
一个 300 副本的服务,每次发版跨可用区拉取,按 200MB 算单次分发约 60GB;缩到 60MB 就是 18GB。我们统计过一个中等规模集群(约 400 个 Pod),月跨区流量费从四位数降到三位数。registry 的存储账单同理,保留 30 天历史版本时,差异会被放大 30 倍。
CI 侧收益也很直接:多阶段构建把编译工具链留在 builder 阶段,最终镜像只做 COPY --from=builder /app/bin /app/bin。配合把变动频繁的 COPY 放在最后、依赖安装放前面,layer 命中缓存后构建时间从 6 分钟降到 90 秒。
攻击面与合规:扫描器数字最直观
同一个服务,基于 ubuntu:20.04 的镜像用 Trivy 扫出 200 多个漏洞(HIGH/CRITICAL 十几个),换成 distroless 后降到个位数。原因不是 distroless 更安全,而是里面根本没装那些包,没有包就没有对应的 CVE。
另一个收益是“没有 shell”带来的约束。镜像里没有 bash,出问题只能靠同命名空间的 debug 容器排查,反而倒逼我们把日志和指标做全——这算是意外收获。
几个真正见效的实操动作
- 清理包管理器缓存必须在同一个 RUN 里:
RUN apt-get update && apt-get install -y --no-install-recommends xxx && rm -rf /var/lib/apt/lists/*。分成两条 RUN 的话,缓存已经写进上一层,删了也不减体积。 - 不要写
apt-get upgrade,它会无谓地引入大量变更且极难复现。 - 写好 .dockerignore,把
.git、node_modules、测试数据排除掉。见过一个镜像光.git就 180MB,原因是COPY . .之前没有忽略文件。 - 基础镜像按需选:Go/Rust 直接
CGO_ENABLED=0静态编译进 scratch 或 distroless-static;Python 用 slim 而不是完整版;Node 用 alpine 前先确认 native 模块能编过。
瘦身的边界:别把它做成信仰
踩过的坑值得写下来:去掉 ca-certificates 后调外部 HTTPS 服务直接报 x509 错误;去掉 tzdata 后容器内是 UTC,日志时间戳和业务对不上;alpine 的 musl libc 不走 nsswitch,DNS 解析行为和 glibc 不同,某些 Java native 库也跑不起来;删掉 curl/wget 后 healthcheck 脚本失效。
我的经验是给镜像定一个“够用”的基线:保留证书、时区、非 root 用户和必要的健康检查工具,其余一律砍掉。瘦身的目标是降低拉取和分发的边际成本、缩小攻击面,而不是把体积数字压到最小。
- 点赞
- 收藏
- 关注作者
评论(0)