Python gunicorn

From wikinotes

Gunicorn is a WSGI Servers.

Documentation

official docs https://gunicorn.org/#docs
flask+gunicorn logging https://medium.com/@trstringer/logging-flask-and-gunicorn-the-manageable-way-2e6f0b8beb2f

Hosting Flask APP

Flask App

example flask app

For the sake of completion, we'll define a simple REST API using flask. This will be the application we're hosting using gunicorn.


python app

import flask

app = flask.Flask(__name__)

@app.route('api/test', methods=['GET])
def route_api_test():
    return 'connection successful!'

example interaction

curl -X GET http://yoururl/api/test
>>> connection successful!

Virtualenv for Flask App

create virtualenv


Create a virtualenv to host your program, and install your program into it.

python -m virtualenv /home/services/<yourprogram>
cd /src/<yourprogram>
/home/services/<yourprogram>/venv/bin/python setup.py install

app entrypoint

Gunicorn needs to be pointed the module/variable-name of your flask app. This step is unecessary unless you need to modify the environment before the app is run.

from yourprogram.restapi import app

System Service

You probably want to have the startup managed by your init system. Here's a systemd service, adapt to your initsystem.

systemd

/etc/systemd/system/yourprogram.service


[Unit]
Description=Your Program, it does...
Requires=network-online.target

[Service]
Type=simple
User=yourprogramuser
Group=www-data
Environment='SERVER_SOFTWARE=gunicorn'  # set so can tell if gunicorn is running within app

ExecStart=/home/services/yourprogram/venv/bin/gunicorn                \
    --bind unix:/home/services/tma-restapi/www-data/yourprogram.sock  \     # may also use addr ex:  '127.0.0.1:8000'
    --workers 4                                                       \
    --umask 007                                                       \
    --certfile /etc/letsencrypt/live/yoursite/cert.pem \
    --keyfile  /etc/letsencrypt/live/yoursite/privkey.pem \
    yourmodule:restapi.app    # module:rest-app-import-within-module

[Install]
WantedBy=multi-user.target

WebServer

nginx

/etc/nginx/nginx.conf


user www-data www-data;

events {
    worker_connections 1024;
}

http {

    # ==============
    # Main Webserver
    # ==============

    # w3m  http://localhost
    server {
        listen       80;       # ipv4
        listen       [::]:80;  # ipv6

        # proxy to other webservers
        location /api/ {
            proxy_pass http://localhost:81/;      ## <-- points to your gunicorn
            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;
        }
    }

    # host your gunicorn on port 81
    server {
        listen             81;
        server_name        api.localhost;

        location / {
            include proxy_params;
            # alternatively: proxy_pass http://127.0.0.1:9000/api
            proxy_pass http://unix:/home/services/tma-restapi/www-data/tma-restapi.sock;
        }
    }
}

Configuration

gunicorn can be configured globally, using a python-file, or using commandline params.


/path/to/config.py

# attributes in this python file are read by gunicorn
bind = '127.0.0.1:5000'
workers = 4
umask = '007'
log_level = 'info'
gunicorn --config /path/to/config.py

Gotchas

Logging

gunicorn allows you to configure loggin on the commandline --log-level info. If you would like to reuse this loglevel throughout your application, you can check/set it.

import logging

gunicorn_logger = logging.getLogger('gunicorn.error')
logging.root.setLevel(gunicorn_logger.level)

Detecting Gunicorn

Your app may run differently under gunicorn than it does when using the regular flask.Flask.run() method.

Unfortunately I have not found a good way of detecting the environment. The best I have come up with is in some environments, gunicorn sets the environment variable SERVER_SOFTWARE. I check for this in my pythonfile, and explicitly set it within the service that runs the flask app.

is_hosted_by_gunicorn = os.environ.get('SERVER_SOFTWARE', '').startswith('gunicorn')


WARNING:

this does not always work -- but you can easily set it within the service-file that runs gunicorn