ARC self-hosted runner で homelab 内部サービスを GitHub Actions から叩く

Progress 19 / 19
目次

Github Self-hosted runner を homelab の K8s に導入した話をします。

runs-on: github-runner を指定するだけで、クラスター内部の Service に届くようになります。homelab の外部公開も不要です。ただいくつか落とし穴があって、そこにしっかりハマりました。

事前準備:GitHub App の登録

ARC は PAT(Personal Access Token)でも動きます。ですが、何となく今回は GitHub App を使いました。App の追加と削除で権限を操作できるので、そこら辺の認知負荷が若干軽めな感じがします。

GitHub の org の Settings → Developer settings → GitHub Apps → New GitHub App から作成します。設定するのはこれだけです。ARC v2(scale-set モード)はジョブを webhook ではなくポーリングで取得するので、webhook の設定は不要です。

項目設定値
Repository permissions → ActionsRead-only
Organization permissions → Self-hosted runnersRead and write
WebhooksActive のチェックを外す(不要)

App を作成したら App ID、Installation ID、秘密鍵(.pem)を控えておきます。Installation ID は App を org にインストールした後の URL(github.com/organizations/<org>/settings/installations/<id>)から確認できます。

これらを K8s の Secret として用意します。

# github-runner-secrets
github_app_id: "<App ID>"
github_app_installation_id: "<Installation ID>"
github_app_private_key: |
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----

RBAC 問題 & デバッグしにくい問題

self-hosted runner は ephemeral runner として実行されるため、ジョブが成功しても失敗してもすぐに Pod ごとログが消えます。これが地味にデバッグしにくいです。

ログを kubectl logs -f で watch しながらジョブをトリガーする、という手順を踏む必要があります。ログ基盤をちゃんと用意していないのが悪いのですが、面倒ポイントとして覚えておくべきです。

権限まわりも問題が頻発しました。権限の問題がある場合は実行ログに forbidden: attempt to grant extra privileges のようなエラーが出るので、まずそこを確認しましょう。ログがすぐ消える問題と重なって、原因に気づくまでそれなりに時間がかかりました。

ARC の Helm chart は controller が自動で ServiceAccount と Role を作りますが、ArgoCD はこれを「管理対象外のリソース」として prune してしまいます。解決策は RBAC リソースを Helm chart の templates に明示的に書いて ArgoCD に管理させることです。ARC が必要とする ServiceAccount を listener pod 用と runner pod 用で 2 つ宣言したところ、prune されなくなりました。

ARC の設定

ARC (Actions Runner Controller) は GitHub 公式の Kubernetes Operator で、Actions の runner を K8s Pod として管理します。v2 の scale-set モードではジョブがキューに入ったときだけ Pod を起動して終わったら削除するゼロスケール構成がデフォルトです。

gha-runner-scale-set:
githubConfigUrl: "https://github.com/otama-homelab"
minRunners: 0
maxRunners: 5
containerMode:
type: "kubernetes-novolume"

githubConfigUrl は org 単位にしています。リポジトリ単位にすると新しいリポジトリを追加するたびに再登録が必要になるので、org 単位のほうが運用が楽です。

なぜ self-hosted runner が必要か

GitHub からは homelab 内部の外部非公開 Service に webhook などのリクエストが届きません。外部公開なしに内部サービスを操作したい場面は意外と多くて、ArgoCD のトリガー、クラスター内 API の呼び出し、内部向けにしか公開していないサービスへのデプロイなど色々あります。

Runner Pod はクラスター内の通常の Pod なので、K8s の内部 DNS でどの Service にも届きます。ワークフロー側は runs-on: github-runner に変えるだけです。

同じ構成で詰まっている方の参考になれば幸いです。