【テクニカル・上級編】GitHub Actionsの「セルフホストランナー」をAuto Scalingさせる技術:KEDA活用によるコストとパフォーマンスの最適解 – バージョン管理・CI/CD活用バイブル

GitHub Actions Runnerの「飽和」を殺す:KEDAによるゼロレイテンシ・オートスケーリングの深淵

GitHub Actionsのセルフホストランナーを管理している諸君、君たちはまだ「静的なランナープール」という遺物と戦っているのか?

「朝のプルリクエスト殺到によるビルド待ち」「夜間の無駄なアイドルコスト」「スケールアップの遅延が引き起こすコンテキストスイッチ」。これらはすべて、設計の怠慢だ。GitHubが提供する`actions-runner-controller` (ARC) をただ導入するだけでは足りない。真のDevOpsエンジニアは、KEDA(Kubernetes Event-driven Autoscaling)を調教し、Kubernetesの深部を制御することで、コストとパフォーマンスの極限を叩き出す。

今日は、理論ではなく「現場で勝つための」設計思想とハックを伝授する。

—

1. 従来の「静的ランナー」という名の負債

多くの現場が犯す最大の過ちは、EC2やVMにランナーを常駐させることだ。

  • アイドルコスト: 夜間や週末も稼働するランナーは、AWSの請求書に刻まれる無駄な墓標だ。
  • スケーラビリティの硬直: 突発的な大規模ビルドが発生した際、既存のプールが枯渇すれば、ビルドは待ち行列(Pending)に放り込まれる。
  • ノードの汚染: 実行環境が永続化されると、一時ファイルやキャッシュが蓄積し、`dirty environment`による再現不能なビルドエラーを誘発する。

これらを解決するための最適解が、「Kubernetes上のEphemeral Runner(使い捨てランナー)」である。

—

2. KEDAによるイベント駆動の真髄

ARCとKEDAを組み合わせることで、GitHubのWebhookやAPIを監視し、ジョブが投入された瞬間にPodを起動、完了と同時に消滅させるアーキテクチャを構築する。

アーキテクチャの核心

  • RunnerDeployment: リポジトリや組織レベルでのランナーの定義。
  • KEDA ScaledObject: GitHub APIをポーリングし、`queued_or_in_progress`のジョブ数を監視。
  • Ephemeral Pods: ビルドごとにPodを生成。終了時は`SIGTERM`を受け取り、キャッシュを焼き払って消える。

構成の要:`ScaledObject`のチューニング

デフォルト設定ではスケールアウトが遅い。これを極限まで詰める。

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: github-runner-scaler
spec:
scaleTargetRef:
name: arc-runner-deployment
pollingInterval: 5 # APIポーリング間隔を5秒まで絞る(GitHub API制限に注意)
cooldownPeriod: 300 # スケールインの猶予は短めに設定し、コストを削る
triggers:

  • type: github-runner

metadata:
githubRunnerScaleTarget: “arc-runner-deployment”
runnerScope: “organization”
organization: “your-org-name”
# 特定のラベルのジョブのみを追跡する(重要!)
labels: “self-hosted,linux,x64”

—

3. パフォーマンスを極限まで引き上げる「ハック」

A. コンテナイメージのプリフェッチ (Warm-up)

Podが起動してからDocker Pullが走っていては遅い。`DaemonSet`を使い、ノード上にベースイメージを予め展開しておく「イメージ・ウォーミング」が必須だ。

ノード上の全イメージを管理するDaemonSetのイメージ
常に最新のランナーイメージをキャッシュしておく
docker pull ghcr.io/actions/actions-runner:latest

B. PVC(Persistent Volume)によるキャッシュ活用

ランナーが使い捨てであっても、`node_modules`や`maven`のレポジトリキャッシュまで消してはビルド時間が延びる。`Local PV`または`EFS CSI`をマウントし、`$RUNNER_TEMP/_github_workflow`を永続化せよ。

C. Pod Anti-Affinityによるリソース競合の排除

同じノードに複数の重いビルドが重なると、CPUの奪い合いでビルド時間が不安定になる。

affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:

  • weight: 100

podAffinityTerm:
labelSelector:
matchExpressions:

  • key: “actions-runner-controller”

operator: In
values: [“true”]
topologyKey: “kubernetes.io/hostname”

—

4. 伝説的エンジニアの「最終兵器」:API直叩きオートスケーリング

KEDAの標準トリガーで足りない複雑なロジック(例:特定の開発者のジョブだけ優先したい、スポットインスタンスの空き状況に応じてランナー数を動的に制限したい)が必要な場合は、自作コントローラーをGoで書け。

GitHubの `CheckRuns` や `WorkflowJobs` API を定期的に叩き、Kubernetesの `Custom Resource` を直接操作するスクリプトをサイドカーとして動かすのだ。

// 擬似コード: GitHub APIを監視し、特定の優先度ジョブがあれば即座にスケールさせる
func ReconcileRunnerCount(ctx context.Context) {
jobs := githubClient.GetQueuedJobs(org, repo)
if len(jobs) > threshold {
k8sClient.ScaleDeployment(“arc-runner”, len(jobs))
}
}

—

5. 最後に:運用の哲学

このアーキテクチャの最大のメリットは、「エンジニアがインフラを意識しなくなること」だ。開発者は `runs-on: self-hosted` と書くだけで、無限に湧き出す計算資源を手に入れる。

  • コスト: ゼロビルド時はほぼゼロ円(FargateやSpotノードを使用)。
  • 速度: Podの起動時間は数十秒。
  • 信頼性: 毎回クリーンな環境で実行されるため、ゴミ溜めのようなレガシーCI環境から脱却できる。

これこそが、DevOpsの到達点だ。ツールに振り回されるな。K8sとGitHub APIを掌の上で転がし、パイプラインを「動く芸術」へと昇華させろ。

何かあればまた聞け。君たちのパイプラインが、世界最速になるその日まで。

タイトルとURLをコピーしました