PHP 製のデプロイツール Deployer で複数プロジェクトのデプロイを行う

2021年2月16日

Deployer は PHP で書かれたデプロイツールです。世代管理機能により、デプロイ時にトラブルが起きてもロールバックできますし、PHP で書かれているので、Laravel など PHP を使ったシステムのデプロイと相性が良いです。今回は、この Deployer を使って、複数のプロジェクトのデプロイを行えるようにします。

Deployer のインストール

Deployer のインストールは、composer を使って行うことができます。Laravel や Symphony、CakePHP など主なフレームワークについては、Deployer のパッケージに付属しているレシピを利用することでデプロイ時に必要な作業を簡単に設定することができます。

composer require deployer/deployer --dev

デプロイ実行時に Slack で通知したいなどのニーズがあるときは、deployer/recipes パッケージをあわせてインストールするとよいでしょう(deployer/recipes パッケージで追加できるレシピの内容はこちら)。

デプロイの設定を書く

このあたりは、プロジェクトの内容にもよるので、参考程度に。下記のような内容を deploy.php という名前で保存します。工夫しているのは、デプロイするプロジェクトや本番デプロイ時のタグの指定を選択式にしていることです。扱っているプロジェクトの数が増えてくると、手入力する際に間違う可能性も増えますし、タグの指定を間違って、異なるバージョンのものをデプロイしてしまう可能性もあります。そのようなミスを防ぐため、askChoice を使って選択肢を表示するようにしています。

加えて、セマンティックバージョニングに基づいて v1.0.0 の形式でタグを付けていることを前提に、バージョンの昇順で並び替えて表示するとともに、minimum_version 未満のメジャーバージョンのものを選択肢に表示しないようにしています(set('minimum_version', 2) を指定すると、v2.0.0 以降のものだけが表示されます)。

<?php

namespace Deployer;

require 'recipe/common.php';

// すべてのプロジェクトに共通の設定はここに記述
// 設定の説明は https://deployer.org/docs/configuration.html を参照
set('git_tty', false);
set('writable_use_sudo', true);
set('allow_anonymous_stats', false);

desc('Deploy Application');
task('deploy', function () {

    // デプロイするものをユーザー選択式にする
    $hostname = askChoice('デプロイするものを選択してください:',
        ['prod_foo', 'stg_foo', 'prod_bar', 'stg_bar']
    );

    set('hostname', $hostname);

    // 各デプロイの設定
    switch ($hostname) {
        case 'prod_foo':
            set('repository', 'git@github-foo:example/foo.git');
            set('deploy_path', '/var/www/prod_foo');
            set('minimum_version', 2);
            break;
        case 'stg_foo':
            set('repository', 'git@github-foo:example/foo.git');
            set('deploy_path', '/var/www/stg_foo');
            set('branch', 'staging');
            break;
        case 'prod_bar':
            set('repository', 'git@github-bar:example/bar.git');
            set('deploy_path', '/var/www/prod_bar');
            set('minimum_version', 0);
            break;
        case 'stg_bar':
            set('repository', 'git@github-bar:example/bar.git');
            set('deploy_path', '/var/www/stg_bar');
            set('branch', 'staging');
            break;
        default:
            exit();
    }

    // 本番の場合にはタグを取得して選択式にする
    if (stripos(get('hostname'), 'prod') !== false) {
        set('branch', function () {

            $repository = get('repository');
            exec("git ls-remote --tags -h {$repository}", $outputLines);
            $versions = [];
            foreach ($outputLines as $line) {
                if (preg_match('#refs/tags/v([0-9]+?)\.([0-9]+?)\.([0-9]+?)$#', $line, $matches)) {
                    // minimum_version 未満のバージョンは表示しない
                    if ($matches[1] < get('minimum_version')) {
                        continue;
                    }
                    $versions[] = [
                        'major' => $matches[1],
                        'minor' => $matches[2],
                        'patch' => $matches[3]
                    ];
                }
            }
            // タグをバージョン順に並び替える
            $major = array_column($versions, 'major');
            $minor = array_column($versions, 'minor');
            $patch = array_column($versions, 'patch');

            array_multisort($major, SORT_ASC, $minor, SORT_ASC, $patch, SORT_ASC, $versions);

            $tags = array_map(static function ($array) {
                return 'v'.$array['major'].'.'.$array['minor'].'.'.$array['patch'];
            }, $versions);

            $tag = askChoice("デプロイするバージョンを選択してください:", $tags);
            if ( ! askConfirmation("本番環境を {$tag} にして問題ありませんか?", false)) {
                writeln('deploy was stopped');
                exit();
            }

            return $tag;
        });
    }

    if (stripos(get('hostname'), 'foo') !== false) {
        // プロジェクトごとの設定はここで。このサンプルは Laravel のデプロイ
        require 'recipe/laravel.php';

        invoke('deploy:info');
        invoke('deploy:prepare');
        invoke('deploy:lock');
        invoke('deploy:release');
        invoke('deploy:update_code');
        invoke('deploy:shared');
        invoke('deploy:vendors');
        invoke('deploy:writable');
        invoke('artisan:storage:link');
        invoke('artisan:view:cache');
        invoke('artisan:config:cache');
        invoke('artisan:queue:restart');
        invoke('artisan:migrate');
        invoke('deploy:symlink');
        invoke('deploy:unlock');
        invoke('cleanup');
    } elseif (stripos(get('hostname'), 'bar') !== false) {
        // 一般的な PHP プロジェクトのデプロイ
        set('shared_files', [
            '.env',
            '.htaccess'
        ]);

        invoke('deploy:info');
        invoke('deploy:prepare');
        invoke('deploy:lock');
        invoke('deploy:release');
        invoke('deploy:update_code');
        invoke('deploy:shared');
        invoke('deploy:vendors');
        invoke('deploy:writable');
        invoke('deploy:symlink');
        invoke('deploy:unlock');
        invoke('cleanup');
    }
});
after('deploy', 'success');

// [Optional] if deploy fails automatically unlock.
fail('deploy', 'deploy:failed');
after('deploy:failed', 'deploy:unlock');

もう一つの工夫は、ソースコードを取得してくるリポジトリの指定の部分(set('repository', 'git@github-foo:example/foo.git'); の部分)です。複数の GitHub のリポジトリを扱う場合、上記のような記載をしたうえで、.ssh/config に以下のような設定を行うことで、複数のリポジトリを使い分けることができます。

Host github-foo
  User git
  Port 22
  HostName github.com
  IdentityFile ~/.ssh/github-foo
  TCPKeepAlive yes
  IdentitiesOnly yes

Host github-bar
  User git
  Port 22
  HostName github.com
  IdentityFile ~/.ssh/github-bar
  TCPKeepAlive yes
  IdentitiesOnly yes

デプロイの実行

deploy.php を作成したら、以下のコマンドでデプロイを実行します。

vendor/bin/dep deploy -vvv

この記事を書いた人

グッドネイバー

“ Webに悩むお客さまの「よき隣人」でありたい ” をモットーに、Web システム開発(主に Laravel)、Web マーケティング支援の仕事をしています。詳しい業務内容はこちら。お仕事のご依頼・ご相談はこちらからお気軽にどうぞ。