본문으로 바로가기
반응형

먼저 해당 포스팅은 기존에 작성해놓은 Flaks boilerplate를 이용한다.

(https://github.com/teamhide/flask_restplus_docker/tree/master)


위 boilerplate를 보면 간단하게 유저 생성 조회, 게시판 기능을 갖추고 있다. (글 생성, 조회/댓글 생성, 조회)

API에 관한 포스팅이 아니므로 소스에 대한 설명은 생략하고 구조부터 보자.


도커 관련된 dockerfile은 /docker 디렉토리에 들어있고 사용할 환경설정 파일들은

전부다 /config폴더에 들어있다.

(참고로 redis는 아직 연동하지 않았지만 dockerfile은 미리 생성해놨다)


보통 도커를 이용하여 작업을 하다보면 여러개의 컨테이너를 하나로 묶어주는 작업이 필요하다.

이 작업에 사용하는것이 바로 docker-compose이다.

docker-compose를 사용하면 여러개의 컨테이너를 하나로 묶어서 구동시킬 수 있다.

먼저 docker에서 사용할 이미지들부터 다운받아야 한다.

아래의 명령어로 우리가 사용할 이미지를 다운받는다.


docker pull nginx

docker pull python:3.6.5

docker pull mongo


다음으로 docker-compose.yml파일부터 살펴본다.


version: '3.7'

services:
nginx:
build:
context: .
dockerfile: docker/nginx/dockerfile
container_name: nginx
hostname: nginx-dev
ports:
- '80:80'
networks:
- backend
links:
- web_project
depends_on:
- web_project

mongodb:
build:
context: .
dockerfile: docker/mongodb/dockerfile
container_name: mongodb
hostname: mongodb-dev
ports:
- '27017:27017'
networks:
- backend

web_project:
build:
context: .
dockerfile: docker/web/dockerfile
container_name: web_project
hostname: web_project_dev
ports:
- '5000:5000'
networks:
- backend
tty: true
depends_on:
- mongodb
links:
- mongodb

# redis:
# image: redis:latest
# container_name: redis
# hostname: redis_dev

networks:
backend:
driver: 'bridge'

(redis는 이 포스팅에서 엮지 않을 것이므로 주석처리 해놨다)


Nginx / Python / MongoDB 총 3개의 컨테이너가 하나로 돌아간다.

보통 docker-compose.yml에 세부적인 내용을 적는것이 아니라 각 컨테이너별로 dockerfile을 생성하고 그곳에 작성한다.

build아래에 각 dockerfile이 존재하는 경로를 dockerfile옵션을 통해 명시해줘야 하는데

여기서 context를 통해 경로를 써주지 않으면 오류가 발생한다.


nginx에서는 파이썬 플라스크 서버(web_project)에 접근해야 하므로 links에 web_project를 적어줬고

또한 플라스크가 구동된후에 시동이 켜지도록 depends_on또한 web_project를 줬다.

다음으로 각 dockerfile을 살펴본다.


[/docker/mongodb/dockerfile]

FROM mongo:latest
MAINTAINER john <john@cupist.com>

EXPOSE 27017
CMD mongod --bind_ip_all

기존에 다운받았던 mongodb를 사용하기 위해 FROM에 명시해뒀으며

EXPOSE를 통해 포트를 정의해줬다.

마지막으로 서버를 구동시키기 위해 mongod 명령어를 내렸으며 모든 아이피 입력을 받기 위해 --bind_ip_all 옵션을 함께 줬다.


[/docker/nginx/dockerfile]

FROM nginx:latest
MAINTAINER john <john@cupist.com>

COPY . ./home
WORKDIR home
RUN rm /etc/nginx/conf.d/default.conf
COPY ./config/nginx.conf /etc/nginx/conf.d/default.conf

설정파일을 포함한 파이썬 소스를 /home에 복사하고 WORKDIR를 통해 작업 디렉토리를 home으로 이동시킨다.

그리고 나중에 uwsgi와 결합한 플라스크 서버로 라우팅을 해줘야 하는데

이 부분은 /etc/nginx/conf.d/default.conf에 작성해야하므로 기존 파일을 삭제하고

우리가 나중에 작성할 파일로 대체시켰다.


[/docker/web/dockerfile]

FROM python:3.6.5
MAINTAINER john <john@cupist.com>

COPY . ./home
WORKDIR home
RUN pip3 install -r app/requirements.txt
RUN apt-get update && apt-get install -y uwsgi-plugin-python3 \
&& apt-get install -y uwsgi-plugin-python && apt-get install -y supervisor \
&& apt-get install -y vim

COPY config/api.conf /etc/supervisor/conf.d/api.conf

RUN rm /etc/supervisor/supervisord.conf
COPY ./config/supervisord.conf /etc/supervisor/supervisord.conf
RUN ["chmod", "+x", "/home/config/run.sh"]
CMD /home/config/run.sh

마찬가지로 파일을 home 디렉토리로 복사하였고 사용하는데 필요한

uwsgi에서 필요한 플러그인과 supervisor를 apt-get을 통해 설치하였다.

그리고 추후 uwsgi를 시작시켜주는 설정파일을 복사하고 설정을 위해 supervisord.conf파일도 복사해줬다.

아래쪽에 보면 run.sh라는 파일에 실행 권한을 주는 것을 볼 수 있다.

이는 실행 권한을 주지 않으면 Permission Denied가 뜨기 때문이다.

그렇다면 sh파일은 왜 필요하냐라는 의문이 생길 수도 있다.


dockerfile에서 CMD를, 또는 docker-compose에서 command를 통해 ls또는 cat과 같이 실행되고 종료되는 명령어를 실행하면

해당 명령어 종료 즉시 이미지도 죽어버린다.

그래서 계속 터미널을 홀딩시키는 명령어를 입력하거나 위처럼 시작하고 죽어도 상관없는 명령어를 쉘스크립트 파일로 따로 빼서 사용하는게 일반적이다.


[/config/api.conf]

[program:uwsgi]
command=/usr/local/bin/uwsgi --ini /home/config/uwsgi.ini
autostart=true
autorestart=true
stderr_logfile=/home/supervisor_err.log
stdout_logfile=/home/supervisor_out.log
stopsignal=INT

supervisor를 위한 설정 부분이다.

command를 통해 uwsgi를 실행시킨다.

또한 플라스크 서버가 죽을 경우를 대비하여 autostart와 autorestart도 true로 설정했다.


[/config/nginx.conf]

server {
listen 80;
server_name localhost;

location / {
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;

proxy_pass http://web_project:5001;
}
}

80포트로 들어오면 web_project의 5001포트로 proxy_pass 시켜주는 부분이다.

여기서 기존에 우리는 web_project를 따로 정의해주지 않았는데 어떻게 사용할 수 있을까 라는 의문이 생길 수 있다.

이는 위에서 설명했던 것 처럼 docker-compose.yml에 nginx 부분에서 links에 web_project를 추가시켜줬기 때문이다.


[/config/run.sh]

#!/bin/bash
service supervisor stop
service supervisor start

supervisor를 재시작해줘야 우리가 작성한 설정파일이 동작하므로 위처럼 작성했다.


[/config/supervisord.conf]

; supervisor config file

[unix_http_server]
file=/var/run/supervisor.sock ; (the path to the socket file)
chmod=0700 ; sockef file mode (default 0700)

[supervisord]
nodaemon=true
logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log)
pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
childlogdir=/var/log/supervisor ; ('AUTO' child log dir, default $TEMP)

; the below section must remain in the config file for RPC
; (supervisorctl/web interface) to work, additional interfaces may be
; added by defining them in separate rpcinterface: sections
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///var/run/supervisor.sock ; use a unix:// URL for a unix socket

; The [include] section can just contain the "files" setting. This
; setting can list multiple files (separated by whitespace or
; newlines). It can also contain wildcards. The filenames are
; interpreted as relative to this file. Included files *cannot*
; include files themselves.

[include]
files = /etc/supervisor/conf.d/*.conf

이건 기존 supervisod.conf파일에 딱 한줄만 추가했다.

[supervisord] 바로 아래에 있는 nodaemon=true 이 부분이다.

위에도 얘기했듯이 실행되고 끝나는 명령어를 사용하면 실행이 끝나고 도커 컨테이너가 같이 죽어버리므로

nodaemon=true를 통해 background가 아닌 foreground로 돌아가게 함으로써 계속 홀딩할 수 있도록 만들었다.

이 부분은 필수적이므로 꼭 작성해줘야 한다.


[/config/uwsgi.ini]

[uwsgi]
chdir = /home
http-socket = :5001
chmod-socket = 777
logto = /home/web.log
process = 2
wsgi-file = manage.py
callable = app
# daemonize = /home/uwsgi.log
lazy-apps = true

uwsgi 설정 파일이다.

여기서 주석으로 표시해놓은 daemonize부분이 중요하다.

daemonize에 경로를 같이주게되면 데몬으로 돌아가게 되는데 supervisor와 같이 돌리면 계속 죽는 현상이 발생한다.

따라서 위 옵션은 없애줘야 제대로 같이 돈다.

또한 wsgi-file 옵션을 통해 실행시킬 파이썬 파일을 적어줬다.

플라스크 소스를 작성할 때 대부분 app = Flask(__name__) 형태로 작성할 것이다.

그런데 app 부분을 application으로 네이밍해줘야 제대로 작동한다.

하지만 나처럼 application이라고 쓰고 싶지 않다면 위처럼 callable을 통해 사용할 이름을 적어주면 된다.


마지막으로 docker-compose build, up을 순차적으로 실행하면 정상적으로 구동되는 모습을 확인할 수 있다.

반응형