はじめに
GitHub actions にはセルフホステッドランナー(Self-Hosted Runner)と呼ばれる機能があります。
通常 GitHub actions のワークフローは GitHub 側が用意してくれたホストで実行されます。
一方セルフホステッドランナー(Self-Hosted Runner)は、GitHub Actions のワークフローをユーザーが用意したホストにできます。
セルフホステッドランナーを使うことにより、ユーザーが実行ホストを詳細にカスタマイズできたり、ワークフロー実行時にかかるランニングコストを抑えることができます。
調べてみると、この環境を Terraform で構築できるモジュールが見つかったので、実際に使ってみました。
構築中いくつかつまりポイントなどがあったので、それらも含めて紹介していきます。
terraform-aws-github-runner とは
Terraform で GitHub actions の実行環境を手軽に構築することができるモジュールです。
もともとは PHILIPS 社(シェーバーや車のライトで有名なメーカー)が開発・管理されていました。
こちらのリポジトリは 2025 年 1 月 16 日にアーカイブされており、現在は上記のリポジトリに移ったみたいです。
アーキテクチャ
こちらは公式のアーキテクチャ図です。
詳しいアーキテクチャが気になったので、少し調べてみました。
- GitHub actions のワークフロー開始をトリガーとして webhook を実行
- AWS はそのリクエストを受取、Spot Instance の作成
- 作成された Spot Instance は GitHub Actions にセルフホステッドランナーとして登録
- GitHub actions はセルフホステッドランナーが登録されたことを検知し、ワークフローを実行
アーキテクチャもわかったので、実際にこれを使って構築してみます。
構築手順
以下の流れで構築をしていきます。
- GitHub App を作成
- Terraform で AWS 環境を構築
- GitHub App に AWS のエンドポイントを登録
- 必要なリポジトリへインストール
GitHub App の作成
ここでは大きく2つ作成します。
- GitHub App の作成
- 秘密鍵の作成
GitHub App の作成
https://github.com/settings/apps/new から作成できます。
各設定値は以下の通りです。
- GitHub App name:任意の名前
- Homepage URL:https://github-aws-runners.github.io/terraform-aws-github-runner/getting-started/#setup-guide(必須なので書いていますが、おそらくなんでも大丈夫)
- Webhook:Active のチェックを外す(あとで有効化するので一旦外しておきます)
- Permissions(すべて Repository permissions)
- Actions:Read-only
- Administration:Read and write(個人のリポジトリの場合)
- Checks:Read-only
- Metadata:Read-only
上記の設定が終わったら「Create GitHub App」を選択します。
「App ID」は Terraform で使うので控えておきましょう。
秘密鍵の作成
「Private keys」で秘密鍵を発行します。
ボタンは App の General カテゴリにあります。
Terraform で AWS 環境を構築
ディレクトリ構成は以下の通りです。
.
├── lambdas-download
│ ├── runner-binaries-syncer.zip
│ ├── runners.zip
│ └── webhook.zip
├── main.tf
└── 秘密鍵.pem
lambdas-download の下の zip ファイルは公式 Releases からダウンロードします。
秘密鍵.pem はさきほど GitHub App で作成した秘密鍵を配置する。
locals {
key_base64 = base64encode(file("./秘密鍵.pem"))
}
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.27"
}
random = {
source = "hashicorp/random"
version = "~> 3.0"
}
local = {
source = "hashicorp/local"
version = "~> 2.0"
}
}
required_version = ">= 1.3.0"
}
provider "aws" {
region = "ap-northeast-1"
}
resource "random_id" "random" {
byte_length = 20
}
# Create a VPC
resource "aws_vpc" "test" {
cidr_block = "10.0.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "test-vpc"
}
}
# Create an Internet Gateway
resource "aws_internet_gateway" "test_igw" {
vpc_id = aws_vpc.test.id
tags = {
Name = "test-igw"
}
}
# Create a Public Route Table
resource "aws_route_table" "test_public_rt" {
vpc_id = aws_vpc.test.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.test_igw.id
}
tags = {
Name = "test-public-rt"
}
}
# Associate Public Route Table with Subnet 1
resource "aws_route_table_association" "test_subnet_1_association" {
subnet_id = aws_subnet.test_subnet_1.id
route_table_id = aws_route_table.test_public_rt.id
}
# Associate Public Route Table with Subnet 2
resource "aws_route_table_association" "test_subnet_2_association" {
subnet_id = aws_subnet.test_subnet_2.id
route_table_id = aws_route_table.test_public_rt.id
}
# Create Subnet 1
resource "aws_subnet" "test_subnet_1" {
vpc_id = aws_vpc.test.id
cidr_block = "10.0.1.0/24"
availability_zone = "ap-northeast-1a"
map_public_ip_on_launch = true
tags = {
Name = "test-subnet-1"
}
}
# Create Subnet 2
resource "aws_subnet" "test_subnet_2" {
vpc_id = aws_vpc.test.id
cidr_block = "10.0.2.0/24"
availability_zone = "ap-northeast-1c"
map_public_ip_on_launch = true
tags = {
Name = "test-subnet-2"
}
}
module "github-runner" {
source = "github-aws-runners/github-runner/aws"
version = "~> 6.0.0"
aws_region = "ap-northeast-1"
vpc_id = aws_vpc.test.id
subnet_ids = [aws_subnet.test_subnet_1.id, aws_subnet.test_subnet_2.id]
prefix = "gh-ci"
github_app = {
key_base64 = local.key_base64
id = "{さきほど取得したAppを入れる}"
webhook_secret = "webhook_secret"
}
webhook_lambda_zip = "lambdas-download/webhook.zip"
runner_binaries_syncer_lambda_zip = "lambdas-download/runner-binaries-syncer.zip"
runners_lambda_zip = "lambdas-download/runners.zip"
enable_organization_runners = false
}
output "runners" {
value = {
lambda_syncer_name = module.github-runner.binaries_syncer.lambda.function_name
}
}
output "webhook_endpoint" {
value = module.github-runner.webhook.endpoint
}
output "webhook_endpoint" {
value = module.github-runner.webhook.endpoint
}
output "webhook_secret" {
value = random_id.random.hex
}
AWS へのデプロイ権限があるターミナルでデプロイします。
terraform init
terraform plan
terraform apply
デプロイが完了すると、webhook_url と webhook_secret がコンソールに出力されるので控えておきます。
Apply complete! Resources: 101 added, 0 changed, 0 destroyed.
Outputs:
runners = {
"lambda_syncer_name" = "gh-ci-syncer"
}
webhook_endpoint = "https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/webhook"
webhook_secret = xxxxxxxx
GitHub App に AWS のエンドポイントを登録
やることは2つです。
- Webhook URL の登録
- Event の設定
Webhook URL の登録
GitHub App の General で Webhook URL を以下のように設定します。
- Active:チェック
- Webhook URL:先ほど Terraform で発行した内容
- WebhookSecret:先ほど Terraform で発行した内容
「Save Changes」をクリックする。
Advanced で ping が通ったログを確認できればOKです。
Event の設定
ping が通ると Permissioin & events に Subscribe to events が追加されるので以下の項目にチェックを入れて保存します。
- Workflow job
※公式ドキュメントだと「Workflow job か Check run のどちらか片方」と書いてあったが、自分の場合 Workflow job でないと動きませんでした。
必要なリポジトリへインストール
Install App で自分のアカウントを選択します。
Only select repositories でセルフホステッドランナーを使いたいリポジトリを選択します。
選択後、Install をクリックします。
ここまでで構築は完了です。
動かしてみる
最初はランナーが登録されていない状態です。
以下のワークフローを実行してみます。
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
jobs:
build:
runs-on: self-hosted
steps:
- uses: actions/checkout@v3
- name: Run a one-line script
run: echo Hello, world!
実行してしばらくすると、セルフホステッドランナーが追加されています。
ワークフローが動いていることも確認できました。
まとめ
今回は GitHub Actions のセルフホステッドランナーを terraform-aws-github-runner モジュールを使って AWS 上に構築する方法を解説しました。
実際に構築完了後、ワークフローを実行することで、自動的に AWS 上に Spot Instance が作成され、GitHub Actions のセルフホステッドランナーとして登録・動作することを確認しました。
セルフホステッドランナーを使うことで、実行環境を詳細にカスタマイズできるだけでなく、ランニングコストの削減も可能になります。
また、ワークフロー実行時だけインスタンスが立ち上がるため、コスト効率の良い CI/CD 環境を実現できます。
みなさんもぜひ terraform-aws-github-runner を使ってみてください。
参考