[가상환경] Python 가상 환경의 원리

지금까지 python 가상환경을 사용하면서 activate/deactivate만 할 줄 알았지 구조를 모르고 사용했습니다.

매번 급하게 사용하다보니 잘 안되면 가상 환경을 날리고 새로 만들었었는데요.

이번 기회에 가상 환경의 구조를 알아보기로 했습니다.

python 가상 환경 중 venv에 대해서만 찾아보았습니다.

 

venv의 동작 방식

파이썬 가상환경은 디렉터리 구조의 격리된 환경을 가집니다.

uv를 사용해 python 가상 환경을 만들어보겠습니다.

user@ip-10-0-27-109:~/test$ uv venv
Using CPython 3.12.3 interpreter at: /usr/bin/python3
Creating virtual environment at: .venv
Activate with: source .venv/bin/activate

# python -m venv venv 동일

.venv라는 가상환경이 만들어졌습니다.

 

user@ip-10-0-27-109:~/test$ ll
total 12
drwxrwxr-x  3 user user 4096 Jun 27 19:14 ./
drwxr-x--- 12 user user 4096 Jun 27 19:14 ../
drwxrwxr-x  4 user user 4096 Jun 27 19:14 .venv/
user@ip-10-0-27-109:~/test$ ll .venv/
total 28
drwxrwxr-x 4 user user 4096 Jun 27 19:14 ./
drwxrwxr-x 3 user user 4096 Jun 27 19:14 ../
-rw-rw-r-- 1 user user    1 Jun 27 19:14 .gitignore
-rw-rw-r-- 1 user user   43 Jun 27 19:14 CACHEDIR.TAG
drwxrwxr-x 2 user user 4096 Jun 27 19:14 bin/
drwxrwxr-x 3 user user 4096 Jun 27 19:14 lib/
lrwxrwxrwx 1 user user    3 Jun 27 19:14 lib64 -> lib/
-rw-rw-r-- 1 user user  125 Jun 27 19:14 pyvenv.cfg

실제로 .venv 디렉토리가 생성되었고, 내부에 가상 환경 구조가 보입니다. 저는 uv를 사용했기 때문에 기존 python을 사용해 생성하는 가상 환경과 디렉토리 구조가 조금 다르게 보일 수 있으나, 기본 디렉토리 구조는 같습니다.

 

실행 파일을 모아두는 bin 디렉토리를 확인해보겠습니다.

user@ip-10-0-27-109:~/test/.venv$ ll bin
total 48
drwxrwxr-x 2 user user 4096 Jun 27 19:14 ./
drwxrwxr-x 4 user user 4096 Jun 27 19:14 ../
-rw-rw-r-- 1 user user 4074 Jun 27 19:14 activate
-rw-rw-r-- 1 user user 2664 Jun 27 19:14 activate.bat
-rw-rw-r-- 1 user user 2612 Jun 27 19:14 activate.csh
-rw-rw-r-- 1 user user 4176 Jun 27 19:14 activate.fish
-rw-rw-r-- 1 user user 3861 Jun 27 19:14 activate.nu
-rw-rw-r-- 1 user user 2770 Jun 27 19:14 activate.ps1
-rw-rw-r-- 1 user user 2387 Jun 27 19:14 activate_this.py
-rw-rw-r-- 1 user user 1728 Jun 27 19:14 deactivate.bat
-rw-rw-r-- 1 user user 1215 Jun 27 19:14 pydoc.bat
lrwxrwxrwx 1 user user   16 Jun 27 19:14 python -> /usr/bin/python3*
lrwxrwxrwx 1 user user    6 Jun 27 19:14 python3 -> python*
lrwxrwxrwx 1 user user    6 Jun 27 19:14 python3.12 -> python*

가상 환경을 켜고 끄는 activate / deactivate 실행 파일이 보입니다.

여기서 python, python3, python3.12 파일들에 로컬(OS) 파이썬의 심볼릭 링크가 걸려있는 것을 확인할 수 있습니다.

가상 환경 acitvate 후 python 경로를 확인해보면 로컬에서 확인하는 python과 다르게 잡히는 이유입니다.

# 로컬 환경 python3 설치 경로
user@ip-10-0-27-109:~/test/.venv/bin$ which python3
/usr/bin/python3

# 로컬 환경에 python 명령 없음
user@ip-10-0-27-109:~/test/.venv/bin$ python
Command 'python' not found, did you mean:
  command 'python3' from deb python3
  command 'python' from deb python-is-python3
  
# 가상 환경 실행
user@ip-10-0-27-109:~/test/.venv/bin$ source ./activate

# 가상 환경에서 python3 경로 확인
(test) user@ip-10-0-27-109:~/test/.venv/bin$ which python3
/home/user/test/.venv/bin/python3

# 가상 환경에서 python 실행
(test) user@ip-10-0-27-109:~/test/.venv/bin$ python
Python 3.12.3 (main, Jun 18 2025, 17:59:45) [GCC 13.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> exit()

# 가상 환경에서 python 설치 경로 확인
(test) user@ip-10-0-27-109:~/test/.venv/bin$ which python
/home/user/test/.venv/bin/python

너무 신기하지 않나요. 로컬에서 python 명령을 따로 python3으로 링크를 걸어두지 않아서 python 명령은 아무것도 실행되지 않습니다.

저는 Ubuntu 24.04를 사용하고 있기 때문에 default가 python3 으로 되어 있고, 다른 버전을 사용 중이라면 python 명령으로 python2 버전이 실행될 수도 있습니다.

 

가상 환경 실행 후 python3 경로를 확인해보면 제가 생성한 가상 환경 내부의 python3로 잡힙니다.

python도 마찬가지로 가상 환경 내부의 /home/user/test/.venv/bin/python 파일로 잡히기 때문에 로컬 환경과는 다르게 python 명령이 실행됩니다.

python -> /usr/bin/python3* 이렇게 가상 환경 디렉토리 내부에서 python이 /usr/bin/python3*으로 심볼릭 링크가 걸려있기 때문입니다.

 

#/home/user/airflow/.venv/bin/activate
deactivate () {
    unset -f pydoc >/dev/null 2>&1 || true

    # reset old environment variables
    # ! [ -z ${VAR+_} ] returns true if VAR is declared at all
    if ! [ -z "${_OLD_VIRTUAL_PATH:+_}" ] ; then
        PATH="$_OLD_VIRTUAL_PATH"
        export PATH
        unset _OLD_VIRTUAL_PATH
    fi
    if ! [ -z "${_OLD_VIRTUAL_PYTHONHOME+_}" ] ; then
        PYTHONHOME="$_OLD_VIRTUAL_PYTHONHOME"
        export PYTHONHOME
        unset _OLD_VIRTUAL_PYTHONHOME
    fi

    # The hash command must be called to get it to forget past
    # commands. Without forgetting past commands the $PATH changes
    # we made may not be respected
    hash -r 2>/dev/null

    if ! [ -z "${_OLD_VIRTUAL_PS1+_}" ] ; then
        PS1="$_OLD_VIRTUAL_PS1"
        export PS1
        unset _OLD_VIRTUAL_PS1
    fi

    unset VIRTUAL_ENV
    unset VIRTUAL_ENV_PROMPT
    if [ ! "${1-}" = "nondestructive" ] ; then
    # Self destruct!
        unset -f deactivate
    fi
}

VIRTUAL_ENV='/home/user/airflow/.venv'
if ([ "$OSTYPE" = "cygwin" ] || [ "$OSTYPE" = "msys" ]) && $(command -v cygpath &> /dev/null) ; then
    VIRTUAL_ENV=$(cygpath -u "$VIRTUAL_ENV")
fi
export VIRTUAL_ENV

activate 파일을 살펴보면 deactivate 명령에 대한 설정이 있습니다. activate 명령에서 설정한 모든 설정들을 해제합니다.

activate 파일에서 VIRTUAL_ENV를 제가 설치한 venv로 설정하고 있습니다.

 

(airflow) user@ip-10-0-27-109:~/airflow/.venv/bin$ echo $VIRTUAL_ENV
/home/user/airflow/.venv
(airflow) user@ip-10-0-27-109:~/airflow/.venv/bin$ deactivate
user@ip-10-0-27-109:~/airflow/.venv/bin$ echo $VIRTUAL_ENV

가상환경에서 VIRTUAL_ENV의 값을 확인할 수 있습니다.

역시 로컬에서는 제가 직접 VIRTUAL_ENV의 값을 할당한 적이 없으므로 빈 값이 출력됩니다.

 

python 가상 환경의 격리 방식

그렇다면 python은 어떤 방식으로 가상 환경 디렉토리를 기본 디렉토리로 바라보게 될까요?

chroot 방식은 아니고 단순히 경로를 변경합니다.

chroot 방식은 운영체제 수준에서 루트 디렉토리를 변경하는 시스템 콜을 사용하지만, 파이썬 가상 환경은 OS 레벨까지 가지 않고 단순히 파이썬 런타임 레벨에서 패키지 경로만 변경합니다.

 

venv 가상 환경에서 lib 디렉토리가 가상 환경에서 패키지 설치 경로입니다.

user@ip-10-0-27-109:~/test/.venv$ ll
total 28
drwxrwxr-x 4 user user 4096 Jun 27 19:14 ./
drwxrwxr-x 3 user user 4096 Jun 27 19:14 ../
-rw-rw-r-- 1 user user    1 Jun 27 19:14 .gitignore
-rw-rw-r-- 1 user user   43 Jun 27 19:14 CACHEDIR.TAG
drwxrwxr-x 2 user user 4096 Jun 27 19:14 bin/
drwxrwxr-x 3 user user 4096 Jun 27 19:14 lib/
lrwxrwxrwx 1 user user    3 Jun 27 19:14 lib64 -> lib/
-rw-rw-r-- 1 user user  125 Jun 27 19:14 pyvenv.cfg

 

(airflow) user@ip-10-0-27-109:~/airflow/.venv/lib/python3.12/site-packages$ ll
total 2280
drwxrwxr-x 239 user user   12288 Jun 27 19:49 ./
drwxrwxr-x   3 user user    4096 Jun 27 19:47 ../
drwxrwxr-x   2 user user    4096 Jun 27 19:49 Deprecated-1.2.18.dist-info/
drwxrwxr-x   2 user user    4096 Jun 27 19:49 Flask-2.2.5.dist-info/
{...}
drwxrwxr-x  39 user user    4096 Jun 27 19:49 airflow/
{...}

airflow 를 설치했던 가상환경의 lib 디렉토리 내부를 살펴보면 airlfow 라이브러리를 확인할 수 있습니다.

 

가상 환경에서 파이썬 라이브러리를 사용하면 로컬에 설치된 패키지가 아니라 가상 환경의 lib 디렉토리 아래에 설치된 패키지를 가져와 실행시키게 되는 것입니다.

그래서 로컬 환경에서 백만번 패키지를 실행시켜봤자 실행이 되지 않는 것입니다.

 

가상 환경에서 패키지를 설치하다보면 가끔 apt (Ubuntu Package Manager)와 충돌하는데, 가상 환경을 제대로 활성화 한 후 패키지 설치 중인지 다시 한 번 확인해볼 필요가 있습니다. 로컬의 apt와 내가 필요한 가상 환경의 패키지 설치 경로가 충돌나면 안 되니까요.

 

user@ip-10-0-27-109:~/airflow/.venv/bin$ ll
total 148
drwxrwxr-x 2 user user 4096 Jun 27 20:07 ./
drwxrwxr-x 5 user user 4096 Jun 27 19:49 ../
-rw-rw-r-- 1 user user 4083 Jun 27 19:47 activate
-rwxrwxr-x 1 user user  356 Jun 27 19:49 activate-global-python-argcomplete*
-rw-rw-r-- 1 user user 2670 Jun 27 19:47 activate.bat
-rw-rw-r-- 1 user user 2621 Jun 27 19:47 activate.csh
-rw-rw-r-- 1 user user 4185 Jun 27 19:47 activate.fish
-rw-rw-r-- 1 user user 3870 Jun 27 19:47 activate.nu
-rw-rw-r-- 1 user user 2776 Jun 27 19:47 activate.ps1
-rw-rw-r-- 1 user user 2390 Jun 27 19:47 activate_this.py
-rwxrwxr-x 1 user user  318 Jun 27 19:49 airflow*
-rwxrwxr-x 1 user user  316 Jun 27 19:49 alembic*
-rwxrwxr-x 1 user user  315 Jun 27 19:49 cadwyn*

참고로, 패키지를 설치하면 bin 파일에 자동으로 실행 파일들도 설치됩니다. 설치 경로가 가상 환경 디렉토리(VIRTUAL_ENV)로 설정되어 있기 때문이죠 !!

 

컨테이너와의 차이

저는 처음에 파이썬 가상 환경이 컨테이너처럼 논리적으로 분리된 공간에서 실행되는 줄 알았는데요.

파이썬 가상 환경은 OS 커널이나 파일 시스템을 건드리지 않고 지금까지 본 것처럼 디렉토리만 분리시키는 구조입니다.

 

 

 

짠 이제 가상 환경에서 패키지 충돌 문제가 발생했을 때 디버깅 할 수 있게 되었습니다.