强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Dockerfile 写作精讲 / 18 - 生产最佳实践

18 - 生产最佳实践:CI/CD 集成、安全基线与维护策略

18.1 生产环境 Dockerfile 清单

在将 Dockerfile 投入生产之前,确保满足以下所有检查项:

基础要求

检查项 标准 验证方式
固定基础镜像版本 使用 digest 或具体版本标签 FROM python:3.12.2-slim-bookworm
非 root 用户 使用 USER 指令 USER appuser
健康检查 HEALTHCHECK 指令 HEALTHCHECK CMD ...
多阶段构建 构建与运行分离 使用 AS builder
无秘密泄露 使用 BuildKit secrets --mount=type=secret
.dockerignore 排除不必要文件 检查 .dockerignore 文件
体积合理 在预期范围内 docker image inspect
无高危漏洞 扫描通过 trivy image

Dockerfile 模板(生产级)

# syntax=docker/dockerfile:1

# ===========================================================
# 生产级 Dockerfile 模板
# ===========================================================

# ----- 全局构建参数 -----
ARG BASE_IMAGE=python
ARG BASE_TAG=3.12.2-slim-bookworm

# ===========================================================
# 阶段一:依赖安装
# ===========================================================
FROM ${BASE_IMAGE}:${BASE_TAG} AS deps

# 系统依赖(合并安装 + 清理)
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        ca-certificates \
        curl \
        libpq5 \
    && rm -rf /var/lib/apt/lists/*

# Python 依赖
WORKDIR /app
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install --no-cache-dir -r requirements.txt

# ===========================================================
# 阶段二:测试(可选,CI 中使用)
# ===========================================================
FROM deps AS tester
COPY requirements-dev.txt .
RUN pip install --no-cache-dir -r requirements-dev.txt
COPY . .
RUN pytest tests/ -v --tb=short

# ===========================================================
# 阶段三:生产镜像
# ===========================================================
FROM deps AS production

# 安全:创建非 root 用户
RUN groupadd -g 1001 appgroup && \
    useradd -u 1001 -g appgroup --no-create-home --shell /bin/bash appuser

WORKDIR /app

# 复制应用代码
COPY --chown=appuser:appuser . .

# 元数据
LABEL maintainer="devops@example.com" \
      version="${APP_VERSION}" \
      description="Production image"

# 切换用户
USER appuser

# 端口声明
EXPOSE 8000

# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
    CMD curl -f http://localhost:8000/health || exit 1

# 启动命令(使用 exec 形式确保 PID 1 正确)
CMD ["gunicorn", "app:app", \
     "--bind", "0.0.0.0:8000", \
     "--workers", "4", \
     "--timeout", "120", \
     "--access-logfile", "-"]

18.2 CI/CD 集成策略

完整的 CI/CD 流水线

代码提交
  │
  ▼
┌─────────────────┐
│  代码检查        │  Hadolint, ESLint, Black
├─────────────────┤
│  单元测试        │  pytest, jest, go test
├─────────────────┤
│  镜像构建        │  docker buildx build
├─────────────────┤
│  安全扫描        │  Trivy, Grype
├─────────────────┤
│  镜像签名        │  Cosign
├─────────────────┤
│  推送 Registry   │  docker push
├─────────────────┤
│  部署 Staging    │  kubectl apply / docker compose
├─────────────────┤
│  集成测试        │  API 测试, E2E 测试
├─────────────────┤
│  部署 Production │  渐进式发布
└─────────────────┘

GitHub Actions 完整流水线

name: Build, Test & Deploy

on:
  push:
    branches: [main]
    tags: ['v*']
  pull_request:
    branches: [main]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  # ===== 代码检查 =====
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Hadolint
        uses: hadolint/hadolint-action@v3.1.0
      - name: Python lint
        run: |
          pip install ruff
          ruff check .

  # ===== 构建与测试 =====
  build:
    runs-on: ubuntu-latest
    needs: lint
    permissions:
      contents: read
      packages: write
      id-token: write
    steps:
      - uses: actions/checkout@v4
      
      - uses: docker/setup-buildx-action@v3
      
      - uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      
      # 构建测试镜像
      - name: Build test image
        uses: docker/build-push-action@v5
        with:
          context: .
          target: tester
          load: true
          tags: ${{ env.IMAGE_NAME }}:test
      
      # 运行测试
      - name: Run tests
        run: docker run --rm ${{ env.IMAGE_NAME }}:test
      
      # 构建生产镜像
      - name: Build production image
        uses: docker/build-push-action@v5
        with:
          context: .
          target: production
          load: true
          tags: ${{ env.IMAGE_NAME }}:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          build-args: |
            APP_VERSION=${{ github.sha }}
      
      # 镜像体积检查
      - name: Check image size
        run: |
          SIZE=$(docker image inspect ${{ env.IMAGE_NAME }}:${{ github.sha }} --format='{{.Size}}')
          SIZE_MB=$((SIZE / 1024 / 1024))
          echo "Image size: ${SIZE_MB}MB"
          if [ "$SIZE_MB" -gt 200 ]; then
            echo "::error::Image size exceeds 200MB limit"
            exit 1
          fi
      
      # 安全扫描
      - name: Trivy vulnerability scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ${{ env.IMAGE_NAME }}:${{ github.sha }}
          format: 'sarif'
          output: 'trivy-results.sarif'
          severity: 'HIGH,CRITICAL'
      
      - name: Upload Trivy results
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: 'trivy-results.sarif'
      
      # 推送镜像
      - name: Push image
        if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')
        run: |
          docker tag ${{ env.IMAGE_NAME }}:${{ github.sha }} \
                     ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
          docker tag ${{ env.IMAGE_NAME }}:${{ github.sha }} \
                     ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
          docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
          docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
      
      # 签名镜像
      - name: Install Cosign
        if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')
        uses: sigstore/cosign-installer@v3
      
      - name: Sign image
        if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')
        run: |
          cosign sign --yes \
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}

18.3 安全基线

CIS Docker Benchmark 关键规则

规则 描述 Dockerfile 实现
4.1 以非 root 用户运行 USER appuser
4.2 使用可信基础镜像 使用官方镜像 + digest
4.6 添加 HEALTHCHECK HEALTHCHECK CMD ...
4.7 不安装不必要的软件 --no-install-recommends
4.9 使用 COPY 而非 ADD 仅在需要解压时使用 ADD
4.10 不在镜像中存储秘密 BuildKit secrets
4.11 安装安全更新 定期更新基础镜像

Docker Daemon 安全配置

// /etc/docker/daemon.json
{
  "features": {
    "buildkit": true
  },
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "no-new-privileges": true,
  "userns-remap": "default",
  "live-restore": true,
  "default-ulimits": {
    "nofile": {
      "Name": "nofile",
      "Hard": 65536,
      "Soft": 65536
    }
  }
}

容器运行时安全

# 以只读文件系统运行
docker run --read-only --tmpfs /tmp myapp

# 限制资源
docker run --memory=512m --cpus=1.0 myapp

# 禁用特权提升
docker run --security-opt=no-new-privileges myapp

# 只读根文件系统 + 临时写入目录
docker run --read-only \
    --tmpfs /tmp:rw,noexec,nosuid \
    --tmpfs /var/run:rw,noexec,nosuid \
    myapp

# 使用 seccomp 限制系统调用
docker run --security-opt seccomp=custom-seccomp.json myapp

# AppArmor
docker run --security-opt apparmor=docker-default myapp

18.4 镜像版本管理

标签策略

# 语义化版本
docker tag myapp:latest myapp:1.2.3
docker tag myapp:latest myapp:1.2
docker tag myapp:latest myapp:1

# Git commit SHA
docker tag myapp:latest myapp:sha-abc1234

# Git tag
docker tag myapp:latest myapp:v1.2.3

# 日期标签
docker tag myapp:latest myapp:20240115

# 环境标签
docker tag myapp:latest myapp:staging
docker tag myapp:latest myapp:production

版本矩阵

标签 更新频率 可重现性 用途
latest 每次构建 仅限本地开发
1.2.3 发布时 生产部署
sha-abc1234 每次提交 CI/CD 流水线
digest 自动 ✅✅ 安全要求极高

Dependabot / Renovate 自动更新

# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "docker"
    directory: "/"
    schedule:
      interval: "weekly"
    reviewers:
      - "devops-team"
    labels:
      - "dependencies"
      - "docker"

18.5 镜像维护策略

定期重建

# 定期重建(获取安全补丁)
# .github/workflows/rebuild.yml
name: Weekly Rebuild

on:
  schedule:
    - cron: '0 2 * * 1'  # 每周一凌晨 2 点
  workflow_dispatch:

jobs:
  rebuild:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - uses: docker/setup-buildx-action@v3
      
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      
      - uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ghcr.io/${{ github.repository }}:latest
          no-cache: true    # 强制重建,获取最新补丁
          cache-from: type=gha
          cache-to: type=gha,mode=max

漏洞监控

# GitHub Security Alerts
# .github/workflows/vuln-scan.yml
name: Vulnerability Scan

on:
  schedule:
    - cron: '0 6 * * *'  # 每天凌晨 6 点

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: aquasecurity/trivy-action@master
        with:
          image-ref: ghcr.io/${{ github.repository }}:latest
          format: 'sarif'
          output: 'trivy-results.sarif'
      
      - uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: 'trivy-results.sarif'

18.6 监控与可观测性

容器日志

# 将日志输出到 stdout/stderr(容器最佳实践)
# 不要将日志写入文件
CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:8000", "--access-logfile", "-"]

Prometheus 指标暴露

EXPOSE 8000   # 应用端口
EXPOSE 9090   # 指标端口(可选,分离端口更安全)
HEALTHCHECK --interval=30s --timeout=3s \
    CMD curl -f http://localhost:8000/health || exit 1

18.7 灾难恢复

镜像备份

# 导出镜像为 tar
docker save myapp:latest | gzip > myapp-latest.tar.gz

# 导入镜像
docker load < myapp-latest.tar.gz

# 从 Registry 重新拉取
docker pull ghcr.io/myorg/myapp:sha-abc1234

回滚策略

# 快速回滚到上一个版本
docker pull ghcr.io/myorg/myapp:sha-old123
docker compose up -d

# Kubernetes 回滚
kubectl rollout undo deployment/myapp

18.8 附录:Dockerfile 编写速查表

指令 最佳实践
FROM 使用具体版本标签或 digest,优先选择 Alpine/Distroless
RUN 合并相关操作,同一层清理缓存
COPY 优先复制依赖清单,后复制源码
ADD 仅在需要自动解压时使用
ENV 用于运行时配置,不存储秘密
ARG 用于构建参数,配合 ENV 持久化
CMD 使用 exec 形式
ENTRYPOINT 使用 exec 形式,配合 tini
EXPOSE 声明文档,不发布端口
USER 始终切换到非 root 用户
WORKDIR 使用绝对路径
HEALTHCHECK 为所有服务添加健康检查
LABEL 添加维护者、版本等元数据
VOLUME 声明数据目录
SHELL 仅在需要时修改默认 shell

18.9 扩展阅读


上一章17 - Docker Compose 集成

完成全部 18 章学习后,你已经掌握了从基础指令到生产级部署的完整 Dockerfile 知识体系。建议收藏本教程作为日常工作参考。