2016年のGCPはFirebaseのアップデートから始まって東京リージョン開設と色々進化がありました。GAEよりはその他のGCPのサービスに注力していたのかなという印象です。
でもアジア初のGAEが日本というのは大きなニュースでした!
2017年にもどんどんデータセンターを増やすみたいで色々楽しみですね。
ちなみにGAEのChannel APIは来年シャットダウンされるらしいです。移行先としてリアルタイムの通信はFirebaseを使うのが公式のオススメのようです。
そう言えばGoogle Cloud PlatformもGoogle Cloudに改名してましたけどGCだと口頭でもわかりにくいしググラビリティも良くないのでGCPって言っていきたいです。
(追記: GCPでいいようです)
そして本題の今年のGCPのアドベントカレンダーでは、皆悩んでいるらしいGAE/Goの構成についてです。
前置きとしてタイトルの実践的というのはあくまで個人的にということです。
前提
コマンド操作とGoがある程度触れてGAE/GoのSDKを入れてHello Worldまでやってそれをデプロイまではできた方を対象にしています。
まだの方は Quickstart for Go App Engine Standard Environment から始めてみてください。
今回僕が試している環境はMacです。
作業ディレクトリはGOPATH内です(理由は後ほど)
GAEのアーキテクチャ
Microservices Architecture on Google App Engine
ここのドキュメントにあるようにまずプロジェクトというものがあってその中にサービス(以前はモジュール)があってさらにその中にバージョンがあるというイメージです。
もっと細かいことをいうとバージョンの中にさらにインスタンスがありますが構成についてはとりあえず関係ないので置いておきます。
そしてサービスやバージョンはruntimeが異なっていても大丈夫です。
webはPHPで書いてapiサーバーはGoで書くということもできますし
Javaで書いていたwebを次のバージョンからGoにするということもできます。
サービスはwebとかapiとかbotとかそれ専用にすべきです。
バージョンはトラフィックを操作できるので徐々に新しいバージョンにルーティングしていき問題があれば即座にロールバックするといったことができたりA/Bテストをするなどに使えます。
これを踏まえた上で以下のapp.yaml
のプロジェクトをGAEに初めてdeployすると何が起きているかというと
application: vendor-example
version: 1
runtime: go
api_version: go1
handlers:
- url: /favicon\.ico
static_files: favicon.ico
upload: favicon\.ico
- url: /.*
script: _go_app
サービスが指定されていないのでdefault
と言うサービス名になります。
そしてバージョンは1
でデプロイされます。
コンソールで確認するとこんな感じ
バージョンは1しかないのでここにhttps://vendor-example.appspot.com/
へのトラフィックの100%が割り当てられるということになります。
ディレクトリ構成
プロジェクトのディレクトリはGOPATH内に作成しています。
GOPATHはgithubのPATHと同じようになってるのでわかりやすくていいです。
このやり方を知ってからGo以外にも色んなプロジェクト含めてGoのお作法に則って管理するほうがなにかと都合が良くてやっています。
それとGo1.5からはベンダリングの条件としてGOPATH内でないと効かないのでGOPATH内での作業を前提としています。
GOPATH内での作業を楽にしてくれるghqというツールがお薦めです。
使い方は以下の記事を参考にしてください。
ghq: リモートリポジトリのローカルクローンをシンプルに管理する
まず先にディレクトリツリーです。
https://github.com/k2wanko/appengine-vendor-example
$GOPATH/src/github.com/k2wanko/appengine-vendor-example
├── README.md
├── backend
│ ├── app.yaml
│ ├── favicon.ico
│ ├── server.go
│ └── server_test.go
├── circle.yml
├── node_modules
├── package.json
├── service_account.json.enc
├── tools
│ └── set_appcfg_token.sh
└── vendor
└── github.com
├── labstack
│ ├── echo
(省略)
タスク管理はpackage.json
を使っています。App Engineを使うということはWebを作るということだと思うので基本的にJavaScriptのパッケージマネージャと一緒にしたほうがフロントの人と協業しやすいと思います。
僕の場合は一人で作ってるので全部yarn run
とかで統一したほうが楽ちんなのでそうしています。
Goのコードはbackend
にあります。
demoのhello worldとかではプロジェクトのルートディレクトリにapp.yaml
を置いてあると思いますがサービスごとにディレクトリを作ってその中にapp.yaml
を置くのがいいと思います。
複数のサービスを一つのリポジトリで管理したい場合は以下のようにサービスごとにディレクトリを作成していけばいいと思います。
(project root)
├── README.md
├── backend
│ ├── app.yaml
│ └── server.go
├── api
│ ├── app.yaml
│ └── server.go
├── bot
│ ├── app.yaml
│ └── server.go
├── user [サービス共通で使うパッケージ]
│ └── user.go
└── vendor
サービス名はdefault
じゃないのかよと思われるかもしれませんがGoで書く場合パッケージ名にdefault
は使えないのでbackend
としています。
backend
という名称はGoogleIOのWebサイトで使われていたのでそこを参考にして使っています。
またデプロイする時app.yaml
と同じ位置にあるファイルは全部デプロイされてしまうので必要なものだけをデプロイするという点でもいいと思います。
一応skip_files
を設定することで弾くことはできますがREADME.md
やpackage.json
などをで一々設定するのもめんどくさいと思うので単一サービスでもサブディレクトリに分けるのが便利です。
Goのパッケージ管理用のvendor
ディレクトリをプロジェクトのルートに置きました。
こうすることによりapp.yaml
以下のGoファイル全部をbuildするという制約を受けません。
ベンダリングのためにnobuild_files
を書く必要もなくなります。
GoのPackage Managerにはmanulを使っています。(2017年現在はdepを利用してます。)
manulはgitのsubmoduleだけで管理してくれるツールです。
なので特別なメタファイルも必要ないですし取り込む側はgitが入ってるはずなのでfetchするためのツールを入れる必要がなくて便利です。
GitHubでコードを参照する場合、依存パッケージも同じようにGitHubで管理されていたらそのままリンクを辿っていけるのが僕的には嬉しかったです。
使い方はREADMEを参照してください。
git submoduleのaliasみたいなものなのでそんなに難しくはないと思います。
サンプルではnpm install
された後にsubmoduleのcheckoutとgo get
をするようにしています。コマンドはpackage.jsonを参照してください。
補足: 複数のサービスと複数のvendor
最初に紹介した構成だと複数サービスの場合で単一のvendorしか使えないと思うかもしれませんが、その場合はGitのリポジトリごと分けて複数のリポジトリで一つのプロジェクトを管理していくというやり方もできます。
$GOPATH/src/github.com/k2wanko/appengine-vendor-example
├── README.md
├── backend
│ ├── app.yaml
│ └── server.go
└── vendor
$GOPATH/src/github.com/k2wanko/appengine-vendor-example-api
├── README.md
├── backend
│ ├── app.yaml
│ └── server.go
└── vendor
$GOPATH/src/github.com/k2wanko/appengine-vendor-example-bot
├── README.md
├── backend
│ ├── app.yaml
│ └── server.go
└── vendor
CircleCIでデプロイする 🚀
ただベンダリングを使って終わりでは実践的とは言えないと思ったのでサンプルにはCircleCIを使ってのデプロイまでを含めています。
CircleCIは無料でGitHubのプライベートリポジトリも回すことができるので学生の身分としてはとてもありがたいです。
登録や使い方はドキュメントを参照してください。
GAEをCIでデプロイするにはCloud Consoleからサービスアカウントを作ります。
サービスアカウントの役割はApp Engineのデプロイを選択しておいて、新しい秘密鍵の提供にチェックをしてJSONを選択します。
ダウンロードしたjsonはservice_account.json
としてプロジェクトのルートに保存しておきましょう。
そして以下のコマンドで暗号化します。
$ export KEY=`pwgen -1 32`
$ openssl aes-256-cbc -e -in service_account.json -out service_account.json.enc -k $KEY
KEYにはできるだけ長めのランダムな文字列を設定しましょう。
そしてCircleCIのSettingsのEnvironment VariablesでKEYを設定します。
復号は以下のコマンドでできます。
$ openssl aes-256-cbc -d -in service_account.json.enc -k $KEY > service_account.json
それと.gitignore
にservice_account.json
を追加して平文の方はリモートに上げてしまわないように注意しましょう。
次にプロジェクトのルートにcircle.yml
を設置します。
CircleCIのデフォルトのプロジェクトルートは~/{$CIRCLE_PROJECT_REPONAME}
なのでこれをGOPATH内に持っていきます。
標準のGOPATHは~/.go_workspace
ですが環境変数には:
区切りで複数していされているのでmachine: environment:
内にGOPATH: "${HOME}/.go_workspace"
と設定します。
次にrsyncを使ってコピーします。GOPATHはキャッシュが取られてるのでrsyncにしたほうが少ないファイルのコピーで済むのでいいかなと思います。
あとgcloudコマンドは標準で入っているのですがバージョンが古いのでアップデートするようにしています。
# Update gcloud
- >
test -e ~/.google-cloud-sdk || sudo mv /opt/google-cloud-sdk ~/.google-cloud-sdk;
sudo rm -rf /opt/google-cloud-sdk &&
sudo ln -fs ~/.google-cloud-sdk /opt/google-cloud-sdk &&
sudo /opt/google-cloud-sdk/bin/gcloud components update -q &&
sudo /opt/google-cloud-sdk/bin/gcloud components install app-engine-go -q &&
sudo chmod +x /opt/google-cloud-sdk/platform/google_appengine/goapp &&
sudo chown $USER:$USER -R ~/.config/gcloud
毎回ダウンロードしないようにdependencies: cache_directories:
に~/.google-cloud-sdk
を追加しています。
次にトークンの設定をします。
まずは先程の複合するためのコマンドをtest: pre:
に書きます。
$ openssl aes-256-cbc -d -in service_account.json.enc -k $KEY > service_account.json
平文になったjsonを使ってログインするコマンドは以下です。
$ gcloud auth activate-service-account --key-file service_account.json
GAE/Goをデプロイする方法でよく使うのはgoapp deploy
かと思いますがこれはappcfg.py update
のラッパーで初回このコマンドを使うとブラウザが開いて認証をしてトークンは~/.appcfg_oauth2_tokens
に保存されます。
なのでCIにも同じ場所にファイルを作ってあげればgoapp deploy
でデプロイができるということです。
gcloudコマンドを使うことでサービスアカウントのアクセストークンが取得できるのでそれを設定してあげれば動きます。以下がアクセストークンを取得するためのコマンドです。
$ gcloud auth print-access-token
そしてサンプルのtools/
内に置いているset_appcfg_token.sh
は~/.appcfg_oauth2_tokens
に設置するためのスクリプトで内容はとてもシンプルです。
#!/bin/bash
access_token=`gcloud auth print-access-token`
cat <<EOF > ~/.appcfg_oauth2_tokens
{
"_module": "oauth2client.client",
"token_expiry": null,
"access_token": "${access_token}",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"invalid": false,
"token_response": null,
"client_id": null,
"id_token": null,
"client_secret": null,
"revoke_uri": null,
"_class": "OAuth2Credentials",
"refresh_token": null,
"user_agent": null
}
EOF
gcloud auth activate-service-account
の後に実行しましょう。
今回の構成ではgoapp deploy backend
でデプロイできます。
でもほとんど必要なコードはpackage.jsonにまとめているのでそこのscripts
を確認してください。
全体のcircle.ymlはサンプルを確認してください。
まとめ
- Goの作業する時はGOPATH内でやりましょう。
- app.yamlはプロジェクトルートじゃなくてサブディレクトリに分けたほうがいいよ。
- タスク管理は
package.json(npmかyarn)
を使うのが良さそう。 - Goのパッケージマネージャはmanulが今は良さそう。
- Goのベンダリングは計画的に
-
golang.org/x/net
とかgoogle.golang.org/appengine
とか公式のパッケージは取る必要がほとんどなさそう。(GAEはPaaSなのでSDKを更新しないのは死を意味しそう) - やんちゃなパッケージだけとかにしといたほうが管理しやすそう (ech○とか)
-
- テスト書いてCIで回して自動でデプロイしましょう!
- 詳しくはGitHubを見てください。
後は感想、
サービスごとに分けるという構成はGoogleIO 2015のコードを見て知ってから使っていましたが、今回はベンダリングを使った場合のやり方を含めて紹介させていただきました。
初心者の方には難しそうに思うかもしれませんがGoogleに身も心も故郷も捧げる(というくらい割り切った気持ちでやる)とすごい楽ちんです難しくないです。でも、「イヤイヤ流石にそこまでは」って人でも軽い気持ちでGCPをGAEから始めてもいいと思います。それなりのパフォーマンスのプラットフォームを無料で使えるのでGoでWebアプリケーションを作りたい人には是非オススメしたいです。
何かわからないことがあればGCPUGのSlackとかスタックオーバーフローのgoogle-app-engineタグとかで気軽に聞いてください。GCPお兄さんたちが優しく答えてくれると思います。
それではGCPの発展とGAEでgRPCを受け取れるようになるかCloud PubSubでFirebaseのRealtime Databaseの変更が受け取れるかCloud Endpoints v2とかCloud DebuggerがGAE/Go Standard Environmentでもサポートされることを願って新年を迎えましょう。