어제까지 잘 돌던 스크립트가 오늘 아침 갑자기 멈춘다. 에러 메시지는 딱 한 줄, ModuleNotFoundError: No module named 'requests'. 분명 어제 pip install requests를 했는데 말이다. 그런데 옆자리 동료 노트북에서는 같은 코드가 아무 문제 없이 돌아간다. 이쯤 되면 코드가 아니라 환경을 의심해야 한다.

파이썬을 배울 때 문법은 친절하게 알려주는 자료가 많은데, 정작 실무에서 사람을 가장 자주 붙잡는 건 이 환경 문제다. 오늘은 파이썬 프로젝트를 시작할 때 제일 먼저 해야 할 일, 즉 가상환경(venv)의존성 관리(pip·requirements.txt)를 처음부터 끝까지 정리해 본다.

왜 프로젝트마다 환경을 따로 만들어야 할까

파이썬을 설치하면 시스템 어딘가에 패키지를 모아두는 폴더가 하나 생긴다. 아무 설정 없이 pip install을 하면 설치된 라이브러리가 전부 그 한 곳에 쌓인다. 문제는 여기서 시작된다.

가령 A 프로젝트는 데이터 분석용으로 오래된 라이브러리 버전에 맞춰 만들어졌고, B 프로젝트는 최신 버전 기능을 쓰고 있다고 하자. 두 프로젝트가 같은 폴더를 공유하면 한쪽을 최신으로 올리는 순간 다른 쪽이 깨진다. 흔히 말하는 의존성 지옥이다. 라이브러리 하나 올렸을 뿐인데 전혀 관계없어 보이던 스크립트가 죽는 이유가 이것이다.

파이썬에서 "내 컴퓨터에선 되는데요"의 8할은 코드가 아니라 환경 차이 때문이다.

리눅스 서버에서는 더 조심해야 한다. 배포판이 기본으로 쓰는 파이썬 패키지를 건드리면 시스템 도구까지 함께 망가질 수 있다. 최근 배포판이 시스템 파이썬에 pip install을 하려 하면 경고를 띄우고 막는 것도 같은 이유다. 프로젝트 폴더 안에 그 프로젝트만의 파이썬 환경을 따로 두는 것, 그게 가상환경이다.

venv, 명령 세 줄이면 끝난다

가상환경 도구는 여러 가지가 있지만, 파이썬 3.3부터는 venv가 표준으로 내장돼 있다. 추가 설치가 필요 없다는 게 가장 큰 장점이다.

# 프로젝트 폴더로 이동
cd ~/project/myapp

# .venv 라는 이름으로 가상환경 생성
python3 -m venv .venv

# 활성화 (리눅스/macOS)
source .venv/bin/activate

# 활성화 (윈도우 PowerShell)
.venv\Scripts\Activate.ps1

활성화되면 프롬프트 맨 앞에 (.venv)가 붙는다. 이 상태에서 설치하는 패키지는 전부 .venv 폴더 안에만 들어간다. 시스템 파이썬은 손끝 하나 건드리지 않는다. 빠져나올 때는 deactivate 한 단어면 된다.

폴더 이름은 관행상 .venv 또는 venv를 쓴다. 앞에 점을 붙이면 목록에서 잘 안 보여 깔끔하고, 대부분의 도구가 이 이름을 자동으로 인식해 준다. 그리고 반드시 기억할 것 하나 — 가상환경 폴더는 절대 Git에 올리지 않는다. .gitignore.venv/ 한 줄을 넣어두자. 수천 개 파일이 커밋에 딸려 들어가는 사고를 막아준다.

제대로 됐는지 확인하는 습관도 들이면 좋다.

which python     # .venv/bin/python 이 나와야 정상
python -V        # 파이썬 버전 확인
pip list         # 갓 만든 환경이면 pip 정도만 보인다

requirements.txt — 환경을 문서로 남기기

가상환경을 만들었다면 다음은 "이 프로젝트가 무엇을 필요로 하는가"를 파일로 남길 차례다. 이 역할을 하는 게 requirements.txt다.

# 현재 환경에 설치된 패키지를 파일로 저장
pip freeze > requirements.txt

# 다른 컴퓨터/서버에서 그대로 재현
pip install -r requirements.txt

pip freeze의 출력은 이런 모양이다.

requests==2.32.3
python-dateutil==2.9.0
urllib3==2.2.2

여기서 중요한 건 == 뒤의 정확한 버전 번호다. 버전 없이 requests라고만 적어두면, 오늘 설치할 때와 석 달 뒤 서버에서 설치할 때 서로 다른 버전이 들어올 수 있다. 그러면 처음 겪었던 "내 컴퓨터에선 되는데" 상황이 다시 시작된다. 로컬과 서버의 결과를 똑같이 맞추고 싶다면 버전을 고정하는 편이 안전하다.

다만 pip freeze는 내가 직접 설치한 패키지와 그 패키지가 끌고 온 하위 패키지를 구분하지 않고 전부 뱉는다. 규모가 커지면 목록이 수십 줄로 불어나 뭐가 진짜 필요한 건지 알기 어렵다. 그래서 실무에서는 이렇게 두 갈래로 관리하는 방식을 많이 쓴다.

파일내용용도
requirements.in내가 직접 쓰는 것만 (requests, pandas)사람이 읽고 고침
requirements.txt하위 의존성까지 버전 고정설치·배포용

두 번째 파일은 pip-tools 같은 도구로 자동 생성한다. 프로젝트가 커지기 시작하면 한 번쯤 도입을 고민해 볼 만하다.

서버 배포와 크론(cron)에서 자주 밟는 지뢰

로컬에서는 잘 되는데 서버 크론에만 올리면 안 되는 경우가 정말 흔하다. 이유는 단순하다. 크론은 로그인 셸이 아니라서 source .venv/bin/activate가 실행되지 않고, PATH도 최소한만 잡혀 있다.

해결책은 활성화를 포기하고 가상환경 안의 파이썬을 절대경로로 직접 호출하는 것이다.

# 나쁜 예 — 시스템 파이썬이 실행돼 ModuleNotFoundError
0 6 * * * python3 /home/deploy/myapp/batch.py

# 좋은 예 — 가상환경 파이썬을 직접 지정
0 6 * * * /home/deploy/myapp/.venv/bin/python /home/deploy/myapp/batch.py >> /var/log/myapp.log 2>&1

로그를 파일로 남기는 부분(>> ... 2>&1)도 같이 붙여두자. 크론은 조용히 실패하는 것이 기본값이라, 로그가 없으면 언제부터 안 돌았는지조차 알 수 없다.

배포 자동화 스크립트도 같은 원리로 짜면 깔끔하다.

cd /home/deploy/myapp
git pull
.venv/bin/pip install -r requirements.txt
sudo systemctl restart myapp

여기서도 pip 대신 .venv/bin/pip을 쓴 점을 눈여겨보자. 어떤 셸에서 실행되든 결과가 같아진다.

자주 만나는 에러와 대처법

ModuleNotFoundError가 계속 뜬다면 — 십중팔구 활성화를 잊었거나, 다른 터미널 창에서 실행한 경우다. which python으로 지금 어떤 파이썬이 잡혀 있는지부터 확인하자.

externally-managed-environment 경고가 뜬다면 — 시스템 파이썬을 보호하려는 최신 배포판의 정상 동작이다. 경고를 무시하고 강제로 설치하기보다 가상환경을 만드는 쪽이 훨씬 안전하다.

가상환경이 꼬여서 손 쓸 수 없을 때 — 고치려 애쓰지 말고 지우고 다시 만드는 게 빠르다. requirements.txt만 있으면 복구는 1분이면 끝난다.

rm -rf .venv
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

가상환경은 원래 언제든 버려도 되는 폴더다. 이 감각을 갖게 되면 환경 문제로 스트레스받을 일이 눈에 띄게 줄어든다.

정리하며

오늘 내용을 다시 짧게 묶어보자.

  • 프로젝트 폴더에서 python3 -m venv .venv로 환경을 만들고 source .venv/bin/activate로 켠다
  • .venv/.gitignore에 넣고, 대신 requirements.txt를 커밋한다
  • 버전은 ==로 고정해 로컬과 서버를 같게 맞춘다
  • 크론·배포 스크립트에서는 .venv/bin/python 절대경로를 쓴다

환경을 다시 만드는 데 1분이면 되는 프로젝트는, 어떤 에러가 나도 무너지지 않는다.

처음엔 명령어 몇 개 외우는 게 번거롭게 느껴질 수 있다. 하지만 새 프로젝트를 만들 때마다 venv 만들고 requirements.txt 남기는 습관이 몸에 붙으면, 몇 달 뒤 그 코드를 다시 열었을 때 스스로에게 고마워하게 된다. 오늘 작업하는 폴더에서 딱 세 줄만 쳐보시길. 시작은 그걸로 충분하다.