目の前に僕らの道がある

勉強会とか、技術的にはまったことのメモ

Plack/PSGIなwebアプリケーションの実行環境

この記事は、モバイルファクトリー Advent Calendar 2015 11日目の記事です

※ 投稿内容は私個人の意見であり、所属企業・部門見解ならびに技術戦略を代表するものではありません。

昨日は@rymizukiさんのnpmライブラリの運用と管理についてでした。今日はPerlの話です。

お仕事やプライベートでPerlのwebアプリケーションを書くことが多く、いろいろ知見が溜まってきてるので、ここで少し紹介しようと思います。 今回はPlack/PSGIなwebアプリケーションの実行環境の話です。mod_perlなアプリケーションとはちょっとコンテキストが違います。 少しかっちりコンテキストに近いです。個人で軽くwebアプリケーション立てるならもう少しゆるふわでも問題ないはずです。

OS

UbuntuのLTSを使うことが多いです。Ubuntu前提の内容が後に続きます。

Perl

System Perlは使ってません。OS/ディストリビューションが変わってもなるべくそのまま動くようにしたいためです。 perl-buildで独自ビルドしたPerlを使います。インストール場所としては、 /usr/local/perl/perl-5.${VERSION} に置きます。 Perlを独自ビルドしたものをDebian package化して実行環境にはインストールします。

他の方法としては、ビルド済みのperlをtarで固めて、配布するというのもあります。 どちらでも構わないのですが、ローカルネットワークにaptサーバ立てている関係で、Debian packageの方が運用しやすいのです。 また、perlのマイナーバージョンアップの際もDebian packageを作り直した上で、 apt-get upgrade (or aptitude safe-upgrade)で完結するので、aptの操作に慣れていて楽というのもあります。

モジュール管理

今風にcpanfileでモジュール管理してます。モジュールインストールはCartonを使ってます。

Cartonの後継でCarmelも開発されてます。個人的にはそろそろ触っておきたいところです。

また、cpanfile.snapshotもレポジトリに入れています。 一般的なモジュールは特定の(古い)バージョンに依存せずに動くべきですが、 依存モジュールのバージョン違いによって現在動いているアプリケーションが壊れるのを防ぐために、バージョン固定します。 cpanfile.snapshotがある状態で下記のように carton install してあげると、どの環境でも同じバージョンの モジュールがインストールされます。

carton install --deployment --without develop,test

今やってないですが、別方法としては、モジュールがインストール済みの状態で、 carton bundle すると vendar/ にモジュールのtarが固められるので、それもレポジトリ管理した上で、下記の様にインストールするという手もあります。インストールの際は vendor/bin/carton にfatpackされたcartonコマンドが入るのでそれを使います。 (アプリ実行環境にcartonを敢えて入れる必要は無い)

# 依存モジュールを固める
carton bundle

# インストール
# env.shは後述
./script/env.sh vendor/bin/carton install --cached --deployment --without develop,test

さらに別方法としては、ビルドサーバで依存モジュールをビルドした上で、ディレクトリごと実行環境にrsyncしてあげる方法です。 ビルドサーバを運用しているならば、この方法でも良いでしょう。

参照

独自モジュール

なるべく、独自モジュールは使わない方が良いのですが、個人的な事情などで、CPANに公開出来ないモジュールに関しては、OrePAN2 でDarkpanを作ってそこからローカルに配信するようにしてます。 OrePAN2のサーバを簡単に立ち上げられるOrePAN2::Serverがありますが、一時期は使っていましたが、モジュールのアップロード機能は別にいらないなどの理由で今はwebサーバから静的配信してます。

環境変数

プロジェクトのレポジトリconfig/env.rc という名前で、アプリケーションを動かすために必要な環境変数を定義したファイルを作ります。

PERL5_VERSION="22"
export PROJECT_BASE="/path/to/project"
export PERL_CARTON_MIRROR="http://orepan.local/"
export PERL5LIB="${PROJECT_BASE}/local/lib/perl5:${PROJECT_BASE}/lib"
export PATH="${PROJECT_BASE}/local/bin:/usr/local/perl/perl-5.${PERL5_VERSION}/bin:${PATH}"
export PLACK_PORT=5555

また、 script/env.sh という名前で config/env.rc を読み込んだ上で、プログラムを実行するラッパースクリプトを作ります。 スクリプトなどは基本的にこれを通して実行します。

#!/bin/bash -ue
# 諸々環境変数を設定した上でコマンドを実行する君
#
#       env.sh perl hogehoge.pl
#
source /path/to/project/config/env.rc
exec "$@"

開発環境で、いちいちラッパースクリプト通すのが面倒な場合は、config/env.rc のsymlinkをプロジェクトルートに .envrc として張った上で、direnv使って済ましてしまう場合もあります。

web サーバ起動スクリプト

psgiファイルを plackup するのではなく、こんな感じのスクリプトscript/web みたいな名前で 用意してアプリケーションサーバを起動するようにしてます。

#!/usr/bin/env perl
use strict;
use warnings;

use lib "$ENV{PROJECT_BASE}/lib";
use Plack::Loader;
use SomeApplication::Config;
use SomeApplication::Web::Handler;

my $config = SomeApplication::Config->load();
my $app    = SomeApplication::Web->to_app();

Plack::Loader->load(
    $config->{psgi}->{server},
    %{ $config->{psgi}->{config} },
)->run($app);

また、このスクリプトstart_serverを経由して起動することで、(graceful restartによる)ホットデプロイをできるようにしてます。 start_server のプロセスにSIGHUPを送ると子プロセスのアプリケーションサーバを再起動してくれるのですが、 plackup コマンドで起動してると start_server に渡した引数をそのまま使ってplackup を再起動するので、 max_workers の数を変えたいときなど、 start_server 自体のプロセスを再起動しなくてはならないので不便です。 なので、起動スクリプトを作ってます。そのほかにも理由があるのですが、参照リンクに詳しくあります。

サーバ実装としては、StarletGazelleを使ってます。

参照

デーモン管理

現在はUpstartアプリケーションサーバのデーモン管理してます。 以下の理由で、個人的には好きでした(過去形)。最新のUbuntuはSystemdに変わってしまったので、将来的にはSystemdに移行することになるでしょう。

  • Ubuntuに標準で入っていて
  • サーバ起動時の自動起動してくれて
  • デーモン異常終了時に自動再起動してくれて
  • 設定はわりかしわかりやすい

/etc/init/web-some-application.conf みたいな名前でこんな設定ファイルを作ります

description 'some web application'
author 'masasuzu <hogehoge@masasuzu.net>'
start on runlevel [2345]
stop on starting rc RUNLEVEL=[016]

setuid webapp
setgid webapp

# 異常時に再起動する
respawn

script
    . /path/to/project/config/env.rc
    export PLACK_ENV="production"

    exec ${PROJECT_BASE}/local/bin/start_server \
        --interval 10           \
        --port ${PLACK_PORT}    \
        -- ${PROJECT_BASE}/script/service/web
end script

上記のファイルを作ると以下のように操作出来ます。reloadでSIGHUPが送れるので、アプリケーションサーバのstart_server経由のgraceful restartができます。

# 起動
service web-some-application start

# 停止
service web-some-application stop

# (start_serverのプロセスごと)再起動
service web-some-application restart

# Plackサーバを再起動
service web-some-application reload

アプリケーションサーバ以外も、ジョブのワーカーなども、独自に設定ファイルを作って、Upstart経由で起動したりしてます。

Upstart以外の選択肢としては、先に挙げたSystemdの他、以下のものがあるでしょう。 好みと要件に合わせて使えば良いと思います。

参照

おわりに

WAF(Web Application Framework)やログの話など膨らまそうと思えばもっと膨らませられますが、実行環境の話なので、ここまでで抑えておきます。

ざっくりと、Plack/PSGIなアプリケーションの実行環境について説明してきました。 PerlでWebアプリケーションを作る時に何か参考になれば幸いです。 また、もっと良い方法があれば、教えていただけるとありがたいです。

明日は、@nekobato さんです webpackのなにか面白い話があるんじゃないかとわくどきしてます。