클릭 한 번으로 패키지와 문서를 배포하는 마법의 버튼 만들기 (1)

오평석
11 min readSep 8, 2022

--

안녕하세요. 기술지원팀에서 Unity 클라이언트 개발자로 일하고 있는 오평석입니다.

기술지원팀에서는 각 스튜디오가 사용하는 공통 모듈을 개발하고, 이를 Unity 패키지 형식으로 관리하고 있습니다. Unity 커스텀 패키지[1]를 제작하여 사내 레지스트리에 배포하면 각 스튜디오에서는 다음 그림과 같이 Unity Package Manager[2]을 열어 설치를 하는 방식입니다.

Unity Package Manager에 대한 소개는 다음 글을 참고해주세요.

이 글에서는 이러한 패키지를 배포하기 위한 CI/CD 워크플로의 도입과 이를 어떻게 구성하였는지, 어떤 고민을 하였는지, 어떻게 개선하고 발전시키고 있는지에 대한 소개를 2편에 걸쳐 전달드리고자 합니다.

참고: 글의 복잡도를 줄이기 위해 실제 사용 중인 워크플로를 약간 단순화하여 서술하고 있습니다.

CI/CD 도구의 선택

당연한 이야기이지만 우선 CI/CD 워크플로를 도입하려면 어떤 도구를 선택해야 하는지부터 정해야합니다. DevOps가 주목받으면서 다양한 CI/CD 도구가 등장하였고, 각자 단점을 보완하고 장점을 부각시키며 개발자들의 선택을 받기 위해 노력합니다. 선택지가 얼마 없던 시절에 비해 지금은 선택의 자유가 생겼으니 상황에 맞는 도구를 선택하는 것이 중요하겠죠.

이 바닥에서 가장 유명한 CI/CD 도구인 Jenkins

쿡앱스에서는 그동안 유니티 프로젝트 빌드 시 Jenkins라는 도구를 많이 사용하였습니다. Jenkins에 대한 경험이 풍부하신 분들도 많고, 사내 문서를 살펴보면 빌드나 각 스토어에 업로드하는 작업 등이 잘 정리되어 있습니다. 따라서 쉽게 접근하자면 이러한 경험을 잘 살려 Jenkins 머신을 하나 세팅하여 워크플로를 구성하면 끝입니다만… 결론만 말씀드리자면 기술지원팀에서는 Jenkins를 사용하지 않았습니다.

역사가 있는 유서 깊은 도구이자 잘 검증되었고, 사내에서도 잘 쓰고 있는 Jenkins를 사용하지 않은 이유는 무엇일까요? 그 이유를 간단하게 살펴보면 다음과 같습니다.

  • 인프라 관리의 시점에서 바라보면 Jenkins는 부족한 부분이 많습니다. 플러그인의 버전 관리가 어렵고, 머신에 필요한 도구가 있다면 Jenkins 외부에서 따로 설치를 진행하여야 합니다. 사내에 있는 Jenkins 머신을 살펴보면 설정이 다 제각각 달라서 관리 코스트가 매우 큰 편입니다.
  • GitHub과의 연동이 매끄럽지 않습니다. 처리하지 못하는 GitHub 이벤트들이 많이 있고, 필요한 경우 Webhook을 이용하여 직접 커스텀하여 이를 처리해야 합니다. 이는 복잡한 CI/CD 워크플로를 붙일 때 많은 부담이 됩니다.
  • 새로운 기술을 도입하고자 하는 열망이 팀 내에 있었습니다. 😄

팀에서는 여러 논의 끝에 GitHub에서 제공하는 GitHub Actions를 사용하기로 하였습니다. GitHub Actions가 무엇인지, 이를 어떻게 사용하고 CI/CD 워크플로를 구성하는지 알아보도록 하겠습니다.

GitHub Actions 소개

GitHub Actions는 GitHub에서 제공하는 CI/CD 플랫폼입니다. GitHub 리포지토리에서 어떤 이벤트가 발생할 때 동작하는 워크플로를 정의하는 방식이며, 이러한 워크플로를 YAML[3] 문법을 이용해 구성할 수 있는 점이 가장 큰 특징입니다. GitHub Actions는 2018년 10월에 베타 버전[4]이 출시되었으며, 2019년 11월에 정식 버전[5]으로 전환하였습니다.

다음은 리포지토리의 Actions 탭에서 워크플로를 만들 때 생성되는 템플릿의 일부입니다.

위에서 정의한 워크플로에서 눈여겨 볼 점은 다음과 같습니다.

  • on 에는 이 워크플로를 트리거 할 이벤트를 정의합니다. 여기서는 main 브랜치에 커밋이 푸시되었을 때 워크플로가 동작합니다.
  • jobs 에는 워크플로에 들어갈 작업들을 정의합니다.
  • runs-on 에는 워크플로를 실행할 러너를 정의합니다. 여기서는 ubuntu-latest 머신을 사용하고 있습니다.
  • steps 에는 하나의 작업에 들어가는 스텝들을 정의합니다.
  • uses 에는 사용할 액션의 패키지 이름이 들어갑니다. 여기서는 러너에서 리포지토리에 접근할 수 있도록 세팅 및 체크 아웃을 하는 actions/checkout 액션을 사용하고 있습니다. 이처럼 GitHub Actions는 복잡하거나 자주 사용하는 작업을 재사용이 가능하도록 Actions 라는 모듈로 묶을 수 있으며, 이러한 액션은 GitHub에서 공식적으로 제공하는 것을 사용하거나 다른 사람이 작성한 것을 가져올 수 있습니다.
  • run 에는 러너에서 명령어를 실행할 때 사용합니다.

이제 이 워크플로를 리포지토리에서 .github/workflows 디렉토리 안에 파일로 저장해두면, 이벤트가 발생할 때 자동으로 워크플로가 트리거되어 Actions 탭에서 확인할 수 있습니다.

리포지토리의 Actions 탭에서 로그를 확인할 수 있습니다

워크플로 구성

CI/CD 워크플로를 구성하려면 우선 수동으로 하는 프로세스를 정확하게 파악한 후, 이를 어떻게 자동화하고 어떤 정책을 가져갈 것인지 정하여야 합니다. 이를 위해 우선 Unity 커스텀 패키지를 배포할 때 하는 일련의 과정을 나열해보겠습니다.

  1. package.json 파일을 열어서 version 을 원하는 값으로 수정합니다.
  2. 커밋을 한 후 태그를 매깁니다.
  3. npm login 명령어를 이용해 사내 패키지 레지스트리에 로그인합니다.
  4. npm publish 명령어로 패키지를 배포합니다.

이제 차례대로 위 과정을 자동화하는 워크플로를 구성하면 됩니다. 하지만 실제로 작성하려면 몇 가지 고민해야 할 점들이 있습니다.

워크플로를 어떻게 실행할 것인가?

GitHub Actions에서는 워크플로를 트리거하는 다양한 이벤트[6]들을 지원합니다. 특정 라벨의 PR이 병합되거나 특정 패턴의 태그가 푸시되었을 때, 심지어는 특정 파일을 수정하는 커밋이 푸시되었을 때 워크플로를 실행하도록 설정할 수도 있습니다. 패키지 버전은 시맨틱 버전[7]을 최대한 따르도록 작성하고 있기에, 어떤 버전을 매길 지 정하는 것은 자동화가 어렵고 사람의 판단을 필요로 합니다. 중요한 것은 어떤 이벤트에 버전 정보를 담아서 워크플로를 실행할 지 정하는 것이었습니다.

여러 방법이 있겠지만, 이 글의 제목에서 알 수 있듯이 여기서는 직접 배포 워크플로를 실행하는 방법을 선택하였습니다. GitHub Actions에서 지원하는 이벤트 중에서는 workflow_dispatch 가 있는데, 이를 이용하면 액션 탭에서 직접 워크플로를 실행할 수 있도록 버튼을 노출합니다. 더 중요한 점은 이렇게 워크플로를 트리거할 때 원하는 입력을 넘겨줄 수 있고, 이 값을 워크플로 내에서 읽고 사용할 수도 있습니다.

예를 들어 다음과 같이 workflow_dispatch 이벤트를 정의하면,

다음 그림과 같이 입력 필드가 나오게 됩니다.

클릭 한 번이면 배포가 되는 마법의 버튼

이제 배포를 하고 싶으면 액션 탭에 들어간 다음, 배포 워크플로를 선택한 후 입력 필드에 원하는 버전을 적고 Run workflow 버튼을 누르면 배포 워크플로를 실행할 수 있는 것이죠. 다른 방법도 많이 있지만, 이 방법이 가장 직관적이라고 생각합니다.

버전 정보를 어떻게 수정할 것인가?

기본적으로 UPM은 package.json 파일의 version 값으로부터 버전 정보를 가져옵니다. 새로운 버전을 배포하기 위해서는 이 값을 바꿔야 하므로 적절한 방법을 이용하여 이 파일을 파싱하고 수정하는 작업이 필요합니다. package.json 파일은 확장자에서도 알 수 있듯이 JSON 형식을 따르므로 이를 파싱하는 데 jq[8]와 같은 프로그램을 사용해도 좋지만, 어차피 특정 값만 수정하는 것이라면 sed[9] 명령어를 이용하여 특정 패턴을 수정하는 방식으로도 작성할 수 있습니다.

여기서는 -e "s/regexp/replacement/ 옵션을 이용하여 version 뒤에 있는 값을 워크플로 트리거 시 입력받은 값으로 바꾸도록 작성하였습니다.

package.json 파일이 변경되었으므로, 이를 커밋하고 원격 리포지토리에 반영하여야 합니다. 그리고 태그 역시 생성하고 푸시하여야 합니다. 이는 다음과 같이 작성할 수 있습니다.

인증에 필요한 민감한 정보는 어떻게 전달하는가?

여기서 조심할 점은, GitHub 리포지토리에 푸시할 때 인증을 진행하여야 한다는 것입니다. 일반적으로는 평범하게 GitHub 아이디와 토큰을 입력하면 되지만, CI/CD에서는 이러한 상호작용 과정이 워크플로를 실패하도록 하는 원인이 됩니다. 이를 막으려면 리포지토리 주소를 명시할 때 다음과 같이 사용자 이름과 토큰을 같이 전달하여야 합니다.

하지만 이는 보안적으로 문제가 됩니다. PAT(Personal Access Token)는 사실상 패스워드에 해당하는 개념이며, 이 토큰이 있으면 주어진 권한 내에서 자유롭게 계정을 사용할 수 있게 됩니다. 따라서 실제 리포지토리에 이러한 코드가 있으면 매우 큰 보안 사고로 이어질 수 있습니다.

다행히 GitHub Actions는 이러한 민감한 값들을 저장하는 공간을 제공해주며, 토큰의 경우 아예 워크플로 내에서 인증 시 사용할 수 있도록 GITHUB_TOKEN [10] 키를 제공합니다. 이 키는 secrets 컨텍스트에서 가져올 수 있는데, 이름에서 알 수 있듯이 비밀스럽게 보관되어야 하는 값들이 저장되는 곳입니다. 이러한 값들은 워크플로 내에서 암호화된 상태로 유지되며, 로그 상에서는 마스킹되어 보이지 않습니다.

이를 이용하면 다음과 같이 안전하게 리포지토리 주소를 명시할 수 있습니다.

레지스트리에 배포하려면?

이제 배포할 패키지가 준비되었으니 레지스트리에 로그인한 후 배포를 하면 끝입니다. 이 때 npm 명령어를 이용하므로 node 환경이 필요한데, GitHub에서는 이러한 node 환경을 구축하기 위한 actions/setup-node[11] 액션을 공식적으로 제공합니다. 그리고 레지스트리 로그인 시 필요한 인증 정보는 앞에서와 마찬가지로 secrets 컨텍스트에 추가한 후 이를 읽어들이면 됩니다.

종합

위에서 작성한 워크플로를 종합하면, 버튼 하나만 누르면 배포 과정이 자동으로 진행되어 바로 UPM을 통해 배포한 패키지를 설치할 수 있습니다. 다음은 실제로 사용 중인 워크플로를 간략화하여 가져온 것입니다. 약 50여 줄의 코드 만으로 마법의 버튼을 만든 것이죠. 놀랍지 않나요? 😋

마치며

지금까지 GitHub Actions를 이용하여 버전을 입력하면 패키지가 배포되는 워크플로를 구축하였습니다. 이처럼 GitHub Actions는 비교적 간단한 방법으로 워크플로를 구축할 수 있으며, GitHub과의 연동도 매끄럽게 진행할 수 있습니다. 무엇보다 워크플로에 대한 정의를 리포지토리 내에서 바로 확인할 수 있어, 어떠한 CI/CD가 있는 지 쉽게 파악이 가능한 것 역시 GitHub Actions의 큰 장점이라고 생각합니다.

긴 글 읽어주셔서 감사합니다. 다음 편에서는 이러한 워크플로를 구성한 후 이를 어떻게 더 발전시켰는지에 대한 이야기를 드리도록 하겠습니다.

레퍼런스

  1. https://docs.unity3d.com/kr/2021.3/Manual/CustomPackages.html
  2. https://docs.unity3d.com/kr/2021.3/Manual/Packages.html
  3. https://yaml.org/spec/1.2.2/
  4. https://github.blog/2018-10-16-future-of-software/
  5. https://github.blog/changelog/2019-11-11-github-actions-is-generally-available/
  6. https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows
  7. https://semver.org/
  8. https://github.com/stedolan/jq
  9. https://man.archlinux.org/man/core/sed/sed.1.en
  10. https://docs.github.com/en/actions/security-guides/automatic-token-authentication#about-the-github_token-secret
  11. https://github.com/actions/setup-node

--

--