Migrating App Engine to Go 1.11 – the price of vendor lock-in
If you are using App Engine on the Google Cloud Platform (GCP) you shouldn’t be too surprised when receiving an email stating the current version you are using is being deprecated. Chances are you haven’t read Google’s deprecation policy, which also applies to App Engine (listed here). which basically states that if Google decides to upgrade their services and deprecate older version, they can. And will. And you will be forced to follow along.
So of course, the email which came on June 27, 2019 should not have come as a surprise to you:
Hello Google App Engine Customer,
We’re writing to let you know that Go versions prior to 1.11 are no longer supported on App Engine, and new deployments using these language versions (1.6, 1.8, and 1.9) will not be available starting October 1, 2019.
This left about 3 months to upgrade.
How much effort?
What does this upgrade exactly mean? Is it just changing some configuration files? Well, yes and no. The migrating guide explains what you need to do. It contains some required and some recommended changes.
There are several required changes for upgrading to the go1.11 runtime one needs to do, while keep using the App Engine specific APIs.
In your app.yaml (or service.yaml) the “runtime” should be set to go111 and the “api_version” should be removed. Under the url property the “application_readable: true” should be removed, this applies by default now. Furthermore the “script: _go_app” should be replaced by “script: auto”. The “includes” tag is deprecated, but fortunately can still be used. The url handlers “login:admin” can still be used, but is also deprecated and will be unsupported with the go1.12 runtime. You might need to change some more things, but you’ll get clear error messages when deploying your application with “gcloud app deploy […]”.
You’ll need to change your application’s entry point. The package must be called “main” and the entrypoint function called “main()” as well. The filename does not matter. At the end of this main() function you’ll need to add “appengine.Main()” if you still want to use App Engine specific APIs. If you don’t, you should read the PORT environment variable and use it to call the http listen and serve method, otherwise your application will not continue running after starting up.
App Engine build tag
In the old runtime one could have two files with identical function names, one with “//build appengine” at the top and another with “//build !appengine”. During the build process one of the files would be built in the App Engine environment, the other while e.g. running unit tests. This functionality is deprecated, which means the file with the App Engine build tag will be completely ignored. There are of course several solutions, our approach was to toggle between an App Engine and a mock function based on the “appengine.IsAppengine()” function. Or maybe even better, to perform the check
os.Getenv("GAE_ENV") == "standard"
Once you have successfully deployed your application, you will notice that your logs are not as pretty as they used to be. The automatic grouping of prints per request is not part of the standard logging anymore and requires some work to get perfect. More on that in a future post.
All of the other changes mentioned in the migration guide are optional, at least for now. When the go1.11 runtime will be deprecated and you start moving to the go 1.12 runtime, these recommended changes become required.
The App Engine packages (google.golang.org/appengine) should be replaced by Cloud packages (cloud.google.com/go). For example Task Queues should be replaced by Cloud Tasks and the Blobstore API should be replaced by Cloud Storage.
Unfortunately not all App Engine packages have a Cloud replacement package. This applies to the App Engine Mail API, Memcache and Search API. If you use these, the migration process is a lot more complicated, since you’ll need to compare third-party providers and choose which one you’ll pick.
Finally, all of this also has an impact on your local development environment as the Cloud packages do not integrate with dev_appserver.py. Instead emulators are provided for most but not all services.
At this point, you might think, “hang on, so I spent all this time to make my Go application work in App Engine by using their APIs and now suddenly they force me to migrate to a newer version and remove some of those APIs”. You could see this as a bad consequence of vendor lock-in. You are tied to a specific vendor and you are basically at the disposal of their whims. However, there is a reason behind the madness.
To understand this better, we need to go back to the genesis of App Engine. App Engine was launched by Google in 2008, which was their first Google Cloud product, together with the Datastore. At that time AWS by Amazon was already active for some years. Amazon started by providing a way of moving from on-premise to the cloud without too much adjustments, by providing file storage and virtual machines. However, Google took a different approach with App Engine. They basically stated “upload your code and we’ll run it”. For convenience, specific APIs were added to App Engine, some of which are now being deprecated.
The deprecation of those App Engine specific APIs in favour of more general Cloud APIs, is arguably actually a good thing. For one, you will get a more idiomatic Go application, using a main package and function to start it. Also, being less dependent on specific App Engine APIs you get the freedom to host and run your Go application outside of the Google Cloud, essentially removing the vendor lock-in.
Initially this migration might look like a daunting task, but all in all it is very doable. While you can’t avoid this migration if you want to continue deploying new versions of your application, it comes with some nice added benefits like being able to use Go modules.
This blog is the start of a series of blogs in which we go over the recommended changes to make sure that you are ready for Go 1.12.
Written by Laurence de Jong and Benjamin Komen