AWS CodeDeploy でEC2のBlue/Greenデプロイを作成する

# 概要
[AWS CodeDeploy](https://aws.amazon.com/jp/codedeploy/) を使ってBlue/Green デプロイの仕組みを構築する為の手順を紹介します。

# Blue/Greenデプロイとは？

現在稼働している環境と別にもう1つ稼働環境を作成し、ロードバランサー等のルーティングを新環境に向けるデプロイ方法です。

常にリクエストを受けている稼働中のサーバを置き換えるよりも安全にデプロイ可能なのがメリットになります。

# Blue/Greenデプロイの簡単な流れ

実際に私が構築したシステム構成図を元に簡単な流れを説明させて頂きます。

![CodeDeployBlueGreen.jpg](https://qiita-image-store.s3.amazonaws.com/0/71899/b04711da-b056-12bf-dada-49e90321f3f4.jpeg)

## 1. アプリケーションのBuildを行いS3にアップロードする

アプリケーションのbuildを行い、ソースコードをS3バケットにアップロードします。

buildの手順は各アプリケーションによって異なると思いますが、パッケージのインストールを行ったりソースコードのコンパイルをここで行います。

## 2. デプロイ要求を出す

開発者がCodeDeployのマネジメントコンソール上からデプロイの要求を行います。

## 3. デプロイ用のソースコードをS3バケットから取得

CodeDeployが`2. デプロイ要求を出す` で指定したリビジョンのソースコードを取得します。

## 4. 既存環境を元に新しい環境を作成する

既存環境を元に新しい環境（Green環境）を作成します。

AutoScalingGroupを利用する方法と手動でインスタンスを作る方法がありますが、この記事ではAutoScalingGroupを使った方法を紹介させて頂きます。

## 5. Green環境にS3バケットから取得したソースコードをデプロイ

CodeDeployが `3. デプロイ用のソースコードをS3バケットから取得` で取得したソースコードを新環境（Green環境）にデプロイを行います。

## 6. ルーティングターゲットをGreen環境に変更

CodeDeployがALBのルーティングターゲットをGreen環境に置き換えます。

処理が成功すると置き換え元のインスタンスは自動的に削除されます。（削除されないように設定する事も可能です）

# 事前に準備が必要な物

- [ALB](https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/application/introduction.html) もしくは [ELB](https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/classic/introduction.html) で動作しているWebアプリケーションを構築している事

ALBを用いた冗長化構成作成方法に関しては、この記事では取り上げません。
以前 [ALB（Application Load Balancer）でWebサービスを冗長化する](https://qiita.com/keita-nishimoto/items/e19ef9f33249828fa94a) という記事を書かせて頂きました。

こちらも参考にして頂ければと思います。

- CodeDeploy用IAM
- CodeDeploy対応EC2 IAM
- AutoScalingGroup

この3つに関しては、この記事で作成方法を紹介させて頂きます。

## IAMの作成を行う

必要なIAMを作成していきます。

### CodeDeploy用IAM

CodeDeployが利用するIAMです。

マネジメントコンソール → IAM → ロール → ロールの作成 より作成を行います。

CodeDeployのフルアクセス権限をアタッチしておきましょう。

また信頼関係の修正が必要です。
仮に東京リージョンで使用する場合は信頼関係を下記のように修正します。

```
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": [
          "codedeploy.ap-northeast-1.amazonaws.com"
        ]
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
```

詳しくは [公式ドキュメント](https://docs.aws.amazon.com/ja_jp/codedeploy/latest/userguide/getting-started-create-service-role.html#how-to-create-service-role-console) を参考にして下さい。

### CodeDeploy対応EC2用IAM

デプロイされる側のEC2に付与するIAMロールです。

`AmazonS3ReadOnlyAccess` をアタッチしておきましょう。

### builderサーバ用IAM

`AmazonS3FullAccess` , `AWSCodeDeployDeployerAccess` をアタッチしておきましょう。

この2つはデプロイ用のS3バケットにソースコードをアップロードする為に必要な設定です。

## EC2インスタンスの準備（デプロイ対象のアプリケーションサーバ）

デプロイ対象のEC2インスタンスを作成します。
先程作成した「CodeDeploy対応EC2用IAM」を設定するのを忘れないようにしましょう。

### AWS CodeDeploy エージェントをインストール

東京リージョンの場合は下記の手順でインストールすればOKです。

```
$ sudo yum update
$ sudo yum install ruby
$ sudo yum install aws-cli
$ cd /home/ec2-user
$ aws s3 cp s3://aws-codedeploy-ap-northeast-1/latest/install . --region ap-northeast-1
$ chmod +x ./install
$ sudo ./install auto
```

下記のコマンドで起動を確認出来れば問題ありません。

```
$ sudo service codedeploy-agent status
```

[公式ドキュメント](https://docs.aws.amazon.com/ja_jp/codedeploy/latest/userguide/codedeploy-agent-operations.html) を参考にしましょう。

### デプロイ対象のEC2インスタンスで利用するAMIイメージを作成する

EC2インスタンスの起動イメージであるAMIイメージを作成します。

作成したインスタンスを元にAMIイメージを作成するのが最も簡単です。

### AutoScalingGroupの作成を行う

マネジメントコンソール → EC2 → Auto Scaling グループ より作成を行います。

<img width="964" alt="CreateAutoScaling1.png" src="https://qiita-image-store.s3.amazonaws.com/0/71899/652b1b8c-ad3a-a128-2d48-989460d4374a.png">

「新しい起動設定を作成する」を選択します。

<img width="782" alt="CreateAutoScaling2.png" src="https://qiita-image-store.s3.amazonaws.com/0/71899/a21c9310-6f64-fc9f-904c-d47d030d2bec.png">

「マイ AMI」より先程作成したAMIイメージを選択します。

![CreateAutoScaling3.png](https://qiita-image-store.s3.amazonaws.com/0/71899/29fb80e6-d467-fcfa-ddad-bae6432260b6.png)

インスタンスタイプを選択します。

<img width="1175" alt="CreateAutoScaling4.png" src="https://qiita-image-store.s3.amazonaws.com/0/71899/ef6acd28-b063-e061-048c-d4b3380cef67.png">

起動設定の名前を入力します。私の場合はアプリケーション名を表す `account-api` と環境変数を表す `dev` を組み合わせて `dev-account-api` としました。

分かりやすい名前であれば何でも問題ありません。

IAMロールに先程作成した「CodeDeploy対応EC2用IAM」を指定するのを忘れないようにしましょう。

<img width="1179" alt="CreateAutoScaling5.png" src="https://qiita-image-store.s3.amazonaws.com/0/71899/1aa52500-f41b-e681-d15f-57c0113f2fe5.png">

この後の手順は省略しますが、セキュリティグループの設定を忘れずに行って下さい。
ここまででAutoScalingGroupで利用する起動設定が作成完了です。

ここからはAutoScalingGroup本体の作成に入っていきます。

起動設定は先程作成した `dev-account-api` を選択します。

グループ名は分かりやすい名前であれば何でも構いません。私は起動設定と同じ `dev-account-api` としました。

VPCとサブネットの選択を行います。

この設定もそれぞれの環境によると思いますが、少なくとも2つ以上のAZ（俗に言うMulti-AZ構成）になるように設定しておく事を推奨します。

![CreateAutoScaling9.png](https://qiita-image-store.s3.amazonaws.com/0/71899/564053ad-80a2-7325-08e7-4260b8eb3156.png)

この記事では残りの設定はデフォルトで設定しておきます。必要に応じてカスタマイズを行って下さい。

<img width="1174" alt="CreateAutoScaling10.png" src="https://qiita-image-store.s3.amazonaws.com/0/71899/2e47443d-e476-5964-bd18-4d5e1a80d70e.png">

<img width="1168" alt="CreateAutoScaling11.png" src="https://qiita-image-store.s3.amazonaws.com/0/71899/1dc18bfe-22ed-21cc-3ee0-b2a2c7ebbdf7.png">

以上でAutoScalingGroupの作成は完了になります。

## EC2インスタンスの準備（builderサーバ）

詳細な手順は省略しますが、先程作成した「builderサーバ用IAM」を忘れずに設定しましょう。

## デプロイ用のソースコードを格納するS3バケットを作成

詳細な手順は省略します。分かりやすい名前でデフォルト設定で非公開のS3バケットを作成します。

## デプロイ対象のアプリケーション側の準備

最低限必要な設定はデプロイ対象アプリケーションのプロジェクトルートに `appspec.yml` というファイルを設置する事です。

アプリケーション再起動やミドルウェアの再起動等、様々な設定が可能です。

参考まで下記にサンプルを載せておきます。

```yaml:appspec.yml
version: 0.0
os: linux
files:
  - source: /
    destination: /home/ec2-user/account-api
permissions:
  - object: /
    owner: ec2-user
    group: ec2-user
    mode: 777
    pattern: "**"
hooks:
  AfterInstall:
    - location: hooks/build.sh
      timeout: 1000
      runas: ec2-user
    - location: hooks/nginx-restart.sh
      timeout: 300
      runas: root
    - location: hooks/php-fpm-restart.sh
      timeout: 300
      runas: root
```

```bash:hooks/build.sh
#!/usr/bin/env bash

source /home/ec2-user/.bash_profile

cd /home/ec2-user/account-api

php artisan cache:clear
```

```bash:hooks/nginx-restart.sh
#!/usr/bin/env bash

service nginx restart
```


```bash:hooks/php-fpm-restart.sh
#!/usr/bin/env bash

service php-fpm restart
```

`appspec.yml` はそれなりに癖があるので、[公式ドキュメント](https://docs.aws.amazon.com/ja_jp/codedeploy/latest/userguide/reference-appspec-file.html) をよく見ておく事をオススメします。

またクラスメソッドさんの下記のブログも参考になるので載せておきます。

- [AWS CodeDeploy の AppSpec を読み解く](https://dev.classmethod.jp/cloud/aws/code-deploy-appspec/)
- [CodeDeployフックのベストプラクティス](https://dev.classmethod.jp/cloud/aws/best-practice-of-code-deploy-hooks/)

# デプロイ設定の作成

事前準備が終わりここからようやくデプロイ設定の作成に入ります。

## 最初はインプレースデプロイの設定を作成する

一番最初は稼働中の実行環境（Blue環境）が存在しない状態なのでまずは普通にインプレースデプロイ（普通にインスタンスにデプロイする）を行っていきます。

マネジメントコンソール → CodeDeploy よりアプリケーションの作成を行います。

<img width="955" alt="create-deploy-app1.png" src="https://qiita-image-store.s3.amazonaws.com/0/71899/5d280955-c387-4505-e538-03dc3c802e99.png">

アプリケーション名は分かりやすい名前であれば何でも構いません。（ここでは `account-api`）

デプロイグループ名を指定します。名前は分かりやすければ何でも構いません。
ここでは環境を表す `dev` を入力しました。

デプロイタイプは「インプレースデプロイ」を選択します。

![create-deploy-app2.png](https://qiita-image-store.s3.amazonaws.com/0/71899/b3639be5-1b68-4641-e78e-adca8042a19e.png)

環境設定でデプロイ先の指定を行います。

ここでは先程作成したAutoScalingGroupである「dev-account-api」を選択します。

![create-deploy-app3.png](https://qiita-image-store.s3.amazonaws.com/0/71899/bb0e8b3e-3a0b-3149-53d7-61c275d4a2e0.png)

サービスロールで先程作成した「CodeDeploy用IAM」のARNを選択します。

![create-deploy-app4.png](https://qiita-image-store.s3.amazonaws.com/0/71899/db141ac1-e6d1-6ae8-9085-d9ebd47d41d1.png)

以上でアプリケーション、及びデプロイグループの作成が完了です。

続いてデプロイの作成を行っていきます。

![create-deploy1.png](https://qiita-image-store.s3.amazonaws.com/0/71899/6758e580-f2aa-10df-41d9-562093e4c7a5.png)

![create-deploy2.png](https://qiita-image-store.s3.amazonaws.com/0/71899/48073515-ccaf-55ce-6702-abe473e2ff51.png)

アプリケーション、デプロイグループを選択、リビジョンの場所にアップロード済のソースコードの場所を選択します。

例えばS3バケット名が「dev-deploy-test-account-api」でソースコードのファイル名が「1518099701.zip」の場合は下記のようになります。

`s3://dev-deploy-test-account-api/1518099701.zip`

例えばbuilderサーバからプロジェクトルートで以下のようなスクリプトを実行する事で環境変数に対応したS3バケットにソースコードをアップロードする事が出来ます。

※ 開発環境なら `./deploy.sh dev` のように実行します。

```bash:deploy.sh
#!/usr/bin/env bash
if [ "$1" != 'qa' ] && [ "$1" != 'dev' ] && [ "$1" != 'stg' ] && [ "$1" != 'prd' ];
then
  echo "Invalid argument! Please set one of 'qa' or 'dev' or 'stg' or 'prd'."
  exit 1
fi

env=$1

unix_time=`date +%s`

aws deploy push \
--region ap-northeast-1 \
--application-name account-api \
--s3-location s3://${env}-deploy-test-account-api/${unix_time}.zip \
--source ./
```

![create-deploy3.png](https://qiita-image-store.s3.amazonaws.com/0/71899/497b555a-7f87-e42c-542c-c4473437eadc.png)

## 対象インスタンスに対してインプレースデプロイを行う

デプロイを実行します。
下記のように表示されればデプロイは成功しています。

![create-deploy4.png](https://qiita-image-store.s3.amazonaws.com/0/71899/2e7e93e8-804e-81f4-ef49-8951d38b3b6d.png)

マネジメントコンソール → EC2 → Auto Scaling グループ のターゲットグループに対象アプリケーションが利用しているターゲットグループを指定します。

![UpdateAutoScaling1.png](https://qiita-image-store.s3.amazonaws.com/0/71899/28e94f6c-d253-b2c9-e632-bcfddf20d466.png)

この操作を行った時点で先程デプロイしたインスタンスがロードバランサーのターゲットになっているハズです。

マネジメントコンソール → EC2 → ターゲットグループ から確認しておきましょう。
ついでにデプロイされたアプリケーションが正常動作するかも確認してみましょう。

![targetGroupCheck.png](https://qiita-image-store.s3.amazonaws.com/0/71899/8ee00f9d-0ec7-41e9-ece0-736d01709d5d.png)

ここまでの操作でBlue/Greenデプロイを行う為の準備が整いました。

## Blue/Greenデプロイの設定を作成する

ここからBlue/Greenデプロイの設定を作成していきます。

アプリケーション名は「account-api」を選択、デプロイグループ名は「dev-blue-green」とします。（分かりやすければ何でも問題ありません）

デプロイタイプは「Blue/Greenデプロイ」を選択します。

![create-blue-green-deploy1.png](https://qiita-image-store.s3.amazonaws.com/0/71899/55112793-b3b1-6ecc-c83c-6604d29a3569.png)

環境設定で「Auto Scaling グループの自動コピー」を選択し、先程作成したAuto Scaling グループを選択します。

![create-blue-green-deploy2.png](https://qiita-image-store.s3.amazonaws.com/0/71899/a5164688-6a5a-11c7-1055-37d45d5b6b54.png)

先程の手順でAuto Scaling グループにロードバランサーのターゲットグループを指定しているので対象アプリケーションのロードバランサーが自動的に選択されているハズです。

![create-blue-green-deploy3.png](https://qiita-image-store.s3.amazonaws.com/0/71899/2e92f7ff-600b-e6a8-bd2a-f416ba89e6ca.png)

サービスロールにはインプレースデプロイと同じように「CodeDeploy用IAM」のARNを選択します。

![create-blue-green-deploy4.png](https://qiita-image-store.s3.amazonaws.com/0/71899/aff9e5d4-4960-2f2f-b3a5-b806c2ce0475.png)

「デプロイグループの作成」をクリックして作成を完了させます。

## Blue/Greenデプロイを実施する

インプレースデプロイと同じ容量でデプロイを実行します。

デプロイグループの選択で先程作成した「dev-blue-green」を選択します。

後の手順はインプレースデプロイの時と同様なので省略させて頂きます。

![create-blue-green-deploy5.png](https://qiita-image-store.s3.amazonaws.com/0/71899/2738d7a9-5bbc-8778-747c-7b0f5668cf88.png)

デプロイを実行すると以下のような画面になります。

![do-blue-green-deploy1.png](https://qiita-image-store.s3.amazonaws.com/0/71899/757e8a16-9665-1905-f24d-825647f28d91.png)

しばらく時間が経つと以下のようにステップ3の下に「終了」ボタンが出現します。

![do-blue-green-deploy2.png](https://qiita-image-store.s3.amazonaws.com/0/71899/f5c1cde2-3964-1f67-acf6-e0ae31f3f191.png)

この時点でロードバランサーのターゲットグループが新しいインスタンス群に置き換わっている事を確認出来るハズです。

アプリケーションの動作確認を行って問題がなければ「終了」をクリックしましょう。

「終了」をクリックした時点で置き換え元の古いインスタンスは削除されます。

## Blue/Greenデプロイ実施後の確認

元になったAuto Scaling グループは削除されます。

代わりに `CodeDeploy_[デプロイ設定名]_[デプロイID]` で新たにAuto Scaling グループが作成されている事を確認出来ます。

次回以降はこの新しく出来たAuto Scaling グループを元にデプロイを実施するという流れになります。

![after-deploy1.png](https://qiita-image-store.s3.amazonaws.com/0/71899/336fc511-d627-8e42-9c7c-5bcbdad517cc.png)

ちなみにデプロイグループのターゲットになっているAuto Scaling グループも自動的に書き換わっている事確認出来ます。

![after-deploy2.png](https://qiita-image-store.s3.amazonaws.com/0/71899/0293ef53-bb4c-9b81-caaf-6265e705140a.png)

## 補足

この他にも Blue環境の終了待ち時間を選択したり、デプロイ時の細かい挙動を選択出来たりしますので、それぞれの要件に合わせてカスタマイズが可能です。

また今回はAuto Scaling グループを使いましたが、手動で実行対象の環境を作成する事も出来ます。

Auto Scaling グループは実質的に常にインスタンスが最低1台以上起動した状態になるので、開発環境やステージング環境等でインスタンスを完全停止したい場合には手動で実行対象のインスタンスを作成する事も有効です。（それか普通にインプレースデプロイを使うとか）

## まとめ

[AWS CodeDeploy](https://aws.amazon.com/jp/codedeploy/) は比較的簡単にBlue/Green デプロイの仕組みを構築する事が出来ます。

この仕組みを自分で作ろうと思うとそれなりに大変なので参考になれば幸いです。

## 参考記事
- [やってみて分かった AWS CodeDeploy の落とし穴](https://qiita.com/sugiyasu-qr/items/c44179a468ba4732c3d3)
- [AWS CodeDeployを使ってPHPアプリケーションをデプロイしてみた](https://qiita.com/kazumax55/items/38349431b8db107fd05d)
- [EC2デプロイのためにCodeDeployを導入する](https://qiita.com/yukofeb/items/e077fc8755416c904032)