ecsをデプロイする際にCodedeploy用のtaskdef.jsonをCodebuild内で生成する

# はじめに
最近CodePipelineを使ってecsのデプロイを自動化する仕事をしていました。
その中でCodeDeployを使用することになったのですが、
CodeDeployを使ってデプロイするためにはアプリケーションコードがあるリポジトリ内に```taskdef.json```ファイルを作成してそこにタスク定義を書く必要があります。

しかし、プロジェクトではterraformでインフラを管理していたため、タスク定義がterraformとアプリケーションコードの両方に存在しているのは避けたいと思い、Codebuild内でtaskdef.jsonを作成することにした。

# 概略
方法
1. ecsからtask定義を持ってきてそれをもとにtaskdef.jsonを作成するコマンドをbuildspec.ymlに記述する
2. 作成したtaskdef.jsonをcodebuildのアーティファクトに出力
3. terraform側でtask定義のアーティファクトをcodebuildからのものを指定

# Codebuild内で生成
```bash
aws ecs describe-task-definition \
--task-definition $ECS_TASK_DEFINITION_ARN \
--query taskDefinition \
| jq '.containerDefinitions[0].image="<IMAGE1_NAME>"' > taskdef.json
```
以上のようなコマンドをコードビルド内で実行します。
やっていることとしては、arnを指定してタスク定義を持ってきて、タスク定義ないのイメージの部分だけ`<IMAGE1_NAME>`に変えて、`taskdef.json`として保存しています。

以下を参考にしました。
https://ngyuki.hatenablog.com/entry/2021/04/07/043415

 `buildspec.yaml`では以下のように記述しました

```yaml
version: 0.2
env:
  variables:
    IMAGE_NAME: 'imagename'
phases:
  install:
    runtime-versions:
      docker: 18
  pre_build:
    commands:
      - $(aws ecr get-login --no-include-email --region ${AWS_DEFAULT_REGION})
      - AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)
      - URI=${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${IMAGE_NAME}
      - $(aws ecs describe-task-definition --task-definition $ECS_TASK_DEFINITION_ARN --query taskDefinition | jq '.containerDefinitions[0].image="<IMAGE1_NAME>"' > taskdef.json)
  build:
    commands:
      - docker build -f ./ecs/Dockerfile -t $URI:$CODEBUILD_RESOLVED_SOURCE_VERSION .
      - docker push $URI:$CODEBUILD_RESOLVED_SOURCE_VERSION
      - printf '{"Version":"1.0","ImageURI":"%s"}' $URI:$CODEBUILD_RESOLVED_SOURCE_VERSION > imageDetail.json
artifacts:
  files: 
    - imageDetail.json
    - taskdef.json
```

prebuildのコマンドとして上述のコマンドを実行し、作成した`taskdef.json`を`artifacts`の`files`に指定しています。

ちなみに、`$ECS_TASK_DEFINITION_ARN`に関しては自分でCodebuild内の環境変数として設定する必要があります。
自分の場合はterraformにて以下のように設定いたしました。

```terraform
resource "aws_codebuild_project" "main" {
# 省略
  environment {
    # 省略
    environment_variable {
      name  = "ECS_TASK_DEFINITION_ARN"
      value = aws_ecs_task_definition.task_def_name.arn
    }
  }
}
```

# taksdef.jsonのアーティファクトを指定
`taksdef.json`のArtifactをCodebuild時に出力した場所を指定します。
自分の場合は、Sourceステージの出力先を`source`、Buildステージは`build`としていたので以下のよう に`TaskDefinitionTemplateArtifact`を`build`に設定をしました。

```terraform
resource "aws_codepipeline" "main" {
# 省略
  stage {
    name = "Deploy"
# 省略
    action {
      name            = "Deploy"
      input_artifacts = ["build", "source"]
      configuration = {
# 省略
        TaskDefinitionTemplateArtifact = "build"
        TaskDefinitionTemplatePath     = "taskdef.json"

        AppSpecTemplateArtifact        = "source"
        AppSpecTemplatePath            = "appspec.yml"
        Image1ArtifactName             = "build"
        Image1ContainerName            = "IMAGE1_NAME"
      }
    }
  }
}
```

# 環境変数にマルチバイト文字を使用した際のエラー

このような方法でタスク定義を実行するとCodedeployにて以下のようなエラーが出る場合があります。

```
Failed to validate the task definition. Check the task definition in the
"TaskDefinitionTemplateArtifact" parameter for your pipeline action
and verify the configuration details for the ECS service.
```

これはECSのタスク定義内でコンテナに設定している環境変数内にマルチバイト文字が使われているのが原因のようです。
なのでコンテナの環境変数にはシングルバイト文字だけにしましょう。

以下を参考にしました。
https://zuntan02.hateblo.jp/entry/2020/12/28/204325