RailsとNuxt3でtodoリストを作ろう[REST-API/Terraform/Fargate]〜その６、Blue/Greenデプロイ前編

# はじめに

RailsとNuxt3でtodoリストの作り方を
初めから丁寧に説明したいと思います。

使用pcはmacを想定しています。

完成した構成図は以下の通りです。

![aws_structure.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/731268/362a1ef8-00e1-75d0-e923-ad2e59eca033.png)


また、githubレポジトリはこちらです。

https://github.com/sugiyama404/ruby_and_nuxt

各シリーズは以下の通りです。

[RailsとNuxt3でtodoリストを作ろう[REST-API/Terraform/Fargate]〜その１、Rails基本設定編](https://qiita.com/sugiyama404/items/637900df3b533fd149f8)
[RailsとNuxt3でtodoリストを作ろう[REST-API/Terraform/Fargate]〜その２、Rails API編](https://qiita.com/sugiyama404/items/acea4cc38fe7f155ab54)
[RailsとNuxt3でtodoリストを作ろう[REST-API/Terraform/Fargate]〜その３、Nuxt.js編](https://qiita.com/sugiyama404/items/4e74c4446add5efc3669)
[RailsとNuxt3でtodoリストを作ろう[REST-API/Terraform/Fargate]〜その４、TerraformECS前編](https://qiita.com/sugiyama404/items/4e19ac1f9be6b8213f55)
[RailsとNuxt3でtodoリストを作ろう[REST-API/Terraform/Fargate]〜その５、TerraformECS後編](https://qiita.com/sugiyama404/items/d0e0e74628d38842c9bd)
[RailsとNuxt3でtodoリストを作ろう[REST-API/Terraform/Fargate]〜その６、Blue/Greenデプロイ前編](https://qiita.com/sugiyama404/items/1ffb4334b3a1546b4e23)
[RailsとNuxt3でtodoリストを作ろう[REST-API/Terraform/Fargate]〜その７、Blue/Greenデプロイ後編](https://qiita.com/sugiyama404/items/ea57e8c08156bf6eca46)

# codepipline

一つのコンテナでBlue/Greenデプロイを行なっているバージョンです。
参考にしていただければ、幸いです。

https://qiita.com/sugiyama404/items/d3059788f6d36c4f476f


## main.tf

main.tfに以下を追加します。

ECSのサービスの立ち上げを待つ為、以下コメントアウトしたままapplyをして、
成功後、コメントアウトを解除し再度applyする。


```terraform:terraform/main.tf

module "codepipeline" {
  source             = "./modules/codepipeline"
  app_name           = var.app_name
  web_app_name       = var.web_app_name
  api_app_name       = var.api_app_name
  api_blue_arn       = module.elb.api_blue_arn
  web_blue_arn       = module.elb.web_blue_arn
  api_green_arn      = module.elb.api_green_arn
  web_green_arn      = module.elb.web_green_arn
  http_arn           = module.elb.http_arn
  api_blue_name      = module.elb.api_blue_name
  web_blue_name      = module.elb.web_blue_name
  api_green_name     = module.elb.api_green_name
  web_green_name     = module.elb.web_green_name
  cluster-name       = module.ecs.cluster-name
  api-service-name   = module.ecs.api-service-name
  web-service-name   = module.ecs.web-service-name
  api-definition-arn = module.ecs.api-definition-arn
  web-definition-arn = module.ecs.web-definition-arn
}
```

## cloudwatch

githubからcodecommitにミラーリングした後、codepiplineが発火するようにcloudwatchで監視しています。

```terraform:terraform/modules/codepipeline/aws_cloudwatch_event_rule.tf
resource "aws_cloudwatch_event_rule" "image_push" {
  name     = "codecommit_push"
  role_arn = aws_iam_role.cwe_role.arn

  event_pattern = jsonencode({
    source      = ["aws.codecommit"]
    detail-type = ["CodeCommit Repository State Change"]
    resources   = ["${aws_codecommit_repository.codecommit_repository.arn}"]

    detail = {
      event         = ["referenceCreated", "referenceUpdated"]
      referenceType = ["branch"]
      referenceName = ["main"]
    }
  })
}
```

```terraform:terraform/modules/codepipeline/aws_cloudwatch_event_target.tf
resource "aws_cloudwatch_event_target" "codepipeline" {
  rule      = aws_cloudwatch_event_rule.image_push.name
  target_id = "${var.app_name}-Image-Push-Codepipeline"
  arn       = aws_codepipeline.codepipeline.arn
  role_arn  = aws_iam_role.cwe_role.arn
}
```

## s3

codepiplineのリソースは一旦s3に保存、経由し次のアクションを実行します。

CodeSourceからSourceArtiディレクトリーに
CodeBuildからBuildArtifディレクトリーに保存されます。

https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/welcome-introducing-artifacts.html

```terraform:terraform/modules/codepipeline/aws_s3_bucket.tf
resource "aws_s3_bucket" "codepipeline_bucket" {
  bucket        = "codepipeline-bucket-${random_string.s3_unique_key.result}"
  force_destroy = true
}

resource "random_string" "s3_unique_key" {
  length  = 6
  upper   = false
  lower   = true
  numeric = true
  special = false
}
```

## iam

codepipline、ccodecommit、codebuildに許可を与えます。

```terraform:terraform/modules/codepipeline/aws_iam_role.tf
resource "aws_iam_role" "codepipeline_role" {
  name = "codepipeline-role"
  assume_role_policy = jsonencode({

    Statement = [
      {
        Effect = "Allow"
        Action = "sts:AssumeRole"
        Principal = {
          Service = "codepipeline.amazonaws.com"
        }
      }
    ]
  })
  tags = {
    Name = "${var.app_name}-codepipeline-role"
  }
}

resource "aws_iam_role" "codebuild-role" {
  name = "${var.app_name}-codebuild-role"
  assume_role_policy = jsonencode({
    Statement = [
      {
        Effect = "Allow"
        Action = "sts:AssumeRole"
        Principal = {
          Service = "codebuild.amazonaws.com"
        }
      }
    ]
  })
  tags = {
    Name = "${var.app_name}-codebuild-role"
  }
}

resource "aws_iam_role" "codedeploy-role" {
  name = "${var.app_name}-codedeploy-role"
  assume_role_policy = jsonencode({
    Statement = [
      {
        Effect = "Allow"
        Action = "sts:AssumeRole"
        Principal = {
          Service = "codedeploy.amazonaws.com"
        }
      }
    ]
  })
  tags = {
    Name = "${var.app_name}-codedeploy-role"
  }
}

# cloud watch
resource "aws_iam_role" "cwe_role" {
  name = "${var.app_name}-cwe-role"
  assume_role_policy = jsonencode({

    Version = "2012-10-17"
    Statement = [
      {
        Sid    = ""
        Effect = "Allow"
        Action = "sts:AssumeRole"
        Principal = {
          Service = "events.amazonaws.com"
        }
      }
    ]
  })
  tags = {
    Name = "${var.app_name}-cwe-role"
  }
}
```

```terraform:terraform/modules/codepipeline/aws_iam_role_policy.tf
resource "aws_iam_role_policy" "codepipeline-policy" {
  name   = "codepipeline-policy"
  role   = aws_iam_role.codepipeline_role.id
  policy = data.aws_iam_policy_document.codepipeline-policy.json
}

data "aws_iam_policy_document" "codepipeline-policy" {
  statement {
    effect = "Allow"

    actions = [
      "s3:GetObject",
      "s3:GetObjectVersion",
      "s3:GetBucketVersioning",
      "s3:PutObjectAcl",
      "s3:PutObject",
    ]

    resources = [
      aws_s3_bucket.codepipeline_bucket.arn,
      "${aws_s3_bucket.codepipeline_bucket.arn}/*"
    ]
  }
  statement {
    effect = "Allow"

    actions = [
      "ecr:DescribeImages",
    ]

    resources = ["*"]
  }
  statement {
    effect = "Allow"

    actions = [
      "codedeploy:CreateDeployment",
      "codedeploy:GetDeployment",
      "codedeploy:GetApplication",
      "codedeploy:GetApplicationRevision",
      "codedeploy:RegisterApplicationRevision",
      "codedeploy:GetDeploymentConfig",
      "ecs:RegisterTaskDefinition"
    ]

    resources = ["*"]
  }
  statement {
    effect = "Allow"
    actions = [
      "codecommit:GetBranch",
      "codecommit:GetCommit",
      "codecommit:UploadArchive",
      "codecommit:GetUploadArchiveStatus",
      "codecommit:CancelUploadArchive"
    ]
    resources = ["${aws_codecommit_repository.codecommit_repository.arn}"]
  }
  statement {
    effect = "Allow"

    actions = [
      "codebuild:BatchGetBuilds",
      "codebuild:StartBuild",
    ]

    resources = ["*"]
  }
  statement {
    effect = "Allow"
    actions = [
      "iam:GetRole",
      "iam:PassRole"
    ]

    resources = ["*"]
  }
}

resource "aws_iam_role_policy" "codebuild-policy" {
  name   = "codebuild-policy"
  role   = aws_iam_role.codebuild-role.name
  policy = data.aws_iam_policy_document.codebuild-policy.json
}

data "aws_iam_policy_document" "codebuild-policy" {
  statement {
    effect = "Allow"

    actions = [
      "logs:CreateLogGroup",
      "logs:CreateLogStream",
      "logs:PutLogEvents",
    ]

    resources = ["*"]
  }
  statement {
    effect  = "Allow"
    actions = ["s3:*"]
    resources = [
      aws_s3_bucket.codepipeline_bucket.arn,
      "${aws_s3_bucket.codepipeline_bucket.arn}/*",
    ]
  }
  statement {
    effect    = "Allow"
    actions   = ["ecs:DescribeTaskDefinition"]
    resources = ["*"]
  }
}
```

```terraform:terraform/modules/codepipeline/aws_iam_policy.tf
# cloud watch
resource "aws_iam_policy" "cwe_policy" {
  name = "${var.app_name}-cwe-policy"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = [
          "codepipeline:StartPipelineExecution",
        ]
        Resource = [
          "${aws_codepipeline.codepipeline.arn}",
        ]
        Effect = "Allow"
      },
    ]
  })
}
```

```terraform:terraform/modules/codepipeline/aws_iam_policy_attachment.tf
resource "aws_iam_policy_attachment" "cws_policy_attachment" {

  name       = "${var.app_name}-cwe-policy"
  roles      = [aws_iam_role.cwe_role.name]
  policy_arn = aws_iam_policy.cwe_policy.arn
}

resource "aws_iam_role_policy_attachment" "AWSCodeDeployRoleForECS" {
  policy_arn = "arn:aws:iam::aws:policy/AWSCodeDeployRoleForECS"
  role       = aws_iam_role.codedeploy-role.name
}
```