카카오엔터프라이즈가 GitHub Actions를 사용하는 이유

– 카카오엔터프라이즈 aaron, greta가 함께 작성하였습니다. 

 

GitHub Actions를 통한 CI/CD 사용설명서 

안녕하세요, 카카오엔터프라이즈 DevOps Engine 아론(aaron), 그레타(greta)입니다.  카카오엔터프라이즈는 최근 전사 DevOps 표준화를 구축하기 위해 GitHub Actions를 도입하였습니다. 이번 글에서는 카카오엔터프라이즈가 GitHub Actions를 사용하는 이유, 동작원리, GitHub Actions Runner를 이용한 GitHub Actions 기동 등에 대해서 공유하겠습니다.


카카오엔터프라이즈 DevOps 표준안

카카오엔터프라이즈 DevOps파트에서는 위의 그림과 같이 전사 데브옵스 표준을 잡아가고 있습니다.

소스 레파지토리로 GitHub Enterprise를 사용하고 있고, 해당 GitHub Enterprise에서 소스가 커밋이 되는 등의 액션이 발생을 하게 되면 GitHub Actions를 통해서 CI 과정을 진행하게 됩니다. 이 과정을 통해 소스가 이미지 형태로 생성되면, 카카오 i 클라우드의 컨테이너 레지스트리에 업로드됩니다.

업로드된 이미지는 Argo CD를 통해 배포하며, 배포 타깃은 카카오 i 클라우드의 쿠버네티스 엔진입니다.

그럼 본격적으로 GitHub Actions에 대해 설명드리고,  GitHub Actions를 기동할 수 있는  GitHub Actions Runner 및 Runner 구축 시 발생했던 이슈와 개선사항에 대해 말씀드리도록 하겠습니다. 

 

GitHub Actions

카카오엔터프라이즈의 경우 현재 Jenkins, Drone, Bamboo 등 여러 CI 솔루션을 사용하고 있습니다. 각 프로젝트에서 각기 다른 CI 솔루션을 사용하다 보니 여러 가지 문제가 발생하기도 합니다. 예를 들어, Jenkins CI 구성이나 Drone CI 구성 등 각 프로젝트마다 CI 담당자를 배정해야 할 필요성이 계속 생기게 되었습니다. 

또한, 여러 프로젝트가 같이 협업할 때 하나의 CI 툴로 통합해서 CI 작업을 진행해야 하는 어려움이 있었습니다. 그 CI를 수행함에 있어서 CI 툴에 맞는 문서 작성을 위한 문법 숙지도 필요했습니다. 이러한 이슈들로 인해 업무와는 크게 관련이 없지만 허들이 계속 발생했습니다. 그래서 하나의 CI 솔루션을 도입하여 이 같은 허들을 제거하고자 하였고, 하나의 솔루션으로 GitHub Actions를 사용하게 되었습니다. 

GitHub Actions를 카카오엔터프라이즈 CI 솔루션으로 채택하게 된 이유는 다음과 같습니다. 

  1. GitHub과 하나로 통일된 환경에서 CI 수행이 가능합니다. 
  2. 중앙에서 관리하는 GitHub Actions Runner에 지속적으로 트러블슈팅하여 원활한 CI 환경 구성이 가능합니다. 
  3. 프로젝트마다 개별 Runner를 통한 빌드 테스트가 가능합니다.
  4. GitHub Actions Runner 기동을 하기 위해 친숙한 문법의 YAML 파일로 간단하게 파이프라인 구성 가능합니다.


GitHub Actions Runner 

GitHub Actions를 기동하는 Runner에 대해 설명드리도록 하겠습니다.

GitHub은 퍼블릭 쪽의 GitHub Actions Runner를 클라우드에서 제공해 주고 있습니다. 그래서 직접 프로비저닝할 필요 없이  Runner를 바로 사용할 수 있습니다. 하지만, 저희는 사정 상 내부의 GitHub Enterprise에서 클라우드에 있는 Actions Runner를 사용하지 못하는 상태입니다. 그래서 별도로 로컬에 있는 Actions Runner나 저희가 중앙에서 프로비저닝하는 Actions Runner로 GitHub Actions를 수행하게 됐습니다.


Self-hosted Runner

현재 저희가 사용하고 있는 GitHub Actions의 모습입니다. 중간에 ‘k8s’라고 라벨을 붙여두었는데 이것이 저희가 중앙에서 제공해 드리는 Actions Runner라고 보시면 됩니다. 위의 이미지는 Runner들이 Access token을 발급받아서 GitHub에서 인증을 하고, GitHub에서도 Runner로 인식하는 과정을 도식화했습니다.

저희가 k8s로 제공해 드리는 Self-hosted Runner 외에도 각 프로젝트에서 자신만의 Runner를 사용하고 싶을 때에는, ‘ian’, ‘aaron’ 같은 라벨링 방식으로 Runner를 등록해서 별도로 사용하실 수 있습니다. 


Access token

Github actions runner 를 인증하기 위한 Access token의 경우, 각 GitHub 계정에서 발급받을 수 있습니다. Select scopes > admin:enterprise의 manage Runners: enterprise 권한을 이용해서 Generate token을 한 다음 Access token을 발급받아서 사용할 수도 있습니다. 


Self-hosted Runner 만들기

또는 Self-hosted Runner를 직접 UI에서 만들 수도 있습니다. 각 Repository의 Settings 페이지에는 Actions > Runner 상세 페이지가 있습니다. 이 Runner 페이지에 들어가서 New self-hosted runner 버튼을 클릭해 Runner를 생성하면 서버 뿐 아니라 로컬 환경에서도 Self-hosted Runner를 설치해서 분석할 수 있습니다.


Actions Runner List 확인

Actions Runner List의 경우, Repository의 Settings에서 Runner를 들어가면 목록을 확인할 수 있습니다.

좌측 이미지는 Actions yaml을 일부 발췌한 것으로, runs-on이라는 k8s 라벨을 찾아서 Actions Runner를 기동한다는 의미로 보시면 됩니다. 즉, 우측에 있는 k8s 라벨을 찾아서 Actions Runner가 기동하는 형태입니다. 

그리고 k8s Runner 라벨 오른쪽에 Status가 Idle과 Active 두 가지가 있는데요, Idle 상태의 Runner는 지금 CI 작업을 할당할 수 있는 Runner이고,, Active 상태의 Runner는 이제 CI 작업이 진행 중인 Runner입니다. 따라서, k8s 라벨에 있는 Runner 중에 Idle 상태의 Runner 하나를 선택해서 CI 작업이 진행되는 것이라 생각하시면 됩니다.


Actions Runner group

이 Actions Runner는 Runner group으로도 관리가 가능한데, 카카오엔터프라이즈에서는 Default라는 그룹으로 16개의 Runner를 제공하고 있습니다. DevOps라는 개별적으로 생성한 Runner group의 경우 Selected organizations(2)의 특정 organizations에서만 이 Runner에 접근이 가능하고, 또 빌드가 가능합니다.


YAML 파일을 이용한 Actions runner 기동

다음은 Actions Runner가 어떻게 CI 작업을 기동할 수 있는지 YAML 파일을 보면서 설명하도록 하겠습니다.

먼저 각 REPO에 .github/workflows/라는 directory가 있고, 여기에 YAML 파일이 하나씩 들어가 있습니다. 이것은 자동 생성은 아니고 직접 작성을 해주셔야 합니다. 이 Repository의 경우 main.yaml이라는 이름으로 파일을 구성했고, 내용은 우측 이미지와 같습니다. 

CI 작업의 명칭으로 main이라고 설정했고, on이라는 이벤트로 트리거링할 수 있습니다. 그리고 jobs가 실제로 CI를 수행하는 과정입니다. jobs의 하위에는 steps 단계로 jobs가 진행되는 과정이라고 보시면 됩니다.


trigger

on을 통해 트리거링을 수행하는 방법에 대해 말씀드리겠습니다. 여러 개의 이벤트를 통해 트리거링이 가능한데요, 아래 이미지와 같이 trigger 예제를 네 가지 준비했습니다.

push 혹은 request가 발생했을 때 자동적으로 CI가 진행되며, branches 경우에는 하나를 선택할 수도 있고, 여러 개를 선택할 수 있습니다. 또한 branches- ignore를 통해서 트리거링이 발생하지 않는 branches를 선택할 수도 있습니다. 이 경우에는 전체 branches에 대해서 진행이 되고, ignore에 해당하는 branches에서만 CI 작업이 진행되지 않습니다.


job

jobs의 경우 예제를 보시면, 크게 build와 deploy의 두 가지 job으로 구성되어 있습니다. 아래 이미지를 통해 build라는 작업이 수행되었다는 것을 확인하실 수 있습니다.

deploy라는 job을 보시면 needs: build라는 키: 밸류 값이 있습니다. 이 경우에는 build라는 job이 수행되고 나서 deploy가 수행되어야 한다는 의미로 이해하시면 됩니다. 만약 needs가 사용되지 않으면 병렬로 job이 수행된다고 보면 됩니다. needs: build가 있기 때문에 파이프라인이 build 다음에 deploy job이 수행되는 것을 확인할 수 있습니다.


pipeline

이 외에도 분기로도 선택을 할 수가 있습니다. 

성공했을 때와 실패했을 때를 분기로 두어서, build에 성공했을 때는 on_success가 돌고 실패했을 때는 on_failure이 도는 것을 확인하실 수 있습니다.


step 수행 방식 

다음은 job 하위에 있는 step 수행 방식입니다. step의 경우 크게 uses를 사용하는 내용과 run을 사용하는 내용 두 가지가 있습니다. uses의 경우에는 Marketplace에 사전 정의된 내용을 이용하여 step을 수행하게 됩니다. actions checkout이라는 패키지를 GitHub Marketplace에서 가져와 변수만 넣어서 step을 수행하거나, default 값으로 수행을 하기도 합니다. 

하지만 run으로 수행했을 때는 하위에 직접 기재한 커맨드를 직접 수행하게 됩니다. 보통 위에 있는 uses를 사용한 내용은 환경설정용이라고 보시면 되고, 아래 커맨드를 직접 수행하는 내용은 실제 수행용이라고 보면 됩니다. 그래서 build를 실제 수행한 내용은 run 하위에 작성하고, 그 앞의 사전 작업을 하실 때는 환경설정용으로 Marketplace에 정의된 내용을 사용하면 됩니다. 


이슈 및 개선사항
 


지금까지 GitHub Actions Runner를 기동하는 방식에 대해서 설명드렸습니다. 이제부터 프로비저닝을 하고, Runner를 제공하면서 발행했던 이슈와 개선사항에 대해 말씀드리도록 하겠습니다.


Config 설정 이슈

카카오엔터프라이즈 내에서 back-end 부서와 프로젝트를 진행할 때 Node.js를 많이 사용하고 계셔서 이와 관련된 build를 많이 했고, 앱의 경우는 Android, Erlang의 경우 elixir로 build를 했습니다. 이러한 경우 사실 하나의 config로 저희가 커버할 수가 없어서, 각 플랫폼이나 언어별로 config 설정을 해줘야 하는 이슈가 생기기도 했습니다.

이때, ‘기승전프록시’라고, 프록시 쪽에 오류가 자주 발생하니 Runner를 설정할 때 npm과 yarn 즉 Node.js의 패키지를 받을 때의 config 설정을 Runner에 기본 설정으로 세팅해 두었습니다. 또한 Android build를 할 때도 JAVA OPTS에 프록시 옵션을 가져와서 프록시 등록을 한 뒤 정상적으로 JAVA build가 되도록 하였습니다.


도커를 통한 Runner 기동

다음은 self-hosted runner를 처음에는 VM 프로세스로 기동을 했다가 현재는 도커로 기동을 하여 쿠버네티스로 스케일링을 하고 있습니다. 

도커로 이동한 이유는 쿠버네티스를 통해서 auto scaling이 가능하고, Actions Runner를 프로비저닝할 때 좀 더 효율적으로 제공할 수 있기 때문입니다. 

중간에 검토할 때는, ephemeral 옵션을 통해서 지속적으로 restarts를 해서 CI 작업이 계속 발생을 하더라도 원래의 Actions Runner 세팅으로 계속 작업을 할 수 있도록 설정을 하려고 했습니다. 하지만 현재 ephemeral 옵션 적용 시에 한 번에 여러 개의 job이 도는 병렬 job 처리가 동시 수행이 불가능한 이슈가 있어서 파악 중이며 개선할 예정입니다.

또한, 도커로 Actions Runner를 기동하려다 보니 다음과 같은 아키텍처 이슈도 발생했습니다. DinD와 DooD 두 가지 방식이 있는데요, 왼쪽의 그림이 DinD, 오른쪽의 그림이 DooD 방식입니다.

DinD의 경우 호스트에 도커가 올라가 있는데 그 도커 중 Actions Runner 도커 위에 Docker 3이 올라가 있습니다. Actions Runner에서 도커를 기동하면 Actions Runner 위에 도커가 올라간다고 해서 Docker in Docker라고 칭하고 있습니다.

DooD는 Actions Runner에서 도커를 수행할 때 호스트 입장에서 도커 데몬을 수행하게 돼서 Docker 1과 Docker 2가 호스트 위에 뜨는 것을 확인할 수 있습니다. 이것은 도커에서 수행을 했지만 실제 내용은 도커 외부에서 수행됐기 때문에 Docker out of Docker라고 합니다.

좀 더 자세히 설명하도록 하겠습니다.

  • Docker in Docker 

먼저 Actions Runner의 CI를 할 때 도커 명령어를 별다른 세팅 없이 기동을 하게 되면 DinD로 수행이 됩니다. 그래서 CI를 통해서 도커를 기동할 수 있는 편한 방법이라 할 수 있습니다. 도커 클라이언트 내에서 도커 데몬을 이동한다고 이해하시면 됩니다. 이러한 방식으로 Actions Runner가 Docker 3에게 도커 명령을 실행했을 때 Docker 3이 Actions Runner 위에 뜨게 되는 것이죠. 

이 경우 Actions Runner를 기동할 때 정상적으로 동작을 하려면 privileged tag를 이용해야 하는데요,  이렇게 되면 이 호스트 머신에서 수행할 수 있는 거의 모든 작업을 Actions Runner내에서 할 수 있게 됩니다. 그래서 보안 이슈가 발생하기도 합니다. 그래서 저희는 DooD 방식으로 Actions Runner를 기동하기로 했습니다.

  • Docker out of Docker

DooD는 Actions Runner에서 도커 작업을 수행해도, 호스트를 빌려서 Docker 1에서 수행을 하도록 하게 되는데, 이 경우에는 docker.sock이라는 파일을 마운트하여 도커 데몬을 기동할 수 있게 해줍니다. 이 경우 docker.sock이라는 파일을 소유한 그룹과 유저를 Actions Runner 내부에서는 인식이 가능해야 합니다. 

그러나 DooD의 경우 도커를 기동하다 보면 도커 설정이 Actions Runner 내부에 있는 도커 설정과 차이가 발생할 때가 있어, 호스트와 Actions Runner 내부의 도커설정도 같이 맞추어야 한다는 점이 이슈가 될 수도 있습니다.  


마치며


지금까지 카카오엔터프라이즈가 GitHub Actions를 사용하는 이유, 동작원리, GitHub Actions Runner를 이용한 GitHub Actions 기동, 이슈 및 개선사항에 대해 설명드렸습니다. 

GitHub Actions이 어떻게 코드의 빌드 및 배포를 자동화할 수 있는지, 그리고 개발자분들이 협업을 하는 데 있어 얼마나 큰 편리함을 제공해 주는지가 잘 전달되었으면 좋겠습니다. 감사합니다. 

  

Latest Posts