Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

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

Kenta Kase

May 29, 2023
Tweet

More Decks by Kenta Kase

Other Decks in Programming

Transcript

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  13. 13
    VMとコンテナのメリデメ
    ● VMのメリット
    ○ github hostedランナーもVMな
    のでインフラ面の差は少ない
    ● VMのデメリット
    ○ VMが立ち上がるのに時間がかか

    ○ マシンイメージを焼くのは
    Dockerfileよりは大変
    ● コンテナのメリット
    ○ 一般的にVMよりは起動が早い
    ○ Dockerfileはメンテしやすい
    ● コンテナのデメリット
    ○ 稀にコンテナ特有の事情が表面
    化する
    ■ dockerを使うために工夫が必
    要(後述)

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  17. 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
    トークンが書き込まれる
    別チームのトークンを使えて
    しまう!!

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  36. 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エンジン
    ソケットをコンテナにマウント
    当日はスキップ

    View Slide

  37. 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エンジン
    当日はスキップ

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  41. 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を
    利用する検証
    当日はスキップ

    View Slide

  42. 42
    まとめ

    View Slide

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

    View Slide

  44. © DeNA Co.,Ltd.

    View Slide