philips-labs/terraform-aws-github-runner でオートスケールするセルフホストランナーの構築・運用

こんにちは、生産性向上チーム@miyajan です!

この記事は、Cybozu Advent Calendar 2022 の一日目です。philips-labs/terraform-aws-github-runner を使ってオートスケールする GitHub Actions のセルフホストランナーを構築・運用している知見を書きます。

この話題については過去に発表しましたが、それから一年以上経って変更も多いため、あらためてブログ記事にしました。

背景

サイボウズには、サイボウズ社内のネットワークからしかアクセスできないシステムに依存して開発しているチームが複数あります。これらのチームが GitHub Actions を利用したいと思っても、GitHub が提供する Actions のランナーからはサイボウズ社内のネットワークにアクセスできません。このため、サイボウズ社内の開発チームが GitHub Actions を利用しづらいという問題がありました。

こういった問題を解決するため、GitHub Actions にはセルフホストランナーという仕組みが公式で用意されています。セルフホストランナーは、自前でランナーをホスティングすることにより、プライベートネットワーク内でジョブを実行したり、公式で用意されていないマシン環境でジョブを実行したりできます。セルフホストランナーをサイボウズ社内のネットワークにアクセスできる環境でホスティングすることにより、上記の問題を解決できます。

しかし、開発チームが各自でセルフホストランナーを構築・運用するとなると少しハードルが高いです。また、ランナーの数が少ないとジョブの待ち時間が発生しますし、逆にランナーの数が多いと管理コストやコンピューティングリソースのコストが高くなります。

なので、いい感じにオートスケールするセルフホストランナーがどの開発チームでも手軽に使えるようになっていると、サイボウズの開発全体に大きなメリットがありそうです。このため、生産性向上チームで GitHub Actions のワークフロー実行に合わせてオートスケールするセルフホストランナーを実現して、社内の開発チームに提供したいと考えました。

philips-labs/terraform-aws-github-runner

philips-labs/terraform-aws-github-runner は、AWS 上にオートスケールするセルフホストランナーを構築するための Terraform モジュールです。

サイボウズでは AWS 上の VPC と社内ネットワークを接続する Transit Gateway をすでに運用しているので、このモジュールを使えば手軽に実現したい環境を構築できそうと判断しました。また、サイボウズはクラウドの GitHub Enterprise Cloud (GHEC) とオンプレの GitHub Enterprise Server (GHES) を社内で併用しており、philips-labs/terraform-aws-github-runner がどちらにも対応しているのもマッチしていました。この取り組みを始めた 2020 年当時は、オートスケールするセルフホストランナーを構築するためのソリューションが他にほとんどなかったのも理由の一つです。

現在では GitHub 公式ドキュメントでオートスケールするセルフホストランナーの推奨ソリューションの一覧が公開されておりphilips-labs/terraform-aws-github-runner もその中に含まれています。

philips-labs/terraform-aws-github-runner の仕組み

philips-labs/terraform-aws-github-runner を利用する場合、GitHub Actions のジョブ実行時にランナーを立ち上げるために GitHub App をセットアップする必要があります。GitHub App をリポジトリにインストールすることにより、そのリポジトリで GitHub Actions のジョブが実行されたとき1に Webhook 経由でリクエストを送ることができるようになります。

philips-labs/terraform-aws-github-runner の Terraform モジュールをデプロイすると AWS 上に複数のリソースがデプロイされ、その中に API Gateway が存在します。Webhook のリクエスト送信先には、その API Gateway の URL を指定します。API Gateway にリクエストが送られると Lambda が起動し、EC2 でインスタンスが立ち上がり、セルフホストランナーが起動されます。2

philips-labs/terraform-aws-github-runner の仕組み

サイボウズでの運用

ここからは、philips-labs/terraform-aws-github-runner をサイボウズでどのように運用しているかについて書きます。

Terraform モジュールとデプロイフロー

サイボウズでは、GHEC や GHES にそれぞれ複数の Organization が存在します。philips-labs/terraform-aws-github-runner は現時点では Enterprise 単位でのランナーの登録をサポートしていないため、セルフホストランナーを利用する Organization ごとにデプロイする必要があります。

Organization ごとに philips-labs/terraform-aws-github-runner モジュールのすべての設定やそれに付随するリソースの設定を記述すると冗長かつメンテしづらくなってしまうため、我々は philips-labs/terraform-aws-github-runner をラップした Terraform モジュール(以下、「ラッパー Terraform モジュール」と呼びます)を作成して、メンテしやすくしています。

Terraform の設定を変更するときは、生産性向上チームの Organization を staging 環境代わりとして最初にデプロイし、動作確認して問題なければ他の Organization にも適用します。

大まかなデプロイフローは以下になります。

  1. ラッパー Terraform モジュールの設定を変更して新しいバージョンのタグを作成する
  2. staging 環境の Organization に新しいバージョンを適用する
  3. staging 環境で 動作確認用のワークフローを流す
  4. 問題なければ他の Organization に新しいバージョンを適用する

AMI のビルド

philips-labs/terraform-aws-github-runner が Webhook のリクエストを受け取ったときに立ち上げる EC2 インスタンスは、デフォルトだと Amazon Linux 2 の AMI を使って立ち上げられます。しかし、GitHub 公式が提供するランナーでは Ubuntu がよく使われるので、セルフホストランナーの OS も Ubuntu にしたいです。また、社内のワークフローでよく使われるソフトウェア一式は事前にインストールしておきたいです。これを実現するため、philips-labs/terraform-aws-github-runner は AMI をパラメータで差し替えられるようになっているので、AMI を自作してそれを使うように設定を変更しています。

AMI は、最初は公式が actions/runner-images で公開しているランナーのイメージをビルドすることを検討しました。しかし、調査してみると、このイメージは Azure 用のマシンイメージなので AWS 向けに修正が必要なこと、サイボウズではほとんど使わないソフトウェアのインストールのためにビルド時間が長いしディスク容量も大量に必要なことなどが懸念として挙がりました。

このため、AMI は自前で新たに作成することにしました。イメージの作成には Packer を使い、社内でよく使われる必要最低限のソフトウェアだけインストールされた Ubuntu イメージを作成しています。これにより、社内から要望があれば AMI に新しいソフトウェアを追加するといった対応も柔軟にできるようになっています。

AMI にインストールされているソフトウェアパッケージのバージョンをできる限り最新に保つため、AMI のビルドとデプロイを週に一回自動で行うようにしています。ビルド時にはインストールされたソフトウェアパッケージとそのバージョンの一覧のドキュメントを自動生成し、セルフホストランナーの利用者が確認できるようにしています。

ちなみに、philips-labs/terraform-aws-github-runner の EC2 インスタンスのデフォルトの user data は Amazon Linux 2 用のスクリプトなので、AMI の OS を変更する場合は その OS 用の user data スクリプトを作成して userdata_template パラメータで指定する必要があります。Ubuntu の user data の例が examples にあるので、そちらが参考になります。

ランナーを一回のビルドで使い捨てにする設定

セルフホストランナーには、ランナーを複数回のビルドで使い回さずに、一回ビルドしたら他のジョブのビルドで使われないようにランナーとしての登録を解除する設定があります。ランナーを複数回のビルドで使い回すと、前のジョブ実行で残されたファイルなどによりビルド結果が想定外になる可能性があるので、ランナーを使い捨てにする設定を有効にしたいと考えました。

philips-labs/terraform-aws-github-runner にもランナーの使い捨てを有効にする enable_ephemeral_runners パラメータが存在するので、このオプションを有効にしています。

ランナーの使い捨て設定を有効にする場合、ランナーをプールするための設定を合わせて有効にすることが重要になります。プールする設定というのは、philips-labs/terraform-aws-github-runner には pool_runner_ownerpool_config というパラメータがあり、これらのパラメータを設定することで指定した台数のランナーを常時起動してプールしておくことができます。

ランナーを使い捨てにする設定を有効にした場合、なにかしらの理由(例:Webhook リクエストが送信されないなど)でジョブ実行時にランナーが立ち上がらなかったときに、ジョブが実行されなくなるリスクがあります。この問題に対して、プール設定を有効にしておくことで、プールされているランナーがジョブを拾って実行してくれます。また、プールされているランナーがあればインスタンスが起動してランナーが登録されるまでジョブが実行されないという待ち時間が発生するケースも少なくなり、ユーザー体験もよくなります。

セルフホストランナーの利用をリポジトリ単位でコントロールしたいケース

Organization によっては、社外の Outside Collaborator が存在するリポジトリがあります。今回のセルフホストランナーへのアクセスを許可すると、社内ネットワークへアクセス可能になってしまうため、本来社内ネットワークにアクセスできない Outside Collaborator がいるリポジトリにはセルフホストランナーの利用を許可したくないケースがあります。

そういったセルフホストランナーの利用をリポジトリ単位でコントロールしたい Organization では、GitHub のランナーグループで利用可能なリポジトリをホワイトリスト方式で選択するようにしています。philips-labs/terraform-aws-github-runnerrunner_group_name パラメータでランナーを登録するランナーグループを設定できるので、利用可能なリポジトリを制限したランナーグループを指定します。3

セルフホストランナーのラベルの設定

philips-labs/terraform-aws-github-runner が起動するセルフホストランナーのラベルには、デフォルトで self-hosted ラベルとアーキテクチャや OS のラベル(LinuxX64 など)が付きます。

このデフォルトの状態だと、利用者側は GitHub Actions のワークフローの設定ファイルの runs-on で以下のように書くことでこのセルフホストランナーを使うことができます。

runs-on: self-hosted

セルフホストランナーが philips-labs/terraform-aws-github-runner が登録したものしか存在しないのであればこれで問題ないのですが、他のチームがこれとは別に独自のセルフホストランナーを構築してランナーに self-hosted ラベルを付けている場合、self-hosted ラベルだけではどちらのランナーが使われるか区別が付かなくなる問題が発生します4

これを防ぐために、philips-labs/terraform-aws-github-runner では runner_extra_labels というパラメータで追加のラベルを指定できるため、internal-network という独自のラベルを追加して他のセルフホストランナーと区別できるようにしています。我々のセルフホストランナーの利用者は、runs-on に以下のように書く運用になります。

runs-on: [self-hosted, internal-network]

また、別の問題として、philips-labs/terraform-aws-github-runner はデフォルトだと runs-on でセルフホストランナーを指定していないジョブの Webhook リクエストでもランナーを起動して、無駄にリソースを使ってしまいます。

これを防ぐために、philips-labs/terraform-aws-github-runnerrunner_enable_workflow_job_labels_check オプションと runner_enable_workflow_job_labels_check_all オプションを有効にしています。これにより、runs-on で指定されたラベルと philips-labs/terraform-aws-github-runner のランナーのラベルが一致するときのみランナーが起動されるようになります。

philips-labs/terraform-aws-github-runner のデバッグ

philips-labs/terraform-aws-github-runner の設定変更をしたときに、こちらの設定ミスやモジュールのバージョンアップに伴う仕様変更やバグにより、セルフホストランナーでワークフローが実行されなくなることがたまにあります。このとき、よくある原因として以下の 3 つがあるので、それぞれどのようにデバッグしているかを書きます。

  1. Webhook のリクエストが届いていない
  2. Webhook のリクエストは届いているが、Lambda が EC2 インスタンスを起動できていない
  3. EC2 インスタンスは起動しているが、ランナーの起動と登録ができていない

1. については、GitHub App の設定画面の Advanced から最近の Webhook リクエストのログが閲覧できるので、エラーが発生していないか確認できます。

2. については、各 Lambda ごとの CloudWatch メトリクスや、CloudWatch Logs のロググループがあるので、そちらでエラーが発生していないかなどを確認できます。EC2 インスタンスが起動していない場合は、webhookscale-up という Lambda が原因の可能性が高いです。

3. については、EC2 インスタンスにログインして調査します。philips-labs/terraform-aws-github-runner には enable_ssm_on_runners というパラメータがあり、そのオプションを有効にすると AWS SSM のセッションマネージャー経由でランナー用の EC2 インスタンスにログインできるようになります。ランナーの起動でエラーが発生している場合は、user data スク リプトのログ(/var/log/cloud-init-output.log)を見るとエラーが記録されていることが多いです。

メトリクス収集&モニタリング

異常に素早く気付きやすくするために、以下のようなメトリクスを監視し、閾値を超えたら Slack にアラートを飛ばすようにしています。

  1. EC2 インスタンスの稼働時間
  2. 起動しているランナーの数
  3. Lambda の実行エラー

1.2. については、自分たちで Lambda を作成して定期実行することで Datadog にメトリクスを収集して監視しています。インスタンスを削除する仕組みが動かなかったり、想定外の理由で大量のインスタンスが立ち上がったりすると料金面でリスクがあるので、監視しています。

3. については、CloudWatch にメトリクスが存在するため、CloudWatch Alarm で監視しています。Lambda のエラーに気づきやすくするために監視していますが、一回のエラーだけでアラートを飛ばすと、瞬間的な GitHub のサービスダウンのような本来スルーして大丈夫な問題を検知してしまうので、一定時間で一定回数以上のエラーが発生したときにアラートを飛ばすように閾値を調整することをおすすめします。

まとめ

サイボウズでは社内のネットワークに依存する開発でも GitHub Actions を利用可能にするために、生産性向上チームがオートスケールするセルフホストランナーを構築して社内に提供しており、多くのチームに活用されています。

philips-labs/terraform-aws-github-runner は少し複雑なところもありますが、初期の頃から活発に開発され続けており、GitHub 側のアップデートにも素早く追従しているので、とても助かっています。こちらからのプルリクもスムーズに取り込んでもらえており、コントリビューションもしやすいと感じています。

我々の運用もだいぶ安定してきており、改善したいポイントはまだあるものの、大きな辛い点はほぼなくなってきたと思います。セルフホストランナーの運用をしている組織がどれぐらいあるかはわかりませんが、この記事で書いた知見がなにかしらの役に立つことがあれば幸いです。

このような開発基盤の運用や社内の開発チームを支援する活動に興味が湧いた方は、ぜひ生産性向上チームの紹介記事もご覧ください。We're hiring!

note.com


  1. Webhook は workflow_job というイベントをサブスクライブしています
  2. この説明はかなり簡略化しており、実際は間に SQS を挟んだりしていてもう少し複雑です。詳細な構成図は philips-labs/terraform-aws-github-runner の README を参照してください。
  3. GitHub App をインストールするリポジトリを絞ればいいのではと思われた人がいるかもしれませんが、GitHub App は Webhook のリクエストを飛ばすかどうかにしか影響がないため、ランナーグループで制御しないと GitHub App をインストールしていないリポジトリでもセルフホストランナーの利用はできてしまいます
  4. self-hosted ラベルは必須ではないですがセルフホストランナーに付けることを強く推奨されているラベルなので、基本的にすべてのセルフホストランナーに付けられます