---
html:
offline: false
toc: true
toc:
depth_from: 1
depth_to: 6
ordered: false
export_on_save:
html: true
print_background: true
---
# Python 모듈 사용하기: KSB Dockerize
---
`ksblib.dockerize` 라이브러리는 사용자가 프로그래밍한 Python 코드를 Docker 이미지로 자동변환하는 기능을 제공합니다. 또한 REST API 기능을 자동으로 추가하여, 사용자 Python 코드로 입력자료를 보내고 결과를 받을 수 있도록 해줍니다.
## 필요 라이브러리
- Python 3.5+
- Docker 18+
- 명령창에서 `docker` 명령어가 실행되는지 확인
## 테스트 환경
- Ubuntu 16.04 및 Mac OS X
- Windows 에서는 테스트되지 않음
## 사용방법
### 1. 설치하기
`ksblib.dockerize`를 설치하기 위해, KSB 프레임워크 소스코드 폴더 안의 "ksblib" 폴더로 이동해 아래 명령어를 입력합니다.
```sh
$> python setup.py install
```
KSB 프레임워크가 설치되어있지 않다면 설치매뉴얼을 참고하여 KSB 프레임워크를 설치합니다.
### 2. base.py 와 main_func 생성하기
사용자 Python 코드들이 아래처럼 "modules" 폴더 안에 들어있다고 가정합니다. 참고: 아래 구조는 하나의 예이고, 폴더이름 (여기서는 "modules") 및 구조는 사용자 임의로 생성할 수 있습니다.
```sh
# Example of user-provided folder structure.
-+ modules
- __init__.py
- base.py
- requirements.txt
- user_python_file_01.py
- datafile01.pkl
- ...
-+ libraries
- library01.py
- ...
```
`ksb.dockerize` 라이브러리를 이용하기위해서는 반드시 두개의 파일이 "modules" 폴더 안에 들어있어야 합니다. 하나는 `__init__.py`이고 다른 하나는 `base.py`입니다. `__init.py__`는 비어있는 Python 파일이어도 상관없습니다 . `base.py`는 `main_func`이라는 함수를 포함하고 있어야 합니다. `ksb.dockerize` 라이브러리는 이 `main_func`을 불러 수행합니다. 예를 들면 아래와 같습니다.
```python
import json
def main_func(x, user_args=None):
# In this example, x is Python string containing Json data.
x = json.loads(x)
input = x['input']
# Do some work with the input data.
added_input = input + input
# Return results in Python string type.
return str(added_input)
```
`x`는 REST API를 통해 입력받을 데이터로, 문자열 형태입니다. `x`는 단순한 Python 문자열이어도 되고 (예: `'Hello World'`), 혹은 Json 문자열이어도 됩니다 (예: `'{"input": 4}'`). 위에 작성된 예제코드는 입력값 `x`가 Json 문자열이라고 가정하고 작성된 `main_func`입니다.
아래 코드는 `x` 가 단순 문자열이라고 가정하고 작성된 `main_func`입니다.
```python
def main_func(x, user_args=None):
# In this example, x is Python string.
input = x
# Do some work with the input data.
added_input = input + input
# Return results in Python string type.
return str(added_input)
```
`x`를 어떤 형태로 정의하고, 이를 `main_func`에서 어떻게 사용할지는 전적으로 사용자 자유입니다. 물론, 입력으로 오는 `x`의 형태와 그를 처리하는 방식이 잘 맞아야만 합니다. 또한, `main_func`은 마지막 줄에 보이는 것처럼, 반드시 문자열형태의 결과를 `return`해야합니다.
아래에서 설명하는 모든 내용들은 `x`가 Json 문자열이라고 가정하고 작성된 코드를 사용합니다. `user_args`에 대한 사용법은 아래에서 설명합니다.
참고: "modules" 폴더 안에 있는 모든 파일들은 Docker 이미지 생성 시에 이미지 내부로 복사됩니다. 따라서 `base.py`에서 자유롭게 불러들여 사용할 수 있습니다. 또한 "modues" 폴더 안에 `base.py` 외에도 여러 사용자 Python 파일들을 넣은 후 `base.py`에서 불러들여 수행할 수도 있습니다.
### 3. main_func 수행 실험하기
`ksb.dockerize`를 이용하기 전에 `main_func`이 성공적으로 수행되는지 반드시 테스트되야 합니다. 이를 위해, "modules"의 상위 폴더에서 Python console을 열고 (e.g. `$> python` 혹은 `$> ipython`), 아래와 같이 입력합니다.
```python
from modules.base import main_func
x = '{"input": 4}'
main_func(x)
```
작성한 `main_func`이 에러없이 수행되도록 `main_func` 및 사용자 Python 코드들을 수정해야합니다.
참고: 실험 중 발생하는 에러는 주로, 1) 해당 모듈이 없다거나 혹은 2) 해당 파일이 없다는 에러일 것입니다. 두 에러 모두 기본적인 Python 프로그래밍 에러로, Google 검색을 통해 해결할 수 있습니다. 간단하게 예를 들면,
- 해당 모듈이 없는 경우
- 사용자가 만든 Python script 파일을 "modules" 폴더 안에 넣은 후 `base.py`에서 `import`하여 사용하고자 할 때 발생할 수 있습니다. 이 경우에는, `import`시에 "modules" (즉, 최상위 폴더이름) 로부터 시작하면 대부분 해결됩니다. 즉, `from modules.xxx import xxx` 혹은 `import modules.xxx.xxx`와 같은 식이 됩니다.
- 해당 파일이 없는 경우
- `base.py`에서 "modules" 폴더 안에 있는 파일에 접근하고자 할 때 이 에러가 발생할 수 있습니다. 간단한 경로 문제로, Python 코드에서 `os.path.dirname(__file__)`을 읽어오는 파일경로 앞에 붙여주면 대부분 해결됩니다.
### 4. Docker 이미지 생성하기
이미지는 Python 혹은 CLI를 이용하여 생성할 수 있습니다. 아래에서 각각에 대해 설명합니다.
#### 4.1 Python 이용하여 생성하기
위 과정까지 문제없이 끝났다면, 아래 Python 코드와 같이 `ksb.dockerize`를 실행합니다.
```python
from ksblib.dockerize import Dockerize
drize = Dockerize('docker_image_name', '/path/to/modules',
requirements=['numpy:scipy>=1.1.0'])
drize.build()
drize.run()
```
위 명령어는 "modules" 폴더에 있는 Python 코드들을 Docker 이미지로 만듭니다. 폴더경로 (`/path/to/modules`)는 절대경로입니다. 생성되는 이미지의 이름을 변경하려면 `docker_image_name`를 바꾸면 됩니다. `ksblib.dockerize`는 8080 포트로 REST API를 제공합니다. 다른 포트로 변경하려면, 마지막 줄을 `drize.run(port=9090)`처럼 변경하면 됩니다.
사용자 Python 코드에서 사용하는 Python 라이브러리가 있다면 Docker 이미지 생성시에 `requirements` 옵션을 이용해 설치할 수 있습니다. 각각의 라이브러리는 `:`로 구분하면 되며, 버전 정보도 명시할 수 있습니다.
라이브러리를 설치하는 또 다른 방법은, "modules" 폴더 안에 "requirements.txt" 파일을 생성하는 것입니다. "requirements.txt" 의 내용은 아래처럼 작성하면 됩니다.
```sh
numpy
scipy>=1.1.0
```
`ksb.dockerize`는 "requirements.txt" 안의 라이브러리를 먼저 설치하고, 그 후에 `requirements`로 명시된 라이브러리들을 설치합니다. 따라서 동일한 라이브러리가 두 곳에 모두 명시되었다면, `requirements`에 명시된 라이브러리가 최종 설치됩니다.
이미 생성된 Docker 이미지가 있어서, 다시 만들고 싶지 않다면 위 예제 코드에서 `drize.build()` 부분을 주석처리하면 됩니다.
만약, 입력값 `x`에 정보를 담아보내지 않고 Docker 이미지로 따로 전달하고 싶은 값이 있다면, `user_args`를 사용하면 됩니다. 예를 들면, `drize.run(user_args='Hello World')`와 같이 사용자변수를 전달할 수 있습니다. 새로 이미지를 생성하지 않더라도, 사용자변수는 기존 이미지로 전달됩니다. 전달받은 `user_args`는 `main_func`에서 사용할 수 있습니다. 예를 들면 아래와 같습니다.
```python
def main_func(x, user_args=None):
if user_args is not None:
user_args_str = 'User arguments: {0}'.format(user_args)
else:
user_args_str = 'No user arguments'
```
참고: `user_args`로 전달되는 값은 반드시 문자열이어야 합니다.
#### 4.2 CLI 이용하여 생성하기
KSB 프레임워크 소스코드 폴더 안의 "ksblib" 폴더로 이동합니다. 해당 폴더에서 아래와 같이 입력합니다.
```python
$> python ksblib/dockerize/call_dockerize.py -h
```
위 명령어는 CLI를 이용하여 Docker 이미지를 생성하는 Python script의 도움말을 출력합니다. 이 script를 이용하여 Docker 이미지를 생성하는 방법은 아래와 같습니다.
```python
$> python ksblib/dockerize/call_dockerize.py json_image /path/to/modules --port 8080 --user_args="\"Hello World\"" --python_libraries=numpy:scipy
```
이미지 이름 (`json_image`)와 폴더경로 (`/path/to/modules`)만 필수이며, 나머지는 모두 옵션사항입니다. `--port`는 접속 포트번호이고, `--user_args`는 문자열로 표현된 사용자변수, `--python_libraries`는 설치할 Python 라이브러리를 나타냅니다.
### 6. 생성된 Docker 이미지 실험하기
위 과정에 따라 Docker 이미지가 성공적으로 만들어지고 실행되었다면, 아래처럼 실험해 볼 수 있습니다.
```
$> curl -d '{"input": 4}' http://localhost:8080
```
위 명령어를 실행하면, 아래와 같은 작업이 순차적으로 수행됩니다.
1. `curl` 명령어가 문자열 `'{"input": 4}'`를 REST API를 통해 Docker 이미지 로 전송.
2. `ksb.dockerize`가 입력값을 `main_func`에 전달. 참고: 위에서 설명한 예제들의 경우, `x`로 전달됨.
3. `main_func`이 문자열 `'8'`을 반환
혹은, 아래처럼 Python 명령어를 이용해 테스트해볼 수도 있습니다.
```python
import requests
r = requests.post('http://localhost:8080', data='{"input": 4}')
status_code = r.status_code
response = r.text
```
반환되는 값 `response`의 형식은 Python 문자열입니다.
### 7. 다양한 형태의 입력자료 사용법
위 예제에서는 문자열로 표현된 Json에 대해 처리하는 방법에 대해 설명하였습니다. 이번 장에서는 Json이 아닌 다른 형태의 Python 자료를 넘기고 싶을 경우에 대해 설명합니다.
#### 7.1 Numpy Array 넘기기
상술하였듯이 KSB Dockerize는 문자열로 입력자료를 주고 받습니다. 따라서 어떠한 형태의 자료든 상관없이, 문자열로 표현이 된다면 입력자료로 사용가능합니다. 먼저 입력자료를 전송받을 `base.py`를 아래와 같이 작성합니다.
```python
import numpy as np
def main_func(x, user_args=None):
data = np.frombuffer(eval(x)).copy()
data /= 10.
return str(np.median(data))
```
위 코드는, Docker 이미지로 전송된 입력값을 Numpy Array로 변환한 후, 10으로 나누는 코드입니다. 리턴값은 Array의 평균값입니다 (주: 상술하였듯이 리턴값은 항상 문자열 형태여야 함).
작성된 파일을 기반으로 Docker 이미지를 생성합니다. 생성된 Docker 이미지로 자료를 전송하기 위해서는 아래와 같은 Python script를 이용합니다.
```python
import requests
import numpy as np
data = np.random.randn(100)
r = requests.post('http://localhost:8080', data=str(data.tobytes()))
```
위 코드에서 보이듯이 Numpy Array를 `tobytes()` 함수를 이용하여 Bytes Array로 변환한 후에 문자열로 변환합니다. 변환된 문자열을 생성된 Docker 이미지에 전송할 때에는 `requests` 라이브러리를 사용합니다.
결과값으로는, 위에 코드에 보이듯이, 표준정규분포에서 랜덤으로 선택된 100개의 수치를 10으로 나눈 후 평균값을 낸 값이 문자열로 리턴됩니다.
#### 7.2 Image 자료 전송하기
Numpy Array가 아닌 다른 형태의 자료도 동일한 방법으로 전송이 가능합니다. 예를 들어, 그림 파일을 전송받아 조작하고 싶은 경우의 `base.py`는
```python
from PIL import Image
from io import BytesIO
def main_func(x, user_args=None):
# For Image data.
im = Image.open(BytesIO(eval(x)))
# Do whatever you want.
...
return 'Some string value'
```
와 같이 작성하면 됩니다. 위 예제에서는 Pillow 라이브러리를 사용하였습니다. 따라서 Docker 이미지 생성시에 설치 라이브러리로 `pillow`를 명시해 주어야 합니다. 위 코드에서는 결과값으로 단순문자열을 돌려주지만, 그림 파일을 돌려줄 수도 있습니다 (주의: 물론 이 때도 문자열 형태로 돌려주어야 합니다).
자료를 전송하는 Python scrip는
```python
import numpy as np
from PIL import Image
with open('image.png', 'rb') as image:
f = image.read()
b = bytearray(f)
# Image manipulation.
r = requests.post('http://localhost:8080', data=str(b))
```
의 형태가 됩니다. Numpy Array 전송시와 마찬가지로, 그림 파일의 Bytes Array 값을 문자열 형태로 넘겨주면 됩니다.
이와 같은 방법으로 영상은 물론 음성을 포함하는 어떠한 형태의 자료도 Docker 이미지로 전송할 수 있습니다.