読者です 読者をやめる 読者になる 読者になる

萬由無事覚書

不知物故萬由無事覚書仕候也。

Gentoo Linux (OpenRC) でinitスクリプトを書くには [実際編]

基本編および多重起動編から続く。

initスクリプトと設定ファイルを分離する方法をメモしたので、次は実際にサンプルのinitスクリプトを覚書。

今回は、SinatraThinを使ったWebアプリを例にしてみるよ。

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を使って必要なパッケージをインストール。

Gemfile
source "https://rubygems.org"

gem "sinatra"
gem "thin"

として適当なディレクトリに保存。

$ bundle

後は、アプリ本体のmyapp.rbとRack用ruckupファイルconfig.ruを作る。

myapp.rb
require 'sinatra/base'

class MyApp < Sinatra::Base
  get '/' do
    # アプリごとに変える。
    'Hello world from app1 !'
  end
end
config.ru
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に書いておく。

/etc/conf.d/webapp
rev_proxy=nginx
server_config=config.yml

ここでは、リバースプロクシとして使うパッケージの名前や、デフォルトのサーバ設定ファイル名などを設定する。

個別設定

各Webアプリごとに作る。

/etc/conf.d/webapp.$app_name
app_root=$app_dir

app_rootはアプリの配置されている場所を設定する。前述のとおり、/srv/webapp/$app_nameだ。

initスクリプト本体

/etc/init.d/webapp
#! /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/webapprev_proxyにパッケージ名をと指定してやれば、きちんと依存関係を構築できる。

その他の依存関係

もし、データベースサーバに依存する場合は、/etc/conf.d/webapp.$app_nameの先頭に次の行を追加する。

/etc/conf.d/webapp.$app_name
rc_need=postgresql

例として挙げたpostgresqlは、PostgreSQLのいずれかのバージョンを表す仮想サービス名。

MySQLなら、mysqlを指定しておけばいい。

応用として、他のWebアプリに依存させることもできる。

/etc/conf.d/webapp.app3
rc_need=webapp.app1

こうすれば、webapp.app1→webapp.app3の順で起動することを保証できる。

他ホスト上で動作するサービスへの依存

データベースサーバとかWebサーバとかが別ホストで動いているのはよくあること。

そういう場合、依存関係を管理するのはどうしたらいいんだろう?

今のところ、残念がらinitスクリプトだけで扱う方法はわからない。

寡聞浅学につき要情報収集であります。

多分、Nagiosとかと組み合わせて使うことになると思うのだけど……。

 

以上、実際編でした。