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

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="[email protected]" \
      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/[email protected]
      - 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添加 HEALTHCHECKHEALTHCHECK 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 知识体系。建议收藏本教程作为日常工作参考。