search
LoginSignup
22

More than 3 years have passed since last update.

posted at

updated at

CodeDeployでECSに複数のImageをBlue/Green Deploymentする方法

CodePipelineとCodeDeployを使って、ECSへBlue/Green Deploymentをする。ただし2つ以上のDockerイメージを持ったタスク定義の場合、少しドキュメントとは異なった手法を取らなければならない。今回はTerraformのサンプルも交えつつ紹介する。

結論から言うと、imageDetail.jsonは使わず、taskdef.jsonをsedコマンドで書き換えて渡す。

CodeBuildで行う処理

buildspec.ymlは以下のようにする。

buildspec.yml
version: 0.2

phases:
  install:
    commands:
      - pip3 install awscli --upgrade --user
  pre_build:
    commands:
      - $(aws ecr get-login --no-include-email --region ap-northeast-1)
      - IMAGE_TAG=$CODEBUILD_RESOLVED_SOURCE_VERSION
      - RP_IMAGE_URI=123456.dkr.ecr.ap-northeast-1.amazonaws.com/my-app-rp
      - WEB_IMAGE_URI=123456.dkr.ecr.ap-northeast-1.amazonaws.com/my-app-web
  build:
    commands:
      - docker build -t $WEB_IMAGE_URI:$IMAGE_TAG -f Dockerfile_web .
      - docker build -t $RP_IMAGE_URI:$IMAGE_TAG -f Dockerfile_rp .
  post_build:
    commands:
      - docker push $RP_IMAGE_URI:$IMAGE_TAG
      - docker push $WEB_IMAGE_URI:$IMAGE_TAG
      - cat taskdef-production-template.json | sed -e s@\<IMAGE_RP_NAME\>@$RP_IMAGE_URI:$IMAGE_TAG@ -e s@\<IMAGE_WEB_NAME\>@$WEB_IMAGE_URI:$IMAGE_TAG@ > taskdef.json

artifacts:
  files:
      - appspec.yml
      - taskdef.json

特別なことをやっているのはpost_buildの最後の行ぐらい。ここは用意したtaskdef-production-template.jsonの中にある<IMAGE_RP_NAME><IMAGE_WEB_NAM>をsedコマンドによって新しいイメージURIに書き換えている。そしてそのまま新しくtaskdef.jsonとしてartifactsで出力し、CodeDeployへ渡している。

taskdef-production-template.jsonは自分で作成したタスク定義の中身をそのままコピーし、image部分だけ"image": "<IMAGE_RP_NAME>"のように書き換えておく。

appspec.ymlは以下のようにロードバランサからのリクエストを受け取るイメージ名とポートを指定しておく。

appspec.yml
version: 0.0
Resources:
  - TargetService:
      Type: AWS::ECS::Service
      Properties:
        TaskDefinition: "<TASK_DEFINITION>"
        LoadBalancerInfo:
            ContainerName: "rp"
            ContainerPort: "80"

これを毎回CodeBuildのartifactsを渡している理由は、taskdef.json を含むアーティファクトのサイズを 3MB 未満にする必要があるからである。

CodePipelineのTerraformも参考程度に載せておく。

codepipeline.tf
resource "aws_codebuild_project" "production" {
  count         = "${local.is_prod ? 1 : 0}"
  name          = "airconkingdom-com-application-production"
  build_timeout = 10
  service_role  = "${data.terraform_remote_state.samurai_iam_user.codebuild_role_arn}"

  artifacts {
    type = "CODEPIPELINE"
  }

  environment {
    compute_type    = "BUILD_GENERAL1_SMALL"
    image           = "aws/codebuild/golang:1.11"
    type            = "LINUX_CONTAINER"
    privileged_mode = true

    environment_variable {
      "name"  = "DOCKER_BUILDKIT"
      "value" = "1"
    }
  }

  source {
    type      = "CODEPIPELINE"
    buildspec = "buildspec-production.yml"
  }
}

#####################################
# CodePipeline
#####################################
resource "aws_codepipeline" "example" {
  stage {
    name = "Source"

    action {
      name             = "Source"
      category         = "Source"
      owner            = "AWS"
      provider         = "CodeCommit"
      version          = "1"
      output_artifacts = ["app-source"]

      configuration {
        RepositoryName = "my-app"
        BranchName     = "master"
      }
    }
  }

  stage {
    name = "ProductionBuild"

    action {
      name             = "Build"
      category         = "Build"
      owner            = "AWS"
      provider         = "CodeBuild"
      version          = "1"
      input_artifacts  = ["app-source"]
      output_artifacts = ["production-artifacts"]

      configuration {
        ProjectName = "my-app-production-build"
      }
    }
  }

  stage {
    name = "ProductionDeploy"

    action {
      name            = "Deploy"
      category        = "Deploy"
      owner           = "AWS"
      provider        = "CodeDeployToECS"
      input_artifacts = ["production-artifacts"]
      version         = "1"

      configuration {
        ApplicationName                = "my-app-production"
        DeploymentGroupName            = "my-app-production"
        TaskDefinitionTemplateArtifact = "production-artifacts"
        TaskDefinitionTemplatePath     = "taskdef.json"
        AppSpecTemplateArtifact        = "production-artifacts"
        AppSpecTemplatePath            = "appspec.yml"
      }
    }
  }
}

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
What you can do with signing up
22