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.gittarget这些文件夹,构建速度能快才怪。

# .dockerignore 示例
.git
node_modules
target
*.log
Dockerfile
.dockerignore
小技巧: 把.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。客户当时就愣住了。

注意: 多阶段构建时,第一阶段的名字(比如这里的builder)要写对。我见过有人写错名字,结果第二阶段拷贝不到文件,镜像启动就报错。

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。记得第一时间改密码。

经验之谈: 生产环境一定要配置HTTPS。Harbor支持自签名证书,也可以用Let's Encrypt。我曾经见过有人用HTTP,结果镜像被中间人篡改,差点出大事故。

3.4.3 配置镜像复制

Harbor支持跨仓库复制。比如你在北京机房有一个Harbor,上海机房也有一个。可以配置规则,让镜像自动同步过去。这样,两地部署时拉取速度飞快。

# 在Harbor UI中配置复制规则
# 源仓库:beijing-harbor/library
# 目标仓库:shanghai-harbor/library
# 触发模式:基于事件(推送时自动触发)

3.4.4 集成漏洞扫描

Harbor内置了Trivy扫描器。每次推送镜像,它会自动扫描已知漏洞。我建议把扫描结果作为CI/CD流水线的一环,有高危漏洞就阻止部署。

避坑指南: 我曾经遇到过Harbor磁盘空间被撑爆的情况。原因是镜像历史版本太多,垃圾回收没开。记得定期执行垃圾回收,清理无用的blob。

3.5 总结:容器化改造的“三板斧”

这一章内容不少,我帮你捋一捋核心思路:

  • Dockerfile最佳实践: 选小镜像、合并层、用.dockerignore。别把镜像写成毛坯房。
  • 多阶段构建: 把编译环境和运行环境分开。这是瘦身最有效的手段。
  • 镜像瘦身策略: 清缓存、压二进制、合并COPY。细节决定成败。
  • Harbor搭建: 企业级私有仓库,支持复制、扫描、权限控制。给镜像安个家。

嗯,这一章就到这里。下一章咱们聊聊Kubernetes上的部署策略,到时候你会看到,镜像做得好,K8s部署就成功了一半。