Autoscaling Runner GitHub Actions di Kubernetes dengan ARC

Foto oleh Mk2010 via Wikimedia Commons (CC BY-SA 4.0)
ARC adalah operator Kubernetes yang didukung resmi oleh GitHub untuk menjalankan dan menskalakan runner self-hosted GitHub Actions sebagai pod. Anda memasang controller sekali per cluster, lalu membuat satu atau lebih runner scale set. Sebuah listener memantau antrean job GitHub dan membuat pod runner ephemeral sesuai permintaan, lalu turun kembali ke nol saat idle.
Penskalaan digerakkan langsung oleh antrean job GitHub Actions, bukan oleh CPU atau endpoint metrik. Pod listener ringan menjaga koneksi long-poll ke GitHub; ketika job yang menargetkan label runner Anda antre, GitHub memberi tahu listener berapa runner yang dibutuhkan dan ia mem-patch EphemeralRunnerSet sehingga Kubernetes membuat sejumlah pod itu. Ini membuat penskalaan cepat sekaligus akurat.
minRunners adalah jumlah runner idle yang dijaga tetap hangat dan siap, sedangkan maxRunners adalah batas keras total pod sekaligus. Menyetel minRunners ke 0 memberi scale-to-zero sejati sehingga Anda tidak membayar apa pun saat idle, dengan konsekuensi cold start pada job pertama. Jumlah yang berjalan kira-kira minRunners ditambah job aktif, dibatasi maxRunners.
Gunakan dind untuk kebanyakan tim — ia menjalankan sidecar dockerd privileged dan langsung berfungsi untuk docker build, service container, dan container action. Gunakan mode kubernetes ketika kebijakan keamanan Anda melarang pod privileged; ia menjalankan setiap langkah sebagai pod terpisah tetapi butuh persistent volume dan setup lebih banyak. Jika job Anda hanya menjalankan skrip dan tes, Anda tidak butuh keduanya.
Tidak. Versi lama ARC memerlukan cert-manager sebagai prasyarat, tetapi controller gha-runner-scale-set modern tidak lagi membutuhkannya, sehingga instalasi controller cukup satu perintah Helm. Pasangkan ARC dengan Cluster Autoscaler Kubernetes atau Karpenter agar node idle juga dihapus, bukan hanya pod runner idle.

Foto oleh Mk2010 via Wikimedia Commons (CC BY-SA 4.0)
Ringkasan Utama
Actions Runner Controller (ARC) menjalankan runner self-hosted GitHub Actions Anda sebagai pod Kubernetes ephemeral. Listener-nya memantau antrean job dan menskalakan pod runner naik saat ada job antre lalu turun ke nol saat idle, jadi Anda berhenti membayar VM yang selalu menyala dan setiap job dimulai pada runner sekali pakai yang bersih.
Saya menjalankan runner self-hosted GitHub Actions di VM biasa dalam waktu lama, dan dua mode kegagalannya selalu sama. Entah beberapa runner yang selalu menyala menganggur membakar biaya sepanjang malam, atau lonjakan push membuat job antre sepuluh menit karena saya membatasi jumlah mesin. Lebih buruk lagi, runner persisten menumpuk state antar job, sehingga sisa node_modules, cache Docker yang teracuni, atau variabel lingkungan liar dari build sebelumnya bocor ke build berikutnya. Actions Runner Controller memperbaiki kedua masalah sekaligus, dan ini adalah cara resmi yang direkomendasikan GitHub untuk menskalakan runner di Kubernetes.
Tulisan ini membahas arsitektur ARC modern (runner scale sets, bukan CRD RunnerDeployment yang sudah usang), bagaimana penskalaan sebenarnya memutuskan berapa banyak pod dibuat, dua container mode yang harus Anda pilih, dan pengaturan spesifik yang mengontrol biaya. Semua di bawah ini berdasarkan Helm chart gha-runner-scale-set terkini yang diterbitkan GitHub.
ARC memiliki dua Helm chart. Chart controller memasang operator dan custom resource sekali per cluster. Chart runner scale set dipasang sekali per kumpulan runner, dan setiap instalasi membuat satu AutoscalingRunnerSet plus satu pod listener ringan. Listener itu menjaga koneksi long-poll ke layanan GitHub Actions. Ketika job yang menargetkan label runner Anda antre, GitHub memberi tahu listener berapa runner yang dibutuhkan, dan listener mem-patch EphemeralRunnerSet sehingga Kubernetes membuat sejumlah pod runner itu. Tidak ada HPA berbasis CPU dan tidak ada polling endpoint metrik di sini — penskalaan didorong langsung oleh antrean job nyata, itulah mengapa cepat sekaligus akurat.
Setiap runner yang dibuat ARC bersifat ephemeral. Ia mendaftar ke GitHub, mengambil tepat satu job, menjalankannya, lalu pod dihapus. Job berikutnya mendapat pod baru dari image yang sama. Jaminan sekali pakai itulah alasan utama memilih ARC dibanding runner persisten: tidak ada state yang terbawa, jadi build tidak bisa disabotase oleh apa pun yang berjalan sebelumnya. Biayanya adalah waktu cold-start — setiap job membayar untuk menarik image runner dan, dalam mode dind, menjalankan daemon Docker — yang Anda mitigasi dengan warm pool lewat minRunners.
Instalasi berupa dua rilis Helm dari OCI registry GitHub. Peningkatan besar dibanding ARC lama adalah cert-manager tidak lagi menjadi prasyarat, sehingga instalasi controller benar-benar satu perintah. Pasang controller lebih dulu, lalu buat secret Kubernetes berisi kredensial GitHub Anda (PAT untuk mulai cepat, atau GitHub App untuk produksi), kemudian pasang scale set yang menunjuk ke URL repository, org, atau enterprise Anda.
# 1. Install the controller (once per cluster)
helm install arc \
--namespace arc-systems --create-namespace \
oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller
# 2. Install a runner scale set (once per runner pool)
helm install arc-runner-set \
--namespace arc-runners --create-namespace \
--set githubConfigUrl="https://github.com/MatthewsWongOfficial/my-repo" \
--set githubConfigSecret.github_token="$GH_PAT" \
oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-setNama rilis Helm menjadi label runner. Apa pun nama instalasi scale set itulah string persis yang Anda taruh di runs-on. Jadi rilis di atas dikonsumsi oleh workflow seperti ini:
# .github/workflows/ci.yml
jobs:
build:
runs-on: arc-runner-set # == the Helm release name
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run buildPAT cukup untuk pengujian tetapi cepat menabrak batas rate API GitHub pada skala besar dan bisa kedaluwarsa. Untuk yang sebenarnya, daftarkan GitHub App dan mount kredensialnya lewat secret — ini mendapat batas rate jauh lebih tinggi dan tidak mati diam-diam saat token dirotasi. Migrasi belakangan berarti membuat ulang secret dan rolling restart, jadi lakukan sejak awal.
Dua nilai menentukan tagihan Anda. minRunners adalah jumlah runner idle yang dijaga tetap hangat dan siap; maxRunners adalah batas keras berapa pod yang boleh ada sekaligus. Setel minRunners ke 0 dan Anda menskalakan penuh ke nol — Anda tidak membayar apa pun saat tidak ada job, dengan konsekuensi cold start pada job pertama setelah periode sepi. Setel ke 1 atau 2 dan Anda menukar sedikit biaya idle demi pengambilan hampir instan selama jam kerja. Jumlah yang benar-benar berjalan pada suatu saat kira-kira minRunners ditambah jumlah job aktif, dibatasi oleh maxRunners.
Pasangkan scale-to-zero dengan Cluster Autoscaler Kubernetes atau Karpenter. ARC menghapus pod runner saat idle, tetapi node tetap memakan biaya sampai ada yang menghapusnya juga. Dengan node autoscaling, pool CI yang sepi total bisa benar-benar turun ke nol node semalaman dan menyalakan node baru pada job antre pertama.
Jika workflow Anda hanya menjalankan skrip, tes, dan build secara langsung, Anda tidak butuh apa pun khusus. Namun begitu sebuah job menggunakan container action, service container, atau docker build, runner butuh cara menjalankan container. ARC memberi dua opsi containerMode, dan pilihannya punya implikasi keamanan dan kompleksitas nyata.
| Aspek | containerMode: dind | containerMode: kubernetes |
|---|---|---|
| Cara kerja | Sidecar dockerd privileged berbagi socket dengan runner | Setiap langkah job berjalan sebagai pod terpisah lewat container hooks |
| Privilese | Memerlukan container privileged | Tidak perlu container privileged |
| Upaya setup | Rendah — langsung jalan | Lebih tinggi — butuh persistent volume / storage class |
| Isolasi | Lebih lemah; daemon Docker dibagi dalam pod | Lebih kuat; langkah terisolasi di tingkat pod |
| Terbaik untuk | Tim yang sekadar butuh docker build berjalan | Cluster sensitif keamanan yang menghindari pod privileged |
Rekomendasi default saya adalah dind untuk kebanyakan tim — paling tidak mengejutkan dan meniru apa yang dilakukan runner biasa. Gunakan mode kubernetes hanya ketika kebijakan cluster Anda melarang pod privileged, dan sediakan waktu untuk konfigurasi storage yang dibutuhkannya, karena sering menjegal orang. Satu jebakan yang patut diketahui: versi chart terbaru pernah memiliki bug di mana init container dind yang disuntikkan berperilaku tak terduga saat containerMode diset ke kubernetes, jadi pin versi chart Anda dan uji mode yang benar-benar Anda pilih sebelum menggulirkannya ke setiap repo.
Untuk tim kecil hingga menengah, saya akan memasang controller sekali, menjalankan satu scale set per lingkungan (satu pool CI umum dan mungkin pool terpisah untuk deploy), menyetel minRunners ke 1 selama jam kerja dan membiarkannya turun ke nol di luar itu, menyetel maxRunners ke angka yang aman di bawah kapasitas cluster, dan memakai mode dind dengan GitHub App. Konfigurasi itu memberi runner sekali pakai yang bersih, pengambilan di bawah satu menit sepanjang hari, dan tagihan mendekati nol di malam hari. Ini jauh lebih murah daripada armada VM yang selalu menyala dan jauh lebih aman daripada runner persisten, dan karena penskalaan digerakkan antrean, ia tidak pernah tertinggal di belakang lonjakan commit seperti autoscaling berbasis CPU.