前面章节我们更多的是学习在单节点上如何通过docker来部署和管理我们的应用,在实际生产环境中一般都会为应用部署多个节点进行负载均衡,实现高可用。本章我们来了解docker集群的实现。我们还是以小需求来驱动学习的方式进行讲解。需求如下:

将上面入门篇中的快速开始章节部署的【myhelloworld】应用【lazy-study-docker-0.0.1-SNAPSHOT.jar】部署3个节点,实现高可用。

 

集群方式介绍

我们要实现上面的需求,有以下方式:

独立容器方式

1、准备三个节点

2、每个节点运行一个docker容器

3、前端架设软负载均衡器(Nginx)代理到三个节点上(必须)

统一资源池方式(群)

1、准备三个节点

2、将三个节点连接起来作为一个资源池(群)

3、指定每个节点角色(群管理者、工作者)

4、将服务提交给群管理者节点,群内部根据每个节点资源使用情况分配运行节点

5、前端架设软负载均衡器(可选)

上面两种方式优劣势比较

独立容器方式缺点:

1、资源使用和分配不灵活。需要自己规划和管理资源使用情况,根据每个节点资源情况来确定部署计划;

2、节点数量越来越多的时候,管理起来很麻烦,每个节点需要执行重复相同的命令;

3、横向扩展和收缩比较繁琐。

4、某个节点宕机,该节点的容器将不可用。

5、必须自己搭建软件负载均衡器,例如nginx apache

独立容器方式优点:

1、如果节点不多情况下可以直接查看容器所在主机的绑定日志,而不需要额外搭建ELK平台。

2、不需要学习额外的组件,例如(swarm)

统一资源池方式(群)缺点:

1、由于服务动态分配,在节点比较多的情况下我们要查看某个容器的绑定日志是很麻烦的,所以一般需要配套搭建ELK平台。

2、我们需要额外学习集群支持组件(例如docker原生支持的swarm)的学习和使用。

统一资源池方式(群)优点:

1、将节点加入群后,只需要通过集群管理器节点来管理即可,提升运维效率。

2、资源使用灵活,可配置不同服务之间的资源使用限制。

3、支持尽可能高可用,任何节点宕机,集群管理器将会为该节点的服务实例重新分配到其它存活的节点。

4、快速和方便地进行横向扩展和收缩。

5、可以不必部署nginx作为负载均衡器,因为集群可以通过覆盖网络的负载均衡特性来实现负载均衡的效果。

 

创建Swarm集群

本章我们主要讲解通过swarm技术支持来实现docker集群部署,至于独立部署方式可以根据前面章节自行改造,无非就是多部署一个节点,然后再安装个Nginx,配置好网络和nginx.conf,就可以进行集群负载均衡了。

下面我们先了解一下swarm集群架构图

 

Docker-高级篇-swarm集群_swarm

swarm有三个角色,分别是管理者(manager)、工作者(worker)和被选举者(Reachable),管理者管理工作者。一般管理者节点也是一个工作者。被选举者是当管理者宕机后可被选举为管理者的节点。

可以通过启动多个管理者进行管理者的高可用。每个节点角色可以动态更换,通过命令:

 

  •  
//将node3提升为管理者,默认也是工作者docker node promote node3//将node3降级为工作者docker node demote node3节点可以随时下线,通过命令:docker node update --availability draindocker node update --availability activedocker node update --availability pause

 

说明:

Active 表示调度程序可以将任务分配给节点。

Pause 表示调度程序不会将新任务分配给节点,但现有任务仍在运行。

Drain表示调度程序不会将新任务分配给节点。调度程序关闭所有现有任务并在可用节点上。

 

下面我们开始创建swarm集群:

1、准备3个节点(node1,node2,node3),部署集群计划如下:

 

节点

 

管理者(Manager)

 

工作者(Work)

node1

yes

yes

node2

yes

yes

node3

 

yes

 

上图可以看到,有两个节点是管理者,三个节点是工作者。其中管理者节点包含两个角色,既是管理者也是工作者。

2、在node1上初始化swarm集群

  •  
docker swarm init

 

Docker-高级篇-swarm集群_swarm_02

3、将node2、node3加入该集群

 

  •  
//查看加入集群命令docker swarm join-token worker

 

Docker-高级篇-swarm集群_swarm_03

复制stdout输出的命令,分别切换到node1/node2上执行。

Docker-高级篇-swarm集群_swarm_04

注意,该命令包含端口2377,需要开放该防火墙,笔者这里为了方便,直接关闭三个节点的防火墙:

  •  
systemctl stop firewalld

 

 

备注:需要开放端口 2377/tcp,7949/udp,4789/udp

 

  •  
firewall-cmd --zone=public --add-port=2377/tcp --permanentfirewall-cmd --zone=public --add-port=2376/tcp --permanentfirewall-cmd --zone=public --add-port=7949/udp --permanentfirewall-cmd --zone=public --add-port=4789/udp --permanentsystemctl restart firewalld

 

OK,我们已经创建好了swarm集群,没错,创建集群主要的就这几步。

4、查看集群节点信息

//节点管理器node1节点上执行

docker node ls

Docker-高级篇-swarm集群_swarm_05

可以看到这个主机的swarm集群管理者管理了三个节点,其中node1是leader,状态均为Active活动状态。注意,集群管理命令均需要在管理者节点执行,工作者节点执行会报错如下:

 

Docker-高级篇-swarm集群_swarm_06

告知你这个节点不是swarm管理者。

 

部署服务

1、创建数据卷

 

  •  
//创建数据卷docker volume create myhelloworld//检查数据卷docker volume ls 或者cd /var/lib/docker/volumes/ && ls//提交服务到集群docker service create --replicas 3 \ --name myhelloworld \ --mount type=volume,src=myhelloworld,dst=/opt/applications/helloworld/logs \ -p 8100:8100 \ registry.laizhiy.cn/myhelloworld

-replicas 3表示启动3个服务实例

其它选项参数读者应该都比较熟悉了。

2、查看部署的服务

  •  
docker service ls

 

3、查看服务动态分配的节点

  •  
docker service ps myhelloworld

 

Docker-高级篇-swarm集群_swarm_07

可以看到,运行在3个节点上,注意,(manager)管理者node1也充当工作者(worker)的角色,运行了一个服务实例,这是默认的行为。

4、查看服务信息

docker service inspect myhelloworld

5、浏览器访问

http://node1:8100/visitor

http://node2:8100/visitor

http://node3:8100/visitor

 

如果慢,多刷新几次,慢是因为链接不上redis,等待redis超时时间。

6、查看网络驱动

docker network ls

docker network inspect ingress | grep Container -A 20

 

Docker-高级篇-swarm集群_swarm_08

默认服务都进入到ingress覆盖网络中。

7、查看数据卷

  •  
docker volume ls

8、查看日志

 

  •  
docker service logs -f myhelloworld或者按平常查看容器日志方式docker psdocker logs -f containerId

直接查看数据卷

 

  •  
less /var/lib/docker/volumes/myhelloworld/_data/info.loginfo.log是应用logback定义的文件名称。

 

更新服务

1、更新服务副本数量

//查看服务现在副本数量

  •  
docker service l

Docker-高级篇-swarm集群_swarm_09

 

  •  
//扩容到5个副本
docker service update --replicas 5 myhelloworld//查看服务副本数量docker service ls

Docker-高级篇-swarm集群_swarm_10

2、查看服务运行节点信息

  •  
docker service ps myhelloworld

 

Docker-高级篇-swarm集群_swarm_11

node1和node2分别运行2个服务实例其中node3运行一个服务实例。细心读者应该想到,现在我们在同一节点运行两个相同的应用,端口不会冲突,这就是docker隔离技术带来的便利。那这两个实例在同一主机是怎么接口请求的呢?

我们下面做个试验:

a、通过docker ps找到两个容器ID

b、分别打开两个SSH窗口

c、通过docker logs -f containerId来实时接收日志输出

d、打开postmain之类的http请求工具类,用post方式请求http://node1:8100/visitor,这样可以看到报错信息【Request method 'POST' not supported】

e、疯狂请求,可以看到,本地两个实例一个输出比较多,另一个偶尔也接收到请求,进行输出。

 

结论:笔者猜想内部可能根据服务实例随机选择一个来处理一定数量的请求。

更新服务不仅仅可以更新副本数量,还可以更新网络驱动、数据卷、端口绑定、硬件资源限制、节点分配等等信息。这里笔者就不一一演示,读者在需要时可以翻阅官方文档。

https://docs.docker.com/engine/reference/commandline/service_update/

 

服务节点分布

我们通过下面命令可以知道服务动态分配到哪些节点

docker service ps myhelloworld

Docker-高级篇-swarm集群_swarm_12

这种方式不太友好,下面推荐一款支持UI界面展示服务节点分布情况的应用镜像。

直接向集群提交visualizer服务。

 

  •  
docker service create --name=viz \--publish=8089:8080/tcp \ --constraint=node.role==manager \--mount=type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock \dockersamples/visualizer:latest

 

--constraint=node.role==manager表示只能在管理者节点上执行。镜像下载慢的话,修改/etc/docker/daemon.json,内容如下:

 

  •  
{  "registry-mirrors": ["https://9cpn8tt6.mirror.aliyuncs.com"]}

更新daemon配置

 

  •  
systemctl daemon-reloadsystemctl restart docker

浏览器访问:

http://node1:8089/

就可以看到你的服务分布在哪些节点上了。还支持搜索功能

 

Docker-高级篇-swarm集群_swarm_13

 

集群和独立容器共存

单个节点参与了集群之后,独立容器还能继续部署吗?答案是“能”。我们现在就在node3部署一个myredis独立容器,步骤如下:

1、创建自定义覆盖网络

 

  •  
//在node1执行docker network create --opt encrypted --attachable -d overlay  my-overlay

 

其中--opt encrypted --attachable表示支持独立容器加入到覆盖网络中。注意,其它工作者节点也会同步创建该网络驱动。

2、更新myhelloworld服务网络

 

  •  
//添加一个覆盖网络驱动docker service update --network-add my-overlay myhelloworld//移除默认的覆盖网络驱动docker service update --network-rm ingress myhelloworld

Docker-高级篇-swarm集群_swarm_14

3、运行redis容器

  •  
docker run --rm --name myredis -d --network my-overlay  redis

注意,这里加入到的网络是my-overlay,这是因为我们创建时增加了--opt encrypted --attachable选项才能使独立容器加入到集群覆盖网络中。

我们通过访问:

http://node1:8100/visitor

http://node2:8100/visitor

http://node3:8100/visitor

这里不截图了,自己查看返回结果是不是【Hello World 你是第[1]个访客】而不再是【Hello World connection to redis error】。还记得我们之前应用application.properties配置文件中配置的redis host是myredis,它对应的是容器名称。docker在同一个网络驱动内部通过容器名称做类似dns功能。如果是覆盖网络还支持负载均衡特性。

 

继续部署其它服务

1、先停掉上面node3部署的独立容器myredis

  •  
docker stop myredis

访问任何一个:

http://node2:8100/visitor

测试停用成功。返回应该是【Hello World connection to redis error】

2、node1上提交服务

 

  •  
docker service create\ --name myredis \--network my-overlay \ redis

 

注意我们这里手工指定网络,因为我们上面对原来的myhelloword网络做了变更,这里笔者发现对ingress进行变更(--network-rm ingress)不生效,也就是说默认所有集群都会加入到ingress网络驱动中,无法删除。

另外,上面没有对redis数据目录做持久化绑定主机操作,由于这是演示,一般生产环境必须做持久化绑定的。

2、验证

http://node1:8100/visitor

http://node2:8100/visitor

http://node3:8100/visitor

你应该收到:【Hello World 你是第[1]个访客】

 

负载均衡

我们前面说过,使用swarm进行集群管理的优点其中有一个说的就是可以通过覆盖网络驱动的负载均衡特性,不需要额外部署nginx进行代理。下面我们来验证一下。

1、查看目前服务

  •  
docker service ls

Docker-高级篇-swarm集群_swarm_15

2、查看myhelloworld服务节点信息

  •  
docker service ps myhelloworld

 

Docker-高级篇-swarm集群_swarm_16

实际运行状态的有node1、node2、node3,其它均shutdown状态。

3、我们下线所有node1节点中的服务实例

 

  •  
docker node lsdocker node update --availability drain node1docker node ls

Docker-高级篇-swarm集群_swarm_17

4、查看myhelloworld服务运行节点信息

  •  
docker service ps myhelloworld

Docker-高级篇-swarm集群_swarm_18

可以看到,node1相关的实例已经shutdown,但是myhelloworld运行的实例保持3个不变,把原来node1转到了node2,现在node2运行两个实例。

5、验证

访问:

http://node1:8100/visitor

 

Docker-高级篇-swarm集群_swarm_19

node1根本就没有运行实例,但依然可以访问。我们继续把node3运行的服务实例下线掉。

 

  •  
docker node update --availability drain node3docker service ps myhelloworld

 

Docker-高级篇-swarm集群_swarm_20

 

 此时访问:

http://node3:8100/visitor

 

Docker-高级篇-swarm集群_swarm_21

可以看到,node3也可以访问。redis估计是启动失败了,因为我们的虚拟机资源有限。没有内存可用。但是我们只要测试能够访问即可。测试完成后,我们把所有节点状态改回active

 

  •  
docker node update --availability active node3docker node update --availability active node1

结论:swarm通过overlay覆盖网络驱动的DNS负载均衡特性实现负载均衡。

 

管理者高可用

按照我们前面的部署计划有两个管理角色节点,分别是node1、node2,实现高可用,具体操作很简单,直接在node1节点上执行:

 

  •  
docker node promote node2//查看服务节点docker service ls

Docker-高级篇-swarm集群_swarm_22

Reachable代表被选举者角色

 

至此,简单入门级使用swarm我们就讲解到这里,更多配置使用请阅读官方文档。