4、应用容器化实战:Dockerfile编写技巧、多阶段构建、镜像瘦身、私有镜像仓库Harbor部署
容器化,说白了就是把你的应用连同它的运行环境打包成一个标准件。我见过太多团队在Dockerfile上翻车,要么镜像大到几百兆,要么构建一次要半小时。今天咱们就聊聊怎么把容器化这件事做得漂亮。
4.1 Dockerfile编写技巧:从能用用到好用
写Dockerfile,很多人上来就是一顿COPY,然后RUN npm install。嗯,这样确实能跑,但问题不少。我个人习惯遵循几个原则:
- 层数越少越好:每个RUN、COPY都会产生一层,层多了构建慢,传输也慢。
- 不变量放前面:把不常变的基础镜像、系统依赖放前面,代码放后面。这样能充分利用缓存。
- 一个RUN干一件事:但别拆太碎,比如安装依赖和清理缓存可以放一起。
反面教材:我见过一个Dockerfile,写了十几个RUN,每个RUN只装一个包。构建一次要20分钟,而且镜像体积1.2GB。后来我帮他们合并成3个RUN,体积直接降到400MB。
来看一个典型的Node.js应用Dockerfile:
FROM node:18-alpine AS base
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
FROM base AS build
COPY . .
RUN npm run build
FROM node:18-alpine AS production
COPY --from=build /app/dist ./dist
COPY --from=base /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/index.js"]
这里有个小技巧:npm ci比npm install快,而且能保证依赖版本一致。我在项目中遇到过因为npm install自动升级了小版本导致线上出bug的情况,后来全换成npm ci了。
4.2 多阶段构建:让镜像瘦下来
多阶段构建是Docker 17.05之后引入的特性。说白了就是:你可以在一个Dockerfile里用多个FROM,每个FROM是一个独立的构建阶段,最终只把需要的文件复制到最后一个阶段。
为什么要这么做?你想想看,编译Go程序需要gcc、glibc,但运行时只需要一个二进制文件。把编译环境和运行环境分开,镜像自然就小了。
| 构建方式 | 镜像大小 | 构建时间 | 安全性 |
|---|---|---|---|
| 单阶段构建 | ~1.2GB | 5分钟 | 低(包含编译工具) |
| 多阶段构建 | ~150MB | 3分钟 | 高(仅包含运行时) |
我曾经帮一个Java团队优化镜像。他们用maven打包,然后直接把整个JDK和maven都塞进镜像里,镜像1.8GB。我改成多阶段构建后,最终镜像只有200MB,而且构建时间从8分钟降到了2分钟。
小提示:多阶段构建时,可以用--from=stage_name指定从哪个阶段复制文件。阶段名可以自定义,比如AS builder、AS runtime。
4.3 镜像瘦身:从GB到MB的实战技巧
镜像瘦身,我总结了几个实用技巧:
- 选对基础镜像:alpine比ubuntu小很多,但要注意兼容性。比如Python的某些包在alpine上需要额外编译。
- 清理缓存:apt-get、yum、npm都会产生缓存,记得在同一个RUN里清理掉。
- 合并RUN指令:能用&&连接的尽量连起来,减少层数。
- 使用.dockerignore:排除不需要的文件,比如.git、node_modules、测试代码。
- 考虑distroless镜像:Google的distroless镜像只包含运行时,连shell都没有,安全性极高。
注意:瘦身不是越瘦越好。我见过有人为了瘦身把ca-certificates都删了,结果应用无法访问HTTPS接口。该留的系统依赖一定要留。
来看一个实际案例。一个Python应用,原始镜像1.2GB:
# 优化前
FROM python:3.11
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "app.py"]
# 优化后
FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user -r requirements.txt
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY . .
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "app.py"]
优化后镜像只有180MB。为什么?因为python:3.11包含了完整的编译工具链,而python:3.11-slim只包含运行时。再加上多阶段构建,把pip的缓存也去掉了。
4.4 私有镜像仓库Harbor部署
镜像造好了,得有个地方存。Docker Hub虽然方便,但企业级应用还是得用私有仓库。Harbor是目前最流行的选择,它支持镜像复制、漏洞扫描、访问控制等功能。
部署Harbor其实不复杂,但有几个坑要注意。我记得第一次部署时,因为没配好HTTPS,客户端一直报证书错误。后来才明白,Harbor默认要求HTTPS,除非你显式配置HTTP模式。
快速部署步骤:
- 下载Harbor离线安装包(大概2GB)
- 修改
harbor.yml配置文件 - 运行
./install.sh
核心配置示例:
# harbor.yml
hostname: harbor.example.com
http:
port: 80
https:
port: 443
certificate: /data/cert/server.crt
private_key: /data/cert/server.key
harbor_admin_password: YourStrongPassword
database:
password: root123
data_volume: /data/harbor
避坑指南:我曾经在生产环境部署Harbor,忘了修改data_volume路径,结果数据全存到了默认的/data目录。后来磁盘满了,镜像推送失败。建议把数据目录挂载到独立的数据盘上。
部署完成后,客户端配置:
# 配置Docker信任私有仓库
# 编辑 /etc/docker/daemon.json
{
"insecure-registries": ["harbor.example.com"]
}
# 重启Docker
systemctl restart docker
# 登录Harbor
docker login harbor.example.com
# 推送镜像
docker tag myapp:latest harbor.example.com/library/myapp:latest
docker push harbor.example.com/library/myapp:latest
Harbor还支持镜像复制功能。比如你在测试环境有一个Harbor,生产环境也有一个,可以配置自动复制。这样测试通过的镜像,一键就能同步到生产环境。我在项目中就用这个功能实现了CI/CD流水线的自动化。
小技巧:Harbor的垃圾回收功能很重要。镜像推多了,会有很多无用的层占用磁盘空间。建议每周执行一次垃圾回收,命令是docker run --rm -v /data/harbor:/data harbor/harbor-registryctl:latest garbage-collect。
好了,这一章的内容就到这里。从Dockerfile编写到多阶段构建,再到镜像瘦身和Harbor部署,每一步都有讲究。记住一句话:容器化不是把应用塞进容器就完事了,而是要让它跑得稳、跑得快、跑得省。