How to setup a UWSGI Flask micro-service server

Daniel Rembiszewski
3 min readJun 9, 2021

When running flask locally, you get the following message:

WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.

That’s a great warning, since many people couldn’t tell that Flask isn’t suited for production. But there’s surprisingly few resources on how to actually set this up simply.

After wasting much too much timing on getting this to work, I finally had it, and decided to write this to help the next developer.

Sample Flask server

This is the code for our example server:

import logging
from logging.config import dictConfig

from flask import Flask, request

import json
from werkzeug.exceptions import HTTPException, BadRequest, InternalServerError


# This sets up the root logger to be like the Flask logger.
# See: https://flask.palletsprojects.com/en/1.1.x/logging/
dictConfig({
'version': 1,
'formatters': {'default': {
'format': '[%(asctime)s] p%(process)d-t%(thread)d '
'%(levelname)s@%(module)s:%(funcName)s:%(lineno)s> %(message)s'
,
}},
'handlers': {'wsgi': {
'class': 'logging.StreamHandler',
'stream': 'ext://flask.logging.wsgi_errors_stream',
'formatter': 'default'
}},
'root': {
'level': 'INFO',
'handlers': ['wsgi']
}
})


app = Flask(__name__)


@app.route('/calc-mean')
def calc_mean():
parsed_body = None
try:
if 'numbers' not in request.args:
raise BadRequest(f'No input received')
try:
numbers = [float(x) for x in request.args['numbers'].split(',')]
except Exception as e:
logging.info(f'Received bad request: {request.data}\n{repr(e)}')
raise BadRequest(f'Invalid request')

logging.info(f'Received mean request: {numbers}')
result = sum(numbers) / len(numbers)
logging.info(f'Returning result: {result}')
return str(result)
except Exception as e:
if isinstance(e, HTTPException):
raise
logging.exception(f'Fatal error calc-mean {parsed_body}:\n{repr(e)}')
raise InternalServerError(f'Unexpected error')


if __name__ == "__main__":
print('Starting Micro Service')
app.run(host="0.0.0.0")

As you can see, the micro-server calculates the mean of a given list of numbers.

There are two important lines here:

  1. Configure Logging

This is very important. Otherwise the uwsgi process won’t show any logs written, making debugging very painful.

2. Only use app.run() if __name__ == “__main__”

Otherwise many unneeded services will try to start, possibly causing them to fail.

UWSGI installation

UWSGI can be installed with

pip install uwsgi

However, this does not work for windows machines. Simple windows installation is not planned for uwsgi unfortunately, so you will need to work harder there.

You will also need to make sure PCRE is installed:

apt-get install libpcre3 libpcre3-dev

Otherwise UWSGI would go up, but won’t do any routing.

UWSGI configuration

We will use UWSGI ini configuration file to setup:

[uwsgi]
module = path.to.module.flask_server
callable = app
; env = OTHER_SERVICE_URL=http://localhost:5123

master = true
processes = 5
threads = 2

vacuum = true
http = :5000
  1. env allows you to pass environment variables to the workers serving requests.
  2. We setup a master configuration with 5 worker processes, each with 2 threads. These need to fine-tuned to your use case.
  3. Vaccum makes sure uwsgi cleans up after itself when it’s done.
  4. We set up the service on port 5000.

Now, from the project’s base directory (such that path.to.module leads to where flask_server.py is), we run the following command:

uwsgi --ini path/to/config.ini

That’s it. If you try accessing

http://localhost:5000/calc-mean?numbers=1,2,3

You should get a page with 2.0. All logs should be printed in the terminal where you ran the command.

Troubleshooting

If you get an error like this:

The process has forked and you cannot use this CoreFoundation functionality safely. 
You MUST exec(). Break on __THE_PROCESS_HAS_FORKED_AND_YOU_CANNOT_USE_THIS_
COREFOUNDATION_FUNCTIONALITY___YOU_MUST_EXEC__() to debug.

This is a bug in Mac OS. You cannot create new processes on any of the service workers.

Some libraries create new processes themselves (like AWS’ boto3 ), so you need to make sure to initialize them outside of the workers (e.g. in the bare python file).

Conclusion

This is a simple solution, but every issue along the way can take a long while to solve. I hope this saved you some time.

--

--

Daniel Rembiszewski

Software Engineer & Data Scientist, previously at Google, now at StuffThatWorks