Goで書いたWebサーバを本番(VPS)で動かす

最近、趣味で書くWebアプリは、いわゆるSPAで、バックエンドのJSONを返すAPIにはGo、フロントエンドは仕事で使うための検証・学習を兼ねて適当なJavaScriptフレームワークを選択している。

この記事は、Goで書いたWebサーバを本番(自分の場合、OSがCentOSVPS)でどうやって動かすかという内容。

プロセス管理ツール

プロセスを複数立ち上げたり、プロセスが死んだ時に再び起動したりしてほしかったので、プロセス管理ツールを検討した。 色々調べたところ、circusが以下の点でよさそうだった。

  • プロセスまわりで癖のある(forkできないなど)Goと組み合わせて使うプロせス管理ツールとして実例を多く見かけた
  • 設定で記述する内容が他のプロセス管理ツールと比べて少ない(ような気がする)
  • (fdを受けとるようにアプリケーションコードを変更しないといけないのがちょっと微妙だけど)プロセス管理のプロセスがMaster-WorkerモデルのMasterプロセスを兼ねることになるのでプロセスの関係がシンプルになる
  • hotdeployを実現できる

circusそのものの振る舞いの説明は、catatsuyさんの Golang_ads_deliver // Speaker DeckGolang_ads_deliver // Speaker Deck が分かりやすかった。

インストールはドキュメント通りpipから。 (refs: Installing Circus — Circus 0.12.2 documentation )
circusのdaemon自体のマネージは、upstartやsystemdを使う。ドキュメントに設定例が載っているので(refs: Deployment — Circus 0.12.2 documentation )、参考にしつつ自分の事情に合わせて修正した。

(自分しか使わない趣味のアプリでプロセス管理ツールを使う必要があるか微妙な気もしてたんですが、herokuゆとりになってしまってここ最近まともにそういうことをやっていなかったのと、普通に楽しいのでいいかな、と)

設定ファイルの記述例

[circus]
statsd = 1

[watcher:webapp]
working_dir = /usr/local/foobar
cmd = ./current
args = --fd $(circus.sockets.web)
numprocesses = 3
use_sockets = True
copy_env = True

stdout_stream.class = FileStream
stdout_stream.filename = /var/log/foobar_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/foobar_stderr.log
stderr_stream.refresh_time = 0.3
stderr_stream.max_bytes = 1073741824
stderr_stream.backup_count = 2

[socket:web]
host = 0.0.0.0
port = 8000

サーバー構成

HTML, CSS, JavaScript等の静的ファイルはGoで書いたサーバの前段にリバースプロキシ(nginx)を置いて配信している。
JSONを返すAPIのパスにリクエストがきたら、後ろのGoで書いたサーバにまわす。

serverディレクティブにunix domain socketに指定しようとしたけど、自分しか使わないアプリだしパフォーマンスはそこまで求めていないのと、ちょっと面倒そうだったのでローカルのポートを指定するようにした。その代わり、TCPのコネクションを張るコストを少しは意識してkeepaliveディレクティブを指定している。

upstream backend {
  server 127.0.0.1:8000;
  keepalive: 16;
}

8/12に追記: unix domain socketにするのそんなに面倒じゃなかった。(refs: Configuration — Circus 0.12.2 documentation ) circusの[socket:web]のセクションを以下のようにするだけ。 pathを指定すれば、勝手にfamilyはAF_UNIXとして扱ってくれるらしいけど、念のため明示しておく。

[socket:web]
path = /var/run/foobar.sock
family = AF_UNIX

デプロイ

デプロイは、シンプルなデプロイツールのfabricを使って、手元のPCでCentOS用にビルドしたバイナリの転送&リモートでcircusctlを実行をするだけ。

本番(VPS)のほうでアプリケーションの実行環境をあれこれ用意する必要がないのでだいぶ楽。
いま動いているバイナリが何者かわからなくなると困るので、ここ( golang - ビルドする際にバージョン情報を埋め込む - Qiita )を参考にバージョン情報を埋め込んでいる。godepで依存管理しているので、アプリケーションのリビジョン番号が分かれば、依存しているライブラリのバージョン(リビジョン番号)も分かる。

参考にしたサイト・スライドなど