[Terraform]Github ActionからECSへデプロイ(タスク定義の更新)する場合に、タスク定義をJsonファイルとして分離させず、Terraformで直接管理する方法

## 概要
ECSで稼働させているサービスをGithubActionからデプロイする際は、ECSタスク定義をJsonファイルで定義して、GithubActionのワークフロー内でaws-actions/amazon-ecs-render-task-definition@v1を利用してタスク定義を上書きした上でECSリソースに反映する、というのが一般的な流れです。

TerraformなどのIaCを利用している場合は、Terraform内で対象のjsonファイルを呼び出すことにより、タスク定義の二重管理についても避けることができます。  

しかし、このようにECSのタスク定義を管理する箇所を分離すると、感覚的にどこで何を管理しているのか分かりにくくなります。そこで、ECSのタスク定義をTerraformで直接管理しつつ、GithubActionでデプロイするための手順を考え、その手順について以下にまとめました。

## ECSのタスク定義をterraformで定義する
ECSのタスク定義の例を以下に示します。  
注目する点は、imageのバージョンを変数で定義している点です。Github Actionでデプロイする際に、この変数を更新することになります。  
```terraform:ecs.tf
#タスク定義
resource "aws_ecs_task_definition" "example_task" {
  family                = "example-task"
  requires_compatibilities = ["FARGATE"]
  network_mode          = "awsvpc"
  cpu    = 256
  memory = 512
  execution_role_arn    = aws_iam_role.ecs_task_execution_role.arn

  container_definitions = jsonencode([{
    name  = "example-container",

    #imageのバージョンを変数で定義
    image = "example-image:${var.image_version}",

    essential: true,
    memoryReservation = 256,
    memory = 512,
    cpu = 256,
    portMappings: [
      {
        protocol: "tcp",
        containerPort: 80
      }
    ],
  }])
}
```
variables.tfには以下のようにimage_versionを空で定義します。  
```terraform:variables.tf
variable "image_version" {}
```
この記事ではモジュール化した場合の例は示しませんが、例えば上位ディレクトリのmain.tfでECSのモジュールを呼び出す際には、上位ディレクトリのvariables.tfでもimage_versionは空で定義してください。

### GithubActionでデプロイする際のワークフローを定義
GithubActionでデプロイする際のワークフローを定義します。  
ECRにimageをpushするstepと、terraform applyを実行するstepを定義します。
```yaml:.github/workflows/deploy.yml
name: deploy

on:
  push:
    branches:
      - main

env:
  AWS_REGION: ap-northeast-1
  #imageのタグをgithubのshaにすることで、常に最新のimageをデプロイする
  IMAGE_VERSION: ${{ github.sha }}

jobs:
  deploy:
    name: Push to ECR
    runs-on: ubuntu-latest

    steps:
      - name: setup aws cli
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ env.AWS_REGION }}

      - uses: actions/checkout@v2

      - uses: aws-actions/amazon-ecr-login@v1
        id: amazon-ecr-login

      - name: push ex-repo image to ECR
        env:
          ECR_REGISTRY: ${{ steps.amazon-ecr-login.outputs.registry }}
          ECR_REPOSITORY: ex-image
        run: |
          docker build -t ex-image -f ./Dockerfile.prod ./
          docker tag ex-image:latest ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ env.IMAGE_VERSION }}
          docker push ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ env.IMAGE_VERSION }}

  terraform:
    name: Depploy to AWS ECS
    runs-on: ubuntu-latest

    #terraformを管理しているディレクトリに移動
    defaults:
      run:
        working-directory: terraform-folder

    steps:
    - uses: actions/checkout@v2

    #terraformのバージョンを指定
    - name: Setup Terraform
      uses: hashicorp/setup-terraform@v1
      with:
        terraform_version: 1.6.3

    #terraform initを実行
    - name: Terraform Init
      run: terraform init
      env:
        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

    #terraform applyを実行
    #引数にimage_versionを渡す
    - name: Terraform Apply
      run: terraform apply -var "image_version=${{ env.IMAGE_VERSION }}" -auto-approve
      env:
        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
```

ポイントとしては、まずGithubActionのワークフローでimageのタグをgithubのshaにすることで、常に最新のimageをデプロイするようにしています。  
また、イメージをビルドしてshaをタグに付与しています。  
そして、Terraformのワークフローでterraform applyを実行する際に、image_versionにshaを渡しています。  

以上の内容をセットアップすることで、ECSのタスク定義をTerraformで直接管理しながら、Github ActionでCDを実現することができます、

## 運用上の注意点
この方法では、ECS以外のリソースを更新するためにTerraformをapplyする際にも、image_versionを引数として渡す必要が出てきます。AWS CLIやコンソールからの目視で現在のimage_versionを確認する必要があるため、ヒューマンエラーが起きる可能性が高いです。

そのため、Terraformを構築する際にリソース単位でtfstaeを分けるなどの対策が必要になります。