이 글은 기존 운영했던 WordPress 블로그인 PyxisPub: Development Life (pyxispub.uzuki.live) 에서 가져온 글 입니다. 모든 글을 가져오지는 않으며, 작성 시점과 현재 시점에는 차이가 많이 존재합니다.
작성 시점: 2020-02-15
가끔 유지보수 하고 있던 node.js 기반의 텔레그램 봇에서 파이썬을 도입하게 되었다. 그 이유는, 봇의 특정 기능 중 요약문을 제공하는 기능을 필요로 했었고, Komoran + TextRank 알고리즘 기반으로 구성하는 것이 그나마 빠르게 진행할 수 있었기 때문이었다.
다만, 다른 곳에서 해당 기능을 사용하므로 api로 배포할 필요가 있었고, 전체적인 구조는 다음과 같다.
이 중, API를 사용하기 위해 Flask-RESTFul 를 사용하고, 배포는 Python WSGI 웹 서버인 Gunicorn 를 사용했다.
이 글에서는 TextRank Analyzer 를 API로 expose 하고, Docker로 배포하는 과정을 정리한다.
먼저, TextRank 알고리즘 수행을 위해 외부에서 받아 와야 할 정보들에 대해 정리가 필요하다.
이제, 이 값들을 body로 받아 보관해야 하는데, Flask-RESTFul 는 reqparse 라는 기능을 제공한다.
이는 argument parsing 에 많이 쓰이는 argparse 와 비슷한 사용법을 가지면서도 사용자의 post body 를 해석하는 기능을 제공한다.
사용법은 `parser.add_argument(name, type) 이고, required 여부나 default 도 지정할 수 있다.
이를 반영한 API 클래스는 다음과 같을 것이다.
class TextRank(Resource):
def post(self):
try:
parser = reqparse.RequestParser()
parser.add_argument('body', type=str, required=True)
parser.add_argument('sentenceCount', type=int, default=3, required=False)
parser.add_argument('keywordCount', type=int, default=5, required=False)
parser.add_argument('beta', type=float, default=0.85, required=False)
parser.add_argument('diversity', type=float, default=0.3, required=False)
parser.add_argument('minWordCount', type=int, default=3, required=False)
parser.add_argument('maxWordCount', type=int, default=20, required=False)
parser.add_argument('minSentencePenalty', type=int, default=10, required=False)
parser.add_argument('maxSentencePenalty', type=int, default=80, required=False)
args = parser.parse_args()
...
return {
'code': '1',
'message': 'Success',
'sentence': sentence,
'keyword': keywords
}
except Exception as e:
return {'code': -1, "message": 'Failed ' + str(e)}
만들어진 TextRank 클래스를 Flask에 연결하게 되면, 지정한 routing 에 따라 API가 호출된다.
app = Flask(__name__)
api = Api(app)
api.add_resource(TextRank, '/summarize')
if __name__ == '__main__':
app.run(port=8000)
이와 같이 작성하고 Run을 하여 127.0.0.1:8000/summarize 로 API 요청을 보내면 결과가 나오게 된다.
python3 server.py
로 서버를 실행시킬 수 있지만, 이는 development 기준으로 배포용도에는 맞지 않다.
실제로, 위 명령어로 배포하게 되면 아래와 같은 메세지가 나오게 된다.
* Serving Flask app "server" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on <http://127.0.0.1:8000/> (Press CTRL+C to quit)
요점은 production WSGI server 를 사용하라는 것이다. 여기서 WSGI 는 PEP-3333 에 정의된 파이썬 웹 서버 게이트웨이로, 파이썬 스크립트가 웹 서버와 통신하기 위해 작성된 인터페이스라고 설명할 수 있다.
여기서는 상기하였듯이 Gunicorn 를 사용했고, 사용법은 다음과 같다.
gunicorn server:app
여기서 server:app 는 각각 Flask를 담고 있는 파이썬 스크립트와 Flask 로컬 변수를 의미한다.
명령어를 입력하면 아래와 같은 로그가 나오게 된다.
% gunicorn server:app
[2020-02-15 13:15:14 +0900] [38332] [INFO] Starting gunicorn 20.0.4
[2020-02-15 13:15:14 +0900] [38332] [INFO] Listening at: <http://127.0.0.1:8000> (38332)
[2020-02-15 13:15:14 +0900] [38332] [INFO] Using worker: sync
[2020-02-15 13:15:14 +0900] [38336] [INFO] Booting worker with pid: 38336
기본적으로 127.0.0.1 (loopback) 주소에만 대응하고 있는데, 이를 도커 등으로 활용하려면 0.0.0.0:8000 등으로 port만 지정할 필요가 있다.
이는 -b 0.0.0.0:8000 으로 지정할 수 있고, 최종 명령어는 다음과 같다.
gunicorn -b 0.0.0.0:8000 server:app
이를 실행하면 다음과 같이 나온다.
% gunicorn -b 0.0.0.0:8000 server:app
[2020-02-15 13:16:04 +0900] [38387] [INFO] Starting gunicorn 20.0.4
[2020-02-15 13:16:04 +0900] [38387] [INFO] Listening at: <http://0.0.0.0:8000> (38387)
[2020-02-15 13:16:04 +0900] [38387] [INFO] Using worker: sync
[2020-02-15 13:16:04 +0900] [38391] [INFO] Booting worker with pid: 38391
위 gunicorn 을 사용해 Dockerfile로 만들면 다음과 같을 것이다.
FROM ubuntu
WORKDIR /usr/src/app
RUN rm -rf /var/lib/apt/list/* && apt-get update && apt-get install python3 python3-pip -y
ADD . .
RUN pip3 install -r requirements.txt
EXPOSE 8000
CMD ["gunicorn", "-b", "0.0.0.0:8000", "server:app"]
간단히, python 과 python3-pip 를 설치하고 모든 파일을 복사하여 requirements.txt 에 적힌 항목을 전부 설치한다. 마지막으로, 포트 8000를 expose 하여 gunicorn를 실행한다.