[Docker] Flask+Gunicorn+Nginx 클라우드 배포

배경

중국 칭화대에 교환학생으로 수업을 듣게된지 벌써 3주차가 되어간다. 软件工程(software engineering) 수업에서 과제를 주었는데, 회원가입 정보 검증코드를 작성하고 파이참으로 유닛테스트하기 그리고 이번 포스팅에서 다룰 docker를 이용하여 클라우드 컴퓨터에 배포를 하라는 것이였다.

나는 배포라고는 아마존에 EC2 정적파일을 배포한 경험밖에 없어 많이 당황하고 많이 헤맸다.. 과제완성의 과정을 차근차근 정리해보겠다.

전체적인 배포의 형태

나는 처음에 이 과제를 받았을 떄 당황했다. 내가 아는거라곤 Docker과 Flask.. 그마저도 그냥 이름만 들어본 정도였다. 그래서 우선적으로 이것들이 무슨 역할을 하는지 전체적인 구조를 파악하는게 중요했다.

간략하게 말하자면

  1. client에서 웹서버로 http요청을 보낸다.
  2. 웹서버는 동적요청을 WSGI에 전달한다.
  3. WSGI는 웹서버와 앱의 통신을 담당한다.

이렇게 정리를 할 수 있다.



자세한 역할(참고)

Nginx

  • 메인 라우팅 관리 (리버스 프록시).
  • 정적 파일 제공
  • 한 번에 들어오는 많은 요청을 처리
  • 느린 클라이언트 처리
  • 동적 요청을 wsgi에 전달
  • SSL (https)
  • Python 코드와 비교하여 컴퓨팅 리소스 (CPU 및 메모리) 절약
  • 로드 밸런싱, 캐싱 등

WSGI

  • 작업자 프로세스 / 스레드 풀 실행 (코드 실행)
  • Nginx에서 들어오는 요청을 WSGI와 호환되도록 번역
  • 앱의 WSGI 응답을 적절한 http 응답으로 변환
  • 요청이 들어오면 실제로 파이썬 코드를 호출

Docker

(이미지가 귀여워서 가져와봤다)

도커는 이러한 전체적인 구조를 한곳에 모아 컨테이너를 만들어 배포 서버와 앱의 환경을 유지하고 관리하기위해서 사용된다.

도커 이미지를 만드는 Dockerfile과 이 이미지들을 모아서 합치고 컨테이너로 만들어주는 docker-compose.yml로 Flask+Gunicorn(WSGI)+Nginx 컨테이너를 만들어 볼 것이다.

전체 디렉터리 구조

Project
ㄴ-flask
   ㄴ-application
      ㄴ-app.py
        -......
     -Dockerfile
     -manage.py
     -requirements.txt
ㄴ-nginx
  ㄴ-static
    -default.conf
    -nginx.conf
    -Dockerfile
ㄴ-docker-compose.yml
  -run_docker.sh

주의할 점은 나는 배포할 flask웹앱을 과제로 받은 상태였기 떄문에 흔히 wsgi.py라고 명명된 파일이 여기선 manage.py 명명되어있다.

flask

# manage.py

import os
from application import create_app
from application import db
from flask_script import Manager, Server
from flask_migrate import Migrate, MigrateCommand
from application.utils import config

app = create_app(os.getenv('TYPE', 'default'))
host = config.get_yaml('app.HOST')
port = config.get_yaml('app.POST')

manager = Manager(app)
migrate = Migrate(app, db)

manager.add_command('runserver', Server(host=host, port=port))
manager.add_command('db', MigrateCommand)

if __name__ == '__main__':
    manager.run()

wsgi.py 역할을 하는 이 파일은 플라스크 앱을 가져와 서버를 실행시켜준다.

#Dockerfile

FROM python:3
WORKDIR /app 
ADD . /app 
RUN pip install -r requirements.txt 

app 디렉토리에 flask앱을 저장한다.

nginx

# nginx.conf

user nginx;

worker_processes 1;

error_log /var/log/nginx/error.log warn;

pid /var/run/nginx.pid;

events{
    worker_connections 1024;
}

http{
    include /etc/nginx/mime.types;

    default_type text/html;

    log_format main '$remote_addr - $remote_user [$time_local] "$request"'
                    '$status $body_bytes_sent "http_referer"'
                    '"$http_user_agent" "$http_x_forwarded_for"';
    access_log /var/log/nginx/access_log main;

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;

    keepalive_timeout 65;

    include /etc/nginx/conf.d/*.conf; 
}

access log와 error log를 쌓기 위해서 nginx.conf도 설정해주었다.

# default.conf

server {
    listen 8000;

    location / {
            proxy_pass http://flask:5000;

            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    location ^~ /static/  {
        include  /etc/nginx/mime.types;
        root /;
    }

}

나는 과제 요구사항으로 8000번 포트를 열어 접속을 받도록 설정했다. (보통 80번 포트로 접속을 받는다) 그리고 정적파일도 과제 파일에 있어서 static 접근 경로를 설정해주었다.

# Dockerfile

FROM nginx:1.21.3

RUN rm /etc/nginx/nginx.conf
COPY nginx.conf /etc/nginx/
RUN rm /etc/nginx/conf.d/default.conf
COPY default.conf /etc/nginx/conf.d	
RUN mkdir static
COPY /static/ /static/

Docker-compose

#docker-compose.yml

version: "3.7"
services:
  flask:
    build: ./flask
    container_name: app
    depends_on:
      - mysql
    restart: always
    environment:
      - APP_NAME=FlaskTest
    ports:
      - "5000:5000"
    command: gunicorn -b 0.0.0.0:5000 manage:app
  nginx:
    build: ./nginx
    container_name: nginx
    restart: always
    ports:
      - "0.0.0.0:8000:8000"
    depends_on:
      - flask

flask 포트는 5000 nginx 포트는 8000으로 설정하였는데, 외부 접속을 허용하기 위해 tcp 0.0.0.0을 붙여주었다.

또한 flask 컨테이너에

gunicorn 바인드 포트 $(MODULE_NAME) : $(VARIABLE_NAME)

커맨드를 추가하여 컨테이너 실행 시 wsgi가 작동하도록 하였다.

run_docker.sh

이렇게 dockerfile, docker-compose 구성한 flask nginx 환경을 간편하게 빌드하기 위해 명령어를 모아주었다.

echo killing old docker processes
docker-compose rm -fs

echo building docker containers
docker-compose up --build -d

이제 실행하면 이미지와 컨테이너들이 생성되고 실행될 것이다.

$ bash run_docker 

혹여나 컨테이너들이 정상적으로 다 띄워졌는데 접속이 안된다면 클라우드 컴퓨터의 방화벽을 체크하자.

근데 나만 과제때문에 특이하게 8000으로 포트를 열어서 여기서 헤매고 뻘짓한듯하다.


후기

Docker와 nginx wsgi 모두 처음 써보는거라 많이 헤매었다.

Docker는 한번 경험해보니 다음부터 쉽게쉽게 이미지를 빌드하고 컨테이너를 만들어 배포할 수 있을 것 같은 느낌이 들정도로 간편하고 직관적이였다.

nginx와 wsgi는 아주 피상적으로 알게되었는데, 파고들어서 공부를 하면 서버의 구조를 이해하는데 도움이 많이 될 것 같다.

근데 우선은 다른 밀린과제부터 하러가야겠다...

Reference

https://uiandwe.tistory.com/1268
https://zinirun.github.io/2020/08/19/deploy-flask-to-docker/
https://blog.dalso.org/it/docker/16828
https://docs.gunicorn.org/en/stable/run.html#commands

좋은 웹페이지 즐겨찾기