안녕하세요. 기술지원팀에서 Unity 클라이언트 개발자로 일하고 있는 오평석입니다.
기술지원팀에서는 각 스튜디오가 사용하는 공통 모듈을 개발하고, 이를 Unity 패키지 형식으로 관리하고 있습니다. Unity 커스텀 패키지[1]를 제작하여 사내 레지스트리에 배포하면 각 스튜디오에서는 다음 그림과 같이 Unity Package Manager[2]을 열어 설치를 하는 방식입니다.
Unity Package Manager에 대한 소개는 다음 글을 참고해주세요.
이 글에서는 이러한 패키지를 배포하기 위한 CI/CD 워크플로의 도입과 이를 어떻게 구성하였는지, 어떤 고민을 하였는지, 어떻게 개선하고 발전시키고 있는지에 대한 소개를 2편에 걸쳐 전달드리고자 합니다.
참고: 글의 복잡도를 줄이기 위해 실제 사용 중인 워크플로를 약간 단순화하여 서술하고 있습니다.
CI/CD 도구의 선택
당연한 이야기이지만 우선 CI/CD 워크플로를 도입하려면 어떤 도구를 선택해야 하는지부터 정해야합니다. DevOps가 주목받으면서 다양한 CI/CD 도구가 등장하였고, 각자 단점을 보완하고 장점을 부각시키며 개발자들의 선택을 받기 위해 노력합니다. 선택지가 얼마 없던 시절에 비해 지금은 선택의 자유가 생겼으니 상황에 맞는 도구를 선택하는 것이 중요하겠죠.
쿡앱스에서는 그동안 유니티 프로젝트 빌드 시 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 탭에서 확인할 수 있습니다.
워크플로 구성
CI/CD 워크플로를 구성하려면 우선 수동으로 하는 프로세스를 정확하게 파악한 후, 이를 어떻게 자동화하고 어떤 정책을 가져갈 것인지 정하여야 합니다. 이를 위해 우선 Unity 커스텀 패키지를 배포할 때 하는 일련의 과정을 나열해보겠습니다.
package.json
파일을 열어서version
을 원하는 값으로 수정합니다.- 커밋을 한 후 태그를 매깁니다.
npm login
명령어를 이용해 사내 패키지 레지스트리에 로그인합니다.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의 큰 장점이라고 생각합니다.
긴 글 읽어주셔서 감사합니다. 다음 편에서는 이러한 워크플로를 구성한 후 이를 어떻게 더 발전시켰는지에 대한 이야기를 드리도록 하겠습니다.
레퍼런스
- https://docs.unity3d.com/kr/2021.3/Manual/CustomPackages.html
- https://docs.unity3d.com/kr/2021.3/Manual/Packages.html
- https://yaml.org/spec/1.2.2/
- https://github.blog/2018-10-16-future-of-software/
- https://github.blog/changelog/2019-11-11-github-actions-is-generally-available/
- https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows
- https://semver.org/
- https://github.com/stedolan/jq
- https://man.archlinux.org/man/core/sed/sed.1.en
- https://docs.github.com/en/actions/security-guides/automatic-token-authentication#about-the-github_token-secret
- https://github.com/actions/setup-node