Programming Language/Python

[Python] Private PyPI Server 세팅 및 upload and install packages

HJChung 2022. 9. 4. 23:13
TL;DR

Private PyPI Server 세팅 후 package 업로드 및 설치 사용방법에 대해서 정리해보는 글입니다. 
아래의 모든 실습에 해당하는 코드는 https://github.com/Gracechung-sw/python-namespace-package-sample 에 있으며 README를 참고하실 수 있습니다. 
가장 많은 도움이 된 자료는 https://testdriven.io/blog/private-pypi/ 입니다. 

Background

크고 복잡하며 확장성을 고려한 소프트웨어는 여러 모듈로 분리 될 수 있고, 각각 ownership을 가지고 주도적으로 개발하는 개발자가 다를 수 있다. 

Use Case를 생각해보면(use case라고 적고, 내가 현업에서 하고 있는 일이라고 읽는다 ㅎㅎ) 현업에서  머신러닝(ML) 모델 서빙을 위한 서버를 포함한 여러 백엔드(BE) 서비스 서버가 있고, 다양한 머신러닝 모델 코드들이 있을 경우가 있다. 

이 때 BE는 견고하되, 여러 버전과 예측 결과가 나올 수 있는 ML 모델들을 모두 서빙할 수 있는 유연하고 확장성을 고려한 디자인이면 좋을 것이다. 

또한 ML 모델 코드는 내부 구현은 달라질 수 있지만 각각의 input, output interface나 file structure는 통일되어 있어야 BE에서 사용하기 적합하다. 

 

하지만 최근까지 

- 모든 ML 모델의 repository가 따로 있는 반면 코드 중복은 많았고,

- 각 모델의 folder structure가 다 달라 그에 따라 이를 사용하는 많은 타 project code에서 import 시 어려움이 있다.

- 그리고 가장 많은 실수, 문제를 유발하고 있고, 앞으로는 더 그럴 가능성이 많아질 수 있는 건 바로  ML 모델 코드를 git submodule를 통해 백엔드에서 가져가 사용하고 있다는 것이다. 


아직 monorepo가 아닌 현 상태에서는

작년에 블로그에 정리한 [Git] git submodule 의 <배경> 부분에서 알 수 있듯이 가져다 쓸 모듈이 하나일 때는 이 방법이 굉장히 편리하고 합리적인 방법이라고 생각했다. 

 

하지만 여러개의 모듈을 모두 git submodule로 가져와 쓰다보니 매번 최신 버전으로 수동으로 pull 해주어야 하고, 버전이 명시되어있지 않아 매번 git commit history를 확인해야 알 수 있는 등의 실수, 문제가 많이 발생한다. 

 

What to do

그래서 이번 기회에 

- 하나의 repository로 모든 ML 모델의 코드를 합치고

- 각 모델은 하나의 file structure를 정하고 앞으로 해당 repo에 push 되는 모든 AI model은 해당 file 구조를 가지고 있어야 한다. 

- 또한 각 ai model은 더이상 git submodule이 아니라 package 형태로 install 되어 사용할 수 있게 하는 목표를 세웠다. 

 

이를 위해

Action Item

  1. 모든 AI model code에 대해서 unit test, inference test 를 작성하고 테스트 자동화를 합니다. 
  2. 하나의 folder structure를 딱 정하고 모두 이것에 맞게 재배치합니다.
  3. 코드 중복을 최소화 하기 위해 common folder(module) 에 필요한 것을 모두 넣고 여기서 import해서 사용하도록 합니다. 
  4. 앞으로 모든 코드는 Test 에 통과되어야만 머지 가능합니다. 
  5. 각 AI model은 더이상 git submodule이 아니라 package 형태로 install 되어 사용할 수 있게 합니다. 
  6. 이대로 다 정리가 된 후 test 통과가 되고 나면 master branch로 한 번 merge 시키고자 합니다.

로 정하고 현재 진행중이다. 

 

이 과정에서 많은 것들을 배우고 적용해보았지만 가장 블로그에 정리하고 싶은 주제는 

1. python namespace package 로 패키징 하는 것과 

2. python package의 패키징 형태, 설치 방법, 패키지 관리 방법등을 생각해보고 정책을 정하고 문서화 한 과정이다. 

 

그래서 이번에 작성해볼 내용은 두 번째 주제에서 'private PyPi server 생성 후 내가 만든 python package upload 및 설치해서 사용해보기' 이다.

 

패키지 관리 방법

패키지 파일은 Github에 둘 수도 있고, 회사(또는 개인) 자체 PyPI 서버를 만들어서 둘 수도 있다. 

이 때 고려해야 할 것은 모두 private하도록 해야 한다는 것이다. (github repository도 private, pypi server도 private..!)

Github를 사용해서 설치해보는 것은 How to install a Python pip Package from github (https & ssh) 해당 영상에 매우 잘 설명이 되어있고, 이번에는 로컬(MacOs)에 PyPI server를 띄워서 사용하는 것을 정리해보고자 한다. 


1. PyPI Setup

PYPI는 Python Packagy Index로,  우리가 pip install로 설치하여 사용할 수 있는 파이썬 관련 패키지 저장소이다. 사내 프로젝트들이거나 private하게 설치되어야 하는 사내에서 개발된 패키지들은 private Pypi server를 구축하고 여기에 업로드해서 사용하는 것이 필요하다. 

 

우선 private한 PyPI server에서 인증을 위해서  htpasswd 를 사용한다.  .htpasswd 파일은 일반적으로 HTTP 인증을 사용하여 비밀번호로 파일, 폴더 또는 전체 웹 사이트를 보호 할 때 사용되며 .htaccess 파일 내에서 규칙을 사용하여 구현된다고 하며 Apache가 기본적으로 .htpasswd를 사용하도록 미리 구성되어 있다고 한다. 

그래서 우선 

apache를 설치하고
$ brew update
$ brew install apache2

user를 생성한다. 

$ mkdir auth
$ cd auth
$ htpasswd -sc .htpasswd <SOME-USERNAME>
# Use htpasswd -s .htpasswd <SOME-USERNAME> for adding additional users.
# example:
#   $ htpasswd -sc .htpasswd hjchung

그 다음 PyPI 서버를 docker로 띄우기 위해 docker-compose 파일을 작성한다. 

version: '3.7'

services:
  pypi-server:
    image: pypiserver/pypiserver:latest # We defined a single service called pypi-server that uses the pypiserver Docker image.
    ports:
      - 8080:8080
    volumes:
      - type: bind
        source: /Users/grace/Study/my-pypi-server/auth # We defined a bind mount to mount the "/home/ec2-user/pypi/auth" folder (where the .htpasswd file resides) on the host to "/data/auth" inside the container.
        target: /data/auth
      - type: volume
        source: pypi-server # We also defined a named volume called pypi-server that maps to the "/data/packages" folder in the container. If the container goes down for whatever reason, the volume and uploaded PyPI packages will persist.
        target: /data/packages
    # We also updated the command so that the /data/auth/.htpasswd is used as the password file (-P /data/auth/.htpasswd) and the update, download, and list commands require authentication (-a update,download,list).
    command: -P /data/auth/.htpasswd -a update,download,list /data/packages # The -P . -a . allows unauthorized access and /data/packages indicates that packages will be served from the "/data/packages" folder in the container.
    restart: always

volumes:
  pypi-server:

그리고 

$ docker-compose up -d --build

명령어를 통해 서버를 실행시키면

이렇게 잘 뜨는 것을 확인할 수 있다. 

 

2. Upload Python Package

PyPI server에 내 package를 업로드 하기 위해 sample namespace package인 https://github.com/Gracechung-sw/python-namespace-package-sample 를 준비해보았다. (자세한 사항은 README와 각 폴더의 setup.py를 참고하실 수 있습니다. )

📦mynamespace
┣ 📂a_module
┃ ┣ 📂AI
┃ ┃ ┗ 📜analyze.py
┃ ┗ 📜**init**.py
┣ 📂b_module
┃ ┣ 📂AI
┃ ┃ ┣ 📜**init**.py
┃ ┃ ┗ 📜analyze.py
┃ ┗ 📜**init**.py
┗ 📂common
┃ ┣ 📂types
┃ ┃ ┗ 📜**init**.py
┃ ┣ 📜**init**.py
┃ ┣ 📜exception.py
┃ ┗ 📜log.py
이런 mynamespace라는 패키지가 업로드되고 설치되어서 사용하는 것이 목표이다. 
 
업로드를 위해  Twine을 설치한다. 
$ pip install twine

그리고 앞서 띄운 PyPI server (http://localhost:8080) 에 위 패키지를 업로드해본다. 

$ cd common_pkg
$ python setup.py sdist
$ twine upload --repository-url http://<PUBLIC-IP-ADDRESS>:8080 dist/*

# example:
# twine upload --repository-url http://localhost:8080 dist/*

그러면 앞서 htpasswd 설정시 해 준 username과 password를 입력하라고 하고, 맞게 입력하였을 때 업로드가 성공적으로 된다. 

Uploading distributions to http://localhost:8080
Enter your username: hjchung
Enter your password:
Uploading common_pkg-1.1.tar.gz
100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.3/4.3 kB • 00:00 • ?

 

만약 매번 이런식으로 인증을 하기가 번거롭다면 ~/.pypirc 파일에 아래와 같이 설정을 해 줄 수있다. 

# vi ~/.pypirc.

[distutils]
index-servers=
    pypi
    devserver

[pypi]
username: hjchung
password: 어쩌구

[devserver]
repository: http://localhost:8080
username: hjchung
password: 어쩌구

그러면 그 다음부터는 

$ twine upload --repository devserver dist/\*

이렇게만 해 줘도 잘 업로드가 된다. 

그리고 PyPI 서버에서 

The complete list of all packages can be found here or via the simple index. 부분의 here나 simple 를 누르면 

업로드시에 했던 것처럼 동일하게 인증 과정을 거치고, 

이런식으로 업로드한 패키지들을 볼 수 있다. 

 

3. Install uploaded Python Package

그럼 이렇게 PyPI 서버에 업로드한 패키지를 설치해서 사용하려면

$ pip install --index-url http://<PUBLIC-IP-ADDRESS>:8080 <PACKAGE_NAMW>==<VERSION> --trusted-host <PUBLIC-IP-ADDRESS>

# example:

# pip install --index-url http://localhost:8080 common_pkg==0.1 --trusted-host localhost

이런 식으로 pip install을 사용해서 작성해주면 설치를 할 수 있다. 

 

이 역시 매번 public ip address와 public ip를 작성해주기 귀찮다면 pip.conf 파일에 설정해두고 쓰면 된다. 

$ mkdir .pip
$ cd .pip
$ vi pip.conf

[global]
extra-index-url = http://localhost:8080
trusted-host = localhost

그러면 그 다음부터는 간단하게 

$ pip install <PACKAGE_NAMW>==<VERSION>

# example:

# pip install common_pkg==0.1

만 해주면 된다!

 

지금까지 Private PyPI Server 세팅 후 package 업로드 및 설치 사용방법에 대해서 정리해보았다. 

많은 자료들 중 https://testdriven.io/blog/private-pypi/ 해당 자료가 가장 자세하게 되어있어서, 거의 번역이라고 할 수 있을 정도로 해당 글을 참고하여 실습 후 내가 이해한 바대로 작성해 본 글이다. 

 

더 나아가

PyPi server로 Github을 사용하는 것과 직접 PyPI 서버를 띄워서 사용하는 것의 장/단점을 알아보고

나의 상황(즉, 회사의 상황)에 맞게 패키징 정책등을 세우는 작업이 앞으로 필요하다. 

이 과정에서 또 배우고 나누고 싶은 것이 있으면 또 정리해보려고 한다. 

 

Reference

글 중간중간에 링크를 달아두었습니다.