golangを使ったwebアプリケーションの動作環境構築をansible playbookに落としました。Debian 8(jessie)向けです。
参考環境(Mac想定)
vagrant上に構築するplaybookをgithubに上げています。※HTTPS(TLS)の設定は入っていません。
必要なもの
- virtualbox(公式からdmg)
- vagrant(公式からdmg)
- ansible(homebrewにて
brew install ansible
)
# cloneしてきて中に移動
git clone https://github.com/reiki4040/playbooks
cd playbooks
# 仮想マシン立ち上げ。(192.168.20.11で起動するので、かぶるとかの場合はVagrantfileいじってください)
# jessieのイメージを指定していて、大きめサイズが落ちてくるのでモバイル環境では注意
vagrant up
# 仮想マシンへのsshの設定追加
vagrant ssh-config >> ~/.ssh/config
# ansibleで自動構築
ansible-playbook -i local/inventory goweb.yml
# apt-get/pip/nginx-buildあたりがしばらくかかります。
# 仮想マシンに入る
vagrant ssh
# ローカルからcurlで確認
curl http://localhost/api/hello
# hello I'm goji! が返ってきます
ポイントをいくつか
- nginx側でhttpsを受け取って、goji側へはhttpで接続(playbook構築分にはhttpsは入っていません)
- nginx - goji間はunix domain socketを使ってオーバーヘッドを低減
- goji+circusを使って、graceful restartを実現
- nginx, circusはsystemdで管理(debian jessie向け)
- これらをansibleを用いて自動構築
簡単なリクエストフローとプロセス管理イメージ
ポイント詳細
nginxの設定
upstreamにunix domain socketのファイルパスを指定して、それをlocationディレクティブで、proxy_passを使って、gojiに投げています。unix domain socketの作成は、後述のcircusにて。
upstream goweb_upst {
server unix:/var/run/goweb.sock;
}
# 中略
location ~^/api/.*$ {
proxy_pass http://goweb_upst;
proxy_set_header X-Forwarded-Host $host;
}
unix domain socketを使ってオーバーヘッドを低減
nginxとgojiを繋ぐ時に、unix domain socketを使用することで、通信のオーバーヘッドを減らします。ただし、nginxとgojiは同じサーバに同居する必要があります。
場合によっては、ややパフォーマンスを犠牲にし、nginxとgojiのサーバを分け、nginx N台+goji M台といった別比率の構成にするのもアリです。
nginxを挟む理由
goji(golang)を世界と直接話させない理由は、フロントの処理をnginxに任せ、golangでわざわざ作らないためです。httpsを受ける程度であればいいんですが、簡易的なアクセス制御、静的配信、リバースプロキシ等、豊富な機能がnginxにはあり、一旦nginxでできることは、nginxに任せましょう。細かいことやりたくなった時にgolangで実装なり。
goji + circus
gojiは、graceful shutdownを実装していて、これにcirucsのsocketを組み合わせることでgraceful restartが実現できます。
この辺りは参考に入れているリンクが詳しいのでそちらを参照していただければと思いますが、簡単に説明すると
- golangでsocketを作ると、どうしてもプロセスを再起動する際には閉じてしまう。
- circusがsocketを作り、渡すことで、golangプロセスの再起動時にもsocketが維持できる。
- なおかつcircusが新しいgolangプロセスを起動してから、古いプロセスを殺すので、途切れない(らしい)
gojiのserveの仕方に注意
graceful shutdownについての注意点は、gojiのServe()
,ServeTLS()
またはServeListener()
を使わないといけません。
gojiを使っていても、golang標準のhttp.ListenAndServe()
等で起動すると、サーバ自体は起動して使えますが、graceful shutdownにならず、処理途中で切断されます。
Circusでgolangアプリケーションを動かす設定(circus.ini)
[circus]
statsd = 1
[watcher:goweb]
cmd = /opt/goweb/goweb -fd $(circus.sockets.web)
stop_signal = SIGINT
numprocesses = 1
use_sockets = True
stdout_stream.class = FileStream
stdout_stream.filename = /var/log/goweb/stdout.log
stdout_stream.refresh_time = 0.3
stdout_stream.max_bytes = 1073741824
stdout_stream.backup_count = 2
stderr_stream.class = FileStream
stderr_stream.filename = /var/log/goweb/stderr.log
stderr_stream.refresh_time = 0.3
stderr_stream.max_bytes = 1073741824
stderr_stream.backup_count = 2
[socket:web]
path = /var/run/goweb.sock
family = AF_UNIX
-
[watcher:goweb]
のgowebは、circus上で動かすプログラムの名称です。circusctl reload goweb
などで使います。 - gojiのgraceful shutdownはSIGINTなので、
stop_signal = SIGINT
を指定しています。(circusデフォルトは、SIGTERM) - 通信用のsocketは
[socket:web]
で宣言して、cmdの-fd $(circus.sockets.web)
で渡しています。 - stdoutとstderrをファイルに出力しています。今回のサンプルだと、gojiのデフォルトの動作ログがstderrに出力されます。stdoutには私が適当に出したリクエスト時のmiddlewareのログが出ます。
nginx, circusはsystemdで管理
debianは8(jessie)から、systemdに移行しました。init.d以下にスクリプトをおいても、start/stop/restartなどは動きますが、systemdのservice unitとして定義しています。内容は公式の案内通りなので、他のオプション等調べておきたいところです。
これらを、/etc/systemd/system/
以下に置くと、systemctl start circus
といった感じで使えます。debian 7(wheezy)だと、/etc/init.d/
とservice
コマンドの関係みたいなものです。
unitファイルを更新した時はsystemctl daemon-reload
systemd用のunitファイルを更新した場合は、systemctl daemon-reload
を呼ばないと失敗します。最初単にsystemctl reload circus
呼んだらfailしました。
Warning: Unit file of circus.service changed on disk, 'systemctl daemon-reload' recommended.
Job for circus.service failed. See 'systemctl status circus.service' and 'journalctl -xn' for details.
ansible
taskにtagをつけて、全部流す時(構築時)と設定ファイルの更新など、実行するタスクをわけることができます。
ansible-playbook -i local/inventory goweb.yml
必要に応じて、個別にタグをつけて、reload/restartが必要な場合は、notifyをつけてhandlerを呼び出しています。
ansible-playbook -i local/inventory goweb.yml -t update-nginx-conf
- name: nginx | put nginx.conf
tags:
- nginx
- update-nginx-conf
template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf owner=root group=root mode=644
notify:
- reload nginx
- name: reload nginx
service: name=nginx state=reloaded
単独で流す場合のタグ
タグ | 内容 |
---|---|
goweb | goweb構築のみ |
update-goweb-bin | gowebのバイナリをアップデート |
update-circus-ini | circus.iniをアップデート |
update-circus-systemd-unit | circusのsystemd unitファイルをアップデート |
nginx | nginx構築のみ |
update-nginx-conf | nginx.confのアップデート |
update-nginx-systemd-unit | nginxのsystemd unitファイルをアップデート |
開発中に何回も流す場合は、aliasやラッパースクリプトで省略することをお勧めします。
まとめ
nginx + circus + gojiを使って、golangのWebアプリケーションを動作させることができます。基本的な動作環境が出来上がるので、あとはアプリケーションの開発を進めていくことができます。
ちなみに、graceful restart気にしなければ、nginxとgolangを直接systemdで動かすのもシンプルです。
ansibleを使って構築すると、毎回デプロイしたり変更したりする時にも使えるので、効率が良いです。playbook作るコストはありますが、何してたか忘れても大丈夫ですし、他の人に引き継ぐのも比較的容易かなと思います。