お題
以下にある通り、3月20日にGoogle App EngineのGo 1.11スタンダード環境の「ベータ版」表記が外れ、GA(General Availability)となっていた。
https://cloud.google.com/appengine/docs/standard/go111/release-notes?hl=ja
表題の通り、とりあえずJSONリクエストを受け取って、その内容をCloud SQLに書き込むだけの簡単なサンプルコードでも書いてみる。
前提
以下は済んだ上での作業。
- GCPプロジェクトの作成
- Cloud SDKのインストールと初期化・認証
開発環境
# OS
$ cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04.2 LTS (Bionic Beaver)"
# Golang
$ go version
go version go1.11.4 linux/amd64
# Cloud SDK
$ gcloud version
Google Cloud SDK 240.0.0
app-engine-go
実践
今回のソース全量は下記。
https://github.com/sky0621/go-webapi-for-gae-study
プロジェクト構成
$ tree
.
├── app.yaml
├── main.go
└── secret.yaml
※今回の説明に関係あるものだけ抜粋
Golangソース(main.go
)解説
環境変数よりCloud SQL接続情報を取得
var (
connectionName = os.Getenv("CLOUDSQL_CONNECTION_NAME")
user = os.Getenv("CLOUDSQL_USER")
password = os.Getenv("CLOUDSQL_PASSWORD")
database = os.Getenv("CLOUDSQL_DATABASE")
)
この接続情報は、ローカル環境であれば上記の名称でセットしておけばよいけど、App Engine にデプロイする上では、どこにどのようにセットしておけばよいのか?
CI/CDのような方式ではなくローカル環境のコマンドプロンプト等からgcloud
コマンドを用いてデプロイする方式が前提にはなるけど、以下のようにすればよい。
機密情報を記載したファイル(secret.yaml
)を用意してApp Engineデプロイ用のファイル(app.yaml
)からインクルード
先ほどのCloud SQL接続情報は以下のように記載する。
env_variables:
# For Cloud SQL 2nd generation instances, this should be in the form of "project:region:instance".
CLOUDSQL_CONNECTION_NAME: 【自分で作成したGCPプロジェクトID】:【Cloud SQLの存在するリージョン】:【Cloud SQLのインスタンス名】
CLOUDSQL_USER: 【Cloud SQLの接続ユーザー名】
CLOUDSQL_PASSWORD: 【Cloud SQLの接続ユーザーのパスワード】
CLOUDSQL_DATABASE: 【Cloud SQLのデータベース名】
上記だとイメージわきづらいので、ダミー値でサンプルを記載。
env_variables:
# For Cloud SQL 2nd generation instances, this should be in the form of "project:region:instance".
CLOUDSQL_CONNECTION_NAME: my-gcp-proj:asia-northeast1:my-cloudsql-instance
CLOUDSQL_USER: root
CLOUDSQL_PASSWORD: password12345678
CLOUDSQL_DATABASE: mydb
※注意点:.gitignore
に記載するなどして、決してpublicなGitHub環境等にアップされないようにすること!
上記ファイルを読み込むデプロイ用のファイルは下記。(こちらはGitHub管理しても問題なし)
runtime: go111
includes:
- secret.yaml
上記のファイル群を用意した上で、同ファイル群が存在するパス上でコマンドプロンプト等から gcloud app deploy
と叩くと、記載した環境変数をセットした上でGolangアプリがGoogle App Engine上にデプロイされる。
Cloud SQL(MySQLインスタンス)に接続
dataSource := fmt.Sprintf("%s:%s@unix(/cloudsql/%s)/%s?parseTime=True", user, password, connectionName, database)
db, err := sql.Open("mysql", dataSource)
if err != nil {
panic(err)
}
defer func() {
if db != nil {
err := db.Close()
if err != nil {
panic(err)
}
}
}()
MySQL接続用のデータソースとしては通常「「ユーザー名」:「パスワード」@tcp(「ホスト名」:3306)/「DB名」」といった形式になる。
GAE-Goの1.11スタンダード環境は特殊で上記ソース記載の形式になる。
【参考】
https://qiita.com/sky0621/items/d91ffd1faf63dbf34e77#gaegoのスタンダード環境におけるcloud-sql接続方法go-v111の場合
ユーザー登録を行うWebAPIの定義
http.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
user, err := parseJsonRequest(r)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
switch r.Method {
case http.MethodPost:
result, err := db.ExecContext(context.Background(), "INSERT INTO user(id, name, mail, create_user) VALUES(?, ?, ?, ?)",
uuid.New().String(), user.Name, user.Mail, "admin")
if err != nil {
if _, err := fmt.Fprintln(w, err.Error()); err != nil {
panic(err)
}
}
if result == nil {
panic(errors.New("result is nil"))
}
}
})
parseJsonRequest(r)
関数の中身は省略。
HTTPリクエストからBodyをJSON形式でパースして、下記構造体にマッピングすることをやっている。
user
構造体の定義
type user struct {
Name string `json:"name"`
Mail string `json:"mail"`
}
WebAPIサーバー起動ロジック
port := fmt.Sprintf(":%s", os.Getenv("PORT"))
if err := http.ListenAndServe(port, nil); err != nil {
panic(err)
}
接続ポートを環境変数から取得する。これが重要。
Google App Engine環境にアプリをデプロイすると PORT
という名前で接続可能なポートが定義される。
そのため、上記のようにアプリ側で立ち上げるサーバーは環境変数「PORT
」から取得するようにしないといけない。
Google App Engineデプロイ
$ ls -l app.yaml
-rw-r--r-- 1 sky0621 sky0621 43 4月 1 09:02 app.yaml
$
$ gcloud app deploy
Services to deploy:
descriptor: [/home/sky0621/work/src/go111/src/github.com/sky0621/go-webapi-for-gae-study/app.yaml]
source: [/home/sky0621/work/src/go111/src/github.com/sky0621/go-webapi-for-gae-study]
target project: [【プロジェクトID】]
target service: [default]
target version: [20190401t092302]
target url: [https://【プロジェクトID】.appspot.com]
Do you want to continue (Y/n)? y
Beginning deployment of service [default]...
╔════════════════════════════════════════════════════════════╗
╠═ Uploading 1 file to Google Cloud Storage ═╣
╚════════════════════════════════════════════════════════════╝
File upload done.
Updating service [default]...done.
Setting traffic split for service [default]...done.
Deployed service [default] to [https://【プロジェクトID】.appspot.com]
You can stream logs from the command line by running:
$ gcloud app logs tail -s default
To view your application in the web browser run:
$ gcloud app browse
WebAPIにリクエスト
Postmanを使ってリクエスト実行(【プロジェクトID】の部分にはGoogle App Engineが存在するGCPプロジェクトのIDが入る。
Cloud SQLに接続して user
テーブルの中身を確認
MySQL [【DB名】]> select * from user;
+--------------------------------------+-----------------------+------------------+-------------+---------------------+-------------+------------+
| id | name | mail | create_user | created_at | update_user | updated_at |
+--------------------------------------+-----------------------+------------------+-------------+---------------------+-------------+------------+
| 720da418-a7ed-41c4-bf51-1fea81ffeca1 | テストユーザー | test@example.com | admin | 2019-04-04 16:35:48 | NULL | NULL |
+--------------------------------------+-----------------------+------------------+-------------+---------------------+-------------+------------+
1 row in set (0.04 sec)
まとめ
GCPプロジェクト作成からgcloud
コマンドのインストールにセットアップ等、事前の準備が済んでさえいれば、あとは、アプリを作って app.yaml
用意して、gcloud app deploy
と叩く。
これだけでマネージドな環境にアプリをデプロイできるのは、やっぱり便利。
・・・「GAになっていたので」と言いつつ、特にGAだからどうこうという内容の記事じゃなかった・・・。