Docker 系列 | 08 - 多容器应用编排:Docker Compose 入门与实践

引言

在之前的文章中,我们学习了如何单独地运行和管理 Docker 容器。然而,在实际应用中,一个完整的服务往往由多个相互关联的容器组成,例如一个 Web 应用可能需要一个应用容器、一个数据库容器和一个缓存容器。手动管理这些容器的启动顺序、网络连接和卷挂载会非常繁琐且容易出错。

这时,Docker Compose 就派上用场了。Docker Compose 是一个用于定义和运行多容器 Docker 应用程序的工具。通过一个 YAML 文件来配置应用程序的服务,然后使用一个命令,就可以从这个配置中创建并启动所有服务。

本篇文章将带您入门 Docker Compose,学习如何编写 docker-compose.yml 文件,并实际部署一个多容器应用。

什么是 Docker Compose?

Docker Compose 是 Docker 官方提供的开源工具,它简化了多容器应用的定义和管理。其核心思想是:

  1. **定义 (Define)**:使用一个名为 docker-compose.yml 的 YAML 文件来定义应用程序的服务、网络和卷。
  2. **运行 (Run)**:通过一个简单的命令 (docker compose up),就可以启动、停止、重建和管理整个应用栈。

Docker Compose 的优势:

  • 简化配置:将所有容器的配置集中在一个文件,清晰明了。
  • 自动化部署:一个命令即可启动整个应用,无需手动逐个启动容器。
  • 服务发现:Compose 默认会为您的服务创建一个自定义网络,并启用内置 DNS,允许服务之间通过名称相互通信。
  • 环境一致性:Compose 文件可以在不同的开发、测试、生产环境中共享,确保环境的一致性。

安装 Docker Compose

Docker Desktop (适用于 Windows/macOS) 通常已经内置了 Docker Compose。
对于 Linux 用户,您可能需要单独安装:

**Linux 安装示例 (Compose V2 - 推荐)**:

1
2
3
4
5
6
7
8
9
10
# 下载最新版本的 Compose V2
# 可以在 GitHub Release 页面查找最新版本:[https://github.com/docker/compose/releases](https://github.com/docker/compose/releases)
# 替换 x.x.x 为最新版本号
sudo curl -L [https://github.com/docker/compose/releases/download/v2.24.5/docker-compose-linux-x86_64](https://github.com/docker/compose/releases/download/v2.24.5/docker-compose-linux-x86_64) -o /usr/local/bin/docker-compose

# 赋予执行权限
sudo chmod +x /usr/local/bin/docker-compose

# 验证安装
docker compose version

注意: 从 Docker Compose V2 开始,命令是 docker compose (无连字符),而不是旧版 V1 的 docker-compose。本文将以 V2 语法为例。

docker-compose.yml 文件结构

docker-compose.yml 是 Docker Compose 的核心配置文件,它使用 YAML 语法。以下是一个典型的 docker-compose.yml 文件的结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
version: '3.8' # Compose 文件格式版本,推荐使用最新稳定版

services: # 定义应用中的各个服务
web: # 服务名称,在自定义网络中可作为 hostname 访问
build: . # 指定 Dockerfile 路径,或 image: 指定镜像名称
ports:
- "80:80" # 端口映射,主机端口:容器端口
volumes:
- ./app:/usr/src/app # 卷挂载,主机路径:容器路径
environment:
- FLASK_ENV=development # 环境变量
depends_on:
- db # 声明服务依赖关系,db 服务会先启动
networks:
- my_network # 指定服务连接的网络
healthcheck: # 健康检查,用于判断服务是否就绪
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 30s
timeout: 10s
retries: 3

db: # 另一个服务,数据库服务
image: postgres:13 # 直接使用 Docker Hub 镜像
environment:
POSTGRES_DB: mydatabase
POSTGRES_USER: user
POSTGRES_PASSWORD: password
volumes:
- db_data:/var/lib/postgresql/data # 使用命名卷持久化数据
networks:
- my_network

volumes: # 定义命名卷,供服务使用
db_data: # 命名卷名称

networks: # 定义自定义网络,供服务使用
my_network: # 自定义网络名称
driver: bridge # 网络驱动,默认为 bridge

常用字段解释:

  • **version**:Compose 文件格式的版本号,如 3.8。不同版本支持的语法和功能略有不同。

  • services:定义应用中的各个服务。每个服务会创建一个或多个容器实例。

    • 服务名称:如 web, db。这些名称在 Compose 创建的自定义网络中可作为 hostname 进行服务发现。
    • **build**:指定 Dockerfile 所在目录(或包含 Dockerfile 的构建上下文)。如果指定了,Compose 会先构建镜像。
    • **image**:指定要使用的 Docker 镜像名称。如果 buildimage 都指定,build 会优先。
    • **ports**:端口映射,格式为 宿主机端口:容器端口
    • **volumes**:卷挂载,格式为 宿主机路径:容器路径 (绑定挂载) 或 命名卷名称:容器路径 (命名卷)。
    • **environment**:定义环境变量,会传递到容器内部。
    • depends_on:声明服务之间的依赖关系。例如 web 依赖 db,则 db 会先于 web 启动。注意: depends_on 仅保证启动顺序,不保证服务就绪(如数据库连接已建立)。对于就绪检查,应使用 healthcheck
    • **networks**:指定服务连接到哪个网络。如果未指定,Compose 会创建一个默认的桥接网络并将所有服务连接到它。
    • **healthcheck**:定义容器的健康检查。当健康检查通过后,Compose 才会认为服务是“就绪”的。
  • **volumes**:顶级字段,用于定义命名卷。这些卷可以被多个服务共享。

  • **networks**:顶级字段,用于定义自定义网络。这些网络可以被多个服务共享,实现服务发现。

Docker Compose 常用命令

在包含 docker-compose.yml 文件的目录中执行以下命令:

  • docker compose up

  • 构建(如果需要)并启动 docker-compose.yml 中定义的所有服务。

    • -d:在后台运行容器(detached mode)。
  • --build:强制重建镜像(即使没有 Dockerfile 变化)。

  • docker compose down

    • 停止并删除所有服务容器、网络和默认卷。
    • -v:同时删除命名卷(慎用,会删除数据)。
  • docker compose ps

    • 列出所有服务的容器状态。
  • docker compose logs [service_name]

    • 查看服务的日志输出。
    • -f:实时跟踪日志。
  • **docker compose exec <service_name> <command>**:

    • 在指定服务的运行中容器内执行命令。
  • docker compose exec web bash:进入 web 服务的容器终端。

  • **docker compose build [service_name]**:

    • 构建或重建指定服务的镜像。
  • **docker compose pull [service_name]**:

    • 拉取指定服务所需的镜像。
  • **docker compose top**:

    • 查看服务的进程。

Docker Compose 实践:一个简单的 Flask 应用 + PostgreSQL 数据库

我们将使用 Docker Compose 来部署一个典型的 Web 应用(Flask)和数据库(PostgreSQL)组合。

项目结构:

1
2
3
4
5
my-flask-app/
├── app/
│ ├── app.py # Flask 应用代码
│ └── requirements.txt # Python 依赖
└── docker-compose.yml # Compose 配置文件

app/app.py (Flask 应用代码 - 假设会连接到 PostgreSQL):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from flask import Flask
import os
import psycopg2 # 假设已安装 psycopg2-binary

app = Flask(__name__)

# 从环境变量获取数据库连接信息
DB_HOST = os.environ.get('DB_HOST', 'localhost')
DB_NAME = os.environ.get('POSTGRES_DB', 'mydatabase')
DB_USER = os.environ.get('POSTGRES_USER', 'user')
DB_PASSWORD = os.environ.get('POSTGRES_PASSWORD', 'password')

@app.route('/')
def hello_world():
try:
conn = psycopg2.connect(host=DB_HOST, database=DB_NAME, user=DB_USER, password=DB_PASSWORD)
cur = conn.cursor()
cur.execute("SELECT 1")
cur.close()
conn.close()
return "Hello from Flask! Connected to PostgreSQL successfully!"
except Exception as e:
return f"Hello from Flask! Failed to connect to PostgreSQL: {e}"

if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)

app/requirements.txt:

1
2
Flask==2.0.3
psycopg2-binary==2.9.3 # 用于连接 PostgreSQL

Dockerfile (在 app/ 目录下创建,如果需要构建 Flask 镜像):

1
2
3
4
5
6
7
8
9
10
11
12
FROM python:3.9-slim-buster

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 5000

CMD ["python", "app.py"]

注意:在 docker-compose.yml 中,我们可以选择 build: ./app 来让 Compose 根据这个 Dockerfile 构建镜像,也可以直接使用预构建的镜像 image: my-flask-app:1.0。这里我们选择 build

docker-compose.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
version: '3.8'

services:
web:
build: ./app # 构建 web 服务镜像,Dockerfile 在 ./app 目录下
ports:
- "5000:5000" # 将主机 5000 端口映射到容器 5000 端口
environment:
DB_HOST: db # 使用服务名称 'db' 作为数据库主机名,Compose 会自动解析
POSTGRES_DB: mydatabase
POSTGRES_USER: user
POSTGRES_PASSWORD: password
depends_on:
- db # 确保 db 服务在 web 服务之前启动
networks:
- app_network # 连接到自定义网络

db:
image: postgres:13 # 使用 PostgreSQL 官方镜像
environment:
POSTGRES_DB: mydatabase
POSTGRES_USER: user
POSTGRES_PASSWORD: password
volumes:
- db_data:/var/lib/postgresql/data # 将命名卷 db_data 挂载到 PostgreSQL 数据目录
networks:
- app_network # 连接到自定义网络

volumes: # 定义命名卷,用于 PostgreSQL 数据持久化
db_data:

networks: # 定义自定义网络,用于 web 和 db 服务之间的通信
app_network:
driver: bridge

部署步骤:

  1. 将上述文件保存到 my-flask-app 目录及其子目录 app/ 中。

  2. 打开终端,进入 my-flask-app 目录。

  3. 启动应用栈:

    1
    docker compose up -d

这个命令会自动构建 web 服务的镜像,拉取 postgres 镜像,创建 db_data 命名卷和 app_network 网络,然后启动 db 服务,接着启动 web 服务,并将它们连接到 app_network

  1. 检查服务状态:

    Bash

    1
    docker compose ps

    您应该看到 webdb 服务都在运行。

  2. 查看日志 (可选):

    Bash

    1
    2
    docker compose logs web
    docker compose logs db
  3. 访问应用: 在浏览器中访问 http://localhost:5000。如果一切顺利,您应该看到 “Hello from Flask! Connected to PostgreSQL successfully!” 的消息。

  4. 停止并清理:

    1
    docker compose down

    这会停止并删除 webdb 容器,以及 app_network 网络。但 db_data 命名卷默认不会被删除(除非您使用 -v 选项)。

结语

通过本篇文章,我们详细了解了 Docker Compose 的概念、其 YAML 文件的结构以及常用的命令。通过一个 Flask 应用与 PostgreSQL 数据库的实例,您已经掌握了如何使用 Docker Compose 来定义和部署一个多容器应用。

Docker Compose 极大地简化了本地开发和测试环境的搭建,是 Docker 生态系统中不可或缺的工具。在下一篇文章中,我们将学习如何将本地构建的 Docker 镜像推送到远程仓库,特别是 Docker Hub,实现镜像的共享和分发。