LoginSignup
10
2

More than 3 years have passed since last update.

ECS Fargateデプロイ用CodePipelineのtaskdef.jsonに変数を持たせる方法

Posted at

はじめに

CodePipelineによるCI/CDパイプラインは便利なんだけど、せっかくTerraformやCloudFormationであれこれ変数化して自動化しているにもかかわらず、ECS Fargateにデプロイするための taskdef.json と Appspec.yml で変数化できずにハマるケースがあって困る。今回は、これを解決する手段を考えた。

前提条件

初学者向けのパイプライン作成ハンズオンに出てくるアウトプットをごちゃごちゃいじるので、少なくとも、CodePipelineの基礎は抑えていないと厳しい。過去の記事では以下のあたりを読んでおくと分かりやすくなっているはずだ。
なお、この記事ではIaCはTerraformで書いている。

方針

CodePipelineの taskdef.json には、プレースホルダ(チュートリアルやハンズオンではという名前で出てくる、コンテナのイメージIDで置換する機能)という強力な機能があるが、これは1つのビルドアーティファクトに対して1つのプレースホルダにしか対応していないし、最大4つまでしか指定できない。
これを駆使して頑張ろうとすると、変なビルドステージを作ったりしなければならずBuildSpecが煩雑化するので没にする。

となると、ビルドステージで良い感じに taskdef.json を生成してあげるのが簡単だろう。

IaC見直し箇所

さて、通常のハンズオンやチュートリアルでは、taskdef.json はリポジトリに入っているものをそのままソースステージから引っ張ってくることになるが、今回はビルドステージで編集したものを利用するため、CodePipelineのリソースを以下のように修正する。

  stage {
    name = "Deploy"

    action {
      run_order = 3
      name      = "Deploy"
      category  = "Deploy"
      owner     = "AWS"
      provider  = "CodeDeployToECS"
      version   = "1"
      input_artifacts = [
        "SourceArtifact",
        "BuildArtifact",
      ]

      configuration = {
        ApplicationName                = aws_codedeploy_app.application.name
        DeploymentGroupName            = aws_codedeploy_deployment_group.application.deployment_group_name
        AppSpecTemplateArtifact        = "SourceArtifact"
        AppSpecTemplatePath            = var.appspec_file_name
        TaskDefinitionTemplateArtifact = "BuildArtifact"  # ★ここをSourceArtifactから変更
        Image1ArtifactName             = "BuildArtifact"
        Image1ContainerName            = "IMAGE1_NAME"
      }
    }

また、taskdef.json は以下のように修正しておこう。
以下を、外部から指定可能にして置換できるようにする。
- <EXECUTION_ROLE_ARN>
- <TASK_ROLE_ARN>
- <CONTAINER_NAME>
- <LOGGROUP_NAME>
- <TASK_FAMILY>

{
    "executionRoleArn": "<EXECUTION_ROLE_ARN>",
    "taskRoleArn": "<TASK_ROLE_ARN>",
    "containerDefinitions": [
      {
        "name": "<CONTAINER_NAME>",
        "image": "<IMAGE1_NAME>",
        "logConfiguration": {
          "logDriver": "awslogs",
          "options": {
            "awslogs-group": "<LOGGROUP_NAME>",
            "awslogs-region": "ap-northeast-1",
            "awslogs-stream-prefix": "ecs"
          }
        },
        "portMappings": [
          {
            "containerPort": 80,
            "hostPort": 80,
            "protocol": "tcp"
          }
        ],
        "essential": true,
        "cpu": 0,
        "memoryReservation": 256,
      }
    ],
    "requiresCompatibilities": ["FARGATE"],
    "networkMode": "awsvpc",
    "cpu": "256",
    "memory": "512",
    "family": "<TASK_FAMILY>"
  }

さて、CodeBuildでは、Buildspec の post_build で以下のようにして、上記の置換対象のタグを環境変数で置換する。
sed の置換の区切り文字をスラッシュから変更しているのは、IAMロールのARNやロググループ名にスラッシュが入ってくる可能性があるからだ。Terraformのtfファイル側で上手くエスケープしようと思ったが、面倒なのでBuildspec側で対応した。

  post_build:
    commands:
      - docker push ${REPOSITORY_URI}:${IMAGE_TAG}
      - docker push ${REPOSITORY_URI}:latest
      - printf '{"name":"%s","ImageURI":"%s"}' $ECR_REPOSITORY_NAME $REPOSITORY_URI:$IMAGE_TAG > imageDetail.json
      # ★以下を追加
      - sed -i -e "s#<EXECUTION_ROLE_ARN>#${EXECUTION_ROLE_ARN}#" taskdef.json
      - sed -i -e "s#<TASK_ROLE_ARN>#${TASK_ROLE_ARN}#" taskdef.json
      - sed -i -e "s#<CONTAINER_NAME>#${CONTAINER_NAME}#" taskdef.json
      - sed -i -e "s#<LOGGROUP_NAME>#${LOGGROUP_NAME}#" taskdef.json
      - sed -i -e "s#<TASK_FAMILY>#${TASK_FAMILY}#" taskdef.json

また、taskdef.json をアーティファクトに指定することを忘れないように。

artifacts:
  files:
    - imageDetail.json
    - taskdef.json

で、肝心のCodeBuild側から、良い感じに環境変数を渡してあげれば、taskdef.json も自動化の流れに乗せらえれる。CPUやメモリを検証環境とプロダクション環境で変更する場合も、tfファイル側でmapしてあげれば環境差分も吸収可能だ!

resource "aws_codebuild_project" "application" {
(中略)
  environment {
    type            = "LINUX_CONTAINER"
    compute_type    = "BUILD_GENERAL1_SMALL"
    image           = "aws/codebuild/amazonlinux2-x86_64-standard:3.0"
    privileged_mode = "true"

    # 以下を追加
    environment_variable {
      name  = "EXECUTION_ROLE_ARN"
      value = aws_iam_role.ecs_taskexecution.arn
    }

    environment_variable {
      name  = "TASK_ROLE_ARN"
      value = aws_iam_role.ecs.arn
    }

    environment_variable {
      name  = "CONTAINER_NAME"
      value = var.container_name
    }

    environment_variable {
      name  = "LOGGROUP_NAME"
      value = aws_cloudwatch_log_group.ecstask_log_group.name
    }

    environment_variable {
      name  = "TASK_FAMILY"
      value = aws_ecs_task_definition.ecsfargate.family
    }
  }
(中略)
}

これでビルドを走らせると、ビルドアーティファクトの taskdef.json が以下のように出力される。

{
    "executionRoleArn": "arn:aws:iam::xxxxxxxxxxxx:role/ContainerPipeline-ECSTaskExecutionRole",
    "taskRoleArn": "arn:aws:iam::xxxxxxxxxxxx:role/ContainerPipeline-ECSTaskRole",
    "containerDefinitions": [
      {
        "name": "ContainerPipeline-ECSContainer",
        "image": "<IMAGE1_NAME>",
        "logConfiguration": {
          "logDriver": "awslogs",
          "options": {
            "awslogs-group": "/ecstask/ContainerPipeline-LogGroup",
            "awslogs-region": "ap-northeast-1",
            "awslogs-stream-prefix": "ecs"
          }
        },
        "portMappings": [
          {
            "containerPort": 80,
            "hostPort": 80,
            "protocol": "tcp"
          }
        ],
        "essential": true,
        "cpu": 0,
        "memoryReservation": 256,
      }
    ],
    "requiresCompatibilities": ["FARGATE"],
    "networkMode": "awsvpc",
    "cpu": "256",
    "memory": "512",
    "family": "ContainerPipeline-ECSTaskFamily"
  }

Appspecの ContainerName も同じ要領で置換可能なはず。
これで、夢の全自動ビルドに一歩近づいた!

10
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
2