网站搜索

如何在 Ubuntu 上安装和使用 Docker Compose


介绍

Docker 简化了在容器中管理应用程序进程的流程。虽然容器在某些方面类似于虚拟机,但它们更加轻量且资源友好。这使得开发人员能够将应用程序环境拆分为多个独立的服务。

对于依赖多个服务的应用程序来说,协调所有容器的启动、通信和关闭可能会很快变得难以管理。Docker Compose 是一个工具,允许你基于 YAML 文件中设定的定义运行多容器应用环境。它使用服务定义来构建可完全自定义的多容器环境,这些容器可以共享网络和数据卷。

本指南将引导您在 Ubuntu 服务器上安装 Docker Compose 并运行一个简单的容器。之后,您将学习如何使用 WordPress 应用程序和 MySQL 数据库构建多服务环境。我们还将讨论更高级的主题,包括服务扩展、定义自定义网络以及使用模块化包含指令。最后,本文提供了从旧版 docker-compose(v1)迁移到现代 docker compose(v2)的指南,以及关于解决常见问题(如端口冲突和权限错误)的详细说明。

主要要点:

  • Docker Compose 是一个工具,用于通过单个 YAML 文件定义和运行多容器应用程序,以配置服务、网络和卷。
  • 在 Ubuntu 上安装 Docker Compose,可以通过添加 Docker 官方仓库并使用 apt 来安装 docker-compose-plugin 软件包。
  • 现代命令是 docker compose(带空格)。它取代了已弃用的 docker-compose(带连字符)v1 工具。
  • 使用 docker compose up -d 以分离模式启动应用程序,使用 docker compose down 停止并移除其所有容器和网络。
  • depends_on 指令有助于控制服务的启动顺序,例如确保数据库容器在 Web 应用容器之前启动。
  • 最常见的问题是“权限被拒绝”(通过将用户添加到 docker 组解决)、端口冲突(通过在 YAML 文件中更改主机端口解决)以及 YAML 语法错误(通过更正缩进解决)。
  • 您可以使用 docker compose up --scale <service_name>=<number> 命令运行服务的多个实例。
  • include 指令允许你将大型的 docker-compose.yml 文件拆分成更小、更易管理的配置文件。

先决条件

要跟随本文,您需要:

  • 以具有sudo权限的非root用户身份访问Ubuntu本地机器或开发服务器。如果您使用的是远程服务器,建议安装有效的防火墙。有关这些设置,请参阅我们的《Ubuntu初始服务器设置指南》。
  • 在您的服务器或本地计算机上安装 Docker,按照《如何在 Ubuntu 上安装 Docker – 分步指南》的步骤 1 和 2进行操作。

注意: 从 Docker Compose v2 开始,Docker 已经转向使用 compose CLI 插件命令,而不再使用原来的 docker-compose,如我们在《如何在 Ubuntu 上安装 Docker Compose(逐步指南)》中所述。虽然安装方式有所不同,但实际上使用时,通常只需将 docker-compose 命令中的连字符去掉,变为 docker compose。有关完整的兼容性详情,请查看官方 Docker 文档中关于新 compose 与旧 docker-compose 命令兼容性的说明。

步骤 1 — 安装 Docker Compose

在 Ubuntu 上安装 Docker Compose 有两种方法:

  • 使用仓库安装
  • 手动安装

我们将在本节中讨论这两种方式。

使用 apt 仓库安装 Docker Compose

首先,让我们设置 Docker apt 仓库。

# Add Docker's official GPG key:

sudo apt-get update

sudo apt-get install ca-certificates curl

sudo install -m 0755 -d /etc/apt/keyrings

sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc

sudo chmod a+r /etc/apt/keyrings/docker.asc



# Add the repository to Apt sources:

echo \

  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \

  $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \

  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update

现在,您可以使用以下命令安装 Docker Compose:

sudo apt install docker-compose-plugin

Docker Compose 已成功安装在您的系统上。要验证安装是否成功,您可以运行:

docker compose version

你将看到类似这样的输出:

Docker Compose version v2.3.3

手动安装 Docker Compose

为了确保你获得最新的稳定版本的 Docker Compose,你将从其官方 Github 仓库下载该软件。

首先,确认他们发布页面上可用的最新版本。在撰写本文时,最新的稳定版本是 v2.40.2。

使用以下命令下载:

mkdir -p ~/.docker/cli-plugins/

curl -SL https://github.com/docker/compose/releases/download/v2.40.2/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose

接下来,设置正确的权限,以便可以执行 docker compose 命令:

chmod +x ~/.docker/cli-plugins/docker-compose

在下一部分,您将看到如何设置一个 docker-compose.yml 文件,并使用此工具启动一个容器化环境。

步骤 2 — 设置 docker-compose.yml 文件

为了演示如何设置 docker-compose.yml 文件并使用 Docker Compose,你将使用来自 Docker Hub(公共 Docker 注册表)的官方 Nginx 镜像创建一个 Web 服务器环境。这个容器化环境将提供一个静态 HTML 文件。

首先在你的主文件夹中创建一个新目录,然后进入该目录:

mkdir ~/compose-demo

cd ~/compose-demo

在此目录中,创建一个应用程序文件夹,作为你的 Nginx 环境的文档根目录:

mkdir app

使用你喜欢的文本编辑器,在 app 文件夹中创建一个新的 index.html 文件:

nano app/index.html

将以下内容放入此文件:

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Docker Compose Demo</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/kognise/water.css@latest/dist/dark.min.css">
</head>
<body>

    <h1>This is a Docker Compose Demo Page.</h1>
    <p>This content is being served by an Nginx container.</p>

</body>
</html>

完成后保存并关闭文件。如果你使用的是 nano,可以通过按 CTRL+X,然后按 Y 和 ENTER 来确认操作。

接下来,创建 docker-compose.yml 文件:

nano docker-compose.yml

将以下内容插入到您的 docker-compose.yml 文件中:

services:
  web:
    image: nginx:alpine
    ports:
      - "8000:80"
    volumes:
      - ./app:/usr/share/nginx/html

在现代 Docker Compose 中,version 字段是可选的,通常会省略,因为 Compose 可以自动检测配置版本。上面的示例没有包含 version 字段,这是大多数新项目推荐的做法。只有在需要兼容旧版本时才需要指定 version。

然后你有 services 块,在这里你设置属于此环境的服务。在你的例子中,你只有一个名为 web 的服务。该服务使用 nginx:alpine 镜像,并通过 ports 指令设置端口重定向。所有来自 主机(运行 Docker Compose 的系统)端口 8000 的请求都会被重定向到 web 容器的端口 80,Nginx 将在该端口运行。

volumes 指令将在主机与容器之间创建一个共享卷。这会将本地的 app 文件夹与容器共享,并且卷将在容器内部位于 /usr/share/nginx/html,这将覆盖 Nginx 的默认文档根目录。

保存并关闭文件。

你已经设置了一个演示页面和一个 docker-compose.yml 文件,用于创建一个容器化的 Web 服务器环境来提供服务。在下一步中,你将使用 Docker Compose 启动这个环境。

步骤 3 — 运行 Docker Compose

有了 docker-compose.yml 文件后,你现在可以执行 Docker Compose 来启动你的环境。以下命令将下载所需的 Docker 镜像,为 web 服务创建一个容器,并在后台模式下运行容器化环境:

docker compose up -d

Docker Compose 会首先在本地系统中查找已定义的镜像,如果找不到该镜像,它将从 Docker Hub 下载镜像。你会看到如下输出:

Creating network "compose-demo_default" with the default driver
Pulling web (nginx:alpine)...
alpine: Pulling from library/nginx
cbdbe7a5bc2a: Pull complete
10c113fb0c77: Pull complete
9ba64393807b: Pull complete
c829a9c40ab2: Pull complete
61d685417b2f: Pull complete
Digest: sha256:57254039c6313fe8c53f1acbf15657ec9616a813397b74b063e32443427c5502
Status: Downloaded newer image for nginx:alpine
Creating compose-demo_web_1 ... done

注意: 如果在运行 docker compose up 时遇到“权限被拒绝”错误,这通常意味着您的非根用户没有权限访问 Docker 守护进程的套接字。

默认情况下,Docker 守护进程绑定到一个 Unix 套接字 (/var/run/docker.sock),该套接字属于 root 用户。要解决此问题,您必须将非 root 用户添加到 docker 用户组中,该用户组是在安装 Docker 时创建的。

运行以下命令将您的用户添加到 docker 组:

sudo usermod -aG docker ${USER}

运行此命令后,您需要退出并重新登录,以使组更改生效。您也可以通过输入以下命令在当前终端会话中激活更改:

newgrp docker

此命令应解决与 Docker 套接字相关的任何权限错误。有关完整的操作指南,请参阅《如何在 Ubuntu 上安装 Docker – 分步指南》的步骤 2。

您的环境现在已在后台运行。要验证容器是否处于活动状态,您可以运行:

docker compose ps

此命令将向您显示正在运行的容器及其状态的信息,以及当前正在进行的任何端口重定向:

       Name                     Command               State          Ports        
----------------------------------------------------------------------------------
compose-demo_web_1   /docker-entrypoint.sh ngin ...   Up      0.0.0.0:8000->80/tcp

如果您在本地机器上运行此演示,现在可以通过将浏览器指向 localhost:8000 来访问演示应用;如果您在远程服务器上运行此演示,则可以通过 your_server_domain_or_IP:8000 来访问。

你会看到这样的页面:

你在 docker-compose.yml 文件中设置的共享卷会使你的 app 文件夹中的文件与容器的文档根目录保持同步。如果你对 index.html 文件进行任何更改,容器会自动捕获这些更改,因此在你重新加载页面时,浏览器中会显示更新后的内容。

在下一步中,您将看到如何使用 Docker Compose 命令管理您的容器化环境。

步骤 4 — 熟悉 Docker Compose 命令

你已经了解了如何设置 docker-compose.yml 文件,并使用 docker compose up 启动你的环境。现在,你将看到如何使用 Docker Compose 命令来管理和与容器化环境进行交互。

要查看您的 Nginx 容器生成的日志,您可以使用 logs 命令:

docker compose logs

你将看到类似这样的输出:

Attaching to compose-demo_web_1
web_1  | /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
web_1  | /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
web_1  | /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
web_1  | 10-listen-on-ipv6-by-default.sh: Getting the checksum of /etc/nginx/conf.d/default.conf
web_1  | 10-listen-on-ipv6-by-default.sh: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
web_1  | /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
web_1  | /docker-entrypoint.sh: Configuration complete; ready for start up
web_1  | 172.22.0.1 - - [02/Jun/2020:10:47:13 +0000] "GET / HTTP/1.1" 200 353 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36" "-"

如果你想在不更改容器当前状态的情况下暂停环境执行,你可以使用:

docker compose pause
Pausing compose-demo_web_1 ... done

在发出暂停后恢复执行:

docker compose unpause
Unpausing compose-demo_web_1 ... done

stop 命令将终止容器的执行,但不会销毁与您的容器相关的任何数据:

docker compose stop
Stopping compose-demo_web_1 ... done

如果你想删除与此容器化环境相关的容器、网络和卷,请使用 down 命令:

docker compose down
Removing compose-demo_web_1 ... done
Removing network compose-demo_default

请注意,这不会移除 Docker Compose 用于启动您的环境的基础镜像(在您的案例中为 nginx:alpine)。这样,每当您使用 docker compose up 再次启动环境时,由于镜像已经存在于您的系统中,启动过程会快得多。

如果您还想从系统中删除基础镜像,可以使用:

docker image rm nginx:alpine
Untagged: nginx:alpine
Untagged: nginx@sha256:b89a6ccbda39576ad23fd079978c967cecc6b170db6e7ff8a769bf2259a71912
Deleted: sha256:7d0cdcc60a96a5124763fddf5d534d058ad7d0d8d4c3b8be2aefedf4267d0270
Deleted: sha256:05a0eaca15d731e0029a7604ef54f0dda3b736d4e987e6ac87b91ac7aac03ab1
Deleted: sha256:c6bbc4bdac396583641cb44cd35126b2c195be8fe1ac5e6c577c14752bbe9157
Deleted: sha256:35789b1e1a362b0da8392ca7d5759ef08b9a6b7141cc1521570f984dc7905eb6
Deleted: sha256:a3efaa65ec344c882fe5d543a392a54c4ceacd1efd91662d06964211b1be4c08
Deleted: sha256:3e207b409db364b595ba862cdc12be96dcdad8e36c59a03b7b3b61c946a5741a

注意:请参考我们的《在 Ubuntu 上安装 Docker 的逐步指南》,以获取关于 Docker 命令的更详细参考。

步骤5 — 构建多服务应用程序

Docker Compose 的真正力量在于管理协同工作的多个服务。Nginx 示例只是一个单一服务。让我们创建一个更实用的多服务应用程序:一个连接到 MySQL 数据库的 WordPress 网站。

这个设置涉及两个服务:wordpress(运行应用程序)和db(运行数据库)。我们还将使用 Docker 卷来确保即使容器被移除,数据库数据仍然可以保留。

让我们为这个应用程序创建一个新目录:

mkdir -p ~/compose-demo/wordpressapp

cd ~/compose-demo/wordpressapp

在这个例子中,我们不需要任何本地文件,因为镜像将包含所有必要的软件。

创建一个新的 docker-compose.yml 文件:

nano docker-compose.yml

粘贴以下配置文件。这个文件比较复杂,所以我们会逐部分进行分析。

services:
  db:
    image: mysql:8.0
    container_name: mysql_db
    volumes:
      - db_data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: <^your_root_password_here<^>
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress_user
      MYSQL_PASSWORD: <^your_wordpress_password_here<^>
    restart: unless-stopped

  wordpress:
    image: wordpress:latest
    container_name: wordpress_app
    ports:
      - "8001:80"
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress_user
      WORDPRESS_DB_PASSWORD: <^your_wordpress_password_here<^>
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - ./wp_content:/var/www/html/wp-content
    depends_on:
      - db
    restart: unless-stopped

volumes:
  db_data:

注意:这里我们为了示例目的,将密码的值硬编码了。在实际环境中使用时,请确保在 .env 文件中使用环境变量,以避免暴露您的凭据。

保存并关闭文件。记得将 <^...^> 占位符替换为强大且安全的密码。

让我们来分解一下这个文件中的新指令:

  • services:这个部分仍然定义了我们的容器。我们现在有两个:db 和 wordpress。
  • image: 我们使用 mysql:8.0 作为数据库,使用 wordpress:latest 镜像作为应用程序。
  • container_name:这用于为容器设置一个特定的、易于阅读的名称,比自动生成的名称更容易引用。
  • environment:这是传递给容器的环境变量列表。mysql 镜像使用这些变量来设置 root 密码并创建初始数据库和用户。wordpress 镜像使用这些变量来确定如何连接到其数据库。
  • 卷(服务级别):

    • 对于 db 服务,db_data:/var/lib/mysql 将一个 Docker 管理的名为 db_data 的卷映射到容器内的 MySQL 数据目录。这可以保障你的数据安全。
    • 对于wordpress服务,./wp_content:/var/www/html/wp-content将本地目录(wp_content)映射到WordPress内容目录。这允许你直接从主机机器编辑主题和插件。
  • depends_on:这表示 Compose 会在启动 wordpress 服务之前先启动 db 服务。这很重要,因为如果启动时找不到数据库,WordPress 将无法运行。
  • volumes(顶层):此块定义了命名卷。db_data: 创建了一个 Docker 管理的卷,这是处理持久数据的推荐方式。

现在,启动这个多服务应用程序:

docker compose up -d

Compose 会拉取 mysql 和 wordpress 镜像,然后创建容器,首先启动 db 服务。

Creating network "compose-demo_default" with the default driver
Creating volume "compose-demo_db_data" with default driver
Pulling db (mysql:8.0)...
...
Pulling wordpress (wordpress:latest)...
...
Creating mysql_db        ... done
Creating wordpress_app ... done

您现在可以通过在浏览器中访问 localhost:8001 或 <^your_server_domain_or_IP<^>:8001 来访问您的新 WordPress 网站。您应该会看到 WordPress 安装界面。有关更详细的示例,请查看我们的文章《如何使用 Docker Compose 安装 WordPress》。

第6步 — 使用 Docker Compose 进行 AI/ML 开发

除了网页应用之外,Docker Compose 对于创建可复现的数据科学和机器学习(AI/ML)环境也是一个非常有用的工具。AI/ML 项目以其复杂的依赖关系而著称,包括特定的 Python 版本、像 TensorFlow 或 PyTorch 这样的库,以及像 NVIDIA CUDA 工具包这样的系统级驱动。Docker Compose 将整个环境捕获在配置文件中,解决了“在我的机器上可以运行”的问题,这对于可复现的研究至关重要。

在此示例中,您将创建一个由多种服务组成的人工智能/机器学习环境,包括:

  • JupyterLab: 一个用于交互式开发的容器,由自定义的 Dockerfile 构建。
  • PostgreSQL:一个用于存储实验结果和元数据的数据库容器。
  • GPU 访问:JupyterLab 容器将被配置为访问主机的 NVIDIA GPU,这是模型训练的常见需求。

先决条件: 这个示例要求您的主机上有 NVIDIA GPU 并已安装 NVIDIA 容器工具包。如果没有,在请求 GPU 资源时,容器将无法启动。

文件结构

首先,为你的项目创建一个目录。在该目录中,你将创建一个 docker-compose.yml 文件、一个 jupyter 目录、一个用于 Jupyter 的 Dockerfile 文件,以及一个 requirements.txt 文件。目录结构应该类似于这样:

ai-project/
├── docker-compose.yml
└── jupyter/
    ├── Dockerfile
    └── requirements.txt

创建组件文件

1. requirements.txt 文件

此文件列出了您的数据科学环境所需的 Python 包。

nano jupyter/requirements.txt

添加所需的包。在此示例中,我们将包含用于数据处理、数据库连接和深度学习框架的库。

pandas
scikit-learn
tensorflow
jupyterlab
psycopg2-binary

保存并关闭文件。

2. Dockerfile

此文件定义了您的自定义 JupyterLab 服务。它使用官方 Jupyter 镜像作为基础,并从 requirements.txt 安装软件包。

nano jupyter/Dockerfile

粘贴以下内容:

# Start from a base image that includes Jupyter and scientific libraries
FROM jupyter/scipy-notebook:latest

# Copy our local requirements file into the container
COPY requirements.txt /tmp/requirements.txt

# Install the Python packages
RUN pip install --no-cache-dir -r /tmp/requirements.txt

此文件指示 Docker 使用 jupyter/scipy-notebook 作为起点,将你的 requirements.txt 复制到容器中,然后使用 pip 安装这些包。

使用 docker-compose.yml 定义环境

现在,创建主 docker-compose.yml 文件。此文件将协调 db 服务和您自定义的 jupyter 服务。

nano docker-compose.yml

粘贴以下配置,并用您自己的安全凭据替换 <^...^> 占位符。

services:
  db:
    image: postgres:15-alpine
    container_name: ai_postgres_db
    environment:
      POSTGRES_USER: <^your_db_user^>
      POSTGRES_PASSWORD: <^your_db_pass^>
      POSTGRES_DB: experiments
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U <^your_db_user^> -d experiments"]
      interval: 10s
      timeout: 5s
      retries: 5
    volumes:
      - postgres_data:/var/lib/postgresql/data
    restart: unless-stopped
    networks:
      - ai_net

  jupyter:
    build: ./jupyter
    container_name: ai_jupyter_lab
    ports:
      - "8888:8888"
    volumes:
      - ./notebooks:/home/jovyan/work
    environment:
      - POSTGRES_HOST=db
      - POSTGRES_USER=<^your_db_user^>
      - POSTGRES_PASSWORD=<^your_db_pass^>
      - POSTGRES_DB=experiments
    restart: unless-stopped
    networks:
      - ai_net
    depends_on:
      db:
        condition: service_healthy
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]

volumes:
  postgres_data:
    name: ai_project_data

networks:
  ai_net:
    driver: bridge

这个配置文件介绍了几个重要的概念:

  • build: ./jupyter:这告诉 Docker Compose 为 jupyter 服务构建自定义镜像。它会在指定的 ./jupyter 目录中寻找 Dockerfile。
  • volumes: - ./notebooks:/home/jovyan/work:这会将本地名为 notebooks 的目录绑定挂载到容器中。这确保了你创建的所有 Jupyter 笔记本都会直接保存在主机上,即使容器被删除,它们仍然会保留。
  • healthcheck:如故障排除部分所示,此检查验证 db 服务不仅处于运行状态,而且在其他服务启动前已经准备好接受连接。
  • depends_on: ... condition: service_healthy:这告诉 jupyter 服务在启动之前等待 db 的健康检查通过。
  • deploy.resources.reservations.devices:这是请求 GPU 访问的模块。

    • driver: nvidia:指定要使用的主机驱动程序。
    • count: 1: 请求一个 GPU。
    • capabilities: [gpu]:确保容器具备使用 GPU 的必要能力。

运行 AI/ML 环境

当您的文件就位后,您就可以构建并运行这些服务了。

  1. 在你的 ai-project 目录下,运行 docker compose up 命令。第一次运行时,你必须添加 --build 标志,以告诉 Compose 构建你的自定义 jupyter 镜像。

    docker compose up -d --build
  2. Compose 首先会构建 jupyter 镜像(可能需要几分钟,因为它会下载 TensorFlow),然后拉取 postgres 镜像,最后启动两个容器。

    [+] Building 5.8s (9/9) FINISHED
     => [internal] load build definition from Dockerfile
    ...
    [+] Running 3/3
     ✔ Network ai-project_ai_net        Created
     ✔ Container ai_postgres_db         Started
     ✔ Container ai_jupyter_lab         Started
  3. 您现在可以通过在浏览器中访问 http://localhost:8888(或 <^your_server_ip^>:8888)来访问 JupyterLab 界面。系统会提示您输入一个令牌,您可以从容器日志中获取该令牌:

    docker compose logs jupyter

    查找类似于 http://127.0.0.1:8888/lab?token=a1b2c3d4e5f6... 的行

  4. 在 Jupyter 笔记本中,你现在可以使用主机名 db 以及你提供的凭据连接到数据库。你的环境还可以访问主机的 GPU 来进行模型训练。

扩展服务和使用自定义网络

Docker Compose 包含用于扩展服务和管理它们所使用网络的功能。

通过副本扩展服务

假设你在第2步中设置的 Nginx 网页服务器流量过大。你可以将 web 服务扩展为运行多个容器实例。Docker Compose 可以自动管理这一过程。

有两种主要方式来扩展服务。

1. 使用 replicas 键(v2 中推荐)

您可以直接在 docker-compose.yml 文件中使用 deploy 和 replicas 键定义所需的实例数量。此功能最初是 Docker Swarm 的一部分,但现在也可用于标准的 Compose 部署。

修改你在第 2 步中的 Nginx docker-compose.yml:

services:
  web:
    image: nginx:alpine
    ports:
      - "8000:80"
    volumes:
      - ./app:/usr/share/nginx/html
    deploy:
      replicas: 3

当你运行 docker compose up -d 时,Compose 会创建三个 web 容器。然而,你会遇到一个问题:所有三个容器都会尝试绑定到主机端口 8000。这会导致第二个和第三个容器出现“端口已被分配”的错误。要在生产环境中解决此问题,你需要 从 web 服务中移除 ports 映射。这样,web 容器只能在 Docker 网络内部访问。一个独立的负载均衡服务将成为唯一拥有公共端口的服务,然后将流量分发到三个 web 副本。

一个示例配置如下所示(这是一个高级示例):

services:
  web:
    image: nginx:alpine
    # No 'ports' mapping here.
    # The service is only accessible inside the 'web-net' network.
    volumes:
      - ./app:/usr/share/nginx/html
    deploy:
      replicas: 3
    networks:
      - web-net

  load_balancer:
    image: nginx:latest
    ports:
      - "80:80" # The load_balancer is the only service with a public port.
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf # A config file that load balances to 'web'.
    networks:
      - web-net
    depends_on:
      - web

networks:
  web-net:

在此设置中,load_balancer 监听端口 80,并将请求内部路由到 web_1、web_2 和 web_3。

这个 replicas 键在与反向代理(如 Traefik 或其他 Nginx 实例)一起使用时最为有用,反向代理可以在 Docker 网络中的副本之间进行请求的负载均衡,而无需每个副本在主机上暴露一个端口。

2. 使用 --scale 标志

一种常见的缩放方法是 --scale 标志,这是从 Compose v1 延续下来的。该标志在运行时应用,并会覆盖任何 replicas 键。

然而,你必须小心端口定义。如果你的服务定义了固定主机端口映射(例如,"8000:80"),运行docker compose up --scale时,第二个和第三个容器会出现错误,因为它们都会尝试绑定到同一个主机端口。

要对暴露端口的服务使用 --scale,必须避免使用固定的主机端口映射。

  • 选项 1:映射到随机主机端口(适合开发)

    你可以修改你的 docker-compose.yml 来只指定容器端口。这会告诉 Docker 将每个容器的 80 端口映射到主机上的一个 随机 可用端口。

    services:
      web:
        image: nginx:alpine
        ports:
          - "80" # No fixed host port
        volumes:
          - ./app:/usr/share/nginx/html

    现在,当你运行 scale 命令时:

    docker compose up -d --scale web=3

    运行 docker compose ps 查看结果。每个容器将运行在不同的随机分配的主机端口上:

          Name                     Command               State          Ports        
    ----------------------------------------------------------------------------------
    compose-demo_web_1   /docker-entrypoint.sh ngin ...   Up      0.0.0.0:49154->80/tcp
    compose-demo-web_2   /docker-entrypoint.sh ngin ...   Up      0.0.0.0:49155->80/tcp
    compose-demo-web_3   /docker-entrypoint.sh ngin ...   Up      0.0.0.0:49156->80/tcp
  • docker-compose.yml - 修正 --scale

  • 选项2:使用反向代理(适合生产环境)

    另一种解决方案,如在replicas部分中提到的,是完全从web服务中移除ports指令。然后,你可以使用一个单独的负载均衡容器(它拥有唯一的公共端口)来管理并将流量分发到Docker网络内的扩展副本之间。

    要停止并删除所有三个容器,命令保持不变:

    docker compose down

自定义网络配置

默认情况下,Docker Compose 会为您的应用创建一个单独的 bridge 网络。文件中的每个服务都会连接到该网络,这就是 wordpress 容器能够仅通过使用服务名 (db) 就找到 db 容器的原因。

但是,您可以定义自己的自定义网络,以获得更好的隔离和控制。

让我们修改 WordPress 示例以使用自定义的 bridge 网络。

services:
  db:
    image: mysql:8.0
    ...
    networks:
      - app_net

  wordpress:
    image: wordpress:latest
    ...
    ports:
      - "8001:80"
    depends_on:
      - db
    networks:
      - app_net

volumes:
  db_data:

networks:
  app_net:
    driver: bridge

这是我们新增的内容:

  • networks(顶层):这定义了一个名为 app_net 的新网络,并指定它应使用标准的 bridge 驱动。
  • 网络(服务级别):在 db 和 wordpress 下,这个键将它们连接到我们的 app_net 网络。

如果你有第三个服务(比如分析工具),并且你没有将其连接到 app_net,它将完全隔离,无法与 db 或 wordpress 容器通信。

对于使用 Docker Swarm 的多主机集群,你需要将 driver 从 bridge 改为 overlay。bridge 驱动用于单个主机上容器之间的通信,这是大多数 Docker Compose 使用场景的标准配置。

使用模块化配置文件

随着您的应用程序不断增长,您的 docker-compose.yml 文件可能会变得庞大且难以管理。Docker Compose 支持 include 指令,允许您将配置拆分到多个文件中。

想象一下,你希望将 WordPress 和数据库的定义分开,并且可能为开发特定的设置(例如在数据库上绑定端口)创建一个通用的 docker-compose.override.yml。

你的目录结构可能是这样的:

compose-demo/
├── docker-compose.yml
├── docker-compose.db.yml
├── docker-compose.web.yml

此文件将仅定义db服务。

services:
  db:
    image: mysql:8.0
    container_name: mysql_db
    volumes:
      - db_data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: <^your_root_password_here<^>
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress_user
      MYSQL_PASSWORD: <^your_wordpress_password_here<^>
    restart: unless-stopped

volumes:
  db_data:

此文件将仅定义wordpress服务。

services:
  wordpress:
    image: wordpress:latest
    container_name: wordpress_app
    ports:
      - "8001:80"
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress_user
      WORDPRESS_DB_PASSWORD: <^your_wordpress_password_here<^>
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - ./wp_content:/var/www/html/wp-content
    depends_on:
      - db
    restart: unless-stopped

现在,你的主要 docker-compose.yml 文件变得非常简单。它只需使用 include 来引入其他文件。

include:
  - docker-compose.db.yml
  - docker-compose.web.yml

当你运行 docker compose up -d 时,Compose 会读取所有三个文件,合并配置,并启动 db 和 wordpress 服务,就好像它们定义在同一个文件中一样。这种方法使你的配置更加模块化且可重用。

注意: include: 指令需要 Docker Compose v2.20 或更高版本。如果您的系统使用旧版本,可以使用 docker compose -f docker-compose.db.yml -f docker-compose.web.yml up 来合并文件。

从 docker-compose(v1)迁移到 docker compose(v2)

您在第1步中安装的 docker compose 命令是“Compose v2”。它是一个基于 Go 的插件,直接内置在 Docker CLI 中。

原始版本“Compose v1”是一个单独的 Python 工具,通过 docker-compose(带连字符)调用。截至 2023 年 7 月,Compose v1 已不再受支持,并已被弃用。

主要变化与向后兼容性

  • 命令:最明显的变化是命令。连字符(docker-compose)现在用空格代替(docker compose)。
  • 安装:v1 是通过 pip 安装的。v2 随 Docker Desktop 提供,或者可以像在步骤 1 中那样作为 CLI 插件安装。
  • YAML 文件:您的 docker-compose.yml 文件几乎 100% 向后兼容。Compose v2 完全支持文件版本 3.x。迁移时您无需更改 YAML 文件,只需更改您的命令即可。
  • --project-name 标志:-p 标志仍然可用,但完整标志现在是 --project-name,而不是 --project_name。

命令迁移指南

大多数命令是相同的,只是去掉了连字符。这里有一张表,将常用的v1命令与其v2对应命令进行比较。

docker-compose(v1)命令docker compose(v2)命令笔记
docker-compose up -d
docker compose up -d
语法没有变化。
docker-compose down
docker compose down
语法没有变化。
docker-compose ps
docker compose ps
语法没有变化。
docker-compose logs
docker compose logs
语法没有变化。
docker-compose stop
docker compose stop
语法没有变化。
docker-compose build
docker compose build
语法没有变化。
docker-compose exec web bash
docker compose exec web bash
语法没有变化。
docker-compose run web bash
docker compose run web bash
run 创建一个新的容器。exec 在已有的容器中运行。
docker-compose up --scale web=3
docker compose up --scale web=3
--scale 标志在 v2 中仍然受支持。

如你所见,对于大多数日常使用来说,唯一的变化是将 docker-compose 改为 docker compose。如果你使用脚本,可以通过简单地去掉连字符来更新它们。

如果你仍然安装了旧的 docker-compose v1,可以将其删除以避免混淆:

sudo pip3 uninstall docker-compose

或者,如果它是通过 apt 安装的:

sudo apt remove docker-compose

常见 Docker Compose 问题排查

当你使用 Docker Compose 时,可能会遇到与文件语法、权限或容器运行时冲突相关的问题。大多数问题可以通过系统地检查你的配置、权限和容器日志来诊断和解决。

让我们来看一些最常见的错误及其解决方法。

权限被拒绝(Docker 套接字)

这是新手 Docker 用户最常遇到的错误之一。你运行 docker compose up 时,会看到有关 Docker 守护进程套接字的错误消息。

permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock
  • 问题:您的非 root 用户没有权限与作为 root 运行的 Docker 守护进程通信。

  • 解决方案: 您必须将您的用户添加到在 Docker 安装过程中创建的 docker 组中。

    1. 将您的用户添加到 docker 组:

      sudo usermod -aG docker ${USER}
    2. 要使新的组成员身份生效,您必须先退出登录,然后重新登录。

    3. 或者,您可以通过输入以下命令在当前终端会话中激活组更改:

      newgrp docker

    这应该可以解决与 Docker 套接字相关的任何权限错误。

YAML 语法和缩进错误

Docker Compose 无法运行,并报告您的 docker-compose.yml 文件无效。

ERROR: The Compose file './docker-compose.yml' is invalid because:
services.web.ports contains an invalid type, it should be a list

或者:

mapping values are not allowed in this context at line 5
  • 问题: docker-compose.yml 文件依赖严格的 YAML 语法。最常见的错误是缩进不正确。YAML 使用空格而不是制表符来定义结构。
  • 解决方案:仔细检查你的docker-compose.yml文件的缩进。

    • 标准是每个缩进级别使用两个空格。
    • 确保同一级别的所有键(如 version、services、volumes)在同一列开始。
    • 确保所有子键(例如服务下的 image、ports、volumes)比其父服务(例如 web)缩进多两个空格。
    • 使用带有 YAML 检查器的代码编辑器自动突出显示语法错误。

端口冲突

你尝试启动你的环境,但命令失败,并出现错误信息,提示地址已被占用。

Error starting userland proxy: listen tcp 0.0.0.0:8000: bind: address already in use
  • 问题: 您主机上的另一个进程已经在监听您尝试映射的端口(在此例中为 8000)。这通常是另一个 Docker 容器或本地开发服务器。

  • 解决方案: 你有两个选择:

    1. 停止其他进程。你可以使用以下命令找到使用该端口的进程:

      sudo lsof -i :8000

      如果是另一个 Docker 容器,请使用 docker stop <container_id> 停止它。

    2. 在你的 docker-compose.yml 文件中更改主机端口。这通常是最简单的解决方法。将 ports 映射从 "8000:80" 改为其他端口,例如 "8001:80"。

      ...
      ports:
        - "8001:80" # Changed from 8000
      ...
    3. docker-compose.yml

服务失败,出现 连接被拒绝 (depends_on)

在多服务应用程序中(例如 WordPress 和 MySQL 的示例),wordpress 容器启动,但其日志显示在尝试连接 db 服务时出现 Connection refused 或 MySQL server has gone away。

  • 问题:您使用了 depends_on,但该指令仅等待 db 容器启动。它不会等待容器内的 MySQL 应用程序 完全初始化并准备好接受连接。

  • 解决方案:应用程序(在此情况下为 WordPress)必须配置为重试连接。大多数现代镜像内置了此重试机制。对于没有内置机制的镜像,您必须实现健康检查。

    你可以为你的 db 服务添加一个 healthcheck。然后可以将 wordpress 服务的 depends_on 配置为等待数据库“健康”,而不仅仅是“启动”。

MySQL 示例健康检查:

services:
  db:
    image: mysql:8.0
    ...
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p<^your_root_password_here<^>"]
      interval: 10s
      timeout: 5s
      retries: 5

  wordpress:
    image: wordpress:latest
    ...
    depends_on:
      db:
        condition: service_healthy # This now waits for the healthcheck
    ...

卷和绑定挂载的错误

你可能会在容器日志中遇到权限错误,或者发现容器的数据目录为空。

文件/目录权限

  • 问题:容器以特定用户运行(例如,www-data,用户 ID 为 33),但你挂载的主机目录由你的用户拥有(例如,ubuntu,用户 ID 为 1000)。容器的用户没有权限写入主机目录。

  • 解决方案: 将主机目录的所有权更改为与容器内的用户 ID 匹配。您可以通过运行 docker compose exec <service_name> id 来查找容器的用户 ID。例如,如果 ID 是 33(www-data 常用的 ID):

    sudo chown -R 33:33 ./wp_content

相对路径问题

  • 问题:你的 docker-compose.yml 使用了像 ./app 这样的相对路径,但你的 index.html 文件没有被提供服务。

  • 解决方案: Docker Compose 会从你运行 docker compose up 命令的目录解析相对路径。始终从包含你的 docker-compose.yml 文件的同一目录运行 Compose 命令。

网络和服务发现

您的应用程序容器日志显示 未找到主机 或 无法解析主机: db。

  • 问题:您的 wordpress 容器无法在网络上找到 db 容器。
  • 解决方案:默认情况下,Docker Compose 会创建一个网络,并将所有服务连接到该网络。服务可以使用它们的服务名称相互查找。

    1. 检查服务名称:确保您应用程序配置中的主机名(例如 WORDPRESS_DB_HOST 环境变量)与 docker-compose.yml 中的服务名称(例如 db)完全相同。

    2. 检查网络:使用 docker compose ps 来查找你的项目名称(例如,compose-demo)。然后,检查默认网络:

      docker network inspect compose-demo_default

      JSON 输出将列出所有连接到此网络的容器。如果您的某个服务丢失,请检查您的 docker-compose.yml 中是否存在可能将其隔离的自定义 networks 配置。

命令不匹配(v1 与 v2)

你输入 docker-compose up(带连字符)时,会看到“找不到命令”的错误。

docker-compose: command not found
  • 问题: 您正在尝试使用适用于 Compose v1 的命令,该工具是已弃用的 Python 工具。本指南安装的是 Compose v2,它是直接集成到 Docker CLI 中的插件。
  • 解决方案:通过将连字符 (-) 替换为空格来使用正确的命令。
docker-compose(v1)docker compose(v2)
docker-compose updocker compose up
docker-compose downdocker compose down
docker-compose psdocker compose ps
docker-compose 日志docker compose 日志

在遵循本指南时,始终使用 docker compose(请注意空格)。

常见问题

1. Docker Compose 用于什么?

Docker Compose 是一个用于定义和运行多容器 Docker 应用程序的工具。

它使用一个单一的 YAML 文件(默认情况下为 docker-compose.yml)来配置应用程序的所有组件,这些组件被称为 服务。该文件还定义了允许服务相互通信的 网络 和用于持久数据的 卷。

通过这个单一文件,你可以使用简单的命令管理整个应用程序堆栈:

  • docker compose up 启动并运行你的整个应用程序,包括所有指定的容器、网络和数据卷。
  • docker compose down 会停止并移除由你的应用创建的所有容器、网络和卷。

它最适用于管理需要多个组件的应用程序,例如需要 Web 服务器(如 Nginx)、应用程序后端(如 WordPress)和数据库(如 MySQL)的网页。

2. 我该如何在 Ubuntu 上安装 Docker Compose?

推荐的方法是将 Docker Compose 安装为 Docker CLI 的插件。这可以通过从 Docker 官方的 apt 仓库安装 docker-compose-plugin 软件包来完成。

  1. 首先,确保您已按照官方 Docker 文档在您的 Ubuntu 系统上设置 Docker 的 apt 仓库。

  2. 更新您的软件包列表:

    sudo apt update
  3. 安装 Docker Compose 插件:

    sudo apt install docker-compose-plugin
  4. 通过检查版本来验证安装。命令使用空格,而不是连字符。

    docker compose version

3. 我如何将 Docker Compose 更新到最新版本?

由于 Docker Compose(v2)是作为系统包使用 apt 安装的,因此您可以通过标准的 Ubuntu 软件更新流程来更新它。

  1. 刷新本地软件包索引:

    sudo apt update
  2. 运行系统范围的升级,其中将包括 Compose 插件:

    sudo apt upgrade

    或者,如果你只想更新插件本身,你可以运行:

    sudo apt install --only-upgrade docker-compose-plugin

4. Docker Compose 可以在生产环境中使用吗?

是的,Docker Compose 经常用于生产环境,特别是用于在单台主机上运行的应用程序。

它提供了一种直接的方法来定义、部署和管理应用程序的服务、网络和数据卷的生命周期。对于单服务器部署来说,这是一个非常有效且易于管理的解决方案。

对于需要在多个主机(集群)之间协调容器的更复杂场景,其他工具更为常用。这些工具包括Docker Swarm(使用类似的 Compose 文件语法)和Kubernetes(它是大规模容器编排的行业标准)。

5. Docker和Docker Compose有什么区别?

本表概述了 Docker CLI 和 Docker Compose CLI 之间的主要区别。

功能docker(Docker 引擎)docker compose(Compose 插件)
范围管理单个Docker对象。将完整的多容器应用作为一个单元进行管理。
主要用途构建镜像(docker build)、运行单个容器(docker run)、管理容器(docker ps)、镜像、卷和网络。编排在 docker-compose.yml 文件中定义的多个服务。
命令专注于单一对象的低级命令。例如:docker run -d -p 80:80 -v ./:/app nginx针对整个应用程序的高级命令。例如:docker compose up
类比一个 构建模块(一个单独的容器)。整个建筑(申请)的蓝图和施工经理。

简而言之,你使用 docker 与单个容器进行交互。你使用 docker compose 来一次性管理整个应用程序堆栈(例如,网页、应用、数据库)。

6. 我如何使用 Docker Compose 运行多个容器?

你可以通过在 docker-compose.yml 文件中将它们定义为 服务 来运行多个容器。

  1. 创建一个名为 docker-compose.yml 的文件。

  2. 在此文件中,使用 services: 键来定义您想要运行的每个容器。

    这里有一个实际的例子,它定义并运行了两个容器:一个 WordPress 网站和一个 MySQL 数据库。

    services:
      db:
        # This is the first service (container)
        image: mysql:8.0
        environment:
          MYSQL_ROOT_PASSWORD: <^your_password_here<^>
          MYSQL_DATABASE: wordpress
        volumes:
          - db_data:/var/lib/mysql
    
      wordpress:
        # This is the second service (container)
        image: wordpress:latest
        ports:
          - "8001:80"
        environment:
          WORDPRESS_DB_HOST: db
          WORDPRESS_DB_PASSWORD: <^your_password_here<^>
        depends_on:
          - db
    
    volumes:
      db_data:
  3. docker-compose.yml

  4. 保存文件并在相同的目录下打开终端。

  5. 运行一个命令即可启动两个容器:

    docker compose up -d

Docker Compose 将读取该文件,为各个服务创建一个共享网络,拉取 mysql 和 wordpress 镜像,并为每个服务启动一个容器。

7. 为什么在开发中使用 Docker Compose?

Docker Compose 是开发环境中的标准工具,因为它解决了几个常见问题。

  • 一致的环境: 它确保团队中的每个开发者运行完全相同的服务(数据库、缓存、Web 服务器),并且使用完全相同的版本和配置。这在 docker-compose.yml 文件中定义,并提交到版本控制中。这消除了“在我机器上可以运行”的问题。

  • 简单性: 它替代了复杂的设置脚本和冗长的 docker run 命令。开发者只需运行 docker compose up 即可启动整个应用栈,运行 docker compose down 即可停止它。

  • 服务隔离:开发人员可以在同一台机器上处理多个项目而不会产生依赖冲突。项目 A 可以使用 PostgreSQL 9.6,而项目 B 可以使用 PostgreSQL 14,因为每个数据库都运行在由各自 Compose 文件管理的隔离容器中。

  • 轻松的集成测试:由于 Compose 会同时启动应用程序的所有依赖项,因此它为运行集成测试提供了一个完美的本地环境,以验证各个服务之间的交互。

结论

在本指南中,您安装了 Docker Compose 并配置了一个完整的多容器应用程序。您从一个用于 Nginx 网络服务器的基本 docker-compose.yml 文件开始,逐步发展到一个更复杂、现实的栈,包括 WordPress 应用程序和 MySQL 数据库。您已经学习了如何管理整个应用程序生命周期,从构建和运行服务到停止和删除它们。

现在,您已经熟悉了有效管理应用程序的关键概念,包括服务扩展、自定义网络定义,以及使用 include 指令将配置拆分为模块化文件。通过遵循迁移指南和故障排除步骤,您还可以解决常见问题,如端口冲突、“权限被拒绝”错误以及 YAML 语法错误。本节介绍的技能将使您能够构建一致、可复现的开发环境,并自信地部署单主机应用程序。

要获取所有可用的 docker compose 命令的完整参考,请查看官方文档。

有关更多 Docker 相关的教程,请查看以下文章:

  • 如何在 Ubuntu 上安装 Docker – 分步指南
  • 如何使用 Docker Compose 安装 WordPress

感谢您与 Linux-Console.net 社区一起学习。