以前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が立ち上がり、ブラウザで確認できるようになります。 [f:id:shinofara:20160505123806g:plain]

実際Serfはどのように動いているのか

ここから本題

SerfのRoleを2つ作成してます。

  • web
  • app

webはLBのroleの事で、appはhttp serverです。
といっても今回はwebrole以外特に何もしていないですが、名目上分けて管理しています。

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/local/src/python/handler.py

#!/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はこちら

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以外も可能

もっと色々やれそうなので、何か進展があれば追加します。