Authorised users only

How to Deploy a Flask App on Cloud Run with Cloud Endpoints

Takashi Nakamura, PhD
FullStackAI
Published in
9 min readSep 9, 2020

--

Recently I had the opportunity to learn how to host a Flask application on Google Cloud Platform (GCP) using Cloud Run and Cloud Endpoints. Though official documentation is provided, it took me some time to understand and implement the various components correctly. In this article, I am going to show you how to deploy a Flask app on Cloud Run with authentication. Let’s deploy a web application on GCP with an authentication process.

Today’s application. A Flask application on Cloud Run, which is protected by Cloud Endpoint.

Today’s application

We are interested in deploying a web application:

  • With Flask;
  • Running on Cloud Run;
  • Using a production ready server (e.g. gunicorn);
  • Protected by Cloud Endpoints (requests are checked by the endpoint, i.e. not everyone can access the application).

Step 1: Create a Flask application

A simple Flask application on Cloud Run

We are interested in a Flask application, with some simple methods. The code below defines three endpoints:

  1. GET /hello
  2. GET /hello/<my_name>
  3. POST /hello_body

Step 2: Deploy the Flask application on Cloud Run

We are now interested in building a Docker image for the Flask application, since applications on Cloud Run are deployed from Docker images. For this step, the official documentation by GCP can be found from here. From here on, we are going to use some linux commands on the terminal.

2.1 Set up GCP account, project, etc.

First of all, we install Cloud SDK, which enables us to connect to GCP via a terminal session on our local machine. After the install, we shall make sure the relevant account and project name are set as default.

# Setup GCP account
$ gcloud config set account $MY_EMAIL_ADDRESS
$ gcloud auth login $MY_EMAIL_ADDRESS
$ gcloud config set project $MY_PROJECT_ID

2.2 Build a Docker image of the Flask application

Now, we are going to build a Docker image of the Flask application. In this example, we need Dockerfile andrequirements.txt files, which are saved in the same folder as main_v1.py:

flaskapp_cr
├── main_v1.py
├── requirements.txt
└── Dockerfile

We are going to use an instantiated Flask application, which is defined in main_v1.py. Therefore, the entrypoint for gunicorn should be defined as main_v1:app . The Dockerfile now reads:

# Use Python37
FROM python:3.7
# Copy requirements.txt to the docker image and install packages
COPY requirements.txt /
RUN pip install -r requirements.txt
# Set the WORKDIR to be the folder
COPY . /app
# Expose port 5000
EXPOSE 5000
ENV PORT 5000
WORKDIR /app# Use gunicorn as the entrypoint
CMD exec gunicorn --bind :$PORT main_v1:app --workers 1 --threads 1 --timeout 60

We shall build the Docker image and push it to Container Registry on GCP. The service name is defined as flaskapp_cr.

# Build Docker on your local machine
$ docker build -t gcr.io/$MY_PROJECT_ID/flaskapp_cr:v1 -f Dockerfile .
# Push the Docker image to Container Registry
$ docker push gcr.io/$MY_PROJECT_ID/flaskapp_cr:v1

If successful, we are able to see the Docker image on Container Registry.

The docker image is now saved in Container Registry

2.3 Select the Docker image from Container Registry and deploy on Cloud Run

From Container Registry, we are able to deploy flaskapp_cr Docker image on Cloud Run via the gcloud command. We shall name the instance as flaskapp-cr-v1.

# Deploy a Docker image on Cloud Run
$ gcloud run deploy flaskapp-cr-v1 \
--image gcr.io/$MY_PROJECT_ID/flaskapp_cr:v1 \
--region us-east1 \
--platform managed \
--memory 128Mi

Once the instance flaskapp-cr-v1 is created, we are able to see the logs on the terminal:

Service [flaskapp-cr-v1] revision [flaskapp-cr-v1-00001-hey] has been deployed and is serving 100 percent of traffic at https://flaskapp-cr-v1-yerjarnciq-ue.a.run.app

We are now able to check the Cloud Run instance from the Console.

This instance flaskapp-cr-v1 can be accessed by anyone since we allowed “unauthenticated access” when we deployed it. We will add authentication later.

Note: Allow unauthenticated. Everyone can access this instance

2.4 Check the Flask application on Cloud Run

Let’s create HTTPS requests to the Cloud Run instance. Since we have three Flask methods, we shall send three requests to flaskapp-cr-v1.

The response can be found from the log on flaskapp-cr-v1 instance on Cloud Run.

Excellent. We have deployed a Flask application on Cloud Run.

Step 3: Add authentication endpoint on Cloud Run

So far, we are able to deploy a Flask application on Cloud Run. However, the problem is that everyone can send requests to the Cloud Run instance. If we leave the Cloud Run instance running on https://flaskapp-cr-v1-yerjarnciq-ue.a.run.app, everyone on the planet can access it.

We shall add authentication to the Flask application on Cloud Run. There are numerous ways to do this, but we shall add a Cloud Endpoint, which protects the Cloud Run instance. The official documentation by GCP can be found here.

The ideas we are going to implement are below:

  • A user creates an HTTPS request to Cloud Endpoints, instead of to Cloud Run directly;
  • We are not allowed to make requests to the Flask application on Cloud Run directly;
  • From the user side, requests must contain an authentication header. The header has a JSON Web Token (JWT). The JWT is created based on a Service account on GCP;
  • Deploy a Cloud Endpoint on Cloud Run, which checks whether the HTTPS request from a user is valid (i.e. check the provided JWT is valid);
  • The Cloud Endpoint configuration is set by a .yaml file.
[Reminder] Today’s application. A Flask application on Cloud Run, which is protected by Cloud Endpoint.

3.1 Set up a Service account and create a JWT on local machine

We are interested in introducing an authentication process to the Flask application on Cloud Run. For GCP applications, we are able to create an authentication token, which shows that the person who has the token is the right person to access the instance. In this example, we shall consider using a JSON Web Token (JWT). In this post, we shall skip the details of a JWT.

There are multiple ways to create a JWT (e.g. using Firebase account), but we are going to have a look at a method based on using a Service account. The official document by GCP can be found from here.

On the GCP console, we can go to IAM & Admin > Service Accounts, where we are able to create a Service account and to create a credential key of the corresponding Service account as a .json file.

Here is a sample code to create a JWT based on a Service account.

Using this script, we can provide relevant values to create a JWT with a Service account credential file, which is saved on our local machine. Note, both MY_SERVICE_ACCOUNT_ADDRESS and AUD_URL are used later to configure the Cloud Endpoint with .yaml file.

We are able to see which information is saved in thejwt using developer tools (e.g. https://jwt.io/). The jwt will be sent as a part of request header to the Flask application on Cloud Run.

3.2 Update Cloud Run

We shall setup the Cloud Run instance again, but we are not allowed to make any HTTPS requests to the Cloud Run instance directly this time. Let’s create a new instance called flaskapp-cr-v2. The same Docker image on Container Registry is used.

# Deploy a Docker image on Cloud Run. Not allowed unauthenticated access
$ gcloud run deploy flaskapp-cr-v2 \
--image gcr.io/$MY_PROJECT_ID/flaskapp_cr:v1 \
--region us-east1 \
--platform managed \
--memory 128Mi \
--no-allow-unauthenticated

On the Console, we can see a new instance on Cloud Run; however, the new instance flaskapp-cr-v2 does not allow unauthenticated access.

Therefore, if we send HTTPS requests to the instance, the status is now 403 Forbidden.

3.3 Create a Cloud Endpoint on Cloud Run

Now, it is time to create a Cloud Endpoint. The official documentation by GCP can be found here. First of all, we deploy the Extensible Service Proxy V2 Beta (ESPv2 Beta) as an API gateway (which is provided on Container Registry as default). The Cloud Endpoint is deployed on Cloud Run as an instance called flaskapp-cr-v2-gateway.

$ gcloud run deploy flaskapp-cr-v2-gateway \
--image "gcr.io/endpoints-release/endpoints-runtime-serverless:2.17.0" \
--allow-unauthenticated \
--platform managed \
--project $MY_PROJECT_ID \
--region us-east1

Once the gateway instance (i.e. Cloud Endpoint) is created on Cloud Run, we need to find two parameters from the instances.

  1. URL of flaskapp-cr-v2
  2. URL of flaskapp-cr-v2-gateway
# URL for Flask application on Cloud Run
APPLICATION_URL = "https://flaskapp-cr-v2-yerjarnciq-ue.a.run.app"
# URL for Cloud Endpoint on Cloud Run
ENDPOINT_HOST = "flaskapp-cr-v2-gateway-yerjarnciq-ue.a.run.app"
## Also need to have parameters below
$MY_SERVICE_ACCOUNT_ADDRESS
$AUD_URL

Once these two parameters are found, we can make a .yaml file, which configures the connection between a) the instance of the Flask application on Cloud Run and b) the cloud endpoint on Cloud Run. In this .yaml file, we also configure the Service account, Flask method, etc.

Once the .yaml file is created, deploy the file using gcloud command.

$ gcloud endpoints services deploy cloud_endpoint_config.yaml \
--project=$MY_PROJECT_ID

If the configuration is deployed, we will get CONFIG_ID, from logs on the terminal.

Service Configuration [2020-09-09r0] uploaded for service [flaskapp-cr-v2-gateway-yerjarnciq-ue.a.run.app]

After the configuration is updated, we shall build a new ESPv2 image, using the gcloud_build_image script. The code can be downloaded from here.

# Configuration ID is now given
CONFIG_ID = 2020-09-09r0
$ chmod +x gcloud_build_image
$ ./gcloud_build_image -s $ENDPOINT_HOST -c $CONFIG_ID -p $MY_PROJECT_ID

After a new ESPv2 Docker image is created, we re-deploy the image on Cloud Run as a Cloud Endpoint.

# Re-deploy the gateway with the new docker image
$ gcloud run deploy flaskapp-cr-v2-gateway \
--image gcr.io/$MY_PROJECT_ID/endpoints-runtime-serverless:2.17.0-$ENDPOINT_HOST-$CONFIG_ID \
--allow-unauthenticated \
--platform managed \
--project $MY_PROJECT_ID \
--region us-east1

That is all for configuring Cloud Endpoint. Now, we can check the flaskapp-cr-v2-gateway instance on Cloud Run, which protects theflaskapp-cr-v2 instance.

Step 4: Check the Cloud Endpoint and the Flask application

It is time to check the application. We are not able to access the Flask application on Cloud Run directly. If we go to the flaskapp-cr-v2-gateway url, we can see an error message, related to jwt.

Endpoint on a browser. Access error due to missing a JWT

We shall make HTTPS requests to the Cloud Endpoint. The sample code is below. For each Flask method (three methods in total), we send two requests as

  1. Without header
  2. With header {"Authorization": f"Bearer {jwt}"}

The jwt is created by a Service account; the same Service account is hardcoded in the .yaml file for Cloud Endpoint configuration.

As we expect, the first three requests are blocked by the Cloud Endpoint as

{"code":401,"message":"Jwt is missing"}

The last three requests are successfully made to the Flask application.

Hello
Hello from url, Foo bar
Hello from body, Foo bar

The logs on Cloud Endpoint is below. The first three requests have Error 401, whereas the last three requests are Status 200.

The first three requests have Error 401, whereas the last three requests are Status 200

On the Flask application instance on Cloud Run, only three requests are logged with Status 200.

Three requests are made to the Flask instance

Conclusion

The process of .yaml configuration for a Cloud Endpoint was confusing for me at first. Hope this article mitigates the technical difficulties and helps someone who is interested in deploying a web application on Cloud Run.

--

--