2020年2月11日火曜日

Google App EngineでGoを動かした

Quickstartに続けてBuilding a Go App on App Engineをやってみた。ただ、そのままでは Hello, World! が表示されるだけなので、少し手を加えてみた。具体的には昔のホームページの定番のアクセスカウンターと掲示板を作った。

まずは app.yaml を作った。
runtime: go113
手元のマシンにインストールされているGo 1.13を使いたいgo113を指定した。
プログラムとHTMLテンプレートを分離したいのでGoの html/template というパッケージを使った。テンプレートはこんな感じ。
<html>
<head><title>Zick's Homepage</title></head>
<body>
<h1>Welcome to Zick's Homepage</h1>
<p>
You are the {{.Counter}} guest!
</p>
<form action="/" method="post">
name:
<input type="text" name="name" /><br />
text:
<textarea name="text" row="4" cols="80"></textarea><br />
<input type="submit" value="submit" />
<table border="1">
{{range .Bbs}}
<tr><td>{{.Date}}</td><td>{{.Name}}</td><td>{{.Text}}</td></tr>
{{end}}
</table>
</form>
</body>
</html>
あえて古臭いHTMLにこだわった。 {{.field}} でテンプレートに渡した構造体のフィールドにアクセスできる。 {{range .repeated_field}} で配列をループで回せる。これでカウンタと掲示板へのすべての書き込みが表示できる。
このテンプレートを使うプログラムはこんな感じ。
package main

import (
 "fmt"
 "html/template"
 "log"
 "net/http"
 "os"
 "sync"
 "time"
)

type Kakikomi struct {
 Date string
 Name string
 Text string
}

var count = 0
var kakikomis []Kakikomi
var mutex sync.Mutex

type Bindings struct {
 Counter string
 Bbs     []Kakikomi
}

func updateVars(name string, text string) (int, []Kakikomi) {
 mutex.Lock()
 defer mutex.Unlock()
 count++
 if name != "" && text != "" {
  kakikomis = append(
   kakikomis, Kakikomi{time.Now().Format("2006-01-02 15:04"), name, text})
 }
 return count, kakikomis
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
 if r.URL.Path != "/" {
  http.NotFound(w, r)
  return
 }
 c, k := updateVars(r.FormValue("name"), r.FormValue("text"))
 tpl := template.Must(template.ParseFiles("index.tpl"))
 m := Bindings{
  Counter: fmt.Sprintf("%dth", c),
  Bbs:     k,
 }
 tpl.Execute(w, m)
}

func main() {
 http.HandleFunc("/", indexHandler)
 port := os.Getenv("PORT")
 if port == "" {
  port = "8080"
  log.Printf("Default to port %s", port)
 }
 log.Printf("Listening on port %s", port)
 if err := http.ListenAndServe(":"+port, nil); err != nil {
  log.Fatal(err)
 }
}
カウンタと掲示板の書き込みはグローバル変数として持つ。これだとプログラムが再起動するとデータが消えるし、複数インスタンスが立ち上がると整合性が保てなくなるが、遊びなのでヨシ!
環境変数PORT以外に特にApp Engine特有の機能は使っていないのでローカルマシンで動く。
go run main.go
localhost:8080にアクセスするとテストができるので便利。
ローカルマシンで動くのが確認できたらデプロイする。
 gcloud app deploy
無事、App Engine上で動くのが確認できた。よかった。

Google App Engine (スタンダード環境) のQuickstartをした


Quickstart for Go 1.12+ in the App Engine Standard Environmentのチュートリアルを試したのでその時のメモ。

まず、Cloud SDKとインストールする。
このページに従ってtar.gzファイルをダウンロードして展開。
省略可と書いてるけど以下のコマンドを実行。
% ./google-cloud-sdk/install.sh
# 統計情報を送信するか聞かれる
Y
# 続行するか聞かれる
Y
# 続行するか聞かれる
Y
# rc fileの場所を聞かれる
/Users/zick/.zshrc
.zshrcを確認すると末尾に以下の行が追加されていた
# The next line updates PATH for the Google Cloud SDK.
if [ -f '/Users/zick/prog/google-cloud-sdk/path.zsh.inc' ]; then . '/Users/zick/prog/google-cloud-sdk/path.zsh.inc'; fi

# The next line enables shell command completion for gcloud.
if [ -f '/Users/zick/prog/google-cloud-sdk/completion.zsh.inc' ]; then . '/Users/zick/prog/google-cloud-sdk/completion.zsh.inc'; fi
便利なことにTABキーでの補完も効くようになった。
省略可と書いてるけど次のコマンドも実行。
% ./google-cloud-sdk/bin/gcloud init
# ログインするか聞かれる
Y
# ブラウザが開くのでGoogleアカウントにログイン
# なんかチュートリアルと違う気がするけど、
# プロジェクトを選べと聞かれる。
# 何故か作成済みのプロジェクトがあったが、
# 新規プロジェクトを作成
2
# プロジェクト名を聞かれるのでテキトーに入力
hogehoge
チュートリアルでは次に gcloud projects create コマンドを実行すると書いてあるけど、多分既にプロジェクトが作られてしまったので省略。
プロジェクトが作られたか確認する。
% gcloud projects describe hogehoge
無事作られたことを確認。最初からあったプロジェクトも確認してみたら、2011-10-24に作られていた。学生のときになにか試したのかもしれないが全く覚えていない。見なかったことにする。
次はApp Engine appを作成する。
% gcloud app create --project=hogehoge
# regionを聞かれるので asia-northeast1 (東京) を選択
# ちなみに asia-northeast2 は大阪
2
次はbillingの設定をしろと書いてあるがお金の話は面倒なので後回しにした(そのせいで後で失敗する)。
gitをインストールしろと書いてあるがすでに入っているので無視。
Go用のApp Engine extensionをインストールするために以下のコマンドを実行
% gcloud components install app-engine-go
# 続行するか聞かれる
Y
「あんたがGoに詳しくてすでにインストールしてるのを仮定してるよ」と書いてある。Goに詳しくないのはさておき、Goをインストールしたのは大昔のためバージョンを確認。
% go version
go version go1.0.3
これはひどい。Goの公式サイトからmacOS用のpkgファイルをダウンロードしてgo1.13.7をインストールした。
次はサンプルプログラムをダウンロードしてデプロイ。
% git clone https://github.com/GoogleCloudPlatform/golang-samples.git
% cd golang-samples/appengine/go11x/helloworld
% gcloud app deploy
Updating service [default]...failed.
ERROR: (gcloud.app.deploy) Error Response: [7] Access Not Configured. Cloud Build has not been used in project hogehoge before or it is disabled.
どうせbillingの設定してないからだろうなと思ったらCloud Buildが無効だと言われる。なんやそれと思ったが、Cloud Buildを有効にするにはbillingの設定が必要という話のようだ。しぶしぶクレジットカードなどを登録して再度 gcloud app deploy を実行。今度は成功。Cloud Buildは自動で有効化された。
最後に起動したappをブラウザで確認。
% gcloud app browse
# ブラウザが起動する
あなたの予想に反して、 Hello, World! が表示された。

code-prettify