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. 9 セルフホストランナーをシンプルに建てる方法 • 自分のチームで1-2台程度を建てるだけならOK • 実際にはrun.shで起動するよりはsvc.sh installの方がオススメ ◦ OSごとに応じたサービスに登録してくれるラッパースクリプト ◦

    マシンを再起動しても自動でランナープロセスが立ち上がってくれる ◦ 公式ドキュメント ▪ セルフホストランナーアプリケーションをサービスとして設定する 当日はスキップ
  2. 12 VMかコンテナをスケールさせる インフラはAWSなどのクラウドサービスを使うことを前提 • VMをスケーリングさせるクラウドマネージドのコンポーネントを使う ◦ (ここではVM = EC2などを指す) ◦

    EC2 Auto Scaling • FaaSなどでVMの立ち上げを自力制御 ◦ LambdaでEC2の立ち上げ、削除 • コンテナオーケストレーター ◦ EKSなどのマネージドなk8s ◦ ECSなどのベンダー独自のオーケストレーター
  3. 13 VMとコンテナのメリデメ • VMのメリット ◦ github hostedランナーもVMな のでインフラ面の差は少ない • VMのデメリット

    ◦ VMが立ち上がるのに時間がかか る ◦ マシンイメージを焼くのは Dockerfileよりは大変 • コンテナのメリット ◦ 一般的にVMよりは起動が早い ◦ Dockerfileはメンテしやすい • コンテナのデメリット ◦ 稀にコンテナ特有の事情が表面 化する ▪ dockerを使うために工夫が必 要(後述)
  4. 16 前のジョブのディレクトリが見えてしまう • セルフホストランナーではジョブ実行後のディレクトリは削除されない ◦ おそらく毎回の無駄な git clone を避けるため ◦

    ジョブ内で ls コマンドに制限は無いので他のチームのジョブのディレクトリを覗け てしまう ◦ 例:$HOME/actions-runner/_work 以下に各ジョブのディレクトリが存在してい る
  5. 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 トークンが書き込まれる 別チームのトークンを使えて しまう!!
  6. 18 セルフホストランナーのマシンに認証情報が残ってしまう • yamlを書くユーザー側が対策できる? ◦ 認証情報を扱う actions の中にはジョブ終了時の post 処理で丁寧に消してくれ

    るものも存在する ▪ ソースコードを見ないと安心はできないが ◦ 自分で docker login などのコマンドを実行する場合は後始末が必要 ▪ 一般的なCIサービスではジョブごとに環境が使い捨てされるのが常識なので後始末 まで考慮する人はほぼいない • 現実的にはリテラシーで防ぐのは難しい 当日はスキップ
  7. 22 ジョブを終えた環境(VM or コンテナ)を破棄する • ephemeralモードであっても同じマシンでランナープロセスを再度動かすと結局同じ環 境を引き継いでしまうので解決しない ◦ コンテナの場合も同様。コンテナが使い回されたら解決しない •

    ランナーのプロセス終了を検知して以下の処理を行う ◦ 1. APIでGitHubからランナー登録を削除 ◦ 2. VM or コンテナを破棄する ◦ 3. 新しいVM or コンテナを立ち上げる • どう実装するかはインフラ設計次第
  8. 28 2. フリー状態のランナーの台数に応じてスケーリング • ジョブ受付可能なフリー状態のランナーのターゲット数を事前に決めておく • 定期的にGitHubのAPIをポーリングしてフリーなランナーの台数を調べる • ターゲット数に対しての過不足に応じてスケールアウト・インさせる ◦

    k8sっぽい • OSSのactions/actions-runner-controller(ARC)がサポートしている オートスケール方式の1つ • APIのポーリングに頼るのでスケールアウト・インのタイミングは遅くなりがち 当日はスキップ
  9. 30 オートスケールは大別するとプール方式かwebhook方式 • プール方式 ◦ 1. スケジュールでスケーリング ◦ 2. フリー状態のランナーの台数に応じてスケーリング

    • webhook方式 ◦ 3. ジョブのwebhookでスケーリング • ハイブリッド方式 ◦ プール方式 + webhook方式の組み合わせ ◦ 良いところどりできそうだが、必要台数の制御はより難しそう
  10. 31 プール方式とwebhook方式の違い • ゼロ台までスケールインできるかどうか ◦ プール方式は最低1台は残さないとジョブが全く処理できなくなってしまう ◦ webhook方式ならゼロまでスケールイン可能 • ユーザーがジョブをリクエストして実行開始されるまでの待ち時間

    ◦ ジョブ実行されるまでの待ち時間 =  VM or コンテナの起動時間 + githubにランナーを登録する時間 ▪ githubにランナーに登録する時間は 30秒強ぐらい ▪ VM or コンテナの立ち上げ時間はインフラによるが、 EC2なら1分-2分ぐらい? ◦ webhook方式だとジョブ開始まで絶対に1-2分はかかってしまうはず ◦ プール方式なら0秒だが、一方で待機時間中もインフラ費が発生する
  11. 35 ランナー内でDockerを使えるようにするには • コンテナを使う ≒ Dockerを使う • VMでランナーを動かすならDockerを起動しておくだけ • コンテナでランナーを動かす場合は考慮することが多い

    ◦ Docker自体をどこで動かすか ▪ Docker outside of Docker(DooD) ▪ Docker in Docker(DinD) ▪ @s4ichiさんが詳しく紹介してくれるはず ◦ コンテナタイプのactionが使えない
  12. 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エンジン ソケットをコンテナにマウント 当日はスキップ
  13. 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エンジン 当日はスキップ
  14. 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. ◦ ランナー側で何かチェックされている? 当日はスキップ
  15. 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からコンテナ内で動作しているか判定されているので 回避は無理そう 当日はスキップ
  16. 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を 利用する検証 当日はスキップ
  17. 43 まとめ 規模の大きい組織でセルフホストランナーを運用する場合の課題と解決策 • スケールさせるためのインフラ選定 ◦ VMかコンテナ • 前のジョブのディレクトリや認証情報が見えてしまう ◦

    1ジョブごとにランナーの環境自体を使い捨てる • キューの待ち時間が長い ◦ ランナーのオートスケーリング • ジョブの中でコンテナを使いたい ◦ DinDを可能にするインフラ設計