--- 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 이미지로 전송할 수 있습니다.