WebOS Goodies

WebOS の未来を模索する、ゲームプログラマあがりの Web 開発者のブログ。

WebOS Goodies へようこそ! WebOS はインターネットの未来形。あらゆる Web サイトが繋がり、共有し、協力して創り上げる、ひとつの巨大な情報システムです。そこでは、あらゆる情報がネットワーク上に蓄積され、我々はいつでも、どこからでも、多彩なデバイスを使ってそれらにアクセスできます。 WebOS Goodies は、さまざまな情報提供やツール開発を通して、そんな世界の実現に少しでも貢献するべく活動していきます。
Subscribe       

supervisord を使って Tornado をデプロイする

先日、 Tornado の使い方を解説しました。本日はその続きで、デプロイについて書きます。 Tornado で構築したアプリケーションを本格的にデプロイするためには、プログラムをデーモン(バックグラウンドプロセス)化する必要があります。 Tornado 自体にはその機能がないのでググってみると、どうやら Supervisor というプログラムを利用するのが一般的なようです。

この Supervisor がまた便利で、複数の通常アプリケーションをバックグラウンドで起動でき、その開始・終了を個別に管理できます。汎用的に作られているので Python 以外で構築されたアプリケーションも管理できますし、異常終了時の再起動やログのローテートの面倒もみてくれます。覚えておけばなにかと役に立ちそうですから、 Tornado アプリケーションのデプロイを題材にして使い方をまとめることにしました。

Supervisor とは

Supervisor は、 Python で構築されたプロセス管理システムです。複数のプログラムをバックグラウンドで起動し、それぞれ個別に起動・終了などを管理できます。常駐のデーモンである supervisord と管理 UI となる supervisorctl に分かれており、 TCP 通信によるリモート管理にも対応しています。その他、以下の特徴があります。

  • フォアグラウンドで動作する通常のプログラムをバックグラウンドで起動できる
  • 複数の管理対象プログラムをグループ化し、まとめて起動・終了が可能
  • 起動・終了の順序(優先順位)も制御できる
  • 異常終了した管理対象プログラムは自動で再起動する
  • 管理対象プログラムの標準出力・エラー出力を supvervisorctl で表示できる
  • Web ブラウザからも管理可能
  • イベントシステムによって各プロセスのモニタリング等が可能

今回は単に Tornado アプリケーションをデーモン化する目的に限定して解説しますが、ほかにも多くのことができます。機会があれば、それらも試していきたいです。

インストール

インストールは easy_install 等を利用すれば簡単にできる…ようなのですが、私はいつもどおりシステムにはインストールせず、手動でプロジェクトに含める形にしました。基本的には、 Supervisor パッケージをダウンロードして setup.py でビルドし、 PYTHONPATH の通った場所にコピーするだけです。

curl -O http://pypi.python.org/packages/source/s/supervisor/supervisor-3.0a12.tar.gz
tar zxvf supervisor-3.0a12.tar.gz
cd supervisor-3.0a12
python setup.py build
cp -R build/lib/supervisor /path/to/your/project

ブラウザ経由の管理コンソールなどの機能も使うなら meld3elementtree といったモジュールも必要ですが、それらが不要ならば supervisor だけで大丈夫なようです。ただし、起動時に meld3 名前空間をインポートしようとするので、ダミーのモジュールを作って誤魔化しておきます ^^;

cd /path/to/your/project
mkdir meld3
touch meld3/__init__.py

だいぶ手抜きですが、以上でインストールは終了です。

設定ファイルの作成

Supervisor で外部プログラムを管理するには、設定ファイル (supervisord.conf) を作成します。設定ファイルは以下の順序で検索されます。

  • 起動オプションの -c で指定されたパス
  • カレントディレクトリの supervisord.conf
  • カレントディレクトリの etc/supervisord.conf
  • /etc/supervisord.conf

したがって、私のようにプロジェクト内にすべてを収めたい人はプロジェクトのルート、もしくは etc ディレクトリに supervisord.conf を作成しておけば便利、ということですね。記述内容は supervisor/skel/sample.conf にサンプルがあるので、それをコピーして必要な箇所を変更するのがよいでしょう。

cd /path/to/your/project
cp supervisor/skel/sample.conf ./supervisor.conf

設定ファイルは Windows の .ini ファイルと同様な形式です。多数のセクションがありますが、今回使うものは以下のとおり。

セクション説明
unix_http_serverローカルの supervisorctl からアクセスするための UNIX ドメインソケットの設定
supervisordデーモン (supervisord) の設定
supervisorctl管理用コマンドラインツール (supervisorctl) の設定
program:〜管理対象の外部プログラムの設定
rpcinterface:〜RPC インターフェースの追加

以下、それぞれのセクションについて簡単に説明していきます。

unix_http_server セクション

supervisord と supervisorctl の通信に使う UNIX ドメインソケットの設定です。 supervisord と superviserctl の両方が同じマシン上で動作しているときに使われます。今回は割愛しますが、ネットワーク経由でリモート管理する場合には TCP ソケットを設定する inet_http_server セクションの記述が必要です。

unix_http_server セクションの設定項目は以下のとおりです。

設定項目説明
fileソケットファイルのパス名
chmodソケットファイルのパーミッション(デフォルト 0700)
chownソケットファイルの所有者(デフォルトは supervisord を起動したユーザー)
username認証用のユーザー名
password認証用のパスワード(生のパスワード、もしくは「{SHA}パスワードの SHA-1 ハッシュ」の形式で指定

inet_http_server のみを使う場合を除いて、 file の指定はほぼ必須です。これがないと supervisord と supervisorctl が通信できなくなってしまいます。

supervisord セクション

supervisord の設定を記述するセクションです。ログローテートの設定や実効ユーザーの指定など、サーバーでよくある設定項目が並びます。

設定項目説明
logfileログファイルのパス名(デフォルトはカレントディレクトリの supervisord.log)
logfile_maxbytesログファイルの最大ファイルサイズ(デフォルト 50MB)
logfile_backupsログローテートで残すログファイルの世代数(デフォルトは 10 で、 0 なら削除しない)
loglevel出力するログレベル(デフォルト info)
pidfilepidファイルのパス名(デフォルトはカレントディレクトリの supervisord.pid)
umasksupervisord プロセスの umask(デフォルト 022)
nodaemontrue を指定すると、 supervisord をデーモン化せず、フォアグラウンドで起動する
minfdssupervisord の起動に必要なファイルディスクリプタの数(デフォルト 1024)
minprocssupervisord の起動に必要なプロセスディスクリプタの数(デフォルト 200)
nocleanuptrue にすると、起動時に古い子プロセスログを削除しない
childlogdir子プロセスのログファイルを作成するパス(デフォルトは Python の tempfile.get_tempdir() の値)
usersupervisord の実効ユーザー(root で起動した時のみ有効)
directoryカレントディレクトリの指定
strip_ansitrue を指定すると、ログのエスケープシーケンスを削除する
environment子プロセスに引き渡す環境変数を key1=value1,key2=value2 の形式で指定
identifiersupervisord プロセスの識別子

このセクションのほとんどは supervisord のコマンドラインオプションでも指定できるので、デフォルトを変更したいもののみ記述すればよいかと思います。

supervisorctl セクション

コマンドラインの管理インターフェースである supervisorctl の設定を行うセクションです。

設定項目説明
serverurl接続する supervisord の URL (デフォルトは http://localhost:9001)
username認証用のユーザー名
password認証用のパスワード
promptプロンプトとして表示する文字列(デフォルトは supervisor)
history_filereadline ライブラリで使う履歴ファイルのパス名(デフォルトでは履歴ファイルを作成しない)

serverurl の指定は unix_http_server セクションの file に指定したパスの前に「unix://」をつけたものを指定します(ローカルの supervisord に接続する場合)。 username と password にも unix_http_server の同名セクションと同じ物を指定しておかないと接続できないので注意してください。

program:〜

管理する外部プログラムの情報を記述するセクションです。実際のセクション名は「program:<任意の識別名>」となり、識別名を変えることで複数の外部プログラムの情報を記述できます。

設定項目説明
command起動する外部プログラムを指定します。必要に応じてオプションも指定可能
process_nameプロセスの名前を定義する(numprocsが1以上の場合に必須で、 %(process_num)s を含めること)
numprocs起動するプロセス数(デフォルト 1)
numprocs_startプロセス番号のオフセット(デフォルト 0)
priority起動・終了の優先順位(値が小さいほど先に開始し、後に終了する。デフォルト 999)
autostartfalse を指定すると supervisord の起動時に自動起動しなくなる
autorestartプロセス終了時の再起動の指定(true なら常に再起動、 false なら常に再起動しない、 unexpected なら 終了コードが exitcodes のあるもの以外なら再起動)
startsecsここに指定した秒数より早く終了したら、終了コードにかかわらず起動失敗とみなす(デフォルト 1)
startretries起動に失敗した場合にリトライする回数(デフォルト 3)
exitcodes正常終了とみなす終了コード(デフォルト 0, 2)
stopsignalプロセスを終了させるために使うシグナル(デフォルト TERM)
stopwaitsecsこの秒数が経ってもプロセスが終了しない場合、 SIGKILL を送信する(デフォルト 10)
killasgrouptrue なら、 SIGKILL を送信する際にプロセスグループ全体に送信する
userプロセスを実行するユーザー(supervisord が root で実行されているときのみ)
redirect_stderrtrue なら、標準エラー出力を supervisord の標準出力にリダイレクトする
stdout_logfile標準出力ログのファイル名
stdout_logfile_maxbytes標準出力ログがこのサイズを超えたらローテートする(デフォルト 50MB)
stdout_logfile_backupsログローテートで残す世代数
stdout_capture_maxbytesCapture Mode 時のバッファサイズ(デフォルト 0)
stdout_events_enabledtrue を指定すると、標準出力への出力時に PROCESS_LOG_STDOUT イベントを発生させる
stderr_logfile標準エラー出力のファイル名
stderr_logfile_maxbytes標準エラー出力ログがこのサイズを超えたらローテートする(デフォルト 50MB)
stderr_logfile_backupsログローテートで残す世代数
stderr_capture_maxbytesCapture Mode 時のバッファサイズ(デフォルト 0)
stderr_events_enabledtrue を指定すると、標準エラー出力への出力時に PROCESS_LOG_STDERR イベントを発生させる
environment環境変数を key1=value1,key2=value2 の形式で指定
directory起動時のカレントディレクトリの指定
umaskumask の指定
serverurlSUPERVISOR_SERVER_URL 環境変数に渡す値(デフォルトは supvervisord の UNIX ドメインソケットの URL)

このセクションは最低限 command さえ記述すれば使えるはずです。あとは必要に応じて定義すればよいでしょう。

rpcinterface:〜

このセクションは supvervisord が使用する RPC インターフェースを拡張するためのものです。通常は以下の記述を含めておけば OK です。

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

Tornado アプリを起動する設定ファイルの例

設定ファイルの記述方法がわかったので、 Tornado アプリケーションを起動するための設定を書いてみます。 Tornado アプリケーションのエントリファイルは main.py で、 --port オプションで listen するポート番号が指定できると仮定しています。

[unix_http_server]
file=/tmp/supervisor.sock

[supervisord]
logfile=%(here)s/log/supervisord.log
pidfile=%(here)s/log/supervisord.pid

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///tmp/supervisor.sock

[program:tornado]
command=python
	%(here)s/main.py
	--port=%(process_num)s
	--logging=info
	--log_file_prefix=%(here)s/log/%(program_name)s-%(process_num)s.log
process_name=%(program_name)s-%(process_num)s
directory=%(here)s
numprocs=4
numprocs_start=8080
stdout_logfile=NONE
stderr_logfile=NONE

何ヶ所か %(〜)s という記述を使っていますが、これは変数展開です。括弧の中に変数名を記述し、括弧に続けて Python のフォーマット文字列を指定します。変数名としては以下のものが使えます。

変数名説明
here設定ファイルがあるディレクトリのパス名
program_nameprogram:〜 セクションの「〜」部分
group_namegroup:〜 セクションのグループ名(だと思う ^^;)
process_numプロセス番号
host_node_namePython の platform.node() の値
ENV_環境変数名supervisord に与えられた環境変数の値

また、上記では Supervisord によるロギングを無効にして Tornado 側でログを出力していますが、そのほうがオーバーヘッドが少ないかなと思ってそうしただけで、あまり深い考えはありません。とくに開発中は supervisord のログ機能を使ったほうが supervisorctl でログの閲覧とかもできて便利かも。

supervisord の起動

以上で準備ができたので、 supervisord を起動してみましょう。 easy_install や setup.py install できちんとインストールしたなら supervisord コマンドで起動できるのですが、今回はビルドしたものをプロジェクトディレクトリにコピーするというイレギュラーな方法をとっているので、以下のコマンドで起動する必要があります。

cd /path/to/your/project
PYTHONPATH=. python supervisor/supervisord.py

これで、 supervisord が起動し、同時に設定ファイルの指定に従って管理対象のプログラムも順次起動されます。起動オプションとしては以下のものが指定できます。ほとんどは設定ファイルの supervisord セクションの指定と同じです。

オプション説明
-c設定ファイルのパス
-nフォアグラウンドで起動
-h使い方を表示
-u実効ユーザー
-mumask を指定
-dカレントディレクトリの指定
-lログファイルのパス
-yログファイルの最大サイズ
-zログの世代数
-eログレベル
-j.pid ファイルのパス
-isupervisord の識別子
-q管理対象プログラムのログの出力先ディレクトリ
-k管理対象プログラムのログの自動削除をしない
-a起動に必要なファイルディスクリプタの数
-tログからエスケープシーケンスを削除する
-vsupervisord のバージョン番号を表示
--profile_optionsプロファイリングの指定
--minprocs起動に必要なプロセスデスクリプタの数

supervisord をマシンの起動時に自動起動させたいなら、単に /etc/rc.local などで上記のコマンドを実行すればよいでしょう。

supervisorctl による管理

supervisorctl を使うと、各プログラムの状態の確認や、起動・終了・再起動といった管理が可能です。 supervisord と同様に普通は supervisorctl コマンドとして起動できるのですが、ここでのインストール方法では以下の方法で起動します。

cd /path/to/your/project
PYTHONPATH=. python supervisor/supervisorctl.py

すると、 supervisor> というプロンプトとともにコマンドの入力状態になり、インタラクティブに各プロセスの管理が行えます。例えば、 status コマンドを実行すると、各プロセスの状態が表示されます。

supervisor> status
tornado:tornado-8080             RUNNING    pid 880, uptime 0:00:21
tornado:tornado-8081             RUNNING    pid 881, uptime 0:00:21
tornado:tornado-8082             RUNNING    pid 882, uptime 0:00:21
tornado:tornado-8083             RUNNING    pid 883, uptime 0:00:21

利用できる主なコマンドは以下のとおりです。

コマンド説明
start <name>プログラムを起動
stop <name>プログラムを終了
restart <name>プログラムを再起動
fg <process>process をフォアグラウンドにする
status各プログラムの状態を表示
rereadデーモンの設定ファイルを再読み込みする
reloadsupervisord をリスタート
shutdownsupervisord を終了させる
quitシェルを終了
help使い方を表示

コマンドは supervisorctl.py のコマンドラインでも指定できます。例えば Tornado のすべてのプロセスを再起動するには以下のようにします。

PYTHONPATH=. python supervisor/supervisorctl.py restart 'tornado:*'

この方法ならインタラクティブシェルを起動せずにコマンドが実行できるので、シェルスクリプトなどを利用した自動化も簡単です。

以上で Supervisor を使って Tornado アプリケーションをデーモン化し、バックグラウンド動作させることができました。今回利用した Supervisor の機能はごく一部ですが、それでもたいへん便利です。今後、デーモン化や各種サーバー管理の手段として活用していこうと思っています。

関連記事

この記事にコメントする

Recommendations
Books
「Closure Library」の入門書です。
詳しくはこちらの記事をどうぞ!
Categories
Recent Articles