Google App Engine basics

App Engine is a fully managed serverless platform, as google defines it.

Similar to Compute Engine, App Engine lets you host your backend code, but unlike Compute Engine, you don’t manage the infrastructure like load balancing, scaling etc.

App Engine costs literally nothing to start with, but it’s pretty expensive on scale.

Does it mean that evil corporations try to lure defenseless coders into using their services with tasty free tiers? Let's figure this out today! And also let's do something practical, like deploying our web server to App Engine.

If you see that something on Google Cloud has seemingly unfair pricing, then most likely you are using it wrong. You may have heard scary stories about Firebase surprising people with huge bills. Well, if you dig deeper you’ll see that it was the users' fault. The Firebase Real Time DB is a good substitute for the websocket connection, not for the entire primary database.

Anyways... App Engine does the scaling for you. It does the load balancing for you. It does "some" caching for you. It manages secure https connections and redirects http calls. It provides convenient health-checks and simple UI to manage it. Zero downtime rolling updates and more. And this is just the tip of the iceberg. All the things the App Engine does is definitely worth the cost... if you actually need all of these features, of course.

Aaand there is one pretty big BUT about the serverless-nes that is everywhere, yet not enough people talk about it. The local development problems. Replicating serverless infrastructures locally is quite difficult sometimes. Unlike Cloud Functions, App Engine doesn’t even try to provide its infrastructure simulator for local development. And it's okay... most of the time.

App Engine has a “Standard” and “Flexible” environment. Which translates into a “truly fully managed environment” and “you better use Cloud Run” respectively. Because of the essence of App Engine, the only reason to overpay for it on a larger scale, is in the fully managed Standard environment.

And another big thing to keep in mind: App Engine is not multi-regional. When you first enable App Engine on your project, you choose the datacenter location and you can never change it. You can try some voodoo magic to create multiple Google Cloud Projects and setup an external load balancer but the effort beats the purpose here. If you need multi-regional scaling, then App Engine is simply not for you. I'll write about multi-regional solutions in the upcoming articles.

Create a simple server

First, create a very basic web server. I’ve created one using NodeJS.

const http = require('http')
const moment = require('moment')

const server = http.createServer((req, res) => {
  const headers = { 'Access-Control-Allow-Origin': '*' }
  res.writeHead(200, headers)
  res.end(moment().format('YYYY-MM-DD hh:mm a'))

server.listen(process.env.PORT || 8080)

It's just a basic http server that returns current date and time. Couple of things to notice though:

  1. First, I set Access-Control-Allow-Origin header to allow cross-origin access.
  2. Second, App Engine provides environment variable PORT, so I set it as a default port for my server backed up by port 8080 for local development.

The other available runtimes are Python, Java, PHP, Ruby and Go, so you can use any of those instead of Node.

Configure app.yaml

Create app.yaml. It’s the main blueprint of your App Engine application infrastructure.

Let’s take a look at the minimal production-ready config.

runtime: nodejs12

instance_class: F1

  - url: /.*
    secure: always
    redirect_http_response_code: 301
    Access-Control-Allow-Origin: *
    script: auto

  max_instances: 5

  - warmup

The instance_class defines the size of a single VM running your server. It varies from 256 MB Memory and 600 MHz CPU to 2GB Memory and 2.4 GHz CPU. Keep in mind that App Engine is designed for horizontal scaling, which means that when it runs out of memory or CPU it creates additional VMs and offloads some traffic to these new VMs. This process takes seconds and, btw, your app boot up time matters here a lot.

The cost varies from 5 to 30 cents per hour based on the instance class. Billing is per-second but each instance stays alive for 15 minutes after the last request, which makes sense if you think about it.

Btw this 15 minutes after the last request are not configurable, so if you put a long-running task there, then it may get terminated, if your server is not serving new requests or if health checks fail or... Anyways App Engine is not designed for long-running tasks, keep that in mind.

handlers define what routes are available on your app. In this case I make all routes available with this wildcard tag. I declare that all requests must go through secure https connection and all http requests must be permanently redirected.

automatic_scaling defines, well… automatic scaling settings. Always put max_isntances to whatever number you can feasibly afford, if you don't wanna get a surprise bill from Google one morning, because the valid range here is from 0 to over 2,147,483,647, where 0 disables the max_isntances setting. It’s disabled by default. As a matter of fact, even if your max_isntances setting disabled, you still limited by your CPU quota, which is set to 32 vCPUs by default and can be increased upon request.

And the last config here is warmup. By default App Engine scales down to 0 instances, if no requests hit it for 15 minutes in a row. If all instances are down, then the first request will take several seconds to spin up a new instance and serve the user’s request. I’d recommend to enable the warmup setting, so Google Cloud would make periodic requests to your server to keep at least one instance alive. To be specific it makes a GET request to this url (/_ah/warmup). You can handle it anyway you want, just make sure to reply with status 200.

How to deploy

Before you can deploy your server to App Engine, you have to enable it. First login with your Project Owner account, because enabling App Engine requires a high level permission and needs to be done only once per project.

gcloud auth login

Then run gcloud app create... which doesn't create anything actually. It just prompts you to select a region and sets it. Forever.

That's it, now whenever you feel like deploying something, just run gcloud app deploy. And, as usual, make sure you are using the right service account on the accurate project and zone:

gcloud auth activate-service-account --key-file=./key.json
gcloud config set project $PROJECT_ID
gcloud config set compute/zone us-central1-a

Local development

Google App Engine doesn’t have any local simulator. The docs are suggesting to literally run your server start command using your runtime. No containerisation, nothing. Without containerisation you can't make sure that your server will behave identically on your local machine and in the cloud. So, put debug logs everywhere. Deploy your app to your staging GCP with debug logging level and test it there. Keep the logs there, but change logging level in production. That’s it. It doesn’t make sense to wrap your app into a docker container for development, because the exact hardware and software configuration of Google App Engine is unknown.