Newer
Older
# Python 모듈 사용하기: KSB Dockerize
---
`ksblib.dockerize` 라이브러리는 사용자가 프로그래밍한 Python 코드를 Docker 이미지로 자동변환하는 기능을 제공합니다. 또한 REST API 기능을 자동으로 추가하여, 사용자 Python 코드로 입력자료를 보내고 결과를 받을 수 있도록 해줍니다.
- Docker 18+
- 명령창에서 `docker` 명령어가 실행되는지 확인
## 테스트 환경
- Ubuntu 16.04 및 Mac OS X
- Windows 에서는 테스트되지 않음
## 사용방법
### 1. 설치하기
`ksblib.dockerize`를 설치하기 위해, KSB 프레임워크 소스코드 폴더 안의 "ksblib" 폴더로 이동해 아래 명령어를 입력합니다.
```sh
$> python setup.py install
```
KSB 프레임워크가 설치되어있지 않다면 설치매뉴얼 (https://csleoss.etri.re.kr/kor/sub05_01.do)을 참고하여 KSB 프레임워크를 설치합니다.
### 2. base.py 와 main_func 생성하기
사용자 Python 코드들이 아래처럼 "modules" 폴더 안에 들어있다고 가정합니다. <b>참고</b>: 아래 구조는 하나의 예이고, 폴더이름 (여기서는 "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`을 불러 수행합니다. 예를 들면 아래와 같습니다.
# 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를 통해 입력받을 데이터로, <b>문자열</b> 형태입니다. `x`는 단순한 Python 문자열이어도 되고 (예: `'Hello World'`), 혹은 Json 문자열이어도 됩니다 (예: `'{"input": 4}'`). 위에 작성된 예제코드는 입력값 `x`가 Json 문자열이라고 가정하고 작성된 `main_func`입니다.
# 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`에서 어떻게 사용할지는 <b>전적으로 사용자 자유</b>입니다. 물론, 입력으로 오는 `x`의 형태와 그를 처리하는 방식이 <b>잘 맞아야만</b> 합니다. 또한, `main_func`은 마지막 줄에 보이는 것처럼, <b>반드시 문자열</b>형태의 결과를 `return`해야합니다.
아래에서 설명하는 모든 내용들은 `x`가 Json 문자열이라고 가정하고 작성된 코드를 사용합니다. `user_args`에 대한 사용법은 아래에서 설명합니다.
<b>참고</b>: "modules" 폴더 안에 있는 모든 파일들은 Docker 이미지 생성 시에 이미지 내부로 복사됩니다. 따라서 `base.py`에서 자유롭게 불러들여 사용할 수 있습니다. 또한 "modues" 폴더 안에 `base.py` 외에도 여러 사용자 Python 파일들을 넣은 후 `base.py`에서 불러들여 수행할 수도 있습니다.
`ksb.dockerize`를 이용하기 전에 `main_func`이 성공적으로 수행되는지 <b>반드시</b> 테스트되야 합니다. 이를 위해, "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 코드들을 수정해야합니다.
<b>참고</b>: 실험 중 발생하는 에러는 주로, 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__)`을 읽어오는 파일경로 앞에 붙여주면 대부분 해결됩니다.
이미지는 Python 혹은 CLI를 이용하여 생성할 수 있습니다. 아래에서 각각에 대해 설명합니다.
#### 4.1 Python 이용하여 생성하기
위 과정까지 문제없이 끝났다면, 아래 Python 코드와 같이 `ksb.dockerize`를 실행합니다.
```python
from ksblib.dockerize import Dockerize
drize = Dockerize('docker_image_name', '/path/to/modules',
위 명령어는 "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'
```
KSB 프레임워크 소스코드 폴더 안의 "ksblib" 폴더로 이동합니다. 해당 폴더에서 아래와 같이 입력합니다.
$> 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 라이브러리를 나타냅니다.
위 과정에 따라 Docker 이미지가 성공적으로 만들어지고 실행되었다면, 아래처럼 실험해 볼 수 있습니다.
```
$> curl -d '{"input": 4}' http://localhost:8080
```
위 명령어를 실행하면, 아래와 같은 작업이 순차적으로 수행됩니다.
1. `curl` 명령어가 문자열 `'{"input": 4}'`를 REST API를 통해 Docker 이미지 로 전송.
2. `ksb.dockerize`가 입력값을 `main_func`에 전달. <b>참고</b>: 위에서 설명한 예제들의 경우, `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
```
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
반환되는 값 `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 이미지로 전송할 수 있습니다.