基于 Docker 的高可用 Rails 集群方案探索
作者:长洪 2015-07-29 13:21:58云计算 本文是暴走漫画团队关于自动化高可用集群方案的探索。主要目的是减少人工接入,实现自动化。主要是用了consul和registrator两个工具。
0x0 问题
运维从来都是一个“不起眼”的大问题。运维工作还特别琐碎,一般要花去工程师大量的时间,同时还有突发性,对工程师机动性要求比较高。所以运维自动化一直在困扰暴走漫画团队。
虽说大部分运维工作都交给第三方云服务提供商了,但是平时还是有一些机器的维护之类的,繁琐的事情。
我 做的最多的应该就是加减服务器了。暴漫的流量趋势非常规律,每年寒暑假是访问的高峰期,一般会比平时流量高出2倍。所以每年寒暑假之前都要添加足量的服务 器以应对马上到来的流量高峰,而在寒暑假结束之后,要去掉一部分机器,以节省开支。刚开始完全是人肉运维,我一个人先去开5台机器,然后一个个上去改配置 文件之类的,感觉好傻。后面就用了ansible作为自动化运维工具,这个要比puppet和chef轻量很多,对付我们这些简单的运维工作绰绰有余。 于是现在就变成了开完机器,用ansible同步配置,代码,启动app server,然后手动更新nginx配置。
使用ansible之后其实已经轻松很多了,但是还不够,我想象中的自动化集群,应该可以随意增加删除node,node不可用的时候自动删除,当然这些都不能影响服务的访问。
下面介绍一种自动化的Rails集群方案
0x1 相关技术
dockerhashicorp/consul(https://github.com/hashicorp/consul)hashicorp/consul-template(https://github.com/hashicorp/consul-template)hashicorp/registrator(https://github.com/gliderlabs/registrator)0X2 整体架构
0x3 实践
由于不是科普帖,各种工具的详细信息就不自己介绍了。
consul & registrator |
这两个工具是集群中每个节点的基础服务。我在一台机器上,所以把consul cluster给省掉了,只用了一个consul节点。下面是docker-compose配置。
consul: hostname: node1 image: progrium/consul command: -server -bootstrap -advertise 172.17.42.1 ports: – “8400:8400” – “8500:8500” – “8600:53/udp” registrator: image: gliderlabs/registrator command: consul://consul:8500 hostname: registrator1 volumes: – /var/run/docker.sock:/tmp/docker.sock links: – consul |
需要注意的是consul的启动参数里的advertise。应该声明为host机器的docker0的ip。如果不声明的话会默认使用容器ip,这样registrator注册的设备ip都是不可访问的。
以上配置启动之后一套自动服务器发现功能就算完工了。
#p#
接下来,我们配置一个web应用测试一下。
web: image: nginx volumes: – ./sites-enabled:/etc/nginx/conf.d ports: – “80:80” links: – rails rails: image: tutum/hello-world ports: – “80” |
这个配置生成了nginx和rails,并且挂载了本地的sites-enabled文件夹作为nginx的配置来源。注意rails的ports是随机的。
在sites-enabled中配置nginx server已rails作为backend。
upstreambackend{}server{server_name"10.0.2.15";location/{proxy_passhttp://backend;}}
对你并没有看错, upstream并没有定义可用的backend node。这个配置将会有consul-template根据服务列表自动填充。
让我们启动这个web应用,访问80端口是无法访问的 应为没有backend node。
接下来需要安装consule-template, 在github下载对应的release。
然后创建一个配置文件ct-nginx.conf
consul="127.0.0.1:8500"log_level="warn"template{source="/home/vagrant/nginx.ctmpl"destination="/home/vagrant/sites-enabled/backend.conf"command="docker-compose-f/home/vagrant/docker-compose.ymlrestartweb"}
我有映射consul的端口,所以直接使用了127.0.0.1。source就是我们配置文件的模板。如下
upstreambackend{{{rangeservice"hello-world"}}server{{.Address}}:{{.Port}};{{end}}}server{server_name"10.0.2.15";location/{proxy_passhttp://backend;}}
consul-template提供了方便的模板查询语句,可以直接从consul中查询并渲染出配置文件。这个模板中就找出了所有名字为“hello-world”的service,并且循环输出service对应的Address,也就是服务ip,Port。
接着看ct-nginx.conf,destination代表模板生成完毕之后的位置, 这里直接放在nginx的配置文件夹,***command是生成配置后的动作,可以重启各种服务。我们这里在更新nginx之后重启生效。
OK,配置已经妥当,consul和registrator也已经启动, 让我们看看当前的服务列表。
$curllocalhost:8500/v1/catalog/service/hello-world?pretty$[]
当前并没有注册名为hello_world的服务。
#p#
稍微提一下registrator注册服务的命名机制,registaor默认是通过提取容器各种参数来生成的的服务参数
比如
dockerrun-d--namenginx.0-p4443:443-p8000:80progrium/nginx
生成的服务信息如下
[{"ID":"hostname:nginx.0:443","Name":"nginx-443","Port":4443,"IP":"192.168.1.102","Tags":[],"Attrs":{},},{"ID":"hostname:nginx.0:80","Name":"nginx-80","Port":8000,"IP":"192.168.1.102","Tags":[],"Attrs":{}}]
当然也可以通过环境变量来指定
$dockerrun-d--namenginx.0-p4443:443-p8000:80\-e"SERVICE_443_NAME=https"\-e"SERVICE_443_ID=https.12345"\-e"SERVICE_443_SNI=enabled"\-e"SERVICE_80_NAME=http"\-e"SERVICE_TAGS=www"progrium/nginx
[{"ID":"https.12345","Name":"https","Port":4443,"IP":"192.168.1.102","Tags":["www"],"Attrs":{"sni":"enabled"},},{"ID":"hostname:nginx.0:80","Name":"http","Port":8000,"IP":"192.168.1.102","Tags":["www"],"Attrs":{}}]
接下来我们启动consul-template
$consul-template-config=./ct-nginx.conf
会直接根据模板生成一个nginx配置 虽然现在并没有backend node。
$cat/home/vagrant/sites-enabled/backend.confupstreambackend{}server{server_name"10.0.2.15";location/{proxy_passhttp://backend;}}
然后启动我们的web应用。
启动之后,请求服务列表就可以看到helle-world服务,说明已经实现自动服务发现。
$curllocalhost:8500/v1/catalog/service/hello-world?pretty[{"Node":"node1","Address":"172.17.42.1","ServiceID":"registrator1:vagrant_rails_1:80","ServiceName":"hello-world","ServiceTags":null,"ServiceAddress":"","ServicePort":32783}]
浏览器中可以正常访问了,Yeah!
#p#
OK, 让我们更进一步,scale我们的rails node。
$docker-composescalerails=4$curllocalhost:8500/v1/catalog/service/hello-world?pretty[{"Node":"node1","Address":"172.17.42.1","ServiceID":"registrator1:vagrant_rails_1:80","ServiceName":"hello-world","ServiceTags":null,"ServiceAddress":"","ServicePort":32783},{"Node":"node1","Address":"172.17.42.1","ServiceID":"registrator1:vagrant_rails_2:80","ServiceName":"hello-world","ServiceTags":null,"ServiceAddress":"","ServicePort":32784},{"Node":"node1","Address":"172.17.42.1","ServiceID":"registrator1:vagrant_rails_3:80","ServiceName":"hello-world","ServiceTags":null,"ServiceAddress":"","ServicePort":32785},{"Node":"node1","Address":"172.17.42.1","ServiceID":"registrator1:vagrant_rails_4:80","ServiceName":"hello-world","ServiceTags":null,"ServiceAddress":"","ServicePort":32786}]
此时nginx已经重启,我们可以看到4个backend都开始处理请求。
你可以看到,rails服务器只要启动之后就自动加入集群,如果一台节点挂掉之后,会自动从集群里去掉,基本上没有任何影响。
肯定有不足的地方,欢迎讨论, 发出来就是为了学习。
博文出处:http://dev.baozou.com/ye-tan-ji-yu-dockerde-railsji-qun/