OpenShift 上に Perl + Carton で Hot-Deploy な環境を無料で構築する

# はじめに
この記事では、無料で使える OpenShift 上に Perl + Carton + Plack で構成されたアプリケーションを Hot-Deploy 可能な状態で構築する方法を説明しています。

## OpenShift とは?
OpenShift は Red Hat が運営する PaaS (Platform as a Service) です。
Ruby, Python, Node など複数の言語に対応しており、もちろん Perl も利用できます。

3 つのアプリケーションまで無料で利用でき、試しに利用してみたり、小さなサービスを運用するのに最適です。データベースサーバー (MySQL 等) も無料範囲内で利用できます。

## Hot-Deploy とは
Hoy-Deploy とは、運用中のサービスを停止せずに、アプリケーションコードの変更を反映させることをいいます。Hot-Deploy が利用されていない場合、一定期間サービスにアクセスできないなどの問題が生じます。

今回は、Hot-Deploy を実現するために [Server::Starter](http://search.cpan.org/perldoc?Server::Starter) を用います。

## Carton とは
アプリケーションごとに依存ライブラリを管理する Perl 用のパッケージマネージャーです。Node.js での NPM、Ruby での Bundler にあたります。Perl で記述されています。

# OpenShift 上で Perl を動作させる方法
OpenShift 上で Perl を動作させるには、大きく分けて 2 つの方法があります。

## 標準の Perl カートリッジを使う方法
OpenShift では、カートリッジという単位でアプリケーション (やデータベース) を追加していきます。

OpenShift には既に Perl のカートリッジが用意されているので、これを使う場合、とても簡単に Perl を OpenShift 上で動作させることができます。しかし、このカートリッジでは、Perl のバージョンが 5.10 で固定されてしまう問題があります。そのため、今回はこの方法は用いません。

※ オートスケーリングに対応してるなど、こちらの方を選択するメリットもあります

## DIY カートリッジ上に自分で環境を構築する方法
DIY (Do-It-Yourself) カートリッジと呼ばれる自分で環境を構築するタイプのカートリッジです。今回はこの方法を採用して、Perl / Carton を自力でインストールして環境を構築していきます。

# OpenShift のアカウント取得
[OpenShift](https://www.openshift.com/) のページから登録してください。クレジットカード情報の登録は不要です。無料で 3 つまでアプリケーションが作れます。

# アプリケーションを登録

![Do-It-Yourself](https://qiita-image-store.s3.amazonaws.com/0/51394/37374aad-a545-909d-8a0e-91afa3f10941.png)

DIY カートリッジでアプリケーションを登録します。作成の際、作成ボタンを押したあと暫く待たされます。

![Git repository](https://qiita-image-store.s3.amazonaws.com/0/51394/1c4c5f1f-94ba-6f58-6276-f1d80ad41b0f.png)

上記のように表示されたら成功です。
SSH で接続できるようになるので、公開鍵を OpenShift へ登録後、SSH で接続します。

![Git repository and SSH](https://qiita-image-store.s3.amazonaws.com/0/51394/b5f5ecee-5ffc-0730-0731-9a335f8ca218.png)

「 Want to log in to your application? 」 をクリックすると、SSH 接続情報が閲覧できます。

※ パスワードが聞かれますが、公開鍵が登録してあれば空で通ります

# Perl のインストール
SSH で作成したアプリケーションに接続します。

```
$ ssh xxxxxxxxxxxxxxxxxxxxxxxx@diy-pinemz.rhcloud.com
    *********************************************************************

    You are accessing a service that is for use only by authorized users.
    If you do not have authorization, discontinue use at once.
    Any use of the services is subject to the applicable terms of the
    agreement which can be found at:
    https://www.openshift.com/legal

    *********************************************************************

    Welcome to OpenShift shell

    This shell will assist you in managing OpenShift applications.

    !!! IMPORTANT !!! IMPORTANT !!! IMPORTANT !!!
    Shell access is quite powerful and it is possible for you to
    accidentally damage your application.  Proceed with care!
    If worse comes to worst, destroy your application with "rhc app delete"
    and recreate it
    !!! IMPORTANT !!! IMPORTANT !!! IMPORTANT !!!

    Type "help" for more info.


[diy-pinemz.rhcloud.com xxxxxxxxxxxxxxxxxxxxxxxx]\> 
```

今回は Perl 5.18.2 をインストールします。

root 権限はないため、ここでは `~/app-root` という OpenShift でいうホームディレクトリにインストールします (OpenShift では、通常のホームディレクトリ `$HOME` に対して書き込み権限がありません)。

```
[diy-pinemz.rhcloud.com xxxxxxxxxxxxxxxxxxxxxxxx]\> cd ~/app-root/data/
[diy-pinemz.rhcloud.com data]\> mkdir src
[diy-pinemz.rhcloud.com data]\> cd src
[diy-pinemz.rhcloud.com src]\> wget http://www.cpan.org/src/5.0/perl-5.18.2.tar.gz # 出力略
[diy-pinemz.rhcloud.com src]\> ls
perl-5.18.2.tar.gz
[diy-pinemz.rhcloud.com src]\> tar xzf perl-5.18.2.tar.gz
[diy-pinemz.rhcloud.com src]\> ls
perl-5.18.2  perl-5.18.2.tar.gz
[diy-pinemz.rhcloud.com src]\> cd perl-5.18.2
[diy-pinemz.rhcloud.com perl-5.18.2]\> ls # 出力略
[diy-pinemz.rhcloud.com perl-5.18.2]\> ./Configure -des -Dprefix=~/app-root/data/perl-5.18.2 # 出力略
[diy-pinemz.rhcloud.com perl-5.18.2]\> make # 出力略
[diy-pinemz.rhcloud.com perl-5.18.2]\> make install # 出力略
```

これで Perl が `~/app-root/data/perl-5.18.2` にインストールされました。

# Carton のインストール
権限の関係上、通常の方法ではインストールできないため、少々特殊な方法を取ります。

```
[diy-pinemz.rhcloud.com perl-5.18.2]\> cd ~/app-root/data/perl-5.18.2/bin
[diy-pinemz.rhcloud.com bin]\> PATH=$PWD:$PATH # Perl 5.18.2 をパスに追加
[diy-pinemz.rhcloud.com bin]\> perl -V:version
version='5.18.2';
[diy-pinemz.rhcloud.com bin]\> curl -L http://cpanmin.us > cpanm
[diy-pinemz.rhcloud.com bin]\> chmod +x cpanm
[diy-pinemz.rhcloud.com bin]\> HOME=~/app-root/data # これが肝
[diy-pinemz.rhcloud.com bin]\> cpanm --version # 出力略
[diy-pinemz.rhcloud.com bin]\> cpanm Carton
[diy-pinemz.rhcloud.com bin]\> carton -v
```

※ cpanm はホームディレクトリへの書き込み権限がないと正しく動作しません

# テスト用のアプリケーションを作成
以降は OpenShift 上ではなく、ローカル環境で実行します。今回は、テスト用アプリケーションとして Amon 2 を使います。リモートの Perl のバージョンと、ローカルの Perl のバージョンは揃えておいてください。

```
$ cpanm Amon2 # 出力略
$ amon2-setup.pl diy # 出力略
$ cd diy
$ carton install # 出力略
$ git status
On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)

        cpanfile.snapshot

nothing added to commit but untracked files present (use "git add" to track)
$ git add .
$ git commit -m "Create cpanfile.snapshot"
[master fa15dac] Create cpanfile.snapshot
 1 file changed, 2745 insertions(+)
 create mode 100644 cpanfile.snapshot
```

ローカル環境で試しに実行してみます。

```
$ carton exec -- perl script/diy-server
diy: http://127.0.0.1:5000/
```

# OpenShift へデプロイ
## Hot-Deploy 非対応版
Hot-Deploy 化は後周りにして、OpenShift 上でインストールした Perl / Carton が正しく動作するか確認します。

OpenShift へのデプロイには Git を使います。OpenShift 上の Git レポジトリをリモートとして登録します (URI はアプリケーションのページに表示されてます)。

![Source Code](https://qiita-image-store.s3.amazonaws.com/0/51394/40ec9651-7096-3317-afe6-c60d8e446298.png)

```
$ git remote add deploy ssh://xxxxxxxxxxxxxxxxxxxxxxxx@diy-pinemz.rhcloud.com/~/git/diy.git/
git pull deploy master # 出力略
```

OpenShift のリモートレポジトリから、アプリケーション起動に必要なファイルが取得されるので確認します。

```
$ ls -a | grep openshift
.openshift
$ ls .openshift
action_hooks  cron  markers  README.md
$ ls .openshift/action_hooks
README.md  start  stop
```

OpenShift の DIY カートリッジでは、アプリケーションの起動時に `.openshift/action_hooks/start`、終了時に `.openshift/action_hooks/stop` が呼ばれる仕様になっています。それぞれ、以下のように編集します。

```bash:.openshift/action_hooks/start
#!/bin/bash

# ソースコードがあるディレクトリ `$OPENSHIFT_REPO_DIR` は毎回リセットされるので、
# キャッシュを効かせるために Carton のインストール先を移動
export PERL_CARTON_PATH=~/app-root/data/local

# PERL_CARTON_PATH を変更したらセットで指定
export PERL_CARTON_CPANFILE=$OPENSHIFT_REPO_DIR/cpanfile

# Carton を読みに行くように
export PERL5LIB=$PERL_CARTON_PATH/lib/perl5

# インストールした Perl 5.18.2 を利用するように
export PATH=~/app-root/data/perl-5.18.2/bin:$PATH

# 今回の肝
HOME=~/app-root/data

cd $OPENSHIFT_REPO_DIR

# 依存ライブラリのインストール
carton install --deployment

# 起動
nohup carton exec -- perl ./script/diy-server --host=$OPENSHIFT_DIY_IP --port=$OPENSHIFT_DIY_PORT > /dev/null 2>&1 &
```

```bash:.openshift/action_hooks/stop
#!/bin/bash

kill `ps -ef | grep diy-server | grep -v grep | awk '{ print $2 }'` > /dev/null 2>&1
exit 0
```

変更したらデプロイします。

```
$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   .openshift/action_hooks/start
        modified:   .openshift/action_hooks/stop

no changes added to commit (use "git add" and/or "git commit -a")
$ git add .
$ git commit -m "Fix start/stop script"
$ git push deploy master
```

ブラウザから表示してみて、表示されたら成功です。

## Hot-Deploy 対応版
無事に OpenShift 上で動作させることができたので、次は Hot-Deploy に対応させます。今回は、Hot-Deploy を実現するために [Server::Starter](http://search.cpan.org/perldoc?Server::Starter) を用います。Server::Starter については、ここでは説明しませんので、詳しく知りたい方は別途調べてください。

先ほど作成した `.openshift/action_hooks/start` と `.openshift/action_hooks/stop` は使わないので消します。代わりに、`.openshift/action_hooks/deploy` というファイルと `` というファイルを作成します。

```
$ rm -f .openshift/action_hooks/start
$ rm -f .openshift/action_hooks/stop
$ touch .openshift/markers/hot_deploy # Hot-Deploy を有効化
```

```bash:.openshift/action_hooks/deploy
#!/bin/bash

echo "Running .openshift/action_hooks/deploy"

# OpenShift 環境で動作させるための変数定義
export PERL_CARTON_PATH=~/app-root/data/local
export PERL_CARTON_CPANFILE=$OPENSHIFT_REPO_DIR/cpanfile
export PERL5LIB=$PERL_CARTON_PATH/lib/perl5

# Carton の bin も PATH に追加する (start_server / plackup で利用)
export PATH=$PERL_CARTON_PATH/bin:~/app-root/data/perl-5.18.2/bin:$PATH

# 今回の肝
HOME=~/app-root/data

cd $OPENSHIFT_REPO_DIR

# 依存ライブラリのインストール
carton install --deployment

# 既に起動しているサーバーの PID を取得
PID=`ps -ef | grep start_server | grep -v grep | awk '{ print $2 }'`

if [ "$PID" != "" ]; then
    kill -s HUP $PID # 再起動
else
    nohup start_server --port=$OPENSHIFT_DIY_IP:$OPENSHIFT_DIY_PORT -- plackup -s Starlet \
        --max-workers=1 \
        $OPENSHIFT_REPO_DIR/script/diy-server \
        >> $OPENSHIFT_DIY_LOG_DIR/plack.log 2>&1 & # 起動
fi
```

デプロイ前に OpenShift 上で既に動いているプロセスを停止しておきます。

```
$ ssh xxxxxxxxxxxxxxxxxxxxxxxx@diy-pinemz.rhcloud.com
[diy-pinemz.rhcloud.com xxxxxxxxxxxxxxxxxxxxxxxx]\> kill `ps -ef | grep diy-server | grep -v grep | awk '{ print $2 }'`
```

デプロイします。

```
$ git status
git status
On branch master
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        deleted:    .openshift/action_hooks/start
        deleted:    .openshift/action_hooks/stop

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        .openshift/action_hooks/deploy
        .openshift/markers/hot_deploy

no changes added to commit (use "git add" and/or "git commit -a")
$ chmod +x .openshift/action_hooks/deploy
$ git add --all
$ git commit -m "Support hot deloy"
$ git push deploy master
```

きちんと動いてるか確認するには、SSH で OpenShift へ繋いでログを確認します。

```
$ ssh xxxxxxxxxxxxxxxxxxxxxxxx@diy-pinemz.rhcloud.com
[diy-pinemz.rhcloud.com xxxxxxxxxxxxxxxxxxxxxxxx]\> tail -f $OPENSHIFT_DIY_LOG_DIR/plack.log
start_server (pid:430204) starting now...
starting new worker 430218
Plack::Handler::Starlet: Accepting connections at http://127.1.253.129:8080/
```

OpenShift では push 時にデプロイスクリプトが動くので、空コミットを送って強制的に走らせます。

```
$ git commit -m "Test hot deploy" --allow-empty
$ git push deploy master
```

Server::Starter により、Hot-Deploy されたら成功です。

```
$ ssh xxxxxxxxxxxxxxxxxxxxxxxx@diy-pinemz.rhcloud.com
[diy-pinemz.rhcloud.com xxxxxxxxxxxxxxxxxxxxxxxx]\> tail -f $OPENSHIFT_DIY_LOG_DIR/plack.log
start_server (pid:430204) starting now...
starting new worker 430218
Plack::Handler::Starlet: Accepting connections at http://127.1.253.129:8080/
received HUP, spawning a new worker
starting new worker 434971
127.0.0.1 - - [03/Jun/2015:10:50:45 -0400] "HEAD / HTTP/1.1" 200 4971 "-" "Ruby"
127.0.0.1 - - [03/Jun/2015:10:50:45 -0400] "HEAD / HTTP/1.1" 200 4971 "-" "Ruby"
127.0.0.1 - - [03/Jun/2015:10:50:45 -0400] "HEAD / HTTP/1.1" 200 4971 "-" "Ruby"
127.0.0.1 - - [03/Jun/2015:10:50:45 -0400] "HEAD / HTTP/1.1" 200 4971 "-" "Ruby"
Plack::Handler::Starlet: Accepting connections at http://127.1.253.129:8080/
new worker is now running, sending TERM to old workers:430218
killing old workers
old worker 430218 died, status:0
```

# まとめ
OpenShift を使うと、簡単に Perl + Carton で Hot-Deploy 環境が構築できます。無料枠もあり、小さなサービスを運用するのに最適です。「 レンタルサーバーだと制約がきついけど VPS 契約するのはちょっと… 」 という方に、ぜひどうぞ!

# 参考にした記事
- [OpenShift Onlineで任意のPerlバージョンでWebアプリを公開する方法](http://qiita.com/ingktds/items/575ed8ed7de34ce74d48)