pallets / flask

The Python micro framework for building web applications.
https://flask.palletsprojects.com
BSD 3-Clause "New" or "Revised" License
68.19k stars 16.24k forks source link

FLASK_RUN_FROM_CLI breaks prometheus_flask_exporter #4616

Closed raxod502 closed 2 years ago

raxod502 commented 2 years ago

Hi, I originally filed an issue at https://github.com/rycus86/prometheus_flask_exporter/issues/135 but determined that it was impossible to resolve the problem downstream, and changes are required in Flask to support the use case of prometheus_flask_exporter.

The problem is described in the issue report I linked above, but I will also summarize it here for convenience. Basically, when you invoke flask run, then Flask sets FLASK_RUN_FROM_CLI as an environment variable. Then, in app.run(), if that environment variable is set, Flask skips starting the app, and prints the following warning:

Warning: Silently ignoring app.run() because the application is run from the flask command line executable. Consider putting app.run() behind an if __name__ == "__main__" guard to silence this warning.

This is all fine and good when app.run() is being invoked from the top level in the main module, and it was not intended to launch a duplicate app. However, sometimes the user actually does want to invoke two separate Flask applications: for example, to expose application metrics on a different port from web traffic, which is generally recommended as a security best practice.

Specifically, prometheus_flask_exporter offers an option to expose metrics on a separate port by starting a separate Flask app on a separate port and thread. However, Flask's behavior around FLASK_RUN_FROM_CLI breaks this functionality, since if the web traffic app is launched using flask run, then the environment variable will prevent prometheus_flask_exporter from starting the metrics app.

Code in the user application looks like this:

metrics = prometheus_flask_exporter.PrometheusMetrics(app)
metrics.start_http_server(8081, host=config.METRICS_HOST)

The metrics.start_http_server() method instantiates a separate Flask application with the provided configuration, and invokes app.run(). If the main Flask application was starting using flask run, then the following warning is printed, and no metrics are served:

website_1   |  * Serving Flask app 'src.app:app' (lazy loading)
website_1   |  * Environment: development
website_1   |  * Debug mode: on
website_1   | /usr/local/lib/python3.8/dist-packages/prometheus_flask_exporter/__init__.py:322: Warning: Silently ignoring app.run() because the application is run from the flask command line executable. Consider putting app.run() behind an if __name__ == "__main__" guard to silence this warning.

Workarounds

Note that this problem only affects development since the Flask builtin server is not used in production. As such, there are a number of workarounds to the problem at present, but all of them have significant drawbacks.

Suggested fix

Add an optional keyword argument to app.run that allows bypassing the check on FLASK_RUN_FROM_CLI. This can be provided by libraries such as prometheus_flask_exporter that invoke Flask as a server. Such a change would solve the use case described above, while preserving all the benefits of the check happening by default.

I would be happy to contribute such a fix, along with documentation and tests as desired, if the maintainers of Flask agree that it is an appropriate path forward. If you have other suggestions for a better way to resolve the issue with prometheus_flask_exporter, please let me know and I would be happy to discuss.

davidism commented 2 years ago

I don't know Prometheus, so I can't evaluate any of what you've written in terms of use cases that Flask's development server is intended to support. Clearly, it's not intended to be used this way, so I'm not clear this is not something Prometheus should address.

davidism commented 2 years ago

It sounds like this metrics server is a production application, not part of the application under development, in which case it should not be run using the development server, even locally.

raxod502 commented 2 years ago

Can you suggest any alternatives to Flask that the prometheus_flask_exporter project could use to serve local web requests, if the Flask project does not desire to support running more than one Flask app in the same process?

davidism commented 2 years ago

Flask project does not desire to support running more than one Flask app in the same process

That's not what I said.

Flask is a WSGI application framework, not a WSGI server. You wouldn't pick an alternative to Flask, unless you wanted to rewrite that application.

What you want is a production WSGI server, as opposed to the development server. Gunicorn, Waitress, uWSGI, mod_wsgi, Eventlet, and Gevent are the most well known.

raxod502 commented 2 years ago

Understood. I'll work with the prometheus_flask_exporter project to see if they can migrate to one of those solutions instead of using the Flask development server.