Skip to content

Instantly share code, notes, and snippets.

@lestrrat
Created November 26, 2018 02:22
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lestrrat/db1e1310efc0d52cae563d2e9130fd91 to your computer and use it in GitHub Desktop.
Save lestrrat/db1e1310efc0d52cae563d2e9130fd91 to your computer and use it in GitHub Desktop.
# App Engine Standard Go 1.9 migration to Go 1.11
tag:["google-app-engine", "Go"]
Migrating from Go 1.9 to 1.11 in App Engine Standard isn't just a matter of updating Go itself -
it introduces a major update in the runtime itelf. Because of this, you can't just change your
code to work on Go 1.11 and expect it to be deployed properly. You will need to work on a few
more issues.
This article describes the new runtime known as the Google App Engine Standard 2nd Gen,
guides you through how much extra work needs to be done, and gives you some suggestions
on when to actually make the leap into the new runtime.
For descriptions on Google App Engine Standard 2nd Generation, you can peruse the following articles (in Japanese)
* [Java 8 ランタイム以降のサンドボックスと gVisor by @apstndb](https://docs.google.com/presentation/d/1GKkv6GAelTwieGThqnX28f6dc7fXKwDPuypRzCYT_Zk/edit#slide=id.p)
* [GCP のサーバーレスを支える gVisor by @apstndb](https://docs.google.com/presentation/d/14AAOJFsf9bSkJPA0rSAwFcn5XIcoalntCsUN8pTGO00/edit#slide=id.p)
* [gVisorとGCP by @apstndb](https://docs.google.com/presentation/d/1F6k6bBS7BOUQWl9WGEpQJDyfvd04Et_-EeIHRoQzz-Y/edit#slide=id.p)
## 2nd Generation - The Good Parts
2nd Generation was born out of the desire to fix the following problems that existed in 1st Generation
### Write Regular Go (or your language of choice)
In the 1st Generation Go runtime, you were restricted in what you can use: for example, you could not
use the unsafe package. This was not a show stopper, but you end up not being able to use certain
libraries.
Communication with the outside world also required a special App Engine SDK.
For example, for http, you needed App Engine URLFetch API. For sockets, you needed
App Engine Socket API.
In the 2nd Generation runtime you can just use http.Client et al.
You were also not allowed to write a local file, so using libraries that writes
to temporary files were problematic. In the 2nd Gen runtime, you can write to `/tmp`
and therefore writing to temporary files should now work.
### Deprecating the App Engine API
App Engine is not just an HTTP Server. It has many other capabilities. The list of
availble API includes Log, Datstore, Memcache, Users, Mail, Search, TaskQueue, Image,
and Blobstore.
These use to exit since the days before Cloud Platform, and were available via the
App Engine SDK. These are useful tools to have, but they are specific to App Engine
and can lead to code that is strongly tied into App Engine.
A decision has been made to avoid using these App Engine specific packages.
For example if you were to output some log in the runtime up to 1.9, you needed
to use the `google.golang.org/appengine/log` package. However, in the 1.11 runtime
you can just write to stdout for the logs to be written.
You no longer need to use urlfetch or socket. These were required even when
you wanted to communicate with other GCP services such as Cloud PubSub and
Cloud Storage. They also counted against your quota and therefore severly limited
their usefulness.
### Using Google Cloud Spanner
You can now connect to Cloud Spanner in 2nd Gen runtime.
## The Bad Parts
### Deprecation of `login:required` , `login:admin`
You can no longer limit access to resources using `login:required` and `login:admin` in your `app.yaml` file.
You need to replace this functionality, but unfortunately GCP does not offer a complete drop-in replacement for it as of Nov 25, 2018.
Cloud Indeity Aware Proxy offers a similar functionality, but it does not complete replace Users Service. For example,
you cannot just specify a setup like:
- `default` service requires no authentication
- `/mypage` requires authentication
- `admin` aservice requires authentication, and you must be an admin
You can actually change the members that can be authenticated for each service, but you cannot change this for each request path.
And you also cannot disable authentication for a particular service.
The official docs tell you to migrate to Firebase Auth, but you actually need to put a significant amount of work
in order to make this work
### Request Logs no longer aggregate
Previously all application logs were aggregated for a single request, and they were sent as a single entity in Stackdriver Logging.
However, 2nd Gen records each log output as a separate entity, so when you have multiple requests it becomes very hard to scour through the logs.
This also adds extra information in each log entity, and therefore impacts the log size.い点です。
### Deprecation of 'includes' directive in app.yaml
The `includes` directive allowed to include the contents of another file. It was very useful to change some
settings depending on where you were deploying to, but now you have to resort to shell trickery to achieve
the same behavior
### You still need to use the App Engine API
In the Go 1.11 App Engine environment the old App Engine API is still supported, so you do
not have to completely remove their use immediately. However, removing these will also
lead to easier unit testing and such, so in general it's something that you should be doing.
However, except for some cases such as `urlfetch` and `socket`, there are still many functionalities
that do not have proper replacements/migration targets within GCP
#### Replacements for App Engine API
* Users -> Identity-Aware Proxy?
* Memcache -> Cloud Memorystore for Redis
* Datastore -> Cloud Datastore
* Search -> ...?
* Mail -> SendGrid?
* TaskQueue -> Cloud Tasks
* Cron -> Cloud Scheduler
* Image -> ...?
##### Users -> Identity-Aware Proxy?
As stated before, it doesn't quite offer the same functionality
##### Memcache -> Cloud Memorystore for Redis
While memorystore provides the functionality, it only allows access from the same VPS and you cannot
access it from App Engine Standard. You will need to wait for something like Cloud SQL Proxy to be
implemented.
##### Datastore -> Cloud Datastore
The backends are the same, but the APIs are different so you will have to change your code.
In case you are using [goon](https://github.com/mjibson/goon) or [nds](https://github.com/qedus/nds),
you cannot migrate until these libraries be updated, as they are heavily tied to `google.golang.org/appengine`.
On the other hand, it's one way to just wait and sit out until these libraries are updated.
You could also use [go.mercari.io/datastore](https://github.com/mercari/datastore) which works with
either `google.golang.org/appengine` or`cloud.google.com/go/datastore`
##### Search -> ...?
There is no service that offers full text search using morphological analysis.
The official docs suggest that you run your own Elastic Search
##### Mail -> SendGrid?
Mail API has been deprecated a while back, so you shold use some other third party solutions.
##### TaskQueue -> Cloud Tasks
[Cloud Tasks](https://cloud.google.com/tasks/) is now in beta. However, it only supports push queues, and no pull queues.
Also, when you migrate to using Cloud Tasks you should note that TaskQueue.Add cannot be part of a Datastore Transaction
##### Cron -> Cloud Scheduler
[Cloud Scheduler](https://cloud.google.com/scheduler/) is now in beta.
##### Image -> ...?
Image Service was actually very useful, and it was a great way to create dynamic thumbnails.
There are no equivalent solutions within GCP, so you should be looking at third party solutions
such as image optimization services in various CDNs
* [Akamai Image Manager](https://www.akamai.com/jp/ja/products/web-performance/image-manager.jsp)
* [Fastly Image Optimization](https://www.fastly.com/io)
* [SAKURA Internet Image Flux](https://www.sakura.ad.jp/services/imageflux/)
## Migratin to 2nd Generation
In general [you just need to do what the official docs tell you to](https://cloud.google.com/appengine/docs/standard/go111/go-differences#migrating-appengine-sdk)
The bare minimum you need to do is to update your `app.yaml` and to create a `main` package.
The following are a few things that you may get stuck while doing this migration.
### appengine.Main()
The samples on the official document tell you to use `http.ListenAndServe` to start your server.
However, this only works if you have completely removed the use of the old App Engine API.
If you are still using them, you need to use `"google.golang.org/appengine".Main()` instead.
```
// This only works if you have removed the use of old App Engine API
port := os.Getenv("PORT")
if port == "" {
port = "8080"
log.Printf("Defaulting to port %s", port)
}
log.Printf("Listening on port %s", port)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil))
```
### script:auto
The documentation in [Migrating your App Engine app from Go 1.9 to Go 1.11](https://cloud.google.com/appengine/docs/standard/go111/go-differences#migrating-appengine-sdk)
does not mention that you need to change the `script` element. Where you used to write `script: _go_app`, you need to change
to `script: auto`.
Without this, you will get an error when you deploy your code
Here's how you fix it: [app.yaml](https://github.com/sinmetal/gcpugjp/commit/b5cdd4aa10c65719713fc31fbf4b8f63525e7162#diff-bb68be076839a38efa2ebc731942536a)
```
- url: /.*
script: auto
```
### Working Directory change from local to production environment
Up to Go 1.9, the initial working directory was where your `app.yaml` was located.
From Go 1.11 on, the initial working directory is where your `go.mod` file is.
However, if you run your code through `dev_appserver.py` in your local environment,
your initial working directory is still where your `app.yaml` is, even when you are
running Go 1.11 code.
This leads to `no such file or directory` errors when you have files specified as
relative paths, such as `./template/index.html`'
There are two workarounds for this. First, you could just place `app.yaml` and `go.mod` in the same directory.
For example, if you just put the main package in the root path, it would just work:
[Sample](https://github.com/sinmetal/codelab/commit/f3eb2cacb0af05bcd733742e225fe68544fda2b3).
The other is to be creative with simlinks and allow access from either environments.
## Goals for 2nd Generation
It looks as though the 2nd Generation runtime is attempting to remove the legacy cruft
that came with App Engine from before GCP was born.
App Engine Standard 1st Gen was a complete product in itself, but it didn't quite fit
the world according to GCP. Attempts has been made to rectify this by introducting
products such as [Managed VMs](https://qiita.com/sinmetal/items/68f0e21e1f33e3a553a1) and
[App Engine Flex](https://qiita.com/sinmetal/items/080a79702b060b33de69) but they could not
quite completely fix all issues.
Now the 2nd Generation is trying to create a new PaaS for web apps that work more seemlessly
with GCP. However, this transition has just started, and probably will take some time to be completed.
## When should you update to Go 1.11?
If you are creating a new App Engine application, you probably should make it using Go 1.11
However, there will be big breaking changes if you are migrating an existing application to 2nd Gen.
Go 1.11 is still in beta. End of life for Go 1.19 will probably come around 2021, so you do not
have to be in a hurry to perform this migration.
One of the big road blocker is the missing `login:admin` auth feature. If you are dependent on this,
you could just sit tight until [Cloud Identity-Aware Proxy](https://cloud.google.com/iap/) evolves
to support it.
Unless you have reasons like "I really want to use Go 1.11 features!", "urlfetch and socket are really
hurting our development", or if you just want to use Cloud Spanner, we believe you can wait to upgrade
for a little bit longer.
On the other hand if you are not using `login:admin`, or if you don't care if your logs are not
aggregate per-request, it's relatively easy to migrate. In this case you can certainly go head
with the migration
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment