Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

40 コンテナタイプの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からコンテナ内で動作しているか判定されているので 回避は無理そう 当日はスキップ

Slide 41

Slide 41 text

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 ● 手元のローカルマシンでの実験では動いた! ○ コンテナで動かしたGithub Actionsセルフホストランナーでrootless dockerを 利用する検証 当日はスキップ

Slide 42

Slide 42 text

42 まとめ

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

© DeNA Co.,Ltd.