基于 Docker 的高可用 Rails 集群方案探索
0x0 問(wèn)題
運(yùn)維從來(lái)都是一個(gè)“不起眼”的大問(wèn)題。運(yùn)維工作還特別瑣碎,一般要花去工程師大量的時(shí)間,同時(shí)還有突發(fā)性,對(duì)工程師機(jī)動(dòng)性要求比較高。所以運(yùn)維自動(dòng)化一直在困擾暴走漫畫團(tuán)隊(duì)。
雖說(shuō)大部分運(yùn)維工作都交給第三方云服務(wù)提供商了,但是平時(shí)還是有一些機(jī)器的維護(hù)之類的,繁瑣的事情。
我 做的最多的應(yīng)該就是加減服務(wù)器了。暴漫的流量趨勢(shì)非常規(guī)律,每年寒暑假是訪問(wèn)的高峰期,一般會(huì)比平時(shí)流量高出2倍。所以每年寒暑假之前都要添加足量的服務(wù) 器以應(yīng)對(duì)馬上到來(lái)的流量高峰,而在寒暑假結(jié)束之后,要去掉一部分機(jī)器,以節(jié)省開(kāi)支。剛開(kāi)始完全是人肉運(yùn)維,我一個(gè)人先去開(kāi)5臺(tái)機(jī)器,然后一個(gè)個(gè)上去改配置 文件之類的,感覺(jué)好傻。后面就用了ansible作為自動(dòng)化運(yùn)維工具,這個(gè)要比puppet和chef輕量很多,對(duì)付我們這些簡(jiǎn)單的運(yùn)維工作綽綽有余。 于是現(xiàn)在就變成了開(kāi)完機(jī)器,用ansible同步配置,代碼,啟動(dòng)app server,然后手動(dòng)更新nginx配置。
使用ansible之后其實(shí)已經(jīng)輕松很多了,但是還不夠,我想象中的自動(dòng)化集群,應(yīng)該可以隨意增加刪除node,node不可用的時(shí)候自動(dòng)刪除,當(dāng)然這些都不能影響服務(wù)的訪問(wèn)。
下面介紹一種自動(dòng)化的Rails集群方案
0x1 相關(guān)技術(shù)
- docker
- hashicorp/consul(https://github.com/hashicorp/consul)
- hashicorp/consul-template(https://github.com/hashicorp/consul-template)
- hashicorp/registrator(https://github.com/gliderlabs/registrator)
0X2 整體架構(gòu)
0x3 實(shí)踐
由于不是科普帖,各種工具的詳細(xì)信息就不自己介紹了。
consul & registrator |
這兩個(gè)工具是集群中每個(gè)節(jié)點(diǎn)的基礎(chǔ)服務(wù)。我在一臺(tái)機(jī)器上,所以把consul cluster給省掉了,只用了一個(gè)consul節(jié)點(diǎn)。下面是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的啟動(dòng)參數(shù)里的advertise。應(yīng)該聲明為host機(jī)器的docker0的ip。如果不聲明的話會(huì)默認(rèn)使用容器ip,這樣registrator注冊(cè)的設(shè)備ip都是不可訪問(wèn)的。
以上配置啟動(dòng)之后一套自動(dòng)服務(wù)器發(fā)現(xiàn)功能就算完工了。
#p#
接下來(lái),我們配置一個(gè)web應(yīng)用測(cè)試一下。
web:
image: nginx volumes: - ./sites-enabled:/etc/nginx/conf.d ports: - "80:80" links: - rails rails: image: tutum/hello-world ports: - "80" |
這個(gè)配置生成了nginx和rails,并且掛載了本地的sites-enabled文件夾作為nginx的配置來(lái)源。注意rails的ports是隨機(jī)的。
在sites-enabled中配置nginx server已rails作為backend。
- upstream backend {
- }
- server {
- server_name "10.0.2.15";
- location / {
- proxy_pass http://backend;
- }
- }
對(duì)你并沒(méi)有看錯(cuò), upstream并沒(méi)有定義可用的backend node。這個(gè)配置將會(huì)有consul-template根據(jù)服務(wù)列表自動(dòng)填充。
讓我們啟動(dòng)這個(gè)web應(yīng)用,訪問(wèn)80端口是無(wú)法訪問(wèn)的 應(yīng)為沒(méi)有backend node。
接下來(lái)需要安裝consule-template, 在github下載對(duì)應(yīng)的release。
然后創(chuàng)建一個(gè)配置文件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.yml restart web"
- }
我有映射consul的端口,所以直接使用了127.0.0.1。source就是我們配置文件的模板。如下
- upstream backend {
- {{range service "hello-world"}}
- server {{.Address}}:{{.Port}};{{end}}
- }
- server {
- server_name "10.0.2.15";
- location / {
- proxy_pass http://backend;
- }
- }
consul-template提供了方便的模板查詢語(yǔ)句,可以直接從consul中查詢并渲染出配置文件。這個(gè)模板中就找出了所有名字為“hello-world”的service,并且循環(huán)輸出service對(duì)應(yīng)的Address,也就是服務(wù)ip,Port。
接著看ct-nginx.conf,destination代表模板生成完畢之后的位置, 這里直接放在nginx的配置文件夾,***command是生成配置后的動(dòng)作,可以重啟各種服務(wù)。我們這里在更新nginx之后重啟生效。
OK,配置已經(jīng)妥當(dāng),consul和registrator也已經(jīng)啟動(dòng), 讓我們看看當(dāng)前的服務(wù)列表。
- $ curl localhost:8500/v1/catalog/service/hello-world?pretty
- $ []
當(dāng)前并沒(méi)有注冊(cè)名為hello_world的服務(wù)。
#p#
稍微提一下registrator注冊(cè)服務(wù)的命名機(jī)制,registaor默認(rèn)是通過(guò)提取容器各種參數(shù)來(lái)生成的的服務(wù)參數(shù)
比如
- docker run -d --name nginx.0 -p 4443:443 -p 8000:80 progrium/nginx
生成的服務(wù)信息如下
- [
- {
- "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": {}
- }
- ]
當(dāng)然也可以通過(guò)環(huán)境變量來(lái)指定
- $ docker run -d --name nginx.0 -p 4443:443 -p 8000: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": {}
- }
- ]
接下來(lái)我們啟動(dòng)consul-template
- $ consul-template -config=./ct-nginx.conf
會(huì)直接根據(jù)模板生成一個(gè)nginx配置 雖然現(xiàn)在并沒(méi)有backend node。
- $ cat /home/vagrant/sites-enabled/backend.conf
- upstream backend {
- }
- server {
- server_name "10.0.2.15";
- location / {
- proxy_pass http://backend;
- }
- }
然后啟動(dòng)我們的web應(yīng)用。
啟動(dòng)之后,請(qǐng)求服務(wù)列表就可以看到helle-world服務(wù),說(shuō)明已經(jīng)實(shí)現(xiàn)自動(dòng)服務(wù)發(fā)現(xiàn)。
- $ curl localhost: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
- }
- ]
瀏覽器中可以正常訪問(wèn)了,Yeah!
#p#
OK, 讓我們更進(jìn)一步,scale我們的rails node。
- $ docker-compose scale rails=4
- $ curl localhost: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
- }
- ]
此時(shí)nginx已經(jīng)重啟,我們可以看到4個(gè)backend都開(kāi)始處理請(qǐng)求。
你可以看到,rails服務(wù)器只要啟動(dòng)之后就自動(dòng)加入集群,如果一臺(tái)節(jié)點(diǎn)掛掉之后,會(huì)自動(dòng)從集群里去掉,基本上沒(méi)有任何影響。
肯定有不足的地方,歡迎討論, 發(fā)出來(lái)就是為了學(xué)習(xí)。
博文出處:http://dev.baozou.com/ye-tan-ji-yu-dockerde-railsji-qun/