オールアバウトTech Blog

株式会社オールアバウトのエンジニアブログです。

Wercker+deployerで始める新リリース生活

@takkyです。
前回私が執筆した記事では、オールアバウトのリリースフローの変遷について説明しました。

allabout-tech.hatenablog.com

FTP手動アップロードの時代からJenkinsやWerckerを使用したフローへと今では変わっています。
前回の記事では、WerckerやDeployerについての説明や実際の運用方法などは触れていなかったため今回の記事で紹介します。

Werckerとは

wercker.com

WerckerはCIツールの一種です。buildやテスト・デプロイを自動で行ってくれるものです。
よく他社で使われているツールにはcircleciTravis CIなどがあると思いますが、それと同様のツールだと捉えてください。
ちょうど検証が終わったころにBitbucketもBitbucket PipelinesというCIツールを出しましたが、 機能が少なく導入は見送っています。

bitbucket.org

Werckerの特徴的な機能

wercker.ymlで動作設定

Wercker.ymlというファイルをアプリケーションのリポジトリに含むことで、そのアプリケーションに対してどういうことを実行するか設定することができます。

コンテナベース

今流行のDockerコンテナをベースにしています。
本番環境と同じ環境でテストを回すことが可能です。

Wercker Workflows

CIで実行する項目をpipelineという単位で作ることができ、 そのpipelineを直列や並列につなげて実行することができるようになります。 例えばPHPLintでPHPの構文チェックを行うpipelineの後にPHP UNITでテストを行うpipelineをつなげることができます。 今までのWerckerではPHPUnit単体でのRetryはできなかったのですが、Workflowsにより、各pipeline毎にRetryができるようになっています。

Wercker Workflowsの日本語ではこちらのQiitaが詳しいです。

qiita.com

豊富な拡張機能

Wercker RegistryというWerckerのpipelineで使用できる便利なメソッドストアのようなものが用意されています。
例えばS3にファイルをアップロードするstep(pipelineを構成する最小要素)が用意されており、これを使用することで簡単にS3にアップロードすることが可能です。

Werckerのいいところ

何よりBitbucketに対応しているところ
弊社ではBitbucketを使用しているのですがBitbucketと連携ができるCIツールは少ないです。
その中でもわりと機能が豊富で無料で使えるためWerckerを採用しています。

Werckerの悪いところ

全世界でキューを共通利用しているようでたまに重くなることが有ります。
課金すれば解決できるようですが...
また、ユーザの管理の仕方もちょっと貧弱だとは思います。

とはいえ、無料で使えますし、Bitbucketに対応していてコンテナベースである。ということで採用をしています。

Deployerとは

DeployerとはPHP製のデプロイツールです。

CapistranoFabricPHP版という認識でOKです。
なお、Twitterが提供しているモバイル向けSDKFabricという名前なので注意が必要です。

Deployerの特徴

・taskというリモートサーバで実行したい機能単位で実装がされています
PHPで書かれています。PHPを書いたことがあるエンジニアであれば、機能の追加が容易です。
・基本的なフレームワークはレシピという形でデフォルトでありますし、rsyncのレシピなども容易に追加が可能です。

Deployerのいいところ

PHPで書かれているので、開発者がリモートサーバで実行したいタスクを書くことができる点。
いままでは、インフラのリリーススクリプト作業待ちで止まることもあったのですが、開発者が準備することができます。
・リリース毎にシンボリックリンクが貼り変わるデプロイが可能です。
シンボリックリンクの張替えのみで済むのでRollBackが高速に行えます。
(以下はLaravelをシンボリックリンクデプロイした例です)
f:id:allabout-techblog:20160704110653p:plain ・書き込み権限を与えるディレクトリや、共有ディレクトリとしたいディレクトリの設定が容易であり、configで追加すれば設定が完了となります。

Deployerの悪いところ

・あまり情報がないところ。 Deployerという名前自体が一般化されているからか検索で引っかかってくる情報が少ないです。
PHPで書かれているのでDeployer自体のソースを読めばいいんでしょうけど。

オールアバウトでの Wercker + Deployerの運用方法

Wercker+Deployerでのフローでの開発チームとインフラチームの役割分担は以下のようになっています。 開発チーム: アプリケーションの開発、WerckerとDeployerの設定、リリース作業
インフラチーム: サーバの準備、鍵交換

以下がかなり簡略化したWercker+Deployer+アプリケーションの関係図になります。

f:id:allabout-techblog:20160704114941p:plain

オールアバウトではGCEにDeployerサーバを構築して、各Applicationのサーバと鍵交換をしてssh接続ができるようにしておきます。
インフラチームは各サーバの準備及び鍵交換をしておきます。 (必要あればVPN等で異なるクラウド上接続できるようにしておきます) 。
開発チームはWerckerとDeployerの設定をしアプリケーションをリリースすれば良いので、どのサーバにリリースすればよいのか悩む必要がありません。

Werckerの設定

社内のボイラープレート集に wercker.ymlを追加しておきます。
開発者はそのファイルをベースにwercker.ymlに設定を加えていきます。
基本的にはボイラープレートのwercker.ymlをそのまま使えるようにしてあります。
その後プルリクエスト、マージの流れになります。

Deployerの設定

Deployerの設定用のリポジトリを用意しておきます。
オールアバウトで使用しているメインのフレームワークはCodeIgniterとLaravelがあり、それぞれに対応したサンプル設定を用意しておきます。
開発者はサンプルをコピーして、リリースのテストを行い、その後プルリクエスト、マージの流れになります。

インフラチームは面倒なアプリケーションリリースの設定を行う必要がなくなりますし、開発者はインフラチームの作業待ちがなくなります。
また、リリース先のサーバがDeployerの設定で決定しているためリリース先サーバに悩むこともありません。

DeployerのTIPS

rsyncデプロイ

f:id:allabout-techblog:20160620171628p:plain オールアバウトのリリースフローの変遷 - オールアバウトTech Blog 上の図や前回の説明を見ていただくと分かる通り、rsyncでデプロイを行っています。

Deployerのレシピ はrsyncのレシピを提供しています。
しかし、このrsyncのレシピはDeployerサーバ上のファイルと対象サーバのファイルをrsyncするレシピになっています。
弊社ではPF環境と本番環境のファイルをrsyncしたく、PF環境とDeployerサーバは別サーバのため工夫が必要になります。

そこで、rsyncをremoteのファイルを対象にできるようにtaskを追加して対応しました。

// 指定したサーバでrsyncする。
task('rsync_remote', function() {
    $config = get('rsync');
    $src = env('rsync_src');
    $dst = env('rsync_dest');
    $server = \Deployer\Task\Context::get()->getServer()->getConfiguration();
    $host = $server->getHost();
    $port = $server->getPort() ? ' -p' . $server->getPort() : '';
    $identityFile = $server->getPrivateKey() ? ' -i ' . $server->getPrivateKey() : '';
    $user = !$server->getUser() ? '' : $server->getUser() . '@';
    $source = input()->getArgument('source');
    $current = $src;

    $source_server = \Deployer\Deployer::get()->servers->get($source);
    run_remote("rsync -{$config['flags']} -e 'ssh$port$identityFile' {{rsync_options}}{{rsync_excludes}}{{rsync_includes}}{{rsync_filter}} '$current/' '$user$host:$dst/'", $source_server);
})->desc('Rsync remote->remote');

run_remote

/**
 * Run command on remote server.
 * 指定したremote serverでコマンド実行する。
 * depoyerであるrunコマンドに引数追加で作成。
 * @param string $command
 * @return Result
 */
function run_remote($command, $server)
{
    $command = env()->parse($command);
    $workingPath = workingPath();

    if (!empty($workingPath)) {
        $command = "cd $workingPath && $command";
    }

    if (isVeryVerbose()) {
        writeln("<fg=red>></fg=red> $command");
    }
    $output = $server->run($command);
    if (isDebug() && !empty($output)) {
        output()->writeln(array_map(function ($line) {
            return "\033[1;30m< $line\033[0m";
        }, explode("\n", $output)), \Symfony\component\Console\Output\OutputInterface::OUTPUT_RAW);
    }

    return new \Deployer\Type\Result($output);
}

deployerにもともとあったrunを使用してrsyncをしようとすると、deployerサーバとデプロイ先のサーバでrsyncをしてしまいます。
今回はサーバAとサーバBでデプロイを行いたいために、指定したremoteサーバでrunができるような関数を実装した後、 rsyncするときもrun_remoteでリモートサーバ同士でrsyncできるようにしています。

Laravelの.envをデプロイされる環境ごとに変更したい。

同一アプリをAWSとGCEに展開して、環境変数AWS/GCE毎に変更したいということがあると思います。
Laravel4では、 .env.{環境名}でできたのですが、Laravel5以上ではどうやら行けないようです。
Deployerで、特定のスクリプトを叩くようなtaskを追加して、そのシェルスクリプトで.envのシンボリックリンクを張り替えるようにしました。シェルスクリプトは以下のような感じでさくっと書いています。

envの配置

   .env -> .env.AWS
   .env.AWS
   .env.GCE
#!/bin/sh
cd $1/current
echo "########## make symlink #########"

HOSTNAME=`hostname`
# env存在チェック
if [ ! -e .env ]; then
    case $HOSTNAME in
        AWS* ) ln -s env.AWS .env
        echo "make symlink to env.AWS";

        # それ以外
        * ) ln -s env.GCS .env
        echo "make symlink to env.GCS";
    esac
    exit 0;
else
    echo "Could not make symlink"
    exit 1;
fi

deployerで特定のシェルスクリプトを叩くのに以下のようにtaskを定義します。

// envを作成
task('make:symlink', function() {
    run("sh {{deploy_path}}/current/symlink.sh {{deploy_path}}");
})->desc('make_symlink');

その後 main taskにmake:symlinkを追加します。

task('deploy', [
    'deploy:prepare',
    'deploy:release',
    'make:symlink',
])->desc('Deploy your project');

まとめ

DeployerはPHPで書かれているので開発者が容易に拡張可能です。
また、設定ファイルをgitを使用していれば属人化しにくくなると思います。

ただ最近の悩みとしてWercker含むSaasを数多く使っているのですが、 Web上で設定したものの使い回しやバックアップができないという点があります。
Werckerのweb上で設定したものをxmlなどからもimportできるともっと使いやすくなるのですが・・・