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(联合文件系统) 分层构建。每一层都是只读的,容器启动时在最上层叠加一个可写层。这种设计使得镜像复用率高、存储空间节省,且容器间互不影响。
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。否则迁移容器可能在数据库尚未完成初始化时就开始执行,导致连接失败。
2.4 本章小结
- 镜像是只读模板,容器是运行实例,仓库负责分发。三者构成 Docker 的基石。
- Dockerfile 编写应遵循“低频变化指令在前”原则,利用分层缓存加速构建,并始终使用非 root 用户运行容器。
- docker-compose 通过声明式 YAML 文件管理多服务生命周期,在数据库迁移场景中必须配合
healthcheck实现可靠的启动顺序控制。