3、容器化改造:Dockerfile最佳实践、多阶段构建、镜像瘦身策略、私有镜像仓库搭建(Harbor)
好,咱们进入第三章。容器化改造,说白了就是把你的应用塞进一个“标准箱子”里。这个箱子就是镜像。我见过太多团队,Dockerfile写得跟流水账似的,镜像动不动几个G,部署一次等半天。这章我就把压箱底的经验掏出来,聊聊怎么写出优雅的Dockerfile,怎么把镜像瘦成一道闪电,再搭一个靠谱的私有仓库。
3.1 Dockerfile 最佳实践:别把镜像写成“毛坯房”
写Dockerfile,很多人第一反应是“能跑就行”。嗯,这想法很危险。我早期吃过这个亏,一个Java应用,基础镜像用了个大而全的,结果镜像1.2G,每次拉取都像在下载电影。后来我总结了几个核心原则。
3.1.1 选择合适的基础镜像
基础镜像选不对,后面全白费。我个人习惯,能用Alpine绝不用Ubuntu。Alpine才5M,Ubuntu奔着200M去了。你想想看,一个Java运行时,用openjdk:11-jre-slim就比openjdk:11-jdk小很多,因为JDK里的编译工具运行时根本用不到。
3.1.2 减少层数,合并RUN指令
Dockerfile每一行指令都会生成一层。层数多了,不仅构建慢,拉取也慢。我建议把相关的操作合并到一个RUN里。比如安装依赖、清理缓存,一气呵成。
# 不推荐:每步一层
RUN apt-get update
RUN apt-get install -y curl vim
RUN rm -rf /var/lib/apt/lists/*
# 推荐:合并成一层
RUN apt-get update && \
apt-get install -y curl vim && \
rm -rf /var/lib/apt/lists/*
为什么要删掉/var/lib/apt/lists/*?因为这些缓存文件在镜像里毫无用处,只会白白增加体积。我在项目中见过有人忘了这步,镜像凭空多了几十M。
3.1.3 利用 .dockerignore 文件
这个文件太容易被忽略了。默认情况下,docker build会把当前目录所有文件都发送给Docker守护进程。你想想,如果目录里有node_modules、.git、target这些文件夹,构建速度能快才怪。
# .dockerignore 示例
.git
node_modules
target
*.log
Dockerfile
.dockerignore
3.2 多阶段构建:把“工地”和“成品”分开
多阶段构建是我最推崇的镜像瘦身手段。它的思路很简单:第一阶段是“工地”,用来编译、打包;第二阶段是“成品”,只拷贝最终产物。这样,编译工具、临时文件都不会进入最终镜像。
举个例子,一个Go应用:
# 第一阶段:编译
FROM golang:1.20 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o myapp .
# 第二阶段:运行
FROM alpine:3.18
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/myapp .
CMD ["./myapp"]
你看,第一阶段用了1G多的Go镜像,第二阶段只用了5M的Alpine。最终镜像只有十几M。我记得有一次帮客户优化一个Python应用,他们原来的镜像800M,用了多阶段构建后,直接降到120M。客户当时就愣住了。
3.3 镜像瘦身策略:从“大胖子”到“肌肉男”
除了多阶段构建,还有一些细节能让镜像更小。我总结了几条实战经验。
3.3.1 清理包管理器的缓存
不管是apt、yum还是apk,安装包后都会留下缓存。记得在同一个RUN指令里清理掉。
RUN apt-get update && \
apt-get install -y --no-install-recommends some-package && \
rm -rf /var/lib/apt/lists/*
3.3.2 使用 --no-install-recommends
这个参数能避免安装不必要的推荐包。比如你装个nginx,它可能顺手给你装上perl、python等依赖。加上这个参数,只装核心依赖。
3.3.3 压缩二进制文件
对于Go、Rust这类编译型语言,可以用upx压缩一下二进制文件。我试过,一个20M的二进制能压到5M左右。不过要注意,压缩后的启动速度会慢一点点,看场景取舍。
RUN upx --best /app/myapp
3.3.4 合并多个COPY指令
COPY指令也会产生层。如果多个文件来自同一个目录,尽量合并成一个COPY。
# 不推荐
COPY config.json .
COPY entrypoint.sh .
# 推荐
COPY config.json entrypoint.sh ./
| 策略 | 优化前 | 优化后 | 节省比例 |
|---|---|---|---|
| 多阶段构建 | 800M | 120M | 85% |
| 基础镜像替换 | 200M | 5M | 97.5% |
| 缓存清理 | 50M | 30M | 40% |
3.4 私有镜像仓库搭建(Harbor):给镜像安个家
镜像做好了,总不能都推到Docker Hub吧?一来慢,二来不安全。私有仓库是必须的。我推荐Harbor,它比原生的Registry功能强太多了。
3.4.1 为什么选Harbor?
Harbor提供了镜像复制、漏洞扫描、访问控制、审计日志等功能。说白了,它就是一个企业级的镜像管理中心。我在项目中用过好几个方案,最后还是觉得Harbor最省心。
3.4.2 快速部署Harbor
Harbor官方提供了离线安装包。我习惯用Docker Compose方式部署,简单直接。
# 下载离线安装包
wget https://github.com/goharbor/harbor/releases/download/v2.8.0/harbor-offline-installer-v2.8.0.tgz
tar xvf harbor-offline-installer-v2.8.0.tgz
cd harbor
# 修改配置文件
cp harbor.yml.tmpl harbor.yml
# 修改 hostname、端口、密码等
# 执行安装
./install.sh
安装完成后,访问 http://your-host:80,默认账号admin,密码Harbor12345。记得第一时间改密码。
3.4.3 配置镜像复制
Harbor支持跨仓库复制。比如你在北京机房有一个Harbor,上海机房也有一个。可以配置规则,让镜像自动同步过去。这样,两地部署时拉取速度飞快。
# 在Harbor UI中配置复制规则
# 源仓库:beijing-harbor/library
# 目标仓库:shanghai-harbor/library
# 触发模式:基于事件(推送时自动触发)
3.4.4 集成漏洞扫描
Harbor内置了Trivy扫描器。每次推送镜像,它会自动扫描已知漏洞。我建议把扫描结果作为CI/CD流水线的一环,有高危漏洞就阻止部署。
3.5 总结:容器化改造的“三板斧”
这一章内容不少,我帮你捋一捋核心思路:
- Dockerfile最佳实践: 选小镜像、合并层、用.dockerignore。别把镜像写成毛坯房。
- 多阶段构建: 把编译环境和运行环境分开。这是瘦身最有效的手段。
- 镜像瘦身策略: 清缓存、压二进制、合并COPY。细节决定成败。
- Harbor搭建: 企业级私有仓库,支持复制、扫描、权限控制。给镜像安个家。
嗯,这一章就到这里。下一章咱们聊聊Kubernetes上的部署策略,到时候你会看到,镜像做得好,K8s部署就成功了一半。