构建一个高可用及自动发现的Docker基础架构

Docker的生态日趋成熟,开源社区也不断孵化出优秀的周边项目,覆盖网络、监控、维护、部署、开发等方面。帮助开发、运维人员快速构建、运营Docker服务环境,其中也不乏有大公司的影子,如Google、IBM、Redhat,甚至微软也宣称后续将提供Docker在Windows平台的支持。Docker的发展前景一片大好。但在企业当中,如何选择适合自己的Docker构建方案?可选的方案有kubernetes与CoreOS(都已整合各类组件),另外一种方案为Haproxy+etcd+confd,采用松散式的组织结构,但各个组件之间的通讯是非常严密的,且扩展性更强,定制也更加灵活。下面详细介绍如何使用Haproxy+etcd+confd构建一个高可用及自动发现的Docker基础架构。

作者简介:刘天斯,目前就职于腾讯-互动娱乐部(高级工程师),曾就职于天涯社区,担任架构师/系统管理员,热衷开源技术的研究,包括系统架构、运维开发、负载均衡、缓存技术、数据库、分布式存储及云计算等领域,擅长大规模集群的运维工作。关注互联网技术发展动向,努力紧靠技术前沿。充当一名普通的传播者和分享者。 著有《python自动化运维:技术与实践》、《Docker技术与实践》(预计2015年5月出版)。

一、架构优势

约定由Haproxy+etcd+confd+Docker构建的基础服务平台简称“HECD” 架构,整合了多种开源组件,看似松散的结构,事实上已经是一个有机的整体,它们互相联系、互相作用,是Docker生态圈中最理想的组合之一,具有以下优势:

自动、实时发现及无感知服务刷新;  支持任意多台Docker主宿机;  支持多种APP接入且打散至不分主宿机;  采用Etcd存储信息,集群支持可靠性高;  采用Confd配置引擎,支持各类接入层,如Nginx;  支持负载均衡、故障迁移;  具备资源弹性,伸缩自如(通过生成、销毁容器实现);  二、架构说明

在HECD架构中,首先管理员操作Docker Client,除了提交容器(Container)启动与停止指令外,还通过REST-API方式向Etcd(K/V)存储组件注册容器信息,包括容器名称、主宿机IP、映射端口等。Confd配置组件会定时查询Etcd组件获取最新的容器信息,根据定义好的配置模板生成Haproxy配置文件Haproxy.cfg,并且自动reload haproxy服务。用户在访问业务服务时,完全没有感知后端APP的上线、下线、切换及迁移,达到了自动发现、高可用的目的。详细架构图见图1-1。

图1-1 平台架构图

为了方便大家理解各组件间的关系,通过图1-2进行架构流程梳理,首先管理员通过Shell或API操作容器,下一步将容器信息注册到Etcd组件,Confd组件会定时查询Etcd,获取已经注册到Etcd中容器信息,最后通过Confd的模板引擎生成Haproxy配置,整个流程结束。

图1-2架构流程图

了解架构流程后,我们逐一对流程中各组件进行详细介绍。

1、Etcd介绍

Etcd是一个高可用的 Key/Value 存储系统,主要用于分享配置和服务发现。

简单:支持 curl 方式的用户 API (HTTP+JSON)  安全:可选 SSL 客户端证书认证  快速:单实例可达每秒 1000 次写操作  可靠:使用 Raft 实现分布式

2、Confd介绍

Confd是一个轻量级的配置管理工具。通过查询Etcd,结合配置模板引擎,保持本地配置最新,同时具备定期探测机制,配置变更自动reload。

3、Haproxy介绍

HAProxy是提供高可用性、负载均衡以及基于TCP和HTTP应用的代理,支持虚拟主机,它是免费、快速并且可靠的一种解决方案。(来源百科)

三、架构部署

平台环境基于Centos6.5+Docker1.2构建,其中Etcd的版本为etcd version 0.5.0-alpha,Confd版本为confd 0.6.2,Haproxy版本为HA-Proxy version 1.4.24。下面对平台的运行环境、安装部署、组件说明等进行详细说明,环境设备角色表如下:

1、组件安装

1.1 Docker安装

SSH终端登录192.168.1.22服务器,执行以下命令:

# yum -y install docker-io  

# service docker start  

# chkconfig docker on

1.2 Haproxy、confd安装

SSH终端登录192.168.1.20服务器,执行以下命令:

1、haproxy  

# yum –y install haproxy  

2、confd  

# wget https://github.com/kelseyhightower/confd/releases/download/v0.6.3/confd-0.6.3-linux-amd64 

# mv confd /usr/local/bin/confd  

# chmod +x /usr/local/bin/confd  

# /usr/local/bin/confd -version  

confd 0.6.2

1.3 Etcd安装

SSH终端登录192.168.1.21服务器,执行以下命令:

# yum -y install golang  

# mkdir -p /home/install && cd /home/install  

# git clone https://github.com/coreos/etcd 

# cd etcd  

# ./build  

# cp bin/etcd /bin/etcd  

# /bin/etcd -version  

etcd version 0.5.0-alpha

2、组件配置

2.1 Etcd配置

由于etcd是一个轻量级的K/V存储平台,启动时指定相关参数即可,无需配置。

# /bin/etcd -peer-addr 192.168.1.21:7001 -addr 192.168.1.21:4001 -data-dir /data/etcd -peer-bind-addr 0.0.0.0:7001 -bind-addr 0.0.0.0:4001 &
由于etcd具备多机支持,参数“-peer-addr”指定与其它节点通讯的地址;参数“-addr”指定服务监听地址;参数“-data-dir”为指定数据存储目录。

由于etcd是通过REST-API方式进行交互,常见操作如下:

1) 设置(set) key操作

# curl -L http://192.168.1.21:4001/v2/keys/mykey-XPUT -d value="this is awesome"  

{"action":"set","node":{"key":"/mykey","value":"this is awesome","modifiedIndex":28,"createdIndex":28}}

2) 获取(get) key信息

# curl -L http://192.168.1.21:4001/v2/keys/mykey  

{"action":"get","node":{"key":"/mykey","value":"this is awesome","modifiedIndex":28,"createdIndex":28}}

3) 删除key信息

# curl -L http://192.168.1.21:4001/v2/keys/mykey-XDELETE         {"action":"delete","node":{"key":"/mykey","modifiedIndex":29,"createdIndex":28},"prevNode":{"key":"/mykey","value":"this is awesome","modifiedIndex":28,"createdIndex":28}}   更多操作API见https://github.com/coreos/etcd/blob/master/Documentation/api.md。

2.2 Confd+Haproxy配置

由于Haproxy的配置文件是由Confd组件生成,要求Confd务必要与haproxy安装在同一台主机上,Confd的配置有两种,一种为Confd资源配置文件,默认路径为“/etc/confd/conf.d”目录,另一种为配置模板文件,默认路径为“/etc/confd/templates”。具体配置如下:

创建配置文件目录

# mkdir -p /etc/confd/{conf.d,templates} (1)配置资源文件

详细见以下配置文件,其中“src”为指定模板文件名称(默认到路径/etc/confd/templates中查找);“dest”指定生成的Haproxy配置文件路径;“keys”指定关联Etcd中key的URI列表;“reload_cmd”指定服务重载的命令,本例中配置成haproxy的reload命令。

【/etc/confd/conf.d/ haproxy.toml】

[template] src = "haproxy.cfg.tmpl" dest = "/etc/haproxy/haproxy.cfg" keys = [ "/app/servers", ] reload_cmd = "/etc/init.d/haproxy reload" (2)配置模板文件

Confd模板引擎采用了Go语言的文本模板,更多见http://golang.org/pkg/text/template/,具备简单的逻辑语法,包括循环体、处理函数等,本示例的模板文件如下,通过range循环输出Key及Value信息。

【/etc/confd/templates/haproxy.cfg.tmpl】

global log 127.0.0.1 local3 maxconn 5000 uid 99 gid 99 daemon defaults log 127.0.0.1 local3 mode http option dontlognull retries 3 option redispatch maxconn 2000 contimeout 5000 clitimeout 50000 srvtimeout 50000 listen frontend 0.0.0.0:80 mode http balance roundrobin maxconn 2000 option forwardfor {{range gets "/app/servers/*"}} server {{base .Key}} {{.Value}} check inter 5000 fall 1 rise 2 {{end}} stats enable stats uri /admin-status stats auth admin:123456 stats admin if TRUE (3)模板引擎说明

本小节详细说明Confd模板引擎基础语法与示例,下面为示例用到的KEY信息。

# curl -XPUT http://192.168.1.21:4001/v2/keys/app/servers/backstabbing_rosalind-d value="192.168.1.22:49156"  

# curl -XPUT http://192.168.1.21:4001/v2/keys/app/servers/cocky_morse-d value="192.168.1.22:49158"  

# curl -XPUT http://192.168.1.21:4001/v2/keys/app/servers/goofy_goldstine-d value="192.168.1.22:49160"  

# curl -XPUT http://192.168.1.21:4001/v2/keys/app/servers/prickly_blackwell-d value="192.168.1.22:49162"1、base

作为path.Base函数的别名,获取路径最后一段。
{{ with get "/app/servers/prickly_blackwell"}}
 server {{base .Key}} {{.Value}} check
{{end}}

prickly_blackwell 192.168.1.22:49162

2、get

返回一对匹配的KV,找不到则返回错误。

{{with get "/app/servers/prickly_blackwell"}}
 key: {{.Key}}
 value: {{.Value}}
{{end}}

/app/servers/prickly_blackwell 192.168.1.22:49162
3、gets

{{range gets "/app/servers/*"}}
 {{.Key}} {{.Value}}
  {{end}}

/app/servers/backstabbing_rosalind 192.168.1.22:49156 /app/servers/cocky_morse 192.168.1.22:49158 /app/servers/goofy_goldstine 192.168.1.22:49160 app/servers/prickly_blackwell 192.168.1.22:49162
4、getv

返回一个匹配key的字符串型Value,找不到则返回错误。

{{getv "/app/servers/cocky_morse"}}

192.168.1.22:49158
5、getvs

返回所有匹配key的字符串型Value,找不到则返回错误。

{{range getvs "/app/servers/*"}}
  value: {{.}}
 {{end}}

value: 192.168.1.22:49156 value: 192.168.1.22:49158 value: 192.168.1.22:49160 value: 192.168.1.22:49162 6、split

对输入的字符串做split处理,即将字符串按指定分隔符拆分成数组。
 {{ $url := split (getv "/app/servers/cocky_morse") ":" }}
 host: {{index $url 0}}
 port: {{index $url 1}}

host: 192.168.1.22 port: 49158 7、ls

返回所有的字符串型子key,找不到则返回错误。
{{range ls "/app/servers/"}}
  subkey: {{.}}
{{end}}

subkey: backstabbing_rosalind subkey: cocky_morse subkey: goofy_goldstine subkey: prickly_blackwell 8、lsdir

返回所有的字符串型子目录,找不到则返回一个空列表。
{{range lsdir "/app/"}}
  subdir: {{.}}
{{end}}

subdir: servers (4)启动confd及haproxy服务

下面为启动Confd服务命令行,参数“interval”为指定探测etcd的频率,单位为秒,参数“-node”为指定etcd监听服务主地址,以便获取容器信息。

# /usr/local/bin/confd -verbose -interval 10 -node '192.168.1.21:4001' -confdir /etc/confd > /var/log/confd.log & # /etc/init.d/haproxy start  
3、容器配置

前面HECD架构说明内容,有讲到容器的操作会即时注册到etcd组件中,是通过curl命令进行REST-API方式提交的,下面详细介绍通过SHELL及Python-api两种方式的实现方法,支持容器启动、停止的联动。

3.1、SHELL实现方法

实现的原理是通过获取“Docker run ***”命令输出的Container ID,通过“docker inspect Container ID”得到详细的容器信息,分析出容器服务映射的外部端口及容器名称,将以“/app/servers/容器名称”作为Key,“主宿机: 映射端口”作为Value注册到etcd中。其中Key信息前缀(/app/servers)与“/etc/confd/conf.d/haproxy.toml”中的keys参数是保持一致的。

【docker.sh】

#!/bin/bash  

if [ -z $1 ]; then  

        echo "Usage: c run <image name>:<version>"  

        echo "       c stop <container name>"  

        exit 1  

fi  

if [ -z $ETCD_HOST ]; then  

  ETCD_HOST="192.168.1.21:4001"  

fi  

if [ -z $ETCD_PREFIX ]; then  

  ETCD_PREFIX="app/servers"  

fi  

if [ -z $CPORT ]; then  

  CPORT="80"  

fi  

if [ -z $FORREST_IP ]; then  

  FORREST_IP=`ifconfig eth0| grep "inet addr" | head -1 | cut -d : -f2 | awk '{print $1}'`  

fi  

function launch_container {  

        echo "Launching $1 on $FORREST_IP ..."  

        CONTAINER_ID=`docker run -d --dns 172.17.42.1 -P -v /data:/data -v /etc/httpd/conf:/etc/httpd/conf -v /etc/httpd/conf.d:/etc/httpd/conf.d -v /etc/localtime:/etc/localtime:ro $1 /bin/sh -c "/usr/bin/supervisord -c /etc/supervisord.conf"`  

        PORT=`docker inspect $CONTAINER_ID|grep "\"Ports\"" -A 50|grep "\"$CPORT/tcp\"" -A 3| grep HostPort|cut -d '"' -f4|head -1`  

        NAME=`docker inspect $CONTAINER_ID | grep Name | cut -d '"' -f4 | sed "s/\///g"|sed -n 2p`  

        echo "Announcing to $ETCD_HOST..."  

        curl -XPUT "http://$ETCD_HOST/v2/keys/$ETCD_PREFIX/$NAME" -d value="$FORREST_IP:$PORT"  

        echo "$1 running on Port $PORT with name $NAME"  

}  

function stop_container {  

        echo "Stopping $1..."  

        CONTAINER_ID=`docker ps -a| grep $1 | awk '{print $1}'`  

        echo "Found container $CONTAINER_ID"  

        docker stop $CONTAINER_ID  

  echo http://$ETCD_HOST/v2/keys/$ETCD_PREFIX/$1 

        curl -XDELETE http://$ETCD_HOST/v2/keys/$ETCD_PREFIX/$1&> /dev/null  

        echo "Stopped."  

}  

if [ $1 = "run" ]; then  

  launch_container $2  

else  

  stop_container $2  

fi  docker.sh使用方法:

1) 启动一个容器

# ./docker.sh run yorko/webserver:v3(镜像)

2) 停止一个容器

# ./docker.sh stop berserk_hopper(容器名)

3.2、Docker-py API实现方法

通过Python语言调用Docker-py的API实现容器的远程操作(创建、运行、停止),并结合python-etcd模块对etcd进行操作(set、delete),达到与SHELL方式一样的效果,很明显,Docker-py方式更加容易扩展,可以无缝与现有运营平台对接。

为兼顾到远程API支持,需对docker启动文件“exec”处进行修改,详细见如下:

# vi /etc/init.d/docker

$exec -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock -d &>> $logfile &  
启动容器的程序如下:

【docker_run.py】

#!/usr/local/Python/bin/python import docker import etcd import sys Etcd_ip="192.168.1.21" Server_ip="192.168.1.22" App_port="80" App_protocol="tcp" Image="yorko/webserver:v3" Port="" Name="" idict={} rinfo={} try: c = docker.Client(base_url='tcp://'+Server_ip+':2375',version='1.14',timeout=15) except Exception,e: print "Connection docker server error:"+str(e) sys.exit() try: rinfo=c.create_container(image=Image,stdin_open=True,tty=True,command="/usr/bin/supervisord -c /etc/supervisord.conf",volumes=['/data','/etc/httpd/conf','/etc/httpd/conf.d ','/etc/localtime'],ports=[80,22],name=
None) containerId=rinfo['Id'] except Exception,e: print "Create docker container error:"+str(e) sys.exit() try: c.start(container=containerId, binds={'/data':{'bind': '/data','ro': False},'/etc/httpd/conf':{'bind': '/etc/httpd/conf','ro': True},'/etc/httpd/conf.d':{'bind': '/etc/htt pd/conf.d','ro': True},'/etc/localtime':{'bind': '/etc/localtime','ro': True}}, port_bindings={80:None,22:None}, lxc_conf=None,publish_all_ports=True, links=None, privileged=F alse,dns='172.17.42.1', dns_search=None, volumes_from=None, network_mode=None,restart_policy=None, cap_add=None, cap_drop=None) except Exception,e: print "Start docker container error:"+str(e) sys.exit() try: idict=c.inspect_container(containerId) Name=idict["Name"][1:] skey=App_port+'/'+App_protocol for _key in idict["NetworkSettings"]["Ports"].keys(): if _key==skey: Port=idict["NetworkSettings"]["Ports"][skey][0]["HostPort"] except Exception,e: print "Get docker container inspect error:"+str(e) sys.exit() if Name!="" and Port!="": try: client = etcd.Client(host=Etcd_ip, port=4001) client.write('/app/servers/'+Name, Server_ip+":"+str(Port)) print Name+" container run success!" except Exception,e: print "set etcd key error:"+str(e) else: print "Get container name or port error." 停止容器的程序如下:

【docker_stop.py】

#!/usr/local/Python/bin/python import docker import etcd import sys Etcd_ip="192.168.1.21" Server_ip="192.168.1.22" containerName="grave_franklin" #指定需要停止容器的名称 try: c = docker.Client(base_url='tcp://'+Server_ip+':2375',version='1.14',timeout=10) c.stop('furious_heisenberg') except Exception,e: print str(e) sys.exit() try: client = etcd.Client(host=Etcd_ip, port=4001) client.delete('/app/servers/'+containerName) print containerName+" container stop success!" except Exception,e: print str(e) 注意:由于容器是无状态的,尽量让其以松散的形式存在,映射端口选项要求使用“-P”参数,即使用随机端口的模式,减少人手干预。

四、业务上线

HECD架构已部署完毕,接下来就是让其为我们服务,案例中使用的镜像“yorko/webserver:v3”为已经构建好的LAMP平台。类似的镜像也可以在docker-pub中下载到,开始跑起,运行dockery.sh创建两个容器:

# ./docker.sh run yorko/webserver:v3 Launching yorko/webserver:v3 on 192.168.1.22 ... Announcing to 192.168.1.21:4001... {"action":"set","node":{"key":"/app/servers/berserk_hopper","value":"192.168.1.22:49170","modifiedIndex":33,"createdIndex":33}} yorko/webserver:v3 running on Port 49170 with name berserk_hopper # ./docker.sh run yorko/webserver:v3 Launching yorko/webserver:v3 on 192.168.1.22 ... Announcing to 192.168.1.21:4001... {"action":"set","node":{"key":"/app/servers/lonely_meitner","value":"192.168.1.22:49172","modifiedIndex":34,"createdIndex":34}} yorko/webserver:v3 running on Port 49172 with name lonely_meitner 访问Haproxy监控地址:http://192.168.1.20/admin-status,刚创建的容器已经添加到haproxy中,见图1-3。

图1-3 Haproxy监控后台截图

1)观察Haproxy的配置文件(更新部分):

# vi /etc/haproxy/haproxy.cfg

时间: 2024-11-02 22:06:32

构建一个高可用及自动发现的Docker基础架构的相关文章

基于Nginx和Consul构建高可用及自动发现的Docker服务架构

本文讲的是基于Nginx和Consul构建高可用及自动发现的Docker服务架构[编者的话]本文对于Docker和Consul Template以及Nginx如何结合使用做了较为详细的介绍. [上海站|3天烧脑式微服务架构训练营]培训内容包括:DevOps.微服务.Spring Cloud.Eureka.Ribbon.Feign.Hystrix.Zuul.Spring Cloud Config.Spring Cloud Sleuth等. 导读 如果你在大量接触或使用微服务的话,你可能会碰到一个问

基于Jenkins与Apache Mesos构建弹性高可用的持续集成环境

本文讲的是基于Jenkins与Apache Mesos构建弹性高可用的持续集成环境[编者的话]Jenkins是目前持续集成领域应用最为广泛的工具,通过Jenkins,项目可以进行自动化的编译.打包.分发与部署,通过持续不断的集成,减少了项目风险和重复过程,最终提高工作效率:Docker做为新的容器化的技术代表,能够保持跨环境的一致性,可以实现更快速地交付和部署:Apache Mesos做为一种分布式系统内核,可理解为一个集群管理器,通过对底层计算资源(物理机.虚拟机.云等)的CPU.内存.存储以

phalapi-入门篇4(国际化高可用和自动生成文档)

phalapi-入门篇4(国际化高可用和自动生成文档) 前言 先在这里感谢phalapi框架创始人@dogstar,为我们提供了这样一个优秀的开源框架. 在本小节主要讲解如何使用phalapi框架自带的国际化和文档自动生成,以及这样做可以的优点和好处和能解决哪方面的问题 附上: 官网地址:http://www.phalapi.net/ 开源中国Git地址:http://git.oschina.net/dogstar/PhalApi/tree/release 1. 国际化 说道国际化大家应该不陌生

WebSphere Operational Decision Management V7.5配置一个高可用和可扩展的环境

WebSphere http://www.aliyun.com/zixun/aggregation/10963.html">Operational Decision Management (以下简称 ODM)使企业能够利用智能的自动化决策响应实时数据.它包含两个相辅相成的组件:Decision Server Rules 和 Decision Server Events.根据业务数据类型和业务需求,Decision Server Events 可用于提供决策管理服务.因为 Decision

轻松构建Mysql高可用集群系统

一. MySQL复制的实现原理 MySQL支持单向.双向复制.异步复制,复制过程中一个服务器充当主服务器,而一个或多个其它服务器充当从服务器.主服务器将更新写入一个二进制日志文件中,并创建一个索引文件以跟踪日志循环.这些日志可以记录发送到从服务器的更新.当一个从服务器连接主服务器时,日志文件会通知主服务器,从服务器在日志中读取的最后一次成功更新的位置.接着,从服务器在上次成功更新的位置处开始进入更新操作.更新完成后从服务器开始进入等待状态,等待主服务器后续的更新. 需要注意的是:在进行复制时,所

zabbix自动发现监控docker stats和top状态

dcoker监控 首先需要将docker的name拿出来,循环执行docker stats将数据拿出重新排序后数值换算追加到/tmp/下以name命名 [root@localhost scripts]# cat docker_host_status.sh #!/bin/bash # ------------------------------------------------------------------------------- # --------------------------

实用技巧:如何用负载均衡构建高可用服务?

当单台服务器已经无法处理访问请求时,当我们的服务宕机时,当有人试图攻击我们的服务时,我们应该怎么办?升级后端服务时,怎样才能不中断服务?    如何构建一个高可用的服务? • 连接级 • 服务器级 • 可用区级 • Region级 健康检查 为什么健康检查总是显示失败? 为什么控制台上一会显示成功,一会显示异常? 直接访问服务器的健康检查地址是好的,但是还是会报健康检查失败,为什么? 健康检查实现机制 • 响应超时时间:5秒 • 健康检查间隔:2秒 • 不健康阈值:3 • 健康阈值:3 • 不可

高可用Hadoop平台-Flume NG实战图解篇

1.概述 今天补充一篇关于Flume的博客,前面在讲解高可用的Hadoop平台的时候遗漏了这篇,本篇博客为大家讲述以下内容: Flume NG简述 单点Flume NG搭建.运行 高可用Flume NG搭建 Failover测试 截图预览 下面开始今天的博客介绍. 2.Flume NG简述 Flume NG是一个分布式,高可用,可靠的系统,它能将不同的海量数据收集,移动并存储到一个数据存储系统中.轻量,配置简单,适用于各种日志收集,并支持Failover和负载均衡.并且它拥有非常丰富的组件.Fl

架构之坑系列1:重构中的过度设计与高可用银弹

这是一个坑系列,会说一些在系统设计.系统架构上的坑,这些都是我想到哪说到哪,有像这篇一样比较宏观的坑,后面的文章也会有到具体技术细节的(比如某个函数,某个系统调用)坑.总之,到处都是坑,这些坑有些是我经历过的,有些是听说的,你也可以留言说说你遇到的坑.   第一部分,我们从重构这个场景来看看系统架构的设计中过度设计这个坑.首先,我们这里说的重构,和<重构:改善既有代码的设计>这本书中的重构不太一样,这是本好书,他主要说的是代码级别的重构,这种重构是需要在编码的时候时时刻刻进行的,更多的是一种编