ARC — 쿠버네티스 위의 GitHub Actions self-hosted 러너
GitHub Actions 는 기본적으로 GitHub 이 관리하는 러너에서 워크플로를 돌립니다. 그런데 내부망 리소스를 건드려야 하거나 특정 도구가 깔린 환경이 필요하면 그 러너로는 안 됩니다. 직접 띄워 쓰는 self-hosted 러너가 필요하고, 그걸 EC2 에 올려 뒀습니다.
쓰다 보니 걸리는 건 대수가 고정이라는 점이었습니다. 올려 둔 대수만큼만 동시에 job 을 처리하니 배포가 몰리는 시간대에는 큐에 쌓여 앞의 것이 끝나기를 기다립니다. 그렇다고 피크에 맞춰 대수를 늘려 두면 나머지 시간에는 노는 인스턴스 요금만 나갑니다.
ARC(Actions Runner Controller)는 이 러너를 쿠버네티스로 옮깁니다. job 이 하나 들어오면 그때 러너 파드를 하나 만들고 끝나면 지우기 때문에 대수를 미리 정해 둘 일이 없어집니다.
통신 방향 — K8s에서 GitHub으로 나가는 단방향
옮기기 전에 제일 먼저 확인한 건 방화벽이었습니다. 안 될 게 있다면 여기겠거니 싶었기 때문입니다. GitHub 이 클러스터 쪽으로 들어와야 하는 구조라면 인바운드를 뚫어야 하는데, 내부망에 있는 클러스터에서는 그게 간단하지 않습니다.
ARC 는 방향이 반대라, 클러스터 안의 Controller 가 GitHub API 를 폴링합니다.
ARC Controller (K8s Pod)
└── GitHub API polling (아웃바운드만)
├── job 없음 → 대기
└── job 감지 → Runner Pod 생성 → job 실행 → Pod 자동 삭제
나가는 쪽 하나뿐이라 아웃바운드 443 만 열려 있으면 동작합니다. 클러스터가 내부망에 있어도 되고 GitHub 에서 들어오는 트래픽 때문에 뭔가를 열어 줄 일이 없습니다.
같은 이유로 GitHub App 을 만들 때 webhook 을 끕니다. webhook 은 GitHub 이 이쪽으로 이벤트를 밀어 주는 방식이라, 폴링으로 가져오는 ARC 와는 맞지 않습니다.
runs-on 값 ↔ RunnerSet 이름 매칭
Controller 가 job 을 가져온다고 해도, 그 job 이 어느 러너 세트의 일감인지는 따로 정해 둬야 하고,
그걸 정하는 게 워크플로의 runs-on 에 적는 값입니다.
runs-on: test-runner-set
이 이름은 설치할 때 runnerScaleSetName 으로 정하고 설치 과정에서 GitHub 에 자동 등록됩니다.
EC2 에 러너를 올릴 때처럼 서버에 들어가 토큰을 받아 수동으로 등록하는 절차가 없습니다.
설치하면서 걸린 것들
Helm 으로 올리는 거라 값만 채우면 끝날 줄 알았습니다. 그런데 Controller 는 떴는데 러너 파드가 안 뜨면서 로그에 이게 남았습니다.
ERROR: failed to get kubernetes secret: "arc-runners/arc-github-secret"
GitHub App 자격증명을 담은 Secret 은 arc-systems 에 만들어 뒀고, RunnerSet 은 자기가 있는
arc-runners 에서 찾고 있었습니다. 찾던 네임스페이스와 이름이 메시지에 그대로 찍히니 확인은 금방 됩니다 — Secret 은 RunnerSet 과
같은 네임스페이스에 있어야 합니다.
그다음은 등록 토큰 발급이 404 로 떨어졌습니다.
ERROR: 404 Not Found POST
https://api.github.com/orgs/{조직명}/actions/runners/registration-token
githubConfigUrl 에 적은 조직명이 실제와 달라서였습니다. 값이 틀려도 helm 설치 자체는 성공하고
GitHub 에 토큰을 받으러 가는 시점에 처음 드러납니다. helm get values 로 지금 들어가 있는 값을
확인하고 고쳤습니다.
job 이 Queued 에서 안 넘어가고 러너 파드가 아예 안 뜨는 경우도 있었고, 원인이 두 갈래였습니다.
하나는 앞에서 말한 runs-on 값이 RunnerSet 이름과 안 맞는 경우입니다. 이름이 다르면 GitHub
쪽에서 그 job 을 이 러너 세트의 일감으로 치지 않으니 Controller 도 가져오지 않습니다. 다른
하나는 Controller 와 GitHub 사이 상태가 안 맞는 경우였습니다. 원인을 특정하지는 못했고,
Controller 를 재시작하니 풀렸습니다.
kubectl rollout restart deployment/arc-gha-rs-controller -n arc-systems
마지막은 러너 파드가 Pending 에 잠깐 있다가 바로 Terminating 으로 죽는 증상이었습니다. 파드가 Pending 이면 노드를 새로 붙여 주는 오토스케일러(Karpenter 나 Cluster Autoscaler)로 노드를 그때그때 띄우는 환경에서 나옵니다. job 이 들어와 파드를 만들었는데 받을 노드가 아직 없으면 새 노드가 올라올 때까지 Pending 이고, 노드가 뜨는 시간보다 Controller 의 타임아웃이 짧으면 파드가 스케줄되기도 전에 Controller 가 먼저 지웁니다.
minRunners 로 러너 파드를 최소 하나 항상 띄워 두면 완화됩니다. 대기 중인 러너가 이미 노드 위에
있으니 job 이 들어오는 즉시 받고 노드가 새로 뜨기를 기다리는 구간이 없어집니다.
minRunners의 대가 — 상시 점유하는 노드
다만 상시로 띄워 둔 그 파드는 job 이 없는 동안에도 노드를 차지합니다. 노드를 새로 띄우는 지연과 노는 파드에 나가는 비용을 맞바꾸는 거라, job 이 얼마나 자주 오는지를 보고 값을 정하면 됩니다. 하루에 몇 번 도는 파이프라인이면 0 으로 두고 새 노드 기다리는 시간을 감수하고, 수시로 돌면 하나쯤 띄워 두는 편이 낫습니다.
댓글 0