Nexus 에 pypi-all · pypi-host · pypi-proxy 세 저장소가 있는데 pip install 은 어디를 봐야 하는지, 내가 만든 패키지는 어디에 올려야 하는지가 헷갈린다. URL 을 잘못 잡으면 No matching distribution found for <패키지> 나 업로드 시 404 Not Found 가 난다.
Nexus 의 PyPI 저장소는 형식이 세 가지이고, 이름은 관례일 뿐 역할은 형식이 정한다.
| 형식 | 흔한 이름 | 역할 | 읽기 | 쓰기 |
|---|---|---|---|---|
| proxy | pypi-proxy |
공개 PyPI 를 대신 받아 캐시 | 가능 | 불가 |
| hosted | pypi-host |
사내에서 만든 패키지 보관 | 가능 | 가능 |
| group | pypi-all |
위 둘을 한 URL 로 묶음 | 가능 | 불가 |
여기서 규칙이 하나 나온다. 읽기는 group 을 보고, 쓰기는 hosted 로 보낸다. group 에는 업로드할 수 없다.
index-url 로 group 저장소의 /simple 경로를 지정한다. /simple 은 PEP 503 이 정한 단순 저장소 API 의 진입점이며, pip 이 패키지 목록을 읽는 자리다.
pip install --index-url http://nexus.example.com:8081/repository/pypi-all/simple <패키지>
매번 적지 않으려면 설정 파일에 둔다. 리눅스는 ~/.config/pip/pip.conf 또는 /etc/pip.conf, 윈도우는 %APPDATA%\pip\pip.ini 다.
[global]
index-url = http://nexus.example.com:8081/repository/pypi-all/simple
trusted-host = nexus.example.com
HTTPS 가 아닌 평문 HTTP 를 쓰면 pip 이 경고와 함께 거부하므로 trusted-host 를 함께 적는다. 사설 CA 로 HTTPS 를 붙였다면 trusted-host 대신 cert = /etc/pki/ca-trust/.../ca.crt 로 CA 를 지정하는 편이 낫다.
익명 읽기를 막아 두었다면 자격증명을 URL 에 넣는다. 비밀번호에 특수문자가 있으면 퍼센트 인코딩해야 한다.
[global]
index-url = https://deploy:${NEXUS_PASSWORD}@nexus.example.com/repository/pypi-all/simple
이 메시지는 "저장소에는 붙었는데 그 이름의 배포본을 못 찾았다"는 뜻이다. 순서대로 가른다.
먼저 인덱스 경로가 실제로 응답하는지 본다. HTML 목록이 오면 정상이다.
curl -sI http://nexus.example.com:8081/repository/pypi-all/simple/requests/
/simple 을 빠뜨리면 404, group 대신 hosted 만 보면 공개 패키지를 못 찾는다. proxy 저장소가 원격 PyPI 에 나가지 못하는 폐쇄망이면 캐시에 없는 패키지는 계속 못 찾는다.
플랫폼과 파이썬 버전도 본다. 저장소에 manylinux 휠만 있는데 대상이 다른 아키텍처이면 같은 메시지가 난다.
pip debug --verbose | head -30
pip install --index-url ... --no-cache-dir -v <패키지>
-v 를 주면 pip 이 실제로 어느 URL 을 두드렸는지 찍힌다. 이것이 가장 빠른 확인법이다.
업로드 대상은 반드시 hosted 저장소다. ~/.pypirc 에 항목을 만든다.
[distutils]
index-servers =
nexus
[nexus]
repository = http://nexus.example.com:8081/repository/pypi-host/
username = deploy
password = ${NEXUS_PASSWORD}
빌드한 뒤 올린다.
python -m pip install --upgrade build twine
python -m build
twine upload --repository nexus dist/*
twine: command not found 는 twine 이 사용자 영역에 설치돼 ~/.local/bin 이 PATH 에 없는 경우가 대부분이다. 모듈로 부르면 경로 문제를 피한다.
python -m twine upload --repository nexus dist/*
업로드에서 404 가 나면 다음을 본다. group 저장소를 대상으로 삼았는지, 저장소 이름의 철자가 맞는지, 그 계정에 해당 저장소의 쓰기 권한이 있는지다.
repository 값 끝에 /simple 을 붙여야 업로드가 됐다는 사례가 있으나, Nexus 3 의 표준 업로드 엔드포인트는 /repository/<hosted>/ 이다. 버전이나 리버스 프록시 구성에 따라 달라질 수 있으므로, 두 형태를 모두 시험해 보고 되는 쪽을 쓴다. (확인 필요)
권한 오류는 401 · 403 으로 온다. 404 와 구분해서 읽는다.
Nexus 의 proxy · hosted · group 3분 구조는 npm · Maven · yum · Docker 에서도 같다. "읽기는 group, 쓰기는 hosted" 라는 규칙을 그대로 적용하면 된다.