실무에서 devpi 서버를 빠르고 안정적으로 구축하는 방법

devpi 서버를 구축하는 빠르고 안정적인 방법은 뭘까?

지금까지 devpi 서버를 구축하고, export(내보내기) 및 import(불러오기) 기능까지 살펴보았다. 그렇다면, 실무에서 폐쇄망에 devpi 서버를 구축하는 가장 빠르고 안정적인 방법은 뭘까?

내 경험을 기준으로는 아래와 같은 작업 순서가 “그나마” 빠르고 안정적인 devpi 서버 구축을 보장할 수 있다고 생각한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 순서
(1) 외부망에서 wheel 파일 생성(다운로드)
(2) wheel 파일을 폐쇄망으로 옮겨온 뒤, devpi 서버에 업로드
(3) 폐쇄망 내 사용자 PC에서 devpi 서버로에 패키지 요청

# 도식
[외부망 PC]
  └─ wheel 생성(다운로드) (모든 dependency 포함)(파일 이동)
[폐쇄망 devpi 서버]
  ├─ devpi 서버 생성 (최초에만)
  └─ wheel 업로드
         ↓
[폐쇄망 사용자 PC]
  └─ pip install (내부 서버만 사용)

이렇게 생각하는 이유는 다음과 같다.

(1) 이미 빌드된 패키지 사용(wheel)

devpi-export, import 를 수행하면, 이미 검증된 devpi 상태를 그대로 떠올 수 있으므로 좋을 것만 같다. 하지만, export 된 데이터에는 빌드가 필요한 의존성 패키지가 포함될 수도 있다. 이게 왜 문제냐면.. 패키지를 빌드하는 작업은 OS 플랫폼과 여러 환경에 따라 달라진다는 점 때문이다. 반면, wheel 파일로 패키지를 다운로드 하면, 이미 패키지가 빌드가 완료된 상태이므로 추가적인 빌드를 고민하지 않아도 된다.

(2) 패키지 추가 파이프라인과 일원화

devpi 서버를 어떻게든 구축했다고 가정해보자. 추가로 필요한 패키지가 생겼을 때, 어떻게 추가하는 게 가장 효율적일까? 바로 wheel 파일로 패키지를 다운로드 받은 뒤, devpi 서버로 업로드하는 방식이 가장 효율적이다. 추가 패키지 하나때문에 export → import 를 다시 수행하는 것은 불필요하고 비효율적이다. 어짜피 devpi 서버 구축 파이프라인을 구성할거면, 초기 세팅과 패키지 추가 모두 같은 결의 파이프라인을 가지게 하는 것이 정신건강에도, 관리 측면으로도 좋을 것이다.

(3) 재현 가능성

wheel 파일로 패키지를 다운로드 할 때에는 pip download 명령어를 사용한다. 이 명령어를 사용하면, 정확하게 내가 원하는 OS 플랫폼과 파이썬 버전에 맞는 패키지를 다운로드 받을 수 있다. 이는, 작업을 동일하게 수행할 수 있는 재현 가능성을 보장하는 장점이 될 수 있다.

그래도 문제는 발생할 수 있다

물론, 이 방법으로 devpi 서버를 구축한다 해도 문제가 발생할 수 있다. 대표적인 문제가 바로 의존성이 깨지는 것이다. 특히나 ipykernel 을 wheel 파일로 다운로드 받아 devpi 서버로 올리는 작업 중에는.. 여러 번의 의존성 불일치 문제가 발생할 수도 있다. 하지만 pip download 를 통해서 하나하나씩 깨진 의존성을 채워나가면, 생각보다 쉽게 문제를 해결할 수 있으니 포기하지 말자.

구축 방법

전제

  • 외부망에 파이썬과 pip 가 설치되어있어야 한다.
  • 외부망의 pip 는 최신 버전으로 업그레이드 해놓는다.

(1) (🌐외부망) 워크스페이스 준비

  • 패키지를 다운로드 받기 전, 워크스페이스를 먼저 준비해보자
  • 외부망에 구축하면 된다.
1
2
3
4
.
├─ wheelhouse        # 패키지 wheel 파일을 보관할 디렉터리
├─ requirements.txt  # 필요한 의존성을 정의한 파일
└─ README.md         # 작업 순서 및 명령어를 보관할 파일

(2) (🌐외부망) 패키지 준비

  • 인터넷 연결이 되는 외부망에서 패키지를 다운로드 받는 단계이다.
  • 우선, 필요 패키지를 파일로 명시해서 저장해둔다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# requirements.txt

# 안정성을 위해 미리 다운로드 하는 것을 권장
setuptools<82 # pkg_resources 모듈 의존성 해결
setuptools<81
setuptools<80
appnope # MAC 용 dependency (ipykernel과 같이 플랫폼에 상관없이 모든 dependency를 검사하는 경우가 있음)
pexpect # Linux 용 dependency (상동)
appnope >=0.1.2
wheel
packaging

# devpi 다운로드
devpi-server
devpi-web    # 오류 발생시 제외해도 됨
devpi-client

# 필요한 패키지 정의
ydata-profiling
pandas
numpy
matplotlib
ipython
ipykernel
  • 다음으로는 pip download 명령어를 통해 패키지를 다운로드 받는다.
  • 반드시 wheel 파일로 받기 위해 --only-binary=:all: 옵션을 적용한다.
  • 플랫폼(OS) 및 파이썬 버전도 명시해준다.
1
2
3
4
5
6
7
8
9
pip download \
    --platform "win_amd64" \ # 플랫폼 지정 ("manylinux2014_x86_64", "win_amd64", "macosx_11_0_arm64")
    --python-version 313 \   # 파이썬 버전 (39, 310, 311, 312 ..)
    --abi "cp313" \          # 호환성 지정. C로 짜인 모듈과의 매칭. (313 = 3.13...)
    --implementation cp \    # 파이썬 구현체 (cp = CPython / pp= PyPy)
    --only-binary=:all: \    # 타겟 환경에 맞춰 이미 컴파일 완료된 바이너리 파일(wheel)만 다운로드 하도록 강제
    --dest ./wheelhouse \    # 패키지를 다운로드할 디렉터리
    -r ./requirements.txt    # 다운로드할 의존성 목록
    

(3) (🚪🤖폐쇄망 서버) devpi-server 구동

  • devpi-server 를 구동한다. 이미 구동된 devpi-server 가 있다면 이 단계는 건너뛴다.
  • devpi-server는 “지속적으로 실행상태를 유지할 수 있는” 머신 위에 구동하는 것을 권장한다. (서버 등)
  • 첫 번째로 아래와 같이 워크스페이스를 준비한다.
1
2
3
# devpi-server 워크스페이스
.
└─ devpi-server  # devpi 서버의 워킹 디렉터리(예정)
  • 다음으로는 devpi 워킹 디렉터리를 초기화한다.
1
devpi-init --serverdir devpi-server
  • devpi-server 를 구동한다.
1
devpi-server --serverdir devpi-server --host 0.0.0.0 --port 3141
  • devpi 사용자와 인덱스를 생성해준다.
1
2
3
4
5
devpi use http://localhost:3141            # 서버 외부에서 접속할 경우 http://<서버IP>:3141
devpi user -c myuser --password=mypassword # 사용자 생성 : username, password는 지정
devpi login myuser --password=mypassword   # 로그인
devpi index -c myindex bases=              # 인덱스 생성. bases 는 비워둬야 미러를 안찾음
devpi use myuser/myindex                   # 인덱스 사용 테스트
  • 브라우저에서 접속 확인
1
http://<서버IP>:3141

(4) (🌐외부망 → 🚪🤖폐쇄망 서버) 패키지를 폐쇄망으로 이동

  • 다운로드 받은 패키지를 폐쇄망으로 이동한다.
  • 패키지 보관 위치 일원화를 위해 devpi-server 디렉터리가 있는 곳에 함께 두는 걸 권장
  • wheelhouse 디렉터리의 하위 wheel 파일들만 이동하면 된다.
1
2
3
4
# devpi-server 워크스페이스
.
├─ devpi-server
└─ wheelhouse   # 여기로 이동

굳이 devpi-server 가 구동되는 서버에 wheel 파일을 옮기는 이유는
(1) 이 서버에는 devpi 패키지가 설치된 게 보장되어 있음
(2) 보관 장소 일원화

(5) (🚪🤖폐쇄망 서버) devpi server 에 업로드

  • 이제 wheel 파일을 devpi-server 에 업로드할 차례다.
  • 이 작업 또한 devpi-server 가 구동되고 있는 서버에서 수행하는 것을 권장.
  • 우선, 실행중인 devpi 서버에 접속한다.
1
devpi use http://localhost:3141            # 서버 외부에서 접속할 경우 http://<서버IP>:3141
  • 패키지를 업로드할 인덱스에 접근권한이 있는 사용자로 로그인한다.
1
devpi login myuser --password=mypassword   # 로그인
  • 패키지를 업로드할 인덱스에 접속한다.
1
devpi use myuser/myindex                   # 사용할 인덱스 지정
  • wheel 파일들을 업로드한다.
1
devpi upload wheelhouse/*

(6) (🚪🧑‍💻폐쇄망 사용자 PC) 클라이언트에서 사용

  • pip 를 사용해 패키지를 설치하는 경우
1
2
3
pip install \
    --index-url http://<devpi-server IP>:3141/myuser/myindex/+simple \
    <패키지명>
  • uv
1
2
3
4
# pyproject.toml에 아래 추가

[tool.uv]
index-url = "http://<devpi-server IP>:3141"
1
2
# 이후엔 uv add를 사용하면 됨
uv add <패키지명>

Outro

최근 한주 간, 폐쇄망에 파이썬 패키지 서버를 설치하는 데 애를 먹었다. 폐쇄망 환경도 처음인데, PyPI 서버는 사용하고 싶고, devpi 는 처음 써보고.. 정말 힘든 싸움이었다.

괜히 사서고생을 한 게 아닌가, 그냥 외부망에서 conda 환경을 구축한 다음 떠오거나, 아나콘다를 설치하는 게 낫지 않을까도 생각했지만, 런타임 환경이 무거워지는 게 싫기도 했고 uv를 사용하지 못한다는 점도 싫었다.

결국에는 새로운 방법(내겐 새로운)을 도전했고, 이를 통해 지식과 대응방법을 넓혔다는 점에서 뿌듯한 경험이었다.

Comments