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

## 概要

* TerraformでECS Fargateの環境を構築
* Codeシリーズを利用してBlue/Greenデプロイをできるようにする。
* Blue/Green時に必要な部分だけ記載

Fargate + CodePipelineのソースコード
https://github.com/saku3/aws-terraform

## ALB
* Fargate用のALBを構築
* TargetGroupをblueとgreen二つ用意する
* リスナールールでポート443のルールとテスト用ポート10443のルールを用意する。
* ALBは443と10443両方からアクセスできるようにする。(テスト用の10443ポートは適宜制限する)

<details><summary>ソースコード</summary>

```terraform:alb
resource "aws_lb" "lb" {
  name               = "${var.project}-${var.env}-alb"
  internal           = false
  load_balancer_type = "application"

  security_groups = [
    "${aws_security_group.alb_sg.id}",
  ]

  subnets = [
    aws_subnet.public_subnet_1.id,
    aws_subnet.public_subnet_2.id,
  ]

  access_logs {
    enabled = true
    bucket  = var.lb_logs_bucket.id
  }
}

resource "aws_lb_listener" "http" {
  load_balancer_arn = aws_lb.lb.arn
  port              = "80"
  protocol          = "HTTP"

  default_action {
    type = "redirect"

    redirect {
      host        = "#{host}"
      path        = "/#{path}"
      port        = "443"
      protocol    = "HTTPS"
      query       = "#{query}"
      status_code = "HTTP_301"
    }
  }
}

resource "aws_lb_listener" "https" {
  load_balancer_arn = aws_lb.lb.arn
  port              = "443"
  protocol          = "HTTPS"

  default_action {
    type = "fixed-response"

    fixed_response {
      content_type = "text/plain"
      status_code  = "403"
    }
  }

  certificate_arn = aws_acm_certificate.cert.arn
}

resource "aws_lb_listener" "test_https" {
  load_balancer_arn = aws_lb.lb.arn
  port              = "10443"
  protocol          = "HTTPS"

  default_action {
    type = "fixed-response"

    fixed_response {
      content_type = "text/plain"
      status_code  = "403"
    }
  }

  certificate_arn = aws_acm_certificate.cert.arn
}

resource "aws_lb_listener_rule" "routing" {
  listener_arn = aws_lb_listener.https.arn
  priority     = 100

  action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.tg_blue.arn
  }

  condition {
    host_header {
      values = ["${var.domain}"]
    }
  }
  lifecycle {
    ignore_changes = [action]
  }
}

resource "aws_lb_listener_rule" "test_routing" {
  listener_arn = aws_lb_listener.test_https.arn
  priority     = 100

  action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.tg_green.arn
  }

  condition {
    host_header {
      values = ["${var.domain}"]
    }
  }
  lifecycle {
    ignore_changes = [action]
  }
}

resource "aws_lb_target_group" "tg_blue" {
  name        = "${var.project}-${var.env}-blue-tg"
  port        = 80
  protocol    = "HTTP"
  target_type = "ip"
  vpc_id      = aws_vpc.vpc.id

  health_check {
    interval            = 60
    path                = "/"
    port                = "traffic-port"
    protocol            = "HTTP"
    timeout             = 10
    healthy_threshold   = 3
    unhealthy_threshold = 3
  }
}


resource "aws_lb_target_group" "tg_green" {
  name        = "${var.project}-${var.env}-green-tg"
  port        = 80
  protocol    = "HTTP"
  target_type = "ip"
  vpc_id      = aws_vpc.vpc.id

  health_check {
    interval            = 60
    path                = "/"
    port                = "traffic-port"
    protocol            = "HTTP"
    timeout             = 10
    healthy_threshold   = 3
    unhealthy_threshold = 3
  }
}
```
</details>


## Fargate
* load_balancerのtarget_group_arnはblueのターゲットグループと紐づけておく
* aws_ecs_serviceでdeployment_controllerのtypeで「CODEDEPLOY」を指定する
* 既に作成済みのaws_ecs_serviceでdeployment_controllerを変更する場合は再作成が必要になる

<details><summary>ソースコード</summary>

```terraform:fargate
resource "aws_ecs_service" "main" {
  name                              = "${var.project}-${var.env}-${var.app_name}"
  cluster                           = var.cluster.id
  task_definition                   = aws_ecs_task_definition.main.arn
  platform_version                  = "1.4.0"
  desired_count                     = var.desired_count
  launch_type                       = "FARGATE"
  health_check_grace_period_seconds = 200
  enable_execute_command            = true

  network_configuration {
    subnets          = [var.private_subnet_1_id, var.private_subnet_2_id]
    security_groups  = [var.ecs_sg_id]
    assign_public_ip = "false"
  }

  load_balancer {
    target_group_arn = var.target_group_blue_arn
    container_name   = "${var.project}-${var.env}-${var.app_name}"
    container_port   = var.container_port
  }

  deployment_controller {
    type = "CODE_DEPLOY"
  }

  tags = {
    Name = "${var.project}-${var.env}-${var.app_name}"
  }

  lifecycle {
    ignore_changes = [
      desired_count,
      task_definition,
      load_balancer,
    ]
  }

  depends_on = [var.alb]
}
```

</details>


## CodeDeploy
* アプリケーションはECSを指定する
* デプロイグループでload_balancer_infoで本稼働リスナー、テストリスナーを指定
    * 本稼働リスナー : prod_traffic_route
    * テストリスナー : test_traffic_route

<details><summary>ソースコード</summary>

```terraform:codedeploy
resource "aws_codedeploy_deployment_group" "main" {
  app_name               = aws_codedeploy_app.main.name
  deployment_config_name = "CodeDeployDefault.ECSAllAtOnce"
  deployment_group_name  = "${var.project}-${var.env}-${var.app_name}-deploy-group"
  service_role_arn       = aws_iam_role.deploy_role.arn

  auto_rollback_configuration {
    enabled = true
    events  = ["DEPLOYMENT_FAILURE"]
  }

  blue_green_deployment_config {
    deployment_ready_option {
      action_on_timeout    = "STOP_DEPLOYMENT"
      wait_time_in_minutes = 60
    }

    terminate_blue_instances_on_deployment_success {
      action                           = "TERMINATE"
      termination_wait_time_in_minutes = 60
    }
  }

  deployment_style {
    deployment_option = "WITH_TRAFFIC_CONTROL"
    deployment_type   = "BLUE_GREEN"
  }

  ecs_service {
    cluster_name = var.ecs_cluster_name
    service_name = var.ecs_service_name
  }

  load_balancer_info {
    target_group_pair_info {
      prod_traffic_route {
        listener_arns = [
          var.lb_listener_https_arn
        ]
      }

      target_group {
        name = var.target_group_blue_name
      }

      target_group {
        name = var.target_group_green_name
      }

      test_traffic_route {
        listener_arns = [var.lb_listener_test_https_arn]
      }
    }
  }
}

```
</details>

CodeDeployで「テストトラフィックルーティングのセットアップ」が完了していればテストリスナーの10443ポートでアクセスができるようになる。



![スクリーンショット 2022-05-05 11.52.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526312/fa0dcc4f-2b54-e25d-b3b0-8ef9bdb20038.png)

## 関連

CloudFormationだと、ECSのBlue/GreenデプロイのDeployment Groupは作成することができない。

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-codedeploy-deploymentgroup-loadbalancerinfo.html

https://dev.classmethod.jp/articles/cloudformation-with-custom-resource-for-fargate-blue-green-deployment/