18
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

nginx+circus+gojiによるgolang webアプリケーションの動作環境構築

Last updated at Posted at 2016-03-24

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にて。

nginx.conf抜粋
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を呼び出しています。

nginx.confのみ更新(update-nginx-confを指定)
ansible-playbook -i local/inventory goweb.yml -t update-nginx-conf
task抜粋
- 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
handler抜粋
- 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作るコストはありますが、何してたか忘れても大丈夫ですし、他の人に引き継ぐのも比較的容易かなと思います。

参考

18
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?