GitHub Actionsオタクによるセルフホストランナーのアーキテクチャ解説

69.6K Views

May 26, 23

スライド概要

CI/CD Test Night #6
https://testnight.connpass.com/event/281681/

profile-image

渋谷のWeb系企業で働くエンジニア 最近はテストや自動化関係に興味があります

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

関連スライド

各ページのテキスト
1.

GitHub Actionsオタクによるセルフホ ストランナーのアーキテクチャ解説 加瀬健太 @Kesin11 品質本部品質管理部SWET第二グループ 株式会社ディー・エヌ・エー © DeNA Co.,Ltd.

2.

自己紹介 加瀬健太 品質本部品質管理部SWET第二グループ ● 全社用GitHub Actionsランナーの設計 ● 社内向けCircleCI Server, Bitriseの運用 @Kesin11 @Kesin11 日課はGitHubのchangelogを読むこと 2

3.

今回のCI/CD Test Nightのテーマ GitHub Actionsセルフホストランナーのイ ンフラ運用 3

4.

セルフホストランナー運用の難しさを 解説します 4

5.

当日は多くのスライドをスキップします 󰢛 詰め込みすぎた・・・ 5

6.

当日はスキップ セルフホストランナーを シンプルに建てる方法 6

7.

当日はスキップ セルフホストランナーをシンプルに建てる方法 ● OrganizationかRepositoryの設定画面のActions -> Runnersのページ 7

8.

当日はスキップ セルフホストランナーをシンプルに建てる方法 ● コマンドを上から順に実行するだけでセルフホストランナーとして登録できる 8

9.

当日はスキップ セルフホストランナーをシンプルに建てる方法 ● 自分のチームで1-2台程度を建てるだけならOK ● 実際にはrun.shで起動するよりはsvc.sh installの方がオススメ ○ OSごとに応じたサービスに登録してくれるラッパースクリプト ○ マシンを再起動しても自動でランナープロセスが立ち上がってくれる ○ 公式ドキュメント ■ セルフホストランナーアプリケーションをサービスとして設定する 9

10.

規模の大きい組織でランナー運用する場合の課題 自分が設計時に悩んだり、他社事例やOSSのドキュメントで学んだこと ● スケールさせるためのインフラ選定 ● 前のジョブのディレクトリや認証情報が見えてしまう ● キューの待ち時間が長い ● ジョブの中でコンテナを使いたい 10

11.

スケールさせるためのインフラ選定 11

12.

VMかコンテナをスケールさせる インフラはAWSなどのクラウドサービスを使うことを前提 ● ● VMをスケーリングさせるクラウドマネージドのコンポーネントを使う ○ (ここではVM = EC2などを指す) ○ EC2 Auto Scaling FaaSなどでVMの立ち上げを自力制御 ○ ● LambdaでEC2の立ち上げ、削除 コンテナオーケストレーター ○ EKSなどのマネージドなk8s ○ ECSなどのベンダー独自のオーケストレーター 12

13.

VMとコンテナのメリデメ ● VMのメリット ○ ● ○ 一般的にVMよりは起動が早い のでインフラ面の差は少ない ○ Dockerfileはメンテしやすい VMが立ち上がるのに時間がかか る ○ コンテナのメリット github hostedランナーもVMな VMのデメリット ○ ● マシンイメージを焼くのは Dockerfileよりは大変 ● コンテナのデメリット ○ 稀にコンテナ特有の事情が表面 化する ■ dockerを使うために工夫が必 要(後述) 13

14.

規模の大きい組織でランナー運用する場合の課題 ● スケールさせるためのインフラ選定 ○ VMかコンテナ ● 前のジョブのディレクトリや認証情報が見えてしまう ● キューの待ち時間が長い ● ジョブの中でコンテナを使いたい 14

15.

前のジョブのディレクトリや 認証情報が見えてしまう 15

16.

前のジョブのディレクトリが見えてしまう ● セルフホストランナーではジョブ実行後のディレクトリは削除されない ○ おそらく毎回の無駄な git clone を避けるため ○ ジョブ内で ls コマンドに制限は無いので他のチームのジョブのディレクトリを覗け てしまう ○ 例:$HOME/actions-runner/_work 以下に各ジョブのディレクトリが存在してい る 16

17.

セルフホストランナーのマシンに認証情報が残ってしまう ● ツールの認証情報は$HOME以下に保存されるツールが多い ● 例:docker login セルフホストランナーのマシン チームAのジョブ チームBのジョブ echo $GITHUB_PAT | docker login ghcr.io \ -u USERNAME \ --password-stdin cat $HOME/.docker/config.json トークンが書き込まれる $HOME/.docker/config.json 別チームのトークンを使えて しまう!! 17

18.

当日はスキップ セルフホストランナーのマシンに認証情報が残ってしまう ● yamlを書くユーザー側が対策できる? ○ 認証情報を扱う actions の中にはジョブ終了時の post 処理で丁寧に消してくれ るものも存在する ■ ○ ソースコードを見ないと安心はできないが 自分で docker login などのコマンドを実行する場合は後始末が必要 ■ 一般的なCIサービスではジョブごとに環境が使い捨てされるのが常識なので後始末 まで考慮する人はほぼいない ● 現実的にはリテラシーで防ぐのは難しい 18

19.

複数のチームを同じランナーに相乗りさせる場合には致命的な問題 ● 関係ないチームのジョブのディレクトリを見ることができてしまう ● 関係ないチームの認証情報を見ることができてしまう 組織次第だが、受け入れられないことの方が多いのでは 19

20.

解決策:ジョブごとにランナーの環境自体を使い捨てる ● github hostedのランナーと同等の挙動を実現させる ○ ジョブを終えたランナーに追加のジョブを実行させない ○ ジョブを終えたランナーの環境(VM or コンテナ)を破棄する 20

21.

ジョブを終えたランナーに追加のジョブを実行させない ● セルフホストランナーのデフォルトではジョブ完了後に次のジョブを待つ ● ephemeralモードにするとジョブ完了時に自動でランナープロセスが終了する ○ 追加でジョブが送られることがなくなる ○ config.shの段階で--ephemeralオプションを追加することで有効化 21

22.

ジョブを終えた環境(VM or コンテナ)を破棄する ● ephemeralモードであっても同じマシンでランナープロセスを再度動かすと結局同じ環 境を引き継いでしまうので解決しない ○ ● ● コンテナの場合も同様。コンテナが使い回されたら解決しない ランナーのプロセス終了を検知して以下の処理を行う ○ 1. APIでGitHubからランナー登録を削除 ○ 2. VM or コンテナを破棄する ○ 3. 新しいVM or コンテナを立ち上げる どう実装するかはインフラ設計次第 22

23.

規模の大きい組織でランナー運用する場合の課題 ● スケールさせるためのインフラ選定 ○ ● VMかコンテナ 前のジョブのディレクトリや認証情報が見えてしまう ○ 1ジョブごとにランナーの環境自体を使い捨てる ● キューの待ち時間が長い ● ジョブの中でコンテナを使いたい 23

24.

キューの待ち時間が長い 24

25.

ランナーのオートスケール ● リクエストされたジョブ数に対してランナーが足りなければジョブは キュー待ちになる ● ジョブの数に応じていい感じにランナーをスケールアウトしてほしい ● ジョブの数が少なければ逆にランナーをスケールインしてほしい ● Webサーバーのスケール戦略と似ているところもあるが、 ランナー特有の事情も存在するので完全に一緒というわけでもない 25

26.

各社やOSSで見られるアプローチ ● 1. スケジュールでスケーリング ● 2. フリー状態のランナーの台数に応じてスケーリング ● 3. ジョブのwebhookでスケーリング 26

27.

1. スケジュールでスケーリング ● 平日の営業時間は台数を増やし、夜間休日は減らす ● MAX/MINは決め打ちになるので足りなかったり逆に余る可能性がある ● シンプルで簡単な割にインフラ費は結構節約できるので最初はオススメ ○ 1ヶ月の営業日は約20日 ○ 営業時間にバッファを入れても12時間ぐらい(09:00 - 21:00) ○ 単純計算でMAX台数で稼働させるのは一ヶ月間の1/3 27

28.

当日はスキップ 2. フリー状態のランナーの台数に応じてスケーリング ● ジョブ受付可能なフリー状態のランナーのターゲット数を事前に決めておく ● 定期的にGitHubのAPIをポーリングしてフリーなランナーの台数を調べる ● ターゲット数に対しての過不足に応じてスケールアウト・インさせる ○ ● k8sっぽい OSSのactions/actions-runner-controller(ARC)がサポートしている オートスケール方式の1つ ● APIのポーリングに頼るのでスケールアウト・インのタイミングは遅くなりがち 28

29.

3. ジョブのwebhookでスケーリング ● ジョブのキュー登録、実行開始、実行完了のwebhookをgithubから飛ばせる ● キュー登録のwebhookを受け取ったら必要な台数のランナーを都度立ち上げる ● OSSのphilips-labs/terraform-aws-github-runnerと actions/actions-runner-controllerの両方とも対応している 29

30.

オートスケールは大別するとプール方式かwebhook方式 ● ● プール方式 ○ 1. スケジュールでスケーリング ○ 2. フリー状態のランナーの台数に応じてスケーリング webhook方式 ○ ● 3. ジョブのwebhookでスケーリング ハイブリッド方式 ○ プール方式 + webhook方式の組み合わせ ○ 良いところどりできそうだが、必要台数の制御はより難しそう 30

31.

プール方式とwebhook方式の違い ● ● ゼロ台までスケールインできるかどうか ○ プール方式は最低1台は残さないとジョブが全く処理できなくなってしまう ○ webhook方式ならゼロまでスケールイン可能 ユーザーがジョブをリクエストして実行開始されるまでの待ち時間 ○ ジョブ実行されるまでの待ち時間 = VM or コンテナの起動時間 + githubにランナーを登録する時間 ■ githubにランナーに登録する時間は 30秒強ぐらい ■ VM or コンテナの立ち上げ時間はインフラによるが、 EC2なら1分-2分ぐらい? ○ webhook方式だとジョブ開始まで絶対に1-2分はかかってしまうはず ○ プール方式なら0秒だが、一方で待機時間中もインフラ費が発生する 31

32.

オートスケールを実現しているOSSの紹介 ● ● ● クラウドとGitHubのAPIを駆使する常駐型のツール ○ whywaita/myshoesはおそらくこの方式 ○ @whywaitaさんが詳しく紹介してくれるはず クラウドのマネージドツールの組み合わせ ○ philips-labs/terraform-aws-github-runner ○ @miyajanさんが詳しく紹介してくれるはず k8sのカスタムコントローラー ○ actions/actions-runner-controller 32

33.

規模の大きい組織でランナー運用する場合の課題 ● スケールさせるためのインフラ選定 ○ ● 前のジョブのディレクトリや認証情報が見えてしまう ○ ● 1ジョブごとにランナーの環境自体を使い捨てる キューの待ち時間が長い ○ ● VMかコンテナ ランナーのオートスケーリング ジョブの中でコンテナを使いたい 33

34.

ジョブの中でコンテナを使いたい 34

35.

ランナー内でDockerを使えるようにするには ● コンテナを使う ≒ Dockerを使う ● VMでランナーを動かすならDockerを起動しておくだけ ● コンテナでランナーを動かす場合は考慮することが多い ○ ○ Docker自体をどこで動かすか ■ Docker outside of Docker(DooD) ■ Docker in Docker(DinD) ■ @s4ichiさんが詳しく紹介してくれるはず コンテナタイプのactionが使えない 35

36.

当日はスキップ Docker自体をどこで動かすか(Docker outside of Docker) ● ホストマシンのdocker.sockをコンテナにマウントして使う一般的な方法 ● 別のジョブでbuild, pullしたイメージが見えてしまうので隔離できない ○ docker image lsするだけで他のジョブがビルドしたイメージが見える ○ docker runすればイメージを動かせてしまう ジョブAのコンテナ ジョブBのコンテナ docker.sock docker.sock ソケットをコンテナにマウント docker.sock Dockerエンジン ホストマシン 参考:dind(docker-in-docker)とdood(docker-outside-of-docker)でコンテナを料理する 36

37.

当日はスキップ Docker自体をどこで動かすか(Docker in Docker) ● コンテナの中で新たにDockerを立ち上げる ● 別のジョブでbuild, pullしたイメージは見えないので隔離されている ○ コンテナごとにDocker自体が別々であるため ジョブAのコンテナ ジョブBのコンテナ docker.sock docker.sock Dockerエンジン Dockerエンジン docker.sock Dockerエンジン ホストマシン 参考:dind(docker-in-docker)とdood(docker-outside-of-docker)でコンテナを料理する 37

38.

当日はスキップ Docker自体をどこで動かすか(Docker in Docker) ● ランナーのコンテナ起動にdocker run --privilegedオプションが必要 ● コンテナを動かすホストマシン自体が完全マネージドの場合は使えない ○ ● Fargate ECSやEKSを使うとしてもホストマシンとなるEC2はこちらで管理する必要がある 38

39.

当日はスキップ コンテナタイプのactionが使えない ● ● Dockerが使えたとしてもコンテナでランナーを動かしているとエラーとなる ○ ジョブ自体をコンテナ上で動かす jobs.<job_id>.container ○ ジョブとは別のコンテナを裏で立ち上げるjobs.<job_id>.services ○ 3rdパーティのコンテナタイプのactionsを動かす jobs.<job_id>.uses Error: Container feature is not supported when runner is already running inside container. ○ ランナー側で何かチェックされている? 39

40.
[beta]
当日はスキップ

コンテナタイプのactionが使えない
●

actions/runnerをエラー文で検索してみる
https://github.com/actions/runner/blob/22d1938ac420a4cb9e3255e47a91c2e43c38db29/src/Runner.Worker/ContainerOperationProvider.cs#L530-L534

var initProcessCgroup = File.ReadLines("/proc/1/cgroup");
if (initProcessCgroup.Any(x => x.IndexOf(":/docker/",
StringComparison.OrdinalIgnoreCase) >= 0))
{
throw new NotSupportedException("Container feature is not supported when
runner is already running inside container.");
}

●

ランナー内部のコードでcgroupsからコンテナ内で動作しているか判定されているので
回避は無理そう

40

41.

当日はスキップ コンテナタイプのactionが使えない、は解決できるかも ● actions/runner-container-hooks ● ジョブの中でコンテナを動かす処理を任意のコードに委譲できる仕組み ○ k8s上で動かすランナーにおいてジョブ内でコンテナが必要な場合に dockerを使う代わりに動的にpodを立ち上げるために用意した仕組み? ■ ○ https://github.com/actions/runner-container-hooks/tree/main/packages/k8s 参考実装として単純にdockerコマンドに置き換えるサンプルも存在 ■ https://github.com/actions/runner-container-hooks/tree/main/packages/doc ker ● 手元のローカルマシンでの実験では動いた! ○ 解説ブログを執筆中です(公開できたらurlを追加しておきます) 41

42.

まとめ 42

43.

まとめ 規模の大きい組織でセルフホストランナーを運用する場合の課題と解決策 ● スケールさせるためのインフラ選定 ○ ● 前のジョブのディレクトリや認証情報が見えてしまう ○ ● 1ジョブごとにランナーの環境自体を使い捨てる キューの待ち時間が長い ○ ● VMかコンテナ ランナーのオートスケーリング ジョブの中でコンテナを使いたい ○ DinDを可能にするインフラ設計 43

44.

© DeNA Co.,Ltd.