Cloud Run is a service on Google Cloud Platform that supports serverless deployments of containers. You provide the container image and it runs it. In this lab, we will incrementally build a container and deploy it onto Cloud Run. The application we will build is an on-demand web proxy that could be used to bypass web filters.

The code for the application will be structured as shown below with <YourOdinID> replaced with yours (e.g. wuchang).

<YourOdinID>-proxy/
├── app.py
├── Dockerfile
├── requirements.txt
└── templates
    └── proxy.html

In Cloud Shell, first create the top-level and templates directory.

mkdir -p <YourOdinID>-proxy/templates

Create the template HTML file that implements the functionality of a proxy in retrieving the URL passed to it.

<h3>Proxy</h3>
<p>Enter URL to access by proxy:</p>
<input id=url_input></input>
<script>
var input = document.getElementById("url_input");
input.addEventListener("keyup", function(event) {
  if (event.keyCode === 13) {
    event.preventDefault();
    url = input.value
    window.location.href = "/<YourOdinID>-secret-firewall-bypass-proxy?url=" + url;
 }
}); 
</script>

Create the application using the code below:

from flask import Flask, redirect, request, url_for, render_template
import requests
import os

app = Flask(__name__)

@app.route('/')
def page():
  return "<html><body><h1>Hello World!</h1>There is nothing to see here so move along.</body></html>"

@app.route('/<YourOdinID>-secret-firewall-bypass-proxy')
def proxy():
  if 'url' not in request.args:
    return render_template('proxy.html')
  else:
    return requests.get(request.args['url']).text

if __name__ == '__main__':
   PORT = (os.getenv('PORT')) if os.getenv('PORT') else 8080 
   app.run(host='0.0.0.0', port=PORT, debug=True)

As the code shows, it has a trivial "Hello World" landing page for the '/' route, but also has a secret bypass route that looks for a URL argument from the incoming request (request.args['url']) and, if it exists, retrieves its contents using the Python requests package (requests.get). In addition, the code is set up to look for an environment variable PORT that determines what TCP to listen on for requests. If the variable doesn't exist, it listens on port 8080. Note that Cloud Run supplies the PORT environment variable when the container is deployed.

Create the Python requirements file to install the packages needed for the application. Flask performs the routing for the web application, requests performs the retrieval of the URL, and gunicorn runs the Python application as a stand-alone server.

flask
requests
gunicorn

The Dockerfile is shown below. Before beginning, change the MAINTAINER to specify your own name and e-mail address. As the file shows, the container we create

Finally, the last line specifies the command to run the Python/Flask app using the PORT environment variable to specify the port number to use. app:app instructs gunicorn to look for the entrypoint in app.py and that within that file, an object called app will handle the incoming requests.

FROM python:3.7-slim 
MAINTAINER You "<YourOdinID>@pdx.edu"
COPY . /app
WORKDIR /app
RUN pip install -r requirements.txt 
CMD gunicorn --bind :$PORT --workers 1 --threads 8 app:app

We will first build the container locally to validate that it works. Change into the directory containing your Dockerfile. Then run the docker build command to build the container image.

docker build -t <YourOdinID>-proxy-image ./

Run the container image in Cloud Shell, passing in the port you wish the container to use via the PORT environment variable within the docker run command to specify 8000. In addition, the command uses the -p flag to map the Cloud Shell port 8000 to the container port 8000 and uses the -di flag to run the container in a detached mode in the background. We name the container instance running proxy.

docker run --env PORT=8000 -p 8000:8000 --name=proxy -di <YourOdinID>-proxy-image

Within the Cloud Shell UI, click on the Web Preview icon and preview the application. Change port to 8000 if necessary:

Show the container and application has been brought up successfully. Then, stop and remove the container instance.

docker stop proxy
docker rm proxy

Finally, delete the container image

docker rmi <YourOdinID>-proxy-image

We have been building container images locally via docker build and pushing the resulting image to a public container registry (e.g. Docker Hub) via docker push. In modern DevOps deployments, it is often the case that developers require an automated way to build artifacts in a CI/CD pipeline and a container registry that is private to the project that provides high-speed delivery of container images.

GCP provides two services to support this:

Cloud Run prefers to run images from Container Registry. Building the image in Cloud Build and storing it in gcr.io can be done with a single command. Run the command in Cloud Shell in the same directory as the Dockerfile (substituting your name) to build the container.

gcloud builds submit --tag gcr.io/${GOOGLE_CLOUD_PROJECT}/<YourOdinID>-proxy

When finished, list the container images in the registry.

gcloud container images list

Then, visit Container Registry in the web console and click on the image you have created.

Show the size of the container in the UI and take a screenshot of it for your lab notebook:

Launch the container on Cloud Run from Cloud Shell using the command below (substituting your image name).

gcloud run deploy <YourOdinID>-proxy \
  --image gcr.io/${GOOGLE_CLOUD_PROJECT}/<YourOdinID>-proxy

Choose:

Note that we could have passed the command below to specify it initially.

  --platform=managed --region=us-west1 --allow-unauthenticated

You should get back a URL that has your container running on it:

Click the link that is returned in Cloud Shell. Note that because the container must be started up on-demand for the first time, there will be a slight delay on first access to it. See the "Hello World" message on the default route.

Although Cloud Run has a generous free tier, it is good practice to remove resources that are not being used. Go back to the Cloud Run landing page and delete the service you've created:

Visit Container Registry and delete the container image.

Alternatively, you can also delete these resources in Cloud Shell via:

gcloud run services delete <YourOdinID>-proxy

gcloud container images delete gcr.io/${GOOGLE_CLOUD_PROJECT}/<YourOdinID>-proxy