httpコンテナが増えたらSerfを使って自動的にNginxが割り振るサーバを増やすというserfを使ってる感を出してみた
以前Qiitaに投稿したGWに一人さみしくDockerとSerfと戯れてみた(๑´ڡ`๑) の続き 今回は、下記構成をDockerを使って構築
- Nginx(LB) x 1
- python(http) x ∞
そして、Serfを使ってhttp serverが増えるたびに、nginxが通知する先を増やす。
というなんか、俺 Serf
使いになったんじゃね?と錯覚するような事をやります。
今回の全ソース類 https://github.com/shinofara/serf/tree/dbb46c60a24dede36b5546f6ff611131d9b4636a
コード量が多くなるので、抜粋して説明しよう
Docker
Dockerfiles
BuildのためのMakefile
別にMakefileでなくてもOK
default: build-nginx build-app build-nginx: @cd docker/nginx && docker build -t shinofara/serf_nginx . build-app: @cd docker/app && docker build -t shinofara/serf_app .
あとは$ make
で作成するだけ
各コンテナの実行
Nginx Container(LB)
docker run -d -t \ --name proxy \ -p 80:80 \ shinofara/serf_nginx
proxyという名前で、80番ポートを開放して立ち上げます。 実際はproxy.shを実行
App Container(Http)
docker run -d -t \ --name node1 \ --link proxy:node \ -h node1 \ shinofara/serf_app
node1
という名前で、proxy
と内部リンクさせて立ち上げます。
実際はnode.shを実行
確認方法
上の2つを実行することで、Nginx(LB) + http serverが立ち上がり、ブラウザで確認できるようになります。
実際Serfはどのように動いているのか
ここから本題
Serf
のRoleを2つ作成してます。
- web
- app
webはLBのroleの事で、appはhttp serverです。
といっても今回はweb
role以外特に何もしていないですが、名目上分けて管理しています。
LBとHTTPの内部で実行されているserf agent
LB
serf agent -bind 0.0.0.0:7946 -event-handler /usr/local/src/python/handler.py -log-level=debug -tag role=web
実際はsupervisorで管理しています。supervisor.conf
HTTP
serf agent -bind 0.0.0.0:7946 -join $NODE_PORT_7946_TCP_ADDR:7946 -tag role=app
実際はsupervisorで管理しています。supervisor.conf
LB側だけにhandlerを登録
Nginx(LB)では、httpサーバが増減したらそれらをupstream
に追加・削除したいので、
event handlerを使って検知して、nginx.confを変更して再起動するという処理を書いています。
汚いけど、これ
#!/usr/bin/env python # -*- coding: utf-8 -*- from serf_master import SerfHandler, SerfHandlerProxy from jinja2 import Environment, FileSystemLoader import logging import sys import bsddb import json import subprocess import os class AppHandler(SerfHandler): def member_join(self): # nginx.confのテンプレート取得 env = Environment(loader=FileSystemLoader('/usr/local/src/python/', encoding='utf8')) tpl = env.get_template('nginx.tpl.conf') # ip管理用配列 ips = [] db=bsddb.hashopen('hashtest.db','c') try: dbips = json.loads(db["ips"]) for ip in dbips: ips.append(ip) except: print "no cache" # 追加されたagent情報は標準入力で来るので(改行コード区切り) for line in iter(sys.stdin.readline, ""): print line agent = line.split("\t") node = agent[0] #node ip = agent[1] #node role = agent[2] #node # この時app roleに追加された場合、追加 if role == "app": ips.append(ip) db["ips"] = json.dumps(ips) db.sync() # テンプレートから書き出すデータを生成 html = tpl.render({'backends':ips}) # nginxに読み込ませるconfファイルを作成 f = open("/etc/nginx/conf.d/proxy.conf", "w") f.write(html) f.close() # Nginxを再起動 cmd = 'supervisorctl restart nginx' ret = subprocess.check_output( cmd.split(" ") ) print ret if __name__ == '__main__': handler = SerfHandlerProxy() # 存在するappとdefaultのみ許可される(Roleごとのハンドラを登録) handler.register('default', AppHandler()) handler.run()
実際に書いたhandler.py
そして、この時のnginx.tpl.conf
はこちら
upstream backend { {% for backend in backends %} server {{ backend }}:9000; {% endfor %} } server { listen 80; location / { proxy_set_header Host $host; proxy_pass http://backend/cgi-bin/index.py; proxy_redirect default; break; } proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Real-IP $remote_addr; }
html = tpl.render({'backends':ips})
で、upsteam
の箇所が色々書き換わるイメージ
これで、app roleに追加があった場合、nginxはかってにupstream
を書き換えて再起動までやっちゃいます。
冒頭で増減と書きましたが、体力の関係で削除は飛ばしました。
最後に
serf
は今まで触りくらいしか触ってこなかったけど、イベントまわりを理解しておけば
便利ツールが色々作れそうだなと思いました
そういえば
https://github.com/sorah/mamiya
はcookpadで使われているSerf+s3を使ったデプロイツールみたいですね。
※S3以外も可能
もっと色々やれそうなので、何か進展があれば追加します。