理系学生日記

おまえはいつまで学生気分なのか

GitLab Self-Managed用のCodeBuild-hosted GitLab RunnerをTerraformで構築する

2025/2/26に、Self-hostedなGitLabに関して、そのCI/CDを実行するGitLab RunnerをCodeBuildで動かせるようになったという発表がありました (2024/9/17に発表された[AWS CodeBuild がマネージド GitLab ランナーのサポート開始:title:bookmark]はあくまでSaaSのGitLabに対してのものでした)。

企業でGitLabを使っている場合、情報資産管理の関係でSelf-hostedなGitLabを使い情報資産を封じ込める構成をとっていることが多いと思います。このSelf-hostedなGitLabにおいても、CodeBuild-hosted GitLab Runnerを使えるようになる。これが意味するところは、Self-hostedなGitLabにおいても、CI/CDが大規模スケールするということです。たくさんのCIジョブが詰まっている諸君、良かったな!

Terraformで構築する

それでは、このCodeBuild-hosted RunnerをTerraformで構築してみましょう。 僕の環境は以下です。

$ terraform version
Terraform v1.10.3
on linux_arm64
+ provider registry.terraform.io/hashicorp/aws v5.94.1

環境前提

GitLab Self-Managedのバージョンは17.10。VPC内のprivate subnet上のEC2サーバ(Amazon Linux2)上にインストールし、インターネットからはALB経由でアクセスできるようにしています。アクセス用のFQDNは仮にgitlab.example.comとしましょう。

モジュール

まずは全体から。CodeBuild用のモジュールのmain.tfは次のようになりました。

resource "aws_codeconnections_host" "gitlab" {
  name              = "GitLab"
  provider_endpoint = "https://gitlab.example.com"
  provider_type     = "GitLabSelfManaged"

  # interface VPC Endpoint経由でアクセスできるようにする
  vpc_configuration {
    # ここは、GitLabがインストールされているVPCのID等を指定
    # これらの設定をもとに、CodeConnections が接続に利用するENIが作成される
    vpc_id             = var.vpc_id
    security_group_ids = var.security_group_ids
    subnet_ids         = var.subnet_ids
  }
}

resource "aws_codeconnections_connection" "gitlab_connection" {
  name     = var.connection_name
  host_arn = aws_codeconnections_host.gitlab.arn
}

resource "aws_codebuild_project" "gitlab_runner" {
  name               = "gitlab-runner"
  description        = "GitLab Runner for GitLab"
  service_role       = aws_iam_role.codebuild.arn
  build_timeout      = "60" # minutes
  badge_enabled      = false
  project_visibility = "PRIVATE"

  environment {
    # see: https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-compute-types.html#environment.types
    compute_type = "BUILD_GENERAL1_SMALL" # 4 GiB、2 vCPU

    # see: https://docs.aws.amazon.com/codebuild/latest/userguide/ec2-compute-images.html
    image = "aws/codebuild/standard:5.0" # Amazon Linux 2023
    type  = "LINUX_CONTAINER"
  }

  logs_config {
    cloudwatch_logs {
      status     = "ENABLED"
      group_name = aws_cloudwatch_log_group.codebuild.name
    }
  }

  source {
    auth {
      type     = "CODECONNECTIONS"
      resource = aws_codeconnections_connection.gitlab_connection.arn
    }
    type                = "GITLAB_SELF_MANAGED"
    buildspec           = var.build_spec_content
    git_clone_depth     = 1
    location            = var.gitlab_repository_url
    report_build_status = true
    # GitLabだとサポートされていないので無視される
    build_status_config {
      context = "CodeBuild / gitlab-runner"
    }
  }

  artifacts {
    type = "NO_ARTIFACTS"
  }

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

resource "aws_codebuild_webhook" "gitlab_runner" {
  project_name = aws_codebuild_project.gitlab_runner.name
  build_type   = "BUILD"

  filter_group {
    filter {
      pattern = "WORKFLOW_JOB_QUEUED"
      type    = "EVENT"
    }
  }
}

resource "aws_cloudwatch_log_group" "codebuild" {
  name              = "/aws/codebuild/gitlab_runner"
  retention_in_days = 30
  tags = {
    Name = "GitLab Runner"
  }
}

data "aws_iam_policy_document" "codebuild_assume_role" {
  statement {
    effect = "Allow"
    principals {
      type        = "Service"
      identifiers = ["codebuild.amazonaws.com"]
    }
    actions = ["sts:AssumeRole"]
  }
}

resource "aws_iam_role" "codebuild" {
  name               = "codebuild-github-runner-role"
  assume_role_policy = data.aws_iam_policy_document.codebuild_assume_role.json
}

data "aws_iam_policy_document" "codebuild_policy" {
  statement {
    effect = "Allow"
    actions = [
      "ec2:Describe*",
      "s3:GetObject",
      "s3:PutObject",
      "logs:CreateLogGroup",
      "logs:CreateLogStream",
      "logs:PutLogEvents"
    ]
    resources = ["*"]
  }

  statement {
    sid    = "UseConnection"
    effect = "Allow"
    actions = [
      "codestar-connections:UseConnection",
      "codeconnections:GetConnectionToken",
      "codeconnections:GetConnection",
    ]
    resources = ["*"]
  }
}

resource "aws_iam_role_policy" "codebuild" {
  name   = "codebuild-github-runner-policy"
  role   = aws_iam_role.codebuild.id
  policy = data.aws_iam_policy_document.codebuild_policy.json
}

色々とゴチャゴチャしていますが、重要なのは多くないです。

  1. GitLab Self-ManagedとAWS CodeBuildを接続するための接続ホスト(aws_codeconnections_host)を定義
  2. その接続ホストを使って、GitLab Self-Managedのリポジトリにアクセスするための接続(aws_codeconnections_connection)を定義
  3. その接続を利用して認証することを前提に、GitLab Self-ManagedのリポジトリをソースにするCodeBuildプロジェクト(aws_codebuild_project)を定義

これでとりあえず概形は完成ですが、実はこの状態ではTerraformの実行が失敗します。 原因は、まだ「接続」(CodeConnections)ができただけで、未認証(Pending)の状態であるからです。実際、CodeConnections(接続)のマネジメントコンソール上では「保留中」となっています。

これにより、aws_codebuild_webhookの生成が失敗し、Terraformの実行も失敗します。

Pending

CodeConnectionsの「保留中」の状態を解消するためには、当該コンソールから、apiスコープを持つGitLabのPAT(Personal Access Token)を使って認証する必要があります。

A Set up host_name page displays. In Provide personal access token, provide your GitLab PAT with the following scoped-down permission only: api. Connections for GitLab self-managed

PATで認証

この認証を完了させてから再度Terraformを実行すると、すべてのリソースの作成が成功します。

GitLab CI/CDの実行

CodeBuild側の設定が完了したので、次はGitLab側の設定です。 設定といってもgitlab-ci.ymlを作成するだけなのですが、CodeBuild特有の癖があり、GitLab CI/CDとCodeBuildを関連づけるのは各ジョブのtagsです。

hello-world-job:
  stage: test
  script:
    - echo "Hello World!"
    - echo "今日は $(date '+%Y年%m月%d日') です"
  tags:
    - codebuild-<project-name>-$CI_PROJECT_ID-$CI_PIPELINE_IID-$CI_JOB_NAME

上の指定では、<project-name>というCodeBuildプロジェクトとGitLab CI/CDジョブを紐づけています。これにより、GitLabから作成したwebhook経由でCodeBuildの対象プロジェクトにジョブが投げられ、CodeBuild上で当該ジョブが実行されます。

他にもtagsで、利用するDockerイメージ等も指定ができます。その辺りの仕様はLabel overrides supported with the CodeBuild-hosted GitLab runnerに記載があります。

  tags:
    - codebuild-myProject-$CI_PROJECT_ID-$CI_PIPELINE_IID-$CI_JOB_NAME
    - image:arm-3.0
    - instance-size:small
    - fleet:myFleet
    - buildspec-override:true

実行結果

GitLab CI/CDジョブの実行結果は、GitLabのWeb UIから確認できます。

GitLab 実行結果

一方で、パイプラインの結果が見えません。なぜなんだ。これは仕様なのか判断がついていない。

GitLab パイプライン結果

参考文献