【GitHub Actions】Ephemeral RunnersがCI/CDの常識を変える:環境汚染ゼロ・コスト極小化を実現する一時的インフラの構築術
テックリードの私たちが日々の開発で直面する最大のフラストレーションの一つは、「なぜかローカルでは動くのにCIで落ちる」という謎の現象と、肥大化し続けるCI/CDインフラの維持費だ。
セルフホステッドランナー(Self-hosted runner)をオンプレミスやクラウドの常駐VMで運用しているチームは多いだろう。だが、ちょっと待ってほしい。そのランナー、「前回のジョブのゴミ」を引きずっていないか?
- npmキャッシュの残骸によるビルドの不整合
- テスト実行時に作成されたデータベースのゴミデータ
- 最悪の場合、前回のジョブで仕込まれた不正なファイルの残留(環境汚染)
そして何より、夜間や週末もアイドル状態のまま課金され続けるクラウドのCPUとメモリ。この無駄を根絶し、セキュリティとコスト効率を極限まで高める唯一の解が 「Ephemeral Runners(エフェメラル・ランナー)」 である。
今回は、GitHub Actionsにおけるエフェメラルランナーの設計思想から、AWS/Terraformを用いた動的プロビジョニング、そして確実な自動クリーンアップの全貌を、実戦投入可能なコードと共に解説する。
—
1. なぜ「常駐型」ランナーは悪なのか?
GitHubが提供する `ubuntu-latest` などのGitHub-hosted runnerは、毎回クリーンな環境が提供されるため安全だが、以下の制約がある。
- 実行時間が長くなるとコストがかさむ
- 社内VPC内のリソース(DBやプライベートAPI)にアクセスするためには、複雑なVPNやパブリックIPの設定が必要
- IPアドレスが固定されないため、セキュリティグループの管理が地獄
これを解決するためにセルフホステッドランナーを導入するわけだが、単にEC2インスタンスを常時起動しておくと、今度は「状態の維持(State)」という魔物が牙を剥く。
エフェメラル(使い捨て)ランナーという思想
エフェメラルランナーとは、「1つのジョブを実行するためだけに爆誕し、ジョブが終了した瞬間(成功しようが失敗しようが)に跡形もなく消滅する」ランナーのことだ。
GitHub Actionsのランナーアプリケーションには、起動時に `–ephemeral` フラグを渡すことで、「ちょうど1つのジョブを消化した瞬間に自動登録解除(deregister)してシャットダウンする」というネイティブな機能が備わっている。
このアーキテクチャが生み出すメリットは計り知れない:
1. 環境汚染リスクが完全ゼロ: 次のジョブは必ずまっさらな環境で実行される。
2. セキュリティの担保: マルウェアや不正なスクリプトが万が一ランナー内で実行されても、ジョブ終了と共に環境ごと消える。
3. コストの最適化: ジョブが走っていない時間はインフラストラクチャのコストが完全にゼロになる。
—
2. 全体アーキテクチャの設計
今回構築するシステムの流れはこうだ。
1. GitHub上でワークフローがトリガーされる。
2. GitHub Actionsの Webhook または GitHub API (Events API) を検知して、クラウド(AWS)側でプロビジョニングが走る。
3. AWS Auto Scaling / Lambda / ECS などを使い、ランナー用のコンテナ(または軽量VM)を起動。
4. 起動したランナーが GitHub に `–ephemeral` モードで接続。
5. ジョブを実行。
6. ジョブ終了後、ランナー自身が消滅し、インフラも自動で破棄される。
今回は、最も堅牢かつスケーラブルな「AWS ECS (Fargate) + Terraform」による実装アプローチをベースに解説する。
—
3. 実践:エフェメラルランナーを構築する設定ファイル群
① ランナーコンテナ用 Dockerfile
まず、GitHub Actions Runnerを内包した軽量なコンテナイメージを作成する。ここで重要なのは、「ジョブ終了後の自動クリーンアップのフック」を正しく理解しておくことだ。
── GitHub Actions Ephemeral Runner Dockerfile ──
FROM ubuntu:22.04
DEBIAN_FRONTEND=noninteractive
必要な依存関係のインストール(Docker, AWS CLI, その他ビルドツール)
RUN apt-get update && apt-get install -y \
curl \
git \
jq \
unzip \
libicu70 \
&& rm -rf /var/lib/apt/lists/
非特権ユーザーの作成(セキュリティ向上)
RUN useradd -m github && usermod -aG sudo github
USER github
WORKDIR /home/github
GitHub Actions Runnerのダウンロード(最新バージョンを動的に取得することを推奨)
ARG RUNNER_VERSION=”2.311.0″
RUN curl -O -L https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz \
&& tar xzf ./actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz \
&& rm ./actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz
起動スクリプトの配置
COPY –chown=github:github entrypoint.sh ./entrypoint.sh
RUN chmod +x ./entrypoint.sh
ENTRYPOINT [“./entrypoint.sh”]
② 起動スクリプト (`entrypoint.sh`)
ここが肝心だ。GitHub APIを叩いて一時的な登録トークン(Registration Token)を取得し、`–ephemeral` フラグつきでランナーを起動する。
!/bin/bash
set -e
必須環境変数のチェック
if [ -z “$GITHUB_REPOSITORY” ] || [ -z “$GITHUB_PAT” ]; then
echo “Error: GITHUB_REPOSITORY and GITHUB_PAT must be provided.”
exit 1
fi
echo “===> Generating GitHub Runner Registration Token…”
GitHub REST APIを使って一時トークンを発行
TOKEN_RESPONSE=$(curl -sX POST \
-H “Authorization: token ${GITHUB_PAT}” \
-H “Accept: application/vnd.github.v3+json” \
https://api.github.com/repos/${GITHUB_REPOSITORY}/actions/runners/registration-token)
RUNNER_TOKEN=$(echo “$TOKEN_RESPONSE” | jq -r .token)
if [ “$RUNNER_TOKEN” == “null” ] || [ -z “$RUNNER_TOKEN” ]; then
echo “Error: Failed to obtain registration token. Response: $TOKEN_RESPONSE”
exit 1
fi
echo “===> Configuring runner in ephemeral mode…”
./config.sh –url https://github.com/${GITHUB_REPOSITORY} \
–token ${RUNNER_TOKEN} \
–ephemeral \
–unattended \
–replace
echo “===> Starting runner…”
ランナーを起動。–ephemeralが指定されているため、1ジョブ完了後に自動終了する
./run.sh
echo “===> Job completed. Ephemeral runner shutting down and deregistering…”
—
4. プロの技:GitHub Actions ワークフロー側の設定
ランナーを動的にプロビジョニングする仕組みを作ったら、実際に呼び出すワークフロー側はどのように設定すべきか。
ここで、開発チーム全体の生産性を高めるための「絶対入れるべきベストプラクティス構成」を紹介する。
name: Secure & Clean CI Pipeline
セルフホステッドのエフェメラルランナーを指定
ラベルに ‘ephemeral’ およびAWSのECS等紐づくタグを付与
runs-on: [self-hosted, linux, ephemeral, aws-fargate]
jobs:
build-and-test:
name: Build and Test with Zero Pollution
runs-on: [self-hosted, linux, ephemeral]
# ジョブのタイムアウトを厳格に設定(無限ループによるコスト爆発を防ぐ)
timeout-minutes: 15
steps:
- name: Checkout Code
uses: actions/checkout@v4
# キャッシュの最適化:エフェメラル環境であっても高速化のためにGitHub ActionsのCacheを活用
- name: Cache Node Modules
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${= hashFiles(‘/package-lock.json’) }}
restore-keys: |
${{ runner.os }}-node-
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: ’20’
- name: Install Dependencies
run: npm ci
- name: Run Unit Tests
run: npm test
- name: Security Vulnerability Scan
run: npx audit-ci –high
—
5. チーム開発を加速する運用ルールとハック
エフェメラルランナーを導入するにあたり、チーム全体で共有すべき設定ルールと、現場で役立つ「隠れた知見」を共有しよう。
1. トークン管理には細心の注意を
GitHub APIを叩いて登録トークンを発行するための Personal Access Token (PAT) や GitHub App のクレデンシャルは、AWS Secrets Manager や Parameter Store に厳重に保管すること。絶対にコードベースにハードコーディングしてはならない。
2. 「スケールダウン・トリガー」の確実な実行
AWS ECSやKubernetes(KEDAなどを使用する場合)でランナーを動かす場合、ジョブがキューに入った瞬間にコンテナを立ち上げ、ジョブ終了(あるいはタイムアウト)時には、コンテナの終了を検知してインフラストラクチャ(タスク)が確実に消滅するライフサイクルを組むこと。
- AWS ECSの場合: タスクの終了ステータスをCloudWatch Eventsで監視し、オートスケーリンググループやECSサービスが適切にリソースを回収するように設定する。
- Kubernetes (KEDA) の場合: `scaledObject` を用いて、GitHub Actionsのキューの長さに応じてポッドを自動増減させ、ポッドが終了すればK8s側が自動でガベージコレクションを行うため非常に相性が良い。
3. キャッシュ戦略の割り切り
エフェメラルランナーは毎回クリーンな状態から始まるため、Dockerイメージのレイヤーキャッシュやビルドキャッシュが失われる。これを毎回スクラッチからビルドしているとCIが遅くなる原因になる。
対策として、AWS ECR(Elastic Container Registry)の層キャッシュや、前述の `actions/cache` を組み合わせ、「インフラストラクチャは使い捨てだが、依存関係のアーカイブは賢く使い回す」ハイブリッドな設計にするのがテックリードの定石だ。
—
結び:インフラの管理コストをゼロにし、開発体験を最大化せよ
CI/CDインフラの管理にエンジニアの貴重な時間が奪われては本末転倒だ。「動かないランナーの再起動」「ディスク容量不足エラーの解消」「謎のキャッシュ汚染のデバッグ」――そんな不毛なタスクとは、今日で終わりにしよう。
Ephemeral Runnersを導入することで、あなたのチームは「常に美しく、安全で、コスト効率の極限まで最適化されたCI環境」を手に入れることができる。
さあ、今すぐコンテナを爆誕させ、そして華麗に消し去ろう。インフラが消える瞬間ほど、モダンなDevOpsエンジニアにとって美しい景色はない。