LaravelのアプリケーションをAWS CodeDeployでデプロイするときの注意点

リポジトリのブランチがマージされたらCodeDeployが自動でLaravelアプリケーションをデプロイする、というのをやろうとしたらハマったので備忘録として書きました。
もし同様のエラーを体験されたら参考にしていただければ幸いです:smiley:

## TL;DR

* CodeDeployを利用してデプロイするなら、デプロイ先のサーバにログインして直接ファイルを追加しないこと。デプロイがコケる。
* appspec.ymlのデプロイ対象のディレクトリが、デプロイ先のディレクトリ内でコード管理対象にないファイルがあると、デプロイがコケる。
* LaravelアプリケーションをCodeDeployでデプロイする際はstorageディレクトリを.gitignoreに追加してデプロイ対象から外す。
* appspec.ymlではワイルドカードを指定することはできない。（※2018/05時点）
* appspec.ymlで最新リビジョンとの差異があった場合の上書きは指定できない。（※2018/05時点）
* CodeDeployのデプロイグループの編集では上書き設定はできない。（手動でデプロイする場合は上書き設定の項目あり）（※2018/05時点）

## 現象

|名前|バージョン|
|---| -------- |
|PHP | 7.2.4    |
|Laravel| 5.5.40 |
|CircleCI| 1.0 |

GitHubにホスティングされているLaravelアプリケーションリポジトリのmasterやdevelopブランチがマージされたら、GitHubと連携したCircleCIがテストが通ったあとAWSCodeDeployを使ってCodeDeployに定義しているデプロイグループに自動でデプロイする、ということをやろうとしました。

そのために以下の作業をしました。各作業の手順については本稿では割愛します。

* AWSCodeDeployの設定
* IAMの設定
* デプロイするソースを格納するためのS3バケットを作成
* インスタンスにAWSCodeDeployエージェントのインストール
* circle.yml（CircleCI2.0なら.circleci/config.yml）の定義
* CodeDeployの定義ファイルのappspec.ymlの定義
* CodeDeployのライフサイクルで実行されるシェルスクリプト（BeforeInstall.sh, AfterInstall.sh, ApplicationStart.sh, ValidateService.sh）

これらの準備をしてデプロイが実行されるか確認したところ、
１回目は問題なくデプロイすることができましたが、
２回目にCodeDeployでエラーが発生しました。

AWSの管理コンソールで確認すると

```
The overall deployment failed because too many individual instances failed deployment, too few healthy instances are available for deployment, or some instances in your deployment group are experiencing problems. (Error code: HEALTH_CONSTRAINTS)
```

というエラーが表示されていました。
失敗したデプロイを選択すると詳細画面に遷移します。

![スクリーンショット 2018-05-17 23.22.07.png](https://qiita-image-store.s3.amazonaws.com/0/82249/9c65a0d8-3d90-9b29-feb4-b452f5c0cf67.png)


インスタンスアクティビティの「イベントの表示」リンクをクリックすると、そのインスタンスのどのステップでエラーが発生した詳細がわかります。

![スクリーンショット 2018-05-17 23.22.28.png](https://qiita-image-store.s3.amazonaws.com/0/82249/3eaf7973-288c-e90c-78e9-c99a44b79319.png)

一覧にある「ログを表示する」リンクをクリックすると、エラーの内容がわかります。
詳細ページには以下のメッセージが表示されていました。

```
The deployment failed because a specified file already exists at this location: /home/testuser/path/to/storage/logs/laravel.log
```

このときのappspec.ymlは以下でした。

```appspec.yml
version: 0.0
os: linux

files:
  - source: /
    destination: /home/testuser/path/to/

hooks:
  BeforeInstall:
    - location: script/BeforeInstall.sh
  AfterInstall:
    - location: script/AfterInstall.sh
      timeout: 60
  ApplicationStart:
    - location: script/ApplicationStart.sh
      timeout: 60
  ValidateService:
    - location: script/ValidateService.sh
      timeout: 60
```

リポジトリのルートから全てをターゲットディレクトリにデプロイするようにしていました。

## 原因

調べてみると、CodeDeployはデプロイグループのソースのリビジョンを監視していて、最新のリビジョンで初めてデプロイされるファイルがすでにデプロイ先にあると書き換えないように上書きしないようにしているようです。
そのため、CodeDeployを使ってデプロイしている場合、直接デプロイ先のサーバにSSHでログインしてファイルを追加していたりするとデプロイがコケる可能性があります。

イメージとしてはドッペルゲンガーみたいな感じです。ドッペルゲンガーを見たら死ぬというアレです。新しく場所に来たらドッペルゲンガーがいて誰だお前！？うわああ！みたいなイメージです。
ファイルが手で編集した状態でCodeDeployされる場合は問題ないようです。

今回は上記のパターンとは違います。
初めてデプロイされるファイルではないし、laravel.logはログファイルなのでコード管理の対象になっていませんでした。
推測するに、storage/logsディレクトリをデプロイしたところ最新のリビジョンにないファイル（laravel.log）が存在したためにCodeDeployがフェイルオーバーして上書きせずにエラーとしているものと思います。

イメージとしては家のカギをあけてドアを開けたら知らん人がいて誰だお前は！？うわああ！みたいな感じです。

## 対策

Laravel5.5はデフォルトではstorage配下は.gitignoreの対象になっておりません。

* storage/app/public/.gitignore
* storage/framework/.gitignore
* storage/logs/.gitignore

とそれぞれ.gitignoreファイルがありこれらのディレクトリ・ファイルがコード管理の対象となっています。
これらのディレクトリは動作に必要ではありますが、cacheファイルやlogファイルが格納されるディレクトリなのでコードの管理上は必要ではありません。
なので、プロジェクトのルートの.gitignoreにstorageディレクトリを追加してコード管理の対象から外しました。

そうすることによって、storageディレクトリがデプロイの対象から外れるのでlogファイルが存在していてもデプロイ対象のディレクトリではないのでエラーが発生せずデプロイが実行されるようになりました。

## 余談

ファイルが存在してても上書きするような設定できないかなーと調べてみたら、
appspec.ymlで上書きの設定をできるようにしたら？というイシューがありました。
https://github.com/aws/aws-codedeploy-agent/issues/14

しかし、どうやらこれはまだ実装されていないようです…こんなに要望あるのに…
上記のイシューを読むと、上書きできるオプションをAWSの管理コンソールに追加した、という情報があったのですがよく見ると
手動でデプロイメントを追加の場合の画面にしか表示されないようです。デプロイグループの編集では項目が表示されません。
つまりリポジトリのブランチにpushされたりマージしたりされたら自動でデプロイするという使い方では上書き設定はできないとのこと。

次にappspec.ymlでルートディレクトリまるごとデプロイ対象にしていたのを、storageディレクトリを除いてデプロイするようにしてみたのですが、appspec.ymlのディレクトリ・ファイル指定でワイルドカードが使えないために、filesのsourceの指定の数が増えてしまい、保守性がとても悪くなりそうだったので断念しました。

https://github.com/aws/aws-codedeploy-agent/issues/98

そのため、コード管理の対象から外すという選択肢を選びました。
しかし、上書きがappspec.ymlや管理画面で対応していたらそちらで設定をしたほうがいいからはるかにラクそうです。早く対応してくれないかなぁ…