ECS Blue/GreenデプロイCI/CDパイプライン構築

## 内容

ECS FargateのBlue/Greenデプロイに関して
AWS Codeサービス（CodeCommit・CodeBuild・CodeDeploy・CodePipeline）によるCI/CDパイプライン構築を行ったのでそのアウトプットとしてまとめていきたいと思います。

CodeサービスやECSについての基礎についてもまとめてみたので、合わせてご確認下さい。
[AWS Codeサービスを利用したCI/CDパイプラインの仕組み](https://qiita.com/tarian/items/1d0882de6a02714a2ff1)
[AmazonECSの構成まとめ](https://qiita.com/tarian/items/8e9e77cb005df1ad9298)

### 事前に作成済の想定

- VPC・サブネット・RDSなどの基本的なネットワークは作成済
- ECSクラスター・タスク・タスク定義を作成済
- Dockerfile, docker-compose.ymlなどのファイルは作成済
- ACMで発行したSSL証明書をALB関連付け済

## 目次
- [パイプラインの構成](#パイプラインの構成)
- [Blue/Greenデプロイとは](#bluegreenデプロイとは)
- [■ ビルド定義・デプロイ定義・タスク定義ファイルの作成](#-ビルド定義デプロイ定義タスク定義ファイルの作成)
- [■ ALBの作成](#-albの作成)
- [■ Blue/Greenデプロイ用のECSサービス作成](#-bluegreenデプロイ用のecsサービス作成)
- [■ CodePipeline作成](#-codepipeline作成)
- [■ CodePipeline作成後にやること](#-codepipeline作成後にやること)
- [パイプラインの実行](#パイプラインの実行)
- [最後に](#最後に)

## パイプラインの構成

今回は以下のようなパイプラインの構成で進めて行きたいと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/692472/ae762fa6-8c57-bcd4-f2e1-47141adbfc14.png)

## Blue/Greenデプロイとは

古い環境（Blue環境）と新しい環境（Green環境）の両方の環境を用意し、サービスのアップデート時にはGreen環境に変更を反映し、動作確認後ロードバランサー等によるルーティングの制御によってトラフィックを切り替え、ダウンタイムなしで環境を切り替えるデプロイ方式。

- Blue環境に既存のアプリケーションが存在し、ロードバランサーがBlueにトラフィックを流している環境に対して、CodeDeployを用いてGreen環境に新しくデプロイを行う。
テストリスナーポートへのアクセスにより、動作確認が可能。
![スクリーンショット 2023-04-11 10.18.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/692472/40469d76-53d5-f1ec-9e0c-60e5ec25c3a8.png)


- Green環境へデプロイ完了後、CodeDeployがロードバランサーを操作してトラフィックがBlue環境を向くように変更
![スクリーンショット 2023-04-11 10.21.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/692472/1b6708d5-9b3c-a07d-e5ad-a0f8f7d2ae05.png)

- ルーティングの変更後、Blue環境はロールバックのために一定期間存在し、一定期間経過後削除される
![スクリーンショット 2023-04-11 10.25.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/692472/6abfa7c4-10de-d09a-ff62-8357c5ce6778.png)

## ■ ビルド定義・デプロイ定義・タスク定義ファイルの作成

以下の３つのファイルを作成する
- ビルド定義ファイル（buildspec.yml）
- デプロイ定義ファイル（appspec.yml）
- タスク定義ファイル（taskdef.json）

### ビルド定義ファイル（buildspec.yml）の作成

詳細の書き方は公式ドキュメントなどをご参照ください。

#### 基本的な処理
- ECRへログイン
- Dockerイメージをビルド
- ECRにイメージをプッシュ

を行います。

#### imageDetail.jsonの作成

Blue/GreenデプロイのCI/CDパイプラインにおいて、ビルド定義ファイルで重要なのが
`imageDetail.json`ファイルの作成と出力です。

imageDetail.jsonに関しての[公式リファレンス](https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/file-reference.html#:~:text=%E3%83%81%E3%83%A5%E3%83%BC%E3%83%88%E3%83%AA%E3%82%A2%E3%83%AB%3A%20%E3%80%8C%E3%80%8D%20CodePipeline%E3%80%82-,Amazon%20ECS%20Blue/Green%20%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%82%A2%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E7%94%A8%E3%81%AE%20imageDetail.json%20%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB,-imageDetail.json%20%E3%83%89%E3%82%AD%E3%83%A5%E3%83%A1%E3%83%B3%E3%83%88)にあるように、以下のJSONを作成します。

```json
{
    "ImageURI": "ACCOUNTID.dkr.ecr.us-west-2.amazonaws.com/dk-image-repo@sha256:example3"
}
```

#### 出力アーティファクトの設定

buildspec.ymlでは、artifactsの項目に出力するアーティファクトを設定します。
ここでは、`appspec.yml`・`taskdef.json`・`imageDetail.json`の出力を設定します。

```yml
artifacts:
  files:
    - appspec.yml
    - taskdef.json
    - imageDetail.json
```

### デプロイ定義ファイル（appspec.yml）の作成

プロジェクトのルート直下に`appspec.yml`を作成します。
ディレクトリを変更（codedeploy/ディレクトリ配下に配置）したかったのですが、うまくいかずルート直下に配置しています。
何かやり方あれば是非コメント下さい！

appspec.ymlの内容は、細かく設定するのであればリファレンスを参照いただければと思います。
基本的には下記の内容となります。

```yml
version: 0.0
Resources:
  - TargetService:
      Type: AWS::ECS::Service
      Properties:
        TaskDefinition: "<TASK_DEFINITION>"
        LoadBalancerInfo:
          ContainerName: "[通信を受け付けるコンテナ名]"
          ContainerPort: "80"
```

デプロイの際にタスク定義を参照するのですが、参照するタスク定義は最新のものである必要があります。
（理由：タスク定義にはECRの最新のイメージURI、つまり最新のソースコードが反映されたDockerイメージが設定される。したがって、更新されたECRリポジトリURIを設定したタスク定義を使用する必要がある。）

`TaskDefinition: "<TASK_DEFINITION>"`の記述によって、後述するタスク定義ファイル（taskdef.json）が動的に使われるようになります。

### タスク定義ファイル（taskdef.json）の作成

タスク定義ファイルは、既に手動で作成したタスク定義から作成します。

- ECS > 対象のクラスター > 「タスク」タブ > 「タスク定義」のリンク > 「JSON」タブ > 「JSONのダウンロード」
※注意）この作業は、必ず「新しいECSエクスペリエンス」をオンにして、新デザインのAWSコンソールから作業してください。古いコンソール画面ではJSON形式が全く異なり、私はここでハマりました。
- ダウンロードしたJSONファイルを、`taskdef.json`という名前でプロジェクトのルート直下に配置
- imageの項目を下記のように修正する
```json
"image": "<IMAGE_NAME>"
```

タスク定義に最新のECRリポジトリのDockerイメージURIを記述する必要がありますが、ここでは動的に設定するためにプレイスホルダーを使います。

このプレースホルダーの文字列は、CodePipelineのデプロイステージで設定します（CodePipeline作成のデプロイステージで後述）。

このプレースホルダーに何が入ってくるかというと、上述した`buildspec.yml`の出力アーティファクトとして設定した
```json
{
    "ImageURI": "ACCOUNTID.dkr.ecr.us-west-2.amazonaws.com/dk-image-repo@sha256:example3"
}
```
この値が入ってくるということになります。

- tagsの項目の1行を削除

```json
    ・
    ・
    "tags": [] # この１行を削除
}
```

### ３ファイル作成完了

以上が３つの定義ファイルの作成となります。
最新のECRリポジトリURIを参照するための流れがわかりづらくなるので、以下にもう一度まとめます。

- `appspec.yml`で、最新のタスク定義を参照する記述を行う
- この最新のタスク定義は`taskdef.json`が使われ、その中に書かれているECRリポジトリのURIはプレースホルダーで動的に最新のものが参照されるようになっている。
- `taskdef.json`のプレースホルダーには、`buildspec.yml`で出力した`imageDetail.json`の値が入ってくる。

この流れを掴むのが最も大事です。
ここは時間をかけて理解することをおすすめします。

## ■ ALBの作成

インターネットからのアクセスをトラフィックするロードバランサー（ALB）を作成

- EC2 > ロードバランサー > 新規作成 > ALB

### ロードバランサーの設定

| 項目 | 値 | 備考 |
| --- | --- | --- |
| 名前 | sample-blue-green-alb |  |
| スキーム | インターネット向け |  |
| リスナー | 80を追加 | ECSサービスで作成するので、後ほど削除 |
| AZ, サブネット | public-1a,public-1c | 自身の環境に合わせてください |

### セキュリティ設定の構成

| 項目 |
| --- |
| ここでは何も設定せずに次の手順へ |

### セキュリティグループの設定

| 項目 | 値 | 備考 |
| --- | --- | --- |
| 既存 | sample-alb-sg | 8080, 4403ポート許可を追加 |

### ルーティングの設定

| 項目 | 値 | 備考 |
| --- | --- | --- |
| ターゲットグループ | sample-blue-green-dummy-tg | ECSサービスで作成するので、後ほど削除 |

### ターゲットの登録

| 項目 |
| --- |
| ここでは何も設定せずに次の手順へ |

- すべて入力できたら確認して「作成」

### リスナーとターゲットグループの削除

下記は後ほどECSサービス作成時に作成されるので、ここでは一度削除する

- ロードバランサー > 先ほど作成したALB（sample-blue-green-alb）> リスナータブ > 作成したリスナーを削除
- ターゲットグループ > 先程作成したターゲットグループ（sample-blue-green-dummy-tg）を削除

## ■ Blue/Greenデプロイ用のECSサービス作成

### 事前作業）CodeDeploy用のIAMロール作成

CodeDeployがECSにデプロイするために、ECSとS3へのアクセス権限を付与する必要がある

- IAM > ロール > ロールを作成

| 項目 | 値 | 備考 |
| --- | --- | --- |
| 信頼されたエンティティタイプ | AWSのサービス |  |
| ユースケース | CodeDeploy > CodeDeploy-ECS |  |
| 許可ポリシー | AWSCodeDeployRoleForECS | S3への権限も含まれる。 |
| ロール名 | CodeDeployRoleForECSBlueGreen |  |

### サービスの設定

- ECS > クラスター > sample-cluster > サービスタブ > 「作成」ボタン

| 項目 | 値 |
| --- | --- |
| 起動タイプ | FARGATE |
| オペレーティングシステム | Linux |
| タスク定義 | sample-task-definition |
| リビジョン | latest |
| クラスター | sample-cluster |
| サービス名 | sample-blue-green-service |
| タスクの数 | 1 |

### デプロイメント

| 項目 | 値 |
| --- | --- |
| デプロイメントタイプ | Blue/Greenデプロイメント |
| デプロイメント設定 | CodeDeployDefault.ECSAllAtOnce |
| CodeDeployのサービスロール | CodeDeployRoleForECSBlueGreen（上述のとおり事前にIAMロールを作成） |

### VPCとセキュリティグループ

| 項目 | 値 |  |
| --- | --- | --- |
| クラスターVPC | sample-vpc |  |
| サブネット | sample-subnet-1a, 1c |  |
| セキュリティグループ | sample-web-sg | HTTP:80(プロダクション), 8080(テスト) を許可 |
| パブリックIP | ENABLED | DISABLEDでも良さそうな気はする |

### ロードバランシング

| 項目 | 値 |
| --- | --- |
| 種類 | ALB |
| ロードバランサー名 | sample-blue-green-alb（上記で作成したALB） |
| ロードバランス用のコンテナ | web-container:80:80 |

**ロードバランス用のコンテナ**

| 項目 | 値 |
| --- | --- |
| プロダクションリスナーポート | HTTP: 80 |
| テストリスナーポート | HTTP: 8080 |
| ターゲットグループ１ | HTTP sample-blue-green-tg-1 |
| ターゲットグループ２ | HTTP sample-blue-green-tg-2 |
| ヘルスチェックパス | / （１も２も。自身の環境で適宜変更） |

### Auto Scaling

| 項目 | 値 | 備考 |
| --- | --- | --- |
| Service Auto Scaling | しない | 適宜設定 |

### 作成

- サービス作成すると以下が自動作成される
    - ALBのリスナー（80と8080で作成しておいて、ALBのリスナ設定画面で443と4403に変更するようにする）
    - TG
    - CodeDeployのアプリケーション、デプロイグループ

#### 以下のエラーで詰まったので、注意

- sample-web-sgにテスト用ポートのインバウンドルールが設定されていなかった
- ALB作成後の手順のリスナとターゲットグループの削除を行っていなかった

### ALBのリスナの設定

- 自動作成された `HTTP:80 ⇒ tg1`, `HTTP:8080 ⇒ tg2` を削除
- リスナの追加で `HTTP:80 ⇒ HTTPS:443へリダイレクト`, `HTTP:8080 ⇒ HTTPS:4403へリダイレクト` , `HTTPS:443 ⇒ tg1`, `HTTPS:4403 ⇒ tg2` を追加する

## ■ CodePipeline作成

- CodePipeline > パイプラインを作成する

### パイプラインの設定を選択

- パイプライン名：SampleBlueGreenPipeline
- その他はデフォルトのまま

| 項目 | 値 |
| --- | --- |
| パイプライン名 | SampleBlueGreenPipeline |
| サービスロール | 新しいサービスロール |
| ロール名 | 自動作成 |
| その他 | デフォルトのまま |

### ソースステージを追加

| 項目 | 値 |
| --- | --- |
| ソースプロバイダー | AWS CodeCommit |
| リポジトリ名 | sample |
| ブランチ名 | main |
| 検出オプション | CloudWatch Events |
| 出力アーティファクト形式 | CodePipelineのデフォルト |

### ビルドステージを追加

※ 事前にビルドプロジェクトを作成しておいてください。本記事では割愛。

| 項目 | 値 |
| --- | --- |
| プロバイダー | AWS CodeBuild |
| リージョン | アジアパシフィック (東京) |
| プロジェクト名 | sample-build-project |
| ビルドタイプ | 単一ビルド |

### デプロイステージを追加

| 項目 | 値 |
| --- | --- |
| プロバイダー | Amazon ECS(ブルー/グリーン) |
| リージョン | アジアパシフィック (東京) |
| CodeDeploy アプリケーション名 | ECS サービスを作成すると自動作成されるアプリケーションを選択 |
| CodeDeploy デプロイグループ | ECS サービスを作成すると自動作成されるアプリケーションを選択 |
| ECS タスク定義 | BuildArtifact |
| CodeDeploy AppSpec ファイル | BuildArtifact |
| 入力アーティファクトを持つイメージの詳細 | BuildArtifact |
| タスク定義のプレースホルダー文字 | IMAGE_NAME |

- ここまで作成できれば完了

## ■ CodePipeline作成後にやること

### CodeDeploy デプロイグループの編集

Blue環境からGreen環境にトラフィックを切り替えるタイミングを設定する必要がある

- CodeDeploy > アプリケーション > デプロイグループ > 編集
- デプロイ設定の項目で、トラフィックを再ルーティングするタイミングを指定するにチェック
    - 新環境にトラフィックを切り替えるまでの時間を設定する（とりあえず1時間）
- 元のリビジョンの終了
    - トラフィックがGreen環境に切り替わったあとで、Blue環境を保持する時間（とりあえず1時間）
- なぜか設定必要）LoadBalancer
    
    
    | 項目 | 値 | 備考 |
    | --- | --- | --- |
    | ロードバランサー | sample-blue-green-alb |  |
    | 本番稼働リスナーポート | HTTPS: 443 | HTTPSの場合この設定でいけました。適宜変更。 |
    | テストリスナーポート | HTTPS: 4403 | HTTPSの場合この設定でいけました。適宜変更。 |
    | ターゲットグループ１ | sample-blue-green-tg-1 |  |
    | ターゲットグループ２ | sample-blue-green-tg-2 |  |

## パイプラインの実行

- CodePipelineのソースステージで設定したブランチに変更が加わると、CodePipelineが走り出します。
デプロイステージで止まるので、詳細ボタンからCodeDeployの画面へ遷移
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/692472/6d4d498b-7f45-6e20-9646-ddbba06815fa.png)


- ステップ３が進行中の状態となれば、テスト用リスナーポートで新しい環境へブラウザからアクセスできるようになります。(https://example.com:4403)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/692472/f76cb847-1dd3-312e-37b0-b316c0055403.png)


- 動作確認後、「トラフィックの再ルーティング」ボタンを押すことで、通信がGreen環境に流れるようになる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/692472/1b70d4d0-f588-e57d-9309-a4db16f9ab89.png)


- 問題なければ「元のタスクセットの終了」ボタンを押すことでBlue環境が削除される
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/692472/18d94b3d-9338-1584-698c-acc99c1869b8.png)


- すべて完了となれば終了
![スクリーンショット 2023-04-07 16.49.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/692472/30ecd1e2-6b76-8378-7386-bd6e3f6788ab.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/692472/9e552021-f531-0d32-485d-d607c9d4eed8.png)




## 最後に

ECS FargateのBlue/Greenデプロイと、Codeサービスを利用したCI/CDパイプラインの構築をまとめました。

また今回は、１タスクに１つのコンテナを起動する想定での設定をまとめましたが、実サービスでは１タスクに複数コンテナを稼働させることがあると思います。
そういった環境下でのパイプライン構築時に、いくつかエラーに遭遇しました。
１タスクに複数コンテナを稼働させる場合の設定や、エラーの内容とエラー解決については別記事にまとめたいと思います。

- [CodePipeline『Exception while trying to read the task definition artifact file from: SourceArtifact』エラーの原因と対処法](https://qiita.com/tarian/items/aa6a5f0f960fc7c24a82)
- [【ECS Blue/Greenデプロイ】１タスクで複数コンテナ稼働している場合のCI/CDパイプライン構築](https://qiita.com/tarian/items/5043abe44345d448e7dc)

ここまで読んでいただき、ありがとうございました。