Dev to Prod in Three Easy Steps with Cloud Run

Why is it so hard to manage applications?

One big reason is that developers often have to be part-time system administrators. Consider this (partial) list of concerns in order to develop, deploy, and manage a modern production-grade web application :

4d018476b4a73b47.png

I don't know about you, but these are all things I don't want to worry about! What I really want to think about is my application logic:

6dfd143d20e5548b.png

This, in a nutshell, is what Cloud Run is all about – giving you the ability to focus on your app, and leaving all the administration and maintenance to someone else, namely Google, who have invested millions of hours into refining and perfecting their skills in this domain.

In addition to the administrative challenges mentioned above, you also have to deal with:

  • Dependencies - The environment in which your app runs should, wherever possible, precisely match the environment in which it was tested. This can encompass several dimensions, including operating system, support libraries, language interpreter or compiler, hardware configuration, and many other factors.
  • Distribution - Moving from a local incarnation of an app to one that is shared broadly on the internet often requires a change of runtime environment, a quantum leap in complexity, and a steep learning curve.

Cloud Run takes care of these and many other concerns for you. But rather than taking my word for it, let's build an app together and see how easy it is to transition from a local development environment to a production-grade cloud app in just a few simple steps.

What you'll do...

  • You'll build a simple web app and verify that it runs as expected in your development environment.
  • Then you'll move to a containerized version of the same app. Along the way, you'll explore what containerization means and why it's so useful.
  • Finally, you'll deploy your app to the cloud, and you'll see how easy it is to manage your Cloud Run service using the command line and the Google Cloud Console.

What you'll learn...

  • How to create a simple web server app in Python
  • How to package your app into a Docker container that runs anywhere
  • How to deploy your app to the cloud so anyone can try your new creation
  • How to simplify the above steps even further using Buildpacks
  • How to use the Google Cloud command line tool and the Cloud Console web UI

What you'll need...

  • A web browser
  • A Google account

This lab is targeted to developers of all levels, including beginners. Although you'll be using Python, you don't need to be familiar with Python programming in order to understand what's going on because we'll explain all the code you use.

5110b5081a1e1c49.png

This section covers everything you need to do to get started with this lab.

Self-paced environment setup

  1. Sign in to Cloud Console and create a new project or reuse an existing one. (If you don't already have a Gmail or Google Workspace account, you must create one.)

96a9c957bc475304.png

b9a10ebdf5b5a448.png

a1e3c01a38fa61c2.png

Remember the project ID, a unique name across all Google Cloud projects (the name above has already been taken and will not work for you, sorry!). It will be referred to later in this codelab as PROJECT_ID.

  1. Next, you'll need to enable billing in Cloud Console in order to use Google Cloud resources.

Running through this codelab shouldn't cost much, if anything at all. Be sure to to follow any instructions in the "Cleaning up" section which advises you how to shut down resources so you don't incur billing beyond this tutorial. New users of Google Cloud are eligible for the $300 USD Free Trial program.

Start Cloud Shell

In this lab you're going to work in a Cloud Shell session, which is a command interpreter hosted by a virtual machine running in Google's cloud. You could just as easily run this section locally on your own computer, but using Cloud Shell gives everyone access to a reproducible experience in a consistent environment. After the lab, you're welcome to retry this section on your own computer.

704a7b7491bd157.png

Activate Cloud Shell

  1. From the Cloud Console, click Activate Cloud Shell 4292cbf4971c9786.png.

bce75f34b2c53987.png

If you've never started Cloud Shell before, you're presented with an intermediate screen (below the fold) describing what it is. If that's the case, click Continue (and you won't ever see it again). Here's what that one-time screen looks like:

70f315d7b402b476.png

It should only take a few moments to provision and connect to Cloud Shell.

fbe3a0674c982259.png

This virtual machine is loaded with all the development tools you need. It offers a persistent 5GB home directory and runs in Google Cloud, greatly enhancing network performance and authentication. Much, if not all, of your work in this codelab can be done with simply a browser or your Chromebook.

Once connected to Cloud Shell, you should see that you are already authenticated and that the project is already set to your project ID.

  1. Run the following command in Cloud Shell to confirm that you are authenticated:
gcloud auth list

Command output

 Credentialed Accounts
ACTIVE  ACCOUNT
*       <my_account>@<my_domain.com>

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
  1. Run the following command in Cloud Shell to confirm that the gcloud command knows about your project:
gcloud config list project

Command output

[core]
project = <PROJECT_ID>

If it is not, you can set it with this command:

gcloud config set project <PROJECT_ID>

Command output

Updated property [core/project].

Set some environment variables in your terminal that will make life easier in subsequent steps:

export PROJ=$GOOGLE_CLOUD_PROJECT 
export APP=hello 
export PORT=8080
export REGION="us-central1"
export TAG="gcr.io/$PROJ/$APP"

Enable the APIs

In later steps, you'll see where these services are needed (and why), but for now, run this command to give your project access to the Cloud Build, Container Registry, and Cloud Run services:

gcloud services enable cloudbuild.googleapis.com         \
                       containerregistry.googleapis.com  \
                       run.googleapis.com          

This should produce a successful message similar to this one:

Operation "operations/acf.cc11852d-40af-47ad-9d59-477a12847c9e" finished successfully.

eef530b56b8e93a3.png

Start by clicking the Open Editor button at the top of your Cloud Shell panel. It looks like this:

9b81c8a37a6bcdd8.png

You'll then find yourself in an IDE environment similar to Visual Studio Code, in which you can create projects, edit source code, run your programs, etc. If your screen is too cramped, you can expand or shrink the dividing line between the console and your edit/terminal window by dragging the horizontal bar between those two regions, highlighted here:

8dea35450851af53.png

You can switch back and forth between the Editor and the Terminal by clicking the Open Editor and Open Terminal buttons, respectively. Try switching back and forth between these two environments now.

Next, create a folder in which to store your work for this lab, by selecting File->New Folder, enter hello, and click OK. All of the files you create in this lab, and all of the work you do in Cloud Shell, will take place in this folder.

Now create a requirements.txt file. This tells Python which libraries your app depends on. For this simple web app, you're going to use a popular Python module for building web servers called Flask and a web server framework called gunicorn. In the Cloud Editor window, click the File->New File menu to create a new file. When prompted for the new file's name, enter requirements.txt and press the OK button. Make sure the new file ends up in the hello project folder.

Enter the following lines in the new file to specify that your app depends on the Python Flask package and the gunicorn web server.

Flask
gunicorn

You don't need to explicitly save this file because the Cloud Editor will auto-save changes for you.

Version 1: Hello world!

Using the same technique, create another new file named main.py. This will be your app's main (and only) Python source file. Again, make sure the new file ends up in the hello project folder.

Insert the following code into this file:

from flask import Flask
import os
import random

app = Flask(__name__)  # Create a Flask object.
PORT = os.environ.get("PORT")  # Get PORT setting from the environment.

# The app.route decorator routes any GET requests sent to the root path
# to this function, which responds with a "Hello world!" HTML document.
@app.route("/", methods=["GET"])
def say_hello():
    html = "<h1>Hello world!</h1>"
    return html


# This code ensures that your Flask app is started and listens for
# incoming connections on the local interface and port 8080.
if __name__ == "__main__":
    app.run(host="0.0.0.0", port=PORT)

Switch back to the terminal and change into the project folder with this command:

cd hello

Run the following command to install your project dependencies:

pip3 install -r requirements.txt

Now launch your app by running this command in the terminal:

python3 main.py

At this point, your app is running on the virtual machine dedicated to your cloud shell session. Cloud shell includes a proxy mechanism that makes it possible for you to access web servers (like the one you just started) running on your virtual machine from anywhere on the global internet.

Click the web preview button and then the Preview on Port 8080 menu item like this:

fe45e0192080efd6.png

This will open a web browser tab to your running app, which should look something like this:

b1f06501509aefb9.png

Version 2: Echoing the URL Path

Return to Cloud Editor (via the Open Editor button) and add support for echoing an optional URL suffix by updating your main.py file as follows:

from flask import Flask
import os
import random

app = Flask(__name__)  # Create a Flask object.
PORT = os.environ.get("PORT")  # Get PORT setting from environment.

# The app.route decorator routes any GET requests sent to the root path
# to this function, which responds with a "Hello world!" HTML document.
# If something is specified as the URL path (after the '/'), say_hello()
# responds with "Hello X", where X is the string at the end of the URL.
@app.route("/", methods=["GET"])
@app.route("/<name>", methods=["GET"])     # ← NEW
def say_hello(name="world"):               # ← MODIFIED
    html = f"<h1>Hello {name}!</h1>"       # ← MODIFIED
    return html


# This code ensures that your Flask app is started and listens for
# incoming connections on the local interface and port 8080.
if __name__ == "__main__":
    app.run(host="0.0.0.0", port=PORT)

Switch back to the Terminal (via the Open Terminal button) and enter control-C (hold down control key while pressing ‘C') to stop your running app and then restart it by entering:

python3 main.py

Again, click the web preview button and then the Preview on Port 8080 menu item to open a web browser tab to your running app. You should again see the "Hello world!" message but now replace the URL text following the slash character with any string of your choice (e.g. /your-name) and verify that you see something like this:

93b87996f88fa370.png

Version 3: Random Colors

Now, add support for random background colors by returning to Cloud Editor (via the Open Editor button) and updating your main.py file as follows:

from flask import Flask
import os
import random

app = Flask(__name__)  # Create a Flask object.
PORT = os.environ.get("PORT")  # Get PORT setting from the environment.

# This function decides whether foreground text should be
# displayed in black or white, to maximize fg/bg contrast.
def set_text_color(rgb):                      # ← NEW
    sum = round(                              # ← NEW
        (int(rgb[0]) * 0.299)                 # ← NEW
        + (int(rgb[1]) * 0.587)               # ← NEW
        + (int(rgb[2]) * 0.114)               # ← NEW
    )                                         # ← NEW
    return "black" if sum > 186 else "white"  # ← NEW


# The app.route decorator routes any GET requests sent to the root path
# to this function, which responds with a "Hello world!" HTML document.
# If something is specified as the URL path (after the '/'), say_hello()
# responds with "Hello X", where X is the string at the end of the URL.
# To verify each new invocation of these requests, the HTML document
# includes CSS styling to produce a randomly colored background.
@app.route("/", methods=["GET"])
@app.route("/<name>", methods=["GET"])
def say_hello(name="world"):
    bg = random.sample(range(1, 255), 3)                       # ← NEW
    hex = (int(bg[0]) * 256) + (int(bg[1]) * 16) + int(bg[2])  # ← NEW
    fg_color = set_text_color(bg)                              # ← NEW
    bg_color = f"#{hex:06x}"                                   # ← NEW
    style = f"color:{fg_color}; background-color:{bg_color}"   # ← NEW
    html = f'<h1 style="{style}">Hello {name}!</h1>'           # ← MODIFIED
    return html


# This code ensures that your Flask app is started and listens for
# incoming connections on the local interface and port 8080.
if __name__ == "__main__":
    app.run(host="0.0.0.0", port=PORT)

Switch back to the Terminal (via the Open Terminal button) and enter control-C (hold down control key while pressing ‘C') to stop your running app and then restart it by entering:

python3 main.py

Again, click the web preview button and then the Preview on Port 8080 menu item to open a web browser tab to your running app. You should see the generated text, with any specified suffix or the default "Hello world!" string, displayed in front of a randomly colored background, like this:

baf8d028f15ea7f4.png

Reload the page a few times to see that the random background color changes each time you visit the app.

And with that, your app is done - congratulations! In the next step, you'll learn how to package your app into a container, and why that's a useful thing to do.

17cc234ec3325a8a.png

What's a Container?

Containers in general, and Docker in particular, give us the ability to create a modular box in which to run an application with all its dependencies bundled together. We call the result a container image. In this section, you'll create a container image, which you'll use to encapsulate your application and all of its dependencies.

Speaking of dependencies, in a previous step, when you were running your app in a developer environment, you had to run pip3 install -r requirements.txt, and make sure the requirements.txt file contained all your dependent libraries and corresponding versions. With containers, you install those requirements when you generate the container image, so there's no need for the consumer of the container to worry about installing anything.

This container image will form the basic building block for deploying your application on Cloud Run. Because containers can be used on nearly any virtual or real server, this gives us a way to deploy your application anywhere you like, and to move your application from one service provider to another, or from on-premise to the Cloud.

Containers help make your applications:

  • reproducible - containers are self-contained and complete
  • portable - containers are cross-industry building blocks, which enable application portability across cloud providers and environments

In short, containers offer the ability to, finally, "write once and run everywhere". One exception to that rule is that the generated container is constrained to run on the processor type on which you created it, but there are ways to generate container versions for other other hardware configurations as well.

Enough talk - let's make a container! You're going to use a specific technology for creating a container called Docker.

In the Cloud Editor, create a new file called Dockerfile. This file is a blueprint for constructing your image. It tells Docker about your operating environment and your source code, how to install your dependencies, build your app, and run your code.

# Use an official lightweight Python image.
FROM python:3.9-slim

# Copy local code to the container image.
WORKDIR /app
COPY main.py .
COPY requirements.txt .

# Install dependencies into this container so there's no need to 
# install anything at container run time.
RUN pip install -r requirements.txt

# Service must listen to $PORT environment variable.
# This default value facilitates local development.
ENV PORT 8080

# Run the web service on container startup. Here you use the gunicorn
# server, with one worker process and 8 threads. For environments 
# with multiple CPU cores, increase the number of workers to match 
# the number of cores available.
CMD exec gunicorn --bind 0.0.0.0:$PORT --workers 1 --threads 8 --timeout 0 main:app

In the Cloud Terminal, build your container image using Cloud Build, by running the following command:

gcloud builds submit --tag $TAG

Once pushed to the registry, you will see a SUCCESS message containing the image name, which should look something like this: gcr.io/<project-id>/hello. The image is now stored in the Google Container Registry and can be re-used whenever and wherever you like.

You can list all the container images associated with your current project using this command:

gcloud container images list

Now run and test the application locally from Cloud Shell, using this docker commands:

docker run -p $PORT:$PORT -e PORT=$PORT $TAG

The -p $PORT:$PORT option tells Docker to map the external port $PORT (set to 8080 above) in the host environment into the same port number inside the running container. This makes life easier because the server code you write and the external port number you connect to when you test your app will be the same (8080), but you could just as easily use the -p option to map any arbitrary external port on the host to any desired internal port inside the container.

The -e PORT=$PORT option tells Docker to make the $PORT environment variable (set to 8080 above) available to your app running inside the container.

Now you're ready to test your app by pointing a web browser to the Python code running inside the container. In the Cloud Shell window, click on the "Web preview" icon and select "Preview on port 8080", as you did in the previous step.

The result should look familiar - you should see the generated text in front of a randomly colored background, just as you did when you ran the app directly in your Cloud Shell terminal. Reload the page a few times to see that the random background color changes each time you visit the app.

Congratulations! You've now run a containerized version of your app. In the next section, without touching a line of code, you'll turn your container image into a production quality web app.

1b0665d94750ded6.gif

Now that you've containerized your app, you'll want to share this bit of awesomeness with the rest of the world, so it's time to deploy it to the Cloud. But you'd really like to do more than just share it. You'd like to make sure it:

  • runs reliably - you get automatic fault tolerance in case a computer running your app crashes
  • scales automatically - your app will keep up with vast levels of traffic, and automatically reduce its footprint when unused
  • minimizes your costs, by not charging you for resources you're not using - you're charged only for resources consumed while responding to traffic
  • is accessible via a custom domain name - you have access to a one-click solution to assign a custom domain name to your service
  • offers excellent response time - cold starts are reasonably responsive but you can fine tune that by specifying a minimum instance configuration
  • supports end-to-end encryption using standard SSL/TLS web security - when you deploy a service, you get standard web encryption, and the corresponding required certificates, for free and automatically

By deploying your app to Google Cloud Run, you get all of the above and more.

Deploy Your App to Cloud Run

First, let's modify your app so you'll be able to tell the new revision from the old one. Do that by modifying the main.py file such that the default message changes from "Hello world!" to "Hello from Cloud Run!". In other words, change this line in main.py from this:

def say_hello(name="world"):

to this:

def say_hello(name="from Cloud Run"):

Cloud Run is regional, which means the infrastructure that runs your Cloud Run services is located in a specific region and is managed by Google to be redundantly available across all the zones within that region. In the "Get Set Up" section above, you defined a default region via the REGION environment variable.

Rebuild your container image and deploy your containerized application to Cloud Run with the following command:

gcloud builds submit --tag $TAG
gcloud run deploy "$APP"   \
  --image "$TAG"           \
  --platform "managed"     \
  --region "$REGION"       \
  --allow-unauthenticated
  • You can also define a default region with gcloud config set run/region $REGION.
  • The --allow-unauthenticated option makes the service publicly available. To avoid unauthenticated requests, use --no-allow-unauthenticated instead.

The image specified here is the docker image you built in the last step. Thanks to the Cloud Build service, which stored the resulting image in the Google Container Registry, the Cloud Run service can find it and deploy it for you.

Wait a few moments until the deployment is complete. On success, the command line displays the service URL:

Deploying container to Cloud Run service [hello] in project [PROJECT_ID...
✓ Deploying new service... Done.                                   
  ✓ Creating Revision... Revision deployment finished. Waiting for health check...
  ✓ Routing traffic...
  ✓ Setting IAM Policy...
Done.
Service [hello] revision [hello-...] has been deployed and is serving 100 percent of traffic.
Service URL: https://hello-....a.run.app

You can also retrieve your service URL with this command:

gcloud run services describe hello  \
  --platform managed                \
  --region $REGION                  \
  --format "value(status.url)"

This should display something like:

https://hello-....a.run.app

This link is a dedicated URL, with TLS security, for your Cloud Run service. This link is permanent (as long as you don't disable your service) and usable anywhere on the internet. It doesn't use the Cloud Shell's proxy mechanism mentioned earlier, which depended on a transient virtual machine.

Click on the highlighted Service URL to open a web browser tab to your running app. The result should display your "Hello from Cloud Run!" message in front of a randomly colored background.

Congratulations! Your app is now running in Google's Cloud. Without having to think about it, your app is publicly available, with TLS (HTTPS) encryption, and automatic scaling to mind-boggling levels of traffic.

But I think this process could be even easier...

This is all pretty cool, but what if I don't want to even think about Dockerfiles and containers. What if, like most developers, I just want to focus on writing my application code and let someone else worry about containerizing it. Well, you're in luck because Cloud Run supports an open source standard called Buildpacks, which exists for this very reason: to automate the process of manufacturing a container from a collection of source files.

Note that there are some cases where a developer might prefer to use an explicit Dockerfile, for example if they want a high degree of customization in how their container is built. But for common cases like this exercise, buildpacks work nicely and avoid the need to hand craft a Dockerfile. Let's modify your code to use buildpacks.

First, let's modify your app so you'll be able to tell the new revision from the old one. Do that by modifying the main.py file such that the default message changes from "Hello from Cloud Run!" to "Hello from Cloud Run with Buildpacks!". In other words, change this line in main.py from this:

def say_hello(name="from Cloud Run"):

to this:

def say_hello(name="from Cloud Run with Buildpacks"):

Now let's take advantage of buildpacks by creating a new file named Procfile. Go ahead and create that file in Cloud Editor and insert this one line of text:

web: python3 main.py

This tells the buildback system how to run your app in the auto-generated container. With that bit of instruction, you don't even need a Dockerfile any more. To verify this, delete your Dockerfile and run the following command in the Cloud Shell Terminal:

gcloud beta run deploy "$APP"  \
    --source .                 \
    --platform "managed"       \
    --region "$REGION"         \
    --allow-unauthenticated

This is similar to the command you ran to deploy your app in the last step but this time around you've replaced the --image option with the --source . option. This tells the gcloud command that you want it to use buildpacks to create your container image, based on the source files it finds in the current directory (the dot in --source . is shorthand for the current directory). Because the service takes care of the container image implicitly, you don't need to specify an image on this gcloud command.

Once again, verify this deployment worked by clicking on the highlighted Service URL to open a web browser tab to your running app and make sure your service is displaying "Hello from Cloud Run with Buildpacks!" in front of a randomly colored background.

Notice that by using buildpacks to manufacture your Dockerfile, you've essentially reduced the three easy steps to two:

  1. Create an app in your development environment.
  2. Deploy the exact same code to the cloud with one command.

No! Like nearly every Google Cloud service, there are three ways to interact with Cloud Run:

  • The gcloud command line tool, which you've just seen.
  • A rich web user interface, via the Cloud Console, which supports an intuitive point and click interaction style.
  • Programmatically, using Google client libraries available for many popular languages, including Java, C#, Python, Go, Javascript, Ruby, C/C++, and others.

Let's deploy an additional instance of your Cloud Run app using the console UI. Navigate to the Cloud Run Service landing page via the upper-left menu:

e2b4983b38c81796.png

You should then see a summary of your Cloud Run Services, like this:

b335e7bf0a3af845.png

Click on the "Create Service" link to start the deployment process:

51f61a8ddc7a4c0b.png

Enter "hello-again" as the service name, take the default deployment platform and region, and click "Next".

8a17baa45336c4c9.png

Enter this URL for the container image: gcr.io/cloudrun/hello, which is a container made by Google for testing purposes, and click the "Advanced Settings" drop-down to see some of the many configuration settings available to you. To point out just a few you can customize:

  • port number and container entry point (which will override the entry point specified when building the container)
  • hardware: memory and number of CPUs
  • scaling: min and max instances
  • environment variables
  • others: request timeout setting, max number of requests per container, HTTP/2

Click the "Next" button to advance the dialog. The next dialog allows you to specify how your service is triggered. For "Ingress", select "allow all traffic", and for "Authentication", select "Allow unauthenticated traffic".

e78281d1cff3418.png

These are the most liberal settings, in that they allow anyone to access your Cloud Run app, from anywhere on the public internet, without specifying authentication credentials. You may want more restrictive settings for your app, but let's keep it simple for this learning exercise.

Now click the Create button to create your Cloud Run service. After a few seconds, you should see your new service appear in your summary list of Cloud Run services. The summary line provides the most recent deployment (date/time and by whom) along some key configuration settings. Click on the service name link to drill down into details about your new service.

To verify your service, click on the URL shown near the top of the summary page as highlighted in the example below:

6c35cf0636dddc51.png

You should see something like this:

3ba6ab4fe0da1f84.png

Now that you've deployed a new Cloud Run service, select the REVISIONS tab to see some ways to manage multiple deployments.

2351ee7ec4a356f0.png

To deploy new revisions directly from the console, you can click the EDIT & DEPLOY NEW REVISION button, as highlighted in example screenshot below:

a599fa88d00d6776.png

Click that button now to create a new revision. Near the container URL, click the SELECT button, as shown below:

5fd1b1f8e1f11d40.png

In the dialog that pops up, find the simple web app you deployed from Cloud Build using Buildpacks earlier and then click select. Make sure you choose the container image under the

gcr.io/<project>/cloud-run-source-deploy

folder , like this:

8a756c6157face3a.png

Once selected, scroll to the bottom and click the DEPLOY button. You've now deployed a new revision of your app. To verify, re-visit your service URL again and verify that you're now seeing your colorful "Hello from Cloud Run with Buildpacks!" web app.

As you can see, the revisions tab provides a summary of every revision you've deployed, and you should now see two revisions for this service. You can select a given revision by clicking on the radio button to the left of the revision name, which will display a summary of the revision details on the right side of the screen. By selecting those buttons, you can see that your two revisions are derived from two different container images.

The MANAGE TRAFFIC button enables you to modify the distribution of incoming requests sent to a given revision. This ability to fine tune how much traffic is sent to a given revision enables several valuable use cases:

  • canary testing a new version of your app with a small portion of incoming traffic
  • revert traffic from a problematic release to a previous revision
  • A/B testing

Find the MANAGE TRAFFIC button here:

519d3c22ae028287.png

Configure a 50/50 traffic split between your two revisions by specifying a 50/50 traffic split like this:

8c37d4f115d9ded4.png

Now click the SAVE button and verify the 50/50 split by visiting your service's URL repeatedly and check that, on average, half of your requests are served by the current revision ("Hello from Cloud Run with Buildpacks!") and half are served by the previous revision ("It's running!").

Other tabs on the Service Details page offer the ability to monitor performance, traffic, and logs, which provide valuable insights into how hard and how well your service is working. You also have the ability to fine tune access to your service via the "Permissions" tab. Take a few moments to explore the tabs on this page to get a sense of the capabilities available here.

Programmatic Interface

As noted previously, you also have the option to create, deploy, and manage your Cloud Run services programmatically. For manual tasks, this option is more advanced than the command line or web console, but it's definitely the way to go for automating Cloud Run services. You have the option to use Google client libraries in several popular programming languages.

198ada162d1f0bf1.png

In this final step, you're going to run an artificial load test to stress test your app and watch how it scales with incoming demand. You're going to use a tool called hey, which is pre-installed in the Cloud Shell and gives us the ability to run load tests and present the results.

Run the Test

In the Cloud Shell Terminal, run this command to run a load test:

hey -q 1000 -c 200 -z 30s https://hello-...run.app

The command arguments are interpreted as follows:

  • -q 1000 - try to drive the load at roughly 1,000 requests per second
  • -c 200 - allocate 200 parallel workers
  • -z 30s - run the load test for 30 seconds
  • make sure to use your service URL as the last argument in this command line

The results of your test should look something like this:

 Summary:
 Total:        30.2767 secs
 Slowest:      3.3633 secs
 Fastest:      0.1071 secs
 Average:      0.1828 secs
 Requests/sec: 1087.2387
 Total data:   3028456 bytes
 Size/request: 92 bytes

Response time histogram:
 0.107 [1]     |
 0.433 [31346] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
 0.758 [1472]  |■■
 1.084 [82]    |
 1.410 [4]     |
...

Latency distribution:
...
 50% in 0.1528 secs
 75% in 0.1949 secs
 90% in 0.2442 secs
 95% in 0.4052 secs
 99% in 0.7062 secs

Details (average, fastest, slowest):
...
 req write:    0.0000 secs, 0.0000 secs, 0.0232 secs
 resp wait:    0.1824 secs, 0.1070 secs, 3.2953 secs
 resp read:    0.0000 secs, 0.0000 secs, 0.0010 secs
Status code distribution:
 [200] 32918 responses

This summary tells us several items of interest:

  • 32,918 requests were sent at roughly 1K/per second for 30s.
  • There were no errors (only 200 HTTP responses).
  • Average latency was 180ms.
  • Minimum latency was 107ms, worst case was 3.3s
  • 90th percentile latency was 244ms.

If you check the METRICS tab on the Cloud Run console, you can see the server's side of the performance story:

e635c6831c468dd3.png

While Cloud Run does not charge when the service is not in use, you might still be charged for storing the built container image.

You can either delete your GCP project to avoid incurring charges, which will stop billing for all the resources used within that project, or simply delete your container image using this command:

gcloud container images delete $TAG

To delete your Cloud Run services, use these commands:

gcloud run services delete hello --platform managed --region $REGION --quiet
gcloud run services delete hello-again --platform managed --region $REGION --quiet

9a31f4fdbbf1ddcb.png

Congratulations - you've successfully built and deployed a production Cloud Run app. Along the way, you learned about containers and how to build your own container. And you saw how easy it is to deploy your app with Cloud Run, using both the gcloud command line tool and the Cloud Console. Now you know how to share your brilliant creations with the whole world!

I want to leave you with one important question:

Once you got your app working in your developer environment, how many lines of code did you have to modify to deploy it to the cloud, with all the production-grade attributes offered by Cloud Run?

The answer, of course, is zero. :)

Codelabs to check out...

Other cool features to explore...

Reference docs...