Laravel5.3をGitLab CIでCIしたり、CDしたり

タグ: Laravel5.3   Laravel   PHP7   GitLab   CI  

前回のGitLab CIのチュートリアル翻訳記事でGitLab組み込みのCIを使えるようになった方も、大勢いるかと思います。

なにせこのサイトは、現在Laravel関連の記事が多いので、Laravelの最新版5.3と絡めてみます。

参照

まずは、https://gitlab.com/でアカウント作ってください。古い情報をお読みの方は、自分でGitLabをホストしないと使えないと思っているかもしれません。実はGitHubと同じように、誰でもコードをホストして置けます。

続いてさっそく、プロジェクトを作ってください。リポジトリのことです。これもGitHubと同様に、新しく作成すると、その後に実行すべきコマンドが表示されます。もともと、GitLabはGitHubクローンとして開発されてきているため、似ているのは当然です。しかし、最近は独自機能をいろいろ追加しており、GitLab CIもその一つです。

ローカルに新しいLaravelプロジェクトを作成し、それを作成したプロジェクトにpushしてください。

単体テスト

Laravel5.3にはPHPUnitのテストができています。Composerのパッケージとして指定されているため、プロジェクトルートでvender/bin/phpunitにより実行できます。

これをGitLabへgit pushするたびに実行してくれるようにしましょう。プロジェクトルートに.gitlab-ci.ymlファイルを以下の内容で作成してください。

image: php:7.1-alpine

# ステージの定義
# ステージはシーケンシャルに実行される
# 途中のステージが失敗した場合、以降のステージは中止される
stages:
  - unittest

# PHPUnitによるテストビルド
phpunit:
  stage: unittest
  cache:
    paths:
      - vendor
  before_script:
    - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
  script:
    - mv .env.example .env
    - composer install --prefer-dist --no-progress --ansi
    - php artisan key:generate
    - vendor/bin/phpunit

GitLabのCIは標準でDockerイメージ上で行われます。Dockerイメージもいろいろ用意されていますので、その中から適当なものを見つけるのが最初の作業となります。

最近は、小さいということでAlpineというLinuxが人気です。実際に、公式やユーザが用意したPHPのイメージを試してみましたが、単純にPHPUnitを実行したいだけであれば、Alpineを使うのがスピード的に有利でした。

PHPの公式Dockerイメージに、Alpine上にPHP7を載せたものが用意されていますので、これを使います。imageで指定しているのがそれです。

その後を簡単に説明すると、Composerをまずインストールし、composer installで必要なパッケージをインストールし、やっと最後にPHPUnitを実行しています。

まずは、上記のように.gitlab-ci.ymlを作成し、変更をコミットし、pushしてみてください。それから、上部ナビのPipelinesリンクから、いろいろ確認してください。リアリティがある方が、内容を理解しやすくなります。

YMLの内容

image項目は、既に説明しました通り、Dockerイメージを指定します。

本来、PHPUnitひとつだけを実行するだけでしたら、もっと単純に書けます。のちほど便利にするために少々手順を追加しています。

ステージはGitLabでは、順番に実行されるジョブグループの名前です。今回はunittestというステージをひとつだけ用意しましたが、便利になるのは2つ以上用意した場合です。

ステージは順番に実行されます。どこかのステージに所属するジョブが失敗すると、それ以降のステージの実行はスキップされます。

複数形のstagesでステージを定義します。各ジョブでは定義したステージをstageという単数形の項目で指定します。

ステージには複数のジョブを含めることができ、各ジョブは並列に実行されます。つまり、それぞれが独立してDocker上で実行されます。(実行順をコントロールするための指定項目もありますが、複雑になるので基本はシーケンシャルに実行されるステージと、パラレルに実行されるステージに所属するジョブで構成することを考えましょう。)

各ジョブのscriptが本体になります。そこにシェルスクリプトを記述していきます。before_scriptはそのジョブの始めに実行されます。まあ、scriptにまとめて書いても良いのですが、意味的にscriptは各ジョブで本来行う仕事、before_scriptはそのための準備を記述するとわかりやすいでしょう。

今回の例の場合、composerコマンドのインストールだけをbefore...に入れましたが、phpunitの実行直前までのコードをこちらに含めてもよいかと思います。

多少、難しいのはcacheです。しかし、今回の例はとても理解しやすですよ。

cacheはその通りキャッシュです。実行中のDockerイメージは「コンテナ」と呼ばれます。PHPのクラスファイルが、実行中はインスタンスになるようなものです。コンテナもPHPのインスタンス同様、そのままでは実行後に消えます。(正確に言えば、Docker CIが消しているわけです。)

実行中に生成したファイルなども消えます。PHPの場合、Composerで必要なパッケージ類を取得する作業は時間がかかります。ですから、インストールしたパッケージが保存されるvendor下をキャッシュして、次回の実行時に再利用できたら、早く実行できるわけです。

ただし、キャッシュの管理はGitLab CIの管理下に置かれており、できるだけ保存しておくという、ベストエフォート形式です。必ずキャッシュされ、次回実行時に利用できるという性格のものではありません。

さて、いくら自動で、GitLabの環境で行われるから、自分の懐もマシンのリソースも傷まないからと言って、毎回テストされるのはうざったいとか、自分やチームのやり方とそぐわない場合もあるでしょう。

採用している、運用フローに従い、いろいろなブランチ名が使用されます。そのブランチ名を条件として各ジョブの実行をコントロールできます。

unittestジョブの部分のみ、変更してみます。

phpunit:
  stage: unittest
  only:
    - staging
    - /^fix(ed)?(-)?(#)?[0-9]+$/
  cache:
    paths:
      - vendor
  before_script:
    - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
  script:
    - mv .env.example .env
    - composer install --prefer-dist --no-progress --ansi
    - php artisan key:generate
    - vendor/bin/phpunit

only項目を追加しました。どのブランチに対して、このジョブを実行するのかを指定できます。この場合のstagingのように、普通の文字列で指定すると、そのブランチ名と一致した場合に実行されます。

また、2つ目の/^fix(ed)?(-)?(#)?[0-9]+$/のように、Rubyの正規表現で指定すると、一致するブランチ名の場合に実行されます。この場合であれば、"fix10"とか"fixed12"、"fix-20"、"fixed#30"、"fix-#44"のような、いかにも「不具合を修正しました」感のあるブランチの場合は、テストを実行するようになります。

スクリプト記述時の注意点

Dockerのイメージは元になるOSにより、デフォルトのシェルが異なります。大抵はLinuxイメージでしょうが、デフォルトのshの正体はBashだったり、Ubuntu系のようにDashだったりします。このAlpineのデフォルトシェルはash(Almquist Shell)らしいです。それぞれ多少の違いがあり、そのためあるページの情報が動かないということもあります。

また、コマンド自身もバージョンやOSの違いをうけ、動作が異なる場合もあります。お手軽なのですが、こうした仮想化やLinuxに関わる、はまりがちなトラップには気をつけてください。

デプロイぽいこと

テストが通ったら、デプロイしたくなります。

デプロイ方法もいろいろありますが、いろいろと勉強になるので、今回はrsyncを使ってみましょう。

さっそく、.gitlab-ci.ymlをご覧ください。

image: php:7.1-alpine

stages:
  - テスト
  - デプロイ

PHPUnit単体テスト:
  stage: テスト
  only:
    - staging
    - /^fix(ed)?-?#?[0-9]+$/
  cache:
    paths:
      - vendor
  before_script:
    - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
  script:
    - mv .env.example .env
    - composer install --prefer-dist --no-progress --ansi
    - php artisan key:generate
    - vendor/bin/phpunit

rsyncによるデプロイ:
  stage: デプロイ
  only:
    - staging
  before_script:
    # Alpineのパッケージインストール定石
    - echo "ipv6" >> /etc/modules
    - apk update
    - apk add openssh-client rsync
  script:
    - eval $(ssh-agent -s)
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
    - chmod 600 ~/.ssh/id_rsa
    - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
    - rsync -av -e "ssh -p 256" ./ myname@sample.com:/somewhere/laravel/project/root/directory

デプロイのサンプルでありますが、いくつも重要なポイントを含んでいます。

Alpineのパッケージ

Alpineが小さいのは、余計なものがないからです。逆に言えば、必要に応じてインストールしなければなりません。

before_scriptはAlpineのパッケージ追加の定石コードです。apkがパッケージ操作コマンドで、最後のapk addがパッケージの追加、つまりインストールです。addの後に、インストールしたいパッケージ名を指定します。

実は、当初はGitLabのブログに書かれている方法でチェレンジしました。ssh-addを使用する方法です。プライベートキーの内容をssh-addに流し込むと、キーチェーンに追加される様なコードになっていましたが、このDockerイメージでは動作しなかったため、.ssh下に設置するベタな方法にしています。

openssh-clientssh-addなどが入っているパッケージで必要ないのですが、複数のパッケージを同時にインストールできることを示すために残しています。

コマンドなどがどのパッケージに入っているかは、Alpineのパッケージ検索ページcontentにコマンド名を指定し、検索します。見つからない場合は、用意されていません。

まずひとつ、Alpineのパッケージのインストール方法を学びました。

日本語の使用

ステージ名とジョブ名を日本語にしていることに、気が付かれていることでしょう。

ご覧の通り、使用できます。ジョブ名は文字列ですので、空白を間に入れ、文章のように指定することもできます。

これが2つ目の学習ポイントです。

機密情報

rsyncのようにSSHを利用するコマンドのために、キーペアを用意する必要があります。公開キーは、相手先サーバーのauthorized_keysに登録しておきます。ここらへんはSSHの設定のため省きます。

SSHを利用するため、プライベートキーを保持する必要があります。もちろんGitで管理するファイルに含めることはできません。(プライベートリポなら可能でしょうが…).gitlab-ci.ymlもGit管理されるファイルです。直接ハードコードはできません。

そこで、プロジェクトごとの設定ページの右側、設定ボタンを押すと表示される項目にある、Valiablesを利用します。これにより指定した値は、シェルの環境変数として取得できます。

今回は、SSH_PRIVATE_KEYという変数名で、プライベートキーの内容を登録してあります。それをDockerコンテナ中からのSSH接続のためのキーとして利用しています。

もう一つ重要なポイントがあります。使用するキーペアは新しく生成したものを利用しましょう。既存のものは使用しないことです。通常、この手のサイトに登録するのは「公開鍵」の方ですが、今回はプライベートキーを登録します。もしGitLabの従業員の中に、悪意を持った人間がいたら悪用されてしまいます。また、中間者攻撃も可能になります。そうした、リスクがありますので、万が一の場合の影響を最低限で済ませるように、キーペアの流用は避け、専用のキーペアを新しく生成しましょう。

SSH接続ができれば、他のデプロイ方法も簡単です。rsync系のコマンドで転送するのではなく、実動機でcomposerやgitで新しいソースを取得して、デプロイもできます。

その他

ランナーを実働機で走らせる方法などもデプロイで使えそうですが、長くなりますので、今回はこの辺で終わります。

現在、たとえばPHPソースコードをphp-cs-fixerにかけ、その結果をpushするとかを簡単に行う方法がまだありません。要望はあり、機能の実現待ちとなっています。