Skip to content

如何使用 Docker Swarm 部署服务集群

Posted on:2023-08-24 at 14:47

我们的系统属于中小规模,一直没有上 k8s,觉得有点大材小用。从年初我将所有服务迁移到 Docker Swarm 以来,服务一直运行很稳定,还没有出现因架构导致的重大问题,所以写篇文章记录一下。

目录

什么是 Docker Swarm

Docker Swarm 是内置在 Docker 中的容器集群编排工具,它为 Docker 补足了服务治理方面的能力,提供了路由网格、服务发现、负载均衡、动态伸缩、滚动更新等功能。

简单来说,如果你有很少几个服务运行,那么使用 Docker 容器管理应用即可。当你的服务越来越多时,就会需要一个工具来管理所有的容器,而 Docker Swarm 就是 Docker 内建的一种解决方案。

Kubernetes 已经成为事实上业界标准的今天,Docker Swarm 无论功能还是社区活跃度都无法与之匹敌,但它仍在市场上占有一席之地。根据 RightScale 最新的市场统计结果(State of Cloud Report 2023),Docker Swarm 是除了 k8s(商业或自建)以外使用率最高的容器管理平台。

RightScale - State of Cloud Report 2023

Docker Swarm 相对其他容器编排工具来说有以下几个特点

原生支持

Docker Swarm 因为内置在 Docker 中,是可以开箱即用的,安装好了 Docker 就可以立即启动 Swarm 了。

配置简单

Docker Swarm 可以通过 docker-compose.yaml 清单文件管理服务,如果你之前使用过 docker compose,那么只需要添加几行配置就可以把服务部署到 Swarm 中。

学习曲线平滑

因为 Docker Swarm 并没有跳脱出 Docker 这个框架,所以相比于学习 Kubernetes 中的一大堆专有名词和“新概念”,Docker Swarm 的学习难度是非常低的。

兼容 Standalone 容器

部署在 Docker Swarm 中的服务可以很好的和直接运行在 Docker 上的独立容器共存,所以只将一部分容器服务部署到 Swarm 中也是可以的。

快速部署

初始化集群

Docker Swarm 集群中的主机称为节点 Node,他们有两种角色 Manager 管理节点和 Worker 工作节点,管理节点负责将工作负载分发到工作节点上,并且一个集群中可以有多个 Manager 和多个 Worker,它们的关系如下图

Docker Swarm 中的节点关系

使用下面的命令初始化一个 Manager 节点

Terminal window
$ docker swarm init

执行成功后会看到这样一段输出:

Swarm initialized: current node (dxn1zxxxxx) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join \
--token SWMTKN-1-49njxxxxx \
192.168.99.100:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

把它提示的这行命令在同一个局域网下的其他主机上运行,即可作为 Worker 节点加入到集群中

Terminal window
$ docker swarm join \
--token SWMTKN-1-49njxxxxx \
192.168.99.100:2377

使用 node ls 命令可以查看集群中节点概况

Terminal window
$ docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
9j68exjopxe7wfl6yuxml7a7j worker1 Ready Active 20.10.5
1vyu1dscvcmnoaylch05u7xdd * manager Ready Active Leader 20.10.5

部署服务

使用 docker service 命令可以管理 Swarm 服务,在管理节点上执行下面的命令启动一个 Nginx 服务

Terminal window
$ docker service create --name my_web --replicas 3 -p 8080:80 nginx

这样我们就创建了一个具有 3 个实例(有 3 个容器运行)的 nginx 服务,并且开放了容器的 80 端口到所有节点8080 端口上,此时我们访问任何一个节点的 8080 端口,都可以访问到 nginx 服务。

访问本地的 8080 端口
$ curl http://127.0.0.1:8080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
</html>

使用 service ls 命令查看服务的概况

Terminal window
$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
od2qgvi81qf5 my_web replicated 3/3 nginx:latest *:8080->80/tcp

使用 service ps 命令查看服务的具体状态

Terminal window
$ docker service ps my_web
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
iqr97uub64o7 my_web.1 nginx:latest worker1 Running Running 1 minutes ago
h3doosl9n0bx my_web.2 nginx:latest manager Running Running 1 minutes ago
bkozkbce1wg8 my_web.3 nginx:latest worker1 Running Running 1 minutes ago

可以看到这个服务有 3 个实例,在 Swarm 中这些实例叫做 Task 任务,管理节点会将任务分配到不同的节点上执行,上面的 NODE 列显示的就是任务由哪个节点负责。

Task、Service和Node之间的关系

服务发现

当我们访问节点的 8080 端口时,我们实际上连接到了三个实例的其中一个,Swarm 帮我们将请求路由到负载较小的实例上。

具体来说,Swarm 默认的服务发现是通过 vip (虚拟 IP)实现的,它根据节点上的连接数量判断节点负载来实现负载均衡,这使客户端的多次请求可能会被不同的实例接收。关于 Swarm 服务发现的更多细节可以在这里找到。

服务日志

使用 service logs 命令可以查看服务的日志。服务上多个实例的日志会合并到一起。

Terminal window
$ docker service logs my_web
my_web.2.h3doosl9n0bx@manager | /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
my_web.2.h3doosl9n0bx@manager | /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
my_web.2.h3doosl9n0bx@manager | /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
my_web.2.h3doosl9n0bx@manager | 10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
my_web.2.h3doosl9n0bx@manager | 10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
my_web.1.iqr97uub64o7@worker1 | /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
my_web.1.iqr97uub64o7@worker1 | /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
my_web.1.iqr97uub64o7@worker1 | /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
my_web.1.iqr97uub64o7@worker1 | 10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
my_web.1.iqr97uub64o7@worker1 | 10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
...

服务堆

只部署一两个服务时使用 docker service 命令很方便,一旦系统中的服务越来越多,我们就需要一个工具来管理服务了。

docker stack 就是 Swarm 用来批量管理服务的工具,docker stack 之于 service 正如 docker compose 之于 container。

使用 compose 文件管理服务

通过命令管理服务的方式不仅效率低,也不利于批量管理。docker stack 采用 compose 文件管理集群配置,例如下面的文件定义了 nginx 服务的各项细节

docker-compose.yaml
version: "3.8"
services:
another_web:
image: nginx
ports:
- 8080:80
deploy:
mode: replicated
replicas: 3

在这个文件所在目录下,执行下面的命令就可以启动这个 stack

Terminal window
$ docker stack deploy -c docker-compose.yml my-stack

使用 stack ls 命令查看 stack 的概况

Terminal window
$ docker stack ls
NAME SERVICES ORCHESTRATOR
my-stack 1 Swarm

使用 stack ps 命令查看这个 stack 的详细信息

Terminal window
$ docker stack ps guet-server-stack
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
yjkp59sf2ask my-stack_another_web.1 nginx:latest worker1 Running Running 1 minutes ago
hcee8c0agbaw my-stack_another_web.2 nginx:latest manager Running Running 1 minutes ago
j0dax7si8fy4 my-stack_another_web.3 nginx:latest worker1 Running Running 1 minutes ago

一个 compose 文件中可以定义多个服务,例如下面这个 WordPress 服务

version: "3.8"
services:
wordpress:
image: wordpress
ports:
- 8080:80
networks:
- overlay
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
deploy:
mode: replicated
replicas: 3
db:
image: mysql:8.0
networks:
- overlay
volumes:
- db-data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: somewordpress
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
deploy:
mode: global
volumes:
db-data:
networks:
overlay:

这个文件定义了一个具有 3 个实例的 wordpress 服务和一个全局唯一实例的 mysql 服务。它们共同加入 overlay 这个网络,所以 wordpress 服务可以通过 db:3306 地址访问到数据库。

服务升级

通过命令升级

对于使用 service create 创建的服务,我们可以通过 service update 命令升级

Terminal window
$ docker service update --image nginx:1.25.3-alpine nginx

--image 参数指定了服务升级的镜像,还可以通过 --env-add --publish-add 等参数来更新环境变量、端口等。

其他参数的详细用法可以通过 docker service update -h 查看。

升级 stack 中的服务

修改 compose 文件中的 image 字段,然后重新执行 docker stack deploy 命令即可。

滚动升级

通过指定 compose 文件中的 deploy.update_config 配置,可以控制滚动更新的行为

deploy:
update_config:
parallelism: 2 # 每次更新 2 个实例
delay: 10s # 每组容器更新后等待 10 秒
order: stop-first # 操作的执行顺序

配置的详细说明

服务回滚

使用 stack 管理服务时一般不需要手动回滚,因为滚动更新配置的 failure_action 可以自动回滚有问题的服务。

也可以使用 service rollback 命令手动回滚服务

Terminal window
$ docker service rollback my-service

回滚时的具体行为可以通过 compose 文件的 deploy.rollback_config 配置

deploy:
rollback_config:
parallelism: 2 # 每次回滚 2 个实例
delay: 10s # 每组容器回滚后等待 10 秒
order: stop-first # 操作的执行顺序

回滚配置和 update_config 相同,只有 failure_action 字段不可以指定为 rollback

服务扩缩容

使用 stack 管理服务时,直接修改 compose 文件的 replicas 数量然后执行 docker stack deploy 即可扩缩容。

使用 service create 创建的服务可以通过 service scale 对服务进行扩缩容

Terminal window
$ docker service scale my-service=10