在GAE第二代上使用Go v1.12构建一个echo服务器(2019年版)
总结
以前我已经发布过关于建立环境的帖子,但它们都是旧的,所以这次我决定再次尝试建立适用于最新的GAE第二代的环境。
另外,此文章的源代码已在GitHub上公开。
https://github.com/brbranch/GaeGo112EchoSample/tree/gae_echo
2019年9月7日
我写了续集。
尝试使用Redis Cloud来替代GAE上的Mercari/datastore的Memcache连接【GAE/Go1.12】
https://qiita.com/br_branch/items/1b63b2c1dd9b4ff3931e
过去的文章 de
使用Golang1.8+echo+GoLand搭建环境,可以在Google App Engine上运行本地服务器。
Go语言初学者可以使用GAE+Echo(v2)+goon搭建服务器。
这次的目标
-
- GAE/Go1.12 + echoサーバーを構築し、Datastoreにアクセスする。
-
- ローカルでの開発環境を構築する
- ローカルでDatastore含めたテストを行う
行为或活动的背景条件
-
- Mac OS 10.14
-
- go1.12.9
-
- Google Cloud SDK 253.0.0
-
- echo v3
- mercari/datastore v1.6.1
从GAE第二代开始,将无法使用AppEngine API。
以前我使用的是goon,但是从这一次开始我打算试试用mercari/datastore的boom。
因为GAE/go1.12(GAE第二代)开始,无法使用AppEngine API。更多详情请参考https://cloud.google.com/appengine/docs/standard/go112/go-differences?authuser=0&hl=ja#migrating-appengine-sdk。
App Engine 不会修改 Go 工具链以包含 appengine 包。如果您正在使用 appengine 包或 google.golang.org/appengine 包,请迁移到 Google Cloud 客户端库。
(省略)
要在 App Engine 中使用 Memcache 服务,请使用 Redis Labs Memcached Cloud 替代 App Engine Memcache。
しれっとひどいことやってきますね。
goonはAppengine APIを前提としてるっぽいので、どうも使えないようなんです。
或许并不只有坏事。听说从go1.12开始要使用App Engine的第二代,虽然不太明白有什么改进,但既然是第二代,可能会有一些变好的地方。
詳細:Google App Engine 标准环境的运行时
https://cloud.google.com/appengine/docs/standard/runtimes
虽然不太清楚什么会改善,但感觉好像有些东西会变好,这是好事。
太好了,太好了。
我认为自由度肯定会增加。顺便说一下,看起来也不能再使用AppEngine Memcache了。
首先尝试以最简配置进行运行。
首先,我們將引入 echo,並在本地運行 Hello World,直到完成。
最初的文件夹组织
我做了以下的改变。
backend (GOPATH)
├-- src (作業フォルダ)
├-- .envrc
└-- .go-version
第一步,首先要确保能够以最低限的要求运行。
首先,为了确认最低限度的工作并能够部署,请将GAE的快速入门示例(helloWorld)放入src文件夹中。
把名字只保留在main.go / main_test.go等文件中。
backend (GOPATH)
├-- src (作業フォルダ)
│ ├ app.yaml
│ ├ main.go
│ └ main_test.go
├-- .envrc
└-- .go-version
然后,我将尝试正常部署。
$ cd ./backend/src
$ $ gcloud app deploy
Services to deploy:
descriptor: [/Users/xxxxx/git/my/xxxxxx/backend/src/app.yaml]
source: [/Users/xxxxx/git/my/xxxxxx/backend/src]
target project: [xxxxxx]
target service: [default]
target version: [20190906t203700]
target url: [https://xxxxxx.appspot.com]
Do you want to continue (Y/n)? Y
Beginning deployment of service [default]...
Created .gcloudignore file. See `gcloud topic gcloudignore` for details.
╔════════════════════════════════════════════════════════════╗
╠═ Uploading 3 files to Google Cloud Storage ═╣
╚════════════════════════════════════════════════════════════╝
File upload done.
Updating service [default]...done.
Setting traffic split for service [default]...done.
Deployed service [default] to [https://xxxxx.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

真好啊!
第二步,尝试在本地运行。
从 `go1.12` 开始,可以简单地在本地使用 `go run` 命令来运行程序。
也就是说,不再需要使用 GAE 本地服务器了。
$ go run main.go
2019/09/06 21:18:30 Defaulting to port 8080
2019/09/06 21:18:30 Listening on port 8080

哦,原来如此。
第三步:安装Go Module
接下来,我们将实际安装Go模块并进行部署的尝试。
$ go mod init gaego112echosample
go: creating new go.mod: module gaego112echosample
$ ls
app.yaml go.mod main.go main_test.go
暫時先不使用,但是我會加入echo。

由于没有包含库文件,将会导致赤字。
我们来尝试构建一下。
$ go build
go: finding github.com/labstack/echo v3.3.10+incompatible
go: downloading github.com/labstack/echo v3.3.10+incompatible
go: extracting github.com/labstack/echo v3.3.10+incompatible
go: finding github.com/labstack/gommon/log latest
go: finding github.com/labstack/gommon/color latest
︙
然后,情况就会变成这样。

如果您在环境变量中设置了 GO111MODULE=on,在 $GOPATH 中设置的位置将不会下载软件包,而是下载到 go env GOPATH 输出的位置。
在这个状态下,我尝试了相同的部署。以前在使用dep或glide时可能会遇到依赖问题或发生错误,但即使现在也能够无任何问题地部署并运行。

第四步。创建为Echo服务器。
好的,下一次我們將開始集成Echo。
首先,最基本的就可以了。
package main
import (
"fmt"
"github.com/labstack/echo"
"log"
"net/http"
"os"
)
func main() {
e := echo.New()
http.Handle("/", e)
e.GET("/", func (e echo.Context) error {
return e.String(http.StatusOK, "Hello Echo!")
})
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))
}
在本地或部署后都能正常运行。


很开心。
第五步:生成日志
如下所述,google.golang.org/appengine 现在已经无法使用。
可以使用标准的log包作为替代。
让我们试试看,这意味着它可以从任何地方轻松地被召唤出来。
e.GET("/", func (e echo.Context) error {
log.Printf("Logger test") /// 追加
return e.String(http.StatusOK, "Hello Echo!")
})
当您部署并访问时,通常会在日志中显示。

嗯,这个挺轻松的呢。
第六步。处理DataStore。
现在,我们终于要开始处理DataStore了。顺便一提,我们还要尝试将处理过程拆分为包。
正如之前提到的,由于第二代AppEngine无法使用AppEngine API,所以我们以前使用的google.golang.org/appengine/datastore已经无法使用了。
相反,我们将使用cloud.google.com/go/datastore,并借用由Mercari开发的boom。
package handler
import (
"cloud.google.com/go/datastore"
"github.com/labstack/echo"
"go.mercari.io/datastore/boom"
"go.mercari.io/datastore/clouddatastore"
"log"
"net/http"
"os"
)
type Post struct {
Kind string `datastore:"-" boom:"kind,post" json:"-"`
ID int64 `datastore:"-" boom:"id" json:"id"`
Content string `datastore:"content" json:"content"`
}
func HelloWorld(e echo.Context) error {
// app.yaml などにPROJECT_IDを設定しておく
projectId := os.Getenv("PROJECT_ID")
log.Printf("Project ID: %s", projectId)
// DataStore Clientの作成
ctx := e.Request().Context()
dataClient, err := datastore.NewClient(ctx, projectId)
if err != nil {
log.Fatalf("failed to get client (reason: %v)", err)
return e.String(http.StatusInternalServerError, "error")
}
// mercari.datastoreでラップする
client, err := clouddatastore.FromClient(ctx, dataClient)
if err != nil {
log.Fatalf("failed to get datastoreclient (reason: %v)", err)
return e.String(http.StatusInternalServerError, "error")
}
defer client.Close()
// boomを利用
b := boom.FromClient(ctx, client)
post := &Post{ID: 12345, Content:"test"}
// 保存
if _, err := b.Put(post); err != nil {
log.Fatalf("failed to put datastore (reason: %v)", err)
return e.String(http.StatusInternalServerError, "error")
}
// 取得
getPost := &Post{ID: 12345}
if err := b.Get(getPost); err != nil {
log.Fatalf("failed to get datastore (reason: %v)", err)
return e.String(http.StatusInternalServerError, "error")
}
return e.JSON(http.StatusOK, getPost)
}
// 抜粋
e.GET("/", handler.HelloWorld)
これでローカル環境でも、GAEの環境でもGCP/DataStoreに保存 / 取得することができました。

如果gloud上登录的用户具有访问权限,似乎可以自动连接到本地环境。
第七步骤:访问本地的DataStore模拟器。
虽然也可以在这里进行开发,但为了方便起见,我们当然希望能连接到本地的模拟器。
因此,我将尝试进行相应的设置。
ローカルのエミュレーターを起動
以下のコマンドでできます。
–project の部分は [a-z][a-z0-9\-]+ の命名規則であれば任意でも問題ないようです。指定しなければ gcloud で設定している current project id が入ります。
$ gcloud beta emulators datastore start --host-port localhost:8059 --project test-project
WARNING: Reusing existing data in [/Users/xxxx/.config/gcloud/emulators/datastore].
Executing: /Users/xxxx/tools/google-cloud-sdk/platform/cloud-datastore-emulator/cloud_datastore_emulator start --host=localhost --port=8059 --store_on_disk=True --consistency=0.9 --allow_remote_shutdown /Users/xxxx/.config/gcloud/emulators/datastore
[datastore] 9 07, 2019 2:00:18 午後 com.google.cloud.datastore.emulator.CloudDatastore$FakeDatastoreAction$9 apply
[datastore] 情報: Provided --allow_remote_shutdown to start command which is no longer necessary.
[datastore] 9 07, 2019 2:00:18 午後 com.google.cloud.datastore.emulator.impl.LocalDatastoreFileStub <init>
[datastore] 情報: Local Datastore initialized:
[datastore] Type: High Replication
[datastore] Storage: /Users/xxxx/.config/gcloud/emulators/datastore/WEB-INF/appengine-generated/local_db.bin
[datastore] 9 07, 2019 2:00:18 午後 com.google.cloud.datastore.emulator.impl.LocalDatastoreFileStub load
[datastore] 情報: The backing store, /Users/xxxx/.config/gcloud/emulators/datastore/WEB-INF/appengine-generated/local_db.bin, does not exist. It will be created.
[datastore] API endpoint: http://localhost:8059
[datastore] If you are using a library that supports the DATASTORE_EMULATOR_HOST environment variable, run:
[datastore]
[datastore] export DATASTORE_EMULATOR_HOST=localhost:8059
[datastore]
[datastore] Dev App Server is now running.
[datastore]
[datastore] The previous line was printed for backwards compatibility only.
[datastore] If your tests rely on it to confirm emulator startup,
[datastore] please migrate to the emulator health check endpoint (/). Thank you!
让我们亲身实际操作一下
在启动上述模拟器的情况下,我将在本地进行协作。
请使用以下命令运行。
$ env DATASTORE_EMULATOR_HOST=localhost:8059 DATASTORE_PROJECT_ID=test-project go run main.go
当访问 localhost:8080 时,页面会正常显示,并在启动模拟器的终端上输出日志。
[datastore] The health check endpoint for this emulator instance is http://localhost:8059/9 07, 2019 2:06:34 午後 io.gapi.emulators.grpc.GrpcServer$3 operationComplete
[datastore] 情報: Adding handler(s) to newly registered Channel.
[datastore] 9 07, 2019 2:06:34 午後 io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
[datastore] 情報: Detected HTTP/2 connection.
[datastore]
[datastore]
[datastore] 9 07, 2019 2:06:48 午後 com.google.cloud.datastore.emulator.impl.LocalDatastoreFileStub lambda$persist$7
[datastore] 情報: Time to persist datastore: 27 ms
这样一来,在本地环境下的开发也会变得更加顺利了(`・ω・´)
在本地仿真器内查看(Google Cloud图形用户界面)
ただ、エミュレーターの場合今までのように Appengine Local Serverの管理画面からDataStoreの中を見るということができません。ちょっと気になったりすることもあるんで、それも見れるようにします。
すごい人が作ってたので、それを利用します。
谷歌云的图形用户界面(Google Cloud GUI)
$ npm i -g google-cloud-gui
$ google-cloud-gui
执行以上操作,浏览器会自动启动。

只需要一种选择:在“Projects”左侧按下+按钮,就可以确认添加项目。

很棒。当我查看终端时,有很多错误,但它可以使用,所以没问题。
第八步:撰写测试用例。
终于快要到达目标了。
这次我们先使用DataStore模拟器,试图以实际连接DataStore的方式来描述测试。
func TestHelloWorld(t *testing.T) {
// 環境変数をセット
os.Setenv("DATASTORE_PROJECT_ID", "test-project")
os.Setenv("DATASTORE_EMULATOR_HOST", "localhost:8059")
e := echo.New()
req, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
c := e.NewContext(req, rr)
err = HelloWorld(c)
if err != nil {
t.Errorf("unexpeced handler reponse (err: %v)", err)
return
}
if status := rr.Code; status != http.StatusOK {
t.Errorf(
"unexpected status: got (%v) want (%v)",
status,
http.StatusOK,
)
}
expected := `{"id":12345,"content":"test"}`
if strings.TrimSpace(rr.Body.String()) != expected {
t.Errorf(
"unexpected body: got (%v) want (%v)",
rr.Body.String(),
expected,
)
}
}
在启动DataStore模拟器的状态下运行,测试通过了。
=== RUN TestHelloWorld
2019/09/07 14:51:33 Project ID:
--- PASS: TestHelloWorld (0.02s)
PASS
尝试后的感受
总的来说,我们成功地完成了搭建,没有遇到太多问题。
感谢Go Module使我们不再被vendoring束缚,这真是太好了。
不过,正如我之前提到的,从GAE/go1.12开始,无法使用AppEngine API,这真是相当令人沮丧。
这对现有项目造成的影响可能相当大…
还没做好的事情
因为这种情况,现在应该无法实现DataStore和memcache的协作。
(我在某处读到mercari/datastore非常出色并提供本地缓存的文章,但我还没有确认其功能)
“未来不要使用Appengine Memcache,而是使用Redis Cloud”,GCP也说了,下一次我们可以尝试实现这种协作。
据说Redis Cloud在30MB之前是免费的。
30MB啊… (´・_・`)
就算是个人使用,实际应用起来似乎还是需要付费。
故障排除
在构建过程中出现的错误将被整理记录。
在Go环境中无法安装Go 1.12。
我在使用 goenv 进行 Go 版本管理,但在我的环境中,即使运行 install –list 命令也没有显示任何内容,一开始无法成功安装。
$ goenv install 1.12.9
go-build: definition not found: 1.12.9
See all available versions with `goenv install --list'.
If the version you need is missing, try upgrading goenv:
brew update && brew upgrade goenv
解决的途径
我通过以下方法进行了更新。
$ brew update # brew自体をアップデートする
$ $ brew install --HEAD goenv
Error: Xcode alone is not sufficient on Mojave.
Install the Command Line Tools:
xcode-select --install
# 怒られたんで、言われるままにやります(というか入ってなかったのか…)
$ xcode-select --install
xcode-select: note: install requested for command line developer tools
$ brew install --HEAD goenv
Error: goenv 1.23.3 is already installed
To install HEAD, first run `brew unlink goenv`.
# また怒られた。言われた通りにunlinkする
$ $ brew unlink goenv
Unlinking /usr/local/Cellar/goenv/1.23.3... 4 symlinks removed
$ brew install --HEAD goenv
# 成功!
只有版本号为Mac OS 10.14的情况下,无法使用go1.12进行测试。
为什么每次尝试运行go test都会出现这样的错误呢?
$ go test
go: creating new go.mod: module github.com/brbranch/JankenShogiOnline
# runtime/cgo
In file included from gcc_libinit.c:8:
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/usr/include/pthread.h:232:66: error: unknown type name 'size_t'
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/usr/include/pthread.h:249:43: error: unknown type name 'size_t'
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/usr/include/pthread.h:256:66: error: unknown type name 'size_t'
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/usr/include/pthread.h:524:1: error: unknown type name 'size_t'
gcc_libinit.c:97:18: error: variable has incomplete type 'struct timespec'
gcc_libinit.c:97:9: note: forward declaration of 'struct timespec'
gcc_libinit.c:110:3: error: implicit declaration of function 'nanosleep' is invalid in C99 [-Werror,-Wimplicit-function-declaration]
FAIL github.com/xxx/xxxx/backend/src [build failed]
解决方案 (jiě jué àn)
这个问题在GitHub上的地址是https://github.com/golang/go/issues/30072。

虽然我不是很明白,但是我按照以下的方法进行后,能够顺利进行测试。
$ env CGO_ENABLED=0 go test
PASS
ok github.com/xxxx/xxxx/backend/src 0.013s
虽然我不太理解,但是因为它在动,所以我认为是正义的(´・ω・`)。
请提供更具体的上下文,以便我为您进行更准确的翻译。
App Engine 标准环境的运行时环境
https://cloud.google.com/appengine/docs/standard/runtimes
将您的App Engine应用迁移到Go 1.12
谷歌云图形用户界面
https://github.com/GabiAxel/google-cloud-gui
分享一下关于Mercari/Datastore的经验和见解
使用GAE+GO+Echo进行了对go1.11的兼容。