온프레미스 GPU 서버에서 Python 개발 환경 구축

pyenv, Poetry, Jupyter까지 sudo 없이 세팅하기

사내 온프레미스 GPU 서버에 접속한 뒤 pyenv로 Python을 설치하고, Poetry로 프로젝트 의존성을 관리하며, SSH 터널로 Jupyter에 접속하는 전체 워크플로를 다룬다.

Engineering
Infrastructure
저자

Kwangmin Kim

공개

2026년 03월 26일

1 전제 조건

온프레미스 GPU 서버에 SSH 접속이 가능한 상태를 전제한다. SSH 접속 원리와 설정은 SSH 개요와 작동 원리를 참고한다.

ssh kmkim@10.10.101.61 -p 3030

접속 후 가장 먼저 해야 할 일은 서버 환경을 파악하는 것이다.


2 서버 환경 파악

2.1 GPU 확인

nvidia-smi

출력에서 확인할 항목:

항목 확인 내용
GPU 모델 어떤 GPU가 몇 장 있는지
VRAM GPU당 메모리 용량
Memory-Usage 현재 사용 중인 메모리
GPU-Util GPU 사용률
Processes 어떤 프로세스가 GPU를 점유 중인지

2.2 GPU 점유 프로세스 확인

nvidia-smi의 Processes 섹션에 PID가 보이면, 해당 프로세스의 소유자를 확인한다.

# PID로 누가 무슨 프로세스를 돌리는지 확인
ps -p <PID> -o user,pid,cmd

예를 들어 root가 FastAPI 서빙 중이고 GPU 4장을 각 6GB씩 점유하고 있더라도, GPU당 VRAM이 48GB라면 나머지 42GB는 사용 가능하다.

2.3 특정 GPU 지정하여 사용

다른 사용자의 프로세스와 충돌을 피하려면 특정 GPU를 지정한다.

# 0번 GPU만 사용
CUDA_VISIBLE_DEVICES=0 python train.py

# 0, 1번 GPU 사용
CUDA_VISIBLE_DEVICES=0,1 python train.py

GPU를 사용하기 전에 담당자에게 사용 가능한 GPU 번호를 확인하는 것이 좋다. 서빙 중인 서비스가 갑자기 메모리를 더 사용할 수 있기 때문이다.

2.4 기타 환경 확인

python3 --version   # 시스템 Python 버전
which conda         # conda 설치 여부
df -h               # 디스크 용량

3 pyenv로 Python 설치

온프레미스 서버에서는 보통 sudo 권한이 없다. 시스템 Python(예: 3.8)은 오래된 버전일 수 있고, 직접 업그레이드할 수도 없다. pyenv는 사용자 홈 디렉토리(~/.pyenv/)에 Python을 설치하므로 sudo가 필요 없다.

3.1 설치 및 Python 빌드

# pyenv 설치
curl https://pyenv.run | bash

# 셸 설정에 추가 (~/.bashrc 또는 ~/.zshrc)
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"

# Python 3.11.9 설치
pyenv install 3.11.9
pyenv global 3.11.9

3.2 시스템 Python과 pyenv Python의 관계

pyenv는 시스템 Python을 건드리지 않는다. PATH 앞에 shim을 끼워넣어 명령을 가로채는 방식이다.

python3 입력
   |
   v
PATH 순서대로 탐색
   |
   v
~/.pyenv/shims/python3 발견 (pyenv가 가로챔)
   |
   v
pyenv가 현재 설정된 버전(3.11.9) 실행
   --> ~/.pyenv/versions/3.11.9/bin/python3

pyenv가 없다면 /usr/bin/python3(시스템 Python)이 실행된다. pyenv는 시스템 바이너리를 수정하지 않고, 앞에서 가로채서 사용자 버전을 대신 실행해주는 구조이다.

3.3 shim이란

~/.pyenv/shims/ 안의 python3, pip, poetry 등은 실제 바이너리가 아니라 중간 스크립트(shim)이다. 이 스크립트가 pyenv에 “현재 어떤 버전을 쓸지” 물어보고, 해당 버전의 실제 바이너리를 실행한다.

# shim 내용 확인
cat ~/.pyenv/shims/python3
# → pyenv exec "$program" "$@" 형태의 bash 스크립트

# 실제 실행되는 Python 경로 확인
pyenv which python3
# → /home/user/.pyenv/versions/3.11.9/bin/python3

이 구조 덕분에 pip install도 시스템이 아닌 ~/.pyenv/versions/3.11.9/lib/에 패키지를 설치한다.

pip install poetry
   |
   v
~/.pyenv/shims/pip → pyenv가 가로챔
   |
   v
~/.pyenv/versions/3.11.9/bin/pip 실행
   |
   v
~/.pyenv/versions/3.11.9/lib/  에 설치됨 (시스템 아님)

4 시스템 라이브러리 누락 트러블슈팅

pyenv는 Python을 소스에서 컴파일한다. 이때 시스템에 특정 라이브러리가 없으면 해당 모듈이 빠진 채로 빌드된다. 대표적으로 libsqlite3-dev가 없으면 sqlite3 모듈이 누락된다.

4.1 증상

import sqlite3
# ModuleNotFoundError: No module named '_sqlite3'

4.2 원인

pyenv가 Python을 컴파일할 때 시스템의 libsqlite3-dev를 찾지 못해 _sqlite3 확장 모듈을 빌드하지 않은 것이다.

4.3 해결

시스템 라이브러리는 sudo 권한이 필요하므로 인프라 담당자에게 요청한다.

# 담당자가 실행 (sudo 필요)
sudo apt install libsqlite3-dev libffi-dev libbz2-dev libreadline-dev libssl-dev

라이브러리 설치 후 Python을 재빌드해야 한다. 이미 설치된 pyenv Python에는 반영되지 않는다.

# 기존 버전 삭제 후 재설치
pyenv uninstall 3.11.9
pyenv install 3.11.9

# 모듈 정상 포함 확인
~/.pyenv/versions/3.11.9/bin/python -c "import sqlite3; print('OK')"

OK가 출력되면 정상이다. 이후 Poetry 가상환경도 재생성해야 한다.


5 Poetry로 프로젝트 의존성 관리

Python이 준비되면 Poetry로 프로젝트 의존성을 설치한다. Poetry 자체의 상세한 사용법은 Poetry 시리즈를 참고한다.

# Poetry 설치 (pyenv Python에 설치됨)
pip install poetry

# 프로젝트 디렉토리로 이동
cd ~/my_project

# 의존성 설치
poetry install

5.1 가상환경 활성화

Poetry 2.x에서는 poetry shell이 deprecated되었다. 대신 poetry env activate를 사용한다.

# 가상환경 활성화 명령 출력 후 실행
eval $(poetry env activate)

# 또는 매번 poetry run을 붙여 실행
poetry run python train.py
poetry run jupyter notebook --no-browser --port 8888

poetry run은 가상환경 안에서 명령을 실행한다. 활성화 없이도 사용 가능하지만, 매번 붙여야 하는 번거로움이 있다.


6 SSH 터널로 Jupyter 원격 접속

서버에서 Jupyter를 실행하면 브라우저가 없어 직접 접근할 수 없다. SSH 포트 포워딩으로 로컬 PC의 브라우저에서 접속한다. 포트 포워딩의 원리는 SSH 포트 포워딩과 터널링을 참고한다.

6.1 서버에서 Jupyter 실행

poetry run jupyter notebook --no-browser --port 8888

출력에서 토큰이 포함된 URL을 확인한다.

http://localhost:8888/tree?token=0d92c500833fd9...

6.2 로컬 PC에서 SSH 터널 열기

새 터미널을 열고 다음을 실행한다.

ssh -L 8888:localhost:8888 kmkim@10.10.101.61 -p 3030

-L 8888:localhost:8888의 의미:

  • 로컬 PC의 8888 포트로 들어오는 요청을
  • SSH 암호화 통로를 통해
  • 서버의 8888 포트로 전달한다
[로컬 PC]                          [GPU 서버]
브라우저                            Jupyter (포트 8888)
   |                                     ^
   |  http://localhost:8888              |
   v                                     |
로컬 포트 8888 <---- SSH 터널 ----> 서버 포트 8888

6.3 브라우저에서 접속

로컬 브라우저에서 http://localhost:8888에 접속하면 로그인 화면이 나타난다. 서버 터미널 출력의 토큰을 붙여넣는다.

매번 토큰을 복사하기 번거로우면 비밀번호를 설정한다.

poetry run jupyter notebook password

이후에는 설정한 비밀번호로 로그인할 수 있다.

6.4 포트 충돌 시

8888 포트를 다른 사용자가 이미 쓰고 있다면 다른 번호를 사용한다. 포트는 논리적인 번호(0~65535)이므로 서버와 터널이 같은 번호만 사용하면 된다.

# 서버
poetry run jupyter notebook --no-browser --port 9999

# 로컬 터널
ssh -L 9999:localhost:9999 kmkim@10.10.101.61 -p 3030

7 전체 워크플로 요약

[1] SSH 접속
    ssh user@server -p 3030

[2] 환경 파악
    nvidia-smi          # GPU 확인
    ps -p <PID> -o user,pid,cmd  # 점유 프로세스 확인

[3] Python 설치 (최초 1회)
    pyenv install 3.11.9
    pyenv global 3.11.9
    pip install poetry

[4] 프로젝트 세팅
    cd ~/my_project
    poetry install

[5] Jupyter 실행
    poetry run jupyter notebook --no-browser --port 8888

[6] 로컬에서 SSH 터널 열기
    ssh -L 8888:localhost:8888 user@server -p 3030

[7] 브라우저 접속
    http://localhost:8888

[8] GPU 지정하여 학습
    CUDA_VISIBLE_DEVICES=0 poetry run python train.py

Subscribe

Enjoy this blog? Get notified of new posts by email: