📌

Laravelプロジェクトをdeployerを使ってEC2にデプロイする

2022/02/07に公開

ローリングアップデートでのCI/CD構築に先駆けて、PHPのデプロイツールであるdeployerを使ってLaravelアプリをEC2にデプロイしてみました。
今回の環境は以下の手順でサクッと作りました。

https://zenn.dev/tokku5552/articles/create-php-env-with-cfn

環境

  • OS
    • macOS Monterey バージョン12.1(Intel)
  • docker
% docker -v
Docker version 20.10.11, build dea9396
  • docker内のphp/composer
# php -v
PHP 7.3.33 (cli) (built: Dec 21 2021 22:11:19) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.33, Copyright (c) 1998-2018 Zend Technologies

# composer -v
   ______
  / ____/___  ____ ___  ____  ____  ________  _____
 / /   / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__  )  __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
                    /_/
Composer version 2.1.14 2021-11-30 10:51:43

コードはGitHubで公開しています。

https://github.com/tokku5552/php-docker-nginx-postgresql

deployerのインストールとdep init

deployerのインストール

プロジェクトルートで以下のコマンドを実行します。

composer require deployer/deployer --dev

composer.jsonrequire-devdeployer/deployerが追加されていればOKです。

dep initの実行

deployerがインストールできたら、vendor/bin/dep initを実行し初期化します。
今回は最初のproject typeは1を選択し、Repositoryは空のまま、統計情報の収集の許可的なものはnoを選択しています。

# vendor/bin/dep init

                                            
  Welcome to the Deployer config generator  
                                            


 This utility will walk you through creating a deploy.php file.
 It only covers the most common items, and tries to guess sensible defaults.
 
 Press ^C at any time to quit.

 Please select your project type [Common]:
  [0 ] Common
  [1 ] Laravel
  [2 ] Symfony
  [3 ] Yii
  [4 ] Yii2 Basic App
  [5 ] Yii2 Advanced App
  [6 ] Zend Framework
  [7 ] CakePHP
  [8 ] CodeIgniter
  [9 ] Drupal
  [10] TYPO3
 > 1

 Repository []:
 > 

 Contribute to the Deployer Development
 
 In order to help development and improve the features in Deployer,
 Deployer has a setting for usage data collection. This function
 collects anonymous usage data and sends it to Deployer. The data is
 used in Deployer development to get reliable statistics on which
 features are used (or not used). The information is not traceable
 to any individual or organization. Participation is voluntary,
 and you can change your mind at any time.
 
 Anonymous usage data contains Deployer version, php version, os type,
 name of the command being executed and whether it was successful or not,
 exception class name, count of hosts and anonymized project hash.
 
 If you would like to allow us to gather this information and help
 us develop a better tool, please add the code below.
 
     set('allow_anonymous_stats', true);
 
 This function will not affect the performance of Deployer as
 the data is insignificant and transmitted in a separate process.

 Do you confirm? (yes/no) [yes]:
 > no

Successfully created: /work/src/deploy.php

以上の初期化が終わると、以下のようなdeploy.phpが生成されます。

deploy.php
<?php
namespace Deployer;

require 'recipe/laravel.php';

// Project name
set('application', 'my_project');

// Project repository
set('repository', '');

// [Optional] Allocate tty for git clone. Default value is false.
set('git_tty', true); 

// Shared files/dirs between deploys 
add('shared_files', []);
add('shared_dirs', []);

// Writable dirs by web server 
add('writable_dirs', []);
set('allow_anonymous_stats', false);

// Hosts

host('project.com')
    ->set('deploy_path', '~/{{application}}');    
    
// Tasks

task('build', function () {
    run('cd {{release_path}} && build');
});

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

// Migrate database before symlink new release.

before('deploy:symlink', 'artisan:migrate');

deploy.phpの編集

このdeploy.phpに必要情報を定義していきます。
ターゲット情報をserver.ymlとして外だししたため、不要な情報を削除して、最低限必要な情報のみ修正します。

deploy.php
<?php
namespace Deployer;

require 'recipe/laravel.php';

// Project name
set('application', 'php-docker-nginx-postgresql');

// [Optional] Allocate tty for git clone. Default value is false.
set('git_tty', false); 

// Shared files/dirs between deploys 
add('shared_files', []);
add('shared_dirs', []);

// Writable dirs by web server 
add('writable_dirs', []);
set('allow_anonymous_stats', false);
    
inventory('servers.yml');

// Tasks

task('build', function () {
    run('cd {{release_path}} && build');
});

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

// Migrate database before symlink new release.

before('deploy:symlink', 'artisan:migrate');

after('deploy:update_code', 'set_release_path');
task('set_release_path',function() {
    $newReleasePath = get('release_path') . '/src';
  set('release_path', $newReleasePath);
});

inventory('servers.yml');とすることで、yamlファイルにターゲットの情報を記載することができます。
また、今回プロジェクトルートからlaravelのソースまで、1階層下る必要があったので、set_release_pathというタスクでrelease_pathを書き換えています。

sever.ymlはこんな感じで定義します。(ALBの操作を後で入れるので、target_group_arnとinstance_idもセットしておきます。)
使う場合はhostnameのIPアドレスやtarget_group_arninstance_idはそれぞれご自身のものに書き換えてください。

server.yml
LaravelWeb1:
  hostname: 111.111.111.111
  stage: LaravelWeb1
  user: ec2-user
  port: 22
  identityFile: ~/.ssh/MyKeypair.pem
  deploy_path: /var/www/
  branch: main
  repository: https://github.com/tokku5552/php-docker-nginx-postgresql.git
  target_group_arn: arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  instance_id: i-xxxxxxxxxxxxxxxxx
LaravelWeb2:
  hostname: 222.222.222.222
  stage: LaravelWeb2
  user: ec2-user
  port: 22
  identityFile: ~/.ssh/MyKeypair.pem
  deploy_path: /var/www/
  branch: main
  repository: https://github.com/tokku5552/php-docker-nginx-postgresql.git
  target_group_arn: arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  instance_id: i-xxxxxxxxxxxxxxxxx

deployerではstageごとにデプロイを行えるのですが、今回はローリングアップデートを行いたいので、1台ずつデプロイできるようにターゲットとstageを同じにしています。

この状態で一度vendor/bin/dep deploy LaravelWeb1コマンドを実行して、デプロイしてみます。
するとartisan:migrateで以下のようなエラーが出ます。

In Client.php line 103:
                                                                                       
  The command "/usr/bin/php /var/www//releases/1/src/artisan migrate --force" failed.  
                                                                                       
  Exit Code: 1 (General error)                                                         
                                                                                       
  Host Name: LaravelWeb1                                                               
                                                                                       
  ================                                                                     
                                                                                       
  In Connection.php line 703:                                                          
                                                                                       
    SQLSTATE[HY000] [2002] Connection refused (SQL: select * from information_s        
    chema.tables where table_schema = forge and table_name = migrations and tab        
    le_type = 'BASE TABLE')                                                            
                                                                                       
                                                                                       
  In Connector.php line 70:                                                            
                                                                                       
    SQLSTATE[HY000] [2002] Connection refused                                          

これはただしくRDSに接続できていないということですので、EC2にログインし、.envを書き換えてあげます。

cd /var/www/shared
vim .env

もう一度実行すれば正しくデプロイが通ると思います。

ドキュメントルートの変更

一度EC2でデプロイされた場所を見てみます。

/var/www
[ec2-user@ip-10-0-1-146 www]$ ll
合計 0
lrwxrwxrwx 1 ec2-user ec2-user  10  27 12:50 current -> releases/3
drwxrwxr-x 5 ec2-user ec2-user 176  130 09:14 php-docker-nginx-postgresql
drwxrwxr-x 5 ec2-user ec2-user  33  27 12:50 releases
drwxrwxr-x 3 ec2-user ec2-user  33  25 16:22 shared

もともとphp-docker-nginx-postgresqlというフォルダのsrc/publicをドキュメントルートとしていましたが、deployerではdeploy_pathの下にcurrent,releases,sharedというフォルダが作成され、最新版は常にcurrentに置かれることになります。ですのでドキュメントルートもcurrentの下を指すように修正する必要があります。

nginxの設定ファイルを以下のように書き換えてあげます。

nginx.conf
    server {
        listen       80;
        listen       [::]:80;
        server_name  _;
        root          /var/www/current/src/public;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

ちなみにsharedにはデプロイを行っても常に共有されるファイル(先程の.envファイルや、ログファイルなど)が置かれ、releasesにはデプロイされたフォルダが順番に置かれ一定個数保持されます。(デフォルト5つ)

ALBからの切り離しと再登録を前後に挟む

deployerでもローリングアップデートが行いたいので、ALBからの切り離しと再登録を前後にはさむよう修正します。AWSCLIのコマンドは以下の記事を参考にします。

https://zenn.dev/tokku5552/articles/aws-target-group-desc

deploy.php
before('deploy:info', 'deregister-targets');
task('deregister-targets', function () {
  runLocally('aws elbv2 deregister-targets --target-group-arn {{target_group_arn}} --targets Id={{instance_id}}');
});

after('deploy:unlock', 'register-targets');
task('register-targets', function () {
  runLocally('aws elbv2 register-targets --target-group-arn {{target_group_arn}} --targets Id={{instance_id}},Port=80 ');
});

deployerでは上記の様にtask()を使って自分でタスクを定義することができます。runLocally()はサーバー側ではなくローカル側でコマンドを実行します。
{{target_group_arn}}のように2つのカッコでくくって変数を呼び出します。server.ymlに定義すれば他の変数も使うことができます。

deregisterの方はdeploy:infoの前に実行、registerの方はdeploy:unlockのあとに実行しています。
どのタイミングで差し込むかについては、以下にLaravelのレシピが乗っているので、こちらをみるとdeployコマンドの中で実際にどんなタスクが実行されているのかを確認することができ、差し込む場所を決める参考になります。

https://github.com/deployphp/deployer/blob/6.x/recipe/laravel.php

実行

1台ずつALBから切り離し->デプロイ->ALBへ再登録を行うことができます。
以下は実行例です。(コンテナ内から実行しています)

root@a17eaed259b0:/work/src# vendor/bin/dep deploy LaravelWeb2
✔ Executing task deregister-targets
✈︎ Deploying main on 18.181.224.249
✔ Executing task deploy:prepare
✔ Executing task deploy:lock
✔ Executing task deploy:release
✔ Executing task deploy:update_code
✔ Executing task set_release_path
✔ Executing task deploy:shared
✔ Executing task deploy:vendors
✔ Executing task deploy:writable
✔ Executing task artisan:storage:link
✔ Executing task artisan:view:cache
✔ Executing task artisan:config:cache
✔ Executing task artisan:optimize
✔ Executing task artisan:migrate
✔ Executing task deploy:symlink
✔ Executing task deploy:unlock
✔ Executing task register-targets
✔ Executing task cleanup
Successfully deployed!

まとめ

ローカル環境からdeployerを使ってEC2へローリングアップデートを行う方法を紹介しました。
次は、これをGitHub Actionsに乗せてCI/CDを組みたいと思います。

参考

Discussion