GAE/Goでgen2を試してみた
発端
gen2周りは触っておこうと思っていて、おざなりになっていましたが
で、このブログのエンジンを切り替えると言ったので、少し触っていこうと思いました。 実際にはDeployしないと細かいデザインを触れなかったりするので、その辺りを変更したかったのですが、せっかくなので今回はGen2を試すことにしました。
ちなみにこのブログは、GoogleAppEngine(Go)で動作していて、HTML変換にgolang.org/x/tool/presentを利用、エディタ上でリアルタイム表示するため、GopherJSで描画して動作しているというなかなかのキワモノな感じになってます(下書き時にサーバでレンダリングしてというコストを抑えたつもり)
また現時点(2019/4/6)で、「これ変わるんじゃね?」という部分もありますから、その点に注意していただければと思います。
dev_appserver.pyの位置
さっそくと思い、まずGoogle Cloud SDKのアップデートをしようと
$ gcloud components update
を行ったら
WARNING: There are alternate versions of the following Google Cloud Platform tools on your system PATH. Please double check your PATH: $GCSDK/platform/google_appengine/dev_appserver.py $GCSDK/platform/google_appengine/endpointscfg.py
というワーニングが出ました。($GCSDKは手元のGoogleCloudSDKのパス)
そんなに古いSDKじゃないのに出たので最近なのかな?実際手元の環境をwhichで見てみると$GCSDK/bin/dev_appserver.pyになっていたので、切り替えました。(binの位置はgcloudもいるので、削除ではなく優先順位をつける必要があります
bin下のdev_appserver.pyはいつか消えるんじゃないかな?
gen2の特徴
gen2では普通のGoのWebアプリとして実装することができるようになります。Go1.11以上なのもあり、Go Modulesに合わせた構成になります。これによりGOPATHの設定や、開発可能になるはずです。
なのでmain()を実装して動作するようになります。google.golang.org/appengineを利用する場合はmainでappengine.Main()を実行とありますが、今回は使わないでDatastoreまでアクセスしてみましょう。
今回の構成
プロジェクトの構成を以下にしました。
Project/ - cmd/ - templates/ ←テンプレートファイル - public/ ← 静的ファイル - app.yaml ←アプリの設定 - index.yaml - main.go ←実行ファイル - local/ ← ローカルパッケージ - local.go - app.go - go.mod ← Modulesのファイル
とにかく私は設定などをソース内に詰め込んで行くのが嫌なので、どうしても1つディレクトリを用意したかったのでこの構成にしました。(例えばtemplate置き場がtemplateパッケージなのか、template置き場なのかわからなくなる
cmdとしたのは、appにするとトップをappにした場合にappの下にappディレクトリが来てしまい混乱すること、cmd下ならGopherは少なからず「main()」があると思ってくれること。辺りかな。。。トップのパッケージ名で他のいいのが思い浮かんだらそれにするかも。
また、この構成にした場合、開発環境では実行位置がmain.goの位置、AppEngine環境では/srv(Projectのトップ)がカレントディレクトリになる為、パスを使用する処理(テンプレートの読み込みなど)に考慮が必要です。
Go Modules
Project下(app.goのある位置)で
$ go mod init app //appはパッケージ名
を行うとgo.modができます。
ローカル使用でなければ、"github.com/shizuokago/blog"とするところですね(パッケージ名を省略してもgitリポジトリだったらremoteのパスが自動で入ってくれるはず)。
app.yaml
app.yamlは単純に
runtime: go111
としておけばOKになります。 go112は現時点ではデプロイできるけど、開発環境で動作しないという挙動のようです。
main.go
cmd/main.goからはそのパッケージ"app"を読み込む形で実行すればOKです。
import "app" func main() { port := os.Getenv("PORT") if port == "" { port = "8080" } log.Fatal(app.Listen(port)) }
こうしておけば、上位のgo.modのモジュール名で一致するのでそこが”app”パッケージになります。
ポートの指定はGo111のサンプルにある通りです。
dev_appserver.py(本番も)を通す時はポートが指定されて、runで実行する場合に8080になる感じです。
app.go
func Listen(port string) error { //handler を追加 log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil)) }
前述した通り、パスの指定が本番環境と開発環境で変わる為、この辺りで吸収しておいた方がよいでしょう。
ローカルパッケージ
ローカルパッケージへはGo Modulesのローカルパッケージの扱いと同じで
import ( "app/local" )
でアクセスが可能になるので、あとは通常通りにアプリを構成していくだけです。 "github.com/shizuokago/blog"みたいなパッケージ名にした場合は、"github.com/shizuokago/blog/local"みたいになるはずです。
開発環境で動作させてみる
単純にWebサーバを実装しただけの状態なので、
$ go run main.go
で実行が可能ですが、その他のものが動きませんので
$ dev_appserver.py .
を実行(main.goの位置)すると今まで通り起動できるはずです。
ただし、Datastore部分を起動する必要があるので、
$ dev_appserver.py --support_datastore_emulator=true .
とします。が
RuntimeError: Cannot use the Cloud Datastore Emulator because Java is absent . Please make sure Java 8+ is installed, and added to your system path
と出てしまいます。 自分でもびっくりしたのですが、自分の端末にJavaが入ってませんでした。
Javaをインストールして起動すると
INFO 2019-04-02 03:43:28,392 datastore_emulator.py:155] Starting Cloud Datastore emulator at: http://localhost:22624
と起動時のログに出ているので起動はしたみたいです。
実際URLにアクセスすると「Ok」と出ます。 ただこちらで立ち上げるより、個別に起動する方法がよいような気がするので、一旦個別で起動してみましょう。
個別で起動
個別で起動を行えるということは、テスト環境などが作りやすいかな?と個人的には思っています。
インストール等は
cloud.google.com/datastore/docs/tools/datastore-emulator
に書いてありますが、
$ gcloud beta emulators datastore start
で起動を行い、コンソールに出る
$ export DATASTORE_EMULATOR_HOST=localhost:8081
をdev_appserver.pyを起動するシェル上で実行しておきます。 エミュレータが同一のマシン上での実行の場合は
$ gcloud beta emulators datastore env-init
でも設定してくれるみたいです。
再度開発サーバを立ち上げる時に
$ dev_appserver.py --support_datastore_emulator=true -A=xxxxxx .
すると、連携が完了します。"-A"の設定ですが、localhost:8000/datastoreにアクセスした場合にその表示になるのでやっておかないと、中身を簡易的にみることができないようです。(GUIの提供はEmulatorのissueなどに上がっているようです
Datastoreへのアクセス
Datastoreにアクセスして、検索、登録を行ってみたいと思います。 google.golang.org/appengineを使わずにcloud.google.com/go/datastoreを利用します。
godoc.org/cloud.google.com/go/datastore
ドキュメントそのままで実装し、プロジェクトIDを設定し、Web側から実行するようにするだけでOKです。
localhost:8000/datastore で登録の確認が行えます。
デプロイ
実際に実行環境で登録できるかを試してみます。
$ gcloud app deploy --project=xxxxxx
を行うと、以下のエラーが出力されました。
ERROR: (gcloud.app.deploy) Error Response: [7] Access Not Configured. Cloud Build has not been used in project shizuoka-go before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/cloudbuild.googleapis.com/overview? project=xxxxxx then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.
書いてある通り「Cloud Build」を有効にする必要があります。かつ有効にするには課金設定が必要です。新しいマニュアル等に課金設定を有効にしろって書いてあるのはこういうことですね。有効にしたらデプロイ可能になります。
他の資料等には別の理由で課金設定しろとあったので、今後どうなるかは不明ですが、勉強会等でハンズオンを行う場合は注意が必要ですね。
注意点
前述した通り、実行環境でのカレントパスが違う部分が微妙です。
また、実行時に
go.mod found in parent directory. Move app.yaml to that directory and add the line "main: cmd"
と出ます。app.yamlとgo.modが同じ位置にあるべきなのでしょうか? 現状は動作はしてくれるので無視しています。
ちなみにこの状態でmain: cmdを追加するとコンパイルエラーになります。 ※main: app/cmdならエラーにはならない
あと、app.yamlのloginがdeprecatedになっているのでログイン周りは少し考慮が必要のようです。
cloud.google.com/appengine/docs/standard/go112/go-differences?hl=ja
一旦動作確認完了
Datastoreへの登録までを行ってみました。 実際のところはライブラリを利用していくと思いますが、一旦生のgen2で構成を掴みたかった感じですね。 Gen2の良さはいっぱいあると思いますが、少なくともvendorから開放されるだけでかなりスッキリするような気がします。(普通に行けばgen1での1.11対応はないのかな?
まだ資料が揃ってない感じはありますが、google.golang.org/appengineを使わないという選択だけでかなり移行する為の工数はかかりそうな雰囲気があります。無理せずにappengine.Main()で一旦移行だけは済ませた方がいいのかな?と感じました。