後編：EC2にFabric1.xでデプロイされていたPlayFrameworkのプロジェクトをGitHub Actions+CodeDeployでのデプロイに変えてみた話

この記事は[株式会社サイバー・バズ Advent calendar 2021](https://qiita.com/advent-calendar/2021/cyberbuzz)の記事です。

また、以下の記事の続きです。

https://qiita.com/kashira2339/items/395f8d1a8d53d8c902f6

## 続き

1. 中間生成物用のS3バケットの作成
2. GitHub Actionsのworkflow作成
3. 必要なIAMの作成
4. CodeDeploy用のappspec.ymlと各スクリプトの作成
5. CodeDeployのセットアップ

前回は、大体３の途中くらいまでを解説しました。
今回は、以下の画像のオレンジ色の部分をメインに解説します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36105/3a381b55-bcf6-ab1f-681b-743e9b71867d.png)

### 必要なIAMの作成

前回の最後にも、S3に中間生成物をアップロードするCI用のユーザーを作成しました。
今回は、CodeDeployに必要なロールの作成です。

まずは、CodeDeployグループに必要なサービスロールを作成します。
AWSコンソールのIAMの画面から、以下の画像のようになるよう、ロールを作成します。

名称はなんでも良いですが、わかるように`CodeDeployServiceRole`。
ポリシーにはAWS管理ポリシーの`AWSCodeDeployRole`をつけておけば問題ありません。

![codedeployservicerole.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36105/cd6e29cc-159a-6037-0469-c3794f65ac78.png)

もう一つ必要なIAMロールがありますが、CodePipelineの設定中に自動で作成させることができます。

### CodeDeploy用のappspec.ymlと各スクリプトの作成

CodeDeployはデプロイに際してライフサイクルイベントを発火する作りになっており、
各イベントにフックさせるスクリプトを定義しておくことができます。
[参考：AppSpec の「hooks」 - AWS CodeDeploy](https://docs.aws.amazon.com/ja_jp/codedeploy/latest/userguide/reference-appspec-file-structure-hooks.html)

中間生成物に含めておくことでデプロイ対象のEC2インスタンスが実行できるようになります。
今回は以下の3つのファイルを含めます。

```
├── appspec.yml
└── scripts
    ├── start.sh
    └── stop.sh
```

#### appspec.yml

CodeDeployが使用するファイルで、中間生成物のソースコード（前回作ってS3にアップロードしたzip）のルートに配置されている必要があります。

- `files` ... 今回の場合、ダウンロードしたS3のファイル郡を解凍後どこに再配置するかを示します。今回はec2-userのホームディレクトリ直下のxxxxxxに配置します。
- `hooks` ... ライフサイクルフックで呼ばれるスクリプトのパスとタイムアウト秒を定義しています。今回はアプリの停止と開始のスクリプトのみを指定しています。

```appspec.yml
version: 0.0
os: linux
files:
  - source: /
    destination: /home/ec2-user/xxxxxx
file_exists_behavior: OVERWRITE
hooks:
  ApplicationStop:
    - location: scripts/stop.sh
      timeout: 60
  ApplicationStart:
    - location: scripts/start.sh
      timeout: 60
```

#### scripts/stop.sh, scripts/start.sh

アプリケーションの停止と終了を行うスクリプトです。
今回の目的はFabric1.xからの移管ということで、必要最低限の記述のみです。
`scripts/stop.sh`ではPlayFramework2.3.xでの起動アプリのプロセス停止を行い
`scripts/start.sh`ではPlayFrameworkアプリケーションのバックグラウンド実行を行っています。

```scripts/stop.sh
#!/bin/bash

set -eo

ps=`ps -ef | grep "xxxxxx/target/universal/stage" | grep -v grep | wc -l`
if [ $ps -eq 1 ]; then
  kill `cat /home/ec2-user/xxxxxx/target/universal/stage/RUNNING_PID`
fi
```

```scripts/start.sh
#!/bin/bash

set -eo

nohup /home/ec2-user/sinbad/target/universal/stage/bin/xxxxxx -Duser.timezone=JST -Dconfig.file=/home/ec2-user/xxxxxx/target/universal/stage/conf/application-prod.conf &>> /dev/null 2>&1 &
```

これらのファイルをソースコードのルートに含めた中間生成物のzipを解凍すると
下記のようなディレクトリ構成になっています。

```
├── appspec.yml
├── scripts
│   ├── start.sh
│   └── stop.sh
└── target
    ├── resolution-cache
    ├── scala-2.11
    ├── streams
    ├── universal
    └── web
```


### CodeDeployのセットアップ

満を持してCodeDeployのセットアップを行なっていきます。
厳密には、CodeDeployおよびCodePipelineのセットアップが必要です。

まずは、新規にアプリケーションを作成しましょう。


| | 画像 |
|---|---|
| CodeDeployトップ |![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36105/f0a5c56a-3dc9-b0d4-eac0-9b49d0bc49f2.png)|
| アプリケーション作成画面 |![スクリーンショット 2021-12-06 20.20.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36105/8a1e8a2b-8fbe-8f75-b9af-5fac0f321650.png)|
|　アプリケーション作成直後 | ![スクリーンショット 2021-12-06 20.22.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36105/8c02d7aa-a5dc-4b9e-f3c2-e92e9d968620.png) |

簡単ですね。

次に、デプロイグループを作ります。上記画像中の「デプロイグループの作成」から作成できます。
ここではいくつかの設定項目があるので、それぞれの設定項目について簡単に解説します。

| 項目 | 入力値　| 補足 |
|---|---|---|
|デプロイグループ名の入力| xxxxxx-production | なんでも良いです。本番やステージングの切り分けがある場合はそれがわかると良いでしょう。 |
|サービスロール| CodeDeployServiceRole | 必要なIAMの作成　で作ったものを選択します |
|デプロイタイプ| インプレース | Blue/Greenデプロイの設定も可能ですが、今回は社内サービスのため、インプレース。インスタンス内のアプリケーションを直接更新します。　|
|環境設定| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36105/9efedb50-d163-f487-c4c5-f379e436fd6f.png) | 元々アプリケーションが稼働していたインスタンスについているタグを選択します（なければ付けましょう）。 タググループ1、2で絞り込んでいます。タググループが同一だとOR検索、異なるとAND検索になります。|
|AWS CodeDeploy エージェントのインストール|1回のみ|インスタンスにCodeDeployエージェントがインストールさえされていれば問題ありません。|
|デプロイ設定|CodeDeployDefault.AllAtOnce|インスタンスが1台の場合、このルールが適切です|
|Load balancer|ロードバランシングを有効にする　チェックを外す|サーバー1台かつ社内サービスなので、今回は必要ありませんでした。が、1台でもデプロイ中にメンテナンス画面等を出したい場合等は必要になるので、チェックを入れデプロイ先インスタンスが紐づいているロードバランサを選択します。|

少し項目が増えましたが、こちらも設定自体はシンプルです。

#### CodePipelineも設定

ここまでできたら、次はCodePipelineを設定します。
S3への中間生成物のアップロードをトリガーにして、先ほど作成したCodeDeployを走らせるために必要です。
以下が設定例になります。

| | 画像 | 補足|
|---|---|---|
|CodePipelineトップ|![スクリーンショット 2021-12-06 20.44.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36105/93b42b2f-55ea-6003-4532-7e2b1db62667.png)|パイプラインの作成ボタンから開始できます。|
|パイプラインの設定|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36105/936c3bad-23aa-9299-117a-6e25ecb8e126.png)|ここのロールは自動作成してくれます。好きなロール名にしましょう|
|ソースステージの追加| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36105/6a8a0474-72b9-52c3-91c5-9dc95391c37b.png)|GitHub ActionsがアップロードしているS3バケット名と、zipのオブジェクトキーを入力しましょう|
|ビルドステージの追加|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36105/9ed33cb1-a2ec-df0d-4bf4-c9941695845d.png)|ビルドはGitHub Actionsが済ませているので、今回はスキップです。|
|デプロイステージの追加|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36105/fc5d30c0-2260-c111-b1f1-349f4eef8866.png)|デプロイステージはつい先ほど設定したアプリケーション名とデプロイグループを選択しましょう。|

ここまで入力できれば、パイプラインは作成できます。

これで全体的な設定は完了となります。

### デプロイできなかったら？

appspec.ymlやEC2インスタンスの設定を主に疑ってみましょう。疑うポイントは以下です。

- appspec.ymlおよびスクリプトの設定
  - この設定ファイルがない、パスが誤っている、起動スクリプトが誤っている
- EC2インスタンスのタグ
  - 先程のCodeDeployのセットアップの環境設定で設定したタグの条件と合っているか
- EC2インスタンスのロール
  - EC2インスタンスについているIAMロールにS3へのアクセスロールがない場合など
- EC2インスタンスにCodeDeployエージェントがインストールされていない
  - おもむろに手動インストールしても良いでしょう。[Amazon Linuxではこの例のように設定できます。](https://docs.aws.amazon.com/ja_jp/codedeploy/latest/userguide/codedeploy-agent-operations-install-linux.html)

### 確認

GitHub ActionsでビルドからS3へのアップロードがうまくいったかは確認できます。
また、CodePipelineの実行がうまくいったかどうかは、AWSのコンソールから確認できます。

![codepipeline-success.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36105/96d3e1ef-e00b-0367-3d93-c9ba7f141b02.png)

S3 + CodeDeploy + CodePipeline に乗っていれば簡単に部分的な再実行やデバッグもできるので、
トラブル時の対応もしやすくなることが期待できるでしょう。

## おわりに

今回は、PlayFrameworkのプロジェクトのデプロイを
Fabric1.xから脱却しGitHub Actions+CodeDeployで自動化できたことで
GitHubが使える社員なら誰もがデプロイ作業を行えるようになりました。
GitHub+GitHub Actionsのおかげで、コード反映直前のレビューもPullRequestベースで行えるのは社内サービスといえども安心ですね。

仕組み自体は非常に簡単なので、うまく他のレガシーな社内サービスにも適用し、
極力運用を省力化していきたいと思います。