Photo by Raghav Bhasin on Unsplash
Gunicorn & Flask
A quick setup demo and introduction to multithreading
This blog will demo running Flask with Gunicorn and how to enable multiple workers.
First, let's start with this simple Flask app called app.py
: which just has two endpoints:
/healthcheck
endpoint which returns"ok"
./sleep
endpoint which sleeps for 10 seconds and then returns"ok"
.
from flask import Flask, jsonify
import sys
import time
app = Flask(__name__)
@app.route("/healthcheck")
def healthcheck():
return jsonify({"status": "ok"})
@app.route("/sleep")
def sleep():
time.sleep(10)
return jsonify({"status": "ok"})
if __name__ == "__main__":
app.run(host="0.0.0.0", debug=True, port=8888)
I run this app via the command line by running:
$ python3 app.py
And to hit the /healthcheck
endpoint, I just run
$ curl localhost:8888/healthcheck
and receive the output
{
"status": "ok"
}
Great! If you look at your logs you will see this:
127.0.0.1 - - [21/May/2022 18:41:09] "GET /healthcheck HTTP/1.1" 200 -
Now we can add Gunicorn and run it with Flask by adding the following Gunicorn config file, which I am calling gunicorn_config.py
, to our repo.
import multiprocessing
bind = "127.0.0.1:8000"
workers = 1
worker_class = "sync"
errorlog = "-"
loglevel = "info"
accesslog = "-"
The file basically says I'll be running my application on port 8000, with 1 worker running synchronously plus some commands to display logs. To run this I just run $ gunicorn --config gunicorn_config.py app:app
telling gunicorn the path to my config file and the app file to run. When I run this, I first get the following log lines:
[2022-05-21 18:55:13 -0500] [41471] [INFO] Starting gunicorn 20.1.0
[2022-05-21 18:55:13 -0500] [41471] [INFO] Listening at: http://127.0.0.1:8000 (41471)
[2022-05-21 18:55:13 -0500] [41471] [INFO] Using worker: sync
[2022-05-21 18:55:13 -0500] [41472] [INFO] Booting worker with pid: 41472
In the logs, there is the single worker with process id of 41472. Next, I am going to run two curls, near simultaneously
curl localhost:8000/sleep
- (in a new terminal window)
curl localhost:8000/healthcheck
.
The logs show:
127.0.0.1 - - [21/May/2022:18:57:49 -0500] "GET /sleep HTTP/1.1" 200 16 "-" "curl/7.77.0"
127.0.0.1 - - [21/May/2022:18:57:49 -0500] "GET /healthcheck HTTP/1.1" 200 16 "-" "curl/7.77.0"
`
Because we only had one worker, we had to wait 10 seconds for /sleep
to complete before the /healthcheck
endpoint could run.
But if I increase the number of workers to 2 and run the curls in the exact same order, I get:
[2022-05-21 19:00:02 -0500] [41588] [INFO] Starting gunicorn 20.1.0
[2022-05-21 19:00:02 -0500] [41588] [INFO] Listening at: http://127.0.0.1:8000 (41588)
[2022-05-21 19:00:02 -0500] [41588] [INFO] Using worker: sync
[2022-05-21 19:00:02 -0500] [41589] [INFO] Booting worker with pid: 41589
[2022-05-21 19:00:02 -0500] [41590] [INFO] Booting worker with pid: 41590
127.0.0.1 - - [21/May/2022:19:00:06 -0500] "GET /healthcheck HTTP/1.1" 200 16 "-" "curl/7.77.0"
127.0.0.1 - - [21/May/2022:19:00:15 -0500] "GET /sleep HTTP/1.1" 200 16 "-" "curl/7.77.0"
`
Firstly, we have two workers with the process ids 41590 and 41589 . Secondly, /healthcheck
returned first and then the /sleep
returned , even though that was not the order I ran them in. Also you can see that/sleep
returned nearly 10 seconds after our /healthcheck
did!
Now you can set up Gunicorn with Flask with multiple workers!