AWS CodeBuildの様々な実行環境をTerraformで作成する

# はじめに
2024年3月に、AWS CodeBuildの実行環境としてAWS Lambdaが使用できるようになった。

https://aws.amazon.com/jp/about-aws/whats-new/2023/11/aws-codebuild-lambda-compute/

CodeBuildが実行されるコンテナのベースイメージとしてカスタムイメージは以前から使えたが、上記アップデートによりEC2 or Lambdaと、マネージドイメージ or カスタムイメージといった組み合わせが定義できるようになっている。

それぞれ用途に応じて何を選択するかを決めれば良いが、種類が増えてきているので、今回、上記の2×2通りの組み合わせのコードスニペットを用意しておく。

本記事の前提知識としては以下の通り。

- AWS CodeBuildの基本的な概念
- TerraformにおけるAWS CodeBuildの基本的な使い方

:::note
せっかく4種類のコードスニペットを書くのだから、起動時間の比較をしてみようと思ったが、今回利用した```aws/codebuild/amazonlinux2-x86_64-standard:5.0```や```aws/codebuild/amazonlinux-x86_64-lambda-standard:go1.21```のイメージでは、起動時間に有意な差はあまり出なかった。
:::

# 事前準備
今回、以下のようにカスタムイメージ作成用のECRを作っておこう。

```HCL
resource "aws_ecr_repository" "example" {
  name = local.ecr_repository_name
}
```

なお、都度IAM側にカスタムイメージをPullするための設定を入れるのが面倒な場合は、ECRのリソースポリシーに以下を設定しておくと楽になる。これはCodeBuildのサービスロール側で設定してもOKなので、お好みで。conditionは忘れず設定しておこう。

```HCL
resource "aws_ecr_repository_policy" "example" {
  repository = aws_ecr_repository.example.name
  policy     = data.aws_iam_policy_document.example.json
}

data "aws_iam_policy_document" "example" {
  statement {
    effect = "Allow"

    principals {
      type = "Service"
      identifiers = [
        "codebuild.amazonaws.com",
      ]
    }

    actions = [
      "ecr:GetDownloadUrlForLayer",
      "ecr:BatchGetImage",
      "ecr:BatchCheckLayerAvailability",
    ]

    condition {
      test     = "StringEquals"
      variable = "aws:SourceAccount"
      values   = [data.aws_caller_identity.current.account_id]
    }
  }
}
```

# コンピューティングタイプとCodeBuild実行イメージ
この後のコードスニペットで利用するコンピューティングタイプとCodeBuild実行イメージは、以下のページを参照する。

- コンピューティングタイプ

https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/build-env-ref-compute-types.html

- CodeBuild実行イメージ

https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/build-env-ref-available.html

# コードスニペット
## EC2 / マネージドイメージ
オーソドックスな組み合わせだと言えるだろう。
今回、AWS CodePipelineからAmazon S3経由でインプットアーティファクトをもらう前提としているため、以下のような書き方になっている。

```HCL
resource "aws_codebuild_project" "ec2_managedimage" {
  name         = local.codebuild_project_name_ec2_managedimage
  service_role = aws_iam_role.codebuild_ec2_managedimage.arn

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

  artifacts {
    type = "CODEPIPELINE"
  }

  environment {
    type            = "LINUX_CONTAINER"
    compute_type    = "BUILD_GENERAL1_SMALL"
    image           = "aws/codebuild/amazonlinux2-x86_64-standard:5.0"
    privileged_mode = "true"
  }

  logs_config {
    cloudwatch_logs {
      group_name = aws_cloudwatch_log_group.codebuild.name
    }
  }

  cache {
    type = "LOCAL"
    modes = [
      "LOCAL_CUSTOM_CACHE",
    ]
  }
}
```

なお、上記の要件を満たすIAMポリシーは以下のようになる。
アーティファクトのAmazon S3からのソース情報取得と、Amazon CloudWatch Logsへの出力をするための記述なので、CodeCommitを使用する場合はAmazon S3のステートメントに代わりにCodeCommitに合わせたポリシーを設定しておこう。

```HCL
resource "aws_iam_role_policy" "codebuild_ec2_managedimage" {
  role   = aws_iam_role.codebuild_ec2_managedimage.id
  policy = data.aws_iam_policy_document.codebuild_custom_ec2_managedimage.json
}

data "aws_iam_policy_document" "codebuild_custom_ec2_managedimage" {
  statement {
    effect = "Allow"

    actions = [
      "logs:CreateLogStream",
      "logs:PutLogEvents",
    ]

    resources = [
      "${aws_cloudwatch_log_group.codebuild.arn}:*",
    ]
  }
  statement {
    effect = "Allow"

    actions = [
      "s3:GetObject",
    ]

    resources = [
      "${aws_s3_bucket.example.arn}/*",
    ]
  }
}
```

## EC2 / カスタムイメージ
カスタムイメージを使用する場合は、予めAmazon ECRにカスタムイメージを入れておいたら、あとは```image```をAmazon ECRのURIの形式に変更すれば良い。

```diff_HCL
resource "aws_codebuild_project" "ec2_customimage" {
  // (中略)
  environment {
    type            = "LINUX_CONTAINER"
    compute_type    = "BUILD_GENERAL1_SMALL"
-   image           = "aws/codebuild/amazonlinux2-x86_64-standard:5.0"
+   image           = "${aws_ecr_repository.example.repository_url}:latest"
    privileged_mode = "true"
  }
  // (中略)
}
```

なお、IAMは以下の内容を追記する。Amazon ECRのリソースポリシーで設定してある場合は、```ecr:GetAuthorizationToken```のステートメントだけ追加すれば良い。

```diff_HCL
data "aws_iam_policy_document" "codebuild_custom_ec2_customimage" {
  // (中略)
+ statement {
+   effect = "Allow"
+
+   actions = [
+     "ecr:GetAuthorizationToken",
+   ]
+
+   resources = [
+     "*",
+   ]
+ }
+ statement {
+   effect = "Allow"
+
+   actions = [
+     "ecr:GetDownloadUrlForLayer",
+     "ecr:BatchGetImage",
+     "ecr:BatchCheckLayerAvailability",
+   ]
+
+   resources = [
+     aws_ecr_repository.example.arn,
+   ]
+ }
  // (中略)
}
```

ECRへのリソースベースのポリシー設定方法は、AWS CodeBuildユーザーガイドの以下の項目が詳しいので、興味がある方は参照していただきたい。

https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/sample-ecr.html

## Lambda / マネージドイメージ
Lambdaのマネージドイメージは、EC2のマネージドイメージと以下の差分がある。
Lambdaの実行環境ではキャッシュがサポートされないので注意が必要だ。
また、```privileged_mode```が、Lambdaのコンピューティング環境ではDocker in Dockerのようなことができないらしく、```privileged_mode```を設定しようとしたらエラーになった。

```diff_HCL
resource "aws_codebuild_project" "lambda_managedimage" {
  // (中略)
  environment {
-   type            = "LINUX_CONTAINER"
+   type            = "LINUX_LAMBDA_CONTAINER"
-   compute_type    = "BUILD_GENERAL1_SMALL"
+   compute_type    = "BUILD_LAMBDA_4GB"
    image           = "aws/codebuild/amazonlinux-x86_64-lambda-standard:go1.21"
-   privileged_mode = "true"
+   privileged_mode = "false"
  }
  // (中略)
- cache {
-   type = "LOCAL"
-   modes = [
-     "LOCAL_CUSTOM_CACHE",
-   ]
- }
}
```

なお、IAMについてはEC2のマネージドイメージとの差分は無い。

## Lambda / カスタムイメージ
Lambdaのカスタムイメージは、Lambdaのマネージドイメージをベースに以下の変更を行う。
image_pull_credentials_typeについては、デフォルトの```CODEBUILD```が設定不可能であるため、```SERVICE_ROLE```に変更する。

```diff_HCL
resource "aws_codebuild_project" "lambda_customimage" {
  // (中略)
  environment {
    type                        = "LINUX_LAMBDA_CONTAINER"
    compute_type                = "BUILD_LAMBDA_4GB"
-   image                       = "aws/codebuild/amazonlinux-x86_64-lambda-standard:go1.21"
+   image                       = "${aws_ecr_repository.example.repository_url}:latest"
    privileged_mode             = "false"
+   image_pull_credentials_type = "SERVICE_ROLE"
  }
  // (中略)
}
```

IAMについては、EC2のカスタムイメージと同じで良い。

これで、CodeBuildの使い方の幅が拡がったのではないだろうか。