Google Cloud App Engine Standard is an easy way to launch supported languages (Go, Java, PHP, Python and NodeJS) in a preconfigured cloud container.  Google gives you 1Gb of data and traffic for free before you start getting charged.  Let's check out App Engine Standard by creating a basic Python app.

Set up your Environment

First, let’s update our GCP command line tool in case it’s out of date.  I make this a habit every time i dig into GCP work.

Usually it will note that you have updates whenever you run a command:

Updates are available for some Cloud SDK components.  To install them,
please run:
  $ gcloud components update

We can then update

$ gcloud components update


Your current Cloud SDK version is: 252.0.0
You will be upgraded to version: 253.0.0

┌────────────────────────────────────────────────────┐
│         These components will be updated.          │
├────────────────────────────┬────────────┬──────────┤
│            Name            │  Version   │   Size   │
├────────────────────────────┼────────────┼──────────┤
│ BigQuery Command Line Tool │     2.0.44 │  < 1 MiB │
│ Cloud SDK Core Libraries   │ 2019.06.28 │ 11.0 MiB │
└────────────────────────────┴────────────┴──────────┘

The following release notes are new in this upgrade.
Please read carefully for information about new features, breaking changes,
and bugs fixed.  The latest full release notes can be viewed at:
  https://cloud.google.com/sdk/release_notes

253.0.0 (2019-07-02)
  Breaking Changes
      ▪ **(Compute Engine)** Deprecated gcloud beta compute resource-policies
        create-snapshot-schedule. Use gcloud beta compute resource-policies
        create snapshot-schedule instead.

  Cloud SDK
      ▪ Promoted --audiences flag of gcloud auth print-identity-token to GA.

  App Engine
    *
      ▪ Updated the Java SDK to version 1.9.76. Please visit the following
        release notes for details:
        <https://cloud.google.com/appengine/docs/java/release-notes>

  BigQuery
      ▪ Added support for persistent user-defined functions and stored
        procedures in bq show --routine, bq ls --routines, and bq rm --routine.
      ▪ Added support for creating scheduled queries; use bq query with
        --schedule as well as --destination_table and --target_dataset flags.
      ▪ Increased number of projects listed from the 'bq show' command.
      ▪ Added 'clustered fields' information for list dataset and show table
        commands.
      ▪ Modified bq show --connection to use the default location if the
        connection name is given in 'short' form.
      ▪ Fixed Python 3 compatibility issues.

  Cloud Firestore Emulator
      ▪ Release Cloud Firestore emulator v1.6.0.
        ◆ Calling SetSecurityRules with invalid rules is allowed.

  Cloud Key Management Service
      ▪ Promoted gcloud kms import-jobs and other import-related commands to
        beta.

  Cloud Run
      ▪ Removed --kubeconfig and --context flags and --platform=kubernetes
        option from gcloud beta run.

  Cloud SQL
      ▪ Updated gcloud sql instances describe to show the 'STOPPED' instance
        state, to be consistent with the 'gcloud sql instances list command.

  Compute Engine
      ▪ Fixed bug where gcloud compute images import would create temporary
        Cloud Storage bucket in US multi-region when source image file is in a
        different region.
      ▪ Promoted gcloud compute instances get-guest-attributes to GA.
      ▪ Promoted --storage-location flag of gcloud compute images create to
        beta.
      ▪ Modified gcloud compute backend-services <create|update> to relax
        validation for specifying --enable-logging, and --logging-sample-rate.
      ▪ Promoted http2 value to v1 for --protocol flag of gcloud compute
        backend-services <create|update>.
      ▪ Promoted gcloud compute start-iap-tunnel to GA.
      ▪ Promoted --tunnel-through-iap flag of gcloud compute ssh and gcloud
        compute scp to GA.
      ▪ Promoted --internal-ip flag of gcloud compute scp to GA.
      ▪ Promoted gcloud compute health-checks <create|update> http2 commands
        to GA.
      ▪ Promoted gcloud compute instances import to beta.
      ▪ Added --ssh_key_expiration and --ssh_key_expire_after flags to gcloud
        compute scp, and gcloud compute ssh, and gcloud compute
        connect-to-serial-port commands to set expirations for ssh keys.

  Kubernetes Engine
      ▪ Promoted Google Kubernetes Engine kubectl to 1.12.9 from 1.12.8, to
        address CVE-2019-11246.
      ▪ Promoted the following flags of gcloud container clusters update to
        beta:

      ▪ --database-encryption-key
      ▪ --database-encryption-key-keyring
      ▪ --database-encryption-key-location
      ▪ --database-encryption-key-project

        These flags control Database Encryption which encrypt Kubernetes Secrets at
        the application layer. For more information, see
        <https://cloud.google.com/kubernetes-engine/docs/how-to/encrypting-secrets>.

    Subscribe to these release notes at
    https://groups.google.com/forum/#!forum/google-cloud-sdk-announce
    (https://groups.google.com/forum/#!forum/google-cloud-sdk-announce).

Do you want to continue (Y/n)?  Y

╔════════════════════════════════════════════════════════════╗
╠═ Creating update staging area                             ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Uninstalling: BigQuery Command Line Tool                 ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Uninstalling: Cloud SDK Core Libraries                   ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Installing: BigQuery Command Line Tool                   ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Installing: Cloud SDK Core Libraries                     ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Creating backup and activating new installation          ═╣
╚════════════════════════════════════════════════════════════╝

Performing post processing steps...done.                                                                                                                     

Update done!

To revert your SDK to the previously installed version, you may run:
  $ gcloud components update --version 252.0.0

Next we need to login

$ gcloud auth login
Your browser has been opened to visit:

    https://accounts.google.com/o/oauth2/auth?redirect_uri=http%3A%2F%2Flocalhost%3A8085%2F&prompt=select_account&response_type=code&client_id=1234512345.apps.googleusercontent.com&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcloud-platform+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fappengine.admin+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcompute+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Faccounts.reauth&access_type=offline


WARNING: `gcloud auth login` no longer writes application default credentials.
If you need to use ADC, see:
  gcloud auth application-default --help

You are now logged in as [isaac.johnson@cyberdyne.com].
Your current project is [None].  You can change this setting by running:
  $ gcloud config set project PROJECT_ID

Then we need to set the default project ID (which we can get from our console page)

Getting API details from GCP dashboard
$ gcloud config set project api-project-799042244963
Updated property [core/project].

Setting up App Engine Standard

Next we need a quick Python sample app.  It works best to put the next two files in their own subdir.

$ cat myapp/myapp.py
import webapp2

class MainPage(webapp2.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'text/plain'
        self.response.out.write('I still like NodeJS better')

application = webapp2.WSGIApplication([('/', MainPage)], debug=True)

Next we need an app.yaml for GCP to know how to run it:

$ cat myapp/app.yaml 
application: myapp
version: 1
runtime: python27
api_version: 1
threadsafe: false

handlers:
- url: /.*
  script: myapp.application

We can first test our app with dev_appserver.py which comes with the GCP CLI installer and will launch our app on a dev instance:

$ dev_appserver.py myapp/
INFO     2019-07-06 14:47:16,249 devappserver2.py:278] Skipping SDK update check.
INFO     2019-07-06 14:47:16,339 api_server.py:275] Starting API server at: http://localhost:63354
WARNING  2019-07-06 14:47:16,340 dispatcher.py:338] Your python27 micro version is below 2.7.12, our current production version.
INFO     2019-07-06 14:47:16,347 dispatcher.py:256] Starting module "default" running at: http://localhost:8080
INFO     2019-07-06 14:47:16,349 admin_server.py:150] Starting admin server at: http://localhost:8000
INFO     2019-07-06 14:47:18,372 instance.py:294] Instance PID: 64150
INFO     2019-07-06 14:47:19,008 instance.py:294] Instance PID: 64152
INFO     2019-07-06 14:47:19,523 module.py:861] default: "GET / HTTP/1.1" 200 26

Deploying to GCP

Next we need to remove a couple lines GCP doesn’t care for and set our project (which i was surprised wasn’t already set from the start):

$ gcloud config set project api-project-799042244963
Updated property [core/project].

$ git diff
diff --git a/myapp/app.yaml b/myapp/app.yaml
index 2f3bb93..ad6a47a 100644
--- a/myapp/app.yaml
+++ b/myapp/app.yaml
@@ -1,5 +1,3 @@
-application: myapp
-version: 1
 runtime: python27
 api_version: 1
 threadsafe: false

Now we can deploy:

$ gcloud app deploy app.yaml 
You are creating an app for project [api-project-799042244963].
WARNING: Creating an App Engine application for a project is irreversible and the region
cannot be changed. More information about regions is at
<https://cloud.google.com/appengine/docs/locations>.

Please choose the region where you want your App Engine application 
located:

 [1] asia-east2    (supports standard and flexible)
 [2] asia-northeast1 (supports standard and flexible)
 [3] asia-northeast2 (supports standard and flexible)
 [4] asia-south1   (supports standard and flexible)
 [5] australia-southeast1 (supports standard and flexible)
 [6] europe-west   (supports standard and flexible)
 [7] europe-west2  (supports standard and flexible)
 [8] europe-west3  (supports standard and flexible)
 [9] europe-west6  (supports standard and flexible)
 [10] northamerica-northeast1 (supports standard and flexible)
 [11] southamerica-east1 (supports standard and flexible)
 [12] us-central    (supports standard and flexible)
 [13] us-east1      (supports standard and flexible)
 [14] us-east4      (supports standard and flexible)
 [15] us-west2      (supports standard and flexible)
 [16] cancel
Please enter your numeric choice:  12

Creating App Engine application in project [api-project-799042244963] and region [us-central]....done.                                                       
Services to deploy:

descriptor:      [/Users/isaac.johnson/Workspaces/gcloud-appengine/myapp/app.yaml]
source:          [/Users/isaac.johnson/Workspaces/gcloud-appengine/myapp]
target project:  [api-project-799042244963]
target service:  [default]
target version:  [20190706t095130]
target url:      [https://api-project-799042244963.appspot.com]


Do you want to continue (Y/n)?  Y

Beginning deployment of service [default]...
╔════════════════════════════════════════════════════════════╗
╠═ Uploading 2 files to Google Cloud Storage                ═╣
╚════════════════════════════════════════════════════════════╝
File upload done.
Updating service [default]...done.                                                                                                                           
Setting traffic split for service [default]...done.                                                                                                          
Deployed service [default] to [https://api-project-799042244963.appspot.com]

You can stream logs from the command line by running:
  $ gcloud app logs tail -s default

To view your application in the web browser run:
  $ gcloud app browse

Let’s run that command to launch a browser to view our site:

$ gcloud app browse
Opening [https://api-project-799042244963.appspot.com] in a new tab in your default browser.

Updating an app:

Now let’s change and redeploy:

$ git diff myapp.py
diff --git a/myapp/myapp.py b/myapp/myapp.py
index ae0ea6f..2dc3184 100644
--- a/myapp/myapp.py
+++ b/myapp/myapp.py
@@ -3,6 +3,6 @@ import webapp2
 class MainPage(webapp2.RequestHandler):
     def get(self):
         self.response.headers['Content-Type'] = 'text/plain'
-        self.response.out.write('I still like NodeJS better')
+        self.response.out.write('Python is just fine')
 
 application = webapp2.WSGIApplication([('/', MainPage)], debug=True)


$ gcloud app deploy app.yaml 
Services to deploy:

descriptor:      [/Users/isaac.johnson/Workspaces/gcloud-appengine/myapp/app.yaml]
source:          [/Users/isaac.johnson/Workspaces/gcloud-appengine/myapp]
target project:  [api-project-799042244963]
target service:  [default]
target version:  [20190706t123307]
target url:      [https://api-project-799042244963.appspot.com]


Do you want to continue (Y/n)?  Y  

Beginning deployment of service [default]...
╔════════════════════════════════════════════════════════════╗
╠═ Uploading 1 file to Google Cloud Storage                 ═╣
╚════════════════════════════════════════════════════════════╝
File upload done.
Updating service [default]...done.                                                                                                                           
Setting traffic split for service [default]...done.                                                                                                          
Deployed service [default] to [https://api-project-799042244963.appspot.com]

You can stream logs from the command line by running:
  $ gcloud app logs tail -s default

To view your application in the web browser run:
  $ gcloud app browse

Canary deployments and Traffic Splitting

We can also split traffic between the new version and the last.  This is useful if you wish to roll out a deployment slowly (Canary) or test routines (A/B testing) for end user reaction.

$ gcloud app services set-traffic default --splits 20190706t095130=0.5,20190706t123307=0.5
Setting the following traffic allocation:
 - api-project-799042244963/default/20190706t123307: 0.5
 - api-project-799042244963/default/20190706t095130: 0.5
NOTE: Splitting traffic by ip.
Any other versions of the specified service will receive zero traffic.
Do you want to continue (Y/n)?  Y

Setting traffic split for service [default]...done.

Note: we got the versions for our command line from the output above.  but we can always look up versions and existing split rules from the command-line as well:

$ gcloud app services describe default
id: default
name: apps/api-project-799042244963/services/default
split:
  allocations:
    20190706t095130: 0.5
    20190706t123307: 0.5
  shardBy: RANDOM

Random splitting:

launching in different browsers

GCP Console Details

We can see details of our service in the GCP console as well:

Noting traffic and versions in the main project dashboard

We can go to the App Engine dashboard to see details including billing:

Billing details in the dashboard

The website also lets us examine all the versions of the code we have and our traffic splitting rules:

seeing traffic split between two versions

Under tools we can view logs and source:

Looking at logs and source for any given version
Stackdriver debug

We also have the power to change the traffic splitting, even doing things like randomly switching:

Adjusting traffic split in the UI

We can also use Migrate Traffic to force all traffic to one version:

Migrating all the traffic to one version

We can also pick a version and choose delete to remove it.  This is helpful when we are remotely managing an app and a deployment is causing issues.  We can roll back to a prior version and delete the problematic deployment.

I’ve left it split randomly 50/50 if you want to try it: https://api-project-799042244963.appspot.com/

Summary

GCP App Engine Standard is a great way to launch a containerized app using a handful of well known and support languages.  The free plan gives us a great way to try containerized web apps without the need of managing our own kubernetes cluster.