2. Docker 基础回顾:核心概念、Dockerfile 与 Compose

在进入数据库迁移与异步任务编排之前,必须夯实 Docker 基础。本章将围绕三大支柱展开:镜像与容器与仓库的核心关系、Dockerfile 的编写规范、以及 docker-compose 的多服务编排基础。掌握这些内容后,后续章节的容器化数据库迁移与任务编排才能顺利落地。

2.1 Docker 核心概念:镜像、容器、仓库

Docker 的三大核心概念构成了容器化世界的基石。理解它们之间的层级关系与生命周期,是后续所有操作的前提。

核心关系速记: 镜像(只读模板)→ 容器(运行实例)→ 仓库(分发中心)。镜像不可变,容器可变,仓库负责版本管理与共享。

2.1.1 概念对比

概念 本质 生命周期 常见操作
镜像 (Image) 只读文件系统 + 元数据 持久存在,可删除 docker pull, docker build, docker rmi
容器 (Container) 镜像的运行实例 + 可写层 创建 → 运行 → 停止 → 删除 docker run, docker start/stop, docker rm
仓库 (Registry) 镜像的存储与分发服务 长期运行,需维护 docker push, docker pull, docker tag

2.1.2 镜像分层与容器可写层

Docker 镜像采用 UnionFS(联合文件系统) 分层构建。每一层都是只读的,容器启动时在最上层叠加一个可写层。这种设计使得镜像复用率高、存储空间节省,且容器间互不影响。

镜像分层与容器可写层结构 可写层 (容器层) 镜像层 3 (应用代码) 镜像层 2 (依赖库) 镜像层 1 (基础OS) 容器 镜像 docker run 容器实例 A 进程 PID 1234 网络命名空间 挂载卷 /data 💡 关键理解: 镜像层只读且共享,容器可写层保存运行时变更。删除容器后,可写层数据丢失(除非使用卷挂载)。

2.2 Dockerfile 编写规范

Dockerfile 是构建镜像的“配方”。一个规范的 Dockerfile 不仅影响构建速度,还直接关系到镜像安全性与运行稳定性。以下是生产环境必须遵守的核心规范。

2.2.1 指令顺序与分层缓存

Docker 构建时会逐条执行指令并缓存中间层。为了最大化缓存命中率,应将变化频率低的指令放在前面,变化频繁的指令放在后面。

推荐顺序 指令 说明
1 FROM 指定基础镜像,必须为第一条指令
2 LABEL / ENV 元数据与环境变量,几乎不变
3 RUN (系统依赖) 安装系统包,如 apt-get、yum
4 WORKDIR 设置工作目录
5 COPY (依赖文件) 先复制 package.json、requirements.txt 等
6 RUN (依赖安装) 安装项目依赖,如 npm install、pip install
7 COPY (应用代码) 最后复制频繁变化的源代码
8 EXPOSE / CMD 暴露端口与启动命令

2.2.2 最佳实践示例

以下是一个 Python 应用的 Dockerfile 示例,遵循了分层缓存、最小化层数、安全运行等原则。

# 1. 选择轻量基础镜像
FROM python:3.11-slim AS builder

# 2. 设置元数据
LABEL maintainer="devops@example.com" \
      version="1.0.0"

# 3. 设置环境变量(避免 Python 生成 .pyc 文件)
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

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

# 5. 先复制依赖文件(利用缓存)
COPY requirements.txt .

# 6. 安装依赖
RUN pip install --no-cache-dir -r requirements.txt

# 7. 复制应用代码(最后复制,变化最频繁)
COPY . .

# 8. 使用非 root 用户运行(安全最佳实践)
RUN addgroup --system appgroup && \
    adduser --system --ingroup appgroup appuser
USER appuser

# 9. 暴露端口
EXPOSE 8000

# 10. 启动命令
CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:8000"]
💡 避坑提示: 不要使用 ADD 指令代替 COPY,除非需要自动解压 tar 包或远程 URL 下载。ADD 行为不透明,容易导致构建缓存失效。

2.3 docker-compose 基础

docker-compose 用于定义和运行多容器应用。在数据库迁移场景中,通常需要同时启动数据库服务、迁移工具、应用服务等多个容器,Compose 正是解决这一问题的标准工具。

2.3.1 docker-compose.yml 核心结构

一个典型的 Compose 文件包含三个顶级字段:services、networks、volumes。以下是一个包含 PostgreSQL 数据库与迁移服务的示例。

version: '3.8'

services:
  # 数据库服务
  postgres:
    image: postgres:15-alpine
    container_name: migration-db
    environment:
      POSTGRES_USER: migrator
      POSTGRES_PASSWORD: secure_pass_123
      POSTGRES_DB: app_database
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
    networks:
      - app-net
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U migrator -d app_database"]
      interval: 10s
      timeout: 5s
      retries: 5

  # 迁移服务(一次性任务)
  migration:
    build:
      context: ./migration
      dockerfile: Dockerfile.migrate
    container_name: db-migrator
    depends_on:
      postgres:
        condition: service_healthy
    environment:
      DB_HOST: postgres
      DB_PORT: 5432
      DB_USER: migrator
      DB_PASSWORD: secure_pass_123
      DB_NAME: app_database
    networks:
      - app-net
    # 执行完迁移后退出
    command: ["alembic", "upgrade", "head"]

volumes:
  pgdata:
    driver: local

networks:
  app-net:
    driver: bridge

2.3.2 常用 Compose 命令

命令 用途 示例
docker compose up -d 后台启动所有服务 启动数据库 + 应用
docker compose down 停止并移除所有容器、网络 清理环境
docker compose logs -f 实时查看所有服务日志 调试迁移失败原因
docker compose exec <service> bash 进入运行中的容器 手动检查数据库状态
docker compose run --rm <service> <cmd> 运行一次性任务 执行迁移脚本

2.3.3 服务依赖与健康检查

在数据库迁移场景中,服务启动顺序至关重要。仅靠 depends_on 只能保证容器启动顺序,无法保证数据库已就绪。必须配合 healthcheck 实现真正的“等待就绪”。

⚠️ 常见陷阱: 不要使用 depends_on 不加 condition: service_healthy。否则迁移容器可能在数据库尚未完成初始化时就开始执行,导致连接失败。
docker-compose 多服务编排与依赖关系 PostgreSQL 端口 5432 healthcheck: pg_isready 迁移服务 alembic upgrade 一次性任务,执行后退出 应用服务 端口 8000 depends_on: postgres depends_on (healthy) depends_on 💡 迁移服务依赖数据库健康检查通过后才启动,应用服务依赖数据库启动(不等待健康检查)。

2.4 本章小结

  • 镜像是只读模板,容器是运行实例,仓库负责分发。三者构成 Docker 的基石。
  • Dockerfile 编写应遵循“低频变化指令在前”原则,利用分层缓存加速构建,并始终使用非 root 用户运行容器。
  • docker-compose 通过声明式 YAML 文件管理多服务生命周期,在数据库迁移场景中必须配合 healthcheck 实现可靠的启动顺序控制。