티스토리 뷰

파이썬으로 개발한 소프트웨어를 'PyInstaller'를 통해 실행가능한 바이너리(Executable Binary)로 만들 수 있다. 파이썬 코드를 실행하는데 필요한 다양한 의존성 패키지들을 한번에 묶어서 배포하고 싶은 경우와 보안관련 코드가 있을 때, 코드를 열어보기 힘들게 만들고 싶은 경우 PyInstaller의 사용을 고려해볼 수 있다.

자세한 내용과 매뉴얼 페이지는 PyInstaller 홈페이지에서 확인할 수 있다. (링크 : PyInstaller 홈페이지)

1. PyInstaller 설치와 사용법

PyInstaller를 pip를 통해서 쉽게 설치할 수 있다.

pip install pyinstaller

설치가 완료되면 바이너리로 만들고 싶은 파이썬 스크립트를 pyinstaller의 인자로 넘겨주면 된다. 예를 들어 'test.py' 파일을 바이너리로 만들고 싶다면,

pyinstaller test.py

 

라고 입력하면된다.

PyInstaller는 기본적으로 디렉토리 형태로 바이너리를 만들어준다. 즉, 파이썬 스크립트를 실행가능한 바이너리로 만들고 필요한 의존성 패키지들을 하나의 디렉토리로 묶어서 배포할 수 있게 해준다.

만약 하나의 디렉토리가 아닌 하나의 파일로 묶어서 배포하고 싶으면 '--onefile' 옵션을 명시하면된다.

pyinstaller --onefile --windowed test.py

--onefile 옵션을 줘서 생성된 결과 파일에는 필요한 의존성 라이브러리들이 모두 포함(Self-contained)되어 있다.

PyInstaller를 이용하여 배포할 바이너리를 만들 때 주의해야하는 점은 실행환경에 따라 바이너리를 모두 만들어줘야 한다는 점이다. 다시말하면, 실행될 운영체제와 파이썬 버전(파이썬2 혹은 파이썬 3), 시스템 아키텍처(32비트, 64비트) 조합에 따라 모두 빌드를 해줘야 한다. 윈도우에서 만든 바이너리는 리눅스에서 정상동작하지 않을 수 있다.

2. PyInstaller 바이너리 생성 과정

PyInstaller가 파이썬 스크립트를 실행가능한 바이너리로 만들어주는 과정을 살짝 들여다보자.

PyInstaller는 입력받은 파이썬 스크립트를 읽어서 'import' 구문을 모두 읽어 들인다. 바이너리로 만들 파이썬 스크립트가 사용하고 있는 의존성 패키지들과 다른 스크립트들을 재귀적(Recursive)으로 따라가면서 필요한 리스트를 조사한다. 이 때, 런타임에 결정되는 일부 패키지들은 포함되지 않을 수 있다. 예를 들어 __import__() 구문을 이용하여 변수 값에 따른 동적 로딩을 할 때, imp.find_module(), sys.path 등을 이용할 때는 파이썬 스크립트를 정적으로 분석하여 의존성 라이브러리를 찾기 힘들다.

이런 경우 PyInstaller 실행시 import에 대한 부가적인 정보를 넣어주면된다. PyInstaller를 실행하면 같은 디렉토리에 test.spec 파일이 생성되는데 이 파일에 정적 분석으로 알 수 없는 정보들을 넣어주면 된다. 예를 들어 실행파일을 만들 때 같이 배포되어야하는 README 파일이나 런터임 정보들을 이 Spec 파일에 적어주면 PyInstaller가 바이너리 생성시 참조한다.

바이너리가 실행될 서버에 기본적으로 설치되어 있는 패키지들은 실행파일에 포함되지 않을 수 있다. 이런 패키지들은 '/lib' 혹은 '/usr/lib' 등에 위치시키면 된다.

2.1 하나의 디렉토리로 생성

PyInstaller는 기본적으로 실행가능한 바이너리를 하나의 디렉토리로 모아준다. 그런 다음 디렉토리를 tar.gz 같은 포맷으로 묶어서 배포를 하면되고, 최종 사용자는 배포된 tar.gz 파일의 압축을 풀어서 디렉토리에 존재하는 바이너리를 실행하면 된다. 디렉토리 내에 있는 실행 파일은 파이썬 스크립트 파일이름과 동일한 이름으로 생성된다.

PyInstaller가 생성하는 디렉토리의 파일 내용들을 직접 열어 볼 수 있기 때문에 만들어진 바이너리에 문제가 생겼을 경우 디버깅하기가 수월하다. 대부분 특정 의존성 패키지가 제대로 포함되지 않은 경우가 많은데 디렉토리를 직접 열어보면 어떤 패키지가 포함되었고, 포함되지 않은 패키지는 어떤 것인지 바로 알아볼 수 있다.

또, 같은 배포 파일에서 파이썬 스크립트만 변경된 경우 기존에 받았던 의존성 패키지들은 그대로두고 실행 파일만 배포하면 되기 때문에 배포가 더 빠르다는 장점도 있다.

하지만 사용자에게 배포된 디렉토리 내부에 너무 많은 파일이 포함되어 있어 복잡하다는 단점도 있다. 사용자가 어떤 파일을 실행해야하는지 헷갈리는 경우도 있고, 실수로 의존성 파일 일부를 지워 정상적으로 동작하지 않는 경우도 생길 수 있다.

2.2 디렉토리 모드 동작 방식

PyInstaller로 만들어진 실행 파일을 실행시키면 실행 환경을 만드는 일련의 동작들이 수행된다.

우선 부트로더라는 프로그램이 실행된다. 부트로더 프로그램은 임시 파이썬 실행 환경을 만들고 파이썬 인터프리터가 실행되어 모든 import 된 모듈과 라이브러리를 디렉토리에서 찾는다. 부트로더는 파이썬 인터프리터의 복제본을 시작하고, 파이썬 스크립트를 실행한다. 파이썬 스크립트를 실행하는데 필요한 모든 의존성 패키지들은 같은 디렉토리에 포함되어 있거나 '/lib' 등의 경로에 존재하므로 문제는 없다.

2.3 One Binary 모드

파이썬 스크립트의 내용을 하나의 바이너리 파일로 만들어 배포할 수도 있다. 하나의 파일로 생성하여 배포하면 사용자는 내부의 복잡한 파일들이 뭔지 알 필요가 없고, 배포된 실행 파일만 사용하면 된다는 장점이 있다.

하지만 결국 내부에는 디렉토리로 압축되어 있기 때문에 실행하기위해서 압축을 해제해야한다. 따라서 바이너리가 실행되는 시간이 조금 더 길고, 디스크 공간을 좀 더 사용할 수 있다는 단점이 있다.

일반적으로는 디렉터리 기반으로 바이너리를 생성하여 테스트를 진행한 다음, 문제가 없는 경우 One Binary 모드로 배포하는 것을 추천한다.

2.4 One Binary 모드 동작방식

위에서  잠깐 얘기했지만 One Binary 모드 동작방식은 압축을 푸는 과정이 필요하다. 바이너리가 실행되는 운영체제의 임시 디렉토리 경로에 실행할 바이너리의 압축을 풀어놓는다. 이 때 압축을 풀 디렉토리는 _MEIxxxxx 같은 이름으로 생성된다.

압축해제가 끝나고 나면 디렉토리 모드의 동작 방식과 동일한 방식으로 실행된다. 부트로더가 실행되고 인터프리터가 실행되고 패키지들이 로드된 다음 파이썬 코드가 수행된다.

부트로더가 정상종료되면 실행시 만들었던 임시폴더와 파일들은 운영체제에서 삭제된다. 부트로더가 실행할 때 만드는 _MEIxxxxx 같은 형식의 이름은 유니크함이 보장되기 때문에 같은 바이너리를 여러번 실행시켜도 문제없이 동작한다. 하지만 여러번 바이너리를 실행하면 실행할 때마다 바이너리의 압축을 풀고 실행하기 때문에 디스크 용량을 낭비할 수 있다.

주의할 점은 바이너리가 정상적으로 종료되지 않은 경우 만등러진 임시디렉토리가 지워지지 않을 수 있다는 점이다. 따라서 자주 실행되는 프로그램이 자주 죽으면 디스크 용량을 영구적으로 낭비하게 될 수 있다. 주기적으로 임시 디렉토리를 확인하여 정리해주는 작업이 필요할 수도 있다. (PyInstaller의 '--runtime-tmpdir' 옵션을 이용하면 압축이 풀리는 디렉토리를 변경할 수 있다)

2.5 코드 숨기기

PyInstaller는 파이썬 스크립트를 .pyc 파일 같은 컴파일된 파이썬 스크립트로 변환한 다음 번들링한다. 이 방식은 기본적으로 디컴파일 가능하며 결국 원본 파이썬 스크립트에서 사용한 로직을 확인할 수 있게 된다.

만약 소스코드를 좀 더 철저하게 숨기기 원한다면, 원본 소스를 Cython으로 컴파일해야한다. Cython을 이용하여 로직을 숨기고 싶은 파이썬 모듈을 C파일로 바꾸고 C 파일을 컴파일하여 기계어로 바꾸는 과정을 거치면 어느정도 로직을 숨길 수 있다. 이후 PyInstaller는 Cython이 생성한 C 모듈을 번들링하여 정상적으로 동작하는 바이너리를 만들게 된다.

추가로 파이썬 바이트 코드는 커맨드라인으로 입력받은 키 값을 이용하여 'AES256'으로 난독화 할 수 있다.

 

Reference

- https://www.pyinstaller.org/

댓글
댓글쓰기 폼
공지사항
최근에 달린 댓글
Total
1,455
Today
0
Yesterday
21
링크
«   2020/02   »
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
글 보관함