【ITニュース解説】Self-Hosted GitHub Runners on GKE: My $800/Month Mistake That Led to a Better Solution
2025年09月04日に「Dev.to」が公開したITニュース「Self-Hosted GitHub Runners on GKE: My $800/Month Mistake That Led to a Better Solution」について初心者にもわかりやすいように丁寧に解説しています。
ITニュース概要
GitHub Actionsの利用料高騰とビルド待ち時間増加を解決するため、GKE上にActions Runner Controllerで自己ホスト型ランナーを構築した。ファイアウォール設定やDockerイメージ最適化などの課題を乗り越え、月800ドル超のコストを200ドルに削減し、ビルド時間も大幅に短縮、開発者の生産性を向上させた。
ITニュース解説
ある企業がGitHub Actionsを使い始めた当初、その費用は月に100ドル程度であったが、チームが3人から15人へと拡大し、システムをデプロイする頻度が増えるにつれて、GitHub Actionsの利用料は月に800ドルを超えるまでに高騰した。しかし、問題は費用だけではなかった。システム開発の最終段階であるデプロイ時に、ビルドがGitHubのキューで10分から15分も待たされることが頻繁に発生した。これは開発者の作業の流れを妨げ、生産性を著しく低下させる深刻な問題であった。開発者たちはビルドが始まるのを待つ間に他の作業を始めたり、コーヒーを飲みに行ったりすることで、集中力を失い、フィードバックのサイクルも遅滞した。この状況を改善するため、「何かより良い方法があるはずだ」という思いから、新たな解決策の模索が始まった。
この問題への解決策として見つけ出されたのが、「Actions Runner Controller (ARC)」と呼ばれる技術であった。これは、GitHub Actionsのジョブを実行するためのプログラム実行環境(Runner)を、自分たちで管理する「セルフホスト型Runner」をGoogle Kubernetes Engine (GKE) 上で動かすための仕組みである。ARCは、ジョブがGitHub Actionsに登録されると、GKE上に一時的な実行環境(Pod)を自動的に作成し、ジョブが完了するとその環境を自動的に削除する。これにより、常にRunnerを稼働させておく必要がなくなり、必要な時に必要なだけリソースを利用できる。このアプローチは、リソースを効率的に利用し、コストを削減するだけでなく、GitHubのキューで待つ必要がなくなるため、待ち時間の解消、さらに実行環境を完全にコントロールできるという大きなメリットをもたらした。
しかし、この新しい仕組みを導入する道のりは決して平坦ではなかった。まず最初に直面した大きな課題は、ネットワークの設定であった。GKEクラスターにARCコントローラーをインストールしても、GitHubから送られてくるジョブ開始の通知(Webhook)がクラスターに届かなかったのだ。これは、GKEクラスターがインターネットからのアクセスを適切に受け入れるように、ファイアウォール規則、ロードバランサー、正しいウェブアドレス(DNS)の設定がなされていなかったためである。GitHubがARCコントローラーに通知を送り、ARCコントローラーがそれを受けてGKE上でRunnerを起動し、そのRunnerがGitHubにジョブの状況を報告するという一連の通信フローを理解し、適切に設定する必要があった。セキュリティを確保するため、SSL証明書を自動で管理する「cert-manager」も導入され、安全な通信経路が確立された。
ネットワークの問題を解決した後、GKEクラスターの基盤構築に着手した。コストを大幅に削減するため、Google Cloudの「Spot Instances」という、安価だがGoogleの都合で突然停止する可能性があるサーバーを使うことを選択した。ARCは、これらのSpot Instancesが停止しても、自動的にRunnerを別の稼働中のサーバーに移動させ再起動する能力を持っていたため、この低コストの選択肢は非常に有効であった。次に重要だったのは、ビルドの速度を向上させるためのストレージであった。初期段階では共有ストレージがなかったため、それぞれのジョブが最初からすべてのビルドファイルをダウンロードし直す必要があり、結果的にビルド時間が遅くなっていた。そこで、「Google Cloud Filestore」という共有ストレージを導入し、ビルドキャッシュとして利用できるようにした。これにより、以前のビルドで使われたファイルを再利用できるようになり、ビルド時間は40%も短縮された。
さらに、Runnerがジョブを実行するために使用するソフトウェアの集合体である「カスタムDockerイメージ」の作成にも課題があった。最初の試みでは、様々なツールやソフトウェアを無差別に詰め込みすぎた結果、8GBもの巨大なイメージができてしまった。この巨大なイメージは、Runnerが起動するたびにダウンロードに15分もかかり、結局待ち時間が発生し、開発者の不満を再び招いた。この経験から、イメージサイズが開発者の満足度に直結することを学び、必要なものだけを厳選し、作成方法を工夫(多段階ビルドや不要なファイルの削除など)することで、イメージサイズを1.5GBから2GB程度にまで削減した。これにより、イメージのダウンロード時間は60秒未満に短縮され、開発者の体験は劇的に改善された。
Runnerの動作原理も重要な学習ポイントであった。各Runnerはジョブが完了すると自動的に消滅する「一時的な(エフェメラル)Pod」として機能する。また、Runnerの内部でDockerコマンドを使ってコンテナをビルドする必要があるため、「Docker-in-Docker (DinD)」という技術を採用した。これは、ホストサーバーのDockerエンジンを直接Runnerに公開する初期のアプローチがセキュリティ上のリスクを伴うため、Runner内に独立したDocker環境を設けることで、安全にコンテナビルドを可能にする仕組みである。加えて、Runnerに割り当てるCPUやメモリのリソースを適切に設定しないという「リソース割り当ての失敗」も経験した。これにより、ジョブが途中で予期せず停止したり、GKEクラスター全体が不安定になったりする問題が発生した。適切なCPUやメモリのリクエスト値と制限値を設定することで、この問題は解決され、システムの安定性が確保された。最終的に、常に最低1つのRunnerを待機させ、最大100個まで自動でスケールするように設定することで、小規模な変更でも即座にジョブが開始され、大規模なデプロイメントでも十分な並行処理能力を確保できる理想的なスケーリングが実現した。
これらの数々の課題を乗り越え、システムを構築し終えるまでに約半年が経過した。その結果は目覚ましいものであった。毎月800ドル以上かかっていたCI/CD(継続的インテグレーション/継続的デリバリー)の費用は、月200ドルへと約70%も削減された。パフォーマンス面では、GitHubのキューで15分も待たされていたビルドが、わずか30秒で開始されるようになり、効果的なキャッシュ戦略によりビルド速度自体も40%向上した。これにより、デプロイの信頼性も高まり、リソース不足による失敗はほぼなくなった。何よりも大きな成果は、開発者たちの満足度向上であった。ビルドの待ち時間がなくなり、開発者たちは作業に集中できるようになり、コンテキストスイッチング(作業の切り替え)による中断が減少した。
このシステムは単に費用対効果が高いだけでなく、「アンチフラジャイル(反脆弱性)」なものになった。これは、トラフィックが増加した際には自動でスケールアップし、サーバーが突然停止してもジョブは別のサーバーで自動的に再開され、ビルドが失敗しても詳細なログから素早く問題原因を特定できるなど、障害が発生してもそこから学び、より強固になる性質を持つことを意味する。ネットワークの問題、ストレージの不足、リソース割り当ての失敗など、遭遇した数々の困難は、それぞれがシステムの設計を改善し、より堅牢なものにするための貴重な教訓となった。
もし月に500ドル以上のGitHub Actions費用がかかり、ビルドの待ち時間に悩まされているのであれば、GKE上でのセルフホスト型Runnerの導入は検討する価値がある。ただし、初期設定には2〜3週間を要し、Kubernetesに関する学習曲線は急であること、そして導入後はインフラの運用責任が自分たちに移ることを覚悟する必要がある。費用削減は魅力的だが、すぐに実現するものではない。まずはシンプルなRunnerから始め、徐々に複雑な機能を追加していくのが賢明である。システムは常に監視し、そして失敗を恐れずにそこから学び続けることが、成功への鍵となる。かつて月に800ドルを支払い、GitHubのキューで待たされていた状況から、月200ドルで瞬時のデプロイと完全に制御された実行環境を手に入れたのである。時には最も厄介な問題の中にこそ、最高の解決策が隠されている。