https://docs.aws.amazon.com/codebuild/latest/userguide/action-runner.html
に記載のある、AWS CodeBuildをGitHub ActionsのSelf-hosted Runnerとして使用可能な機能を試します。
確認時点では英語のドキュメントのみあり、日本語版ではページごとありませんでした。
2024-05-14時点での内容です。最新情報は上記ドキュメントを参照ください。また実際に動かしてみるのが一番わかると思います。
普段はGitHub ActionsをGitHub-hosted Runnerで動かしているので、CodeBuildにはあまり慣れていません。CodeBuild使っていれば常識的な部分もあるかもしれません。
2024-12-03時点の内容にアップデートした記事を投稿しました。
https://qiita.com/k-kojima-yumemi/items/573bda88d0fb607b3224
この記事の内容も有効ですが、書き方が新しくなっている箇所などがあります。
基本的な使い方
AWS CodeBuild上でGitHubに接続設定したプロジェクトを用意し、その名前をGitHub Actionsのyamlのruns-onに記載することで動作します。
Terraformで表すと以下のような設定です。
resource "aws_codebuild_project" "main" {
name = "github_runner"
service_role = aws_iam_role.cb.arn
project_visibility = "PRIVATE"
badge_enabled = false
artifacts {
type = "NO_ARTIFACTS"
}
source {
buildspec = <<-EOF
version: 0.2
phases:
build:
commands:
- echo "Hello"
EOF
git_clone_depth = 1
insecure_ssl = false
location = "https://github.com/OWNER/REPOSITORY.git"
report_build_status = false
type = "GITHUB"
git_submodules_config {
fetch_submodules = false
}
}
environment {
compute_type = "BUILD_LAMBDA_2GB"
image = "aws/codebuild/amazonlinux-x86_64-lambda-standard:nodejs20"
image_pull_credentials_type = "CODEBUILD"
type = "LINUX_LAMBDA_CONTAINER"
}
logs_config {
cloudwatch_logs {
status = "ENABLED"
group_name = aws_cloudwatch_log_group.main.id
}
}
}
resource "aws_codebuild_webhook" "main" {
project_name = aws_codebuild_project.main.name
build_type = "BUILD"
filter_group {
filter {
pattern = "WORKFLOW_JOB_QUEUED"
type = "EVENT"
}
}
}
Self-hosted Runnerとして動かすためには、CodeBuildのWebhookで WORKFLOW_JOB_QUEUED
のイベントを受け取るように設定します。
この設定があるプロジェクトがSelf-hosted Runnerとして認識されるようです。
コンソールでは以下のような設定になります。
buildspecについては、Self-hosted Runnerとして稼働させる場合無視されます。そのため適当なbuildspecを入れるようにしています。リポジトリ内のbuildspec.yamlを使用する設定になっていても問題ありません。
コンソールから作成した場合にはリポジトリ内のbuildspec.yamlを使用する設定になっているようです。
上記で作成した、 github_runner
の名前のプロジェクトを使用するために、 GitHub Actionsのyamlに以下のように記述します。
jobs:
build:
runs-on: codebuild-github_runner-${{ github.run_id }}-${{ github.run_attempt }}
詳しくはドキュメントに記載がありますが、 codebuild-<プロジェクト名>-${{ github.run_id }}-${{ github.run_attempt }}
と記載すると動きます。
この記載ではCodeBuildに設定されているランタイムで動作します。上の例ではx86_64のnodejs20がLambda上で動作します。
GitHub ActionsからAWSにアクセスする際にはOIDCを使用されるかと思いますが、OIDCによる接続は問題なく動作します。
例えば以下のjobであれば1回目の aws sts get-caller-identity
ではCodeBuild自身の、Assume Roleした後の2回目の呼び出しではAssume Roleした先のロールが表示されることを確認しました。
CodeBuild実行ロールとAssume Roleするロールに特別な設定は不要で、GitHub-hosted Runnerで実行する時と同じように使えます。
oidc:
timeout-minutes: 5
runs-on: codebuild-RUNNER-${{ github.run_id }}-${{ github.run_attempt }}
permissions:
id-token: write
contents: read
steps:
- run: aws sts get-caller-identity # CodeBuildの実行ロールが表示される
- uses: aws-actions/configure-aws-credentials@v4
with:
role-session-name: 'Session'
aws-region: 'ap-northeast-1'
role-to-assume: ${{ secrets.AWS_ARN }}
- run: aws sts get-caller-identity # Assume Roleしたロールが表示される
ランタイムの変更
runs-on
に特殊な記述をすることで実行するランタイムを変更できます。
EC2インスタンスを使用する場合は以下のように設定できます。image等のパラメータはドキュメントを参照してください。
runs-on: codebuild-<project-name>-${{ github.run_id }}-${{ github.run_attempt }}-<image>-<image-version>-<instance-size>
Lambdaを使用する場合は以下の設定になります。こちらもパラメータはドキュメントを参照してください。
runs-on: codebuild-<project-name>-${{ github.run_id }}-${{ github.run_attempt }}-<environment-type>-<runtime-version>-<instance-size>
このランタイムの設定はCodeBuildのプロジェクトの設定を上書きします。
もともとEC2を使用するように設定されていても、runs-onがLambdaの記法になっていればLambdaが使用されます。
このことから、AWSで提供されているイメージを使用する場合CodeBuildのプロジェクトは1つでよく、runs-onでランタイムやスペックを調整する運用がいいと思われます。
Lambdaを使用する際には使い物にならないイメージもあります。注意点の欄を参照ください。
カスタムイメージの使用
CodeBuildではEC2、Lambdaともにカスタムイメージを設定できます。
このカスタムイメージでもGitHub Self-hosted Runnerを動かすことが可能です。
制約として、curlかwgetがイメージ内に必要になります。 ubuntu:jammy
のようなイメージを使用してしまうとRunnerのインストールができずに実行が失敗します。
node:20
のようなイメージであれば問題ありません。
Lambdaで使用する際にはECRにあるイメージに限定されますが、Amazon ECR Public Galleryのdocker/library配下に基本的なイメージがあるのでそこまで困ることはないと思います。
この場合上記のランタイムの変更はできません。Webhookで以下のエラーが出ます。
{"message":"Invalid input: cannot use a CodeBuild curated image with imagePullCredentialsType SERVICE_ROLE"}
CIが起動せずずっとwaitingの状態になります。
注意点
起動が始まらない場合
実行が始まらない場合はCodeBuildのログか、ドキュメントにある通りGitHubのWebhookの記録を見ると解決できることがあります。
例えばランタイムを変更して使用している場合、存在しないランタイムを指定するとCodeBuild側がWebhookの呼び出しに対して400を返します。
そうするとActionsの実行画面ではずっと起動待ちの状態になります。
Waiting for a runner to pick up this job...
Self-hosted runnerなので特に課金が発生したりはしませんが、実行中だと再実行もできないのでjob単位でtimeoutを設定しておくのがいいと思います。
標準では6時間と設定されているので適当な長さを指定してください。
Lambdaのイメージ
AWSではdotnet6やGo、Java、nodeなどのLambdaのイメージがCodeBuild用に用意されていますが、CIでよく使われるであろう actions/checkout
が動かないイメージがありました。
EC2での実行ではどのランタイムでも動作を確認しました。
actions/checkout
が動作するイメージ
- corretto21
- nodejs20
- python3.12
(各言語のバージョンについては調査していません)
actions/checkout
が動作しなかったイメージ
- dotnet6
- go1.21
- ruby3.2
これらのイメージではx86_64(linux-lambda)でもaarch64(arm-lambda)でも失敗します。
実行したところ下記メッセージが表示され失敗しました。
/tmp/codebuild/output/src652/src/7a41e58b_4d64_4177_8f5d_5e169f2e7544/actions-runner/externals/node20/bin/node: /lib64/libm.so.6: version `GLIBC_2.27' not found (required by /tmp/codebuild/output/src652/src/7a41e58b_4d64_4177_8f5d_5e169f2e7544/actions-runner/externals/node20/bin/node)
/tmp/codebuild/output/src652/src/7a41e58b_4d64_4177_8f5d_5e169f2e7544/actions-runner/externals/node20/bin/node: /lib64/libc.so.6: version `GLIBC_2.28' not found (required by /tmp/codebuild/output/src652/src/7a41e58b_4d64_4177_8f5d_5e169f2e7544/actions-runner/externals/node20/bin/node)
そのうち動作するようになるかもしれませんが、2024-05-14時点では動作しませんでした。
これらの言語を使用する際にはEC2を使用するか、setup-**のactionsを使用する必要があります。
setup-**のactionsが動作するかは言語によるので、調査の必要があります。
AWSで提供されているLambdaはAmazonLinuxなので、その辺りも考慮の必要があります。
Lambdaにはこちらで記述のある制約が存在します。
特にパッケージマネージャーは特権を必要とする場合使用できないので注意が必要です。
あとdockerもLambdaでは使用できないのでその場合はEC2を使うことになります。
Lambdaのカスタムイメージ
CodeBuildの環境タイプをARM Lambdaにしている場合、node:20のイメージの起動ができませんでした。
Linux Lambdaに変更したら起動するようになったので、x86_64のイメージを使用しているような挙動でした。
そのためmanifestを使用して同じタグで複数のアーキテクチャに対応しているようなイメージはLambdaのARMでは使用できない可能性があります。AWS側の変更でそのうち修正されるのかもしれません。
この場合CodeBuild側のフェーズ詳細の欄に以下のログがありました。
CLIENT_ERROR: Image public.ecr.aws/docker/library/node:20 architecture mismatch: exec format error
Organizationで使用する場合
この機能ではリポジトリごとのWebhookを利用してCodeBuildを起動させています。そのためリポジトリごとにSelf-hosted Runnerを使用可能になっている必要があります。
Organizationによっては、OrgレベルでのSelf-hosted Runnerは許可されていてもリポジトリレベルでは許可されていないことがあります。
その場合CodeBuildのログに400や404でGitHubと通信できなかったと表示されます。
リポジトリレベルでは許可されていなかった際には以下のログが表示されました。
[Container] 2024/05/14 06:09:18.935404 Running on CodeBuild On-demand
[Container] 2024/05/14 06:09:18.935416 Waiting for agent ping
[Container] 2024/05/14 06:09:19.136591 Waiting for DOWNLOAD_SOURCE
[Container] 2024/05/14 06:09:19.566743 Phase is DOWNLOAD_SOURCE
[Container] 2024/05/14 06:09:19.567558 CODEBUILD_SRC_DIR=/codebuild/output/src3911739186/src
[Container] 2024/05/14 06:09:19.567607 YAML location is /codebuild/readonly/buildspec.yml
[Container] 2024/05/14 06:09:19.571112 Processing environment variables
[Container] 2024/05/14 06:09:19.796892 No runtime version selected in buildspec.
[Container] 2024/05/14 06:09:19.896397 Error while fetching Github runner token: error code 400: Runner token unavailable: POST https://api.github.com/repos/OWNER/REPOSITORY/actions/runners/registration-token: 404 Not Found []
[Container] 2024/05/14 06:09:19.896412 Phase complete: DOWNLOAD_SOURCE State: FAILED
[Container] 2024/05/14 06:09:19.896423 Phase context status code: CLIENT_ERROR Message: Error while fetching Github runner token: error code 400: Runner token unavailable: POST https://api.github.com/repos/OWNER/REPOSITORY/actions/runners/registration-token: 404 Not Found []
ちなみにGitHubに接続する設定が未設定だと以下のログになります。
[Container] 2024/04/26 10:35:19.366788 YAML location is /tmp/codebuild/readonly/buildspec.yml
[Container] 2024/04/26 10:35:19.367037 Processing environment variables
[Container] 2024/04/26 10:35:19.368519 Error while fetching Github runner token: 400 error calling github/runnerToken: 400 Bad Request
[Container] 2024/04/26 10:35:19.429987 Runtime error (*clienterr.PhaseContextError: Error while fetching Github runner token: 400 error calling github/runnerToken: 400 Bad Request)
actions/cache
の使用
EC2のCodeBuildで、Dockerでビルドしたイメージをcacheに保存していたのですが、よく actions/cache/restore@v4
や actions/cache/save@v4
の実行中にRunnerが終了してしまうことがありました。
CodeBuildのログにはなにもなく、フェーズ詳細の欄に Internal Service Error: CodeBuild is experiencing issues
と記載がありました。
うまくいくときも稀にあるのですが、失敗パターンの方が多くcacheを使わない方法を検討しています。
ライタイムをUbuntuからAmazonLinux2023に変更して試してみましたが同じ箇所で失敗していました。
Javaのコンパイルをする際にGradleのキャッシュを保存するようにしていたのですが、そちらでは時間はかかるものの特に問題なく動作していたので環境依存なのかもしれません。
ghの使用
ARMのLambdaを使用してさくっとPRやIssueの管理をしたい場合があります。
Lambdaは1秒単位の課金で、ARMであればGitHub-HostedのRunnerよりもかなりコストが低いので小さいスクリプトを実行する際には重宝します。
その時非常に便利なのがghなのですが、Lambdaでは標準で入っていないので動作しません。
dnfコマンドも動作しないので、実行可能なバイナリをインストールする必要があります。
例えば以下のコマンドです。
gh_url=$(curl https://api.github.com/repos/cli/cli/releases/latest | jq -r '.assets | .[] | select(.name | endswith("arm64.tar.gz")) | .browser_download_url')
curl -L -o /tmp/gh.tar.gz "${gh_url}"
mkdir /tmp/gh
tar -xvzf /tmp/gh.tar.gz -C /tmp/gh
echo "$(dirname $(ls /tmp/gh/**/bin/gh))" >> $GITHUB_PATH
dockerコンテナが残り続ける?
EC2のCodeBuildでコンテナを起動するCIを実行した後、同じCIを再実行すると同じ名前のコンテナがあるという理由でコンテナ立ち上げに失敗することがありました。
その後再実行しても同じ現象には当たらなかったので稀にしか発生しないのかもしれません。
composeを使用していたので最後に docker compose down
を入れるようにしました。
終わりに
AWS CodeBuildでSelf-hosted Runnerを使用できる機能を試しました。
やや動作が不安定だったりする箇所もありましたが、GitHub Actionsでやっていたちょっとしたことを済ませるには経済的かなと思いました。
LambdaのARMであれば一番低いスペックで \$0.00001/s (\$0.0006/min)となります。課金は秒単位です。
GitHub-hostedでは\$0.008/minになり、分単位の課金なので軽いタスクをやらせるだけであればLambdaのほうが経済的と判断できます。