開発メモ#1 : Cinnamon によるデプロイ

このごろ作っているものが幾つかあるのだけど備忘録代わりにこの辺はこうしているということを書いて行こうかなと思います。

まずは Perl によるアプリケーションのデプロイについて。id:antipopid:shiba_yu36 が開発した "Cinnamon" というミニマムなデプロイツールを利用しています。

シンプルで使いやすいデプロイツールです。

Capistrano?

デプロイツールの定番といえば Capistrano で、最初は Capistrano を使っていました。けど、作っているものはほぼ Perl で書かれているのにデプロイツールだけ CapistranoRuby というのが、例えばモジュールの管理に Carton と Bundler を二つ使ったりとかなんだか気持ちがわるい。

気持ちが悪いというだけならともかく、実際のところ Capistrano よりもう少しミニマムな仕様のデプロイツールが欲しかった。デプロイ、というと大げさだけれども個人で作っているような小規模なものなので「ssh して git fetch する」程度のことができればそれで良いわけで、緊急時の切り戻しとかいろいろな ops ツールとの動的な連携といったところまでは必要としていない。

Capistrano を使って居ると Rails を前提としている箇所とか、バックグラウンドであれこれやっているところを自分の環境に合わせるためにいろいろ work around を入れる必要があって、どうにもそこがストレスでした。

Cinnamon

・・・と、思っていたら全く同じようなことを考えてそんなツールを作っている人がいた。詳しくは シンプルなデプロイツールを書いているという話 - Kentaro Kuribayashi's blog にも書かれているけど、

デプロイの詳細についてはデプロイツールは関知せず、ツール外部のオブジェクトにより提供される方が、ツールに縛られない柔軟性を確保できる。

というポリシーが自分の求めるものにとてもちかいのであった。

Capistranossh した後にサーバーサイドでいろいろごにょごにょとやる部分が Capistrano のバックエンド、DSL で提供されるデプロイルール記述のフレームワークや複数ホストへの同時実行処理周りがフロントエンドとすると、Cinnamon はそのフロントエンドしか提供しないというツールです。サーバーに入ってから何をするかは自分で「cd /hoge && git fetch origin && git checkout -q origin/master」とか書く。

これを利用している、という話があまりウェブ上にもないので情報も少ないのだけれど、ツールがミニマムなので README に目を通してざっとコードを斜め読みすればだいたい何をしていてどこまでがスコープなのかは把握できた。

Cinnamon を使う

インストールは cpanm で。perl モジュールと、cinnamon コマンドがインストールされる。

% cpanm Cinnamon

cinnamon コマンドを発行するホストからデプロイ先へは ssh が疎通していることが前提になっている。今回自分のデプロイ先は Amazon EC2インスタンスで公開鍵認証なので .ssh/config で鍵パスを指定しておく。(Cinnamon の中で ssh key を指定するオプションがなかったので)

# ~/.ssh/config
Host *.ap-northeast-1
  IdentityFile ~/.ssh/aws-naoyaskeys.pem

あとはサーバー側での sudo 周りとか、必要に応じて設定しておく。

アプリケーションのルートディレクトリから見て config/deploy.pl に、perl でデプロイルールを書いて行く。この辺は Capistrano と同じ、DSL が提供する一通りのルールも Capistrano によく似ているので、見ればわかる。

ここでは

  • "cinnamon development deploy:setup" で development 環境に git でソースを配置
  • "cinnamon development deploy:update" で development 環境のソースを最新化
  • "cinnamon development server:start" で development 環境のサーバーを start
  • "cinnamon development carton:install" で development 環境の perl モジュールを、Carton でインストール

という感じでタスクを定義している。

# config/deploy.pl
use strict;
use warnings;
use Cinnamon::DSL;

set application => 'myapp';
set repository  => 'git@github.com:naoya/myapp.git';
set user        => 'fiorung';
set password    => '';

role development => ['myapp-development.ap-northeast-1'], {
    deploy_to   => '/home/fiorung/apps/myapp',
    branch      => 'master',
};

task deploy  => {
    setup => sub {
        my ($host, @args) = @_;
        my $repository = get('repository');
        my $deploy_to  = get('deploy_to');
        my $branch   = 'origin/' . get('branch');
        remote {
            run "git clone $repository $deploy_to && git checkout -q $branch";
        } $host;
    },
    update => sub {
        my ($host, @args) = @_;
        my $deploy_to = get('deploy_to');
        my $branch   = 'origin/' . get('branch');
        remote {
            run "cd $deploy_to && git fetch origin && git checkout -q $branch && git submodule update --init";
        } $host;
    },
};

task server => {
    start => sub {
        my ($host, @args) = @_;
        remote {
            sudo "supervisorctl start myapp";
        } $host;
    },
    stop => sub {
        my ($host, @args) = @_;
        remote {
            sudo "supervisorctl stop myapp";
        } $host;
    },
    restart => sub {
        my ($host, @args) = @_;
        remote {
            run "kill -HUP `cat /tmp/myapp.pid`";
        } $host;
    },
    status => sub {
        my ($host, @args) = @_;
        remote {
            sudo "supervisorctl status";
        } $host;
    },
};

task carton => {
    install => sub {
        my ($host, @args) = @_;
        my $deploy_to = get('deploy_to');
        remote {
            run ". ~/perl5/perlbrew/etc/bashrc && cd $deploy_to && carton install";
        } $host;
    },
};
  • リモートレポジトリは github にある
  • サーバー周りは Plack + Server::Starter で、それを動かすのに supervisor を使っているので server タスクはそれと連携するような記述になっている
    • この辺に関しても後日続けて書きたい
  • production 環境を追加するときは、role production を追加すればよい
  • この deploy.pl もレポジトリに追加しておく
  • EC2 インスタンスの Puclic DNS が変更になるので deploy.pl に記述できないという問題は、ec2ssh (http://blog.mirakui.com/entry/20101205/1291551625) を利用して解決している
    • deploy.pl から API を叩いてホスト一覧を取ってくる、でも良いでしょう

これで、手元のホストから

% cinnamon development deploy:update
% cinnamon development server:restart

とすると、開発用サーバーのソースが最新になって、Server::Starter による hot deploy が完了する。めでたし。

感想

サーバーに ssh ログインした後なにをしているかすべて明確になっているし、やっていることも少なくシンプルなのが、手に馴染んで非常に良いです。ツールのポリシー通り、どんなツールを使っているかには関知しないので、perlbrew、Plack、supervisor、Carton みたいな環境でも不都合はありません。

デプロイツールは自動化によって自分の手間を軽減してくれるというところも大事ですが、個人的には、deploy.rb や deploy.pl の中に環境依存のものをすべて書いてしまってさらにそれを scm で管理することで、忘却によるめんどくささを解消してくれるところが重要だなと思ってます。後日、サーバーがどこにあって、どういうディレクトリに何を放り込んでいるかを忘れてしまってもとりあえず "cinnamon deploy development:update" さえすれば ok … というのが今後、安心ですね。実際そんなこと忘れるの? と思うのですが、10年くらい前に作ったアプリケーションだと見事に忘れます。というか忘れていて、非常に面倒でした。

Cinnamon 自体の利用者が多くなさそう (というか開発メンバー以外にいない・・・?) とか、半年ぐらい特に新しい commit はない、というのが若干不安と思われる向きもありそうですが、まあ perl だし仕様小さいし何か必要なときは pull request すればいいか、と思っています。