왜 문제가 되는가
고정 크기 러너 풀은 두 방향으로 손해를 본다. 항시 켜둔 러너는 야간·주말에 유휴 상태로 과금되고, 반대로 릴리스 직전 몰리는 빌드는 큐에 쌓여 개발자를 기다리게 한다. 특히 self-hosted 러너를 상시 VM으로 운영하면 CPU 사용률이 10~20%에 머무는 경우가 흔하다. 오토스케일의 목표는 단순하다. 큐가 있을 때만 러너를 늘리고, 일이 끝나면 즉시 반납하는 것이다.
스케일 신호를 무엇으로 잡을 것인가
CPU 기반 오토스케일은 CI에 맞지 않는다. 빌드가 큐에 있어도 아직 러너가 없으면 CPU 지표는 0이기 때문이다. CI에서는 큐 깊이(pending jobs)를 신호로 써야 한다. GitHub는 Actions Runner Controller(ARC)의 webhook 스케일러, GitLab은 job 큐 API가 이 역할을 한다.
event: workflow_job(queued) 수신
→ 라벨 매칭되는 러너 부족?
→ 러너 1개 추가 (VM/Pod)
job 완료 or idle timeout
→ 러너 회수
핵심은 job 하나당 러너 하나를 쓰고 끝나면 폐기하는 ephemeral(1회용) 러너 패턴이다. 상태가 남지 않아 캐시 오염과 보안 문제를 동시에 줄인다.
Kubernetes 기반 예시(ARC)
ARC의 RunnerScaleSet은 큐 이벤트에 반응해 Pod를 생성한다. 최소 0, 최대 N으로 두면 유휴 비용이 사실상 사라진다.
apiVersion: actions.github.com/v1alpha1
kind: AutoscalingRunnerSet
metadata:
name: linux-arc
spec:
githubConfigUrl: https://github.com/my-org
minRunners: 0
maxRunners: 30
template:
spec:
containers:
- name: runner
image: ghcr.io/actions/actions-runner:latest
resources:
requests: { cpu: "2", memory: 4Gi }
limits: { cpu: "2", memory: 4Gi }
minRunners: 0이 핵심이다. 대신 첫 job은 Pod 기동 시간(수십 초)을 감수해야 한다. 지연이 민감한 파이프라인은 워밍 풀로 min을 1~2로 둔다.
Spot/Preemptible로 단가 낮추기
CI 작업은 대부분 짧고 재실행 가능해 spot 인스턴스와 궁합이 좋다. 회수되면 job만 재시도하면 된다. 단, 캐시 빌드나 장시간 통합 테스트는 on-demand로 분리한다.
| 워크로드 | 인스턴스 | 이유 |
|---|---|---|
| 단위 테스트·린트 | Spot | 짧고 재시도 저렴 |
| 릴리스 빌드 | On-demand | 중단 시 손실 큼 |
| 야간 배치 | Spot + 큰 max | 지연 허용 |
비용을 실제로 측정하기
스케일 정책은 감이 아니라 데이터로 조정한다. 러너 실행 로그를 적재해 job당 시간과 유휴율을 본다.
SELECT
labels,
COUNT(*) AS jobs,
ROUND(AVG(duration_sec)/60, 1) AS avg_min,
ROUND(SUM(duration_sec)/3600 * 0.05, 2) AS est_cost_usd
FROM runner_job_runs
WHERE run_date >= CURRENT_DATE - INTERVAL '7' DAY
GROUP BY labels
ORDER BY est_cost_usd DESC;
여기서 job 시간의 90퍼센타일이 유독 긴 라벨은 캐시 미스나 과대 스펙 러너를 의심한다. 대형 러너를 남발하는 것보다 캐시 적중률을 올리는 편이 비용에 더 효과적일 때가 많다.
주의점
몇 가지 함정이 있다. 첫째, 스케일 인 지연. idle timeout이 너무 길면 반납이 늦어 비용이 새고, 너무 짧으면 연속 job 사이에 러너를 매번 다시 띄운다. 30~120초 사이에서 실측으로 맞춘다.
둘째, 최대치(maxRunners) 상한을 반드시 둔다. 버그로 job이 폭주하면 오토스케일이 그대로 청구서로 이어진다. 상한은 안전장치다.
셋째, ephemeral 러너의 캐시 부재. 매번 새 러너라 로컬 캐시가 없다. 원격 캐시(레지스트리 레이어, S3 기반 빌드 캐시)를 붙이지 않으면 스케일로 아낀 비용을 캐시 미스로 다시 토해낸다. 오토스케일과 원격 캐시는 항상 함께 설계해야 한다.