📘 第08课:多阶段构建

镜像构建

🏗️ 为什么需要多阶段构建?

传统构建方式中,构建工具(编译器、SDK、源码)会留在最终镜像里,导致镜像臃肿且不安全。多阶段构建让你只保留运行时需要的内容

传统构建 vs 多阶段构建: ❌ 传统方式(单阶段): ┌─────────────────────────────────┐ │ 镜像大小: 1.2GB │ │ ┌───────────────────────────┐ │ │ │ 构建工具 (GCC, Make, SDK) │ │ ← 不需要但占空间 │ │ 源代码 │ │ ← 安全风险 │ │ 中间产物 (.o文件) │ │ ← 完全不需要 │ │ 运行时二进制 │ │ ← 唯一需要的 │ │ 运行时依赖 │ │ ← 唯一需要的 │ └───────────────────────────┘ │ └─────────────────────────────────┘ ✅ 多阶段构建: ┌──────────────┐ ┌──────────────┐ │ Stage: build │ │ Stage: final │ │ 大小: 1.2GB │ │ 大小: 50MB │ │ ┌──────────┐ │ │ ┌──────────┐ │ │ │ 构建工具 │ │ │ │ 运行时 │ │ │ │ 源代码 │ │ ──► │ │ 二进制 │ │ │ │ 编译产物 │ │COPY │ │ 依赖 │ │ │ └──────────┘ │ │ └──────────┘ │ │ (丢弃) │ │ (最终镜像) │ └──────────────┘ └──────────────┘

📝 多阶段构建语法

# 阶段1:构建阶段 ✅
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# 阶段2:运行阶段 ✅
FROM node:20-alpine AS runtime
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY --from=builder /app/dist ./dist

ENV NODE_ENV=production
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]

🦀 实战:Go应用多阶段构建

# Go应用:编译型语言,最终镜像可以极小 ✅
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /myapp

# 最终阶段:scratch空镜像或distroless ✅
FROM gcr.io/distroless/static-debian12
COPY --from=builder /myapp /myapp
EXPOSE 8080
ENTRYPOINT ["/myapp"]

# 构建并查看大小 ✅
docker build -t my-go-app .
docker images my-go-app
REPOSITORY   TAG    IMAGE ID       SIZE
my-go-app    latest a1b2c3d4e5f6   2.5MB     ← 极致精简!

🐍 实战:Python应用多阶段构建

# Python多阶段:编译依赖 + 运行时 ✅
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt

FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY . .
ENV PATH=/root/.local/bin:$PATH
ENV PYTHONUNBUFFERED=1
EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:create_app()"]

📦 实战:React前端多阶段构建

# React/Vue前端:构建 + Nginx托管 ✅
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

🔗 多阶段高级技巧

# 指定构建目标阶段 ✅
docker build --target runtime -t my-app .

# 三个阶段:测试 → 构建 → 运行
FROM node:20-alpine AS test
WORKDIR /app
COPY . .
RUN npm ci && npm test

FROM node:20-alpine AS build
WORKDIR /app
COPY . .
RUN npm ci && npm run build

FROM node:20-alpine AS runtime
COPY --from=build /app/dist ./dist
CMD ["node", "dist/server.js"]

# 从外部镜像复制 ✅
COPY --from=nginx:alpine /etc/nginx/nginx.conf /etc/nginx/

# 使用构建参数选择阶段
ARG TARGET=runtime
FROM node:20-alpine AS ${TARGET}

❓ 常见问题

❓ 多阶段构建会影响构建速度吗?不会!未使用的阶段不会执行。Docker只会构建最终阶段及其依赖的阶段。使用--target可以指定构建到某个阶段。
❓ scratch镜像没有shell,怎么调试?调试时用alpine作为最终基础镜像(有sh),生产再用scratch或distroless。也可以docker cp提取文件到宿主机查看。
❓ 多阶段构建的缓存策略?每个阶段有独立的缓存。修改源码只影响build阶段的后续层,runtime阶段如果COPY --from的内容没变则可以使用缓存。

🏆 本课成就

📚 深度补充:多阶段构建进阶要点

【镜像进阶阶段】生产环境注意事项

要点说明最佳实践
资源规划根据业务负载合理分配CPU/内存先压测再上线,设置requests和limits
监控告警设置关键指标阈值和告警规则Prometheus + AlertManager,5分钟P99延迟告警
备份策略定期备份关键数据和配置自动化备份脚本 + 异地存储 + 定期恢复演练
灰度发布新版本逐步放量降低风险金丝雀发布5%→20%→50%→100%
回滚预案部署前确认回滚方案和步骤保留前一版本镜像,数据库迁移向前兼容
文档更新配置变更必须同步更新文档GitOps管理配置,变更即文档
安全基线遵循CIS Docker Benchmark非root运行、只读FS、最小能力
日志规范结构化日志,统一格式JSON格式日志,包含traceId

常见误区与避坑指南

  1. 过度配置:不是所有服务都需要高可用,根据实际需求选择架构复杂度,避免过度工程
  2. 忽略日志:日志是排障的关键,确保日志格式统一、级别合理、采集完整
  3. 资源超卖:容器资源限制不是摆设,超卖会导致性能下降甚至OOM Kill
  4. 安全忽视:默认配置不等于安全配置,生产环境必须加固(非root、只读FS、最小能力)
  5. 监控缺失:没有监控等于盲飞,至少要有基础的健康检查和资源监控
  6. 手动运维:能自动化的绝不手动,手动操作容易出错且不可追溯
  7. 忽略网络策略:默认所有容器互通不安全,应按最小权限原则配置网络

进阶阅读与参考

Docker命令速查卡

# 容器生命周期
docker create/start/stop/restart/rm/pause/unpause
docker logs/top/stats/inspect/exec diff

# 镜像构建
docker build/pull/push/tag/rmi/images/history
docker save/load/import/manifest

# 网络与存储
docker network create/ls/inspect/connect/disconnect/rm/prune
docker volume create/ls/inspect/rm/prune

# Docker Compose
docker compose up/down/ps/logs/build/exec
docker compose config/stop/start/scale/top/cp

# 系统维护
docker system df/prune/info
docker builder prune
docker container/prune/image prune

# K8s常用命令
kubectl get/describe/logs/exec/apply/delete
kubectl rollout status/undo/history
kubectl scale/autoscale/set

本阶段知识脉络

镜像进阶阶段知识体系: 核心概念实操演练最佳实践故障排查 每一课都遵循:概念讲解 → 命令实操 → 代码示例 → 常见问题 → 练习巩固 确保学完即能上手,理论实践并重。

镜像层与存储驱动

overlay2 存储驱动 上层(可写) ← 容器运行时修改 ┌─────────────────────┐ │ Container Layer │ ← docker diff 可查看变更 ├─────────────────────┤ │ Image Layer N │ ← COPY app.py ├─────────────────────┤ │ Image Layer N-1 │ ← RUN pip install ├─────────────────────┤ │ ... │ ├─────────────────────┤ │ Image Layer 1 │ ← RUN apt-get install ├─────────────────────┤ │ Base Layer │ ← FROM ubuntu:22.04 └─────────────────────┘ 关键特性: • 每层只存储与下层的差异(CoW - Copy on Write) • 只读层可被多个容器共享 • 删除文件不会减小镜像大小(只是标记删除) • 多阶段构建只保留最终阶段的层

OCI标准与镜像规范

标准内容作用
OCI Image Spec镜像格式规范镜像可跨运行时使用
OCI Runtime Spec容器运行时规范runc/crun/kata等兼容
OCI Distribution Spec镜像分发规范Registry API标准化

实战案例:故障排查流程

# Step 1: 检查容器状态
docker ps -a                          # 查看所有容器
docker inspect <container>            # 查看详细配置

# Step 2: 查看日志
docker logs --tail 100 <container>    # 最近100行日志
docker logs --since 1h <container>    # 最近1小时

# Step 3: 进入容器排查
docker exec -it <container> sh        # 进入容器shell

# Step 4: 检查资源
docker stats --no-stream              # 资源使用概览
docker system df                      # 磁盘使用

# Step 5: 网络排查
docker network ls                     # 网络列表
docker exec <c> ping <target>        # 网络连通性
docker exec <c> nslookup <svc>       # DNS解析

# Step 6: 检查健康状态
docker inspect --format '{{.State.Health}}' <c>
docker inspect --format '{{.State.ExitCode}}' <c>
docker inspect --format '{{.State.OOMKilled}}' <c>

Dockerfile模板:多语言通用

# 通用最佳实践模板
FROM alpine:3.19 AS builder
# ... 构建步骤 ...

FROM alpine:3.19
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --from=builder /app/output .
USER appuser
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD wget -qO- http://localhost:8080/health || exit 1
EXPOSE 8080
CMD ["./app"]

Docker Compose健康检查模板

services:
  app:
    build: .
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:8080/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 30s
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 512M