CI/CD

デプロイメントパイプライン

Flow

  1. GithubのWebhookで変更検知
  2. Githubからソースを取得
  3. Dockerイメージをbuildしてコンテナレジストリにプッシュ
  4. コンテナレジストリからDockerイメージをPullしてECSでデプロイ

ECRリポジトリの定義

resource "aws_ecr_repository" "example" {
  name = "example"
}

ECRのライフサイクルポリシー

  • ECRリポには保存できるイメージに限りがあるため
  • releaseで始まるイメージタグを30個までに制限
resource "aws_ecr_lifecycle_policy" "example" {
  repository = aws_ecr_repository.example.name

  policy = <<EOF
  {
    "rules": [
      {
        "rulePriority": 1,
        "description": "Keep last 30 release tagged images",
        "selection": {
          "tagStatus": "tagged",
          "tagPrefixList": ["release"],
          "countType": "imageCountMoreThan",
          "countNumber": 30
        },
        "action": {
          "type": "expire"
        }
      }
    ]
  }
EOF
}

CI(continuous integration)の設定

  • DockerイメージのビルドとECRのPushをCodebuildでやる
  • CIは狭義にはビルドやテスト、インスペクションなどを継続的に実行していくこと

CodeBuildが使用するIAMロールの作成

次のポリシードキュメントを作る

  • ビルド出力アーティファクトを保存するためのS3操作権限
    • ビルド出力アーティファクトとは、Build後のコードのこと
  • ビルドログを出力するためのCloudWatch Logs操作権限
  • Docker ImageをPushするためのECR操作権限
data "aws_iam_policy_document" "codebuild" {
  statement {
    effect    = "Allow"
    resources = ["*"]

    actions = [
      "s3:PutObject",
      "s3:GetObject",
      "s3:GetObjectVersion",
      "logs:CreateLogGroup",
      "logs:CreateLogStream",
      "logs:PutLogEvents",
      "ecr:GetAuthorizationToken",
      "ecr:BatchCheckLayerAvailability",
      "ecr:GetDownloadUrlForLayer",
      "ecr:GetRepositoryPolicy",
      "ecr:DescribeRepositories",
      "ecr:ListImages",
      "ecr:DescribeImages",
      "ecr:BatchGetImage",
      "ecr:InitiateLayerUpload",
      "ecr:UploadLayerPart",
      "ecr:CompleteLayerUpload",
      "ecr:PutImage",
    ]
  }
}

サービスロールの定義

module "codebuild_role" {
  source     = "./iam_role"
  name       = "codebuild"
  identifier = "codebuild.amazonaws.com"
  policy     = data.aws_iam_policy_document.codebuild.json
}

CodeBuildのプロジェクト作成

resource "aws_codebuild_project" "example" {
  name         = "example"
  service_role = module.codebuild_role.iam_role_arn <===== さっき作成したロールを指定

  source { <============ ビルド対象のファイル
    type = "CODEPIPELINE" <=========== CODEPIPELINEの指定で、CodePipelineと連携することを宣言
  }

  artifacts {<============ ビルド後のファイル
    type = "CODEPIPELINE"  <=========== CODEPIPELINEの指定で、CodePipelineと連携することを宣言
  }

  environment { <========== ビルド環境
    type            = "LINUX_CONTAINER"
    compute_type    = "BUILD_GENERAL1_SMALL"
    image           = "aws/codebuild/ubuntu-base:14.04"
    privileged_mode = true <=========== Dockerコマンドを利用するため、特権を付与
  }
}

CodeBuildの定義ファイル(buildspec.yml)の作成

  • buildspec.ymlはCodeBuildのビルド処理を規定する
version: 0.2

phases:
  pre_build: <========== ECRにログイン
    commands:
    - $(aws ecr get-login --region $AWS_DEFAULT_REGION --no-include-email)
    - REPO=$(aws ecr describe-repositories --repository-names example --output text --query "repositories[0].repositoryUri")
    - IMAGE=$REPO:latest
  build: <=============== Dockerイメージのビルドとプッシュ
    commands:
    - docker build -t $IMAGE .
    - docker push $IMAGE
  post_build: <=========================== imagedefinitions.jsonファイルの作成
    commands:
    - printf '[{"name":"example","imageUri":"%s"}]' $IMAGE > imagedefinitions.json
artifacts:
  files: imagedefinitions.json

CD(continuous delivery)の設定

  • アプリのコードがmasterにマージされたら、ECSにデプロイされる仕組みを作る
  • CDはCIを拡張した手法で、ビルドやテストだけでなく、リリースプロセス全体を自動化すること

ポリシードキュメントの定義

  • ステージ間でデータを受け渡すためのS3操作権限
  • ECSにDockerイメージをデプロイするためのECS操作権限
  • CodeBuildやECSにロールを渡すためのPassRole権限
data "aws_iam_policy_document" "codepipeline" {
  statement {
    effect    = "Allow"
    resources = ["*"]

    actions = [
      "s3:PutObject",
      "s3:GetObject",
      "s3:GetObjectVersion",
      "s3:GetBucketVersioning",
      "codebuild:BatchGetBuilds",
      "codebuild:StartBuild",
      "ecs:DescribeServices",
      "ecs:DescribeTaskDefinition",
      "ecs:DescribeTasks",
      "ecs:ListTasks",
      "ecs:RegisterTaskDefinition",
      "ecs:UpdateService",
      "iam:PassRole",
    ]
  }
}

CodePipeline用のIAMロール

module "codepipeline_role" {
  source     = "./iam_role"
  name       = "codepipeline"
  identifier = "codepipeline.amazonaws.com"
  policy     = data.aws_iam_policy_document.codepipeline.json
}

CodePipelineの各ステージで、データの受け渡しに使用するアーティファクトのストア

resource "aws_s3_bucket" "artifact" {
  bucket = "artifact-pragmatic-terraform-on-aws"

  lifecycle_rule {
    enabled = true

    expiration {
      days = "180"
    }
  }
}

CodePipelineの定義

CodePipelineは複数のステージから構成される

  1. Sourceステージ: githubからコードを取得
  2. Buildステージ: CodeBuildを実行して、ECRにDockerイメージをPushする
  3. deployステージ: ECSへDockerイメージのデプロイ
resource "aws_codepipeline" "example" {
  name     = "example"
  role_arn = module.codepipeline_role.iam_role_arn

  stage {  <============ source stage
    name = "Source"

    action {
      name             = "Source"
      category         = "Source"
      owner            = "ThirdParty"
      provider         = "GitHub"
      version          = 1
      output_artifacts = ["Source"]

      configuration = {
        Owner                = "your-github-name"
        Repo                 = "your-repository"
        Branch               = "master"
        PollForSourceChanges = false  <============== webhookからcodepipelineの起動を行うたポーリングは無効化
      }
    }
  }

  stage { <====================== Buildステージ
    name = "Build"

    action {
      name             = "Build"
      category         = "Build"
      owner            = "AWS"
      provider         = "CodeBuild"
      version          = 1
      input_artifacts  = ["Source"]
      output_artifacts = ["Build"]

      configuration = {
        ProjectName = aws_codebuild_project.example.id
      }
    }
  }

  stage { <=================== Deployステージ。 ECSクラスタとECSサービスを指定する
    name = "Deploy"

    action {
      name            = "Deploy"
      category        = "Deploy"
      owner           = "AWS"
      provider        = "ECS"
      version         = 1
      input_artifacts = ["Build"]

      configuration = {
        ClusterName = aws_ecs_cluster.example.name
        ServiceName = aws_ecs_service.example.name
        FileName    = "imagedefinitions.json" <=============== コンテナ定義の一部を参照する
      }
    }
  }

  artifact_store {  
    location = aws_s3_bucket.artifact.id <=========== 作成したアーティファクトの保存用のS3
    type     = "S3"
  }
}

Codepipeline Webhookの作成

resource "aws_codepipeline_webhook" "example" {
  name            = "example"
  target_pipeline = aws_codepipeline.example.name
  target_action   = "Source"
  authentication  = "GITHUB_HMAC" <======== HMAC形式

  authentication_configuration {
    secret_token = "VeryRandomStringMoreThan20Byte!" <======== HMACの秘密鍵
  }

  filter { <================== CodePipelineの起動条件
    json_path    = "$.ref"
    match_equals = "refs/heads/{Branch}" <==== aws_codepipeline.example の Source.actionのBranchの意味=master
  }
}

Githubプロバイダ

  • 環境変数のGITHUB_TOKENが自動的に使用される
provider "github" {
  organization = "your-github-name"
}

Github Webhook

  • コードの変更を通知するためのWebhoook
resource "github_repository_webhook" "example" {
  repository = "your-repository"
  name       = "web"

  configuration {
    url          = aws_codepipeline_webhook.example.url
    secret       = "VeryRandomStringMoreThan20Byte!" <====== ここのsecretはauthentication_configuration.secret_tokenと同じにする
    content_type = "json"
    insecure_ssl = false
  }

  events = ["push"] <=========== pushだけではなく、pull_requestも指定できる。
}