GitLab CI/CDとEnvoyを使ったLaravelアプリケーションのテストとデプロイ
導入
GitLab は継続的インテグレーションでアプリケーションを機能させ、新しいコード変更をいつでも簡単に本番サーバーにデプロイすることができます。
このチュートリアルでは、Laravelアプリケーションの初期化とEnvoyタスクの設定方法を紹介し、GitLab CI/CDによる 継続的デリバリーでテストとデプロイを行う方法を紹介します。
LaravelとLinuxサーバーの基本的な経験があり、GitLabの使い方を知っていることを前提としています。
LaravelはPHPで書かれた高品質のWebフレームワークです。素晴らしいコミュニティがあり、ドキュメントも充実しています。通常のルーティング、コントローラ、リクエスト、レスポンス、ビュー、(ブレード)テンプレートはもちろんのこと、Laravelはキャッシュ、イベント、ローカライズ、認証など多くの追加サービスを提供しています。
PHPベースのSSHタスクランナーとしてEnvoyを使用します。リポジトリからのプロジェクトのクローン、Composerの依存関係のインストール、Artisanコマンドの実行など、リモートサーバー上で実行できるタスクを設定するために、クリーンで最小限のBlade構文を使用します。
GitLab上でLaravelアプリを初期化します。
新しい Laravel プロジェクトをインストールしたと仮定して、まずはユニットテストを行い、プロジェクトの Git を初期化しましょう。
ユニットテスト
Laravelの新規インストール(現在8.0)には、testsディレクトリに’Feature’と’Unit’の2種類のテストが含まれています。test/Unit/ExampleTest.php
からのユニットテストです:
<?php
namespace Tests\Unit;
...
class ExampleTest extends TestCase
{
public function testBasicTest()
{
$this->assertTrue(true);
}
}
このテストは、与えられた値がtrueであることをアサートするだけのシンプルなものです。
LaravelはデフォルトでテストにPHPUnit
。vendor/bin/phpunit
を実行すると、緑色の出力が表示されるはずです:
vendor/bin/phpunit
OK (1 test, 1 assertions)
このテストは、後でGitLab CI/CDでアプリを継続的にテストするために使用します。
GitLab にプッシュします。
ローカルでアプリを立ち上げることができたので、次はコードベースをリモートリポジトリにプッシュしましょう。GitLab にlaravel-sample
という名前の新しいプロジェクトを作成しましょう。プロジェクトのトップページに表示されるコマンドラインにしたがってリポジトリを立ち上げ、最初のコミットをプッシュします。
cd laravel-sample
git init
git remote add origin git@gitlab.example.com:<USERNAME>/laravel-sample.git
git add .
git commit -m 'Initial Commit'
git push -u origin main
本番サーバーの設定
Envoy と GitLab CI/CD の設定を始める前に、本番サーバーがデプロイできる状態になっていることを確認しましょう。Linux, NGINX, MySQL, PHP の略である LEMP スタックを Ubuntu 16.04 にインストールしました。
新しいユーザーを作成します。
それでは、ウェブサイトをデプロイするために使用する新しいユーザーを作成し、Linux ACL を使用して必要な権限を与えてみましょう:
# Create user deployer
sudo adduser deployer
# Give the read-write-execute permissions to deployer user for directory /var/www
sudo setfacl -R -m u:deployer:rwx /var/www
UbuntuサーバーにACLがインストールされていない場合は、このコマンドを使ってインストールしてください:
sudo apt install acl
SSHキーの追加
GitLab 上の非公開リポジトリから本番サーバーにアプリをデプロイしたいとしましょう。まず、デプロイユーザーのためにパスフレーズなしのSSH キーペアを生成する必要があります。
その後、デプロイ作業を自動化するために、デプロイユーザーとしてサーバーに SSH 接続するための秘密鍵をコピーします:
# As the deployer user on server
#
# Copy the content of public key to authorized_keys
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
# Copy the private key text block
cat ~/.ssh/id_rsa
では、これをCI/CD変数としてGitLabプロジェクトに追加しましょう。プロジェクトの CI/CD 変数はユーザー定義の変数で、セキュリティのために.gitlab-ci.yml
の外に保存されます。プロジェクトの設定>CI/CDからプロジェクトごとに追加することができます。
KEYフィールドにSSH_PRIVATE_KEY
という名前を追加し、VALUEフィールドに先ほどコピーした秘密鍵を貼り付けます。この変数は後で.gitlab-ci.yml
、パスワードを入力せずにデプロイユーザーとしてリモートサーバーに簡単に接続するために使用します。
また、公開キーをプロジェクト>設定>リポジトリに デプロイキーとして追加する必要があります。これにより、SSHプロトコルを使用してサーバーからリポジトリにアクセスできるようになります。
# As the deployer user on the server
#
# Copy the public key
cat ~/.ssh/id_rsa.pub
Title」フィールドに任意の名前を追加し、「Key」フィールドに公開鍵を貼り付けます。
では、deployer
ユーザーがリポジトリにアクセスできることを確認するために、サーバー上にリポジトリをクローンしてみましょう。
# As the deployer user on server
#
git clone git@gitlab.example.com:<USERNAME>/laravel-sample.git
Are you sure you want to continue connecting (yes/no)?
と聞かれたらyes と答えましょう。GitLab.com が既知のホストに追加されます。
NGINXの設定
ここで、ウェブサーバの設定がpublic
ではなくcurrent/public
を指していることを確認しましょう。
デフォルトのNGINXサーバーブロック設定ファイルを開きます:
sudo nano /etc/nginx/sites-available/default
設定は次のようになります。
server {
root /var/www/app/current/public;
server_name example.com;
# Rest of the configuration
}
/var/www/app/current/public
、アプリ名をアプリのフォルダ名に置き換えてもかまいません。
Envoyのセットアップ
これでLaravelアプリの本番準備は完了です。次はEnvoyを使ってデプロイを行います。
Envoyを使用するには、まずLaravelの指示に従ってローカルマシンにインストールします。
Envoyの仕組み
Envoyの長所は、Bladeエンジンを必要とせず、Bladeのシンタックスを使用してタスクを定義できることです。まず始めに、アプリのルートにEnvoy.blade.php
、Envoyをテストするシンプルなタスクを作成します。
@servers(['web' => 'remote_username@remote_host'])
@task('list', ['on' => 'web'])
ls -l
@endtask
ご想像の通り、ファイルの先頭の@servers
ディレクティブ内に配列があり、web
というキーとサーバーのアドレスの値(例えば、deployer@192.168.1.1
)がコンテナとして格納されています。そして、@task
ディレクティブの中に、タスクが実行されたときにサーバー上で実行される Bash コマンドを定義します。
内部マシンではrun
コマンドを使用して Envoy タスクを実行します。
envoy run list
先ほど定義したlist
タスクが実行され、サーバに接続してディレクトリの内容をリストアップします。
EnvoyはLaravelの依存関係ではないので、どのようなPHPアプリケーションにも使用できます。
ダウンタイムなしのデプロイ
本番サーバにデプロイするたびに、Envoy は GitLab リポジトリからアプリの最新リリースをダウンロードし、プレビューリリースに置き換えます。Envoy はダウンタイムなしでこれを行うので、誰かがサイトをレビューしている間、デプロイ中に心配する必要はありません。私たちのデプロイ計画は、GitLab リポジトリから最新リリースをクローンし、Composer の依存関係をインストールし、最後に新しいリリースを有効化することです。
setup ディレクティブ
デプロイプロセスの最初のステップは、@setupディレクティブで変数のセットを定義することです。app
はアプリケーションの名前に変えてもかまいません:
...
@setup
$repository = 'git@gitlab.example.com:<USERNAME>/laravel-sample.git';
$releases_dir = '/var/www/app/releases';
$app_dir = '/var/www/app';
$release = date('YmdHis');
$new_release_dir = $releases_dir .'/'. $release;
@endsetup
...
-
$repository
はリポジトリのアドレスです。 -
$releases_dir
ディレクトリはアプリをデプロイする場所です。 -
$app_dir
はサーバー上で動作するアプリの実際の場所です。 -
$release
アプリの新しいリリースをデプロイするたびに、現在の日付を名前に持つ新しいフォルダを取得します。 -
$new_release_dir
は新しいリリースのフルパスです。
storyディレクティブ
storyディレクティブは、1つのタスクとして実行できるタスクのリストを定義することができます。ここでは、clone_repository
,run_composer
,update_symlinks
という3つのタスクがあります。これらの変数は、タスクのコードをよりすっきりさせるために使用します:
...
@story('deploy')
clone_repository
run_composer
update_symlinks
@endstory
...
これら3つのタスクを1つずつ作ってみましょう。
リポジトリのクローン
最初のタスクは、releases
ディレクトリを作成し(存在しない場合)、リポジトリのmain
ブランチ(デフォルト)を$new_release_dir
変数で指定した新しいリリースディレクトリにクローンします。releases
ディレクトリには、すべてのデプロイが格納されます:
...
@task('clone_repository')
echo 'Cloning repository'
[ -d {{ $releases_dir }} ] || mkdir {{ $releases_dir }}
git clone --depth 1 {{ $repository }} {{ $new_release_dir }}
cd {{ $new_release_dir }}
git reset --hard {{ $commit }}
@endtask
...
プロジェクトが大きくなるにつれて、Git の履歴も長くなっていきます。リリースごとにディレクトリを作成するので、リリースごとにプロジェクトの履歴をダウンロードする必要はないかもしれません。--depth 1
オプションは、システムの時間とディスクスペースを節約できる素晴らしいソリューションです。
Composerによる依存関係のインストール
ご存知かもしれませんが、このタスクは新しいリリースディレクトリに移動し、Composerを実行してアプリケーションの依存関係をインストールするだけです:
...
@task('run_composer')
echo "Starting deployment ({{ $release }})"
cd {{ $new_release_dir }}
composer install --prefer-dist --no-scripts -q -o
@endtask
...
新しいリリースのアクティベート
新しいリリースの要件を準備したら、次にすることは、ストレージディレクトリを削除し、アプリケーションのstorage
ディレクトリと.env
ファイルを新しいリリースに向けるためのシンボリックリンクを 2 つ作成することです。次に、current
アプリディレクトリに配置した current
名前で、新しいリリースへのシンボリックリンクをもう1つ作成する必要があります。current
シンボリックリンクは current
常にアプリの最新リリースを指します:
...
@task('update_symlinks')
echo "Linking storage directory"
rm -rf {{ $new_release_dir }}/storage
ln -nfs {{ $app_dir }}/storage {{ $new_release_dir }}/storage
echo 'Linking .env file'
ln -nfs {{ $app_dir }}/.env {{ $new_release_dir }}/.env
echo 'Linking current release'
ln -nfs {{ $new_release_dir }} {{ $app_dir }}/current
@endtask
ご覧のように、ln
コマンドのオプションとして-nfs
を使用しています。これは、storage
、.env
、current
がプレビューのリリースを指すことがなくなり、強制的に新しいリリースを指すようになることを意味します(-nfs
からのf
は強制を意味します)。
フルスクリプト
スクリプトは準備できましたが、deployer@192.168.1.1
をあなたのサーバーに変更し、/var/www/app
をアプリをデプロイしたいディレクトリに変更してください。
最後に、Envoy.blade.php
ファイルは次のようになります:
@servers(['web' => 'deployer@192.168.1.1'])
@setup
$repository = 'git@gitlab.example.com:<USERNAME>/laravel-sample.git';
$releases_dir = '/var/www/app/releases';
$app_dir = '/var/www/app';
$release = date('YmdHis');
$new_release_dir = $releases_dir .'/'. $release;
@endsetup
@story('deploy')
clone_repository
run_composer
update_symlinks
@endstory
@task('clone_repository')
echo 'Cloning repository'
[ -d {{ $releases_dir }} ] || mkdir {{ $releases_dir }}
git clone --depth 1 {{ $repository }} {{ $new_release_dir }}
cd {{ $new_release_dir }}
git reset --hard {{ $commit }}
@endtask
@task('run_composer')
echo "Starting deployment ({{ $release }})"
cd {{ $new_release_dir }}
composer install --prefer-dist --no-scripts -q -o
@endtask
@task('update_symlinks')
echo "Linking storage directory"
rm -rf {{ $new_release_dir }}/storage
ln -nfs {{ $app_dir }}/storage {{ $new_release_dir }}/storage
echo 'Linking .env file'
ln -nfs {{ $app_dir }}/.env {{ $new_release_dir }}/.env
echo 'Linking current release'
ln -nfs {{ $new_release_dir }} {{ $app_dir }}/current
@endtask
デプロイする前にもう1つすべきことは、アプリケーションのstorage
フォルダを手動でサーバの/var/www/app
ディレクトリにコピーすることです。この作業は別のEnvoyタスクで行うことができます。また、Laravelの本番環境変数を設定するために、同じパスに.env
。これらは永続的なデータで、新しいリリースごとに共有されます。
ここで、envoy run deploy
を実行してアプリをデプロイする必要がありますが、このチュートリアルの後半で説明する CI の環境で GitLab が処理してくれるので、その必要はありません。
いよいよEnvoy.blade.phpをコミットしてブランチにプッシュしますmain
。 main
コラボレーションはこのチュートリアルの範囲外なのでmain
、シンプルに main
するために、フィーチャーブランチを使わずにmain
直接コミット main
します。実際のプロジェクトでは、ブランチをまたがってコードを移動させるためにイシュートラッカーや マージリクエストを使用します:
git add Envoy.blade.php
git commit -m 'Add Envoy'
git push origin main
GitLab による継続的インテグレーション
GitLab でアプリの準備ができたので、手動でデプロイすることもできます。しかし、一歩進んで継続的デリバリーを使って自動でデプロイしてみましょう。イシューにいち早く気づくために、自動化されたテストのセットですべてのコミットをチェックし、テストの結果に満足したらターゲット環境にデプロイする必要があります。
GitLab CI/CDでは、Dockerエンジンを使ってアプリのテストとデプロイのプロセスを処理することができます。Dockerについてよく知らない場合は、Set up automated buildsを参照してください。
GitLab CI/CDでアプリをビルド、テスト、デプロイできるようにするには、作業環境を準備する必要があります。そのために、Laravelアプリの実行に必要な最低限の要件を備えたDockerイメージを使用します。他にも方法はありますが、ビルドの動作が遅くなる可能性があります。
コンテナイメージの作成
アプリのルート・ディレクトリに、以下の内容でDockerfileを作成しましょう:
# Set the base image for subsequent instructions
FROM php:7.4
# Update packages
RUN apt-get update
# Install PHP and composer dependencies
RUN apt-get install -qq git curl libmcrypt-dev libjpeg-dev libpng-dev libfreetype6-dev libbz2-dev
# Clear out the local repository of retrieved package files
RUN apt-get clean
# Install needed extensions
# Here you can install any other extension that you need during the test and deployment process
RUN docker-php-ext-install mcrypt pdo_mysql zip
# Install Composer
RUN curl --silent --show-error "https://getcomposer.org/installer" | php -- --install-dir=/usr/local/bin --filename=composer
# Install Laravel Envoy
RUN composer global require "laravel/envoy=~1.0"
公式のPHP 7.4 Dockerイメージを追加しました。このイメージは、PHPがプリインストールされたDebian busterの最小インストールで構成されており、私たちのユースケースで完璧に動作します。
必要なPHP拡張モジュールをインストールするために、docker-php-ext-install
(公式のPHP Dockerイメージで提供されています)を使用しました。
GitLabコンテナレジストリのセットアップ
Dockerfile
ができたので、ビルドしてGitLab コンテナレジストリにプッシュしましょう。
レジストリは、後で使う画像を保存したりタグ付けしたりする場所です。開発者は、非公開のイメージや会社のイメージ、あるいはテストにだけ使う使い捨てのイメージのために、自分自身のレジストリを管理したいと思うかもしれません。GitLab Container Registryを使えば、別のサービスを立ち上げて管理したり、公開レジストリを使う必要はありません。
GitLabプロジェクトのリポジトリでレジストリタブに移動します。
このタブを見るには、プロジェクトのコンテナレジストリを有効にする必要があるかもしれません。プロジェクトのSettings > General > Visibility, project features, permissions にあります。
私たちのマシンでContainer Registryを使い始めるには、まずGitLabのユーザー名とパスワードを使ってGitLabレジストリにサインインする必要があります。DockerMachineがインストールされていることを確認してから、以下のコマンドを実行します:
docker login registry.gitlab.com
そしてイメージをビルドしてGitLabにプッシュします:
docker build -t registry.gitlab.com/<USERNAME>/laravel-sample .
docker push registry.gitlab.com/<USERNAME>/laravel-sample
おめでとうございます!ページを更新すると、最初の Docker イメージが GitLab レジストリにプッシュされているはずです:
GitLab CI/CDを使ってDockerイメージをビルドしてプッシュすることもできます。
このイメージは.gitlab-ci.yml
設定ファイルのさらに下のほうで、アプリのテストとデプロイの処理に使います。
Dockerfile
ファイルをコミットしましょう。
git add Dockerfile
git commit -m 'Add Dockerfile'
git push origin main
GitLab CI/CDのセットアップ
GitLab CI/CD でアプリをビルドしてテストするには、リポジトリのルートに.gitlab-ci.yml
というファイルが必要です。これは Circle CI や Travis CI と似ていますが、GitLab に組み込まれています。
.gitlab-ci.yml
ファイルは以下のようになります:
default:
image: registry.gitlab.com/<USERNAME>/laravel-sample:latest
services:
- mysql:5.7
variables:
MYSQL_DATABASE: homestead
MYSQL_ROOT_PASSWORD: secret
DB_HOST: mysql
DB_USERNAME: root
stages:
- test
- deploy
unit_test:
stage: test
script:
- cp .env.example .env
- composer install
- php artisan key:generate
- php artisan migrate
- vendor/bin/phpunit
deploy_production:
stage: deploy
script:
- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
- eval $(ssh-agent -s)
- ssh-add <(echo "$SSH_PRIVATE_KEY")
- mkdir -p ~/.ssh
- '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
- ~/.composer/vendor/bin/envoy run deploy --commit="$CI_COMMIT_SHA"
environment:
name: production
url: http://192.168.1.1
when: manual
rules:
- if: $CI_COMMIT_BRANCH == "main"
これだけでも大変でしょう?それでは、一歩ずつ見ていきましょう。
イメージとサービス
Runner は .gitlab-ci.yml
で定義されたスクリプトを実行します。image
キーワードは、Runnerに使用する画像を指示します。services
キーワードは、メインイメージにリンクされる追加イメージを定義します。ここでは、前に作成したコンテナイメージをメインイメージとして使用し、サービスとして MySQL 5.7 を使用します。
default:
image: registry.gitlab.com/<USERNAME>/laravel-sample:latest
services:
- mysql:5.7
...
異なる PHP バージョンやデータベース管理システムでアプリをテストしたい場合は、テストジョブごとに異なるimage
やservices
キーワードを定義できます。
CI/CD 変数
GitLab CI/CDではCI/CD変数をジョブで使うことができます。データベース管理システムとしてMySQLを定義しました。MySQLにはデフォルトでスーパーユーザーrootが作成されています。
そこで、MYSQL_DATABASE
変数をデータベース名として、MYSQL_ROOT_PASSWORD
変数をroot
のパスワードとして定義することで、MySQLインスタンスの設定を調整する必要があります。 MySQL変数についての詳細は、公式のMySQL Docker Imageを参照してください。
また、変数DB_HOST
をmysql
に、DB_USERNAME
をroot
に設定します。これらはLaravel固有の変数です。メインDockerイメージにリンクされたサービスとしてMySQL Dockerイメージを使用するため、127.0.0.1
の代わりにmysql
としてDB_HOST
を定義します。
variables:
MYSQL_DATABASE: homestead
MYSQL_ROOT_PASSWORD: secret
DB_HOST: mysql
DB_USERNAME: root
最初のジョブとしてのユニットテスト
unit_test
ジョブを実行するときに実行されるスクリプトキーワードの配列として、必要な Shell スクリプトを定義しました。
これらのスクリプトはLaravelを準備するためのいくつかのArtisanコマンドで、スクリプトの最後に、PHPUnit
によってテストを実行します。
unit_test:
script:
# Install app dependencies
- composer install
# Set up .env
- cp .env.example .env
# Generate an environment key
- php artisan key:generate
# Run migrations
- php artisan migrate
# Run tests
- vendor/bin/phpunit
プロダクションへのデプロイ
ジョブdeploy_production
はアプリを本番サーバーにデプロイします。Envoyでアプリをデプロイするために、$SSH_PRIVATE_KEY
変数をSSH秘密鍵として設定する必要がありました。SSHキーが正常に追加されたら、Envoyを実行できます。
前述したように、GitLabは継続的デリバリーの手法もサポートしています。environmentキーワードは、このジョブがproduction
環境にデプロイすることをGitLabに伝えます。url
キーワードは、GitLab Environments ページに私たちのアプリケーションへのリンクを生成するために使われます。rules:if
キーワードは、パイプラインがmain
ブランチをビルドしているときだけジョブを実行するように GitLab CI/CD に伝えます。最後に、when: manual
はジョブを自動実行から手動アクションに切り替えるために使います。
deploy_production:
script:
# Add the private SSH key to the build environment
- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
- eval $(ssh-agent -s)
- ssh-add <(echo "$SSH_PRIVATE_KEY")
- mkdir -p ~/.ssh
- '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
# Run Envoy
- ~/.composer/vendor/bin/envoy run deploy
environment:
name: production
url: http://192.168.1.1
when: manual
rules:
- if: $CI_COMMIT_BRANCH == "main"
本番環境にデプロイする前にアプリケーションを最終テストするために、ステージング環境用の別のジョブを追加することもできます。
GitLab CI/CD をオンにします。
GitLab CI/CDでアプリをテストしてデプロイするために必要なものはすべて用意しました。そのためには、.gitlab-ci.yml
をコミットしてmain
ブランチにプッシュします。これでパイプラインが起動し、プロジェクトのパイプラインからライブで見ることができます。
ここでは、テストと デプロイのステージを見てみましょう。Testステージではunit_test
ビルドが実行されています。Runner の出力を見るには、これを選択してください。
コードがパイプラインを正常に通過したら、右側の再生ボタンを選択して本番サーバーにデプロイできます。
デプロイパイプラインが正常に通過したら、パイプライン > 環境に移動します。
期待通りに動作しない場合は、アプリの最新動作バージョンにロールバックできます。
右側にある外部リンクアイコンを選択すると、GitLabが本番用のウェブサイトを開きます。デプロイが成功し、アプリケーションが稼動していることがわかります。
デプロイ後の本番サーバー上のアプリケーションのディレクトリ構造がどうなっているのか知りたい場合は、current
、releases
、storage
という名前の3つのディレクトリをご覧ください。ご存知のように、current
ディレクトリは最新リリースを指すシンボリックリンクです。.env
ファイルはLaravelの環境変数で構成されています。
current
ディレクトリに移動すると、アプリケーションの内部が見えるはずです。ご覧のように、.env
は/var/www/app/.env
ファイルを指しており、storage
は/var/www/app/storage/
ディレクトリを指しています。
結論
GitLab CI/CDを設定して自動テストを実行し、継続的デリバリーの手法を使ってEnvoyを使ったLaravelアプリケーションをコードベースから直接本番環境にデプロイしました。
Envoyはまた、私たちのカスタムBashスクリプトを書いたり、Linuxの魔法を使ったりすることなく、アプリケーションをデプロイするのに役立ちました。