プライベート用途でFreeBSDでApache+mod_wsgi+flask
という構成でサーバを建てていたのだけど、gunicornを使ってみようか、と思い始めてきました。
gunicornであれば、アプリケーションの再起動もApacheを再起動しなくても済みますしね。まぁプライベートなんで、再起動しても問題ないんですが。
で、gunicornを使うには、gunicornをサービスプログラムとして起動させなきゃいけません。
ubuntuでも使っていたのだけど、その場合にはsystemdの情報がいくらでもあったので困らないのだけど、FreeBSDの場合、古い情報だったり、英語だったりして、意外に少ない。
あいや、英語だからダメ、じゃコンピュータの仕事をしている人間としてアレなんで、何とか動かすところまで頑張ってみました。
頑張ったので、ホントに動かしてみただけ、正しいやり方かどうかまでは知らないので、あしからず。
とりあえず、本家のまず見るべき資料。
なお、以下は以下の環境で行いました。
FreeBSD test01.localdomain 12.2-RELEASE FreeBSD 12.2-RELEASE r366954 GENERIC amd64
テストアプリケーション
テストするwsgiアプリケーションを以下のように用意しました。
# coding: utf-8
from wsgiref.simple_server import make_server, demo_app as application
if __name__ == '__main__':
with make_server('', 8080, application) as server:
server.serve_forever()
pythonには、標準でwsgiref.simple_server
という、テスト用のwsgiアプリケーションのモジュールが用意されています。さすが、Python様だねっ!
これを動かすために、python3をインストールします。
pkg install python3
まずは、テストアプリケーションのみを動かしてみます。
python3 /public/testweb/testweb.py
これで、ブラウザより8080ポートにアクセスすると、「Hello world!
」のメッセージと、環境変数が表示されます。
特に、「SERVER_SOFTWARE = 'WSGIServer/0.2'
」のところに注目。
gunicornのインストール
gunicornをインストールします。
pkgでインストールしてもいいし、pipでインストールしてもいいし。この件に関しては、今回は置いときます。
今回は、pkgでインストールします。
pkg install py39-gunicorn
serviceスクリプトの作成
さて、ここからが本題。
「/usr/local/etc/rc.d
」ディレクトリ以下に、testweb
という名前でスクリプトを作成します。
#!/bin/sh
# PROVIDE: testweb
# REQUIRE: NETWORKING
. /etc/rc.subr
name=testweb
rcvar=${name}_enable
testweb_user=katsuko
pidfile="/var/run/${name}/${name}.pid"
command="/usr/sbin/daemon"
command_args="-P ${pidfile} -r -f /usr/local/bin/gunicorn --bind '0.0.0.0:8080' --pythonpath /public/testweb testweb"
load_rc_config ${name}
run_rc_command "$1"
コメントにある「PROVIDE
」は提供するサービスの名前、「REQUIRE
」は依存する他のサービス名(rcスクリプト)だそうです。(ここより参照)
gunicornはwebサービスなので、NETWORING
を指定しました。
デフォルトではrootユーザでサービスプログラムが実行されますが、できればrootで動かしたくはないので、その場合には「サービス名_user」に実行ユーザを指定します。
なお、「pidfile
」に「/var/run
」ディレクトリ以下にディレクトリを作って、そこにファイルを作るようにしているのは、「/var/run
」ディレクトリはroot権限でしかファイルを作成できないので、ディレクトリを作って実行ユーザに権限を与えます。
さて、肝となるのは「command
」と「command_args
」。
これは、サービスの開始コマンドで、以下のように実行されます。
${command} ${command_args}
このコマンドにより実行されたプロセスは、
- 実行中のサービスプログラムのプロセスIDを、「
pidfile
」で指定したファイルに書き込む。 - コマンド自体は、即座に終了しなければならない。(つまり、サービスプログラム自体はforkして動かなければならない)
という必要条件があります。
status
/stop
サブコマンドを実行した際、サービスプログラムのプロセスを探すわけですが、それは pidfile
に書かれたプロセスIDで、かつプロセスコマンドがcommand
のものかどうか(もしくは、procname
が指定されていればそちらを参照する) で判断されます。
(サービスプログラムがcommand
での実行と違う場合には、procname
を指定する必要があります)
この辺りが正しくないと、start
は出来ても、status
/stop
は出来ず、散々悩むことになります。てか、悩みました。
さて、gunicornはプロセスIDをファイルに出力する設定はありますが、サービスをデーモンプロセスとして起動するやり方は見つけられませんでした。(もしあれば、失礼 ちゃんとありますがな。)
なので、daemon
コマンドを使用します。
daemon
コマンドは、上記の条件を満たしてくれる、とってもありがたいコマンドです。
最後に、このスクリプトに実行権を与えます。
chmod a+x /usr/local/etc/rc.d/testweb
これで、後はいつものサービス通り、「/etc/rc.conf
」に「testweb_enable="YES"
」を書き込み、「service testweb start
」でサービスを起動し、またブラウザでアクセスしてみます。
先程のように「Hello world!
」と環境変数地が表示されますが、中には「SERVER_SOFTWARE = 'gunicorn/20.1.0'
」という表示に変わっているはずです。
これで、gunicornが動いていることを確認できます。
終わりに
今回はシンプルな(と思われる)スクリプトの書き方をしましたが、その気になればstart
コマンドなどを上書きしてしまうことも出来ます。
まぁそこまでやりたいのであれば、「man rc.subr」を読んだり、「/etc/rc.subr
」自体を読みましょう。
自分は疲れたので、ここまでにします。