背景
凭借敏捷开发部署理念的推行,相信对于很多人来说docker这项容器技术已经并不陌生,Docker 1.12引擎发布了快两个月,新引擎中包含了许多特性。诸如: Swarm模式,容器集群的健康检查,节点的身份加密,docker Service API调用,容器启动的过滤匹配方式(constraint), docker的内建路由,以及支持在多平台系统上运行docker(MAC、Windows、AWS、AZURE),以及一些插件升级等等. 特性之多,就连Docker 自己的产品经理也表示这次的新版本可能是公司有史以来变化最大的一次产品发布。
很长一段时间里,docker在集群模式的管理上一直广受外界诟病。Docker服务自身只能在单台host上进行操作,官方并没有真正意义上的集群管理方案。直到现在1.12的出现, 引擎在多主机、多容器的集群管理上才有了进一步的改进和完善,版本自身内嵌了swarm mode集群管理模式。
本文主要是介绍一下swarm 集群管理模式的新特性,以及如何该模式下如何实现集群的搭建和服务部署。
Swarm cluster 模式新特性介绍
1. 批量创建服务
1.12引擎中多了docker service命令,和之前的docker run命令类似,但不同的是它能同时对多主机中的容器进行管理操作。下面就以1台manager节点,5台worker节点的swarm集群来阐述这些特性。
首先看下容器的创建:
$ docker network create -d overlay mynet
$ docker service create –replicas 3 –name frontend –network mynet –publish 80:80/tcp frontend_image:latest
$ docker service create –name redis –network mynet redis:latest
建立容器之前先创建一个overlay的网络,用来保证在不同主机上的容器网络互通的网络模式,后面两条命令用来在同一个名叫mynet的overlay网络里新建三个相同的web容器副本,和一个 redis副本,并且每个web容器都提供统一的端口映射关系。就像这样:
2. 强大的集群的容错性
既然是集群,当然难免会出现某几个节点故障的情况:
当三个web副本中的其中两台web节点宕机后,cluster会根据自己的服务注册发现机制,以及之前设定的值–replicas 3,在集群中剩余的空闲节点上,重新拉起两个web副本。不难看出,docker service其实不仅仅是批量启动服务这么简单,而是在集群中定义了一种状态。Cluster会持续检测服务的健康状态并维护集群的高可用性。
新节点的分布示意图如下:
3. 服务节点的可扩展性
Swarm Cluster不光只是提供了优秀的高可用性,同时也提供了节点弹性扩展的功能。当web这个容器组想动态扩展至六个节点个数时,只需执行$ docker service scale frontend=6就能立刻复制出三个新的副本出来。
眼尖的朋友可能注意到了,所有扩展出来的新web副本节点都run在原先的web节点下面,如果有需求想在每台节点上都run一个相同的副本有没有办法呢?答案也是肯定的:
$ docker service create –mode=global –name extend_frontend frontend_image:latest
一条命令分分钟搞定!
4. 调度机制
Docker1.12的调度机制也值得一提。
所谓的调度其主要功能是cluster的server端去选择在哪个服务器节点上创建并启动一个容器实例的动作。它是由一个装箱算法和过滤器组合而成。每次通过过滤器(constraint)启动容器的时候,swarm cluster 都会调用调度机制筛选出匹配约束条件的服务器,并在这上面运行容器。
还是拿刚刚那个例子来说,再加上–constraint参数,就能指定容器只run在服务器硬盘是SSD的节点上(前提是加入到cluster的节点,在启动daemon时,本身需要加上参数 --label com.example.storage=”ssd”):
$ docker service create –replicas 3 –name frontend –network mynet –publish 80:80/tcp –constraint engine.labels.com.example.storage=ssd frontend_image:lastest
搭建一个swarm集群
有了以上这些介绍,我们对swarm cluster 的一些新特性应该有了初步的了解 ,下面再看一个模拟网站rolling_update的实例,相信这也是许多平时做版本发布的devops们真正想要看到的东西。
1. 搭建一个swarm集群
准备三台机器
Node1:192.168.133.129
Node2:192.168.133.137
Node3:192.168.133.139
在构建一个swarm cluster前,需在cluster节点的防火墙上放行2377/tcp(cluster 管理端口)、7946/udp(节点间通信端口)、4789/udp(overlay 网络端口)
首先在node1上运行 $docker swarm init 去启动一台cluster manager节点,然后在任意需要添加进集群的节点上运行docker swarm join –token *** 192.168.133.129:2377 就能将节点加入到cluser(加入到集群里的节点身份可在后面自由设置成worker或manager)。现在swarm cluster的节点就像下面的图一样,箱子都准备好了,就差货物往里面装了。
通过$docker node ls能看到所有swarm节点的运行状态:
P.S.Swarm cluster的创建过程包含以下三个步骤:
-
发现Docker集群中的各个节点,收集节点状态、角色信息,并监视节点状态的变化
-
初始化内部调度(scheduler)模块
-
创建并启动API监听服务模块
一旦创建好这个cluster,就可以用命令docker service批量对集群内的容器进行操作。搭建cluster只有两步,是不是非常酷?
2. 制作一个演示用的demo镜像
镜像中存放一个python写的简单的http web服务:env.py,目的是显示容器的containerID:
from flask import Flask
import os
app = Flask(__name__)
@app.route("/")
def env():
return os.environ["HOSTNAME"]
app.run(host="0.0.0.0")
3. 用swarm mode创建service task
有了这个镜像,然后通过docker service create命令去创建一个名叫test的task:
$ docker service create --name test -p 5000:5000 demo python env.py
用docker ps看一眼
欸?为什么没有起来呢?再用docker service ls 查看task的状态:
注意这个REPOLICAS的值,0/1说明docker create 已经创建了一个副本但是还没有起来,稍等一会再运行一遍命令:
补充:
一些情况下已经运行了容器,可是运行docker ps在本机还是看不到容器,为什么呢?
其实,docker 会根据当前每个swarm节点的负载判断,在负载最优的节点运行这个task任务,用docker service ps + taskID 可以看到任务运行在哪个节点上。
好了container已经起来了并且运行在node1上
用浏览器打开地址能看到容器对应的ID:
4. 增加service节点
有了单个容器实例之后,下一步再尝试下动态扩展实例个数
$ docker service scale test=6
Node1:
Node2
Node3
一条命令让现在swarm cluster里三台节点,每台都运行了两个test副本实例。
此时你是不是已经留意到,一个天然的HA集群出现了。docker会把对每个host的http请求依据轮询算法,均匀地发送到每个task副本上。
5. 模拟其中一个swarm cluster节点离线的情况
正常来讲让一个swarm cluster中的一个node退出集群的方式,是在要推出的节点上运行$ docker swarm leave命令,但是为了让实验更疯狂,我在node3上直接stop docker的daemon
再去剩余两个节点上任意一个查看task状态:
原本在node3上运行的两个test任务:test3、test4,分别在node1和node2两台host上被来起来了。整个副本迁移的过程无需人工干预,迁移后原本的集群的load balance依旧好使!
--- 第二篇 ---
上篇对node1、node2、node3的请求是通过绑host的方式测试的,本期我们接着往下聊。
负载均衡和服务发现
测试中只是每个host节点中的containers之间实现了负载均衡,生产环境在做rolling_update时,必须确保持在同一时刻,至少有一个容器能正常提供服务。
那么问题来了,有没有办法能自定义检测到每个节点中应用的运行状态,如果其中一个服务运行不正常,则立即通知前面做反向代理的HTTP服务器,让它自动摘除不正常的节点,等到节点修复后又重新自动注册节点信息到负载均衡器上呢?并且全程没有人工干预。
答案是肯定的。这里介绍两种实现服务注册发现的方式:
1. docker1.12内置的服务注册发现机制
讲到docker的服务发现机制之前,不得不提overlay网络,这个特性最早出现在docker1.9版本发布的功能特性中,他的特点就是能够使不同宿主机上的容器进行网络互通。
而在此之前,如果要做到位于不同主机的容器之间通信,一般有几种方法:
使用端口映射:直接把容器的服务端口映射到主机上,主机直接通过映射出来的端口通信
把容器放到主机所在的网段
通过第三方工具flannel,weave 或者 pipework 等,这些方案一般都是通过 SDN 搭建 overlay 网络达到容器通信的
Docker1.12中依然继承了这个overlay的网络模型,并且为自己的服务注册发现提供了强有力的网络保障。
Docke的注册发现原理其实是采用一个分布式的Key-Value Storage作为存储的抽象层。Docker 1.12 提供了内置的 Discovery 服务, 这样集群的搭建不需要再依赖外部的 Discovery 服务, 比如 consul 或 etcd。(当然swarm mode下也可以使用这些Discovery 服务,具体的下个小节会详细介绍)。目前Swarm mode提供了6种discovery机制:Token(默认)、Node、File、Consul、Etcd、Zookeeper。其中有两种方式的节点需要对配置文件或者其他相关内容进行维护,其他服务发现仅需要通过join命令行来完成。这两种方式分别是Node和File discovery。
好了继续实验,首先创建一个自定义的overlay网络:
$docker network create -d overlay test
然后在同一个网络上分别吧应用容器和http服务容器起来:
$ docker service create --name test -p 5000:5000 --replicas 6 –network test demo python env.py
$ docker service create --name nginx --replicas 3 --network test -p 80:80 nginx-2
Nginx容器的default.conf配置如下,其中test:5000对应之前由docker service create出来的test任务,docker 引擎已经将task name对应的ip关系映射成内部的DNS解析。
server {
listen 80;
server_name localhost;
access_log /var/log/nginx/log/host.access.log main;
location / {
# root /usr/share/nginx/html;
# index index.html index.htm;
proxy_pass http://test:5000;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
至此全部操作完成,当浏览器访问http://node2后,http请求根据VIP负载均衡算法均匀的分配至3个swarm cluster node上的6个python容器去响应请求,并且无论哪个后端容器挂了,只要三台docker swarm cluster的节点不同时出事,都不会影响正常 的网站服务。
对于上述的VIP负载均衡算法做下补充说明:docker1.12使用的是linux自身的IPVS作为负载均衡方式。IPVS实则linux内核中一个叫做ip_vs的负载均衡模块。不同于DNS负载均衡将IP列表顺序轮询,IPVS会将负载均匀的分发到每个容器。IPVS是四层的转发者,能够转发TCP、UDP、DNS并且支持八种负载均衡算法。
2. docker结合外置的配置存储服务
这类的服务有多种选择,consul和etcd,zookeeper,这里以consul为例。consul是一款服务注册发现的软件,自身是一个key/value的store。在docker1.12发布之前,许多人选择用它和docker一起结合来提供一个高可扩展性的web服务。
开始实验前要先修改docker的主配置文件,使用consul替换缺省的docker自身的key/value store中心
ExecStart=/usr/bin/dockerd --registry-mirror=http://057aa18c.m.daocloud.io -H unix:///var/run/docker.sock --cluster-store=consul://192.168.133.137:8500
1)还是在上面演示的几台机器中选一台node2来做consul的server(consul的server最好也配置成cluster的模式,实现consul自己的HA,本文为了快速介绍功能就不搭建了,只起一个节点)。
还需注意一点,本文中选用了一台业务节点作为配置存储服务的运行位置,不过通常建议是这种base service能与运行业务容器的节点分开,使用独立的服务节点,这样才能确保所有运行业务容器的节点是无状态的,可以被平等的调度和分配运算任务。
2)$docker run –d --restart=always -h node -p 8500:8500 -p 8600:53/udp progrium/consul -server -bootstrap -advertise 192.168.133.137 -log-level debug
Concul自带UI,打开192.168.133.137:8500你就能看到,consul启动后会开启两个端口,一个事53/udp,还有一个是8500/tcp,从dashboard上都能看到他们的状况。
3)启动 registrator 容器,目的是注册 docker container 的信息到consul 集群中
$docker run -d --restart=always -v /var/run/docker.sock:/tmp/docker.sock -h 192.168.133.137 gliderlabs/registrator consul://192.168.133.137:8500
4)启动一个最简单的http服务器验证是否已经将自身信息注册到了consul中,实现了自动发现的功能:
$docker run -d -p 7070:80 --name httpd httpd
5)最后在测试机上安装 consul-template 来从 consul 获取数据,更新本地的某个模板配置文件。
安装consul-template:
$ curl https://releases.hashicorp.com/consul-template/0.15.0/consul-template_0.15.0_linux_amd64.zip -o consul-template.zip && unzip consul-template.zip && mv ./consul-template /usr/bin/
生成模板文件:
$ echo -e '{{range service "httpd"}}
server {{.Address}}:{{.Port}}{{end}}' > /tmp/consul.ctmpl
填写模板:
$consul-template -consul 192.168.133.137:8500 -template "/tmp/consul.ctmpl:/tmp/consul.result" --once
现在再把httpd容器stop掉,重新执行填写模板的命令。
可以看到注册进consul的容器信息被填写进模板当中了! 如果把模板做成nginx的配置文件,就能依据consul来检测容器是否启动从而动态更新nginx的配置文件了。
upstream consul_nodes {
server 192.168.133.137:7070;
server 192.168.133.139:7070;
}
location / {
root html;
index index.html index.htm;
proxy_pass http://consul_nodes;
}
以上两种是实现服务注册发现的方式,都列出来给各位看看,对比之下看得出来在配置的容易程度方面,docker1.12自带的还是要占有比较明显的优势的。
滚动部署
从前docker的旧版本下,容器必须手动蓝绿部署,或者手写脚本实现滚动升级。1.12有了滚动更新以后,我们就不需要把更新规则写成脚本去实现透明部署。Swarm mode中,服务可以更新逐步节点,并且控制服务的部署之间的延迟到不同的节点集合。如果出现任何错误,能够马上回滚上一个任务,回到先前版本的服务。
当现在要更新test这个task所引用的镜像时,可以这么操作:
$docker service update --update-parallelism 2 --image demo:2.0 --update-delay 10s test
其中--update-parallelism参数用来指定最大同步更新任务数。这意味着我们可以安全透明的更新容器副本。关于透明,当然要确保你的容器是向后兼容的,否则最好销毁旧的容器,再去更新所有的容器。
然后容器就会每隔10秒跟新2个容器,直至30秒后此次更新操作完毕。
最后提醒一句,docker 1.12的swarm cluster的功能选项是可开始,不是必须选项。原先的单主机运行方式依然保留。但是看到了这些炫酷的新特性你还舍得关闭这个选项吗?
Docker生态已经逐渐从单纯的镜像生态,衍生到容器集群的管理,docker的前途依旧一片光明。