【Laravel & AWS】AWSのCodePipelineでユニットテストを自動化する【6日目】

AWSのサービス縛りでコスト最適で弾力性のあるLaravelアプリケーションのインフラを作ろうという企画の6日目です。
今回は、コードがプッシュされたらCodePipeline経由でユニットテスト(PHPUnit)が自動実行される様にしてみます。
ちなみにやりたい事の一覧は以下の通り(破線は完了した物)

  • アプリケーションサーバの構築 (1日目)
  • ビルド・デプロイの自動化 (1日目)
  • RDBに接続できる (2日目)
  • cronを使える様にする(Laravelのタスクスケジュールのトリガー) (2日目)
  • HTTPS対応 (3日目)
  • storageフォルダにシンボリックリンクを作成する (3日目)
  • storageをEFSに置き換える (3日目)
  • フロントのリソースの自動ビルド(node, npm)に対応 (4日目)
  • アプリケーションサーバのオートスケール (4日目)
  • ストレージ(EFS)のバックアップ/リストア (5日目)
  • RDSのバックアップ/リストア (5日目)
  • テストの自動化
  • Redisに接続できる
  • リソースをCDNから配信する
  • RDSの容量のオートスケール
  • ビルド完了時のslack通知
  • System Managerでシステムを可視化してみたい
  • Inspectorで脆弱性チェックを受けてみたい

ユニットテストを作成する

tests/Unit/ExampleTest.phpに以下の様なメソッドを追加する。
DBも絡むテストに対応したいので、EloquentModelでデータの保存も行っている。

/**
     * @test
     */
    public function canAccessDatabase()
    {
        Carbon::setTestNow(Carbon::create(2000, 1, 1, 0, 0, 0));

        $item = new Item();
        $item->name = "dummy item";
        $item->image = "dummy image";
        $item->save();

        $this->assertDatabaseHas('items', [
            'name' => 'dummy item'
        ]);
    }

ローカルで動作を確認する。

/var/www/app $ ./vendor/bin/phpunit
PHPUnit 8.5.4 by Sebastian Bergmann and contributors.

...                                                                 3 / 3 (100%)

Time: 976 ms, Memory: 24.00 MB

OK (3 tests, 3 assertions)

buildspec.ymlにphpunitの実行を追記する。

version: 0.2

phases:
  install:
    runtime-versions:
      php: 7.3
      nodejs: 12
  build:
    commands:
      - composer install
      - npm install
      - npm run prod
      - rm -rf node_modules
      - vendor/bin/phpunit # 追記
artifacts:
  files:
    - '**/*'

ちなみにphpunit.xmlにDBの接続設定を記述でき、今回はsqliteを使用している。
DBMS固有のクエリを使うとsqliteではテストできなくなるので、そのうちMySQLでテストを実行する方法を調べようと思う。

        <server name="DB_CONNECTION" value="sqlite"/>
        <server name="DB_DATABASE" value=":memory:"/>

CodeBuildでテストを実行する

コードをリポジトリにプッシュしてCodePipelineをトリガーする。
SQLiteがインストールされてなくてエラーになったりするかと思ったが、問題なく通った。

わざとテストを失敗させてみる。
ちゃんとエラー扱いになった。

テストのレポートを確認する

テストレポートという機能があるらしいが、どうやって使うんだろ?

ここにドキュメントがある。
どうやら最近追加された機能らしい。
buildspec.ymlphpunit実行コマンドにオプションを追加し、ログを吐き出す様にする。

./vendor/bin/phpunit --log-junit ./phpunit-log.xml

buildspec.ymlにログの場所などを追記してやる。

reports:
  SurefireReports:
    files:
      - 'phpunit-log.xml'
    base-directory: '/tmp'
    file-format: JunitXml

再度CodePipelineをトリガーするとレポートが追加された。

レポートをクリックするといい感じのレポートが表示された。
テストケースを見れば、どのテストが失敗しているかも分かる。

そんな感じでCodePipelineの中でテストが回せる様になった。

デプロイでエラーが発生

実は、前回の記事から期間が空き、Beanstalkのインスタンスが復旧できなくなっていました。
そのため再構築したのですが、そのインスタンスにデプロイするとエラーが発生する様になりました。
具体的には以下の様なエラーがインスタンスのイベントログに残っていました。
備忘を兼ねて解決方法を残しておく。

Failed to deploy application.
During an aborted deployment, some instances may have deployed the new application version. To ensure all instances are running the same version, re-deploy the appropriate application version
Create environment operation is complete, but with errors. For more information, see troubleshooting documentation.

ログを確認すると3日目に設定したEFSのマウントでエラーが発生している模様。

2020/07/24 11:54:07.231802 [ERROR] Error occurred during build: Command 01_mount failed

2020/07/24 11:54:07.231836 [ERROR] An error occurred during execution of command [app-deploy] - [PreBuildEbExtension]. Stop running the command. Error: EbExtension build failed. Please refer to /var/log/cfn-init.log for more details. 

/var/log/cfn-init-cmd.logで詳細を確認すると、/opt/elasticbeanstalk/bin/get-configを呼び出しているが存在せずエラーになっている事が分かる。

2020-07-25 04:21:04,977 P27625 [INFO] Command 00_init
2020-07-25 04:21:04,981 P27625 [INFO] Completed successfully.
2020-07-25 04:21:04,986 P27625 [INFO] ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2020-07-25 04:21:04,986 P27625 [INFO] Config prebuild_1_laravel_aws_example_app
2020-07-25 04:21:06,169 P27625 [INFO] ============================================================
2020-07-25 04:21:06,169 P27625 [INFO] Command 01_mount
2020-07-25 04:21:06,192 P27625 [INFO] -----------------------Command Output-----------------------
2020-07-25 04:21:06,192 P27625 [INFO]   /tmp/mount-efs.sh: line 3: /opt/elasticbeanstalk/bin/get-config: No such file or directory
2020-07-25 04:21:06,192 P27625 [INFO]   /tmp/mount-efs.sh: line 4: /opt/elasticbeanstalk/bin/get-config: No such file or directory
2020-07-25 04:21:06,192 P27625 [INFO]   Mounting EFS filesystem  to directory  ...
2020-07-25 04:21:06,193 P27625 [INFO]   Stopping NFS ID Mapper...
2020-07-25 04:21:06,193 P27625 [INFO]   rpc.idmapd is already stopped!
2020-07-25 04:21:06,193 P27625 [INFO]   Checking if EFS mount directory exists...
2020-07-25 04:21:06,193 P27625 [INFO]   Directory  already exists!
2020-07-25 04:21:06,193 P27625 [INFO]   
2020-07-25 04:21:06,193 P27625 [INFO]   Usage:
2020-07-25 04:21:06,193 P27625 [INFO]    mountpoint [-qd] /path/to/directory
2020-07-25 04:21:06,193 P27625 [INFO]    mountpoint -x /dev/device
2020-07-25 04:21:06,193 P27625 [INFO]   
2020-07-25 04:21:06,193 P27625 [INFO]   Check whether a directory or file is a mountpoint.
2020-07-25 04:21:06,193 P27625 [INFO]   
2020-07-25 04:21:06,193 P27625 [INFO]   Options:
2020-07-25 04:21:06,193 P27625 [INFO]    -q, --quiet        quiet mode - don't print anything
2020-07-25 04:21:06,193 P27625 [INFO]    -d, --fs-devno     print maj:min device number of the filesystem
2020-07-25 04:21:06,193 P27625 [INFO]    -x, --devno        print maj:min device number of the block device
2020-07-25 04:21:06,194 P27625 [INFO]   
2020-07-25 04:21:06,194 P27625 [INFO]    -h, --help     display this help and exit
2020-07-25 04:21:06,194 P27625 [INFO]    -V, --version  output version information and exit
2020-07-25 04:21:06,194 P27625 [INFO]   
2020-07-25 04:21:06,194 P27625 [INFO]   For more details see mountpoint(1).
2020-07-25 04:21:06,194 P27625 [INFO]   mount -t efs -o tls :/ 
2020-07-25 04:21:06,194 P27625 [INFO]   mount: :/: can't find in /etc/fstab.
2020-07-25 04:21:06,194 P27625 [INFO]   ERROR: Mount command failed!
2020-07-25 04:21:06,194 P27625 [INFO] ------------------------------------------------------------
2020-07-25 04:21:06,194 P27625 [ERROR] Exited with error code 1

どうやら、Amazon Linux 2にはget-configが実装されていないらしい。
https://github.com/awsdocs/elastic-beanstalk-samples/blob/master/configuration-files/aws-provided/instance-configuration/storage-efs-mountfilesystem.config#L54
この問題はBeanstalkの開発チームも認識していて、本記事執筆時点では対応中となっている。
https://github.com/aws/elastic-beanstalk-roadmap/issues/76

回避策として.ebextensions/storage-efs-mountfilesystem.config(ココ)にマウントディレクトリとファイルシステムIDをハードコードして回避した。

files:
  "/tmp/mount-efs.sh":
      mode: "000755"
      content : |
        #!/bin/bash

        # ハードコードしているので注意
        EFS_MOUNT_DIR=/efs/storage
        EFS_FILE_SYSTEM_ID=fs-XXXXXXXX

再度、デプロイするとまた別のエラーが発生した。
ログを確認すると、mount -t efs -o tls fs-XXXXXXXX:/ /efs/storageを実行した時に以下のエラーが発生している事が分かる。

mount.nfs4: Connection reset by peer

恐らくBeanstalkのインスタンスとEFSのインスタンスの間で通信が確立できていないっぽい。
セキュリティグループの問題か??

確認した所、BeanstalkのインスタンスにVPCのデフォルトのセキュリティグループが付与されておらず、NFSへ通信できない状態になっていた。
(VPCのデフォルトのセキュリティグループ同士は自由に通信できる。また、EFSのネットワークインタフェースにはVPCのデフォルトのセキュリティグループが付与されている。)

所感

いろいろ気になる所が更に増えてきた。おいおい確認しておきたい。

  • セキュリティグループ、ACL、VPC、サブネットあたりの理解度向上
  • フロントのアセット(JSやCSS)はビルドパイプラインを分けてCDNから配信したい
  • CodeBuildのPHPUnitでMySQLを使用する方法

コメントする