🐲

AWS CodeBuild入門:セキュアなCIをTerraformで構築したよ

に公開

このブログを要約すると

CodeBuildでCIだけAWSに寄せてみたら、認証情報の管理がめっちゃラクになった話。
Terraformでサクッと構築して、GitHub連携やECRへのPushも試してみたよ。
CodeBuildの初体験メモとしてどうぞ。

はじめに

AWS CodeBuild(以降CodeBuild)に入門したので、以下に初感をつらつらと綴ってみます。

CodeBuildとは

GitHub ActionsではまるッとCI/CDを実現できますが、CodeBuildはCI(Continuous Integration)を切り出して、コードのビルド、テスト、コンテナ化をマネージドとして提供するサービスです。

例えばのユースケースとして、GitHub ActionsなどのAWS外のサービスでCI/CDを実装しようとなった場合に、CIの中でE2Eテストを組む際はDB認証情報などをAWS外に出す必要があります。
それをCodeBuildでは、CIをAWS内で完結できるので、IAMポリシーさえしっかり組んでいれば、認証情報を外出しせずにE2Eテストを実施できます。こうした認証情報をAWS内から出さないといったメリットがあるので、CIをCodeBuildで実装してテスト、ビルド、プッシュ(アーティファクトの格納)などを行い、CD(Continuous Derivery)をGitHub Actionsなどで行うのは良い選択肢あると感じました。

そんなCodeBuildを、今回はTerraformで組んでみました。
https://github.com/jnytnai0613/blue-green-upgrade-blueprints/tree/main/system/common/codebuild

実装

今回のCodeBuildでは、Pythonコードをdocker buildでImage化を行い、ImageをECRにPushするのみの単純なbuildspecを組んでいます。

ポリシー

CodeBuildが使用するロールに紐づくポリシーは以下の通りです。
(今回は解説を省きますが、AssumeRoleではCodeBuildを指定ください)
ECRへのログインとPush、BuildのストリームログのCloudWatchへの格納と、GitHubへの接続のためのCodeConnections取得のみをできるポリシーとしています。

data "aws_iam_policy_document" "codebuild" {
  statement {
    effect = "Allow"
    sid    = "ECRAccess"
    actions = [
      "ecr:CompleteLayerUpload",
      "ecr:UploadLayerPart",
      "ecr:InitiateLayerUpload",
      "ecr:BatchCheckLayerAvailability",
      "ecr:PutImage",
      "ecr:BatchGetImage"
    ]
    resources = [data.aws_ecr_repository.ecr_repository.arn]
  }

  statement {
    effect = "Allow"
    sid    = "ECRAuth"
    actions = [
      "ecr:GetAuthorizationToken"
    ]
    resources = ["*"]
  }

  statement {
    effect = "Allow"
    sid    = "CloudWatchLogsAccess"
    actions = [
      "logs:CreateLogGroup",
      "logs:CreateLogStream",
      "logs:PutLogEvents"
    ]
    resources = ["*"]
  }

  statement {
    effect = "Allow"
    sid    = "CodeConnectionAccess"
    actions = [
      "codeconnections:GetConnectionToken",
      "codeconnections:GetConnection"
    ]
    resources = [aws_codeconnections_connection.github.arn]
  }
}

CodeBuild

キャッシュをS3に格納したり、 CodeBuildの実態の環境を定義したりしています。今回はEC2のLinux Containerを選択しています。実行時間が長いのであればEC2、すぐに終わるのであればLambdaといったように選択するといいと思います。
さらに今回は、GitHubにあるコードを使ってCIしたいので、sourceでGitHubを選択し、かつ認証にはGitHub Appを使いたいのでCodeConnectionsを利用しています。

resource "aws_codebuild_project" "codebuild" {
  name          = local.codebuild_project_name
  description   = "practice for CI from GitHub"
  service_role  = aws_iam_role.codebuild.arn
  build_timeout = 5

  artifacts {
    type = "NO_ARTIFACTS"
  }

  cache {
    type     = "S3"
    location = aws_s3_bucket.codebuild.bucket
  }

  environment {
    # https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-compute-types.html#environment.types
    compute_type = "BUILD_GENERAL1_SMALL"
    type         = "LINUX_CONTAINER"

    # https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-available.html
    # EC2インスタンスは"aws/codebuild/ami/amazonlinux-x86_64-base:latest"や
    # "aws/codebuild/ami/amazonlinux-arm-base:latest"のみ。基本的には自前でビルドが必要。
    image                       = "aws/codebuild/standard:6.0" # Ubuntu 22.04
    image_pull_credentials_type = "CODEBUILD"
  }

  source {
    type                = "GITHUB"
    location            = "https://github.com/jnytnai0613/blue-green-upgarade-blueprints"
    report_build_status = true
    buildspec           = "system/assets/sample-app/container/buildspec.yml"
    auth {
      type     = "CODECONNECTIONS"
      resource = aws_codeconnections_connection.github.arn
    }
  }

  logs_config {
    cloudwatch_logs {
      group_name  = "buildlog"
      stream_name = "log-stream"
    }
  }
}

Webhook

今回はGitHubで発生するイベントをトリガーに、buildspec.ymlに定義したCIを実行したいです。そのため、WebhookではPull Requestのマージをトリガーとしています。さらに、対象リポジトリにはTerraformコードもあるので、アプリケーションコードのみが変更された場合にトリガーしたいのでパスでfilterをかけています。

resource "aws_codebuild_webhook" "github" {
  project_name = aws_codebuild_project.codebuild.name
  build_type   = "BUILD"
  filter_group {
    filter {
      type    = "EVENT"
      pattern = "PULL_REQUEST_MERGED"
    }

    filter {
      type    = "FILE_PATH"
      pattern = "system/assets/sample-app/container/.*"
    }
  }
}

また、このresourceをapplyすることでGitHubのSettings/WebhooksにWebhook定義が生えてくるのが確認できるかと思います。

ちなみにこのWebhook、少々applyにコツが要ります。
CodeBuildと一緒にデプロイしようとすると、CodeBuildの定義内の以下個所でARNが無効のエラーが発生します。これはCodeConnectionsはGitHub AppをGitHubにインストールするのですが、それまでは、接続が保留されるためです。そのため、保留中のCodeConnectionsを指定したCodeBuildにWebhookを紐づけようとすると、Webhook側で無効なARNが検出されるというわけです。
WebhookのapplyまでにはCodeConnectionsを承認しましょう

  source {
    type                = "GITHUB"
  ;
    auth {
      type     = "CODECONNECTIONS"
      resource = aws_codeconnections_connection.github.arn
    }
  }

buildspec.yml

なんてことはないDocker Imageを作成してECRへPushするだけのスクリプトです。

version: 0.2

env:
    variables:
        IMAGE_REPO_NAME: blue-green-ecr

phases:
    pre_build:
        commands:
            - export IMAGE_TAG=$(TZ=Asia/Tokyo date '+%Y%m%d%H%M%S')
            - export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output=text)
            - 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
    build:
        commands:
            - docker build system/assets/sample-app/container -t $IMAGE_REPO_NAME:$IMAGE_TAG
            - docker tag docker.io/library/$IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
    post_build:
        commands:
            - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG

さいごに

今まで個人や業務ではGitHub ActionsでCI/CDを実装してきましたが、AWSのマネージドサービスであるCodeBuildでセキュアなCIが実装できるのは新たな扉を開いた感があります。
これを応用すると、AWSで完結するE2EテストのCI実装や、SQL変更とDBの問い合わせ結果の突合などをPRにコメントするCIなどできそうです。
Terraformでは上記のコードを利用すればある程度実装できるかと思いますので、ぜひやってみてください!

Discussion