Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

사이드 프로젝트로 만든 Private Terraform 모듈 레지스트리: citizen

작년 HashiConf에 참석했을 때 Terraform Module Registry가 공개되었다. 모듈 같은 개념에서는 지극히 자연스러운 흐름인데 npm이나 PyPI와 마찬가지로 공개 레지스트리를 이용하게 되면 회사 내에서는 비공개 레지스트리가 필요하게 된다.

비공개 레지스트리가 필요한 이유는 오픈소스가 아니라서 외부에 공개하기 어려운 것도 있고, 공개되어도 상관없는 내용이더라도 외부에 공개하려면 정리도 해야 하고 관리에도 더 리소스가 들어가기 마련이다. Terraform의 모듈 레지스트리는 엔터프라이즈 버전에서만 비공개 모듈을 사용할 수 있다. 모듈 레지스트리는 오픈 소스가 아니므로 회사 내부에 설치해서 사용할 수가 없지만, 다행히도 API는 문서로 공개되어 있다.

개발 과정

그래서 API를 보고 Private Terraform Module Registry를 만들면 써먹을 수 있겠다 싶어서 citizen이라고 이름 짓고 작년 말부터 만들기 시작했고 손에 익은 Node.jsExpress를 선택했다.(다른 언어로도 자꾸 해봐야 하는데...)

컨트리뷰션 그래프

API 문서는 정리가 잘 되어 있는 편이었다. Terrafrom CLI가 있으므로 만들고 있는 서버에 직접 찔러 보면서 어떻게 요청이 들어오는지 확인하면서 작업을 했다. 작업하다 보니 API 문서에서 잘못된 부분과 내부에서 URL 처리가 잘못되는 부분이 있어서 PR을 3개나 올리게 되었다.

HTTPS

처음 겪은 문제는 Terraform Module Registry가 HTTPS로만 동작한다는 점이다. Terraform CLI에서 레지스트리를 지정하면 서비스 디스커버리 API를 요청해서 JSON을 받아가고 이를 기반으로 다른 API를 요청하게 되는데 여기서 HTTPS로만 동작하게 된다. API 구현 작업 자체는 어차피 테스트코드를 작성할 것이므로 HTTP를 사용해도 되지만 문서만 보고 만들어서 동작한다고 보장할 수 없으므로 Terraform CLI와 통합테스트를 작성해야 했다. 다른 사람들이 얼마나 사용할지는 모르지만, 나중에 버그를 잡거나 Terraform의 기능이 변경되더라도 따라가기 위해서는 통합테스트가 필요했다.

로컬에서 셀프사인 인증서로 동작 여부를 테스트했지만, 통합테스트를 HTTPS로 작성하려고 여러 가지 방법을 고민하다가 ngrok를 이용해서 HTTPS로 통합 테스트를 할 수 있도록 구현했다. 통합테스트는 특정 버전의 Terraform CLI를 다운로드 받아서 서버를 띄우고 ngrok으로 HTTPS 프락시를 만든 다음에 CLI와 동작이 잘하는지를 확인했다.

파일 스토리지

사이드 프로젝트이지만 내가 사용할 가능성을 생각하고 만들고 있으므로 파일 스토리지로 처음에는 S3를 사용했다. 개인 사이드 프로젝트이므로 초기 버전에서는 당연히 범용성보다는 내가 사용할 형태를 먼저 생각하다 보니 디스크 관리를 하지 않으려고 처음에는 S3를 파일 스토리지로 선택했다. 내가 프로덕션에서 이 프로젝트를 사용한다면 디스크 관리를 고민하기보다는 그냥 S3를 쓸 것 같았기 때문이다.

프로젝트 초기에 너무 범용성을 고민하기보다는 적당히 내가 원하는 기능에 맞추는 것은 나쁘지 않은 선택이라고 생각하지만, 결과적으로는 좋지 않은 선택이 되었다. Terraform 모듈을 업로드하면 압축된 모듈 파일을 스토리지에 저장해야 하므로 이 선택은 스토리지에 의존성이 있는 로직의 테스트를 어렵게 만드는 결과가 되었다. 로컬이나 CI에서 테스트할 때마다 S3를 이용하는 건 비용도 비용이고 시간이 오래 걸리기 때문에 nock으로 외부 요청을 모킹해서 테스트를 작성했지만, 테스트 시나리오가 많아질수록 관리가 무척 어려워졌다.

최종적으로는 S3만 지원하는 것은 사용자 편의성에 좋지 않다는 생각에 로컬 디스크를 사용하는 옵션을 추가해서 스토리지 타입을 선택해서 사용할 수 있도록 했다. 이후로는 테스트는 로컬 디스크를 사용하도록 작성하고 S3는 S3의 기능만 따로 테스트하면 되므로 모킹을 대부분 제거하고 테스트가 더 깔끔해졌다. 처음부터 로컬 디스크로 만들었으면 편했을 텐데....

클라이언트

쉽게 생각하면 Terrafor Module Registry의 클라이언트는 Terraform이지만 공식 Terraform Module Registry는 GitHub와 통합되어 있다. 그래서 모듈을 배포하려면 GitHub에 올린 뒤에 이 저장소를 Terraform Module Registry에서 연동시켜서 사용하게 된다.

이런 상황이다 보니 citizen에서는 어떻게 모듈을 업로드하게 할 것인가 하는 문제가 생겼다. 공식 레지스트리처럼 GitHub 등과 통합시킬 수도 있지만 Private이라는 특성 때문에 비공개 저장소에 접근하기 위한 권한 관리 등도 해야 하고 비공개이므로 사용자가 GitHub을 사용한다는 보장도 없었고 개인 프로젝트에서 여러 VCS를 지원할 여력도 없었다.

결국, 모듈을 업로드하는 CLI 클라이언트를 만들기로 했다. VCS와 통합하는 것보다야 불편하겠지만 구현하는 입장에서는 환경에 상관없이 사용할 수 있다는 장점이 있어서 Commander.js를 이용해서 CLI를 만들었다. CLI를 만들다 보니 결국 서버와 CLI 클라이언트를 2개 배포해야 하는 상황이 되었는데 HashiCorp의 대부분 도구처럼 하나의 바이너리에서 서버와 클라이언트를 모두 제공하는 게 낫겠다 싶었고 Go 언어에서 하듯이 하나의 바이너리로 배포하니까 파일 하나만 받아놓고 서버, 클라이언트를 모두 사용할 수 있으니까 꽤 편했다.

Commander.js를 이용해서 하나의 CLI로 서버와 클라이언트를 모두 가동할 수 있게 작성하고 전부터 사용해보고 싶었던 Pkg를 이용해서 하나의 바이너리 파일로 만들었다. Pkg는 require()를 추적해서 사용하는 코드만 모아서 하나의 바이너리로 만들어 준다. 기존에는 pouchdb를 사용하고 있었는데 pouchdb가 LevelDB를 사용하기 때문에 네이티브 애드온이 포함되어 있다. pkg가 아직 네이티브 애드온을 번들링 하지 못하는 문제때문에 결국 NeDB로 갈아탔다. Private 모듈 레지스트리의 디비가 엄청나게 클 것 같지는 않으므로 큰 문제는 안될 거라고 생각한다. 처음 pkg를 적용할 때는 Spread Operator를 지원하지 않아서 Spread Operator의 사용을 모두 제거했지만 이후 다시 지원하게 되어 Spread Operator를 다시 적용했다.

통합 테스트

유닛테스트도 작성하고 API 레벨에서도 테스트코드를 작성하면서 작성했지만 결국 Terraform CLI를 이용해서 사용하게 되므로 동작여부를 제대로 확인하려면 Terraform CLI를 이용해서 통합테스트를 작성해야 한다고 생각했다. 만들면서 Terraform으로 동작을 확인하기는 했지만 릴리스를 하고 나서 버그를 확인하거나 동작을 확인하려면 통합테스트가 있는 편이 관리하기 쉽다고 생각했다. 일단 레지스트리를 문서를 보고 만든 것이라서 잘못 이해하고 구현했거나 Terraform에 버그가 있을수도 있으므로...

통합테스트 실행 결과

버전을 지정해놓고 Terraform CLI를 다운로드 받아오는 스크립트를 작성하고 테스트에서 레지스트리 서버를 띄우고(이때 HTTPS가 필요해서 앞에서 얘기한 ngrok이 필요했다) Child Process로 Terraform CLI로 레지스트리에 접속하고 등록된 모듈을 가져오는 기본 통합 테스트를 추가했다. 앞으로 이슈가 생기면 테스트를 추가하면서 확인해 보면 될 것 같다.

릴리스 준비

내가 사용할 목적으로 만든 사이드 프로젝트이지만 아직 사용하고 있지는 않다. 실제로 사용하면서 발견되는 문제가 꽤 있겠지만 일단 기능 구현이 마무리되어 릴리스를 준비했다. 나중에 배포 등을 관리하는 건 귀찮으므로 작업 마무리 할 때 아예 태그를 푸시하면 자동으로 GitHub 릴리스까지 생성하도록 했다. 단순히 소스코드만 배포하면 되는 것이 아니라 pkg로 만든 바이너리를 플랫폼별로 배포해야 했기에 TravisCI에서 바이너리를 생성한 뒤에 GitHub 릴리스에 배포하도록 했다.

GitHub 릴리스 페이지

요즘은 서버를 Docker로 배포하는 게 일반적이므로 당연히 릴리스할 때마다 Docker 이미지로 만들어서 배포하도록 했다. 처음에는 CI에서 직접 만들어서 Docker Hub에 푸시하다가 Docker Hub에 Automated Build가 있다는 걸 최근에 깨닫고 Docker Hub를 이용해서 배포했다. 최근에는 README를 Markdown 대신 AsciiDoc으로 작성하고 있는데 GitHub에서는 괜찮지만, Docker Hub는 AsciiDoc을 파싱하지 못해서 안타까울 뿐이다.

라이센스는 Terraform과 같은 Mozilla Public License 2.0를 사용했다. 보통은 그냥 MIT 라이센스를 쓰는 편이지만 그냥 다른 걸 한번 써보고 싶었다. 라이센스 해석은 항상 어렵지만, MIT와 다른 점은 특허 관련해서 사용할 수 있고 배포할 때 소스코드를 항상 공개해야 하고 수정해도 같은 라이센스로 배포해야 한다는 제약 정도가 있어 보인다. 라이센스는 그냥 LICENSE 파일만 넣어도 무방하지만 오픈소스 라이센스를 검사해 주는 FOSSA를 연동했다.

FOSSA 뱃지

프로젝트 라이센스 뿐만 아니라 의존성의 라이센스검사까지 해준다.

릴리스

개발하면서 버전을 올릴 일은 없지만, 릴리스를 준비하면서 0.2로 작업하다가 0.3으로 릴리스를 했다. 버전에 큰 의미는 없고 어차피 열어 넣고 개발하고 있었으므로 릴리스해도 아무런 일은 일어나지 않는다. 그냥 릴리스하고 프로젝트를 마무리했다는 개인적인 만족감이랄까? ㅎㅎㅎ HashiCorp에서 리트윗이라도 하나 해주지 않을까 기대했지만 역시 그런 일은 일어나지 않았다. ㅎㅎㅎ

citizen을 포크한 사용자

그래도 Terraform 사용자 중에 Private Module Registry를 찾는 사람들이 있는지 다 만들기도 전에 몇 명이 포크 해서 약간 수정도 하고 그랬다. 나만 필요하다고 생각한 건 아니라는 거에 위안하고 있다

작년 말에 시작하면서는 2월 내지 3월 정도까지 마무리하는 게 목표였지만 6개월이나 걸렸다. 그래도 중간에 그만두지 않고 완성해서 릴리스까지 한 것에 만족한다.

2018/06/17 15:13 2018/06/17 15:13