22/10/2018

Flask gunicorn nginx docker

Best practice of dockerizing flask gunicorn nginx.

PS. If you want to see the sample code. Click this link GitHub Repository

Concept

Starting with the installation of flask application, stack it step by step with Bottom Up.

PS. This following descriptions may not be accurate and in such cases pleas read and understand the code in the repository

main app versions

  • python/3.6.4
  • nginx/1.15.5

Flask Web Application

$ python -m venv venv
$ . venv/bin/activate
$ python -V
$ pip install --upgrade pip
$ pip install Flask
$ pip list
# main.py
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return "HELLO, WORLD!"


@app.route('/ping')
def ping():
    return "PONG"
$ FLASK_APP=main.py flask run
$ curl 127.0.0.1:5000/ping
PONG
$ curl 127.0.0.1:5000/
HELLO, WORLD!

debug or development mode

  • it activates the debugger
  • it activates the automatic reloader
  • it enables the debug mode on the Flask application.
$ export FLASK_ENV=development
$ export FLASK_DEBUG=1
$ export FLASK_APP=main.py
$ flask run

PS. This makes it a major security risk and therefore it must never be used on production machines.

performance test for flaks standalone

$ flask run
$ ab -n 32 -c 16 127.0.0.1:5000/sleep/10
This is ApacheBench, Version 2.3 <$Revision: 1807734 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient).....done


Server Software:        Werkzeug/0.14.1
Server Hostname:        127.0.0.1
Server Port:            5000

Document Path:          /sleep/10
Document Length:        19 bytes

Concurrency Level:      16
Time taken for tests:   20.031 seconds
Complete requests:      32
Failed requests:        0
Total transferred:      5536 bytes
HTML transferred:       608 bytes
Requests per second:    1.60 [#/sec] (mean)
Time per request:       10015.349 [ms] (mean)
Time per request:       625.959 [ms] (mean, across all concurrent requests)
Transfer rate:          0.27 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.2      0       1
Processing: 10006 10012   2.9  10012   10017
Waiting:    10005 10011   2.8  10011   10017
Total:      10006 10012   2.9  10012   10017

Percentage of the requests served within a certain time (ms)
  50%  10012
  66%  10013
  75%  10015
  80%  10015
  90%  10016
  95%  10017
  98%  10017
  99%  10017
 100%  10017 (longest request)

gunicorn

$ pip install gunicron
$ gunicorn -w 3 --threads 3 wsgi:app -b 0.0.0.0:5000
[2018-10-09 19:30:57 +0900] [79365] [INFO] Starting gunicorn 19.9.0
[2018-10-09 19:30:57 +0900] [79365] [INFO] Listening at: http://0.0.0.0:5000 (79365)
[2018-10-09 19:30:57 +0900] [79365] [INFO] Using worker: threads
[2018-10-09 19:30:57 +0900] [79368] [INFO] Booting worker with pid: 79368
[2018-10-09 19:30:57 +0900] [79369] [INFO] Booting worker with pid: 79369
[2018-10-09 19:30:57 +0900] [79370] [INFO] Booting worker with pid: 79370

main configuration

  • bind
  • workers
  • worker_class
  • threads
    • This setting only affects the Gthread worker type.
  • access_logfile
    • - means log to stdout
  • access_logformat
    • default: %(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"
  • error_logfile
    • - means log to stderr
# TCP SOCKET BINDING
$ gunicorn -w 3 --access-logfile - -k gevent wsgi:app -b 0.0.0.0:5000
...
# UNIX SOCKET BINDING
$ gunicorn -w 3 --access-logfile - -k gevent wsgi:app -b unix:/tmp/gunicorn.sock
...

# Check The Socket
$ echo -e "GET /ping HTTP/1.0\r\n\r\n" | nc -q 2 -U /tmp/gunicorn.sock
PONG
$ gunicorn --config gunicorn_config.py wsgi:app

Nginx

mac os var, etc path

/usr/local/etc/nginx/
/usr/local/var/log/nginx

gunicorn-nginx.conf

/usr/local/etc/nginx/servers/gunicorn-nginx.conf

upstream gunicorn-app {
    server unix:/tmp/gunicorn.sock fail_timeout=0;
}


server {
    listen 80;
    server_name 0.0.0.0;

    client_max_body_size 5M;

    access_log /usr/local/var/log/nginx/access.log combined;
    error_log /usr/local/var/log/nginx/error_log.log warn;

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;

        proxy_pass http://gunicorn-app;
    }
}
sudo nginx -c [config_path]
sudo nginx -s stop

Docker

When I dockerize, first setup the tools and environment necessary for dockerizing image. Then, enter console of the container, check the configuration, and run each application to verify normal operation. Afrter verifcation is completed, write CMD or ENTRYPOINT COMMAND.

Dockerfile
FROM python:3.6.6-jessie

MAINTAINER cgex

ENV SRVHOME=/srv/app

WORKDIR $SRVHOME

COPY ./ $SRVHOME

RUN apt-get update &amp;&amp; apt-get install -y \
    nginx \
    supervisor

COPY ./nginx/nginx.conf /etc/nginx/
COPY ./nginx/gunicorn-app.conf /etc/nginx/conf.d/

COPY ./gunicorn_config.py /etc/gunicorn/

COPY supervisord.conf /etc/supervisor/

RUN pip install --upgrade pip
RUN pip install -r requirements.txt

EXPOSE 80
EXPOSE 8080

# for receving stream of log 
RUN ln -sf /dev/stdout /var/log/nginx/access.log \
    &amp;&amp; ln -sf /dev/stderr /var/log/nginx/error.log

CMD ["/usr/bin/supervisord"]

REFERENCES

gunicorn vs uwsgi flask-concurrency-test


Previous post
WEB Framework Check Template 이 글에서 말하는 웹 프레임웍은 REST API 중심의 웹 프레임웍을 말한다. 해당 글의 작성 목적은 웹 프레임웍을 선택 혹은 구축하는데 있어 고려해볼사항들에 대해서 포괄적으로 언급한다. 파이썬 진형의 대표적인 웹 프레임웍 장고에서 많은 개념을 참고했다.
Next post
Install Parity to Ubuntu systemd unit file - 데이터 경로를 백업볼륨 경로로 선정. 자세한 설정은 해당 링크 참고. Configuring-Parity-Ethereum parity config file - parity 를 실행하기 위한 기본적인 셋업은 끝났으며,