以前、GAE/Goでglideを使用する場合のプロジェクト構成についての記事を書いた。
GAE/Go+glide的な構成での環境構築 ~ローカルサーバー立ち上げまで~ - Qiita
あれからしばらくGAE/Goで開発を続けていて、こんな構成もよさそうだなーと思うものが出てきたので、改めてまとめてみる。
前回はパッケージ管理にglideを使用したが、今時depだろjkという気分なので、今後はdep
を使って行くことになると思う。
ディレクトリ構成
前回の記事ではこんな感じの構成だった。
$GOPATH(PROJECT_ROOT)
├── app
│ ├── app.yaml
│ └── main.go
└── src
├── glide.yaml
├── glide.lock
├── PACKAGE
└── vendor
つまりは、プロジェクトルートにGOPATHを設定する感じ。
それで今回試すのがこんな構成
$GOPATH
└── src
└── PROJECT_ROOT
├── Gopkg.lock
├── Gopkg.toml
├── Makefile
├── app
│ ├── app.yaml
│ └── main.go
├── SUB_PACKAGE
└── vendor
違いとしては、プロジェクトルートにGOPATHを設定するのをやめ、基本的なGoFlowに沿って、グルーバルなGOPATHにプロジェクトを配置するやり方。
src直下じゃなくて、github.com/{USER}/{REPOSITORY}
のように通常のGoプロジェクトのようにしてもOK。
この構成の利点としては、わざわざプロジェクトごとにGOPATHの変更が必要無くなって、goenv
とかdirenv
で変更する必要が無いから色々と楽。
Makefileはアプリのデプロイとかテスト実行だったり、開発が進むに連れて色々と発生してくる手間を省くためのタスクランナーとして使ってる。
シェルスクリプト書いてもいいけど、その場合でもシェル叩くのはmakeタスクからやらせるようにしている。
タブ保管ができるし、同じ構文で様々なタスク走らせられるので色々捗る。
ぶっちゃけこれで話は終わりなんだけど、せっかくなのでこの構成でアプリデプロイするところまで解説してみようと思う。
サンプルアプリの作成
というわけで、サンプルの作成。
完成版 - github.com
上で解説してるように、まずはこんな感じでプロジェクトを作成する。
サブパッケージ名はわりと適当。
$GOPATH
└── src
└── github.com/...
├── app
└── gae
app.yaml
これがなくては始まらないのでapp.yaml
を作成する。
runtime: go
api_version: go1.8
handlers:
- url: /.*
script: _go_app
app.yaml
を追加した後はこうなる
$GOPATH
└── src
└── github.com/...
├── app
│ └── app.yaml
└── gae
ハンドラー作成
リクエストを処理するハンドラーを書く。
ハローワールド返すだけ。
package gae
import (
"fmt"
"net/http"
)
func SayHello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello world")
}
これは、サブパッケージ直下に配置してあげる。
$GOPATH
└── src
└── github.com/...
├── app
│ └── app.yaml
└── gae
└── hello.go
main.go作成
ハンドラーを作っただけじゃアプリは動かない。
作ったハンドラーをルーティングとして設定しなきゃいけないわけで、その設定はapp
以下に配置するmain.go
にやらせる
package main
import (
"net/http"
"github.com/gorilla/mux"
"github.com/ryutah/gae-structure-sample/gae"
)
func init() {
r := mux.NewRouter()
r.HandleFunc("/", gae.SayHello)
http.Handle("/", r)
}
せっかくなので、ルーティング処理にはgorilla/mux
を使ってる。
ライブラリの取得は普通にgo get
でやった。
$ go get -u github.com/gorilla/mux
ここまで終わると、ディレクトリ構成はこうなる
$GOPATH
└── src
└── github.com/...
├── app
│ ├── app.yaml
│ └── main.go
└── gae
└── hello.go
ここまで終わると、goapp serve app
でローカルサーバは立ち上がるようになる。
Makefile
せっかくなので、いろんなタスクをMakefileに書いていく
.PHONY: all
help: ## Print this help
@echo 'Usage: make [target]'
@echo ''
@echo 'Targets:'
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
serve: ## ローカルサーバ実行
goapp serve app
このMakefileはプロジェクトルートに配置しておく。
$GOPATH
└── src
└── github.com/...
├── Makefile
├── app
│ ├── app.yaml
│ └── main.go
└── gae
└── hello.go
ローカルサーバを起動したい場合はこんな感じでmakeタスクを実行する
$ make serve
dep
やっとdep
を使う。
dep自体は普通にgo get
でインストール
$ go get -u github.com/golang/dep/cmd/dep
vendoringのために、プロジェクトルートでdep init
を実行
$ dep init
今回のプロジェクトだと、main.go
がgorilla/mux
に依存してるので、勝手にパッケージ取ってきてくれる。
dep init
が終わるとこんな感じの構成になる
$GOPATH
└── src
└── github.com/...
├── Gopkg.lock
├── Gopkg.toml
├── Makefile
├── app
│ ├── app.yaml
│ └── main.go
├── gae
│ └── hello.go
└── vendor
└── github.com
└── gorilla
├── context
│ ├── LICENSE
略...
というわけで、サンプルアプリ完成。
デプロイしてみる
早速できたアプリをデプロイしてみる。
せっかくなので、これもMakefile
で定義しておく。
.PHONY: all
project_id := ${PROJECT_ID}
version := ${GAE_VERSION}
help: ## Print this help
@echo 'Usage: make [target]'
@echo ''
@echo 'Targets:'
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
serve: ## ローカルサーバ実行
goapp serve app
deploy: ## gaeへデプロイ OPTIONS: project_id=${PROJECT_ID} version=${VERSION}
goapp deploy -application ${project_id} -version ${version} app
プロジェクトIDとバージョンを環境変数を設定するか、引数として指定するようにしてみた。
gcloud app deploy
を使いたいところだが、そっちはまだGoのVendoringに対応してないので仕方ないのでgoapp deploy
を使う
というわけでデプロイ。
$ make deploy project_id={PROJECT_ID} version=1
goapp deploy -application {PROJECT_ID} -version 1 app
07:28 PM Application: {PROJECT_ID} (was: None); version: 1 (was: None)
07:28 PM Host: appengine.google.com
07:28 PM Starting update of app: {PROJECT_ID}, version: 1
07:28 PM Getting current resource limits.
07:28 PM Scanning files on local disk.
07:28 PM Cloning 13 application files.
07:28 PM Uploading 4 files and blobs.
07:28 PM Uploaded 4 files and blobs.
07:28 PM Compilation starting.
07:28 PM Compilation: 10 files left.
07:28 PM Compilation completed.
07:28 PM Starting deployment.
07:28 PM Checking if deployment succeeded.
07:28 PM Deployment successful.
07:28 PM Checking if updated app version is serving.
07:28 PM Completed update of app: {PROJECT_ID}, version: 1
OK
動作確認
せっかくなので、ちゃんと動くか確認する
$ gcloud app browse --version 1
ちゃんと動いた。
まとめ
というわけで、駆け足でプロジェクト構成からサンプルアプリの作成までやってみた。
GAE/Goのプロジェクト構成に関するベストプラクティスは未だに無いと思ってるので、今後も色々と試行錯誤していければと思う。
2017年12月29日 追記
There are too many files in your application for
をなんとかしたい
コメントにもあるように、GOPATH内のパッケージが多くなるとローカルサーバ実行中のファイル監視が上手く動いてくれない。
普通に開発していると、当然GOPATH内にはいろんな開発ツールやらが入っていると思うので、かなりの確率でこの状態になったりする。
動作自体に問題はないが、ファイル変更を検知してくれなくなるため地味に不便。
解決方法
GOPATHにパッケージがありすぎるのが問題なのでそれを解消する必要がある。
つまり、プロジェクトごとにクリーンなGOPATHを用意して、余計なファイルが存在しない状態にすればいいわけだ。
とはいえ、コーディングを行う環境だと大体何かしらのツールや他プロジェクトのコードがGOPATH内にあるものなので、ローカルサーバ起動のたびに、わざわざそんな状態を用意するのも大変だ。
なので、GAE/Goが実行可能なDockerイメージを作ってみた。1
DockerHub - ryutah/gcloud-gaego
Dockerコンテナを利用することで、常にクリーンなGOPATHでアプリケーションが実行できるため、よほどのことがなければちゃんとファイル監視してくれるようになる。
試してみる
コンテナでローカルサーバを実行できるように、Makefileにタスクを追記する
serve-docker: ## ローカルサーバをDockerコンテナで実行する
$(eval src := $(shell pwd))
@docker run --rm \
-it \
-v ${src}:/work/src/github.com/ryutah/gae-structure-sample \
-p 8080:8080 \
-p 8000:8000 \
ryutah/gcloud-gaego \
dev_appserver.py --host 0.0.0.0 --admin_host 0.0.0.0 /work/src/github.com/ryutah/gae-structure-sample/app
警告文が出なくなった。
この状態でファイルを編集して、変更を検知できているか確認してみる。
やったぜ。
参考
Go でツール書くときの Makefile 晒す - Qiita
Makefileを自己文書化する - POSTD
-
すでにGAE/GoのDockerイメージはいくつかありますが多分これが一番イメージサイズが小さいと思います ↩