Docker-swarm(Docker集群管理平台) 部署与简单使用
Swarm是Docker公司推出的用来管理docker集群的平台,几乎全部用GO语言来完成的开发的, 它是将一群 Docker宿主机变成一个单一的虚拟主机,Swarm使用标准的Docker API接口作为其前端的访问入口,也就是各种形式的Docker 均可以直接与Swarm通信,甚至Docker本身都可以很容易的与Swarm集成,这大大方便了用户将原本基于单节点的系统移植到Swarm上,同时Swarm内置了对Docker网络插件的支持,用户也很容易的部署跨主机的容器集群服务。
基础命令
XML/HTML代码
- # 集群初始化,节点成为 manager 节点
- docker swarm init --advertise-addr=x.x.x.x
- # 集群丢失 Leader 时,强制重建集群
- docker swarm init --advertise-addr=x.x.x.x --force-new-cluster
- # 获取作为 worker 节点加入集群的命令
- docker swarm join-token worker
- # 获取作为 manager 节点加入集群的命令
- docker swarm join-token manager
- # 加入集群
- docker swarm join --token xxx x.x.x.x:xxx --advertise-addr=x.x.x.x
官方文档中有提到,Docker 会自动设置 --advertise-addr ,该参数非必填。不过根据个人经验来看,还是强烈建议显式指定该参数,尤其当 VPS 有多个网卡时。
XML/HTML代码
- #一键安装Docker+docker-compose+Portainer
- bash <(curl -sL https://iii80.com/sh/docker.sh)
- bash <(curl -sL https://iii80.com/sh/docker-cn.sh)
注:docker-cn.sh会设置国内镜像加速,docker-compose使用国内代理下载
上面脚本支持Debian/Ubuntu Centos Alpine系统,以下系统必须安装Docker,主节点可以安装Portainer,可以只管看到集群,其他节点非必要。
机器准备
我们准备三台机器以及并划分ip:
Manager: Manager: 192.168.0.100
Node1: 192.168.0.101
Node2: 192.168.0.102
1 、manager节点初始化swarm
XML/HTML代码
- docker swarm init --advertise-addr 192.168.0.100
输出:
XML/HTML代码
- docker swarm init --advertise-addr 192.168.0.100
- Swarm initialized: current node (57mvxsdn9qg2tq6aiz17et7ot) is now a manager.
- To add a worker to this swarm, run the following command:
- docker swarm join --token SWMTKN-1-544xekdg657cd57fecr4g43uxk7t5pjkvxkkcbitjnygxc0nyh-0f4o0ujh0vz4nm5gxcjh3ywi3 192.168.0.100:2377
- To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
2 、运行docker info以查看集群的当前状态:
XML/HTML代码
- root@master:~# docker info
- Client: Docker Engine - Community
- Version: 24.0.5
- Context: default
- Debug Mode: false
- Plugins:
- buildx: Docker Buildx (Docker Inc.)
- Version: v0.11.2
- Path: /usr/libexec/docker/cli-plugins/docker-buildx
- compose: Docker Compose (Docker Inc.)
- Version: v2.20.2
- Path: /usr/libexec/docker/cli-plugins/docker-compose
- Server:
- ......
- Live Restore Enabled: false
3 、查看节点状态
XML/HTML代码
- root@master:~# docker node ls
- ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
- 57mvxsdn9qg2tq6aiz17et7ot * master Ready Active Leader 24.0.5
将节点添加到swarm集群中
使用管理器节点创建群后,我们就可以添加工作节点了。
打开一个终端,然后ssh进入要运行工作节点的机器。本教程使用名称master。
在master节点运行 docker swarm init 输出生成的命令,在其他机器上执行一下命令并创建工作节点:
XML/HTML代码
- root@node1:~# docker swarm join --token SWMTKN-1-544xekdg657cd57fecr4g43uxk7t5pjkvxkkcbitjnygxc0nyh-0f4o0ujh0vz4nm5gxcjh3ywi3 192.168.0.100:2377
- This node joined a swarm as a master.
如果我们忘记了命令, 则可以在 master 节点执行以下命令来查看相关命令:
XML/HTML代码
- root@master:~# docker swarm join-token worker
- To add a worker to this swarm, run the following command:
- docker swarm join --token SWMTKN-1-544xekdg657cd57fecr4g43uxk7t5pjkvxkkcbitjnygxc0nyh-0f4o0ujh0vz4nm5gxcjh3ywi3 192.168.0.100:2377
3 、在 node2 节点重复执行 node1 上面的命令
4、在master节点运行的机器,并运行 docker node ls 命令以查看工作节点:
XML/HTML代码
- root@master:~# docker node ls
- ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
- 57mvxsdn9qg2tq6aiz17et7ot * master Ready Active Leader 24.0.5
- lqv1e6oa2hle33ff4xgxfslz3 node1 Ready Active 24.0.5
- wwb5rc9nypj1e98wwr5qw99ye node2 Ready Active 24.0.5
MANAGER列标识了集群中的管理器节点。此列node1和node2中的空状态标识为worker节点。
将服务部署到swarm中
创建集群后,我们可以将服务部署到集群中。
打开一个终端,然后ssh进入运行管理器节点的机器。使用名为master的机器。
运行以下命令:
XML/HTML代码
- docker service create --replicas 1 --name helloworld alpine ping docker.com
- docker service create命令创建服务。
- --name标志为helloworld服务命名。
- --replicas标志指定了1个运行实例的所需状态。
- 参数alpine ping docker.com将服务定义为执行命令ping docker.com的Alpine Linux容器。
运行 docker service ls 以查看正在运行的服务列表:
检查集群上的服务
我们使用ssh进入运行master节点的机器。
运行 docker service inspect --pretty <SERVICE-ID> ,以易于阅读的格式显示有关服务的详细信息。
执行以下命令查看服务的详细信息
XML/HTML代码
- docker service inspect --pretty helloworld
如果需要输出 json 格式的内容,执行命令 docker service inspect helloworld
查看服务具体运行在那个节点,我们可以执行以下命令查看:helloworld 为我们要查看的服务名称
XML/HTML代码
- docker service ps helloworld
我们可一看到 服务 helloworld 运行在节点 master 的机器中, 而服务 helloword 运行在 node1 的节点中.
helloworld 的服务运行在master的节点上,说明 master 节点也可以像 node 节点一样执行任务.
服务 helloword 运行在node1的节点上, 我们在 node1 的机器上执行命令 docker ps
XML/HTML代码
- root@node1:~# docker ps
- CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
- d3bbf47cf010 alpine:latest "ping docker.com" 14 minutes ago Up 14 minutes helloword.1.nw2qt760d0tupaqqyhodtz2ko
可以看到在 node1 的机器上正在运行 helloword 的服务容器。
扩大集群中服务部署的规模
创建好集群我们就可以使用Docker CLI来扩展服务中的容器数量。在服务中运行的容器被称为“任务”。
我们使用ssh进入master节点的机器。
运行以下命令以更改在群中运行的服务的所需状态:
将 helloworld 服务扩展为 5 个
XML/HTML代码
- docker service scale helloworld=5
执行完成后我们查看服务具体运行在那个节点
XML/HTML代码
- docker service ps helloworld
可以看到,swarm 创建了 5 个新的任务,并扩展了总共 5 个的 docker 容器实例, 并分布在不同的节点上。
删除集群上运行的服务
如果我们部署的服务不需要了, 那么我们可以从集群中删除对应的服务。
1 、运行docker service rm helloworld以删除helloworld服务。
执行完命令后我们在执行 docker service ls 查看服务,服务已经被我们删除了。
2、我们可以执行命令 docker service inspect helloworld 来查看服务的信息
执行的命令可以看出服务已经彻底被删除了。
3、当删除服务的命令执行完成后,任务容器也需要几秒钟才能清理完成,我们可以在节点上执行 docker ps 查看容器是否存在。
服务的滚动更新
在工作中我们需要经常的将我们的应用程序的新版本更新,以替换原有的老的版本的应用程序。在 Docker 的 swarm 中我们如何实现应用程序的滚动更新呢?
1 、比如我们需要部署新的一个 redis 服务到我们的swarm服务集群中。 并以10秒的更新延迟配置swarm。为了更直观的操作, 我们先部署一个较老版本的redis:6.0-alpine 服务,执行以下命令部署:
XML/HTML代码
- docker service create --replicas 3 --name redis --update-delay 10s redis:6.0-alpine
我们看到服务已经部署成功了。我们执行 docker service ls 结果显示已经将服务发布在所有的节点上了。
XML/HTML代码
- root@master:~# docker service ls
- ID NAME MODE REPLICAS IMAGE PORTS
- e2b2aoag8kr0 redis replicated 3/3 redis:6.0-alpine root
部署命令参数说明:
--replicas : 表示我们部署的服务数量是几个,--replicas 3 表示部署 3 个
--update-delay : 表示任务更新的时间延迟,我们可以将时间设置为 Ts,Tm,,Th,比如:5m10s 表示延迟 5 分钟 10 秒。
--update-parallelism 表示在更新时候的最大任务数量, 默认情况下调度程序一次只更新一个任务。
默认情况下,当单个任务的更新返回RUNNING状态时,调度程序会安排另一个任务进行更新,直到所有任务更新。如果在更新期间的任何时候任务返回FAILED,调度程序将暂停更新。我们可以使用docker service create或docker service update的--update-failure-action标志来控制行为。
2、我们查看部署的 redis 服务情况:
XML/HTML代码
- docker service inspect --pretty redis
3 、现在我将已经部署好的 redis 服务更新到一个较新的版本 redis:6.0.20-alpine,管理器根据UpdateConfig策略将更新应用于节点:
执行更新命令:docker service update --image redis:6.0.20-alpine redis
XML/HTML代码
- baroot@master:~# docker service update --image redis:6.0.20-alpine redis
- redis
- overall progress: 3 out of 3 tasks
- 1/3: running [==================================================>]
- 2/3: running [==================================================>]
- 3/3: running [==================================================>]
- verify: Service converged
我们在来查看下服务详细信息:
image 已经变成了 redis:6.0.20-alpine , 已经更新成功.
我们在节点查看容器信息:
XML/HTML代码
- baroot@node2:~# docker ps
- CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
- f036280bde79 redis:6.0.20-alpine "docker-entrypoint.s…" 2 minutes ago Up 2 minutes 6379/tcp redis.1.
- 01wlu8r8z0jxmb4c2s399a6qo
同样,容器的镜像已经变成了 redis:6.0.20-alpine
调度程序更新说明:
停止第一个任务。
为停止的任务安排更新。
启动更新任务的容器。
如果任务的更新返回RUNNING,请等待指定的延迟期,然后开始下一个任务。
如果在更新期间的任何时候,任务返回FAILED,请暂停更新。
4、 如果我们需要重启服务,可以执行重启服务的命令:
docker service update redis
XML/HTML代码
- baroot@master:~# docker service update redis
- redis
- overall progress: 3 out of 3 tasks
- 1/3: running[==================================================>]
- 2/3: running[==================================================>]
- 3/3: running[==================================================>]
- verify: Service converged
5、查看更新的信息:docker service ps redis
停止Swarm集群上的一个节点
目前我们所有的节点都 ACTIVE 的状态运行的,master可以将任务分配给任何节点,所以所有的节点都可以接收到任务。
很多时候我们需要维护应用的时候,您需要将节点设置为DRAIN可用性。DRAIN状态的节点Maser 阻止此类型的节点接收新任务。这也意味着停止在节点上运行的任务,并在具有ACTIVE可用性的节点上启动副本任务。
注意: 将节点设置为DRAIN不会从该节点中删除独立容器,例如使用docker run、docker-compose up或Docker Engine API创建的容器。节点的状态,包括DRAIN,只影响节点调度群服务工作负载的能力。
1 、我们在 master 节点的机器上查看集群中节点的可用性:
XML/HTML代码
- baroot@master:~# docker node ls
- ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
- 57mvxsdn9qg2tq6aiz17et7ot * master Ready Active Leader 24.0.5
- lqv1e6oa2hle33ff4xgxfslz3 node1 Ready Active 24.0.5
- wwb5rc9nypj1e98wwr5qw99ye node2 Ready Active 24.0.5
查看任务分配的信息:
XML/HTML代码
- baroot@master:~# docker service ps redis
- ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
- 01wlu8r8z0jx redis.1 redis:6.0.20-alpine node2 Running Running 16 minutes ago
- w630bshgl3ga \_ redis.1 redis:6.0-alpine node2 Shutdown Shutdown 16 minutes ago
- mkm0o152ns3e redis.2 redis:6.0.20-alpine master Running Running 16 minutes ago
- sqmmetgn9mqy \_ redis.2 redis:6.0-alpine master Shutdown Shutdown 16 minutes ago
- ezwkml7u8gih redis.3 redis:6.0.20-alpine node1 Running Running 16 minutes ago
- qgvbqgmcmu7i \_ redis.3 redis:6.0-alpine node1 Shutdown Shutdown 17 minutes ago
docker node update --availability drain node2 我们将 node2 节点停掉,并查看当前服务以及节点信息:
可以看到node2 的节点状态已经是 Drain 并且分配在 node2 上的服务状态已经是 Shutdown
管理器通过在具有 drain 状态的节点上结束任务,并在Active 状态的节点上创建新的任务来保持所需要的状态。
3 、启用节点的可用性
docker node update --availability active node2
节点 Node2 的状态 已经更新为 Active。
当节点设置回Active可用性时,它可以接收新任务:
在服务更新期间进行扩展
在滚动更新期间
将另一个节点设置为Drain可用性时
当任务在另一个活动节点上失败时
使用swarm模式路由网格
Docker Engine集群模式可以轻松发布服务端口,以使其可用于集群之外的资源。所有节点都参与入口路由网格。路由网格使群中的每个节点能够接受群中运行的任何服务在已发布端口上的连接,即使节点上没有运行的任务。路由网格将所有传入的请求路由到可用节点上的已发布端口到活动容器。
要在集群中使用网络,在启用群模式之前,我们需要在群节点之间打开以下端口:
Port 7946 TCP/UDP 用于容器网络发现。
Port 4789 UDP 可为容器入口网络配置。
我们还必须在集群的节点和任何需要访问端口的外部资源(如外部负载平衡器)之间打开已发布的端口。
发布服务端口
创建服务时,使用--publish标识指定要对外发布端口。target用于指定容器内的端口,而published用于指定在路由网格上绑定的端口。如果没有配置published`端口,则每个服务任务都会绑定一个随机的高编号端口。
XML/HTML代码
- docker service create --replicas 3 --name redis --publish published=63790,target=6379 --update-delay 5s redis:6.0.20-alpine
XML/HTML代码
- root@master:~# docker service create --replicas 3 --name redis --publish published=63790,target=6379 --update-delay 5s re
- dis:6.0.20-alpine
- vmmg8v0zr26os5johw7187mla
- overall progress: 3 out of 3 tasks
- 1/3: running [==================================================>]
- 2/3: running [==================================================>]
- 3/3: running [==================================================>]
- verify: Service converged
XML/HTML代码
- baroot@master:~# docker service ls
- ID NAME MODE REPLICAS IMAGE PORTS
- vmmg8v0zr26o redis replicated 3/3 redis:6.0.20-alpine *:63790->6379/tcp
我们已经看到了端口为 *:63790->6379/tcp
<PUBLISHED-PORT>是swarm提供服务的端口。如果省略它,将绑定一个随机的高编号端口。<CONTAINER-PORT>是容器侦听的端口。此参数是必需的。
例如,以下命令将nginx容器中的端口80发布到群中任何节点的端口8080:
XML/HTML代码
- docker service create \
- --name web \
- --publish published=8080,target=80 \
- --replicas 2 \
- nginx
当访问任何节点上的端口8080时,Docker会将请求路由到活动容器。在群节点本身上,端口8080实际上可能没有绑定,但路由网格知道如何路由流量并防止任何端口冲突的发生。
路由网格在已发布的端口上监听分配给节点的任何IP地址。对于外部可路由的IP地址,该端口可从主机外部获得。对于所有其他IP地址,只能从主机内部访问。
部署完成后我们在宿主机上面访问对应的地址和端口:
如果操作正常,服务正常访问。并且我们访问部署了的服务节点都可以访问到 nginx 服务。
同样对于已经部署的 service,我们也可以重新给他发布端口:
XML/HTML代码
- docker service update \
- --publish-add published=<PUBLISHED-PORT>,target=<CONTAINER-PORT> \
- <SERVICE>
您可以使用docker service inspect来查看服务的已发布端口。例如:
XML/HTML代码
- docker service inspect --format="{{json .Endpoint.Spec.Ports}}" web
- [{"Protocol":"tcp","TargetPort":80,"PublishedPort":8080}]
输出显示来自容器的<CONTAINER-PORT>(标记为TargetPort)和<PUBLISHED-PORT>(标记为PublishedPort),其中节点侦听对服务的请求。
使用上面的命令我们查看部署的 nginx web 服务:
XML/HTML代码
- root@master:~# docker service inspect --format="{{json .Endpoint.Spec.Ports}}" web
- [{"Protocol":"tcp","TargetPort":80,"PublishedPort":8080,"PublishMode":"ingress"}]
仅为TCP或仅UDP发布端口
默认情况下,当您发布端口时,它是一个TCP端口。您可以专门发布UDP端口,而不是或除了TCP端口。当您同时发布TCP和UDP端口时,如果您省略协议说明符,该端口将作为TCP端口发布。如果您使用较长的语法(推荐),请将protocol密钥设置为tcp或udp。
仅限TCP
长语法:
XML/HTML代码
- docker service create --name dns-cache \
- --publish published=53,target=53 \
- dns-cache
简短的语法:
XML/HTML代码
- docker service create --name dns-cache \
- -p 53:53 \
- dns-cache
TCP和UDP
长语法:
XML/HTML代码
- docker service create --name dns-cache \
- --publish published=53,target=53 \
- --publish published=53,target=53,protocol=udp \
- dns-cache
简短的语法:
XML/HTML代码
- docker service create --name dns-cache \
- -p 53:53 \
- -p 53:53/udp \
- dns-cache
仅限UDP
长语法:
XML/HTML代码
- docker service create --name dns-cache \
- --publish published=53,target=53,protocol=udp \
- dns-cache
简短的语法:
XML/HTML代码
- docker service create --name dns-cache \
- -p 53:53/udp \
- dns-cache
绕过路由网格
我们可以绕过路由网格,这样当访问给定节点上的绑定端口时,我们总是访问该节点上运行的服务实例。这被称为host模式。这种情况下需要注意:
如果访问未运行服务任务的节点,则该服务不会监听该端口。有可能什么都没有,或者访问到了一个完全不同的应用程序。
如果希望在每个节点上运行多个服务任务(例如,当您有5个节点但运行10个副本时),则无法指定静态目标端口。要么允许Docker分配一个随机的高编号端口(通过关闭published端口),要么通过使用全局服务而不是复制服务,或使用放置约束,确保服务仅在给定节点上运行。
要绕过路由,必须使用长--publish服务并将mode设置为host。如果省略mode或将其设置为ingress,则使用路由网格。以下命令使用host模式并绕过路由网格创建全局服务。
XML/HTML代码
- docker service create --name dns-cache \
- --publish published=53,target=53,protocol=udp,mode=host \
- --mode global \
- dns-cache
配置外部负载均衡器
我们可以为集群服务配置外部负载平衡器,要么与路由网格结合使用,要么根本不使用路由网格。
使用路由网格
我们可以配置外部负载均衡器将请求路由到集群服务。可以配置HAProxy来均衡对发布到端口8080的nginx服务的请求。
在这种情况下,端口8080必须在负载平衡器和集群中的节点之间打开。集群节点可以在代理服务器可以访问的专用网络上,但不能公开访问。
可以配置负载平衡器,以均衡集群中每个节点之间的请求,即使节点上没有安排任务。例如,我们可以在/etc/haproxy/haproxy.cfg中拥有以下HAProxy配置:
XML/HTML代码
- global
- log /dev/log local0
- log /dev/log local1 notice
- ...snip...
- # Configure HAProxy to listen on port 80
- frontend http_front
- bind *:80
- stats uri /haproxy?stats
- default_backend http_back
- # Configure HAProxy to route requests to swarm nodes on port 8080
- backend http_back
- balance roundrobin
- server master 192.168.0.100:8080 check
- server node2 192.168.0.101:8080 check
- server node3 192.168.0.102:8080 check
当访问端口80上的HAProxy负载均衡器时,它会将请求转发到群中的节点。群路由网格将请求路由到活动任务。集群调度器将任务发送到不同的节点,则无需重新配置负载均衡器。
不使用路由网格
要使用没有路由网格的外部负载均衡器,请将--endpoint-mode设置为dnsrr,而不是vip的默认值。在这种情况下,没有一个虚拟IP。相反,Docker为服务设置DNS条目,使服务名称的DNS查询返回IP地址列表,客户端直接连接到其中一个地址。