상세 컨텐츠

본문 제목

[Python] 패키지 배포 리스트 관리 (ft. Requirements.txt 포맷팅 오류)

개발 이야기/Python

by 리치윈드 - windFlex 2023. 1. 18. 16:09

본문

반응형

이번 포스팅이서 다루고자 하는 내용은 Python 소스를 배포하고 구동하는 발생하는 어려움 중 하나의 대해서 이야기 하고자 한다. 결론부터 이야기하면, Python 소스를 배포할 때 사용하는 패키지 리스트 (requirements.txt)가 간혹 호환성 (Compatible)이 없을 때가 있다. 필자의 경우는, 이런경우를 수정하고 배포하거나 검증/수정을 하는데, 다른 개발자가 개발한 배포 버전에서 이런 부분이 다수 발견되었다. 

python 패키지 배포 시 requirements.txt의 설치 목록 포맷팅 오류

다른 분들도 이런 경우가 발생하지 싶어서 포스팅을 남겨 본다. 

 

[순서]

1) Python 패키지 배포 (기본사항 리마인드)

2) Python 패키지 리스트 중 발생 가능한 이슈 

3) 문제 발생 원인

4) 문제 해결 방안

 

[결론]

일관된 requirements.txt 포맷을 출력하기 위해서 다음 명령어를 사용하자.

pip list --format=freeze > requirement_pip_list.txt

 

1. Python 패키지 배포, 가상환경 생성 및 배포 (리마인드)

Python으로 모듈 개발을 완료한 후에는 이제 Staging/Production 환경에 구축하고, 구동을 해야 할것이다. 이미 잘 알고 계시듯, Python에서는 개발환경과 동일한 환경을 구축 할 수 있도록 가상환경 체계와 설치환경을 제공한고 있다. 패치지 관리자가 지원하는 가상환경 (VirtualEnv)과, 설치된 패키지 목록(requirements.txt)을 통해서 수월하게 환경을 재구축 할 수 있다. 

 

간략히 요약해 보자. (PIP 기준)

 

[ 설치 목록 생성 ]

설치된 패키지 목록은 아래와 같이 상태를 고정 (freeze)하면서 목록을 얻을 수 있다. 다음 명령은 pip를 사용하여 requirements.txt를 생성한다. 

pip freeze > requirements.txt

 

 

 

[ 가상환경 생성 및 패키지 설치 ]

다음은 Python 기본 내장 모듈인 `venv`를 사용하여 가상환경을 생성하고, requirements.txt에 표기된 패키지를 설치한다. 

python -m venv .venv
source .venv/bin/activation
pip install -r requirements.txt
가상환경을 생성할 때, venv 외에 virtualenv 또는 conda env를 사용할 수도 있다. virtualenv의 용법은 venv와 거의 유사하면, conda의 경우는 다음과 같이 가상환경을 생성할 수 있다.  ex) `conda create -n <가상환경 이름> python=3.9` 

Conda 등 가상환경 생성에 대한 부분은 다음 포스팅을 참조 바란다: 

2022.12.06 - [개발 이야기/Python] - [Conda-Jupyter] Conda 가상환경과 Jupyter Kernel 연동/생성/삭제

 

[ Docker 환경에서 배포/설치하는 경우 ]

Docker/Container의 경우에는 Container 자체가 가상환경이며, 초기 상태를 가정하고 있으므로 굳이 새로운 가상환경을 만들 필요는 없을 것이다. 이 경우, 아래와 같이 COPY명령어로 requirementx.txt를 복사하고, RUN명령어로 pip install을 실행해 주면 된다. 다음은 Dockerfile의 일부 이다. 

COPY ./requirements.txt /path/to/requirements.txt
RUN pip install -r requirements.txt

 

 

2. Python 패키지 목록 발생 가능 문제점

위에서 우리는 Python 패키지 설치목록을 배포하고, 이를 통해서 개발환경을 재구축하는 방법에 대하여 상기하여 보았다. 이제 발생가능한 문제점을 살펴보자. 아래는 정상적인 상태에서 패키지 목록 (requirements.txt)의 유형을 표기한 것이다. 아래와 같이 requirements.txt는 설치된 패키지 이름과 버전을 표기하고 있는 텍스트 파일일 뿐이다. 

패키지이름1==버전
패키지이름2==버전
패키지이름3==버전

다음은 실제 사례 (requirements.txt) 중 일부이다. 

...
matplotlib==3.6.0
mkl-fft==1.3.1
mkl-service==2.4.0
...

그런데, 개발을 진행하다가 보면, 다음과 같은 이상한 문자열을 보기도 한다. 

numpy @ file:///tmp/abs_653_j00fmm/croots/recipe/numpy_and_numpy_base_1659432701727/work

잘 살펴보면, 패키지 이름을 나열하고, 그 패키지 파일이 어디에 있는지를 표기하고 있는것으로 보인다. 기본적인 pip는 PyPy를 기반으로 <패키지명>, <버전>만 표기하면 그때 그때 다운로드 받아서 설치하는 구조인데, 상기의 표기는 무엇인가 기본적인 정책에 부합하지 않는다. 

아래는 실제 필자가 사용하는 패키지 중 일부에서 발췌한 실 사례이다.  certifi, mkl-random, numpy 등이 비정상적인 포맷으로 표기되어 있는 것을 볼수 있다. 

blinker==1.5
cachetools==5.2.0
certifi @ file:///opt/conda/conda-bld/certifi_1663615672595/work/certifi
cffi==1.15.1
charset-normalizer==2.1.1
click==8.1.3
commonmark==0.9.1
contourpy==1.0.5
decorator==5.1.1
entrypoints==0.4
importlib-metadata==4.12.0
Jinja2==3.1.2
joblib==1.2.0
llvmlite==0.39.1
MarkupSafe==2.1.1
matplotlib==3.6.0
mkl-fft==1.3.1
mkl-random @ file:///home/builder/ci_310/mkl_random_1641843545607/work
mkl-service==2.4.0
numba==0.56.2
numpy @ file:///tmp/abs_653_j00fmm/croots/recipe/numpy_and_numpy_base_1659432701727/work
packaging==21.3
(생략)
...

 

당연하게도, 이러한 requirements.txt를 기반으로 `pip install -r requirements.txt`를 실행하면 아래와 같은 에러를 만나게 될것이다. (필자는 타사에서 requirements.txt로 배포/전달하여 곤란을 겪은 경험이 있다.)

정상적인 포맷이 아닌 requirements.txt를 사용하여 패키지 설치. 당연하게도 에러가 발생한다.

3. 문제 발생 원인 

문제 발생의 원인을 알아보자. (빈번하게 이런 상황이 발생하면, 많이 힘들어 진다.) 정확하지는 않아도 발생하는 케이스를 살펴봄으로써, 정답에 근접한 답을 도출할 수 있다.  우선 두가지 상황을 비교해 보자. 1) venv로 가상환경을 생성한 결과, 2) conda로 가상환경을 생성한 결과.

 

1. venv 가상환경

python -m venv .venv_test 
source .venv_test/bin/activate
pip freeze
  • 실행결과 : 아무것도 출력 되지 않는다. (목록 없음) --> 당연한다. 

그렇다면, 기본적인 패키지를 하나 (joblib) 설치해 보자. 

pip install joblib
  • 실행 결과 :
joblib==1.2.0

 

 

2. conda 가상환경

conda create -n test_env python=3.9
conda activate test_env
pip freeze
  • 실행 결과 :
certifi @ file:///croot/certifi_1671487769961/work/certifi
으응.....????

기본적인 패키지 (joblib) 를 하나 설치해 보자.

conda install joblib
  • 실행 결과:
joblib @ file:///croot/joblib_1666298844297/work
네 이놈......... 잡았다 !!!

확실하게 conda로 패키지를 설치하고, pip freeze의 결과는 그 포맷이 다름을 알 수 있다.  확실하게 하기 위해서, conda install 을 사용한 joblib를 삭제하고, pip install로 다시 설치해 보자. 

conda uninstall joblib
pip install joblib
pip freeze
  • 실행 결과 :
joblib==1.2.0

 

예상과 같이 `conda install`로 설치한 결과와, `pip install`로 설치하는 결과는 `pip freeze`로 표현한 결과의 포맷이 다르다는 것을 확인 할 수 있다. 즉, `pip freeze`를 통하여 패키지 목록을 추출할 때, conda와 pip 는 다른 포맷으로 목록을 출력해 준다는 사실을 확인했다. 어떻게 보면 너무나도 당연한 결과이다. 

그러나, 많은 개발자/연구자들이, python 가상환경을 생성/관리할 때, conda와 venv, 그리고 pip를 혼용해서 사용한다. 필자의 경우는 대부분 venv/pip를 사용하고 있지만, 경우에 따라서는 conda를 사용하기도 한다. 필자의 경우 conda를 사용하는 경우는, python version을 변경할 때 base에 버전을 추가 설치하기는 번거로울 때 이다. venv는 os에서 지정된 python을 그대로 사용하며, conda의 경우에는 python 버전을 자체로 추가 설치하기 때문에 개별 버전을 따로 지정할 수 있기 때문이다. 

각설하고, 결국 venv, conda, pip 는 각각 다른 패키지 인것이다. 이것들을 혼용하여 사용한다면 각각의 특징과 출력 결과를 인지하고 있어야 할것이다. 필자 주변에서 venv와 conda install 조합( ^^? )하는 특이한 케이스도 봤는데, 이런 경우 괴랄한 결과가 나오더라. 


추가적인 PIP 버그 (Ubuntu) : pkg_resources

전체적인 맥락과는 다른 이야기 이지만, Ubuntu에서 PIP에 버그가 존재한다. 이 때문에 아래와 같은 불필요한 패키지가 하나 추가 된다. (아래는 PIP freeze 결과중 해당 패키지 라인이다.)

pkg_resources==0.0.0

pkg_resources==0.0.0

버전을 보면 알 수 있듯이, 버전 `0.0.0`으로 무엇인가 이상하다. 이것은 Linux/Ubuntu에서 발생하는 PIP 버그로 알려져 있다. (PIP Github issue : https://github.com/pypa/pip/issues/4022)  이러한  이 때문에, `pip install -r requirements.txt`를 실행하면 에러가 발생한다. 해결 방안은 requirements.txt에서 해당 라인을 제거하면 된다. 매번 `pkg_resources==0.0.0`을 제거하기 번거롭다면, `grep -v`를 사용하여 requirements.txt를 생성하면 된다. 해당 명령 내용은 아래 "해결방안"에서 거론한다. 

 

4. 해결 방안 

대부분의 문제에서 원인이 밝혀지면, 해결방법은 그다지 어렵지 않다. 

사실은 conda가 되었든, pip 가 되었든, 다음과 같이 패키지 리스트와 버전이 출력이 가능하다. (이렇다는 이야기는 단순 포맷팅의 차이라 할 수 있겠다. ) 

  • pip list
  • conda list

pip list 실행 결과 (좌), conda list 실행 결과 (우)

`conda list`의 경우는 상단에 package 환경에 대한 위치와 가상환경 이름이 같이 출력되고 있다. 사실, 이러한 환경 정보를 활용하여 가상환경을 재현하는 방법이 별도로 존재한다. 아래는 conda를 이용하여 패키지 설치 목록을 생성하고, 이를 통하여 배포/재설치하는 명령이다. 

[설치 패키지 목록 생성]

conda env export > conda_requirements.txt

[conda 가상환경 생성 및 패키지 재설치]

conda env create -f conda_requirements.txt
conda_requirements.txt에는 가상환경에 대한 정보도 포함되어 있기 때문에, 가상환경 이름을 포함하여 동일한 환경을 재구축한다.

이러한 패키지를 관리하는 포맷이 다르기 때문에, pip 중심의 requirements.txt와는 거리가 있다. 따라서, 해결 방법은 이러한 포맷을 동일하게 맞추어 주면 된다. 

생각외로 해결 방법은 단순하다.  `pip freeze` 대신 `pip list`를 사용하고, 포맷은 freeze 형태로 지정해 주면 된다. 

pip list --format=freeze > requirement_pip_list.txt

위 명령어를 사용하면, conda install을 사용하였거나, pip install을 사용한것과 무관하게 동일한 포맷으로 패키지 목록을 추출할 수 있다. 


이제 이전 섹션에서 언급한 `pkg_resources=0.0.0`을 제거해 보자. 

pip list --format=freeze | grep -v pkg_resources > requirement_pip_list.txt
`grep -v "문자열"` 은 인버스 매칭이다. 해당 문자열/조건에 매칭되지 않는 라인만을 출력한다. 상세 내용은 `grep --help`를 참조하기 바란다. 

혹은 `pip freeze`에 문제가 없다면 바로 다음과 같이 표현 해도 될것이다. 

pip freeze | grep -v "pkg_resources" > requirements.txt

 

 

반응형

관련글 더보기

댓글 영역