Module 11: Migrating from Google App Engine to Cloud Functions

1. Overview

This series of codelabs (self-paced, hands-on tutorials) aims to help Google App Engine (Standard) developers modernize their apps by guiding them through a series of migrations. The most significant step is to move away from original runtime bundled services because the next generation runtimes are more flexible, giving users a greater variety of service options. Moving to the newer generation runtime enables you to integrate with Google Cloud products more easily, use a wider range of supported services, and support current language releases.

There are situations when you don't have an "entire app" to require the resources of App Engine or Cloud Run. If your code consists only of a microservice or simple function, Cloud Functions is likely a better fit. This tutorial teaches you how to migrate simple App Engine apps (or break apart larger apps into multiple microservices) and deploy them to Cloud Functions, a fully-managed service which was created specifically for use cases like this. This codelab begins with the Python 3 version of the Module 2 Cloud NDB App Engine sample app. Note that Cloud Functions does not support Python 2.

You'll learn how to

  • Use Cloud Shell
  • Enable the Google Cloud Translation API
  • Authenticate API requests
  • Convert a simple App Engine app to run on Cloud Functions
  • Deploy functions/microservices to Cloud Functions

What you'll need

Survey

How will you use this tutorial?

Read it through only Read it and complete the exercises

How would you rate your experience with Python?

Novice Intermediate Proficient

How would you rate your experience with using Google Cloud services?

Novice Intermediate Proficient

2. Background

PaaS systems like Google App Engine and Cloud Functions provide many conveniences for users. These serverless platforms enable your technical team to focus on creating business solutions rather than spend time on investigating platforms to use and determining the amount of hardware needed. Applications can autoscale up as needed, scale down to zero with pay-per-use billing to control costs, and they allow for a variety of today's common development languages.

However, while full-stack web application development or complex back-ends for mobile apps is a great fit for App Engine, it's often the case that developers are mainly trying to put some functionality online, such as updating a news feed or pulling in the latest score of the home team's playoff game. While there is coding logic behind both scenarios, neither appear to be full-blown "applications" requiring the power of App Engine. This is where Cloud Functions come in.

Cloud Functions is for deploying the small bit of code that:

  • Isn't a part of an entire application
  • Isn't needed in an entire development stack
  • Is in an application or single mobile app backend that focuses on one thing

You can also use Cloud Functions to break up a large, monolithic application into multiple microservices, each using a shared common database such as Cloud Firestore or Cloud SQL. And if you're in a situation where you would like your function or microservice containerized and executed serverlessly on Cloud Run, you can do that too.

Our sample App Engine app that's been featured in almost all of the migration tutorials is a simple app with basic functionality that would work well for Cloud Functions rather than App Engine. In this tutorial, you'll learn how to modify that app so that it runs on Cloud Functions. From the App Engine perspective, because functions are simpler than entire apps, your getting started experience should be easier (and faster), and there should be less "overhead" in general. You will experience this as well

This migration features these steps:

  • Setup/Prework
  • Remove configuration files
  • Modify application files

3. Setup/Prework

Before we get going with the main part of the tutorial, let's set up our project, get the code, then deploy the baseline app so we know we started with working code.

1. Setup project

If you completed the Module 2 codelab (and ported it to Python 3), we recommend reusing that same project (and code). Alternatively, you can create a brand new project or reuse another existing project. Ensure the project has an active billing account and App Engine (app) is enabled.

2. Get baseline sample app

One of the prerequisites to this codelab is to have a working Module 2 sample app. If you don't have one, go complete either tutorial linked above before moving ahead here. Otherwise if you're already familiar with its contents, you can start by grabbing the Module 2 code below.

Whether you use yours or ours, the Module 2 Python 3 code is where we'll START. This Module 11 codelab walks you through each step, concluding with code that resembles what's in the Module 11 repo folder (FINISH).

The directory of Python 3 Module 2 STARTing files (yours or ours) should look like this:

$ ls
README.md               main.py                 templates
app.yaml                requirements.txt

3. (Re)Deploy baseline app

Your remaining prework steps to execute now:

  1. Re-familiarize yourself with the gcloud command-line tool
  2. Re-deploy the sample app with gcloud app deploy
  3. Confirm the app runs on App Engine without issue

Once you've successfully executed those steps, you're ready to convert it to a Cloud Function.

4. Remove configuration files

The app.yaml file is an App Engine artifact not used with Cloud Functions so delete it now. If you don't or forget to do this, there is no harm since Cloud Functions won't use it. That's the only configuration change as requirements.txt should stay identical to that from Module 2.

If you happen to be porting a Python 2 App Engine app to Python 3 at the same time, also delete appengine_config.py as well as any lib folder you might still have. They are older App Engine artifacts and unused in Python 3 App Engine.

5. Modify application files

There is only one application file, main.py, so all necessary changes to move to Cloud Functions occur in this file.

Imports

Because we're only working with functions, there's no need for a web application framework. However, for convenience, when Python-based Cloud Functions are called, they are automatically passed a request object for your code to use as needed. (The Cloud Functions team selected it to be a Flask Request object that is passed to your function.)

Since a framework isn't used, you won't import anything from Flask unless your app uses other features available from Flask. This is indeed our case. While we're not using the framework, we do need to render the same HTML template as our App Engine app. That means we still need to call flask.render_template(), so that's the only thing we're keeping around to import from Flask. No web framework means we also need to delete its instantiation. Here are the changes from the top part of the source code resulting from these updates:

  • BEFORE:
from flask import Flask, render_template, request
from google.cloud import ndb

app = Flask(__name__)
ds_client = ndb.Client()
  • AFTER:
from flask import render_template
from google.cloud import ndb

ds_client = ndb.Client()

If you are dependent on the app object (app) or any other web framework infrastructure, you need to resolve all those dependencies, finding appropriate workarounds, or remove their usage entirely or find proxies. Only then can you convert your code to a Cloud Function. Otherwise, you'll be better of staying on App Engine or containerizing your app for Cloud Run

Update main handler function signature

The changes required in the function signature are as follows:

  1. Since we're no longer using Flask when moving to Cloud Functions, remove the route decorator.
  2. Because Cloud Functions automatically passes in the Flask Request object as a parameter, create a variable for it. In our sample app, we'll call it request.
  3. Deployed Cloud Functions must be named. Our main handler was named appropriately root() in App Engine to describe what it was (the root application handler). As a Cloud Function, it makes less sense to use that name. Instead, we'll deploy the Cloud Function with the name visitme, so use that as the Python function's name as well. Similarly, in Modules 4 and 5, we also named the Cloud Run service visitme.

Here are the before and after with these updates:

  • BEFORE:
@app.route('/')
def root():
    'main application (GET) handler'
    store_visit(request.remote_addr, request.user_agent)
    visits = fetch_visits(10)
    return render_template('index.html', visits=visits)
  • AFTER:
def visitme(request):
    'main application (GET) handler'
    store_visit(request.remote_addr, request.user_agent)
    visits = fetch_visits(10)
    return render_template('index.html', visits=visits)

There are no changes at all to the main application code, so the code from Module 2 will stay as is. Here is a pictorial representation of the changes that were made:

2b2ebd5510a35eef.png

Deploying to Cloud Functions is slightly different from App Engine. Because there are no configuration files outside of requirements.txt, a few more things must be specified in the deployment command.

6. Build and deploy

Deploy your transformed visitme function to Cloud Functions running under Python 3.9 triggered by HTTP with this command:

$ gcloud functions deploy visitme --runtime python39 --trigger-http --allow-unauthenticated

The output should look as follows, and provide some prompts for next steps:

Deploying function (may take a while - up to 2 minutes)...⠹
For Cloud Build Stackdriver Logs, visit: https://console.cloud.google.com/logs/viewer?project=PROJECT_ID&advancedFilter=resource.type%3Dbuild%0Aresource.labels.build_id%3D7e32429d-ec36-422c-8a8b-43c4d661a15c%0AlogName%3Dprojects%2FPROJECT_ID%2Flogs%2Fcloudbuild
Deploying function (may take a while - up to 2 minutes)...done.
availableMemoryMb: 256
buildId: 7e32429d-ec36-422c-8a8b-43c4d661a15
entryPoint: visitme
httpsTrigger:
  securityLevel: SECURE_OPTIONAL
  url: https://REGION-PROJECT_ID.cloudfunctions.net/visitme
ingressSettings: ALLOW_ALL
labels:
  deployment-tool: cli-gcloud
name: projects/PROJECT_ID/locations/REGION/functions/visitme
runtime: python39
serviceAccountEmail: PROJECT_ID@appspot.gserviceaccount.com
sourceUploadUrl: https://storage.googleapis.com/gcf-upload-REGION-873f8448-838f-4eb2-beda-3e200a1420d/cb1cbdca-34eb-41d0-88d6-c27d25fb.zip?GoogleAccessId=service-104690130103@gcf-admin-robot.iam.gserviceaccount.com&Expires=1619139674
status: ACTIVE
timeout: 60s
updateTime: '2021-04-23T00:32:58.065Z'
versionId: '3'

After your function has deployed, use the URL from the deployment output and visit your app. The URL is of the form: REGION-PROJECT_ID.cloudfunctions.net/visitme. The output should be identical to when you deployed it earlier to App Engine:

54a31af0805e64c1.png

As with most other codelabs and videos in the series, the baseline app functionality doesn't change. The purpose is to make a single modernization and have the app work exactly as before but with something newer, for example migrating from an older App Engine legacy service to its replacement Cloud standalone product, or you moved the app to another Google Cloud serverless platform. Today, it was the latter with moving your code from App Engine to Cloud Functions.

7. Summary/Cleanup

Congratulations for converting this small App Engine app to a Cloud Function! Another use case which could be appropriate would be breaking-up a large monolithic App Engine app into a series of microservices, each as a Cloud Function. This is a more modern development technique resulting in a more "plug-and-play" component (a la " JAM stack") style. It allows for mixing and matching, and code reuse, which are two goals, but another benefit is that these microservices will continue to get debugged over time, meaning stable code and lower maintenance costs overall.

Optional: Clean up and/or disable service

If you're not ready to go to the next tutorial yet, disable the Module 2 App Engine app to avoid incurring charges. When you're ready to move to the next codelab, you can re-enable it. While App Engine apps are disabled, they won't get any traffic to incur charges, however Datastore usage may be billable if it exceeds its free quota, so delete enough to fall under that limit.

Unfortunately Cloud Functions doesn't have a "disable" feature. Backup your code and just delete the function. You can always redeploy it with the same name later. On the other hand, if you're not going to continue with any other migrations and want to delete everything completely, shutdown your Cloud projects.

Next steps

Beyond this tutorial, other migration modules to look at include containerizing your App Engine app for Cloud Run. See the links to the Module 4 and Module 5 codelabs:

  • Module 4: Migrate to Cloud Run with Docker
  • Containerize your app to run on Cloud Run with Docker
  • This migration allows you to stay on Python 2.
  • Module 5: Migrate to Cloud Run with Cloud Buildpacks
  • Containerize your app to run on Cloud Run with Cloud Buildpacks
  • You do not need to know anything about Docker, containers, or Dockerfiles.
  • Requires your app to have already migrated to Python 3 (Buildpacks doesn't support Python 2)

8. Additional resources

App Engine migration module codelabs issues/feedback

If you find any issues with this codelab, please search for your issue first before filing. Links to search and create new issues:

Migration resources

Links to the repo folders for Module 8 (START) and Module 9 (FINISH) can be found in the table below. They can also be accessed from the repo for all App Engine codelab migrations which you can clone or download a ZIP file.

Codelab

Python 3

Module 2

code

Module 11

code

Online resources

Below are online resources which may be relevant for this tutorial:

App Engine

Cloud Functions

Other Cloud information

Videos

License

This work is licensed under a Creative Commons Attribution 2.0 Generic License.