4. 应用容器化:编写 Dockerfile、构建镜像、推送至私有仓库的最佳实践

应用容器化是云原生弹性伸缩的基石。本章将围绕 Dockerfile 编写规范、多阶段构建优化、镜像安全扫描 以及 私有仓库推送策略 四个核心环节,提供可直接落地的工程化最佳实践。

4.1 Dockerfile 编写规范与多阶段构建

一个生产级的 Dockerfile 应遵循 分层最小化、缓存友好、安全合规 三大原则。以下是一个基于 Spring Boot 应用的多阶段构建示例:

# 第一阶段:编译构建
FROM maven:3.8.6-eclipse-temurin-17 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn clean package -DskipTests

# 第二阶段:运行镜像
FROM eclipse-temurin:17-jre-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --from=builder /app/target/*.jar app.jar
EXPOSE 8080
USER appuser
ENTRYPOINT ["java", "-jar", "app.jar"]
最佳实践要点:
  • 使用显式基础镜像标签:避免 latest,固定到 17-jre-alpine 等具体版本。
  • 分离依赖与源码:先拷贝 pom.xml 并下载依赖,利用 Docker 缓存层加速后续构建。
  • 非 root 运行:创建专用用户 appuser,降低容器逃逸风险。
  • 多阶段构建:最终镜像仅包含 JRE 和 JAR,体积从 700MB 降至约 180MB。

4.2 镜像构建与标签策略

构建命令应结合 CI 环境变量 生成唯一且可追溯的镜像标签。推荐标签格式如下:

标签类型 格式示例 适用场景
语义化版本 v1.2.3 正式发布
Git 提交 SHA sha-a1b2c3d4 开发/测试环境追溯
分支+构建序号 main-42 CI 流水线中间产物
时间戳 20250321-1530 临时调试镜像
# 典型 CI 构建命令
docker build \
  --build-arg APP_VERSION=${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA} \
  -t registry.example.com/myapp:${CI_COMMIT_SHORT_SHA} \
  -t registry.example.com/myapp:latest \
  .
避坑提醒: 生产环境禁止推送 latest 标签到私有仓库,它会导致部署时无法精确回滚。仅在开发/测试环境保留 latest 作为快捷引用。

4.3 镜像安全扫描与合规检查

在推送之前,必须对镜像进行 漏洞扫描 和 合规性检查。推荐集成 Trivy 或 Grype 等工具:

# 使用 Trivy 扫描本地镜像
trivy image --severity CRITICAL,HIGH \
  --ignore-unfixed \
  --exit-code 1 \
  registry.example.com/myapp:sha-a1b2c3d4

扫描结果应作为 CI 流水线的 质量门禁:

  • CRITICAL 漏洞:阻断构建,必须修复。
  • HIGH 漏洞:记录告警,允许人工审批通过。
  • MEDIUM/LOW:记录至报告,定期跟踪。
安全基线建议:
  • 基础镜像优先选择 distroless 或 alpine 变体,减少攻击面。
  • 定期(每周)重新构建基础镜像,拉取最新安全补丁。
  • 在 Dockerfile 中删除 apt/apk 缓存,避免遗留敏感文件。

4.4 推送至私有仓库的工程化流程

推送流程需要处理 认证、重试、签名 和 清理 四个环节。以下是一个完整的推送脚本片段:

#!/bin/bash
set -euo pipefail

REGISTRY="registry.example.com"
IMAGE_NAME="myapp"
IMAGE_TAG="${CI_COMMIT_SHORT_SHA:-dev}"

# 1. 登录私有仓库(使用 CI 预置的凭据)
echo "${REGISTRY_PASSWORD}" | docker login ${REGISTRY} \
  --username "${REGISTRY_USER}" --password-stdin

# 2. 推送镜像(带重试机制)
for i in {1..3}; do
  docker push ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} && break
  sleep 5
done

# 3. 推送语义化标签(仅当有 Git Tag 时)
if [[ -n "${CI_COMMIT_TAG:-}" ]]; then
  docker tag ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} \
    ${REGISTRY}/${IMAGE_NAME}:${CI_COMMIT_TAG}
  docker push ${REGISTRY}/${IMAGE_NAME}:${CI_COMMIT_TAG}
fi

# 4. 清理本地镜像(释放 CI 节点磁盘空间)
docker rmi ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} || true

4.5 容器化交付流程总览

下图展示了从代码提交到镜像就绪的完整容器化交付链路:

容器化交付流程 容器化交付流程 代码提交 CI 触发构建 Dockerfile 构建 安全扫描 推送私有仓库 镜像签名/加签 通知部署系统 说明: 安全扫描失败时阻断推送;镜像签名使用 Cosign 或 Notation 实现供应链安全。 推送完成后通过 Webhook 触发 ArgoCD/Flux 进行自动部署。

4.6 镜像清理与存储成本控制

私有仓库的镜像数量会随着 CI 运行快速膨胀,必须建立 生命周期管理策略:

策略 保留规则 实现方式
按版本保留 保留最近 10 个语义化版本 Harbor 保留策略 / 脚本清理
按时间保留 保留最近 30 天的构建产物 CI 定时任务 + docker registry gc
按标签保留 保留所有 v* 和 sha-* 标签 正则匹配 + 批量删除
按引用保留 保留正在运行的 Pod 引用的镜像 Kubernetes API 查询 + 差异清理
推荐工具: Harbor 内置镜像清理规则引擎,支持基于标签、时间、引用数的自动清理;对于自建 Registry,可使用 regclient 或 crane 命令行工具编写清理脚本。

4.7 本章小结

应用容器化的最佳实践可归纳为以下检查清单:

  • Dockerfile:多阶段构建、固定基础镜像版本、非 root 运行、合并 RUN 指令减少层数。
  • 构建与标签:使用 Git SHA 或语义化版本作为唯一标签,避免 latest 污染。
  • 安全扫描:集成 Trivy/Grype,设置 CRITICAL 漏洞阻断门禁。
  • 推送与签名:使用 CI 凭据登录、实现推送重试、启用 Cosign 镜像签名。
  • 清理策略:按版本/时间/引用自动清理,控制存储成本。

以上实践已在多个生产集群中验证,可显著提升镜像交付的 安全性、可追溯性 和 运维效率。下一环节将进入 CI/CD 流水线集成,将容器化产物自动部署至 Kubernetes 集群。