Docker Compose 构建、运行与管理多容器应用
当你在 docker-compose.yml 文件中精心定义了多容器应用的服务、网络和数据卷之后,下一个关键步骤就是让这些定义“活”起来。
Docker Compose 提供了直观的命令来简化应用栈的整个生命周期管理:从为服务构建自定义镜像,到编排它们的启动、管理运行状态,再到优雅地关闭并清理它们。本章将引导你学习这些核心命令,只需敲击几行简单的代码,就能将声明式的 Compose 文件转化为全面运行的多容器应用,让你轻松实现构建、运行、检查和销毁整个应用栈。
1. 使用 docker compose up 编排应用启动
docker compose up 是运行 docker-compose.yml 中定义的多容器应用的基石命令。当你执行这个命令时,Docker Compose 会读取你的配置,处理必要资源的创建(如不存在的网络和数据卷),构建指定的自定义服务镜像,然后按照正确的顺序启动所有服务,并妥善管理它们之间的依赖关系。
1.1 docker compose up 是如何工作的
- 解析
docker-compose.yml:Compose 首先解析你的 YAML 文件,以理解整个应用的结构。 - 创建资源:它会检查 YAML 中声明的命名网络或数据卷是否存在。如果不存在,它会自动创建。
- 构建镜像:对于任何指定了
build上下文(意味着它使用Dockerfile来创建镜像)的服务,Compose 都会去构建该镜像。如果镜像在本地已存在,且Dockerfile或构建上下文没有发生改变,Compose 会直接使用现有镜像,除非你显式要求重新构建。 - 创建并启动服务:接着,Compose 为每个服务创建容器,将它们连接到定义的网络,并按照指定的挂载数据卷。它会根据显式的
depends_on声明和隐式的网络依赖,以智能的顺序启动服务。 - 日志记录:默认情况下,
docker compose up会附加到所有运行中服务的输出上,将它们的日志直接流式传输到你的终端。这对于调试和初步观察非常棒。
1.2 docker compose up 的核心选项
-d或--detach:在后台运行容器,释放你的终端。这对于生产环境部署,或者当你启动应用后还需要继续使用终端时必不可少。--build:强制 Compose 为带有 build 指令的服务重新构建镜像,即使镜像已经存在且未发生改变。当你在构建上下文中修改了 Dockerfile 或应用代码,但 Compose 的缓存可能阻止了重建时,这个选项非常有用。--no-recreate:如果服务的容器已经存在,此选项会阻止 Compose 重新创建它们。它会尝试直接启动现有容器。如果你没有进行需要重新创建的配置更改,这有助于保留容器状态。--force-recreate:强制 Compose 停止并重新创建容器,即使它们的配置没有改变。这有助于确保干净的启动环境,或应用某些可能无法触发常规重建的微小环境更改。--remove-orphans:删除 Compose 文件中不再定义但依然存在的服务容器。当你重构docker-compose.yml并删除了旧服务时,这有助于保持环境的整洁。
1.3 示例:启动包含数据库的 Web 应用
让我们考虑一个常见的场景:一个连接到 PostgreSQL 数据库的简单 Web 应用。
项目结构:
my-web-app/
├── docker-compose.yml
├── web/
│ ├── Dockerfile
│ ├── app.py
│ └── requirements.txtweb/Dockerfile:
# 使用官方 Python 运行环境作为基础镜像
FROM python:3.9-slim-buster
# 设置容器内的工作目录
WORKDIR /app
# 复制 requirements.txt 并安装所需的包
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用程序的其余代码
COPY . .
# 暴露 Flask 应用的 5000 端口
EXPOSE 5000
# 定义环境变量
ENV FLASK_APP=app.py
# 运行 Flask 应用程序
CMD ["flask", "run", "--host=0.0.0.0"]web/requirements.txt:
Flask
psycopg2-binaryweb/app.py:
from flask import Flask
import os
import psycopg2
app = Flask(__name__)
@app.route('/')
def hello():
db_host = os.environ.get('DB_HOST', 'db') # 'db' 是 docker-compose 中的服务名称
db_name = os.environ.get('POSTGRES_DB', 'mydatabase')
db_user = os.environ.get('POSTGRES_USER', 'myuser')
db_password = os.environ.get('POSTGRES_PASSWORD', 'mypassword')
try:
conn = psycopg2.connect(host=db_host, database=db_name, user=db_user, password=db_password)
cur = conn.cursor()
cur.execute("SELECT version();")
db_version = cur.fetchone()[0]
cur.close()
conn.close()
return f"Hello from Flask! 成功连接到 PostgreSQL 版本: {db_version}"
except Exception as e:
return f"Hello from Flask! 无法连接到数据库: {e}"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)docker-compose.yml:
version: '3.8'
services:
web:
build: ./web # 指示 Compose 使用 './web' 目录中的 Dockerfile 构建镜像
ports:
- "5000:5000" # 将宿主机的 5000 端口映射到容器的 5000 端口
environment:
# 这些环境变量将被传递到 web 服务的容器中
- DB_HOST=db # 主机名 'db' 会在 Docker 网络内解析为 'db' 服务
- POSTGRES_DB=mydatabase
- POSTGRES_USER=myuser
- POSTGRES_PASSWORD=mypassword
depends_on:
# 确保 'db' 服务在 'web' 之前启动。
# 注意:这仅保证启动顺序,不保证服务已准备就绪。
- db
networks:
- app-network # 将 web 服务连接到 'app-network'
db:
image: postgres:13 # 使用 Docker Hub 上官方的 PostgreSQL 13 镜像
environment:
# 用于初始化设置的 PostgreSQL 专用环境变量
POSTGRES_DB: mydatabase
POSTGRES_USER: myuser
POSTGRES_PASSWORD: mypassword
volumes:
# 用于 PostgreSQL 数据的持久化卷,防止容器重建时丢失数据
- db-data:/var/lib/postgresql/data
networks:
- app-network # 将 db 服务连接到 'app-network'
networks:
app-network: # 为应用服务定义一个自定义桥接网络
volumes:
db-data: # 为数据库持久化定义一个命名卷要运行这个应用,请在终端中导航到 my-web-app 目录并执行:
docker compose up你将在终端中看到来自 web 和 db 服务的日志不断滚动。如果你想在后台运行它(这在日常开发或部署中更为常见),请加上 -d:
docker compose up -d现在,服务将在后台运行。你可以打开浏览器访问 http://localhost:5000 来查看你的 Web 应用。
2. 使用 docker compose build 显式构建镜像
虽然 docker compose up 会自动处理带有 build 上下文的服务的镜像构建,但在某些场景下,你可能只想显式地构建或重新构建镜像,而不一定要启动整个应用栈。这时就可以使用 docker compose build 命令。
2.1 何时使用 docker compose build
- CI/CD 预构建:在持续集成/持续部署流水线中,你可能希望将所有镜像的构建作为一个独立的步骤,然后再将它们推送到镜像仓库或进行部署。
- 调试 Dockerfile:在积极开发和调试
Dockerfile时,通过仅构建镜像而不等待应用启动,你可以更快地迭代构建过程。 - 强制重新构建:如果你怀疑缓存镜像存在问题,或者想要确保获得一个全新的构建,
docker compose build能让你拥有直接的控制权。
2.2 docker compose build 的核心选项
--no-cache:在镜像构建过程中不使用缓存层。这会强制 Docker 从头开始运行Dockerfile中的每一条指令,这对于确保干净的构建或拉取新的基础镜像非常有用。--pull:始终尝试拉取Dockerfile中FROM指令所引用的镜像的最新版本。这能确保你的基础镜像是最新的。[SERVICE...]:你可以指定一个或多个服务名称,以便仅构建这些特定服务的镜像,而不是构建docker-compose.yml中定义的所有服务。
2.3 示例:构建特定的服务
继续使用前面的 my-web-app 示例:
如果只想构建 web 服务的镜像:
docker compose build web如果你想构建 docker-compose.yml 中带有 build 指令的所有服务镜像,并且强制拉取最新的基础镜像且不使用缓存:
docker compose build --no-cache --pull这条命令会重新构建 web 服务镜像,忽略任何缓存层,并确保如果 Docker Hub 上有更新的版本,它会拉取最新的 python:3.9-slim-buster 基础镜像。
3. 管理运行中的多容器应用
一旦你的应用跑起来了,你就需要使用各种命令来检查其状态、查看日志以及控制其服务的生命周期。Docker Compose 提供了一套完整的命令来完成这些管理任务。
3.1 使用 docker compose ps 检查服务状态
docker compose ps 命令会列出与你的 Compose 应用关联的容器,并显示它们的状态、端口和运行命令。
docker compose ps预期输出(示例):
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
my-web-app-db-1 postgres:13 "docker-entrypoint.s…" db 2 minutes ago Up 2 minutes 5432/tcp
my-web-app-web-1 my-web-app-web "flask run --host=0.…" web 2 minutes ago Up 2 minutes 0.0.0.0:5000->5000/tcp这个输出清晰地表明 db 和 web 服务都在正常运行(Up),同时也展示了它们的镜像、命令以及端口映射情况。
3.2 使用 docker compose logs 查看日志
docker compose logs 命令用于显示服务的日志输出。默认情况下,它会显示所有服务的日志。
docker compose logs核心选项:
-f或--follow:跟随日志输出,实时流式传输新日志(类似tail -f)。--tail N:每个容器仅显示最后N行日志。[SERVICE...]:指定一个或多个服务名称,以仅查看特定服务的日志。
如果想专门查看 web 服务的日志并实时跟踪:
docker compose logs -f web这对于实时调试和监控极其有用。
3.3 使用 docker compose stop 停止服务
docker compose stop 命令会优雅地停止正在运行的服务容器,但不会删除它们。稍后你可以使用 docker compose start 重新启动这些容器。
docker compose stop你也可以指定停止特定的服务:
docker compose stop web3.4 使用 docker compose start 启动已停止的服务
如果你使用了 docker compose stop 停止了服务,你可以使用 docker compose start 重新启动它们(前提是容器仍然存在)。
docker compose start或者启动特定服务:
docker compose start web3.5 使用 docker compose restart 重启服务
docker compose restart 命令会先停止然后再启动服务。这是应用那些需要服务重启才能生效的更改的便捷方式。
docker compose restart或者重启特定服务:
docker compose restart web3.6 使用 docker compose exec 在服务内执行命令
docker compose exec 命令允许你在正在运行的服务容器内执行任意命令。这对于调试、运行数据库迁移或执行管理任务来说具有不可估量的价值。
docker compose exec [SERVICE] [COMMAND]如果想在 web 服务容器内打开一个交互式的 bash 终端:
docker compose exec web bash进入容器后,你可以检查文件、查看环境变量或运行特定于应用程序的命令。输入 exit 即可退出容器终端。
如果想在 db 服务容器内运行 SQL 客户端:
docker compose exec db psql -U myuser mydatabase这允许你直接与 PostgreSQL 数据库进行交互。
3.7 使用 docker compose down 销毁应用
docker compose down 命令会停止运行中的容器,并删除由 docker compose up 创建的容器、网络以及(默认配置下的)默认网络。
docker compose down核心选项:
--volumes或-v:删除在docker-compose.yml的volumes部分声明的命名卷以及任何匿名卷。谨慎使用此选项,尤其是对于数据库,因为它会永久删除你的持久化数据!--remove-orphans:删除 Compose 文件中不再定义但可能仍在运行的服务的容器。--rmi all:删除被任何服务使用的所有镜像,即使是缓存的镜像。(--rmi local仅删除没有自定义标签的本地镜像)。
如果要销毁应用并同时删除 db-data 命名卷(这会删除数据库数据):
docker compose down --volumes强烈注意:不带 --volumes 选项的 docker compose down 会保留你的命名卷(如 db-data)。这意味着如果你再次运行 docker compose up,你的数据库数据依然存在。这是开发中常见且推荐的实践。只有当你明确想要重置为全新的数据状态时,才使用 --volumes。
4. 应用依赖与编排
docker-compose.yml 中的 depends_on 关键字在编排服务的启动顺序中扮演着至关重要的角色。当你运行 docker compose up 时,Compose 会利用这个信息,确保在启动目标服务之前,先启动它所依赖的服务。
在我们的例子中:
services:
web:
# ...
depends_on:
- db这告诉 Docker Compose 必须先启动 db 服务,只有当 db 正在运行(意味着其容器已经启动)后,它才会继续启动 web 服务。
4.1 理解 depends_on 的局限性
必须理解 depends_on 的一个关键局限:它仅保证作为依赖项的容器已经启动。它不会等待容器内部的应用程序准备就绪、变为健康状态或开始监听连接。
例如,depends_on: - db 确保了 PostgreSQL 容器正在运行,但它不能保证该容器内部的 PostgreSQL 服务器已经完全初始化、创建了数据库并准备好接受客户端连接。
对于生产级别的应用,你通常需要更健壮的就绪性检查(Readiness Checks)。虽然本章侧重于基础的构建和运行,但你要知道,要构建真正具有弹性的多容器应用,通常需要使用像 healthcheck(健康检查,属于进阶的 Docker Compose 主题)或轮询依赖项是否就绪的自定义入口点脚本(Entrypoint Scripts)。不过,对于许多开发场景而言,depends_on 提供的基础编排已经足够了。
5. 实战演示
让我们用刚学到的命令,走一遍 my-web-app 的完整生命周期。
步骤 1: 首次设置并运行(全新启动)
假设你拥有前面描述的包含 docker-compose.yml、web/Dockerfile、web/app.py 和 web/requirements.txt 文件的 my-web-app 项目目录。
导航到该目录:
cd my-web-app在后台模式下启动应用。这将会构建 web 镜像,创建 app-network 网络和 db-data 数据卷,然后启动 db 和 web 容器。
docker compose up -d你应该会看到指示网络、数据卷和容器创建的输出,接着是类似 Started 的消息。
验证服务是否正在运行:
docker compose ps两个服务的状态都应显示为 Up。
检查日志确保一切启动正常:
docker compose logs寻找表明 PostgreSQL 服务器已就绪以及 Flask 应用正在 5000 端口上运行的消息。
在浏览器中访问 http://localhost:5000。你应该能看到 "Hello from Flask! 成功连接到 PostgreSQL 版本: ..."。
步骤 2: 修改代码并重新构建
让我们修改 app.py 更改显示的信息。
编辑 web/app.py:
# ... (保留原有的导入和应用定义)
@app.route('/')
def hello():
# ... (保留数据库连接参数)
try:
# ... (保留数据库连接逻辑)
return f"来自*修改后* Flask 应用的问候!成功连接到 PostgreSQL 版本: {db_version}" # 在此处修改了消息
except Exception as e:
return f"来自*修改后* Flask 应用的问候!无法连接到数据库: {e}"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)现在,因为我们修改了应用代码,我们需要重新构建 web 服务的镜像并重启它的容器。
首先,停止 web 服务(db 服务可以保持运行,因为它的镜像没变):
docker compose stop web然后,重新构建 web 镜像。使用 --no-cache 确保最新的 app.py 被包含到镜像中。
docker compose build --no-cache web最后,重新启动 web 服务。简单来说,在构建之后再次对 web 服务执行 up 命令即可。
docker compose up -d web(或者,在构建镜像后,一个更简单的应用更改的方法是直接重启服务:docker compose restart web)
刷新浏览器 http://localhost:5000。你现在应该能看到更新后的消息:“来自修改后 Flask 应用的问候!...”
步骤 3: 通过 exec 执行数据库操作
使用 docker compose exec 与数据库交互。我们将连接到 PostgreSQL shell 并创建一个简单的表。
确保服务仍在运行 (docker compose ps)。
进入 db 容器的 psql 终端:
docker compose exec db psql -U myuser mydatabase你现在位于 PostgreSQL 客户端内。执行一些 SQL 命令:
CREATE TABLE messages (id SERIAL PRIMARY KEY, text VARCHAR(255));
INSERT INTO messages (text) VALUES ('来自数据库的问候!');
SELECT * FROM messages;
\q\q 命令会退出 psql 终端。你现在回到了宿主机的终端。
这演示了 docker compose exec 如何让你与特定服务进行交互,以实现管理或调试目的,而无需直接登录到宿主机或容器内。
步骤 4: 销毁应用(保留数据)
当你完成开发或者想停止应用但为下次保留数据库数据时:
docker compose down这会停止并删除 web 和 db 容器以及 app-network 网络。最关键的是,它不会删除 db-data 命名卷,从而保留了你的 messages 表数据。
验证容器是否已消失:
docker compose ps不应该列出任何服务。
步骤 5: 重启应用(携带持久化数据)
现在,让我们再次启动应用。因为我们在 down 时没有使用 --volumes,我们的 db-data 卷依然存在。
docker compose up -dDocker Compose 将重新创建容器和网络。db 服务会找到其现有的 db-data 卷并使用它。如果你应用设计为展示该数据,那么你之前插入的数据依然会在那里。
步骤 6: 彻底销毁并清除所有数据
如果你想要一个完全干净的环境,包括删除所有持久化数据:
docker compose down --volumes这会停止并删除容器、网络以及 db-data 卷。你的 messages 表和任何其他数据库数据将被永久删除。
验证卷是否已消失:
docker volume ls你应该再也看不到 my-web-app_db-data 这个卷了。