Terraformで FargateのBlue/Greenデプロイ環境を構築する


#はじめに
本記事では、Terraform で ECS の Fargate の環境とBlue/Green デプロイのパイプライン
([CodeCommit + CodeBuild + CodeDeploy +CodePipeline](https://aws.amazon.com/jp/devops/))をお試しで構築する手順を記載しています。

#Terraform で構築する全体構成図
![00_fargate_blue_green_deploy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283246/82af5029-0a2f-66a4-9c35-324c568f4fe8.png)


#Terraform で構築するAWSリソースのBlackBeltの資料
[VPC](https://d1.awsstatic.com/webinars/jp/pdf/services/20201021_AWS-BlackBelt-VPC.pdf)
[ALB](https://d1.awsstatic.com/webinars/jp/pdf/services/20191029_AWS-Blackbelt_ELB.pdf)
[ECS](https://d1.awsstatic.com/webinars/jp/pdf/services/20190925_AWS-BlackBelt_AWSFargate.pdf)
[CodeCommit](https://d1.awsstatic.com/webinars/jp/pdf/services/20201020_BlackBelt_AWS_CodeCommit_AWS_CodeArtifact.pdf)
[CodeBuild](https://d1.awsstatic.com/webinars/jp/pdf/services/20201125_AWS_BlackBelt_AWS_CodeBuild.pdf)
[CodeDeploy](https://d1.awsstatic.com/webinars/jp/pdf/services/20210126_BlackBelt_CodeDeploy.pdf)
[CodePipeline](https://d1.awsstatic.com/webinars/jp/pdf/services/20201111_BlackBelt_AWS%20CodeStar_AWS_CodePipeline.pdf)
[CloudWatchEvents](https://d1.awsstatic.com/webinars/jp/pdf/services/20200122_BlackBelt_EventBridge.pdf)
[IAM](https://d1.awsstatic.com/webinars/jp/pdf/services/20190129_AWS-BlackBelt_IAM_Part1.pdf)

#Terraform のコードと構成

https://github.com/okubo-t/aws-tf-fargate-blue-green-deploy

```
$ tree aws-tf-fargate-blue-green-deploy
aws-tf-fargate-blue-green-deploy
├── modules
│   ├── ecs
│   │   ├── alb.tf
│   │   ├── alb_listener.tf
│   │   ├── alb_security_group.tf
│   │   ├── alb_target_group.tf
│   │   ├── buildspec.yml
│   │   ├── cloudwatch_event.tf
│   │   ├── codebuild.tf
│   │   ├── codecommit.tf
│   │   ├── codedeploy.tf
│   │   ├── codepipeline.tf
│   │   ├── ecr_docker_push.tf
│   │   ├── ecr_repository.tf
│   │   ├── ecs_cluster.tf
│   │   ├── ecs_security_group.tf
│   │   ├── ecs_service.tf
│   │   ├── ecs_task.tf
│   │   ├── ecs_task_role.tf
│   │   ├── ecs_taskdef.json
│   │   ├── files
│   │   │   ├── Dockerfile
│   │   │   └── index.html
│   │   ├── iam_policy
│   │   │   ├── cloudwatch_event_policy.json
│   │   │   ├── cloudwatch_event_trust_policy.json
│   │   │   ├── codebuild_policy.json
│   │   │   ├── codebuild_trust_policy.json
│   │   │   ├── codedeploy_trust_policy.json
│   │   │   ├── codepipeline_policy.json
│   │   │   ├── codepipeline_trust_policy.json
│   │   │   ├── ecs_task_policy.json
│   │   │   └── ecs_task_trust_policy.json
│   │   ├── iam_role.tf
│   │   ├── outputs.tf
│   │   └── variables.tf
│   └── network
│       ├── internet_gateway.tf
│       ├── outputs.tf
│       ├── route_table.tf
│       ├── subnet.tf
│       ├── variables.tf
│       └── vpc.tf
├── main.tf
├── provider.tf
├── outputs.tf
├── variables.tf
├── sample-app
│   ├── Dockerfile
│   ├── appspec.yaml
│   ├── index.html
│   └── taskdef.json
└── terraform.tfvars-
```

#Terraform で構築する各リソース名
|リソース|名前(デフォルト)|
|:---|:---|
|VPC 名|prefix-dev-vpc|
|ALB 名|prefix-dev-sample-app-alb|
|ALB セキュリティグループ名|prefix-dev-sample-app-alb-sg|
|ECS クラスタ名|prefix-dev-cluster|
|ECR リポジトリ名|prefix-sample-app|
|タスク定義名|prefix-dev-sample-app-task|
|コンテナ名|prefix-sample-app|
|サービス名|prefix-dev-sample-app-svc|
|サービス セキュリティグループ名|prefix-dev-sample-app-svc-sg|
|CodePipeline 名|prefix-dev-sample-app-cp|
|CloudWatch Events名|prefix-dev-sample-app-cp-event|
|CodeCommit リポジトリ名|prefix-sample-app|
|CodeBuild プロジェクト名|prefix-dev-sample-app-cb|
|CodeDeploy アプリケーション名|prefix-dev-sample-app|
|CodeDeoloy デプロイグループ名|dev| 

#モジュールに渡すパラメータ

```main.tf
###################
# Network SAMPLE
###################
module "network" {
  source = "./modules/network"

  vpc = {
    name = "${var.prefix}-${var.env}-vpc"
    cidr = "10.0.0.0/16"
  }

  igw_name = "${var.prefix}-${var.env}-igw"

  public_subnet_a = {
    name = "${var.prefix}-${var.env}-public-a"
    cidr = "10.0.1.0/24"
  }

  public_subnet_c = {
    name = "${var.prefix}-${var.env}-public-c"
    cidr = "10.0.2.0/24"
  }

  private_subnet_a = {
    name = "${var.prefix}-${var.env}-private-a"
    cidr = "10.0.10.0/24"
  }

  private_subnet_c = {
    name = "${var.prefix}-${var.env}-private-c"
    cidr = "10.0.20.0/24"
  }

}

############################################
# ECS on Fargate Blue/Green Deploy SAMPLE
############################################
module "ecs" {
  source = "./modules/ecs"

  ## AWS Account ID
  aws_account_id = data.aws_caller_identity.current.account_id

  ## Region
  aws_region = var.aws_region

  ## Network
  vpc_id = module.network.vpc_id
  subnet_ids = [
    module.network.public_subnet_a_id,
    module.network.public_subnet_c_id,
  ]
  my_remote_ip = var.my_remote_ip

  ## ALB
  alb_name = "${var.prefix}-${var.env}-sample-app-alb"

  ## ECS Cluster
  cluster_name = "${var.prefix}-${var.env}-cluster"

  ## ECR
  ecr_name = "${var.prefix}-sample-app"

  ## ECS Task
  task_definition = {
    name           = "${var.prefix}-${var.env}-sample-app-task"
    container_name = "${var.prefix}-sample-app"
    cwlogs_name    = "/aws/ecs/${var.prefix}-sample-app"
    cwlogs_prefix  = var.env

  }

  ## ECS Service
  service_name  = "${var.prefix}-${var.env}-sample-app-svc"
  desired_count = 1

  ## CodePipeline
  codepipeline_name = "${var.prefix}-${var.env}-sample-app-cp"

  ## Stage > Source > CodeCommit 
  repository_name        = "${var.prefix}-sample-app"
  repository_description = "Sample App"

  ## Stage > Build > CodeBuild
  build_project_name = "${var.prefix}-${var.env}-sample-app-cb"

  ## Stage > Deploy > CodeDeploy
  codedeploy_app_name              = "${var.prefix}-sample-app"
  codedeploy_deployment_group_name = var.env

}
```
#事前準備
・Docker Desktop のインストール
・Terraform のインストール
・tfenv のインストール
・[Terraform を実行するための IAM のアクセスキーとシークレットキー](https://aws.amazon.com/jp/premiumsupport/knowledge-center/create-access-key/)
・[CodeCommit への HTTPS 接続用の Git 認証情報](https://docs.aws.amazon.com/ja_jp/codecommit/latest/userguide/setting-up-gc.html#setting-up-gc-iam)
・AWS CLI のインストール

#Terraform の動作確認環境

```
$ terraform -version
Terraform v1.0.4
on darwin_amd64
+ provider registry.terraform.io/hashicorp/aws v3.59.0
+ provider registry.terraform.io/hashicorp/null v3.1.0
+ provider registry.terraform.io/hashicorp/template v2.2.0

Your version of Terraform is out of date! The latest version
is 1.0.7. You can update by downloading from https://www.terraform.io/downloads.html
```

#Terrraform で環境を構築する手順

本手順は、Mac環境で実施することを想定しています。
Terraformのコードをダウンロードします。

```
$ git clone https://github.com/okubo-t/aws-tf-fargate-blue-green-deploy.git
```

Terraform のコードがあるディレクトへ移動します。

```
$ cd aws-tf-fargate-blue-green-deploy/
```

terraform.tfvars ファイルを作成します。

```
$ cp -p terraform.tfvars- terraform.tfvars
```

作成した terraform.tfvars 内の各パラメータを環境に応じて、任意で変更します。

```terraform.tfvars
# リージョン
aws_region = "ap-northeast-1"

# リソース名のプレフィックス
prefix = "prefix"

# リソースの環境
env = "dev"

# ALBに HTTPアクセスする時の送信元 IPアドレス
my_remote_ip = "0.0.0.0/0"
```

Docker Desktop が起動していることを確認します。

```
$ docker info
```

AWS CLIがインストールされていることを確認します。（AWS CLIの動作確認済み環境）

```
$ aws --version
aws-cli/1.19.92 Python/3.7.4 Darwin/18.7.0 botocore/1.20.92
```

AWS 認証情報を設定します。

```
$ export AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXXXX     # アクセスキー
$ export AWS_SECRET_ACCESS_KEY=XXXXXXXXXXXXXXX  # シークレットアクセスキー
$ export AWS_DEFAULT_REGION=ap-northeast-1      # デフォルトリージョン 
```

下記コマンドで、Terraform の初期設定をします。

```
$ terraform init
```

下記コマンドで、環境のデプロイを実行します。

```
$ terraform apply
```

デプロイ後に出力される Outputsの内容をメモします。(下記の値は例です。)

```
Apply complete! Resources: 53 added, 0 changed, 0 destroyed.

Outputs:

codecommit_repository = "https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/prefix-sample-app"
prod_url = "http://prefix-dev-sample-app-alb-1234567890.ap-northeast-1.elb.amazonaws.com"
test_url = "http://prefix-dev-sample-app-alb-1234567890.ap-northeast-1.elb.amazonaws.com:10080"
```

これで、Terraformによる環境の構築は完了です。

#Blue/Green デプロイパイプラインを実行する

**ALBの本番用のリスナー**に、curl コマンドを実行して、「Hello World v1」 と表示されることを確認します。（メモしたOutputsの prod_url の値を参照します。）

```
$ curl http://prefix-dev-sample-app-alb-1234567890.ap-northeast-1.elb.amazonaws.com
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>Hello</title>
    </head>
    <body>
        <h1>Hello World v1</h1>
    </body>
</html>
```

下記コマンドを実行して、CodeCommit のリポジトリをクローンします。（メモしたOutputsの codecommit_repository の値を参照します。）

```
$ git clone https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/prefix-sample-app
```

Terraform のコード内のディレクトリ（sample-app）の各ファイルをクローンしたリポジトリにコピーします。

```
$ cp -p ./sample-app/* [クローンしたリポジトリ名]
```

クローンしたリポジトリに移動します。

```
$ cd [クローンしたリポジトリ名]
$ ls
Dockerfile	appspec.yaml	index.html	taskdef.json
$ cat index.html 
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>Hello</title>
    </head>
    <body>
        <h1>Hello World v2</h1>
    </body>
</html>

```

「Hello World v2」と表示されるコードを、リポジトリにプッシュします。

```
$ git add .
$ git commit -m "Hello World v2"
$ git push
```

CodePipeline の管理コンソールから **Blue/Green** デプロイパイプラインが実行されていることを確認し、Deploy の**詳細**をクリックします。
![01_fargate_blue_green_deploy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283246/6b150999-bfab-0179-a01a-98df92c6d324.png)

CodeDeploy の管理コンソールからデプロイのステータスを確認します。
![02_fargate_blue_green_deploy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283246/51fed74f-7147-b730-1a29-ac2ff6214a2e.png)

[デプロイのステータス]が、ステップ2 の状態で、**ALBのテスト用のリスナー** に対して、curl コマンドを実行して、「Hello World v2」 と表示されることを確認します。（メモしたOutputsの test_url の値を参照します。）

```
$ curl http://prefix-dev-sample-app-alb-1234567890.ap-northeast-1.elb.amazonaws.com:10080
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>Hello</title>
    </head>
    <body>
        <h1>Hello World v2</h1>
    </body>
</html>
```

[トラフィックの再ルーティング]をクリックして、**ALBの本番用のリスナー**にトラフィックを切り替えます。

![03_fargate_blue_green_deploy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283246/4630115f-190e-85da-02e5-4186c476024f.png)

デプロイのステータスの各ステップが、成功することを確認します。
![04_fargate_blue_green_deploy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283246/7a595ee1-6c37-f28d-e77a-3943210461e0.png)

CodePipeline の管理コンソールからデプロイパイプラインが成功していることを確認します。
![05_fargate_blue_green_deploy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283246/04386a89-d683-f37c-c86f-8f37eb9f6212.png)


**ALBの本番用のリスナー**に、curl コマンドを実行して、「Hello World v2」 と切り替われば、Blue/Green デプロイ環境の構築は完了です。（メモしたOutputsの prod_url の値を参照します。）

```
$ curl http://prefix-dev-sample-app-alb-1234567890.ap-northeast-1.elb.amazonaws.com
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>Hello</title>
    </head>
    <body>
        <h1>Hello World v2</h1>
    </body>
</html>
```

詳細は、管理コンソール上から各リソースの設定を確認して下さい。

#後片付け
下記コマンドで、Terraform で作成したAWS環境を削除します。

```
$ terraform destroy
```
#さいごに
少しでも初学者のFargate の環境とBlue/Green デプロイパイプラインの学習に役立てれば幸いです。

#参考サイト
[ECS Fargateデプロイ用CodePipelineのtaskdef.jsonに変数を持たせる方法](https://qiita.com/neruneruo/items/a5313beb3074a1af4df2)