强曰为道

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

第 15 章 · 最佳实践:项目结构、生产规范与 CI/CD 集成

第 15 章 · 最佳实践

15.1 项目结构规范

推荐目录结构

myproject/
├── compose.yaml                # 基础配置
├── compose.override.yaml       # 开发覆盖(自动加载)
├── compose.prod.yaml           # 生产覆盖
├── compose.test.yaml           # 测试覆盖
├── compose.monitoring.yaml     # 监控组件
├── .env                        # 默认环境变量
├── .env.example                # 变量模板(提交到 Git)
├── .env.production             # 生产变量(不提交)
├── .gitignore
├── Makefile                    # 常用命令封装
├── README.md
│
├── app/                        # 应用代码
│   ├── Dockerfile
│   ├── Dockerfile.prod         # 生产专用 Dockerfile
│   ├── .dockerignore
│   ├── src/
│   └── tests/
│
├── nginx/                      # Nginx 配置
│   ├── nginx.conf
│   └── conf.d/
│
├── monitoring/                 # 监控配置
│   ├── prometheus.yml
│   ├── alertmanager.yml
│   ├── grafana/
│   │   └── provisioning/
│   └── rules/
│
├── secrets/                    # 密钥文件(不提交)
│   ├── .gitkeep
│   ├── db_password.txt
│   └── tls/
│
├── scripts/                    # 辅助脚本
│   ├── backup.sh
│   ├── deploy.sh
│   └── health-check.sh
│
└── docs/                       # 文档
    ├── architecture.md
    └── runbook.md

.gitignore

# 环境变量(敏感)
.env
.env.*
!.env.example

# 密钥
secrets/

# Docker 构建产物
.docker/

# 数据卷挂载目录(如果在项目内)
data/
logs/

# IDE
.vscode/
.idea/

# 系统文件
.DS_Store
Thumbs.db

Makefile 封装

# Makefile
.PHONY: dev up down build test prod logs clean backup

# ===== 开发环境 =====
dev:
	docker compose up

dev-d:
	docker compose up -d

down:
	docker compose down

# ===== 构建 =====
build:
	docker compose build

build-nc:
	docker compose build --no-cache

# ===== 生产部署 =====
prod:
	docker compose -f compose.yaml -f compose.prod.yaml up -d --build

prod-down:
	docker compose -f compose.yaml -f compose.prod.yaml down

# ===== 测试 =====
test:
	docker compose -f compose.yaml -f compose.test.yaml up \
		--abort-on-container-exit --exit-code-from test
	docker compose -f compose.yaml -f compose.test.yaml down -v

# ===== 日志 =====
logs:
	docker compose logs -f

logs-web:
	docker compose logs -f web

# ===== 清理 =====
clean:
	docker compose down -v --rmi local --remove-orphans

clean-all:
	docker compose down -v --rmi all --remove-orphans
	docker system prune -f

# ===== 备份 =====
backup-db:
	docker compose exec db pg_dump -U postgres mydb > \
		backups/db_$$(date +%Y%m%d_%H%M%S).sql

# ===== 数据库 =====
db-shell:
	docker compose exec db psql -U postgres mydb

db-migrate:
	docker compose run --rm app python manage.py migrate

# ===== 健康检查 =====
health:
	@echo "=== Service Health Status ==="
	@docker compose ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}"

# ===== 帮助 =====
help:
	@echo "Available commands:"
	@echo "  make dev        - Start development environment"
	@echo "  make dev-d      - Start development environment (detached)"
	@echo "  make down       - Stop all services"
	@echo "  make build      - Build images"
	@echo "  make prod       - Deploy production"
	@echo "  make test       - Run tests"
	@echo "  make logs       - View logs"
	@echo "  make clean      - Clean up everything"
	@echo "  make health     - Check service health"

15.2 compose.yaml 编写规范

规范一:文件命名

# ✅ 推荐:使用 compose.yaml(官方推荐)
# ✅ 可接受:docker-compose.yml
# ❌ 不推荐:docker-compose.yaml(较长)
# ❌ 不推荐:compose.yml(较少见但合法)

规范二:服务排序

# 按照依赖关系从底到上排列
services:
  # 1. 反向代理(最上层)
  nginx:

  # 2. 应用服务
  web:
  api:

  # 3. 后台任务
  worker:
  scheduler:

  # 4. 数据存储
  db:
  redis:

  # 5. 辅助工具
  adminer:

规范三:镜像标签

services:
  # ❌ 不要用 latest
  web:
    image: myapp:latest

  # ✅ 使用精确版本
  web:
    image: myapp:1.2.3

  # ✅ 使用 digest(最安全)
  web:
    image: myapp:1.2.3@sha256:abc123...

  # ✅ 官方镜像使用精确版本
  db:
    image: postgres:16.2-alpine

规范四:健康检查

# 所有长期运行的服务都应配置健康检查
services:
  web:
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  db:
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

规范五:重启策略

# 生产环境所有服务设置 restart
services:
  web:
    restart: unless-stopped
  db:
    restart: unless-stopped
  redis:
    restart: unless-stopped

  # 一次性任务不重启
  migration:
    restart: "no"

规范六:资源限制

services:
  web:
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 512M
        reservations:
          memory: 256M

规范七:日志配置

services:
  web:
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "5"

规范八:网络安全

services:
  db:
    # ❌ 不要暴露数据库端口到公网
    ports:
      - "0.0.0.0:5432:5432"

    # ✅ 仅本地访问
    ports:
      - "127.0.0.1:5432:5432"

    # ✅ 最佳:完全不暴露,仅通过 Docker 网络访问
    # 不配置 ports

15.3 Dockerfile 最佳实践

通用规范

# 1. 使用精确的基础镜像版本
FROM python:3.12.3-slim-bookworm

# 2. 设置元数据
LABEL maintainer="[email protected]"
LABEL version="1.0.0"
LABEL description="My Application"

# 3. 设置工作目录
WORKDIR /app

# 4. 系统依赖安装(合并 RUN 减少层数)
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        libpq-dev \
        curl \
    && rm -rf /var/lib/apt/lists/*

# 5. 先复制依赖文件(利用缓存层)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 6. 再复制应用代码
COPY src/ ./src/

# 7. 创建非 root 用户
RUN groupadd -r appuser && useradd -r -g appuser appuser
RUN chown -R appuser:appuser /app
USER appuser

# 8. 暴露端口(文档性质)
EXPOSE 8000

# 9. 使用 exec 格式的 CMD
CMD ["gunicorn", "src.main:app"]

多阶段构建

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

# 阶段二:运行
FROM node:20-alpine AS production
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
COPY package.json .
USER node
EXPOSE 3000
CMD ["node", "dist/main.js"]

安全规范

# 不要安装不需要的工具
# ❌ apt-get install vim wget telnet

# 使用非 root 用户
USER 1000:1000

# 不要在镜像中存储密钥
# ❌ ENV DB_PASSWORD=secret

# 使用 Docker Secrets
# RUN --mount=type=secret,id=db_pass cat /run/secrets/db_pass

15.4 安全最佳实践

镜像安全

实践说明
使用官方镜像比第三方镜像更可信、更安全
使用精简镜像alpineslimdistrolessfull 更小更安全
固定版本标签避免 latest,使用精确版本或 digest
定期更新修补已知漏洞
扫描镜像使用 docker scout 或 Trivy 扫描漏洞
# 使用 Docker Scout 扫描漏洞
docker scout cves myapp:latest

# 使用 Trivy
trivy image myapp:latest

运行时安全

services:
  app:
    # 只读文件系统
    read_only: true
    tmpfs:
      - /tmp
      - /var/run

    # 禁用特权
    privileged: false

    # 能力限制
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE    # 仅在需要时添加

    # 安全选项
    security_opt:
      - no-new-privileges:true

网络安全

services:
  db:
    # 不暴露端口
    networks:
      - backend

  nginx:
    # 仅 nginx 暴露到外部
    ports:
      - "80:80"
      - "443:443"
    networks:
      - frontend
      - backend

networks:
  frontend:
  backend:
    internal: true    # 内部网络,不允许外部访问

15.5 CI/CD 集成

GitHub Actions

# .github/workflows/ci.yml
name: CI/CD

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run tests with Compose
        run: |
          docker compose -f compose.yaml -f compose.test.yaml up \
            --build --abort-on-container-exit --exit-code-from test

      - name: Cleanup
        if: always
        run: |
          docker compose -f compose.yaml -f compose.test.yaml down -v

  build:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: ./app
          push: true
          tags: |
            ghcr.io/${{ github.repository }}/app:latest
            ghcr.io/${{ github.repository }}/app:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  deploy:
    needs: build
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: production
    steps:
      - name: Deploy to server
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.DEPLOY_HOST }}
          username: ${{ secrets.DEPLOY_USER }}
          key: ${{ secrets.DEPLOY_KEY }}
          script: |
            cd /opt/myapp
            docker compose -f compose.yaml -f compose.prod.yaml pull
            docker compose -f compose.yaml -f compose.prod.yaml up -d
            docker compose -f compose.yaml -f compose.prod.yaml exec -T app python manage.py migrate

GitLab CI

# .gitlab-ci.yml
stages:
  - test
  - build
  - deploy

variables:
  DOCKER_TLS_CERTDIR: "/certs"

test:
  stage: test
  services:
    - docker:dind
  script:
    - docker compose -f compose.yaml -f compose.test.yaml up --build --abort-on-container-exit --exit-code-from test
  after_script:
    - docker compose -f compose.yaml -f compose.test.yaml down -v

build:
  stage: build
  only:
    - main
  script:
    - docker build -t $CI_REGISTRY_IMAGE/app:$CI_COMMIT_SHA ./app
    - docker push $CI_REGISTRY_IMAGE/app:$CI_COMMIT_SHA

deploy:
  stage: deploy
  only:
    - main
  environment: production
  script:
    - ssh deploy@server "cd /opt/myapp && docker compose pull && docker compose up -d"

部署脚本

#!/bin/bash
# scripts/deploy.sh
set -euo pipefail

PROJECT="myapp"
COMPOSE_FILES="-f compose.yaml -f compose.prod.yaml"
ENV_FILE="--env-file .env.production"

echo "=== Deploying $PROJECT ==="

# 1. 备份
echo "→ Backing up database..."
docker compose $COMPOSE_FILES exec -T db pg_dump -U postgres mydb > \
    "backups/pre_deploy_$(date +%Y%m%d_%H%M%S).sql" || true

# 2. 拉取新镜像
echo "→ Pulling images..."
docker compose $COMPOSE_FILES $ENV_FILE pull

# 3. 执行迁移
echo "→ Running migrations..."
docker compose $COMPOSE_FILES $ENV_FILE run --rm app python manage.py migrate

# 4. 滚动更新
echo "→ Starting services..."
docker compose $COMPOSE_FILES $ENV_FILE up -d --remove-orphans

# 5. 健康检查
echo "→ Waiting for services to be healthy..."
sleep 10
docker compose $COMPOSE_FILES ps

# 6. 清理旧镜像
echo "→ Cleaning up..."
docker image prune -f

echo "=== Deploy complete ==="

15.6 性能优化

镜像优化

技巧效果
使用 Alpine 基础镜像体积减少 50-80%
多阶段构建构建工具不进入运行镜像
合并 RUN 指令减少镜像层数
清理缓存rm -rf /var/lib/apt/lists/*
.dockerignore减小构建上下文
BuildKit 缓存加速增量构建

Compose 性能

# 并行构建
docker compose build --parallel

# 并行拉取
docker compose pull --parallel

# 只重建变化的服务
docker compose up -d --build web   # 只重建 web

# 使用 BuildKit(更快)
DOCKER_BUILDKIT=1 docker compose build

容器运行时性能

services:
  app:
    # 网络性能(仅 Linux)
    network_mode: host    # 去掉 NAT 层,提升网络性能

    # 存储性能
    volumes:
      - type: volume        # 命名卷比 bind mount 性能好
        source: app-cache
        target: /app/cache

15.7 备份与恢复策略

自动备份脚本

#!/bin/bash
# scripts/backup.sh
set -euo pipefail

BACKUP_DIR="./backups/$(date +%Y%m%d_%H%M%S)"
mkdir -p "$BACKUP_DIR"

echo "=== Starting backup ==="

# 数据库备份
echo "→ Backing up PostgreSQL..."
docker compose exec -T db pg_dump -U postgres -Fc mydb > "$BACKUP_DIR/db.dump"

# 卷备份
echo "→ Backing up volumes..."
for VOL in $(docker volume ls --filter "label=com.docker.compose.project=$(basename $(pwd))" -q); do
    echo "   → $VOL"
    docker run --rm -v "$VOL":/source:ro -v "$(pwd)/$BACKUP_DIR":/backup \
        alpine tar czf "/backup/${VOL}.tar.gz" -C /source .
done

# 配置备份
echo "→ Backing up configuration..."
cp compose.yaml "$BACKUP_DIR/"
cp .env.production "$BACKUP_DIR/" 2>/dev/null || true

# 压缩
echo "→ Compressing..."
tar czf "backups/backup_$(date +%Y%m%d_%H%M%S).tar.gz" -C "$BACKUP_DIR" .

# 清理旧备份(保留最近 7 天)
find backups/ -name "backup_*.tar.gz" -mtime +7 -delete

echo "=== Backup complete: $BACKUP_DIR ==="

定期备份(cron)

# crontab -e
# 每天凌晨 3 点备份
0 3 * * * cd /opt/myapp && bash scripts/backup.sh >> logs/backup.log 2>&1

15.8 团队协作规范

文档要求

文档内容
README.md项目简介、快速开始、常用命令
.env.example所有环境变量的模板和说明
docs/architecture.md架构设计、服务关系图
docs/runbook.md运维手册、常见问题处理

代码审查清单

## Docker Compose 审查清单

### 镜像
- [ ] 使用精确版本标签,非 `latest`
- [ ] 基础镜像来自可信来源
- [ ] 已扫描已知漏洞

### 安全
- [ ] 敏感信息使用 Secrets,非 environment
- [ ] 数据库不暴露公共端口
- [ ] 非 root 用户运行
- [ ] 最小权限原则

### 可靠性
- [ ] 配置了健康检查
- [ ] 配置了重启策略
- [ ] 配置了资源限制
- [ ] 依赖关系正确使用 `depends_on.condition`

### 可维护性
- [ ] 代码有 `.dockerignore`
- [ ] 日志配置了轮转
- [ ] 数据使用命名卷
- [ ] Makefile 包含常用命令

### CI/CD
- [ ] 测试使用独立的 compose 文件
- [ ] 构建使用多阶段构建
- [ ] 部署脚本包含回滚机制

15.9 从 Compose 迁移到 Kubernetes

迁移时机

信号说明
需要多节点部署单机无法满足容量需求
需要自动扩缩容流量波动大,需要弹性
需要蓝绿/金丝雀部署发布策略更复杂
团队规模增长需要更完善的 RBAC 和命名空间隔离

迁移工具

# Kompose — 将 Compose 文件转换为 K8s 资源
kompose convert -f compose.yaml

# 生成的文件:
# web-deployment.yaml
# web-service.yaml
# db-deployment.yaml
# db-persistentvolumeclaim.yaml

Compose vs K8s 概念映射

ComposeKubernetes
services.webDeployment + Service
imagecontainer image
portsService + Ingress
volumesPersistentVolumeClaim
environmentConfigMap / Secret
depends_onInit Container
healthchecklivenessProbe / readinessProbe
restartrestartPolicy
networksNetworkPolicy
deploy.replicasreplicas
deploy.resourcesresources (requests/limits)

15.10 总结回顾

全书知识图谱

第 1 章  概念 ─── 什么是 Compose,为什么用
    │
第 2 章  安装 ─── 怎么装,V2 vs V1
    │
第 3 章  基础 ─── services / image / ports / volumes
    │
    ├── 第 4 章  网络 ─── 默认网络 / 自定义 / 外部
    ├── 第 5 章  卷   ─── 命名卷 / 绑定 / tmpfs
    ├── 第 6 章  环境 ─── .env / environment / env_file
    │
第 7 章  依赖 ─── depends_on / healthcheck / restart
第 8 章  构建 ─── Dockerfile / 多阶段 / 缓存
    │
    ├── 第 9 章  多环境 ─── override / profiles / include
    ├── 第 10 章 安全   ─── secrets / configs
    │
第 11 章 Swarm ─── deploy / replicas / 滚动更新
第 12 章 日志 ─── logging driver / Loki
第 13 章 监控 ─── cAdvisor / Prometheus / Grafana
    │
第 14 章 排障 ─── 常见问题 / 调试技巧
第 15 章 实践 ─── 项目规范 / CI/CD / 生产化

关键经验总结

#经验
1永远不用 latest 标签 — 版本不可控
2所有服务都要健康检查 — 否则 depends_on 毫无意义
3日志必须配轮转 — 否则磁盘会满
4敏感信息用 Secrets — 永远不要硬编码密码
5数据库不要暴露端口 — 通过 Docker 网络内部访问
6使用多阶段构建 — 镜像体积减少 80%+
7.env.example 提交到 Git — 方便团队了解配置项
8使用 Makefile — 封装常用命令,降低团队门槛
9开发用绑定挂载,数据用命名卷 — 各取所长
10先 Compose,需要时再 K8s — 不要过度工程化

扩展阅读

官方资源

社区资源

相关工具


结语

Docker Compose 看似简单,但用好它需要理解网络、存储、安全、监控等多个维度的知识。希望这 15 章的系统讲解能帮助你在实际项目中游刃有余。

记住:最好的基础设施是看不见的基础设施。

祝你构建顺利!🚀


上一章:第 14 章 · 故障排查 ← | 回到 目录