글 작성자: 모두의 근삼이

GitHub Action이 Self-Hosted Runner의 새로운 공식 피쳐로 ARC라는 것을 제공하기 시작했다.
ARC는 Action Runner Controller의 약자인데, 쿠버네티스에 설치되고 관리되며 Action Runner를 쿠버네티스 Pod로 실행할 수 있도록 기능을 제공한다.

ARC는 원래 GitHub이 공식지원하지 않는 community 오픈소스로 관리되는 프로젝트였는데, 2022년 12월 12일 부터 GitHub이 인수해서 공식 관리하는 프로젝트로 승격되며 많은 기능들이 개선되었다.

공식 문서를 보며 동작을 이해하려고 했는데, 생소한 개념이 많아서 한번에 이해되지 않는 부분이 많았다. 그래서 직접 설치하고 코드를 분석하며 이해한 내용을 바탕으로, 컨트롤러의 동작을 좀더 쉽게 설명하여 기록해 본다.

GitHub Action Self-Hosted Runner

장점

  • Action 실행환경의 대역 및 권한에 따른 서비스 접근 권한 관리의 편의성
  • 머신의 리소스 크기 및 운영체제 선택의 자유

단점

  • 관리 포인트의 증가

ARC(Actions Runner Controller)

Self-Hosted Runner는 사용자가 관리하는 머신에서 자유롭게 러너를 구성하고 활용할 수 있도록 지원하는데, 최근에는 K8S에서 Pod으로 이 러너를 구동할 수 있도록 돕는 컨트롤러인 ARC를 깃헙에서 공식적으로 지원하면서 선택의 폭이 더 넓어졌다.

ARC를 활용하면 runner scale sets라는 개념을 활용해서 repo, org, enterprise 단위로 실행중인 워크플로우 갯수에 따라 러너의 갯수가 오토스케일링 되도록 쉽게 설정할 수 있다.

ARC가 아닌 셀프호스티드 러너도 테라폼을 활용해 오토스케일링 할 수 있는 방법이 존재 하지만, ARC를 활용하면 컨테이너라는 장점을 활용하여 러너를 더욱 빠르게 스케일 업/다운 할 수 있다는 장점이 있다.

runner scale sets

ARC의 동작을 이해하려면 runner scale sets의 개념을 이해하고 가는 것이 좋다.
runner scale sets는 ARC를 GitHub에서 공식지원하기 시작하면서 나온 개념이다.
runner scale sets러너 그룹의 하위개념으로, 러너가 실행중 이어야 그룹에 속해서 관리될 수 있는 다른 Self-Hosted 러너의 개념과는 다르게, 사전에 정의 된 min / max 값에 따라 워크플로우 실행을 요청 받을 때 마다 러너 pod를 쿠버네티스에 생산하도록 약속된 추상적인 개념이다.
GitHub Action Runner UI에서도 다른 러너들과는 다른 형태의 하위 UI를 확인할 수 있다.
일반적으로 러너 그룹에 속하는 다른 러너와 같이 여러개의 label등을 지정하여 사용할 수는 없고 runner scale sets를 생성할 때 등록한 이름만 유일한 label로 활용할 수 있다.

아래 스크린 샷은 runner scale sets의 min값을 2로 max 값을 10으로 설정하여 등록하고 아무런 workflow도 실행하지 않은 상태의 UI이다.

 

여담이지만, ARC를 GitHub에서 공식지원하기 이전에 community 버전일 때에는 ARC에 의해 배포되고 동작하는 러너 pod를 오토스케일링하기 위한 옵션이 두가지 있었다.

  • web-hooks 방식
  • pulling 방식
    하지만 두가지 방식 모두 이상적인 방법은 아니었다.

pulling 방식은 api limit에 제약을 받는 단점이 있었고, api자체가 해당 목적에 맞게 설계된 api도 아니었기 떄문에 잘 스케일링 되지 않는다는 문제가 있었다.

web-hooks 방식의 문제는 GitHub으로 부터 출발한 hook이 컨트롤러에 제대로 전달 될 것을 보장하기 어렵기 때문에 트러블 슈팅이 어려워지고, 실패한 웹훅을 다시 트리거하도록 하기도 어렵다는 문제가 있었다.

GitHub이 공식 지원하면서 리뉴얼 오토스케일링을 지원하는 방식에서는 runner scale sets를 관리하는 별도의 listener가 GitHub Action 서버와 http long-poll 커넥션을 맺음으로써 액션 트리거 이벤트를 구독하게 되어 위 두가지 방식의 단점을 보완하였다.

ARC의 구성요소

ARC는 크게 두가지 구성요소로 구성되어 있다.

  • controller-manager(pod)
    특정 namespace에 pod로 배포되며, 아래와 같은 4개의 컨트롤러들의 역할을 수행한다.
    • AutoScalingListener Controller
    • AutoScalingRunnerSet Controller
    • EphermeralRunner Controller
    • EphermeralRunnerSet Controller
      각 컨트롤러들은 ARC가 설치될 때 함께 설치되는 약 20가지의 CRD를 지원하도록 동작하며, 커스텀 리소스들의 동작과 상태를 관리한다.
  • Runner ScaleSet Listener(pod)
    특정 namespace에 AutoScalingLisener Controller에 의해 pod로 배포된다. GitHub Action 서버와 HTTPS long-poll connection을 맺어 액션 트리거를 구독한다.
    리스너 팟은 액션을 트리거 받으면 scale set의 max값에 따라 액션을 실행 할 수 있는 상태인지 판단하고, 실행할 수 있다고 판단되는 경우, CRD인 Ephemeral RunnerSet resource 의 값을 업데이트하여 EphemeralRunner Controller가 새로운 러너를 띄울 수 있도록 한다.
    클러스터에 존재하는 runner scale sets 하나당 리스너 pod이 하나씩 생성된다.
    • Ephemeral RunnerSet(Custom Resource)
      여러개의 CRD 중, ARC의 동작을 이해하기 위해 알아두면 좋은 CRD이다.
      Runner ScaleSet Listener에 의해서 값이 업데이트 되고, EphemeralRunner Controller에 의해 관리되는 커스텀 리소스이다. HPA와 비교하여 생각하면 편리한데, 실행가능한 runner의 min / max 값을 가지고 있다.
      실제로 액션이 수행되는 러너로써 동작하는 pod이 이 리소스에 의존적으로 실행된다.

순서로 살펴보는 ARC의 동작

아래 서술된 동작은 클러스터에 ARC와 하나의 세트의 runner scale sets로 구분할 수 있는 리소스들 (AutoScalingRunnerSet 및 각종 secret 및 role) 이 배포된 상황을 가정한다.

이해의 편의를 위해 설명을 더하자면, ARC의 동작과정에서 GitHub과 통신하기 위해 사용하는 엔드포인트는 두가지이다. 아래 동작과정 설명에서 이해의 편의를 위하여 아래 각각의 엔드포인트를 다음과 같은 이름으로 기억하자.

이제 우리는 깃헙 docs에서 제공하는 ARC의 동작을 설명한 문서를 어렵지 않게 이해할 수 있다.

  1. AutoScalingRunnerSet Controller 가 GitHub API를 호출하여 runner scale set을 매핑하기 위한 러너 그룹의 ID를 가져온다.
  2. AutoScalingRunnerSet Controller가 GitHub API를 한번 더 호출하여, Action Servicerunner scale set을 생성하거나, 생성을 확인한다.
  3. AutoScaling Listener ControllerRunner ScaleSet Listener pod를 생성한다. 이 pod 내부에 뜨는 listener application은 Action Service 엔드포인트에 연결하고 인증한 뒤 HTTPS long poll 커넥션을 맺는다. 이 연결은 Action Service로부터 Job Available 메시지를 받을 때 까지 지속된다.
  4. repo로부터 workflow 실행이 트리거되면, Action Service는 workflow 매니페스트의 runs-on 섹션에 명시된 대로 runner에 작업을 dispatch한다. runs-on 섹션의 내용이 runner scale set을 가리키면 Runner ScaleSet Listener와 연결된 커넥션으로 Job Available메시지를 응답한다.
  5. Runner ScaleSet ListenerJob Available메시지를 수신하면, 리스너는 Ephemeral RunnerSet의 desired count를 스케일 업 할 수 있는지 확인하고, 가능하다면 메시지를 승인한다.
  6. Runner ScaleSet ListenerService AccountRole 바인딩을 통해 할당받은 권한을 통해Ephemeral RunnerSet을 fetch할 수 있는 쿠버네티스 api를 호출하여 desired replicas count를 스케일 업 한다.
  7. Ephemeral Runner ControllerEphemeral RunnerSet의 업데이트 된 값을 기준으로 새로운 러너를 띄우려고 시도한다. 이때 생성하는 runner에는 Action Service에 등록할 때 사용하기 위한 JIT(Just-In-Time) configuration token을 발행받아 전달한다. 만약 pod가 정상적으로 뜨지 못하면, 컨트롤러는 최대 5번까지 재시도한다. Action Service는 24시간이 지나도 job을 할당받는 runner가 없으면, 할당을 해제한다.
  8. runner pod가 생성되면, runner pod 내부에 설치된 application은 생성 될때 전달받은 JIT onfiguration token을 활용하여 스스로를 Action Service에 등록한다. 그 이후, 새로운 HTTPS long poll 커넥션을 수립하여 실행할 job에 관련된 세부사항을 전달받는다.
  9. Action Service는 한편 runner pod의 등록을 승인하고, 작업 실행을 위한 세부사항을 전달한다.
  10. runner는 작업이 실행되는 동안 작업 상태와 함께 로그를 지속적으로 Action Service로 전달한다.
  11. runner가 성공적으로 job을 마치면, Ephemeral Runner ControllerAction Service로 부터runner pod를 삭제해도 되는지 확인한 뒤 삭제한다.

ARC 동작 demo

이부분 업데이트 toto..

모니터링

ARC 전용 메트릭들을 controller에서 별도로 제공하기 때문에 메트릭을 Prometheus로 수집하여

커스텀한 Grafana 대시보드를 구성해 볼 수 있다.

그럴 수 있어야 했는데, 아직 브랜뉴 ARC는 메트릭 응애다.
기존 버전의 ARC를 위해 미리 만들어진 대시보드를 가져와서 적용해 봤지만, 딱히 행복함이 느껴지지 않았다.
리뉴얼 된 버전의 ARC의 메트릭을 까서 심문해보니 몇가지 값들은 호환이 되어서 겨우 아래와 같은 조악한 형태로 과거의 영광을 일부 복원해 볼 수는 있었다.
서비스별 action duration 같은 정보도 같이 볼 수 있었으면 행복했을 것 같은데, 아직 새 버전의 컨트롤러에서는 파이프라인별로 구분된 메트릭을 서빙해 주진 않았다. (혹은 내가 설정을 잘못했거나...)

임시 참고용 대시보드

참고로 기대했던 그림은 이랬다.

하지만, 그럼에도 불구하고 러너 자체가 쿠버네티스에 속해서 pod으로 동작하기 때문에 러너의 상태와 사용 패턴등에 대한 간단한 모니터링은 충분히 가능했다.

비용 절감이 가능할까?

현재 재직중인 회사의 사용 패턴을 기준으로 분석해 보았다.
우리 회사에서는 GitHub에서 호스팅하는 GitHub Hosted 러너와 Self-Hosted러너를 복합적으로 사용하고 있는데, Self-Hosted러너의 경우 더 큰 인스턴스를 사용하기 위해 사용하는 경우가 대부분이다.
사실 더 큰 인스턴스를 사용하기 위함이 목적이라면, GitHub Hosted러너도 이제 더 큰 인스턴스를 사용할 수 있도록 기본적으로 기능을 제공하기 때문에 선택지가 다양한 상황이다.

정확한 예측은 현재 시점에서는 데이터가 부족해서 불가능하고, 대략적으로 예측하기 위해 기준이 되는 가격들을 점검해 보았다.

  • GitHub Hosted 러너 요금(분당)
  • ec2 인스턴스 일반 요금(시간당)
  • ec2 스팟 인스턴스 요금(시간당)
  • fargate 일반 요금
  • fargate 스팟 인스턴스 요금 (시간당)

각기 표기 기준이 다르기 때문에 8코어 16기가 스펙의 인스턴스를 기준으로 시간당 요금으로 일괄적으로 계산해 보았다.

  • GitHub Hosted 러너 요금 : 시간당 약 1.92$
  • ec2 일반 인스턴스 요금 : 시간당 약 0.38$
  • ec2 스팟 인스턴스 요금 : 시간당 약 0.12$
  • fargate 일반 인스턴스 요금 : 시간당 약 0.46$
  • fargate 스팟 인스턴스 요금 : 시간당 약 0.14$

대충 살펴봐도 GitHub Hosted 러너의 요금이 압도적으로 비싸다는 것을 알 수 있다.
가격 비율은 다른 클래스의 인스턴스들에 대해서도 비슷한 비율이라고 가정하고, 각 workflow가 실행 될 때 마다 pod가 생성되고 삭제되는 ARC의 동작을 생각하면 스팟 인스턴스로 fargate를 사용하게 될 경우, 일반 ec2 머신을 활용하던 기존 방식에 비해 훨씬 싸게 먹힐 것이라는 것을 유추할 수 있다.


위 스크린샷은 self-hosted러너로 설치하여 동작하고 있는 ec2머신 중 가장 활발한 머신을 기준으로 24시간 동안 CPU 사용 그래프를 표시한 것이다.

정말정말정말정말정말 넉넉히 잡아서 실제 활성상태 시간을 하루 기준 12시간 사용한다고 가정하여 스팟 인스턴스 fargate를 사용하도록 ARC를 구성하면, 기존 셀프 호스티드 러너 운영 비용을 1/6 수준으로 절감할 수 있다고 계산할 수 있다.

추가로, 현재 GitHub Hosted러너를 사용하고 있는 일반 액션들도 모두 fargate를 활용하도록 구성하면 더 큰 효과가 있을 것이라고 기대할 수 있다.

나중에 실현 가능할 수도 있지만 지금은 못하는 방법

괜히 그냥 마무리 짓기 아쉬우니까 상상해보자면..
GitHub Action에서 셀프호스티드 러너를 지정할 때 우선순위를 줄 수 있게 된다면 (현재는 안됨),
일부 runner set은 비용절감 노드그룹에 상시 뜨도록 세팅하고,
일부 runner set은 Fargate로 생성되어 동작하도록 하여 비용을 최적화 할 수도 있을 것 같다.

ARC와는 관련 없지만 DataDog의 강력한 CI Pipeline Visibility

DataDog에서는 GitHub의 webhook, api 등을 활용해 Action 환경에 대해서 굉장히 다양한 지표를 모니터링 할 수 있는 방법을 제공한다.

 

 


강력한 기능인 만큼 멍뭉이는 역시 비싸긴 하다. 위 스크린샷에 해당하는 기능들을 활용하려면 pipeline visibility 에 관한 기능을 활성화 해야하는데, 요금은 아래와 같다.

참고 자료

반응형