実践的なGAE/Goの構成について #golang #gcpja

2016年のGCPはFirebaseのアップデートから始まって東京リージョン開設と色々進化がありました。GAEよりはその他のGCPのサービスに注力していたのかなという印象です。
でもアジア初のGAEが日本というのは大きなニュースでした！
2017年にもどんどんデータセンターを増やすみたいで色々楽しみですね。
ちなみにGAEの[Channel APIは来年シャットダウン](https://cloud.google.com/appengine/docs/deprecations/channel#shutdown_timetable)されるらしいです。移行先としてリアルタイムの通信は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](https://cloud.google.com/appengine/docs/go/quickstart) から始めてみてください。

今回僕が試している環境はMacです。
作業ディレクトリはGOPATH内です（理由は後ほど）

# GAEのアーキテクチャ

[Microservices Architecture on Google App Engine](https://cloud.google.com/appengine/docs/go/microservices-on-app-engine)

ここのドキュメントにあるようにまず**プロジェクト**というものがあってその中に**サービス（以前はモジュール**）があってさらにその中に**バージョン**があるというイメージです。

もっと細かいことをいうとバージョンの中にさらにインスタンスがありますが構成についてはとりあえず関係ないので置いておきます。

そしてサービスやバージョンはruntimeが異なっていても大丈夫です。
webはPHPで書いてapiサーバーはGoで書くということもできますし
Javaで書いていたwebを次のバージョンからGoにするということもできます。

サービスはwebとかapiとかbotとかそれ専用にすべきです。
バージョンはトラフィックを操作できるので徐々に新しいバージョンにルーティングしていき問題があれば即座にロールバックするといったことができたりA/Bテストをするなどに使えます。

これを踏まえた上で以下の`app.yaml`のプロジェクトをGAEに初めてdeployすると何が起きているかというと

```yaml:app.yaml
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`でデプロイされます。

コンソールで確認するとこんな感じ

<img width="550" alt="スクリーンショット 2016-11-28 18.47.29.png" src="https://qiita-image-store.s3.amazonaws.com/0/15818/302ce9a4-9c65-af45-a316-697a8a3974c7.png">

バージョンは１しかないのでここに`https://vendor-example.appspot.com/`へのトラフィックの１００％が割り当てられるということになります。

# ディレクトリ構成 

プロジェクトのディレクトリはGOPATH内に作成しています。
GOPATHはgithubのPATHと同じようになってるのでわかりやすくていいです。
このやり方を知ってからGo以外にも色んなプロジェクト含めてGoのお作法に則って管理するほうがなにかと都合が良くてやっています。

それとGo1.5からはベンダリングの条件としてGOPATH内でないと効かないのでGOPATH内での作業を前提としています。

GOPATH内での作業を楽にしてくれる[ghq](https://github.com/motemen/ghq)というツールがお薦めです。

使い方は以下の記事を参考にしてください。

[ghq: リモートリポジトリのローカルクローンをシンプルに管理する](http://motemen.hatenablog.com/entry/2014/06/01/introducing-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サイト](https://github.com/GoogleChrome/ioweb2016)で使われていたのでそこを参考にして使っています。

またデプロイする時`app.yaml`と同じ位置にあるファイルは全部デプロイされてしまうので必要なものだけをデプロイするという点でもいいと思います。
一応`skip_files`を設定することで弾くことはできますが`README.md`や`package.json`などをで一々設定するのもめんどくさいと思うので単一サービスでもサブディレクトリに分けるのが便利です。

Goのパッケージ管理用の`vendor`ディレクトリをプロジェクトのルートに置きました。
こうすることにより`app.yaml`以下のGoファイル全部をbuildするという制約を受けません。
ベンダリングのために`nobuild_files`を書く必要もなくなります。

GoのPackage Managerには[manul](https://github.com/kovetskiy/manul)を使っています。(2017年現在は[dep](https://github.com/golang/dep)を利用してます。)
manulはgitのsubmoduleだけで管理してくれるツールです。
なので特別なメタファイルも必要ないですし取り込む側はgitが入ってるはずなのでfetchするためのツールを入れる必要がなくて便利です。
GitHubでコードを参照する場合、依存パッケージも同じようにGitHubで管理されていたらそのままリンクを辿っていけるのが僕的には嬉しかったです。

<img width="682" alt="スクリーンショット 2016-11-28 19.39.43.png" src="https://qiita-image-store.s3.amazonaws.com/0/15818/bda7beb3-a537-147f-5532-dc84ebdd130f.png">

使い方はREADMEを参照してください。
git submoduleのaliasみたいなものなのでそんなに難しくはないと思います。
サンプルでは`npm install`された後にsubmoduleのcheckoutと`go get`をするようにしています。コマンドは[package.json](https://github.com/k2wanko/appengine-vendor-example/blob/master/package.json#L10)を参照してください。

## 補足: 複数のサービスと複数の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](circleci.com)は無料でGitHubのプライベートリポジトリも回すことができるので学生の身分としてはとてもありがたいです。

登録や使い方は[ドキュメント](https://circleci.com/docs/)を参照してください。

GAEをCIでデプロイするには[Cloud Console](https://console.cloud.google.com/iam-admin/serviceaccounts/project)からサービスアカウントを作ります。

サービスアカウントの役割はApp Engineのデプロイを選択しておいて、新しい秘密鍵の提供にチェックをしてJSONを選択します。

<img width="559" alt="スクリーンショット 2016-11-28 23.22.15.png" src="https://qiita-image-store.s3.amazonaws.com/0/15818/9e14a799-a3b7-f804-5b06-ce79dcc6bea1.png">

ダウンロードしたjsonは`service_account.json`としてプロジェクトのルートに保存しておきましょう。
そして以下のコマンドで暗号化します。

```bash
$ 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を設定します。
復号は以下のコマンドでできます。

```bash
$ 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:`に書きます。

```bash
$ openssl aes-256-cbc -d -in service_account.json.enc -k $KEY > service_account.json
```

平文になったjsonを使ってログインするコマンドは以下です。

```bash
$ gcloud auth activate-service-account --key-file service_account.json
```

GAE/Goをデプロイする方法でよく使うのは`goapp deploy`かと思いますがこれは`appcfg.py update`のラッパーで初回このコマンドを使うとブラウザが開いて認証をしてトークンは`~/.appcfg_oauth2_tokens`に保存されます。
なのでCIにも同じ場所にファイルを作ってあげれば`goapp deploy`でデプロイができるということです。

gcloudコマンドを使うことでサービスアカウントのアクセストークンが取得できるのでそれを設定してあげれば動きます。以下がアクセストークンを取得するためのコマンドです。

```bash
$ gcloud auth print-access-token
```

そしてサンプルの`tools/`内に置いている`set_appcfg_token.sh`は`~/.appcfg_oauth2_tokens`に設置するためのスクリプトで内容はとてもシンプルです。

```bash
#!/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](https://github.com/k2wanko/appengine-vendor-example/blob/master/package.json)にまとめているのでそこの`scripts`を確認してください。

全体のcircle.ymlは[サンプル](https://github.com/k2wanko/appengine-vendor-example/blob/master/circle.yml)を確認してください。

# まとめ

- Goの作業する時はGOPATH内でやりましょう。
- app.yamlはプロジェクトルートじゃなくてサブディレクトリに分けたほうがいいよ。
- タスク管理は`package.json(npmかyarn)`を使うのが良さそう。
- Goのパッケージマネージャは[manul](https://github.com/kovetskiy/manul)が今は良さそう。
- Goのベンダリングは計画的に
    - `golang.org/x/net`とか`google.golang.org/appengine`とか公式のパッケージは取る必要がほとんどなさそう。（GAEはPaaSなのでSDKを更新しないのは死を意味しそう）
    - やんちゃなパッケージだけとかにしといたほうが管理しやすそう (ech○とか)
- テスト書いてCIで回して自動でデプロイしましょう！
- 詳しくは[GitHub](https://github.com/k2wanko/appengine-vendor-example)を見てください。

後は感想、

サービスごとに分けるという構成はGoogleIO 2015の[コード](https://github.com/GoogleChrome/ioweb2015)を見て知ってから使っていましたが、今回はベンダリングを使った場合のやり方を含めて紹介させていただきました。

初心者の方には難しそうに思うかもしれませんがGoogleに身も心も故郷も捧げる（というくらい割り切った気持ちでやる）とすごい楽ちんです難しくないです。でも、「イヤイヤ流石にそこまでは」って人でも軽い気持ちでGCPをGAEから始めてもいいと思います。それなりのパフォーマンスのプラットフォームを無料で使えるのでGoでWebアプリケーションを作りたい人には是非オススメしたいです。

何かわからないことがあれば[GCPUG](https://gcpug.jp)の[Slack](https://gcpug.jp/join)とか[スタックオーバーフローのgoogle-app-engineタグ](http://ja.stackoverflow.com/questions/tagged/google-app-engine)とかで気軽に聞いてください。GCPお兄さんたちが優しく答えてくれると思います。

それではGCPの発展とGAEでgRPCを受け取れるようになるかCloud PubSubでFirebaseのRealtime Databaseの変更が受け取れるかCloud Endpoints v2とかCloud DebuggerがGAE/Go Standard Environmentでもサポートされることを願って新年を迎えましょう。 :innocent: