프로그래밍/개발

[개발] 주니어 개발자의 우당탕탕 MSA 전환기 - Nexus 편

riroan 2023. 12. 13. 21:50

지난 스토리의 마지막에 결국 서로 다른 서비스에서 공통으로 사용되는 함수나 ORM객체들을 공유 모듈을 사용하여 진행하기로 결정했다. 대표적으로 Nexus라는 것이 있다고 하여 이걸 사용해서 사설 공유 레포지토리를 구축하게 되었다.

 

Nexus 구축하기

Nexus를 구축하는 방법은 정말 간단하다. 그냥 포트 하나 열고 docker run으로 실행하기만 하면 끝난다. 설치는 이렇게 간단한데도 고민해야 할 상황이 생겼다.

그럼 Nexus 서버는 어디에 있어야 할까?

후보군으로는 EC2와 쿠버네티스가 있었다. 지금까지 쿠버네티스에서 누려온 수많은 편리한 점을 생각하면 "당연히 후자지"라고 생각했다. 하지만 여기에서 반대 의견도 있었다.

Nexus는 dev, prod 같이 사용하기 때문에 쿠버네티스와 별도의 서버에서 돌아가야 한다.

나는 처음에 "엥? 당연히 dev, prod 각각에 Nexus를 올려야되는거 아니야?"라고 생각했다. 하지만 조금만 생각해보면 그럴 필요는 전혀 없었다. ㅋㅋ Nexus 자체가 각 라이브러리의 버전을 모두 가지고 있기 때문에 각 환경에서 어떤 버전을 사용할지만 명시하면 Nexus 서버는 나누지 않아도 된다. 예를들어 0.0.1 ~ 0.0.12 버전까지 있으면 dev는 0.0.12, prod는 0.0.6을 설치하여 사용하는 식이다. 그리고 현재 쿠버네티스 노드는 spot instance로 돌아가기 때문에 라이브러리 설치 시 노드가 다운되면 영문도 모른 채 삽질을 할 문제도 있었다.

 

그렇게 EC2의 의견에 힘이 실리던 차에 해당 방법을 사용하게 되면 피할 수 없는 문제가 있다. 바로 Nexus를 위한 별도의 EC2를 띄워야 하므로 추가적인 비용이 든다는 점이다. 이 문제는 가장 사양이 낮은 EC2를 사용하면 월 N만원으로 해결할 수 있으므로 부담이 적었다. 따라서 EC2로 Nexus만 실행시키는 서버를 만들기로 하였다.

 

하지만 문제가 있다.

Nexus를 실행시키기 위한 최소 사양이 있었던 것 ㅋㅋㅋ. cpu는 4코어를 권장하고 메모리는 4기가보다 낮으면 실행조차 안된다. 그래서 nano급 인스턴스는 사용하지 못했고 적어도 medium은 됐어야 했다. 이것도 2코어 4기가라서 메모리는 통과하지만 cpu에서 경고를 준다. 하지만 사용하는데 문제는 없으니 그냥 사용하기로 했다. 물론 비용은 소폭 더 늘어나겠지만 그래도 부담 없을정도

 

pypi, maven, nuget(?)저장소를 사용할 수 있었다. 전에 npm도 본 것 같은데 여기엔 없나보다. 그렇게 사설 레포지토리가 구축되었다.

 

영광의 첫 업로드

이제 공들여 만든 라이브러리를 Nexus에 업로드할 때가 왔다. 업로드하기 위한 세팅을 끝내고 난생 처음 만들어보는 setup.py도 작성해서 무사히 업로드를 완료하였다. 각 서비스에서 해당 라이브러리를 사용하기 위해 pip install을 하고 서버를 키려고 하는데 자꾸 import에러가 떴다. 분명 install 완료된 것도 확인하고 패키지 목록에 라이브러리 폴더도 있었다. 라이브러리를 열어보니 내가 업로드한 것이 아닌 다른 이상한 라이브러리가 설치돼있었다. "벌써 우리 레포지토리가 해킹당한것인가?"라고 생각하여 즉시 조사해봤는데 Nexus는 안전했고 업로드한 라이브러리도 잘 있었다.

 

원인을 찾아본 결과 내가 Nexus에 업로드한 라이브러리가 public pypi에 있는 라이브러리와 이름이 겹쳐서 발생한 문제였다! 그럼 이제 원인도 알았으니 하고싶은건 

  • 기본적으로 pip install을 하면 private repository를 먼저 탐색한다.
  • 만약 해당 라이브러리가 private repository에 없으면 public repository를 탐색한다.

많이 보이는 흐름이니 당연히 가능할 것이라 생각했다. 하지만 불행하게도 위 방법은 불가능했고 기본 세팅대로 public을 먼저 탐색한 뒤 private을 탐색하는 구조라고 한다.(적어도 pypi는 그렇다.) reference와 이유는 기억이 안나는데 이렇게 세팅된 것은 의도된 사항이라고 한다. 예를들어 public pypi에 "abc==0.0.1"이 있고 Nexus에 "abc==0.0.1"이 있는 상태에서 pip install abc==0.0.1을 하게 되면 public에 있는 라이브러리를 다운받게 되는 것이다. 

 

라이브러리 Versioning

위와 같은 문제가 있었지만 public에 없는 버전이라면 Nexus를 찾기는 한다. 그래서 처음에는 남들이 절대 사용하지 않을만한 버저닝을 사용하려고 했다. 예를들면 0.0.1-nexus-private같은 형식을 도전했다. 정상적으로 업로드 돼서 안도하고 있었는데 이번엔 다운로드 자체가 안되는 것이다;; 

pip._vendor.packaging.version.InvalidVersion: Invalid version: '0.0.1-nexus-private'

오타가 났는지도 확인하고 실제로 업로드가 됐는지도 확인했는데 전혀 문제가 없었다. 검색해도 잘 안나오길래 직접 에러가 난 소스코드를 분석하기 시작했다.

re.compile(’^\s*
    v?
    (?:
    (?:(?P<epoch>[0-9]+)!)?                           # epoch
    (?P<release>[0-9]+(?:\.[0-9]+)*)                  # release segment
    (?P<pre>              , re.IGNORECASE|re.VERBOSE)

 

그랬더니 나온 수상한 코드. 정규표현식을 잘 몰라서 chatgpt에게 물어봤다.

쉽게말하면 라이브러리 버전을 위한 규칙이 존재했고 우리 마음대로 규칙을 벗어날 수 없다는 내용이었다. 그래서 버전만으로 구분하는건 굉장히 위험한 행동이었다. 혹시라도 우리가 지정한 버전을 동일한 이름의 public pypi 레포가 같은 버전을 사용하게 된다면 다운로드 받을 방법이 없었기 때문이다.

 

그렇다면 우리의 Nexus에 올라갈 라이브러리의 이름은 절대로 public pypi에 있는 것과 같으면 안된다. 혹시라도 버전이 겹치게 되면 우리 라이브러리를 사용할 수 없기 때문이다. 우여곡절 끝에 public에 없는 이름을 정하게 되었고(이름을 정하는게 가장 힘들다) 혹시라도 나중에 우리 라이브러리 이름이 public에서 사용될 수 있으므로 미리 public에 우리 라이브러리 이름을 가진 빈 레포를 만들었다. 우리 소유이기 때문에 우리가 업데이트하지 않는 한 다른 사람도 사용할 수 없다.

 

사용 후기

이제 각 서비스마다 공통으로 사용되는 코드를 한 곳에서만 관리하면 되기 때문에 굉장히 편리해졌다. 그럼에도 불편한 점은 있었다.

  • CICD 과정에서 사설 레포지토리 주소를 명시해야한다.
  • 라이브러리에서 한 줄을 고치더라도 다시 업로드와 다운로드를 받는 과정이 필요하다.

하지만 위 사항들은 공유 모듈을 사용하기 위해 감수해야 하는 것들이다. 불편한 점보다 편리한 점이 더 많아서 만족하며 사용중이다!