TerraformでECS ローリング更新のCodePipelineを作ってみた

2022.08.19

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

「TerraformでECSローリング更新のCodePipelineを作りたい」

サンプルがあまりなかったので書いてみました。

サンプルコードは以下になります。

msato0731/terraform-sample/tree/main/ecs-rolling-update

やってみた

terraformで環境構築

いくつかコードを解説します。

まずは、CodePipelineを定義している部分です。

CodeCommitをソースに、CodeBuildでDockerイメージのBuild・Push、最後にECSへのローリング更新を行なっています。

(B/Gデプロイでは、CodeDeployが必要ですがローリング更新であれば無くてもできます)

少しはまった点としては、CloudWatch Eventの部分を記述忘れていてCodePipelineが発火しなかった点です。

codepipeline.tf

resource "aws_codepipeline" "sample_app" {
  name     = local.name_prefix
  role_arn = aws_iam_role.codepipeline.arn

  artifact_store {
    location = aws_s3_bucket.codepipeline_artifact.bucket
    type     = "S3"
  }

  stage {
    name = "Source"
    action {
      name             = "Source"
      category         = "Source"
      owner            = "AWS"
      provider         = "CodeCommit"
      version          = 1
      output_artifacts = ["source_output"]
      configuration = {
        RepositoryName       = aws_codecommit_repository.sample_app.repository_name
        BranchName           = "main"
        OutputArtifactFormat = "CODE_ZIP"
        PollForSourceChanges = "false"
      }
    }
  }
  stage {
    name = "Build"
    action {
      name             = "Build"
      category         = "Build"
      owner            = "AWS"
      provider         = "CodeBuild"
      version          = 1
      input_artifacts  = ["source_output"]
      output_artifacts = ["build_output"]

      configuration = {
        ProjectName = aws_codebuild_project.sample_app.id
      }
    }
  }
  stage {
    name = "Deploy"
    action {
      name            = "Deploy"
      category        = "Deploy"
      owner           = "AWS"
      provider        = "ECS"
      version         = 1
      input_artifacts = ["build_output"]
      configuration = {
        ClusterName = aws_ecs_cluster.this.id
        ServiceName = aws_ecs_service.sample_app.name
      }
    }
  }
}

resource "aws_cloudwatch_event_rule" "codepipeline_sample_app" {
  name = "${local.name_prefix}-codepipeline-sample-app"

  event_pattern = templatefile("./file/codepipeline_event_pattern.json", {
    codecommit_arn : aws_codecommit_repository.sample_app.arn
  })
}

resource "aws_cloudwatch_event_target" "codepipeline_sample_app" {
  rule     = aws_cloudwatch_event_rule.codepipeline_sample_app.name
  arn      = aws_codepipeline.sample_app.arn
  role_arn = aws_iam_role.event_bridge_codepipeline.arn
}

次にCodeBuildです。

buildspec.ymlはアプリケーションリポジトリに置くパターンも多いかと思いますが、今回はterraformに含めました。

buildspec.ymlで必要なAWSアカウントIDやpushする先のECRの情報は、CodeBuildの環境変数で渡す形にしました。

codebuild.tf

resource "aws_codebuild_project" "sample_app" {
  name         = local.name_prefix
  service_role = aws_iam_role.codebuild.arn

  source {
    type                = "CODEPIPELINE"
    git_clone_depth     = 0
    insecure_ssl        = false
    report_build_status = false
    buildspec           = file("./file/buildspec.yml")
  }
  environment {
    compute_type    = "BUILD_GENERAL1_SMALL"
    image           = "aws/codebuild/standard:4.0"
    type            = "LINUX_CONTAINER"
    privileged_mode = true
    environment_variable {
      name  = "AWS_ACCOUNT_ID"
      value = data.aws_caller_identity.current.account_id
    }
    environment_variable {
      name  = "REPOSITORY_URI"
      value = aws_ecr_repository.httpd.repository_url
    }
  }
  artifacts {
    type                = "CODEPIPELINE"
    encryption_disabled = false
    name                = local.name_prefix
    packaging           = "NONE"
  }
}

file/buildspec.yml

version: 0.2
phases:
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - aws --version
      - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com
      - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
      - IMAGE_TAG=${COMMIT_HASH:=latest}
  build:
    commands:
      - echo Build started on `date`
      - echo Building the Docker image...
      - docker build -t $REPOSITORY_URI:$IMAGE_TAG .
  post_build:
    commands:
      - echo Build completed on `date`
      - echo Pushing the Docker images...
      - docker push $REPOSITORY_URI:$IMAGE_TAG
      - echo Writing image definitions file...
      - printf '[{"name":"httpd","imageUri":"%s"}]' $REPOSITORY_URI:$IMAGE_TAG > imagedefinitions.json
artifacts:
    files: imagedefinitions.json

CodeCommitにDockerfileをアップロード

CodeCommitはTerraformを流した状態だとブランチが存在しないため、以下のエラーになります。

codepipeline_faile

マネジメントコンソール上から、リポジトリにあるDockerfileをアップロードすることでCodePipelineが成功するようになります。

ALBのエンドポイントにアクセスすると、以下のようにDockerfile上で設定したテキストが返ってきます。

$ curl <ALBのエンドポイント>
test

CodePipelineの動作確認

CodeCommit変更時に、変更内容がデプロイされるか試してみます。

マネジメントコンソールでCodeCommitにアクセスし、Dockerfileを編集します。

本来ならアプリケーションのコードを変更して反映を確認する流れになりますが、簡略化のためにhttpdのDockerファイルのindex.htmlを書き換えています。

CodeCommit上でコミットすることで、ファイルの変更をCodePipelineで検出してパイプラインが動作します。 パイプラインの動作終了後に、再度ALBのエンドポイントにアクセスするレスポンスが変わっていることが確認できます。

$ curl <ALBのエンドポイント>
test2

おわりに

TerraformでCodePipelineを使用したECSローリング更新をやってみました。

Terraformの記述に改善の余地があると思いますが、参考になると嬉しいです。

以上、AWS事業本部の佐藤(@chari7311)でした。