GitHub Actions 講座 #8 セルフホストランナー — 導入基準、登録、セキュリティ原則
これまでのすべてのジョブは runs-on: ubuntu-latest、つまり GitHub が運営するランナーで実行されてきました。管理が一切不要な優れたデフォルトですが、これでは対応できない状況が 3 種類あります。そのときの答えがランナーを自分で運用するセルフホストランナーで、今回は導入判断からセキュリティ原則までを扱います。先に結論を置くと、必要が実在するときだけ導入する道具です。ランナーの運用はサーバーの運用そのものなので、理由なく導入すると管理の負担が増えるだけです。
導入基準 — 実在する 3 つの必要 #
- 内部ネットワークへのアクセス: 社内ネットワーク内のリソース(内部 API、プライベート DB、社内アーティファクトリポジトリ)にアクセスする必要があるパイプラインには、ホストランナーはそもそも届きません。内部ネットワークの中にランナーを置くのが基本です。
- コスト: #1で見たとおり、プライベートリポジトリのホストランナーは分単位の課金です。実行量が多い組織では、常時稼働サーバー(またはスポットインスタンス)上のセルフホストランナーの方が安くなる損益分岐点が来ます。逆に実行量が少なければホストランナーが圧倒的に安上がりです。ランナーを遊ばせていてもサーバー代はかかるからです。
- 特殊なスペック: GPU が必要な ML ジョブ、大きなメモリ・ディスクが必要なビルド、特定の OS・アーキテクチャなど、ホストランナーのラインナップにないスペックです。
この 3 つのどれにも当てはまらないなら、今回は「いつかのための地図」として読み、ホストランナーを維持するのが正解です。
登録とルーティング — ラベルが宛先です #
登録の手順自体はシンプルです。リポジトリ(または組織)の Settings にある Runners メニューで New self-hosted runner をクリックすると、ランナーエージェントのダウンロードと登録のコマンドが案内されます。対象マシンでそのまま実行すると、ランナーが GitHub に接続されて待機状態になります。サービスとして登録し、マシンの起動時に自動で立ち上がるようにするところまでが基本のインストールです。
ワークフロー側では runs-on のラベルでランナーを指定します。
jobs:
build:
runs-on: [self-hosted, linux, gpu]ランナーは登録時にデフォルトラベル(self-hosted、OS、アーキテクチャ)を持ち、カスタムラベル(gpu、internal など)を追加できます。runs-on に並べたすべてのラベルを持つランナーだけがジョブを受け取るため、ラベル設計がそのままジョブのルーティング設計になります。「GPU ジョブは gpu ラベルのランナーへ、内部ネットワークへのデプロイは internal ラベルのランナーへ」のように用途別のラベルを決めておけば、ランナーが増えてもワークフローは変わりません。
セキュリティ原則 — パブリックリポジトリでは使いません #
セルフホストランナーで最も重要な原則は、フォーク PR を受け付けるパブリックリポジトリにはセルフホストランナーを接続しないことです。理由は構造的です。PR のワークフローは PR のコードを実行しますが、パブリックリポジトリのフォーク PR は誰でも作れます。つまり見知らぬ人のコードがこちらのランナーマシンで実行される道が開くということで、ランナーが内部ネットワークにあれば、内部ネットワークの中で実行されるということです。GitHub ホストランナーは実行のたびに破棄される隔離 VM なので許容できるリスクですが、自前のサーバーは違います。セルフホストランナーはプライベートリポジトリ専用と覚えておくのが安全で、このテーマの残り半分(pull_request_target の罠)は次回扱います。
同じ流れで、ランナーの寿命管理にも原則があります。ランナーマシンを長く使うと、ジョブが残した状態(キャッシュ、資格情報の残骸、ツールのバージョン)が積み重なり、「昨日は通っていたビルド」の原因になります。可能ならエフェメラル(一時)ランナー、つまりジョブを 1 つ処理したら破棄される方式で運用し、ホストランナーのクリーンさを再現するのが現行のベストプラクティスです。
規模が大きくなったら — actions-runner-controller #
ランナーが数台の規模を超え、需要に合わせて増減させる必要が出てきたら、Kubernetes 上でランナーを Pod として管理する公式オープンソースの actions-runner-controller(ARC)が標準の答えです。待機中のジョブ数に合わせてランナー Pod を自動で増減させ、エフェメラルランナーの原則も Pod のライフサイクルとして自然に実現されます。Kubernetes の運用が前提という参入障壁があるため、K8s トラックを知っているチームがランナーの台数管理に疲れ始めたときが導入のタイミングです。
まとめ #
今回扱った内容です。
- 導入基準は内部ネットワークへのアクセス、実行量ベースのコスト損益分岐、特殊スペックの 3 つです。当てはまらなければホストランナーの維持が正解です
- 登録は Settings の案内コマンドで完了し、runs-on のラベルの組み合わせがジョブのルーティングです
- フォーク PR を受け付けるパブリックリポジトリには接続しません。見知らぬコードがこちらのマシンで動く道になるからです
- ランナーはエフェメラル(一時)運用がベストプラクティスです。長生きするランナーは状態が積もって再現性の問題を生みます
- 規模が大きくなったら Kubernetes 上の actions-runner-controller で自動スケールします
次回(#9 セキュリティ対策)では、ワークフローのセキュリティを正面から扱います。GITHUB_TOKEN の権限最小化、サードパーティアクションの SHA 固定、そして pull_request_target の罠です。