Gentoo Linux (OpenRC) でinitスクリプトを書くには [実際編]
initスクリプトと設定ファイルを分離する方法をメモしたので、次は実際にサンプルのinitスクリプトを覚書。
今回は、SinatraとThinを使ったWebアプリを例にしてみるよ。
Webサーバやデータベースサーバなんかとの依存関係 (ただし同じホストで動作しているものと仮定) も織り込んでみる方向でメモメモ。
構成
複数のWebアプリが、リバースプロクシを挟んで提供される、次のような構成を例にする。
各Webアプリは、次のような名前を持つと仮定。
- app1
- app2
- app3
それぞれのWebアプリは、固有のディレクトリ
/srv/webapp/app1
/srv/webapp/app2
/srv/webapp/app3
に配置することにする。また、
- $app_name
- Webアプリの名前。
- $app_dir
- Webアプリの設置ディレクトリ。具体的には
/srv/webapp/$app_name
。 - $app_port
- Webアプリのポート番号。
- $app_host
- WebアプリやWebサーバが動作しているホスト名。
というふうに書くことにする。
Webアプリ
まずはWebアプリを作り、Thinの設定を作って起動できるようにする。この作業は、Webアプリの数だけ繰り返す。
Webアプリ本体
とりあえずBundlerを使って必要なパッケージをインストール。
source "https://rubygems.org"
gem "sinatra"
gem "thin"
として適当なディレクトリに保存。
$ bundle
後は、アプリ本体のmyapp.rbとRack用ruckupファイルconfig.ruを作る。
require 'sinatra/base'
class MyApp < Sinatra::Base
get '/' do
# アプリごとに変える。
'Hello world from app1 !'
end
end
require './myapp.rb'
run MyApp
アプリケーションは、適当な文字列を表示するだけのもの。実際にはちゃんと作る。
myapp.rb
とかconfig.ru
とかは、$app_dirに入れておく。
Thinの設定ファイル
では、次にThinの設定ファイルを作ってみる。
まず、$app_dirで次のコマンドを実行。
$ bundle exec thin --config config.yml config
これで、$app_dir/config.yml
へデフォルトの設定が書き込まれる。こんなかんじになると思う。
---
chdir: /srv/webapp/$app_name
environment: development
address: 0.0.0.0
port: 3000
timeout: 30
log: log/thin.log
pid: tmp/pids/thin.pid
max_conns: 1024
max_persistent_conns: 100
require: []
wait: 30
daemonize: true
$app_nameはWebアプリの名前になってるはず。
設定できるオプションは……だいたい見ればわかるかな? 詳しくはマニュアルを。
ここでは、port
の設定のみを変える。
ポート番号はアプリごとに変えること。
ここでは、
- app1 → 3001
- app2 → 3002
- app3 → 3003
として、$app_portで表すことにする。この部分ね。
port: $app_port
起動テスト
ここで、起動テストをしておくこともできる。
まずconfig.yml
を編集して、デフォルトではデーモン化しないようにしておく。
daemonize: false
これは、エラーが発生した時にわかりやすくするため。
起動テストは、$app_dirディレクトリで次のコマンドを実行する。
$ bundle exec --config config.yml start
起動して、http://$app_host:$app_port/
にアクセスしてWebアプリが動いていればOK。
Ctrl+C
で停止させて、config.yml
を元に戻しておくのを忘れずに。
Webサーバ (リバースプロクシ)
今回の構成では、Webサーバが直接データをブラウザに返すわけではなく、Webアプリが生成したデータをWebサーバが代理で返すようにする。
リバースプロクシだね。
こうすると、Webアプリの更新時にWebサーバ自体を再起動しなくても良くなる。
Webサーバとしては、nginxを使う。
nginxの設定ファイルは長くなりがちなので、必要なところだけピックアップ。詳しくはマニュアル等参照のこと。
...
server {
...
location /app1 {
proxy_pass http://127.0.0.1:3001;
}
location /app2 {
proxy_pass http://127.0.0.1:3002;
}
location /app3 {
proxy_pass http://127.0.0.1:3003;
}
...
}
...
実際の運用では、リバースプロクシ用拡張ヘッダを追加した方がいい (X-Forwarded-For
とか)。
同時にキャッシュの設定とかしたらいいかもね。
ここまでが前準備。
initスクリプト
いよいよinitスクリプトを書いていく。まずは設定から作ろう。
共通設定
共通設定は、/etc/conf.d/webapp
に書いておく。
rev_proxy=nginx
server_config=config.yml
ここでは、リバースプロクシとして使うパッケージの名前や、デフォルトのサーバ設定ファイル名などを設定する。
個別設定
各Webアプリごとに作る。
app_root=$app_dir
app_root
はアプリの配置されている場所を設定する。前述のとおり、/srv/webapp/$app_name
だ。
initスクリプト本体
#! /sbin/runscript
server=${RC_SVCNAME%%.*}
app_name=${RC_SVCNAME##*.}
description="Thin Web Server"
if [ ${server} != ${app_name} ]; then
description="${description}for ${app_name}"
fi
depend() {
need net # 冗長だとおもうけど、確実性のために指定しとく。
need ${rev_proxy}
}
start() {
if [ ${server} = ${app_name} ]; then
eerror "Cannot start ${RC_SVCNAME} directly"
return
fi
cd "${app_root}"
# PIDファイルの相対パスを取得。
pid_file=`awk '/pid:/ { print $2 }' "${app_root}/${server_config}"`
if [ "${RC_CMD}" = "restart" -a -e "${app_root}/${pid_file}" ]; then
ebegin "Restarting ${app_name} using Thin"
bundle exec thin --config ${server_config} restart
else
ebegin "Starting ${app_name} using Thin"
bundle exec thin --config ${server_config} start
fi
eend $?
}
stop() {
if [ "${RC_CMD}" != "restart" ]; then
ebegin "Stopping ${app_name} using Thin"
cd "${app_root}"
bundle exec thin --config ${server_config} stop
eend $?
fi
}
このinitスクリプトでは、PIDファイルがある (== 起動している) 場合のみrestart
できるようにしている。
エラーは結構無視してるので、実際には適宜エラー処理をする方がいい。
リバースプロクシとして使用するパッケージが変わった場合、/etc/conf.d/webapp
のrev_proxyにパッケージ名をと指定してやれば、きちんと依存関係を構築できる。
その他の依存関係
もし、データベースサーバに依存する場合は、/etc/conf.d/webapp.$app_name
の先頭に次の行を追加する。
rc_need=postgresql
例として挙げたpostgresql
は、PostgreSQLのいずれかのバージョンを表す仮想サービス名。
MySQLなら、mysql
を指定しておけばいい。
応用として、他のWebアプリに依存させることもできる。
rc_need=webapp.app1
こうすれば、webapp.app1→webapp.app3の順で起動することを保証できる。
他ホスト上で動作するサービスへの依存
データベースサーバとかWebサーバとかが別ホストで動いているのはよくあること。
そういう場合、依存関係を管理するのはどうしたらいいんだろう?
今のところ、残念がらinitスクリプトだけで扱う方法はわからない。
寡聞浅学につき要情報収集であります。
多分、Nagiosとかと組み合わせて使うことになると思うのだけど……。
以上、実際編でした。