⚙️

Github Actionsのself-hosted runnerをCloud Build, Cloud Run上で動かす(POC段階)

2021/12/04に公開

この記事は GitHub Actions Advent Calendar 2021 の 5日目の記事です。

はじめに

普通の github.com やEnterprise Cloudをお使いのチームであればGithub Actionsを実行するマシンは通常GithubがホスティングしているVMが使われるのですが、Enterprise Server(オンプレ版)では未だにGithubホスティングのVMを使うことができません。そのためGithub Actionsを使うにはself-hosted runner(以降ランナーと呼ぶ)を自前で用意する必要があります。

自前でランナーを用意するのはGIthubホスティングのVMと比べてたくさんの苦労が存在します。

  1. ビルドに必要なツールをインストールしておく必要がある
  2. ジョブによってホストマシンの環境が壊される可能性がある
    • 例えば$HOMEに設定ファイルを配置するようなactionsが実行された場合
  3. ジョブごとに環境が完全に隔離されていない
    1. actions/checkout@v2を実行するとデフォルトでは git clean -ffdx が実行されるので前回のジョブによって生成されたものは基本的には残らないが、2の問題もあるため完全に隔離されているとは言い切れない
  4. ランナーが安定して動くインフラを用意する必要がある
  5. スケーリングが難しい

まず1と2に関してはマシン上でランナーを直接動かすのではなく、コンテナの中でランナーを動かすことである程度解決します。自分でDockerfileを書いてもいいですが、 https://github.com/myoung34/docker-github-actions-runner などランナーのインストールと実行に加えてデフォルトでjqなど基本的なツールをインストールしてくれているイメージを使うと非常に楽です。追加でインストールしたいツールがある場合もこれらのイメージをベースに拡張するのが楽でしょう。

3はジョブごとにコンテナを作り直すことで解決されます。ランナーのデフォルトの設定では一度起動するとプロセスがずっと起動しっぱなしでジョブを受け付け続けるのですが、最近追加された --ephemeral オプションを使うと1つのジョブを完了した時点でプロセスが終了されます。何らかの方法でプロセスの終了を検知してコンテナを作り直すことで、毎回VMを作り直しているGithubホスティングのランナーと同様に完全に隔離することが可能です。

4はEC2やGCEといったクラウドのマシン上でランナーを動かすのが一番簡単な解決策でしょう。

5はアプローチがいくつも存在しますが https://github.com/jonico/awesome-runners を見ると、k8sを利用するかクラウドのVMをオートスケーリングする機能を利用するかに大別されます。さらにスケジューリングで台数を変動させる単純なスケーリングか、実際のジョブ数や負荷に応じて自動で最適な数に調整する高度なオートスケーリング機能を有しているものも存在するようです。

自分が所属する組織ならではの制約

k8sによる高度なオートスケーリングを実現できている例として https://github.com/actions-runner-controller/actions-runner-controllerhttps://github.com/whywaita/myshoes があります。ですがこれらはk8sのコントローラーを自作したり、専用のGithub Appsを用意する必要があるようです。

組織全体のインフラを管理しているようなSREチームが存在する組織であれば運用可能でしょうが、自分が所属する組織では状況が異なります。基本的には各チームがそれぞれでクラウド上にインフラの構築と管理をしているため、必ずしもEKSやGKEといったk8s環境を保持しているとも限らないのです。

また、これは個人的なスキルの問題ですが3rdパーティ製のコントローラーを導入してメンテできるほどk8sに習熟していないため、もっと手軽な手段が存在しないかと検討することにしました。

クラウドのマネージドサービス上でランナーを動かす

自分自身と所属している組織の問題を踏まえた上で、誰でもランナーを手軽に動かすことができ、さらにオートスケーリングを実現する方法としてCloud BuildとCloud Run上にランナーを動かすというアイディアを思いつきました。

k8sは不要、インスタンスの管理すら不要になり、どちらのサービスも実際に使った分だけの従量課金。さらにインスタンスの台数も難しい設定を無しにオートスケールしてくれて、使わない時間は0台まで減らされるので夜間分の料金はかからないとメリットだらけです。

この最高に理想的とも言える方法が実際に実現可能かどうかを確認するために検証しました。

前提となるランナーのオプションや環境

  • ランナーはOrganizationスコープ
    • 各リポジトリにいちいちランナーを追加しなくても済むようにOrganizationに立てる
  • --ephemeralでコンテナは毎回立て直す(一部例外あり)
    • 前述のようにジョブごとの環境を完全に隔離するため
  • dockerを使えるようにする(一部例外あり)
    • コンテナの中でランナーを動かしているがdockerコマンドは使えるようにする
  • GitHubはcom版
    • Enterprise Serverはまだランナーの--ephemeralをサポートしていなかったり、後述のwebhookの機能が来ていない

docker上で実行

最も簡単な初歩として単純に1つのコンテナを実行するだけのパターンです。この後の実用的なパターンの全てがこの単純にdocker上で実行するパターンの応用のため、まずはここからスタートです。

docker run -d --restart always --name github-runner \
  -e RUNNER_NAME_PREFIX="ephemeral-docker" \
  -e ACCESS_TOKEN="${RUNNER_PAT}" \
  -e RUNNER_SCOPE="org" \
  -e ORG_NAME="${ORG}" \
  -e LABELS="container" \
  -e EPHEMERAL=1 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  myoung34/github-runner:latest

ちょっとした応用としては、dockerのソケットをマウントすることでコンテナの中であってもdockerを利用することを可能にしています。

各環境変数については https://github.com/myoung34/docker-github-actions-runner#environment-variables を参照。

k8s上で実行

自分の中ではk8sは本命ではないものの、すでにEKSやGKEなどのk8s環境を持っているチームであれば構築コストが安い上、例えばGPU付きマシンを扱えるなどの最高の自由度を手にできるというメリットもあるのでまずはk8sを用いた方法も検証しました。

ただし3rd製のコントローラーは使わず、標準で用意されている機能だけで実現できるかを確認しました。

Deployment(不採用)

固定数のランナーを常に立ち上げておくような使い方であればDeploymentに先程のdocker runのコマンドを書き下してreplica数を設定するだけで簡単に実現が可能です。

ただし--ephemeralオプションで動かす場合にはうまく動かないです。理由は、--ephemeralだと1つのジョブを完了するとランナーのプロセスが終了されるのですが、Deploymentの挙動的にはこれはPodが異常終了した時と同じにみなされるためです。

Deploymentはpodの数を常にreplicaと同数に揃える挙動をするため、終了したPodを再起動させることで動いているpod数を増やそうとしてくれるのですがrestartを重ねるたびに待機時間が伸びていきます。本来Podが勝手に終了するのは何らかの理由でPodが正常に立ち上がらなかった場合なので、k8sでは無駄にリスタートし続けないようにexponential back offによってリスタートするたびに起動までのwait時間を増やす仕組みになっていることが原因です。

--ephemeralオプション付きのランナーでは1ジョブごとに終了することは正しい挙動なのですが、リスタートするたびに次のPodが立ち上がるまでの時間が伸びてしまうのでDeploymentでは実用的ではないと判断しました。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: actions-runner
spec:
  replicas: 1
  selector:
    matchLabels:
      app: actions-runner
  template:
    metadata:
      labels:
        app: actions-runner
    spec:
      volumes:
      - name: dockersock
        hostPath:
          path: /var/run/docker.sock
      containers:
      - name: runner
        image: myoung34/github-runner:latest
        env:
        - name: EPHEMERAL
          value: "1"
        - name: ACCESS_TOKEN
          valueFrom:
            configMapKeyRef:
              name: runner-config # あらかじめConfigMapを作成しておく
              key: ACCESS_TOKEN
        - name: ORG_NAME
          valueFrom:
            configMapKeyRef:
              name: runner-config # あらかじめConfigMapを作成しておく
              key: ORG_NAME
        - name: RUNNER_SCOPE
          value: "org"
        - name: LABELS
          value: container
        - name: RUNNER_NAME_PREFIX
          value: ephemeral-k8s
        volumeMounts:
        - name: dockersock
          mountPath: /var/run/docker.sock

CronJob(採用)

Deploymentでは上手くいかなかったので代わりにJobを使うことを思いつきました。JobはPodのプロセスが正常に終了することが正しい挙動として扱われているので--ephemeralなランナーとぴったりなはずです。

しかしJobは completion で設定した回数だけPodが正常に終了するとJob自体が消えてしまいます。ランナーの役割的に無限にPodが立ち上がり続けて欲しいのですがcompletion の数を無限として設定はできないようです。 completion に膨大な数を設定することで実質的に無限にできるのですが、そうすると今度はランナーのイメージを更新するなどの理由で再度 kubectl apply をする時に手動で古いJobを削除する必要があります。

そこで、CronJobを短い間隔(1分とか)で動かしてJobを無限に作り続けることにしました。このときJobの completion はかなり小さな数にしておき、CronJobのForbid を有効にすることにより最短で1分間隔で新しいJobに生まれ変わるようになります。こうすることでランナーは無限に起動しつつ、kubectl apply で新しい設定をデプロイした場合にはJobが生まれ変わるタイミングで新しい設定を無停止で反映できるようになりました。

Deploymentでの問題を解決しつつ、Deploymentのようにデプロイを無停止に行えるという点でこのアイディアはかなり良かったと思っています。

apiVersion: batch/v1
kind: CronJob
metadata:
  name: actions-runner
spec:
  schedule: "*/1 * * * *"
  concurrencyPolicy: Forbid
  jobTemplate:
    spec:
      parallelism: 2
      completions: 4
      ttlSecondsAfterFinished: 100
      template:
        metadata:
          labels:
            app: actions-runner
        spec:
          restartPolicy: Never
          volumes:
          - name: dockersock
            hostPath:
              path: /var/run/docker.sock
          containers:
          - name: runner
            image: myoung34/github-runner:latest
            env:
            - name: EPHEMERAL
              value: "1"
            - name: ACCESS_TOKEN
              valueFrom:
                configMapKeyRef:
                  name: runner-config # あらかじめConfigMapを作成しておく
                  key: ACCESS_TOKEN
            - name: ORG_NAME
              valueFrom:
                configMapKeyRef:
                  name: runner-config # あらかじめConfigMapを作成しておく
                  key: ORG_NAME
            - name: RUNNER_SCOPE
              value: "org"
            - name: LABELS
              value: container
            - name: RUNNER_NAME_PREFIX
              value: ephemeral-k8s
            volumeMounts:
            - name: dockersock
              mountPath: /var/run/docker.sock

Cloud Build

かなり長くなりましたがここまでが前置きで、ここからが本題です。

Cloud Buildは元々がGithubと連携して動作するCIのサービスなので、ランナーのように常駐するタイプのプロセスを動かす環境として本来は全く向いていないです。

しかし、今年のCloud Buildのアップデートによりwebhook経由で起動できるようになったことと、ランナーにも--ephemeral機能が追加されたことで以下の一連の流れが可能になりました。

  • Github Actionsのジョブを開始
  • ジョブ開始のwebhookを受けてCloud Buildを起動
  • Cloud Buildの中でランナーを起動
  • ランナーがGithub Actionsのジョブを実行する
  • ジョブが終了したらCloud Buildも終了

GithubとCloud BuildのWebhookの設定

Cloud Buildのwebhookトリガーはまだalpha版のため今回はCLIではなく手動で設定しました。

まずCloud Buildのwebhookトリガーの設定を途中まで作成します。トリガーイベントをwebhookにしてシークレットの設定を行いwebhook URLを入手します。

次はGithubのOrganizationのwebhook設定で workflow_job だけを有効化してURLにはCloud Buildで発行されたURLを指定します。

ビルド構成の設定

今回の構成ではGithubへのpushでCloud Buildを起動するわけではないため、Cloud Buildのジョブ自体はリポジトリで管理しません。代わりにインラインYAMとして以下のようなコードを設定します。

steps:
  - name: myoung34/github-runner:latest
    dir: /actions-runner # Cloud Buildのデフォルトは/workspaceだが、myoung34/github-runnerが/actions-runnerを前提としているため変更する
    env:
      - 'EPHEMERAL=1'
      - 'RUNNER_SCOPE=org'
      - 'LABELS=container'
      - 'RUNNER_NAME_PREFIX=ephemeral-cloudbuild'
    secretEnv: ['ACCESS_TOKEN', 'ORG_NAME']
availableSecrets:
  secretManager:
  - versionName: projects/YOUR_PROJECT_ID/secrets/github_org_admin_pat/versions/1
    env: 'ACCESS_TOKEN'
  - versionName: projects/YOUR_PROJECT_ID/secrets/github_org_name/versions/1
    env: 'ORG_NAME'
timeout: 600s
# options:
#   machineType: E2_HIGHCPU_8

timeoutは万が一この構成がうまく動かず、Cloud Build内でランナーが無限に待機状態に陥ってしまったときのために安全弁として設定しています。Cloud Buildで10分に設定するとGithub Actions側でそれを超えた時間のジョブの実行ができなくなってしまうため、実用上はもっと長くする方がよいでしょう。

machineTypeはハイスペックなマシンで実行したい場合に設定してください。

webhookペイロードの参照とフィルタリング

workflow_job のwebhookはジョブがキューに積まれたとき(queued)、実行を開始したとき(in_progress)、完了したとき(completed)のそれぞれで送られますが、今回の構成で必要なのはqueuedだけなのでCloud Build側でフィルタリングをします。

フィルタリングするためには変数を参照できるようにする必要があるため、まずは代入変数(Substituting variable value)を使用してwebhookのペイロードを参照できるようにします。

pushトリガーの場合などに各種情報を参照する方法はドキュメントに書いてあるのですがwebhookのペイロードを参照する方法はまだドキュメントに書いていなかったのでstackoverflowに助けられました。

https://stackoverflow.com/questions/66875343/post-body-for-google-cloud-build-webhook-triggers

最低限必要なのはこの2つです。webhookの他の情報も参照したい場合は任意で追加してください。

  • _ACTION$(body.action)
  • _LABELS$(body.workflow_job.labels)

そしてフィルタはこのように設定します。ここではコードを貼りますが、実際に設定するときはGUI上でANDやORの組み合わせで設定することになります。

_ACTION.matches("queued") &&
_LABELS.matches(".*self-hosted.*") &&
_LABELS.matches(".*container.*")

まずqueued でジョブ開始時のwebhookだけに限定した上で、さらに特定のラベルだけで起動するように設定しています。GithubからのwebhookではラベルはJSONの配列なのですが、Cloud Build側で配列としてパースはできないようなので雑な正規表現でフィルタしています。

SecretManagerにシークレット情報を登録(オプション)

先ほどのコードで ACCESS_TOKENORG_NAME としていたところです。トークンが丸見えでも問題なければYAMLにベタ書きでいいですが、ちゃんと隠しておきたい場合はSecretMangerとCloud Buildを連携させましょう。

メリット

インスタンスやk8sのインフラを自分でメンテする必要が全くない

Cloud Buildはフルマネージドなのでインスタンスの立ち上げや終了は全てGCPが面倒を見てくれます。

必要なときに必要な並列数分だけ確保される完璧なオートスケーリング

Github Actionsの1ジョブに対してCloud Buildが1対1で立ち上がるため、自動的に完璧なオートスケーリングが実現されます[1]

マシンのスペックをある程度カスタムできる

Cloud BuildはCPUのスペック、SSDのサイズなどをYAMLで手軽に設定可能です。当然料金は割増になりますが。

デメリットと懸念点

Cloud Buildが立ち上がってランナーがジョブを受け付けるまで1分弱ぐらいかかる

Cloud Buildは毎回 docker pull から始めるため、本来一瞬で終わるような軽いジョブでも最低1分弱程度の時間がかかっていました。

myoung34/github-runnerはそこそこ大きいイメージなので、自前でもっと軽いイメージを作れば改善できる可能性はありそうです。

もし何らかの理由でwebhookとCloud Buildの1対1の関係が崩れた場合のリカバリーができない

k8sのCronJob、後述のCloud Runではwebhookが来る前からランナーを常駐させることが可能ですが、Cloud Buildの場合はwebhookと完全に1対1の関係になるのでそのような構成は不可能です。そのため、何らかの理由で1対1の関係が崩れた場合はGithub Actionsのキューに積まれたジョブを処理するランナーが永久に立ち上がらないということになる可能性が起こりそうです。

今回の検証ではそのような事態は発生しなかったのですが、大量にジョブを同時実行させるような負荷がかかる状況でどうなるかは試してみないとわからないです。

Cloud Run

Cloud Runは今まではwebサーバー用途にしか向いていませんでしたが、最近のアップデートで追加されたAlways on CPU(Preview)モードにすることでリクエストとは関係なくプロセスを動かせるようになりました。これを利用してGithub Actionsのランナーを動かします。

Cloud Runは難しい設定をせずとも自動でオートスケーリング可能であり、逆に使わないときは最小0コンテナまでスケールインが可能なのでランナーを動かす環境として向いていると言えます。

Cloud Runの制約

何でもいいのでwebサーバーをバックグラウンドで動かす必要がある

一方でCloud RunはAlways on CPUモードにしたとしてもいずれかのポートでリクエストを受け付けられるようにしておく必要があります。いずれのポートもリッスンしていないコンテナではデプロイ自体が失敗してしまいます。そのためGithub Actionsのランナーとしては全く不要なのですが、何らかのwebサーバーをバックグラウンドで動かすようにランナーのコンテナに機能を追加する必要があります。

今回はmyoung34/github-runnerに元々組み込まれていたPython3を使ってGET, POSTで200を返すだけの雑なサーバーを書いてバックグラウンドで動かすことにしました。

webhookのフィルタリング機能を自作する必要がある

Cloud Buildで解説したGithubの workflow_job webhookのうち queued だけを受け付けるフィルタリングがCloud Runの構成でも必要です。

Cloud Buildには組み込みでフィルタリング機能がありましたが、Cloud Runにはそのような機能がありません。そのためwebhookのフィルタリング機能を自前でコードを書いて実現する必要があります。

しかし、Cloud Runは受け付けたリクエストの数もオートスケールに関係する(?)[2]ようなので自前でフィルタリングしていようがwebhookを受け付けた時点でランナーが無駄にスケールしてしまいます。

そこでwebhookをフィルタリングするだけのFrontendと、ランナーを動かすBackendの2つをそれぞれ別のコンテナとして実装し、2つを別々のCloud Runにデプロイすることにします。最終的には以下のフローでランナーがジョブを受け付けるようになります。

  • Github Actionsのジョブを起動
    • webhookをFrontendに流す
  • Frontendでフィルタリング
    • action: queued とジョブのラベルでフィルタリング
    • フィルタを通ったwebhookをそのままBackendに流す
  • Backendでランナーを立ち上げる
    • Frontendでフィルタされたwebhookの量に応じてオートスケールされる(?)
  • BackendのランナーがGithub Actionsのジョブを受け付ける

これを実現したサンプルコードを全て載せると長くなってしまうためGithubにコードを置きました。興味がある方はGithubの方を見てみてください。Cloud Runをデプロイするコマンドも併せて載せてあります。

https://github.com/Kesin11/self-hosted-runner-template/tree/v1.0/org-cloud-run

メリット

起動が早い

Cloud Buildに比べたメリットとしては最初からコンテナがpull済みの状態のため起動が早いです。さらにMINコンテナ数を1以上に設定しておけばコールドスタート問題も解消されます。

起動したコンテナがしばらく残るのでローカルキャッシュも残る

Cloud Buildの場合は毎回新しいVMがアサインされるのでローカルファイルは一切残りませんでした。これはビルド本来としては正しい挙動ですが、一方で巨大なgitリポジトリを相手にする場合などは毎回git cloneを行うだけで相当時間がかかってしまうため、キャッシュ観点から前ビルドのファイルが残っていると嬉しいケースもあります。

Cloud Runでは一度起動したコンテナはリクエストが全くなかったとしても数十分程度は維持されるようなので、キャッシュをGCSに保存するなどの高速化テクニックを使わずともローカルファイルによってある程度キャッシュが効くはず。リクエストが来なくなったらコンテナと共にキャッシュも破棄されることになるので、実質的にある程度の間隔でキャッシュのクリーンも行われます。

しかしCloud Runのディスクがどれぐらいの大きさなのかまで調べていないので、もしかするとそもそも数GBあるような巨大なgitリポジトリの場合はCloud Run上で扱うのは無理かもしれない?

スペックを柔軟に選択可能

Cloud Buildではスペックがせいぜい3種類しか選べなかった[3]一方、Cloud RunではCPUとメモリの値を直接指定できるためほぼ自由なスペックが可能です。

デメリットと懸念点

構成が複雑

自前でwebhookのフィルタリング機能を実装する必要があることと、ランナーのオートスケーリング暴発を回避するためにFrontendとBackendの2つのCloud Runを用意する必要があるのでCloud Buildと比べると構成が複雑になってしまいます。

ジョブの中でdockerが使えない

Cloud Runではホストマシンのdockerソケットを使うことができないため、Github Actionsの中で docker コマンドを使うことができません

これはかなり致命的なのでdockerを一切使わないチームではない限りは、結局他の方法でdockerが使えるランナーを立てる必要があるでしょう。

ダミーのwebサーバーを立てるためにDockerfileを自前で用意する必要がある

Cloud Runの制約で説明したように、実際には全く不要であったとしても何らかのwebサーバーのプロセスが立ち上がっている必要があります。

ランナーにはそのような機能は本来不要なため、例えば myoung34/github-runner には当然そのような機能は存在していません。そのため自分でDockerfileを書いて拡張が必要になるのと、ビルドしたイメージをGCPのArtifact Registryなどにdocker pushしておく必要があります。

ephemeralが向いていない

--ephemeralでの起動を試したところ1ジョブを完了するとコンテナが終了し、実際にCloud Run上でも課金されるコンテナ数からはカウントされなくなるようでした。k8sのDeploymentでは異常終了とみなされてrestart時間が次第に長くなる問題が存在しましたが、Cloud Runではそのようなデメリットはおそらく無いように見えました。

ただ、Cloud Buildではwebhookと立ち上がるランナーの関係が1:1だったのに対して、Cloud Runではオートスケーリングが自動なので複数のwebhookに対して立ち上がるランナーは1つだけというN:1の状況になることがあります。従って、Cloud Run上で全てのephemeralなランナーがジョブを完了した判定をしたとしても実際にはGithub Actions上のジョブはまだキューに残ったままという状況が容易に発生しました。

対策としてはephemeralでの運用をやめるか、あるいはMINコンテナ数を最低でも1は確保しておくことです。

ephemeralの運用を諦めれば、CPUが仕事をしている限り(=ジョブがキューに残っている状態)はコンテナの数はゼロにはならないと予想してます[4]。一方で夜間や休日のように全くジョブが実行されない時間がある程度続けば最小で0コンテナまでスケールインしてくれます。

ephemeralを諦めない場合は、MINコンテナ数を0ではなく1以上にすることで最低限1つのランナーは常に動いている状態を維持できるので、キューのジョブが全く実行されないという状態は回避できるはずです。ただし、24時間365日分の料金がかかってしまいますが。

Cloud BuildとCloud Runどちらを採用するか?

Cloud Runは0台からのコールドスタートであったとしてもCloud Buildよりは圧倒的に起動が早いため、例えばGithub上のラベルを操作するなどのAPIを叩くだけの軽いジョブには向いているでしょう。

一方でCloud RunはFrontend, Backendの2つをデプロイする構成の複雑さに加えて、dockerが使えないという特大のデメリットが存在します。

総括すると、どんなに軽いジョブでも1分程度のスタートアップ時間を許容できるのであればCloud Buildの方が圧倒的に簡単でオススメ。もしも軽いジョブを素早く起動したいというニーズが強いのであれば多少の面倒を乗り越えてCloud Runを使うのもアリだと思います。

自分としては、そもそもとにかくシンプルな構成かつメンテフリーを追求してk8s以外の方法を検討し始めたという経緯があるので、起動に多少時間がかかったとしてもCloud Build1本で運用しようかなと考えています。

実際に利用するために残ってる障壁

ここまでで現在の github.com であれば今日から使うことが可能であることを確認できましたが、業務で使うためにはまだいくつか障壁が残っています。

Github Enterprise Serverのバージョン

workflow_job のwebhookとランナーの--ephemeralがサポートされている必要があります。Enterprise ServerのChangelogを見たところ、3.3からサポートされるようですが執筆時点でまだrcのためもうしばらくかかりそうです。

https://docs.github.com/ja/enterprise-server@3.3/admin/release-notes

社内ネットワークへの接続

各社のネットワーク事情次第になるかと思いますが、IPが固定化されていないCloud BuildとCloud Runから社内ネットワークへ疎通させるためには何らかの手段が必要かと思います。

おそらくCloud BuildのPrivate Pool、Serverless VPC Access Conectorあたりを使えば何とかなりそう?

https://cloud.google.com/blog/ja/products/devops-sre/cloud-build-private-pools-offers-cicd-for-private-networks

https://cloud.google.com/run/docs/configuring/connecting-vpc

まとめ

低コスト運用のGithub Ationsセルフホストランナーをk8s, Cloud Run, Cloud Buildで実現する方法を色々試してみました。冒頭にも書きましたがあくまでまだ検証であり実現可能な見込みが一応ありそうだというレベルの内容です。

こんなニッチなネタの長文記事をここまで読んで頂いた方は、おそらく自分と似た状況にいらっしゃる同志でしょう。この記事で誤っている点を発見されたり、別の手法を思いついた方がいらっしゃいましたらZennのコメント機能でもTwitterでもGitHub Actions User Group JapanのSlackでもどこでも構いませんのでコメント、ディスカッションできると嬉しいです。

検証に使ったコードやコマンド一式について

コードやコマンドでみなさんに公開できない情報(シークレットやGCPのプロジェクト名)は一部別名に置き換えているので、試される場合には自身の環境に合うように書き換えてください。

https://github.com/Kesin11/self-hosted-runner-template/tree/v1.0

脚注
  1. Cloud Buildのquotaに並列数の上限はあるらしいですが、問い合わせれば増やせそう? ↩︎

  2. Cloud Run自体にそれほど詳しくないので間違ってるかもしれません。 ↩︎

  3. Cloud Buildもプライベートプールを使用することで通常よりも豊富なスペックから選択可能になるようです(まだ使ったことない) ↩︎

  4. Cloud Runのオートスケーリングの挙動について詳しくないので間違っているかもしれません。 ↩︎

Discussion