This is the code repo for a set of codelab tutorials highlighting a single "nebulous" sample app. What makes this app unique is that it demonstrates the flexibility of where you can run your apps as far as Google Cloud serverless compute platforms go. With minor configuration tweaks, this app can be deployed (at least) eight different ways:
Deployment | Python 2 | Python 3 |
---|---|---|
Local/hosted Flask | codelab | same as Python 2 |
App Engine | codelab | codelab |
Cloud Functions | N/A | codelab |
Cloud Run (Docker) | codelab | codelab |
Cloud Run (Buildpacks) | N/A | codelab |
Admittedly, there may seem to be a bit of "cheating" due to the duplicity of Python 2 and 3, especially since the application is compatible across both language versions without modification or use of compatibility libraries. However there are significant differences between both App Engine runtimes beyond the version language differences. For local Flask or Cloud Run deployments, there are either little or no updates to go from 2.x to 3.x. Neither Cloud Functions nor Cloud Buildpacks support Python 2.
Python == 2.7 or >= 3.6
This code sample was inspired by a user's suboptimal experience trying to create a simple App Engine app using a Cloud API. It was also inspired by a colleague's blog post showing a similar Node.js example "drifting" between GCP serverless platforms.
This app shows developers how to use the Cloud Translation API, the API for Google Translate, and one of GCP's AI/ML "building block" APIs. Such APIs are backed by pre-trained machine learning models, allowing developers with little or no background in AI/ML to leverage machine learning with only API calls. The application implements a mini Google Translate "MVP" (minimally-viable product) web service.
Aside from local deployment, this app is deployable to these serverless compute platforms:
- Google App Engine (Standard)
- Standard source code application deployments (app-hosting in the cloud; "PaaS")
- Google Cloud Functions
- Instead of an entire app, this is for cloud-based functions or microservices ("FaaS")
- Google Cloud Run
- Fully-managed serverless container-hosting in the cloud ("CaaS") service
The purpose of this app is to show users how to deploy the same app to each platform and give developers hands-on experience with each. It also shows users how similar the platforms are to each other that one can "shift" between then typically with just minor configuration changes. A fourth product, App Engine Flexible, which sits somewhere between App Engine Standard and Cloud Run, is out-of-scope for this sample app.
The app uses the Flask micro web framework. When deploying locally, the Flask development server — also see its docs — is used. As an application, you're likely to deploy to either App Engine or Cloud Run, depending on whether your app is containerized. Since this "app" only has a single purpose/function, it is also reasonable to deploy it to Cloud Functions.
App Engine is for users who wish to deploy a traditional web stack (LAMP, MEAN, etc.) application direct from source without knowledge of containers, Docker, or Dockerfile
s. Cloud Run is similar but for applications that are explicitly containerized, freeing you from language, library, or binary restrictions. Cloud Functions is for deploying simple microservices like ours, and its Python runtime sends Flask request objects directly to deployed functions.
When running on App Engine or Cloud Functions, this sample app uses the default web server that comes with those services (gunicorn
). For Cloud Run, developers must start their own web server; this sample app again chooses Flask's development server (but can be configured to your server of choice; gunicorn
can be enabled if uncommented in the configuration.
The credentials JSON file — call it anything you like, but we're using the name credentials.json
— is only used when running locally to specify the service account used to talk to Cloud APIs. Developers are prompted to download it after creating a service account key-pair. Check out the documentation for service accounts and the service account public/private key/pairs.
Why just locally? For simplicity, this sample app uses default service accounts when deploying to the cloud, so neither the service account key-pair created for local deployment nor its credentials.json
will be used in those cases. In other words, if not deploying/testing locally, you don't have to create a service account key.
However, while we suggest you "delete" credentials.json
and not use it when deploying to the cloud ("finding credentials automatically"), you can disregard that advice and opt to use that service account key-pair ("passing credentials manually") if desired as long as you point to those credentials. Read more about these options in the documentation.
While many Google APIs can be used without fees, use of GCP products & APIs is not free. Certain products do offer an "Always Free" tier which you have to exceed in order to be billed. For our purposes, while the Translation API does not explicitly list a free quota on that page, its pricing information page (link below) indicates a certain number of translated characters as a free monthly quota applied as a credit, so long as you stay within that limit, you should not incur any additional charges. When enabling services, you may be asked for an active billing account which requires a financial instrument such as a credit card. Reference relevant pricing information before doing so.
While Cloud Functions and Cloud Run share a similar Always Free tier and pricing model, App Engine is slightly different. Here are each product's corresponding pricing pages for you to learn more as well as link to our pricing calculator:
Furthermore, deploying to GCP serverless platforms incur minor build and storage costs. Cloud Build has its own free quota as does Cloud Storage. For greater transparency, Cloud Build builds your application image which is then sent to the Cloud Container Registry, or Artifact Registry, its successor; storage of that image uses up some of that (Cloud Storage) quota as does network egress when transferring that image to the service you're deploying to. However you may live in region that does not have such a free tier, so be aware of your storage usage to minimize potential costs. (You may look at what storage you're using and how much, including deleting build artifacts via your Cloud Storage browser.)
Once you have a billing account, you can enable the services/APIs for each product used. Go to the Cloud console pages for each respective Cloud product used and enable the service:
Alternatively, you use the gcloud
CLI (command-line interface) available from the Cloud SDK. Review the Cloud SDK install instructions if needed. New users should also reference the gcloud
cheatsheet.
Enable all 4 services with this one gcloud
command: gcloud services enable translate.googleapis.com run.googleapis.com cloudfunctions.googleapis.com appengine.googleapis.com
The app consists of a simple web page prompting the user for a phrase to translate from English to Spanish. The translated results along with the original phrase are presented along with an empty form for a follow-up translation if desired. While the majority of this app's deployments are in Python 3, there are still many users working on upgrading from Python 2, so some of those deployments are available to help with migration planning. This is what the app looks like after completing one translation (Cloud Run version):
These are the files provided in this repo and the deployments they're applicable to:
NOTES:
- —
requirements.txt
is used for local and App Engine (2.x) package installations and not required in deployments themselves unlike all othersmain.py
andtemplates/index.html
comprise the entire application and are always requirednoxfile.py
andtest_translate.py
are for testing only; see Testing section below- All
.*ignore
and.git*
files/folders are administrative and not listed in table above or deployments below- Files applicable only to a specific language version are annotated above
Below are the required settings and instructions for all documented deployments. The "TL:DR;" section at the top of each configuration summarizes the key files (see above) while the table beneath spells out the details. No administrative files are listed.
- TL;DR: application files (
main.py
&requirements.txt
) pluscredentials.json
File | Description |
---|---|
main.py |
use as-is from repo |
credentials.json |
create (if necessary) and use per instructions below |
app.yaml |
unused (delete or leave as-is) |
appengine_config.py |
unused (delete or leave as-is; only for Python 2 App Engine) |
requirements.txt |
use as-is to install packages locally (see below) but unused thereafter |
lib |
unused (delete or leave as-is if it exists) |
Dockerfile |
unused (delete or leave as-is) |
Procfile |
unused (delete or leave as-is) |
- Create service account key, download key file as
credentials.json
in working directory, and setGOOGLE_APPLICATION_CREDENTIALS
environment variable pointing to it (more above and here) - Run
pip install -U pip -r requirements.txt
to install/update packages locally (orpip2
) - Run
python main.py
to run on local Flask server (orpython2
)
- TL;DR: app files plus
credentials.json
(identical to Python 2 deployment)
File | Description |
---|---|
main.py |
use as-is from repo |
credentials.json |
create (if necessary) and use per instructions below |
app.yaml |
unused (delete or leave as-is) |
appengine_config.py |
unused (delete or leave as-is; only for Python 2 App Engine) |
requirements.txt |
use as-is to install packages locally (see below) but unused thereafter |
lib |
unused (delete or leave as-is if it exists) |
Dockerfile |
unused (delete or leave as-is) |
Procfile |
unused (delete or leave as-is) |
- Reuse existing
credentials.json
or create new one per Python 2 instructions above - Run
pip install -U pip -r requirements.txt
to install/update packages locally (orpip3
)- While you can reuse
credentials.json
from Python 2, you must still install the packages for Python 3.
- While you can reuse
- Run
python main.py
to run on local Flask server (orpython3
)
- TL;DR: app files plus
app.yaml
,appengine_config.py
, andlib
File | Description |
---|---|
main.py |
use as-is from repo |
credentials.json |
delete (or rename) if it exists (default credentials used in the cloud) |
app.yaml |
use as-is from repo (ensure #runtime:python39 commented out) |
appengine_config.py |
use as-is from repo |
requirements.txt |
use as-is to install packages locally (see below) but unused thereafter |
lib |
create folder per instructions below |
Dockerfile |
unused (delete or leave as-is) |
Procfile |
unused (delete or leave as-is) |
- Delete
credentials.json
(see above) - Run
pip install -t lib -r requirements.txt
to populatelib
folder (orpip2
) - Run
gcloud app deploy
to deploy to Python 2 App Engine
- TL;DR: app files plus
app.yaml
File | Description |
---|---|
main.py |
use as-is from repo |
credentials.json |
delete (or rename) if it exists (default credentials used in the cloud) |
app.yaml |
uncomment runtime:python39 (or Python 3.7 or 3.8); delete all other lines |
appengine_config.py |
unused (delete or leave as-is; only for Python 2 App Engine) |
requirements.txt |
use as-is from repo |
lib |
delete (or rename) this folder if it exists (not used with Python 3 App Engine) |
Dockerfile |
unused (delete or leave as-is) |
Procfile |
unused (delete or leave as-is) |
- Edit
app.yaml
and deletecredentials.json
andlib
(see above) - Run
gcloud app deploy
to deploy to Python 3 App Engine
- TL;DR: app files
File | Description |
---|---|
main.py |
use as-is from repo |
credentials.json |
delete (or rename) if it exists (default credentials used in the cloud) |
app.yaml |
unused (delete or leave as-is; only for App Engine) |
appengine_config.py |
unused (delete or leave as-is; only for Python 2 App Engine) |
requirements.txt |
use as-is from repo |
lib |
delete (or rename) this folder if it exists (not used with Cloud Functions) |
Dockerfile |
unused (delete or leave as-is) |
Procfile |
unused (delete or leave as-is) |
- Delete
credentials.json
andlib
(see above) - Run
gcloud functions deploy translate --runtime python39 --trigger-http --allow-unauthenticated
to deploy to Cloud Functions (or Python 3.7 or 3.8)- That command creates & deploys a new HTTP-triggered Cloud Function (name must match what's in
main.py
)
- That command creates & deploys a new HTTP-triggered Cloud Function (name must match what's in
- There is no support for Python 2 with Cloud Functions
- TL;DR: app files plus
Dockerfile
File | Description |
---|---|
main.py |
use as-is from repo |
credentials.json |
delete (or rename) if it exists (default credentials used in the cloud) |
app.yaml |
unused (delete or leave as-is; only for App Engine) |
appengine_config.py |
unused (delete or leave as-is; only for Python 2 App Engine) |
requirements.txt |
uncomment grpcio==1.39.0 |
lib |
delete (or rename) this folder if it exists (not used with Cloud Run) |
Dockerfile |
use as-is from repo (ensure #FROM python:3-slim commented out) |
Procfile |
unused (delete or leave as-is) |
- Delete
credentials.json
andlib
(see above) - Edit
requirements.txt
to specify final version ofgrpcio
supporting Python 2 - Run
gcloud run deploy translate --allow-unauthenticated --platform managed
to deploy to Cloud Run; optionally add--region REGION
for non-interactive deploy- The above command wraps
docker build
anddocker push
, deploying the image to Cloud Artifact Registry, and finallydocker run
to deploy the service, all in one convenient command.
- The above command wraps
- You can also use this shortcut to deploy to Cloud Run:
- By default, App Engine & Cloud Functions launch production servers; with Cloud Run, the Flask development server is used for prototyping. For production, bundle and deploy a production server like
gunicorn
:- Uncomment
gunicorn
fromrequirements.txt
(commented out for App Engine & Cloud Functions) - Uncomment the
ENTRYPOINT
entry forgunicorn
replacing the default entry inDockerfile
- Re-use the same deploy command
- Uncomment
- TL;DR: app files plus
Dockerfile
(nearly identical to Python 2 deployment)
File | Description |
---|---|
main.py |
use as-is from repo |
credentials.json |
delete (or rename) if it exists (default credentials used in the cloud) |
app.yaml |
unused (delete or leave as-is; only for App Engine) |
appengine_config.py |
unused (delete or leave as-is; only for Python 2 App Engine) |
requirements.txt |
use as-is from repo |
lib |
delete (or rename) this folder if it exists (not used with Cloud Run) |
Dockerfile |
replace FROM python:2-slim with FROM python:3-slim (commented out) but keep all other lines |
Procfile |
unused (delete or leave as-is) |
- Edit
Dockerfile
and deletecredentials.json
andlib
(see above) - Run
gcloud run deploy translate --allow-unauthenticated --platform managed
to deploy to Cloud Run; optionally add--region REGION
for non-interactive deploy - The shortcut "button" above can be customized for Python 3 if you make the
Dockerfile
update above and commit it to your fork/clone. - By default, App Engine & Cloud Functions launch production servers; with Cloud Run, the Flask development server is used for prototyping. For production, bundle and deploy a production server like
gunicorn
:- Uncomment
gunicorn
fromrequirements.txt
(commented out for App Engine & Cloud Functions) - Uncomment the
ENTRYPOINT
entry forgunicorn
replacing the default entry inDockerfile
- Re-use the same deploy command
- Uncomment
- TL;DR: app files plus
Procfile
File | Description |
---|---|
main.py |
use as-is from repo |
credentials.json |
delete (or rename) if it exists (default credentials used in the cloud) |
app.yaml |
unused (delete or leave as-is; only for App Engine) |
appengine_config.py |
unused (delete or leave as-is; only for Python 2 App Engine) |
requirements.txt |
use as-is from repo |
lib |
delete (or rename) this folder if it exists (not used with Cloud Run) |
Dockerfile |
delete (or rename) this file (required) |
Procfile |
use as-is from repo |
- Delete
Dockerfile
,credentials.json
, andlib
(see above) - There is no support for Python 2 with Cloud Buildpacks (2.x developers must use Docker)
- Run
gcloud run deploy translate --allow-unauthenticated --platform managed
to deploy to Cloud Run; optionally add--region REGION
for non-interactive deploy - By default, App Engine & Cloud Functions launch production servers; with Cloud Run, the Flask development server is used for prototyping. For production, bundle and deploy a production server like
gunicorn
:- Uncomment
gunicorn
fromrequirements.txt
(commented out for App Engine & Cloud Functions) - Uncomment the
web:
entry forgunicorn
replacing the default entry inProcfile
- Re-use the same deploy command
- Uncomment
-
Google Cloud serverless product pages
- App Engine
- Cloud Functions
- Cloud Run
-
App Engine migration between runtimes/platforms
-
Cloud SDK and
gcloud
product pages- Cloud SDK
- Cloud SDK installation
gcloud
CLI (command-line interface)gcloud
cheatsheet
-
Cloud build-relevant product pages
-
Google AI/ML API product pages
- Cloud Translation home page
- Cloud Translation documentation
- Cloud Translation Python client library (v3 for 3.x)
- Cloud Translation Python client library (v2 for 2.x)
- Translation API pricing page
- All Cloud AI/ML "building block" APIs
- Google ML Kit (Cloud AI/ML API subset for mobile)
- Google ML Kit Translation API
-
Other Google Cloud documentation
-
External links
Testing is driven by nox
which uses pytest
for testing and flake8
for linting, installing both in virtual environments along with application dependencies, flask
and google-cloud-translate
, and finally, blinker
, a signaling framework integrated into Flask. To run the lint and unit tests (testing GET
and POST
requests), install nox
(with the expected pip install -U nox
) and run it from the command line in the application folder and ensuring the noxfile.py
file is present. Expected output:
$ nox
nox > Running session tests-2.7
nox > Creating virtual environment (virtualenv) using python2.7 in .nox/tests-2-7
nox > python -m pip install pytest blinker flask google-cloud-translate
nox > pytest
============================================ test session starts =============================================
platform darwin -- Python 2.7.16, pytest-4.6.11, py-1.10.0, pluggy-0.13.1
rootdir: /private/tmp/cloud-nebulous-serverless-python
collected 2 items
test_translate.py .. [100%]
============================================== warnings summary ==============================================
.nox/tests-2-7/lib/python2.7/site-packages/google/cloud/translate_v3/__init__.py:32
/private/tmp/cloud-nebulous-serverless-python/.nox/tests-2-7/lib/python2.7/site-packages/google/cloud/translate_v3/__init__.py:32: DeprecationWarning: A future version of this library will drop support for Python 2.7. More details about Python 2 support for Google Cloud Client Libraries can be found at https://cloud.google.com/python/docs/python2-sunset/
warnings.warn(message, DeprecationWarning)
-- Docs: https://docs.pytest.org/en/latest/warnings.html
==================================== 2 passed, 1 warnings in 1.02 seconds ====================================
nox > Session tests-2.7 was successful.
nox > Running session tests-3.6
nox > Creating virtual environment (virtualenv) using python3.6 in .nox/tests-3-6
nox > python -m pip install pytest blinker flask google-cloud-translate
nox > pytest
============================================ test session starts =============================================
platform darwin -- Python 3.6.8, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
rootdir: /private/tmp/cloud-nebulous-serverless-python
collected 2 items
test_translate.py .. [100%]
============================================= 2 passed in 1.22s ==============================================
nox > Session tests-3.6 was successful.
nox > Running session tests-3.9
nox > Creating virtual environment (virtualenv) using python3.9 in .nox/tests-3-9
nox > python -m pip install pytest blinker flask google-cloud-translate
nox > pytest
============================================ test session starts =============================================
platform darwin -- Python 3.9.1, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
rootdir: /private/tmp/cloud-nebulous-serverless-python
collected 2 items
test_translate.py .. [100%]
============================================= 2 passed in 1.04s ==============================================
nox > Session tests-3.9 was successful.
nox > Running session lint-2.7
nox > Creating virtual environment (virtualenv) using python2.7 in .nox/lint-2-7
nox > python -m pip install flake8
nox > flake8 --show-source --builtin=gettext --max-complexity=20 --exclude=.nox,.cache,env,lib,generated_pb2,*_pb2.py,*_pb2_grpc.py --ignore=E121,E123,E126,E203,E226,E24,E266,E501,E704,W503,W504,I202 --max-line-length=88 .
nox > Session lint-2.7 was successful.
nox > Running session lint-3.6
nox > Creating virtual environment (virtualenv) using python3.6 in .nox/lint-3-6
nox > python -m pip install flake8
nox > flake8 --show-source --builtin=gettext --max-complexity=20 --exclude=.nox,.cache,env,lib,generated_pb2,*_pb2.py,*_pb2_grpc.py --ignore=E121,E123,E126,E203,E226,E24,E266,E501,E704,W503,W504,I202 --max-line-length=88 .
nox > Session lint-3.6 was successful.
nox > Running session lint-3.9
nox > Creating virtual environment (virtualenv) using python3.9 in .nox/lint-3-9
nox > python -m pip install flake8
nox > flake8 --show-source --builtin=gettext --max-complexity=20 --exclude=.nox,.cache,env,lib,generated_pb2,*_pb2.py,*_pb2_grpc.py --ignore=E121,E123,E126,E203,E226,E24,E266,E501,E704,W503,W504,I202 --max-line-length=88 .
nox > Session lint-3.9 was successful.
nox > Ran multiple sessions:
nox > * tests-2.7: success
nox > * tests-3.6: success
nox > * tests-3.9: success
nox > * lint-2.7: success
nox > * lint-3.6: success
nox > * lint-3.9: success