GCP: deploying a Python WSGI Gunicorn app on Cloud Run

Flask is a suitable web server during development, but if you are going to deploy in a production environment, a Python WSGI server such as Gunicorn should be used.

This also applies to Python Flask apps deployed to GCP Cloud Run. Gunicorn is necessary to tune the worker and thread count of each instance to your traffic shape and is more scalable and performant.

In this article I will show you how a Python web application can be developed and tested locally using either Flask or Gunicorn, and then deployed to GCP Cloud Run as a Docker image running Gunicorn.

Download project and create Python virtual environment

First download my github project and setup the isolated Python virtual environment with the required module dependencies.

# make sure essential OS packages are installed
sudo apt-get install software-properties-common python3 python3-dev python3-pip python3-venv make curl git -y

# download project
git clone https://github.com/fabianlee/gcp-cloudrun-python3-gunicorn.git
cd gcp-cloudrun-python3-gunicorn

# create and load virtual env
python3 -m venv .
source bin/activate
pip install -r requirements.txt

Test locally using Flask

If you are in the code-test-debug phase, Flask provides debug and reload abilities that create an efficient workflow for developers.

# run Python Flask app locally, open local browser to http://localhost:8080
python3 -m hellomodule.app run --port 8080

Test locally using Gunicorn

To test locally using the Python WSGI Gunicorn app server which will emulate production more closely, run the command below.

# run Gunicorn locally, open local browser to http://localhost:8080
gunicorn --config gunicorn.conf.py --log-config=gunicorn-logging.conf hellomodule.app:app

Deploy to GCP Cloud Run as Docker image using Gunicorn

Notice there is a Dockerfile located in the base directory of the project that invokes same pip install and gunicorn invocation done manually above.

$ cat Dockerfile

...

RUN set -ex \
  && pip install --no-cache-dir -r requirements.txt
...

ENTRYPOINT [ "/usr/local/bin/gunicorn", "--config", "gunicorn.conf.py", "--log-config", "gunicorn-logging.conf", "hellomodule.app:app" ]

‘gcloud run deploy’ has the ability to build a Docker image using this Dockerfile and then deploy the image as a GCP Cloud Run service.

# enable GCP project level services
gcloud services enable cloudbuild.googleapis.com artifactregistry.googleapis.com run.googleapis.com

# setup variables
app_name="gcp-cloudrun-python3-gunicorn"
region=$(gcloud config get compute/region)
project_id=$(gcloud config get project)

# deploy to GCP Cloud Run
gcloud run deploy $app_name --source=. --region=$region --ingress=all --allow-unauthenticated --execution-environment=gen2 --no-use-http2 --quiet

# show details of Cloud Run deployment
gcloud run services list
gcloud run services describe $app_name --region=$region

Validate GCP Cloud Run deployment

A simple curl test can be done to validate the deployment.

# test pull of content
run_url=$(gcloud run services describe $app_name --region=$region --format='value(status.url)')
echo "CloudRun app at: $run_url"
curl $run_url

And if you want to put load on the application, ‘hey‘ is a simple load tool available across multiple platforms.

# simple load tool
sudo apt install hey -y

# 200 total requests, 20 concurrent at a time
hey -n 200 -c 20 $run_url

This kind of load testing would serve as the basis for tuning the workers, threads, logging, and other configuration to your specific needs and traffic shape.

 

REFERENCES

fabianlee github project, gcp-cloudrun-python3-gunicorn

Gunicorn site

gcloud run deploy

google codelabs, Cloud Run and Python/Gunicorn example

google ref, QuickStart for Python Flask/Gunicorn on Cloud Run

google ref, Optimize Python apps and gunicorn for Cloud Run

google ref, General optimization tips for Cloud Run

gunicorn docs, worker-class design document