8 个服务镜像从平均 ~110MB 精简约 88%,降到平均 ~13MB。
一、以前 vs 现在:基底镜像
| 项 | 以前 | 现在 |
|---|---|---|
| 基底镜像 | debian:bookworm-slim |
alpine:3.20 |
| 基底体积 | ~70MB | ~3.5MB |
| 装了什么 | ca-certificates |
ca-certificates + tzdata |
| 运行用户 | root | 非 root(app 用户) |
为什么选 alpine 而不是 distroless/scratch?
-
Go 服务是
CGO_ENABLED=0纯静态二进制,任何最小基底都能跑 -
alpine 比 debian-slim 小约 90%,自带 busybox shell,容器里排查问题方便
-
apk add装个证书就完事,无需包管理器红海 -
distroless/scratch 虽更小(2-3MB),但没有 shell,出问题无法进入排查
二、最终体积对比
| 服务 | 之前 (debian) | 现在 (alpine) | 节省 |
|---|---|---|---|
| 微服务 A | ~177MB | 19.0MB | ~89% |
| 微服务 B | ~147MB | 18.0MB | ~88% |
| 微服务 C | ~133MB | 16.0MB | ~88% |
| 微服务 D | ~147MB | 16.0MB | ~89% |
| 微服务 E | ~37MB | 10.0MB | ~73% |
| 微服务 F | ~90MB | 9.0MB | ~90% |
| 微服务 G | ~80MB | 9.0MB | ~89% |
| 微服务 H | ~90MB | 9.5MB | ~89% |
| 平均 | ~110MB | ~13MB | ~88% |
三、为什么以前那么大?
不是单纯的"debian 比 alpine 大"那点差距,真正的大头是 镜像里装了不必要的重量级工具:
-
图片转 WebP 用
ffmpeg实现,多个服务都要装 ffmpeg(含大量共享库 + 构建依赖) -
硬件能力探测 调用了
ffmpeg -encoders,导致核心服务也要装 ffmpeg -
浏览器依赖 被误打进容器------某些工具依赖只在独立的 CLI 工具中,本不该进容器镜像
四、瘦身三板斧
1. 基底换 alpine
Dockerfile 运行阶段:
dockerfile
FROM alpine:3.20 AS runtime
RUN apk add --no-cache ca-certificates tzdata \
&& addgroup -S app && adduser -S -G app app
USER app
COPY --from=build /out/app /app
ENTRYPOINT ["/app"]
2. 图片转 WebP 前移到前端
前端上传前用 canvas / OffscreenCanvas 完成 WebP 转换:
-
等比缩放到最大 1920px
-
质量参数 0.6
-
GIF/WebP 原样跳过
后端改成纯 pass-through,不再调用 exec ffmpeg。
额外收益: 上传前压缩节省了网络带宽和存储空间,绝大多数现代浏览器都支持 WebP。
3. 砍掉容器里根本不用的依赖
确认主服务不 import 某个工具模块 → 该模块的依赖(如浏览器)只保留在独立的 CLI 工具中,不进容器镜像。
五、验证结果
-
所有镜像重建完成
-
全部容器启动成功,进程日志无异常
-
核心接口正常返回
-
业务链路不受影响
六、还能不能更小?
可以,但投入产出比不高:
| 方案 | 收益 | 代价 |
|---|---|---|
| distroless/scratch | 再省 3-4MB | 无 shell,排查问题困难,需从 gcr.io 拉取 |
| 去掉 tzdata | 省 1-2MB | 日志时间变 UTC |
| upx 压缩二进制 | 省十几 MB | 增加复杂度,压缩后启动有额外开销 |
结论:alpine + 纯静态 Go + 前端处理图片 = 生产可用 + 最精简的甜点位。
七、核心经验
-
换基底镜像是最直接的瘦身手段 --- debian-slim → alpine,一步省 60+ MB
-
审视容器里真正需要什么 --- 不要把全量构建工具链带进运行镜像
-
能往前端推的就往前端推 --- 图片处理在浏览器端完成,后端只做透传
-
多阶段构建 + 非 root 用户 --- 安全与体积兼顾
-
定期审视依赖 --- 有些依赖只在特定场景使用,可以拆分到独立镜像