1. 简介

Docker 容器是一个自包含的服务单元,它将应用程序代码及其所有依赖打包在一起。这些轻量级环境将应用进程与底层系统以及其他容器隔离开来。

在这一基础上,Docker Compose 和 Docker Swarm 是用于管理多容器应用的工具。Docker Compose 允许我们通过一个配置文件部署多容器应用,而 Docker Swarm 则可以在多台机器上协调这些容器以实现横向扩展。

本文将学习如何在 Docker Compose 和 Docker Swarm 之间做出选择。这两种工具在某些场景下都可能是合适的,具体取决于实际需求。

2. Docker Compose

Docker Compose 是一个用于在单个主机上定义和运行多容器应用的工具。它允许我们使用 YAML 文件描述应用的服务组成,并通过一条命令启动、停止、重建或扩展这些服务。此外,Docker Compose 避免了我们多次运行 docker run 命令来启动多个容器的繁琐操作。

虽然 Docker Compose 并不默认包含在 Docker 中,但我们可以通过多种方式安装它:

通过提供单一的配置文件和命令,Docker Compose 大大简化了在单个主机上管理多容器应用的过程。

2.1. 在单主机上运行多容器应用

为了演示,我们将在一台虚拟机上部署一个三层应用,包括数据库、应用服务和 Web 服务器。我们可以在一个名为 docker-compose.yml 的 YAML 文件中定义这些服务:

version: '3'
services:
  web:
    image: nginx
    ports:
      - "80"
    volumes:
      - var/www/html
  app:
    image: tomcat
    ports:
      - "8080"
  db:
    image: redis
    ports:
      - "9080"

docker-compose.yml 文件定义了一个多容器应用的配置,使用了 YAML 语法的第 3 版本。

该文件的核心是 services 部分,它将应用拆分为多个服务,每个服务都在各自的容器中运行。

第一个服务 web 使用名为 nginx 的预构建 Docker 镜像运行一个 Web 服务器image 关键字指定该镜像。ports 部分将容器端口(如 80)映射到主机端口(也设为 80),这样我们就可以通过浏览器访问 Web 服务。

另一个名为 app 的服务使用 tomcat 镜像运行应用服务器。和 Web 服务一样,它也有端口映射,暴露了主机的 8080 端口。

最后一个服务 db 是基于 Redis 镜像的数据库容器。它也有端口映射,允许外部连接数据库。

总的来说,这个 YAML 文件创建了一个包含三个相互连接服务的环境,分别运行在不同的 Docker 容器中。

我们可以使用以下命令创建并启动这些服务:

$ docker-compose -f docker-compose.yml up -d

该命令指示 Docker Compose 启动 docker-compose.yml 文件中定义的多容器应用。

执行 docker ps 可以确认服务是否成功创建:

$ docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED        STATUS         PORTS                                                   NAMES
d232f55ebe4b   nginx     "/docker-entrypoint.…"   29 hours ago   Up 5 seconds   0.0.0.0:32768->80/tcp, :::32768->80/tcp                 root-web-1
2578203054e8   tomcat    "catalina.sh run"        29 hours ago   Up 5 seconds   0.0.0.0:32769->8080/tcp, :::32769->8080/tcp             root-app-1
0425c5dd931c   redis     "docker-entrypoint.s…"   29 hours ago   Up 5 seconds   6379/tcp, 0.0.0.0:32770->9080/tcp, :::32770->9080/tcp   root-db-1

输出确认了我们在不同的容器中成功启动了各个服务。

2.2. Docker Compose 的优势

Docker Compose 有以下优势:

集中式配置:我们可以在一个 YAML 文件中定义和管理应用服务(容器),无需逐个启动容器。
提升开发效率:通过单条命令快速启动整个环境,简化迭代流程,利用缓存配置加快重启速度。
支持变量:Docker Compose 文件支持变量,使得应用可以在不同环境(开发、测试、生产)或用户之间灵活定制,便于移植。

总之,Docker Compose 使得在单个主机上定义和管理多容器应用变得非常方便

2.3. Docker Compose 的局限性

尽管 Docker Compose 是定义和管理多容器应用的强大工具,但它无法有效扩展应用或处理高并发流量

虽然早期版本支持使用 scale 子命令进行服务扩展,但现在该功能已被弃用。现在只能通过 --scale 选项进行服务扩展

$ docker-compose up --scale <service>=<number_of_containers>

例如,我们可以增加 webappdb 服务的容器数量:

$ docker-compose up --scale web=2 --scale app=2 --scale db=3
WARN[0000] /root/compose.yml: `version` is obsolete     
[+] Running 7/7
 ✔ Container root-app-1  Running                                                                                                 0.0s 
 ✔ Container root-app-2  Created                                                                                                 0.2s 
 ✔ Container root-db-1   Running                                                                                                 0.0s 
 ✔ Container root-db-3   Created                                                                                                 0.2s 
 ✔ Container root-web-1  Running                                                                                                 0.0s 
 ✔ Container root-web-2  Created                                                                                                 0.2s 
 ✔ Container root-db-2   Created    

接着执行 docker ps 查看容器数量是否增加:

$ docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED              STATUS         PORTS                                                   NAMES
db815ce62839   redis     "docker-entrypoint.s…"   About a minute ago   Up 7 seconds   6379/tcp, 0.0.0.0:32781->9080/tcp, :::32781->9080/tcp   root-db-3
311836c1dc60   tomcat    "catalina.sh run"        About a minute ago   Up 8 seconds   0.0.0.0:32779->8080/tcp, :::32779->8080/tcp             root-app-2
ed3311900d92   nginx     "/docker-entrypoint.…"   About a minute ago   Up 7 seconds   0.0.0.0:32777->80/tcp, :::32777->80/tcp                 root-web-2
1587b10d532c   redis     "docker-entrypoint.s…"   About a minute ago   Up 8 seconds   6379/tcp, 0.0.0.0:32775->9080/tcp, :::32775->9080/tcp   root-db-2
d232f55ebe4b   nginx     "/docker-entrypoint.…"   30 hours ago         Up 8 seconds   0.0.0.0:32776->80/tcp, :::32776->80/tcp                 root-web-1
2578203054e8   tomcat    "catalina.sh run"        30 hours ago         Up 8 seconds   0.0.0.0:32778->8080/tcp, :::32778->8080/tcp             root-app-1
0425c5dd931c   redis     "docker-entrypoint.s…"   30 hours ago         Up 7 seconds   6379/tcp, 0.0.0.0:32780->9080/tcp, :::32780->9080/tcp   root-db-1

虽然看起来扩展成功,但 --scale 选项只能执行一次。因此,使用 Docker Compose 扩展服务时必须调整所有服务,否则在扩展某个服务后再运行 --scale 会将未指定服务的副本数重置为 1。

另外,Docker Compose 的 scale 命令不适用于有状态服务(如数据库),因为每个容器都有自己的状态,容易引发一致性问题。

另一个扩展时的常见问题是,新容器会被分配随机端口号,容易引发端口冲突。虽然可以通过额外的 YAML 配置解决,但过程较为复杂。

3. Docker Swarm

Docker Swarm 是一个容器编排工具,可以管理并扩展已有的容器。它会创建一个由多个 Docker Engine 组成的集群,称为 Swarm。Swarm 由多个节点组成,这些节点是运行 Docker 的物理或虚拟机

Docker Swarm 中主要有两种节点类型:

  • Manager 节点:一个或多个(用于冗余)节点,负责调度任务(Docker 容器及其内部命令)给 Worker 节点,并确保一切正常运行。
  • Worker 节点:运行由 Manager 分配的任务,在容器中处理负载。

因此,Docker Swarm 通过这种层级结构高效地在集群中分发任务。

3.1. 在多主机上扩展服务

Docker Swarm 可以在多个主机上编排容器。为了演示,我们准备两台服务器:一台作为 Manager 节点,另一台作为 Worker 节点。

首先,在 Manager 节点上初始化 Docker Swarm:

$ docker swarm init --advertise-addr=eth1
Swarm initialized: current node (x2kn79feoq1qgoqzf2b5popim) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-6ci5msq8d57agjee0xo942g7u9pd59yxa4jzvypz0svrnvyv3p-4og3g3bhn55kmzpxvw9i544fw 192.168.56.12:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

init 命令初始化了一个新的 Docker Swarm 集群,并将当前服务器设为该 Swarm 的主 Manager 节点

如输出所示,生成了一个用于 Worker 加入的 token。Worker 节点执行如下命令加入集群:

$ docker swarm join --token SWMTKN-1-6ci5msq8d57agjee0xo942g7u9pd59yxa4jzvypz0svrnvyv3p-4og3g3bhn55kmzpxvw9i544fw 192.168.56.12:2377
This node joined a swarm as a worker.

在 Manager 节点上执行以下命令验证 Worker 是否已加入集群:

$ docker node ls
ID                            HOSTNAME       STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
szuxj4p9t3qff8cde7aq8o8x5     worker          Ready     Active                          26.1.4
x2kn79feoq1qgoqzf2b5popim *   manager         Ready     Active         Leader           26.1.4

现在 Swarm 集群已就绪。我们创建一个名为 svc1 的服务,运行 6 个 nginx 容器副本:

$ docker service create --name svc1 --replicas 6 -p 1234:80 nginx
lkxzy5607gxoe3db036vk8x82

确认服务是否创建成功:

$ docker service ls
ID             NAME      MODE         REPLICAS   IMAGE          PORTS
lkxzy5607gxoe   svc1      replicated   6/6        nginx:latest   *:1234->80/tcp

使用 Docker Swarm,副本会分布在 Manager 和 Worker 节点上。可以通过以下命令查看 svc1 服务中正在运行或停止的容器:

$ docker service ps svc1
ID             NAME         IMAGE          NODE          DESIRED STATE   CURRENT STATE            ERROR        PORTS
x8zjcj6ap6lq   svc1.1       nginx:latest   manager       Running         Running 3 minutes ago
5akgfkqfn6dc   svc1.2       nginx:latest   worker        Running         Running 4 minutes ago
otgcqnyr6xtm   svc1.3       nginx:latest   worker        Running         Running 4 minutes ago
yoube1prqrqf   svc1.4       nginx:latest   manager       Running         Running 3 minutes ago
1j7th6s4pbur   svc1.5       nginx:latest   manager       Running         Running 3 minutes ago
3mqps5vqf9ki   svc1.6       nginx:latest   worker        Running         Running 4 minutes ago

输出显示容器分布在 Manager 和 Worker 节点上运行。

3.2. Docker Swarm 的优势

Docker Swarm 的一个关键优势是其自愈能力。这意味着 Swarm 可以检测并恢复容器化应用中的故障。

它通过内置的健康检查机制持续监控容器健康状态。当健康检查发现某个容器异常时,Swarm 会自动在相同节点上重启容器,或将其调度到集群中健康的节点上。

我们来演示一下这个功能:删除 Manager 节点上的所有容器

$ docker rm -f $(docker ps -qa)

执行后,Docker Swarm 会立即检测到部分容器缺失,并自动创建新的容器。我们可以通过以下命令验证:

$ docker service ps svc1
ID             NAME         IMAGE          NODE                    DESIRED STATE   CURRENT STATE             ERROR                           PORTS
o3s3vk2q3u4d   svc1.1       nginx:latest   localhost.localdomain   Running         Running 26 seconds ago                                    
x8zjcj6ap6lq    \_ svc1.1   nginx:latest   localhost.localdomain   Shutdown        Failed 34 seconds ago     "task: non-zero exit (137)"       
5akgfkqfn6dc   svc1.2       nginx:latest   localhost.localdomain   Running         Running 38 minutes ago                                      
otgcqnyr6xtm   svc1.3       nginx:latest   localhost.localdomain   Running         Running 38 minutes ago                                       
xju28oe60jlf   svc1.4       nginx:latest   localhost.localdomain   Running         Running 26 seconds ago                                    
yoube1prqrqf    \_ svc1.4   nginx:latest   localhost.localdomain   Shutdown        Failed 33 seconds ago     "task: non-zero exit (137)"        
hsph97tdm7dg   svc1.5       nginx:latest   localhost.localdomain   Running         Running 26 seconds ago                                    
1j7th6s4pbur    \_ svc1.5   nginx:latest   localhost.localdomain   Shutdown        Failed 33 seconds ago     "task: non-zero exit (137)"        
3mqps5vqf9ki   svc1.6       nginx:latest   localhost.localdomain   Running         Running 38 minutes ago                                    

从输出中可以看到,被删除的容器处于 Shutdown 状态,并报错 *task: non-zero exit (137)*。Docker Swarm 随即创建了新的容器来替代它们。

其次,Docker Swarm 还可以动态扩展服务中的副本数量。例如,我们将 svc1 的副本数从 6 个扩展到 8 个

$ docker service scale svc1=8
svc1 scaled to 8
overall progress: 8 out of 8 tasks 
1/8: running   [==================================================>] 
2/8: running   [==================================================>] 
3/8: running   [==================================================>] 
4/8: running   [==================================================>] 
5/8: running   [==================================================>] 
6/8: running   [==================================================>] 
7/8: running   [==================================================>] 
8/8: running   [==================================================>] 
verify: Service svc1 converged 

这表明 Docker Swarm 支持真正的动态扩展,而非静态配置。

最后,Docker Swarm 在网络方面也具备优势:Ingress 覆盖网络用户自定义覆盖网络。集群初始化时,Swarm 会自动创建一个名为 ingress 的覆盖网络,用于集群内部的服务发现和负载均衡。

在创建服务时,我们将容器的 80 端口映射到主机的 1234 端口,因此 ingress 网络负责将流量分发到所有容器的 80 端口。

3.3. Docker Swarm 的局限性

虽然 Docker Swarm 是一个强大的容器编排工具,但也有一些局限性需要注意:

功能有限:相比 Kubernetes 等平台,Swarm 的功能较为基础,可能不适合复杂部署。
依赖 Docker API:这限制了一些定制化能力。
节点数量限制:Swarm 最多只支持 7 个 Manager 节点,不适合大规模环境。
监控能力有限:内置监控工具较为基础,仅依赖 Docker 的事件和日志功能,需借助第三方工具增强监控。

虽然 Docker Swarm 适合构建可扩展的环境,但上述限制在某些场景下可能成为瓶颈

4. 如何选择合适的工具

我们已经了解了 Docker Compose 和 Docker Swarm 的特点,下面是它们的主要区别总结,帮助我们根据项目需求做出决策:

特性 Docker Compose Docker Swarm
功能定位 单主机多容器应用部署 多节点容器编排
部署范围 单主机 多节点集群
主要用途 开发、测试环境快速搭建 生产环境高可用、负载均衡
配置方式 YAML 文件 CLI 命令
扩展性 有限,不支持动态扩展 支持动态扩展
自愈能力 ❌ 无 ✅ 有
适用场景 本地开发、小型应用 大规模服务、生产部署

根据上表,我们可以更快速地判断哪种工具更适合我们的容器编排需求。

5. 总结

本文对比了 Docker Compose 和 Docker Swarm 的功能、优势、适用场景和局限性。

总结来说,没有哪个工具是绝对优于另一个的,它们各自适用于不同的场景和环境

  • 如果你只需要在单台服务器上运行多容器应用,Docker Compose 是首选。
  • 如果你需要跨多台服务器部署、扩展和自愈服务,Docker Swarm 更合适。

根据实际需求选择合适的工具,才能最大化 Docker 的优势。


原始标题:Choosing Between Docker Compose and Docker Swarm