App Engineのgo111用ディレクトリ構成

App Engine Standard EnvironmentでGo1.9を使う時は以下のように、リポジトリのルートをGOPATHとして設定し、appcfg.pyを使ってデプロイをしていました。

.
├── app.yaml
└── src
    └── app
        ├── glide.lock
        ├── glide.yaml
        ├── app.go
        ├── handler
        ├── ...
        └── vendor
  • app.go
package app

import (
        "net/http"

        "app/handler"
)

func init() {
        http.Handle("/", handler.New())
}

これがgo111のランタイムになると、main関数が必須になり、デプロイコマンドもgcloud app deployに変わります。*1

そのため、以下の条件を満たしつつ、どのようなディレクトリ構成にするのが良いのか考えてみました。

  • app.yamlのパスは変更しない
  • GOPATHリポジトリごとに変更しなくても良いようにする
    • src/apppkgに変更
    • app.gopkg.goに変更
  • 依存関係の管理にgo.modを使う

1) ./main.goを作成する

.
├── app.yaml
├── go.mod
├── go.sum
├── main.go
└── pkg
    ├── handler
    ├── ...
    └── pkg.go

2) ./cmd/app/main.goを作成し、app.yamlmainでパスを指定する

.
├── app.yaml
├── cmd
│  └── app
│      └── main.go
├── go.mod
├── go.sum
└── pkg
    ├── handler
    ├── ...
    └── pkg.go

この時、プライベートなパッケージ*2を使っているとCloud Buildで依存関係を解決できません。
対策としては、代わりにvendorを使う、もしくはgo.modreplaceディレクティブを使う、のどちらかです。

しかし、vendorを使う場合にはgo.mod.gcloudignoreに追加し、GO111MODULEoffにしないといけません。
また、go.modを使うにはGO111MODULEonにしないといけません。
GO111MODULEリポジトリGOPATHの配下に置くか、外に置かでautoの時の値が変わるため*3、組み合わせが非常に複雑です。

まとめると次のようになります。

依存管理 リポジトリの場所 GO111MODULE デプロイ可否
go.mod GOPATH配下 auto(=off) offだと使用不可
go.mod GOPATH配下 on OK
go.mod GOPATH外 auto(=on) OK
go.mod GOPATH外 off offにできない
vendor GOPATH配下 auto(=off) 1)はOK、2)はNG
vendor GOPATH配下 on NG
vendor GOPATH外 auto(=on) NG
vendor GOPATH外 off offにできない

※確認する際にCloud Buildで使われたイメージはgcr.io/gae-runtimes/go111_app_builder:go111_20190503_1_11_9_RC00です

正しくgo.modを使えば問題なくデプロイできるため、replace対象のパッケージが管理できるのであればこちらを使うのが良さそうです。
ただ、replace対象のパッケージはgitのsubmodulessubtreeで管理することになり、それが煩雑になってしまう可能性があります。

そういった場合、現段階だと1)をGOPATH配下に置くのが無難です。
app.yamlmainを指定することができませんが、今のプロジェクトでは必要なかったので以下のディレクトリ構成にすることにしました。

.
├── app.yaml
├── go.mod  // デプロイしない
├── go.sum  // デプロイしない
├── main.go
├── pkg
│  ├── handler
│  ├── ...
│  └── pkg.go
└── vendor  // `GO111MODULE=on go mod vendor` で作成

*1:https://cloud.google.com/appengine/docs/standard/go111/go-differences 参照

*2:GitHub Enterprise含む

*3:GOPATH配下: off、GOPATH外: on