La Vita è Bear

Switch from Google App Engine to Cloud Run

When I wrote NotifBot Android app, I needed a backend for it (both for handling Android app requests and to handle Telegram webhooks), and I picked Google App Engine for it as it has a generous free tier, that I can run the backend almost free.

Later on I added more App Engine backends: url2epub’s Telegram bot, and go.yhsif.com for my go libraries’ vanity URLs. But even with all those combined, plus a Compute Engine for my websites, I still pay less than $10 every month because of the free tiers.

Then when I started to use PolarBearBlog, I learnt about Google Cloud Run, which is similar to App Engine in some ways but also different in other ways. Today I finally decided to switch all my 3 App Engines to Cloud Runs.

So why the switch?

There’s one big thing about App Engine that annoys me: For its standard environment (which is the only environment with free tier), the go runtime only supports up to Go 1.16, but with the release of Go 1.19 earlier this month, the oldest supported version of go is 1.18, so App Engine’s supported go version is already out of support and severely lagged behind.

Cloud Run is kind of like App Engine’s flexible environment that you can use whatever runtime you like, so I can just use the latest Go version, with the nice atomic.Pointer with it.

Another thing is about billing. From what I understand, App Engine bills on the accumulated time your instances are running, while Cloud Run bills on the actual CPU and memory resources used. So say you limit the instance to 1 CPU and run it for an hour, with App Engine you are billed for one hour, but with Cloud Run, if your average CPU utilization is only 25%, then you are only billed for 15 minutes (for the CPU). We’ll see in 2 months whether this actually saves me some money :)

Another thing, that mostly only applies to url2epub, is that with Cloud Run I can actually choose my own cpu to memory ratio. With App Engine I can only choose from pre-defined instance classes, which more or less all have the same cpu to memory ratio. But url2epub actually uses more memory because it needs to cache all images in memory, and I got it OOM killed several times before when converting image heavy articles and had to bump it to more expensive instance classes to make those work. With Cloud Run, I can increase memory without increase cpu.

How to switch?

App Engine Go SDK provided some packages under google.golang.org/appengine/v2 to help you interact with other resources. Those are obviously no longer available with Cloud Run, but for the majority of them there are replacements:

appengine.Main

Instead of using appengine.Main directly, you can just get the port from PORT environment variable and start the HTTP server manually:

	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
		l(ctx).Warnw(
			"Using default port",
			"port", port,
		)
	}
	l(ctx).Infow(
		"Started listening",
		"port", port,
	)

	l(ctx).Infow(
		"HTTP server returned",
		"err", http.ListenAndServe(fmt.Sprintf(":%s", port), nil),
	)

datastore

Instead of using App Engine’s datastore library (google.golang.org/appengine/v2/datastore), you can use Google Cloud’s datastore library (cloud.google.com/go/datastore). You no longer can just use the request context to gain access and need to create a client with your project ID (you also lose the auto injected environment variable of the project ID from App Engine, but its trivial to stamp it into your Cloud Run’s environment variables via --update-env-vars args from gcloud run deploy).

memcache

This is something without a replacement as it’s only available in App Engine, but it’s not a big deal. During the second generation App Engine transition the whole memcache was unavailable for a long time, and it’s only added back recently, so at least for my projects, it’s more of a “nice bonus” than “must haves”.

secretmanager

This is not really under App Engine to begin with, but with Cloud Run, I can actually stamp secrets as environment variables directly via gcloud run deploy’s --set-secrets args, or mount secrets as files, instead of using the API to get secrets directly.

#English #tech #cloud #go