Python 실전 개발 생태계

Java 에는 maven 이 있고, Javascript / node 에는 npm 이 있고, python 에는 pip 가 있는건가? setuptools 가 있는건가? conda??? 뭐지??

Java 는 배포할때 maven 뷜드 하면 되고, Javascript 는 그냥 브라우저에서 돌고, python 은 git pull 하면 되는건가?? 뭐지?

Java 는 dependency 작업을 maven / gradle 로 하면 되고, Javascript 는 npm 하고 package.json 하면 되는데, python 은 어따가 뭘 명시해서 하지?

Java 는 jdk 깔고 개발하면 되고, Javascript 는 ES6를 할지 Typescript 해서 webpack 을 하든지 하고 python 은 2는 곧 죽는대서 3 쓰면 되는건데 python 3.6.3 깔았는데 3.7.3 에서 왜 에러가 나지??? 시스템 python은 2.x 인데 뭐지??

대충 내가 아는 다른 언어 개발 생태계와 비교해서 python 을 처음 개발할때부터 느꼈던 코딩 외적인 생태계의 좌절감을 표현해봤다.

(짧은 레퍼런스의 한계..)

그동안 겪었던 python 개발 생태계를 정리해서 개인적인 의견으로 best practice 겸 해서 써봅니다. 더 나은 방법이 있으면 공유해 주세요ㅎㅎ

저는 pc로 맥을 쓰고 있고, 서버로 linux 를 사용하기 땜에 windows 사용자와는 안맞는 말이 있을수 있습니다.

pyenv - python 설치 버전관리, dependency 격리

자신의 pc 에서 터미널을 켜고 python 을 치면 어떤 버전이 동작하는지 확신 할수 있는가?
현재 프로젝트가 어떤 python 버전으로 돌려야 하며, dependency 설치한게 어디에 설치되어있고, 설치된 패키지 중, 이 프로젝트에 최소한으로 필요한 dependency 가 무엇인지 알 수 있는가? (다 알면.. 패쓰..)

mac 에서는 기본 python 이 2.x 이다. 우분투는 최근? 3.x 로 넘어왔고, 어느 리눅스 배포판은 python 자체가 없다.

brew 로 python3 를 설치하고, virtualenv 로 프로젝트별 dependency 를 격리해서 개발하고 있다면, 더이상 할게 없어 보인다.
하지만 개인적으로는 pyenv 를 통해 이걸 쉽게 하고 있다.

https://github.com/pyenv/pyenv

pyenv 를 통해서 python 을 각 버전별로 설치 가능하고, (anaconda 도 python 버전중에 하나로 취급해버림..)
pyenv-virtualenv 를 통해서 손쉽게 virtualenv 를 사용할수 있다. (no more venv/bin/activate & deactivate)

혹 virtualenv 를 모르시는 분은 먼저 virtualenv 가 무언지 검색해보고 옵시다. ㅎㅎ

설치

참고1 : https://github.com/pyenv/pyenv#installation

참고2 : https://github.com/pyenv/pyenv-installer

아래 커맨드로 설치하자. brew 로 인스톨할경우 잘 안된다는 사람들이 종종 있으니 확실한 curl 로 설치하자.

curl https://pyenv.run | bash

설치하면 아래 내용을 ~/.bash_profile (또는 ~/.zshrc) 에 추가하라고 나온다.

export PATH="$HOME/.pyenv/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"

shell 이 시작될때 pyenv 를 초기화 하는 코드이다.

pyenv 사용법

특정 python 버전 설치

일단 뭘 설치할 수 있는지 본다. (설치 가능한 list)

pyenv install -l

쭉 나오는 거중에 3.7.2 를 골라서 설치한다고 한다면

pyenv install 3.7.2

(여기서 가끔 sqlite library 가 어쩌고 하면서 설치가 실패하는 경우가 있다. stackoverflow의 힘을 빌려보자)

설치 후 내 pc 에 어떤 python 들이 존재하는지 보자

pyenv versions

system 이라고 나온것이 system 에 설치된 기본 python이다.

그 외에 pyenv 로 설치한 버전이 나오며, brew 등 다른 방법으로 설치한 python 은 나오지 않으니 주의 (pyenv 를 쓰다보면 그놈들을 없애고 싶어진다)

default python 지정

터미널을 켜고 python 을 실행했을때 기본으로 실행할 python version 을 지정하자

pyenv global {VERSION}

shell 을 켜면 기본으로 사용하게 될 python 을 지정한다고 보면 된다.

특정 폴더 아래에서 특정 version 의 python 을 지정

터미널에서 어떤 폴더 (프로젝트 폴더)로 진입하면, 거기부터는 자동으로 특정 version 이 동작하게 해보자

pyenv local {VERSION}

recursive 하게 그 하위 폴더까지도 버전이 적용된다.

virtualenv 생성 && 사용

virtualenv 는 python 의 실행 환경을 격리시켜서 pc 에 설치된 전체 python package 와 격리된 환경에서 python package 관리를 하게 해준다.

virtualenv 와 pip 를 통해 dependency 관리를 쉽게 할수 있다.

기존의 virtualenv 는 해당 프로젝트의 폴더 아래에 venv 같은 이름으로 폴더를 생성하고, 수동으로 activate && deactivate 하면서 썼었다.
하지만 pyenv 로 virtualenv 를 생성하면 ~/.pyenv/versions/.. 해당 폴더는 아래 자리를 잡는다. 그리고 activate && deactivate 는 위에서 설명한 pyenv local 로 자동으로 설정하면 된다.

일단 한번 생성해보자. 3.7.2 기반의 my-project 라는 virtualenv 이다.

pyenv virtualenv 3.7.2 my-project
pyenv virtualenv {VERSION} {ENV_NAME}

그리고 pyenv versions 를 해보면, 3.7.2/envs/my-projectmy-project 항목이 보인다. (두개가 같은거)

그리고 어떤 프로젝트의 폴더를 들어가면 방금 생성한 my-project virtualenv 가 자동으로 activate 되게 하자

pyenv local my-project

다른 폴더로 이동하면 자동으로 deactivate 된다.

그리고 열심히 개발을 하자…

pyenv 의 실체

이 놀라운 일을 해주는 pyenv 는 사실 ~/.pyenv 폴더 안에 모든 실행 파일과 참조 library 를 저장하고, 상황에 맞게 심볼릭 링크 / 참조 path 를 조작 해주는 유틸이라고 생각하면 된다.

자세한건 아래 링크를 읽어보자.

https://github.com/pyenv/pyenv#how-it-works

이 정도면 pyenv 에서 필수적인 부분은 다 해봤다. pyenv 의 나머지 기능은 알아서 탐구해 보자.. ㅎㅎ

dependency 설치, 참조 path 관리

pip 기본

virtualenv 를 처음 구성하면, 3rd party 모듈이 하나도 설치되어있지 않은 청정 구역이 나타난다. (pip 를 실행하기 위한 최소한의 모듈만 설치된 상태이다.)

개발을 진행하면서 pip install 로 여러 모듈을 설치하고 아래와 같이 프로젝트에 설치한 모듈의 리스트를 구하자

# requests 하나 설치해 봅니다.
pip install requests

# requirements.txt 로 설치된 3rd party 모듈을 내보낸다
pip freeze > requirements.txt

requirements.txt 를 열어보면, 모듈명과 그 버전이 나열되어있다.

나중에 이걸 어디가서 똑같이 설치하려명 이렇게 하면 된다.

pip install -r requirements.txt

근데 pip 로 뭘 설치하면 모듈 코드는 어디에 저장이 되는거고, python 은 어떻게 그 참조 위치를 가져오는 건가?

python 참조관계 기본

~/.pyenv 하위 폴더를 까보면 거의 모든걸 알수 있다.

방금 설치한 3.7.2 python 과 my-project virtualenv 를 기준으로 돌아다녀보자

일단 python 3.7.2 상태에서 설치한 모듈은 여기에 모인다.

~/.pyenv/versions/3.7.2/lib/python3.7/site-packages

그리고 my-project virtualenv 상태에서 설치한 모듈은 여기에 모인다.

~/.pyenv/versions/3.7.2/envs/my-project/lib/python3.7/site-packages

그리고 python runtime 에서 참조하는 path 는 이렇게 알 수 있다.

python
>>> import sys
>>> sys.path

그러면 사용중인 python 에 따라 위에서 언급한 site-packages 폴더를 참조하는것을 알 수 있다.

참조 path 를 추가하거나 관리하는 것은 각자 탐구해보자.

내가 발견한 방법은 아래와 같다.

  • PYTHONPATH 라는 환경변수에 지정하거나 (여러 path 간 구분자는 PATH 와 동일하게 : 사용),
  • sys.path 에다가 추가 하거나 (sys.path.append('path/to/lib'))
  • site-packages/xxx.pth 파일에 추가하거나.. (참고) https://docs.python.org/3.7/library/site.html

상황에 맞춰 쓰면 될거 같다.

python 패키징, 배포

패키징이라 함은, 개발한 프로젝트를 어떠한 방식으로 묶어서 재사용이 가능하게 하는 일이다.

라이브러리 모듈을 개발했다면, pip 같은걸로 받아서 쓸수있게 패키징하여 배포하면 되고,

웹서버를 개발했다면, 적절히 패키징해서 배포 / 실행할수있게 해야겠다.

물론 개발 상황은 각자 다르고, 각자의 노하우가 있음을 아는 상태에서, 이런 방법도 있다더라 하는 정도로 받아들일 내용을 적어본다.

pip 패키징 (pypi)

오리지날 공식 가이드는 이걸 참조한다.

https://packaging.python.org/

모듈을 개발했고, 이걸 pip 로 다운받아 쓸수 있게 하려고 한다면, 이 가이드를 따르면 되겠다.

pypi 는 pip 의 서버라고 생각하면 되겠다.

가이드를 따라가다보면, README, LICENSE 이런 내용도 나오는데, 그건 그 문서에서 보도록 하고, 구체적인 할일들만 쫓아가 보자.

모듈 개발은 완료했다고 치고..

  1. https://pypi.org/ 사이트에 가입한다.
  2. setup.py 파일을 작성한다
    • 여기에 모듈 이름이라던지, 이 모듈을 사용하기 위한 dependency 라던지 하는 내용을 기술하게 되어있다.
    • 이건 setuptools 라는 배포 유틸에서 쓰게 되는데, 배포 유틸들에 대해 자세히 알고싶으면 이걸 누르자 : https://packaging.python.org/key_projects/
  3. 패키징을 위해 setuptools, wheel, twine 을 설치한다. 각자가 뭔지 알고 싶으면 위 링크에 같이 나와 있다.
pip install --upgrade setuptools, wheel, twine
  1. 패키징 실시
python setup.py sdist bdist_wheel
  1. 그러면 dist/ 폴더 아래에 파일이 생성된다.
  2. 이걸 pypi 서버에 배포한다
twine upload dist/*
  1. 아이디 패스워드를 치고 업로드가 된다. pip install xx 로 받을수 있다!!

가이드에서는 테스트를 위해 twine 으로 업로드할때 test url 을 먼저 써보는걸 장려한다.

twine upload --repository-url https://test.pypi.org/legacy/ dist/*

한번 해보면 나중엔 아마 실서버에만 올리게 될듯..

Docker 패키징

python 으로 앱을 만들었다면 Docker 로도 한번 패키징 해보자.

Docker 로 패키징을 하면 python 버전관리 문제로부터도 안전해지고, 뭔가 이게 제대로된 프로덕트가 된듯한 기분도 얻을수 있다!

base image 고르기

특별한 사유가 없다면 python 공식 docker image 중에서 base 를 고르자

https://hub.docker.com/_/python

마치 pyenv 에서 설치가능한 버전리스트를 받은것마냥 각종 버전 + linux 배포판 들이 모여있다.

예전같았다면 최대한 lite 하다는 alpine 을 추천했겠지만,, docker 를 쓰다보니 용량이 쪼금 더 많더라도 debian 을 고르게 되었다.
alpine 은 가끔가다 호환성 이슈가 발생해서..

일단 뭐 위에서 pyenv 하면서 작성한 3.7.2 my-project 를 돌리기 위해서 같은 버전 + debian 인 3.7-slim-buster 를 사용해본다. (patch 버전따위 무시한다 )

일단 돌려보기

dockerfile 로 이미지를 빌드하기 전에, 간단하게 base image 상태로 프로젝트를 돌려볼 수 있다.

현재 프로젝트 폴더와, virtualenv 에서 설치한 pip 모듈이 포함된 폴더 정도 mount 해서 돌려보자.

돌려야되는 파일이 main.py 라고 친다면..

# main.py
import requests

print('hello')
print(requests.get('http://google.com').text)

요렇게 돌린다.

docker run -v $(pwd):/app \
-v $PYENV_VIRTUAL_ENV/lib/python3.7/site-packages:/libs \
-e PYTHONPATH=/libs \
python:3.7-slim-buster \
python /app/main.py
  • 현재 작성한 프로그램은 /app 에 마운트 했다
  • pip 로 설치한 모듈은 /libs 에 마운트하고 PYTHONPATH 환경변수로 참조 경로를 잡아줬다

python 코드에 다른거 추가하다가 c 로 빌드된 모듈이 있다면 위 방법으로 돌지 않는다. (pc 는 맥이고 docker 는 리눅스 이기 때문에..)

개발 초기에 docker 테스트 등에만 잠깐 사용해볼만한거..

바로 dockerfile 작성으로 넘어가 보자.

dockerfile 작성

도커라면 역시 dockerfile 로 이미지 만들어 돌려야 제맛이다.

dockerfile 을 돌리기 전에 pip freeze 를 사용해서 requirements.txt 를 생성해두자.

pip freeze > requirements.txt

그리고 dockerfile 작성 - 완전 미니멀이다

FROM python:3.7-slim-buster

COPY . /app
RUN pip install -r /app/requirements.txt

ENTRYPOINT python /app/main.py

빌드하고 돌려보자. 이건 돌아야 한다.

docker build . --tag test
docker run test

그리고 이 이미지를 릴리즈 하고, 돌리면 패키징이 완벽..하게 된거다.

하지만 이제부터 시작하게 되는 일이 아래와 같이 벌어진다.

  1. docker 를 non-root 유저로 실행하게 하기
  2. 3rd party 모듈에서 필요로 하는 각종 c library 설치 (리눅스 배포판에 따라 yum / apt 등등으로 설치)
  3. docker volume / environment variable 로 각종 변동사항 제어하기
  4. 빌드 / 배포 / container 제어 등등 devops

해당 부분은 python 이슈라기 보다는 docker 의 영역일수 있으니, 열심히 사용하면서 내공을 단련해보자.

마무리

개인적으로 java 를 사용하다 python 을 처음 사용하게 되면서 그 편안함에 박수를 치다가

중구남방 관리안되는 환경 + 버전별로 이어지는 충돌에 좌절을 겪고,

그래도 편하니까 계속 쓰다보니 python 개발 생태계에 대해 조금 이해가 생겼다고 판단되어 글을 써봤습니다.

python 매우 편하니까 많이 씁시다.