Python Docker 메뉴얼
들어가며
내가 잠을 자는 동안에도 PC가 대신 일해주면 얼마나 좋을까요. 자동화는 향상된 삶의 질을 위해 꼭 필요한 부분입니다. 최근 진행중인 프로젝트에 다량의 최신 샘플이 필요한 일이 있었습니다. 정상/악성 모두 말이죠. 샘플의 필요갯수가 1000 단위가 넘다보니 이미 손으로 해결할 수 있는 영역을 벗어난 상태였습니다.
해결방안은 찾아보던 중 마침 VirusTotal Private API 가 있으니 신규 유입되는 샘플을 자동으로 다운로드 하면 어떨까 하는 아이디어가 떠올라 이 스크립트를 작성했습니다. 다만 그 실행에 있어 개인PC에 돌리자니 PC를 계속 켜둬야 하고… NAS 에 바로 올리자니 이상동작과 보안문제가 걸리고… 가상하나 올리자니 불필요한 리소스를 너무 잡아먹고… 간편하면서 독립된 실행환경을 찾던중 떠오른게 Docker 입니다.
python 를 Docker 로 실행하는것은 third-party module dependency 와 작업폴더 마운팅 등의 문제로 약간의 사전작업을 필요로 합니다. 쉽게 말해 기본 python 이미지를 기반으로 각종 모듈이 설치된 상태로 나만의 이미지를 만드는것이죠. 처음엔 조금 어려울 수 있지만 한번만 적응하면 확장성이 또 이만큼 좋은게 없습니다. 관련 튜토리얼이나 문서화가 워낙에 잘 되어 있기도 하기에 Docker 에 관한 더 자세한 정보는 다른 블로그나 공식페이지를 참고하시면 됩니다. 앞으로 진행할 작업은 다음과 같습니다.
- pip install 로 이미지에 설치할 모듈 목록 (requirements.txt) 작성
- Dockerfile 작성
- Dockerfile 로 이미지 빌드
- 이미지로 컨테이너 생성
requirements.txt
pip 은 -r, --requirement <file>
옵션으로 여러 패키지를 한번에 설치할 수 있습니다. 단순한 설치목록 나열 외에도 버전등을 명시할 수 있어 머신러닝등 버전 크리티컬한 모듈을 사용할 때 매우 유용합니다. 메뉴얼 과 예제를 보시면 쉽게 이해할 수 있습니다.
스크립트에서 사용한 third-party 모듈을 한번에 설치할 수 있도록 아래와같은 내용의 requirements.txt 파일을 작성했습니다. 버전영향을 받는 모듈이 아니다보니 내용물이 아주 간단합니다.
PyMySQL
PyYAML
requests
telepot
paramiko
Dockerfile
Dockerfile 의 차례입니다. Dockerfile 은 Docker 이미지를 만들 때 필요한 각종 설정(?)이라 생각하면 편합니다. 친절하고 자세한 설명이 도처에 있으니 간단한 검색으로 자세한 내용을 확인할 수 있습니다. 공식페이지 매뉴얼과 예제입니다. 빌드에 실패할 땐 매뉴얼을 보시면 대부분의 문제를 해결할 수 있습니다.
저는 아래와 같이 작성했습니다. 파이썬 Docker 공식 리포지터리가 제공하는 예제와 거의 동일합니다. 현재는 로컬의 소스코드를 복사하지만, 이 위치에 github 등 원격 리포지터리를 넣을수도 있습니다.
# 베이스 이미지
FROM python:3
# 폴더관련 작업(생성, 작업폴더설정, 외부노출)
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
# requirements.txt 이미지로 복사 및 모듈 설치
COPY requirements.txt ./
RUN pip install --no-cache-dir --requirement requirements.txt
# 소스코드 복사
COPY . .
# 스크립트 실행
CMD [ "python", "./main.py" ]
주의하실 점으로 인라인 주석은 지원하지 않는다는 점과, 마운팅 포인트를 지시하는 VOLUME 명령어를 사용하면 이후 Dockerfile 에서 설정한 파일복사 등 폴더내용 변화는 무시된다는 점(?)입니다. 소스코드를 넣은 폴더를 외부 노출시키고 싶었는데… 이부분에서 약간의 시간이 소요됐습니다. 여기 를 보시면, Dockerfile 작성시 VOLUME 에 관한 주의사항이 있습니다.
예제에는 빌드시에 모든 소스코드를 넣고 실행하는 명령까지 들어있어, 컨테이너 생성만 하면 스크립트가 실행됩니다. 다른 방법으론, VOLUME 명령으로 컨테이너 내부에 마운트 위치만 명시하고, 컨테이너를 생성시 소스코드가 있는 로컬 폴더를 마운트시켜도 컨테이너 내부에 소스코드가 들어가게 됩니다. 테스트 용도로는 이 방법이 더 직관적이긴 합니다. 예제로 사용한 스크립트는 악성샘플을 SFTP 로 전송하니 별도의 마운팅은 생략했습니다.
Changing the volume from within the Dockerfile: If any build steps change the data within the volume after it has been declared, those changes will be discarded.
Docker 이미지 빌드
준비물이 모두 갖춰졌으니 이미지를 생성할 차례입니다. Docker 의 강점이 여기서 나타납니다. 일종의 적층 방식으로 이미지가 생성되기에 이미지의 크기를 줄일 수 있고, 생성한 이미지를 네트워크로 배포할 수도 있기에 여러 PC에서 동일한 환경을 아주 쉽게 구성할 수 있습니다. 적층 방식의 의미는 Dockerfile 의 내용물을 조금씩 바꿔가며 빌드해보시면 쉽게 알 수 있습니다.(수정한 부분부터만 새롭게 빌드합니다, 이 글을 참고하세요) 기회가 된다면 생성한 이미지로 동일한 실행환경을 여러 PC에서 구성하는 방법에 대해 추가로 포스팅토록 하겠습니다. 이미지 네이밍 컨밴션은 여기서 확인할 수 있습니다.
docker build -t vthunt .
컨테이너 생성
드디어 마지막 단계입니다. 앞서 언급했듯이, Docker 는 컨테이너 내의 폴더를 외부에서 접근할 수 있도록 매핑해주는 기능을 제공합니다. -v, --volume
옵션이 그것입니다. 예를들어 transmission 같이 토렌트 기능을 하는 이미지라면, 다운로드한 데이터를 컨테이너 밖으로 꺼낼 필요가 있겠죠. 이 때 transmission 컨테이너 내부의 다운로드 폴더를 특정 로컬 폴더와 매핑해 컨테이너 내부 폴더에 접근할 수 있는 기능입니다. 여담으로 Docker가 버전업되며 -v 보단 –mount 옵션 사용을 권장합니다만 제가 사용하는 Synology NAS Docker(v1.11.2)는 지원하지 않았습니다.
Dockerfile 로 이미지 생성시에 폴더 매핑(마운팅)도 함께하면 좋겠지만, Docker 가 설치된 PC 환경이 제각각이기에, 폴더매핑은 반드시 컨테이너 생성시에 설정해야합니다. 또한 마운트 되는 순간, 컨테이너의 마운트 폴더를 로컬 폴더로 덮어쓰므로(리눅스의 마운트와 완전히 동일, 참고) 내부 파일을 노출시키려면 이 포스트의 Persisting data항목을 참고하면 좋을것입니다.
이와는 별개로, 단일 파일로 된 스크립트를 실행할 경우, 아래와 같은 명령을 통해 컨테이너 밖의 스크립트를 마운트나 복사 없이 손쉽게 실행해 볼 수 있습니다.
docker run -it --rm --name my-running-script -v "$PWD":/usr/src/myapp -w /usr/src/myapp python:3 python your-daemon-or-script.py
예제로 쓴 스크립트에선 쓰지도 않는 마운트에 대해 장황하게 설명했네요. 그만큼 중요하고 자주 쓰이는 기능이니 알아두시면 좋을것 같습니다.
docker run -it --rm --name vthunt_con vthunt
마치며
자 이제 스크립트가 실행되는 것을 확인해 보겠습니다. -d
옵션을 주면 백그라운드 모드로 실행됩니다. 현재는 생략했으니 쉘에 stdout 이 계속 출력 될것입니다. 코드 안정화가 완료되고 데몬(서비스)로 돌리려면 -d
옵션을 주면 됩니다.
잘 실행되는군요. 샘플은 나스가 받아줄테니 전 이제 다른 일에 집중 해야겠습니다. 이 포스트는 여기를 참고해 작성했습니다.
안녕하세요.
저도 제 Synology NAS에 telepot를 설치하려는데 아마도 파이썬이 3.5 버전이라 설치시 에러가 나는 것 같다고 Stack Overflow에서 토론하는 것을 봤습니다.
NAS가 어떤 제품이시며 설치된 파이썬 버전은 몇인지 여쭤봐도 될까요?