Go+App Engine+Cloud SQLで始めるGo言語Webアプリケーション開発

こんにちは!先週末、GalaというGoogle主催のGo言語ハッカソンへ参加し、Webアプリケーションのベース開発とGoogle Cloud Platformの対応をしていました。

Go言語+Google Cloud Platform

Gala gcp 00

さて、今回はそのハッカソンで用いたGoogle Cloud PlatformとGo言語によるWebアプリケーションの開発を紹介したいと思います。前半でGo+App Engine, Cloud SQLの解説をし、後半では実際にGCP環境を利用した開発について書かせていただきます。

既にGAEに詳しいが、Go言語でのWebアプリケーション開発にピンと来ない方は後半から記事を読むことをオススメします。

Google App Engine

Gcp gae

Google App Engine はGAEと略されるPaaSで、サポートしている言語は Java, Python, PHP そして Go です。GAEの機能としては

  • データの永続化とトランザクション機構
  • オートスケールとロードバランシング
  • 非同期にキューの処理を実行可能
  • タスクスケジューリング
  • 他のGoogleのサービスやAPIとの連携が可能

ただし、データストアにRDBMSを使用するには次に紹介するCloud SQLを使用しなくてはなりません。

Google Cloud SQL

Gcp sql

Cloud SQLはDBにMySQLを採用したフルマネージドサービスです。GAEでは最初からデータの永続化に Google Cloud Datastore のフレームを提供していますが、Cloud DatastoreはKVSのため、RDBMSを使用するにはCloud SQLかGoogle Compute Engine上に構築するかが必要となります。
今回のハッカソンではなるべくインフラの構築に時間をかけたくなかったので、Cloud SQLを使用することにしました。

GCPとWebアプリケーションの設計

GCP (GAE) をWebアプリケーションで最大に活用するための設計はGoogle Cloud Platformのページでも紹介されています。

01_WebApp_ArchDiagram-1

App Engineの機能にある「データの永続化」はCloud Datastoreで可能ですし、キャッシュ処理もMemcacheによって可能です。
これらの構築を全てGCP側で連携するだけで実現が可能となっています。

ref: Architecture: Web Application on Google App Engine


Go言語Webアプリケーション開発

GCPでアカウントを取得し、Go言語のApp Engineをインストールしてください。

$ goapp version
go version go1.4.2 (appengine-1.9.30) darwin/amd64

プロジェクト作成

今回はtagcloudという名前でサンプルWebアプリケーションを作成することにします。プロジェクトの新規作成ですが、GAE/Goで開発するときは通常の $GOROOT, $GOPATH を使用せずに開発をします。
ここを気にせずに開発をしていると、通常のGo環境とGo App Engineの環境が混同して変なエラーに悩まされたことがあるので、各自が自分に合った構成を見つけておくとベストです。

私の例ですが、新規プロジェクトを作成するときは新しくディレクトリを作成し、そのディレクトリを直接$GOPATHに通して開発を進めています。そして、プロジェクトのルートに app.yaml と初期ロードファイルを置き、アプリケーションコードは app 以下に置いています。(もしくは別プロジェクトとしてgo getしています。)

tagcloud/             # プロジェクト ($GOPATH)
 |_ .envrc            # 環境変数管理
 |_ app/              # アプリケーションコード
 |  |_ app.yaml          # GAE Manifest
 |  |_ init.go           # 初期ロードファイル
 |_ src/              # $GOPATH/src
    |_ github.com/
    |_ code.google.com/
    |_ golang.org/

$GOPATH などの環境変数を変更する必要があるためdirenvというツールを使用しています。direnvはディレクトリに.envrcが存在するとディレクトリ移動時に読み込みを実行します。

# GoAppEngineの存在確認
goapp=`which goapp 2>&1`
if [[ ! "${?}" = "0" ]]; then
  echo "Need to install `goapp` to execute this script."
  exit 127
fi
GOAPPROOT="`dirname $goapp`/goroot"

export GOROOT="${GOAPPROOT}"   # GoAppEngineのGOROOTを設定する
export GOPATH="`pwd`"          # プロジェクトルートをGOPATHに設定する
export GOBIN="${GOPATH}/bin"
# export PATH=${GOBIN}:${PATH} # 必要ならばパスを通しておく

このスクリプトを.envrcに記述しておくことによって自分のGAE/Go環境の設定をシームレスに変更しています。

Hello world!

GAE/Goで「Hello world!」を出力させるサンプルはHello, World! in 5 minutesのページに書いてあります。
本来、ここの説明は公式ページや別のサイトを見るだけで済むのですが少しだけ解説をします。

「Hello world!」の作成についてはGo標準のhttpパッケージを使用するだけで完成させることができるので初心者の方でも問題なく作成できます。

init関数でハンドリング

package tagcloud

import (
	"fmt"
	"net/http"
)

func init() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "Hello, world!")
	})
}

httpパッケージの超基本的な記述だけでOKです。ここで必ず注意していただきたいのですがmain関数は使用せず、initで必要な処理をハンドルしておきます。main関数はGAE側が提供しています。間違えて使用してしまうと「404 page not found」が出力されます。

このくらいの処置を書くなら下記のテンプレートファイルをVimから使用すれば簡単に記述することができますね。

app.yaml

app.yamlの設定に関しては「Configuring with app.yaml」にドキュメントがあります。初回ならば以下のようなフォーマットでapp.yamlを用意しておくことになります。

version: 1
runtime: go
api_version: go1

handlers:
- url: /.*
  script: _go_app

上記をそのままコピペして保存してください。一度でもGAEを触ったことがある方は application: [project ID] の存在を知っていると思いますが、ここに書かなくてもよい情報なので今回は割愛しています。

アプリケーションの起動

プロジェクトのルートにてapp.yamlが存在している場所を指定して起動します。今回だと goapp serve ./app とすればlocalhostでアプリケーションが立ち上がります。

$ goapp serve ./app
INFO     2016-01-25 12:59:14,211 api_server.py:205] Starting API server at: http://localhost:57920
INFO     2016-01-25 12:59:14,215 dispatcher.py:197] Starting module "default" running at: http://localhost:8080
INFO     2016-01-25 12:59:14,217 admin_server.py:116] Starting admin server at: http://localhost:8000

"default"で起動しているlocalhost:8080を叩けば Hello world! の文字が表示されます。

$ curl -X GET "http://localhost:8080"
Hello, world!

このserveコマンドはファイルの変更を検知して、ライブリロードを行ってくれるので非常に開発がやりやすいです。ファイルを変更してみると下記の様に表示されてビルド→起動を行います。

INFO     2016-01-29 07:33:33,656 module.py:401] [default] Detected file changes:
  /path/to/tagcloud/tagcloud.go

アプリケーションのデプロイ

まだ「Hello world!」しか表示されませんがデプロイをします。GCPにて新規にプロジェクトを作成し、そのプロジェクトのproject IDを指定してコマンドを叩くだけでデプロイが完了します。

Gcp tagcloud

project IDは真ん中くらいに記載されています。(少しモザイクを入れている部分です)

$goapp deploy -application tagcloud-xxxxx ./app

デプロイ完了後、http://[project ID].appspot.com へアクセスすると「Hello world!」が表示されていると思います。

2. +Webアプリケーションフレームワーク

先ほどの実装ではGo標準のhttpパッケージでしたが、Webアプリケーション開発をするなら何かのWebアプリケーションフレームワークを使用したいと思うはずです。
Go界隈で大体名前が上がってくるフレームワークと言ったら下記ですかね。(個人の偏見があります。)

各種フレームワークのベンチマークがginのリポジトリにあるので、そちらを参照してみるのも面白いです。
ref: BENCHMARKS.md

フレームワークとGAE

GoのWebアプリケーションを使用した開発をした場合、どのようにGAEにハンドリングさせればいいのか微妙にわかりづらいかもしれません。
フレームワークのコアとなる機能は大抵 Handlerインタフェースを満たしているので、これによって起動させることが可能です。

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

・フレームワークのハンドリング

独自のビルドシステムを持っているRevelとkochaについてはハンドリングさせるのが面倒(生成しなければいけない)なので今回は省略します。

package gowaf

import (
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/go-martini/martini"
	"github.com/zenazn/goji"
	"github.com/zenazn/goji/web"
)

func initNetHTTP() {
	http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "Hello, world!")
	})
}

func initGoji() {
	goji.Get("/ping", func(c web.C, w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "Hello, world!")
	})
	http.Handle("/", goji.DefaultMux)
}

func initGin() {
	router := gin.Default()
	router.GET("/ping", func(c *gin.Context) {
		c.String(200, "Hello, world!")
	})
	http.Handle("/", router)
}

func initMartini() {
	m := martini.Classic()
	m.Get("/ping", func() string {
		return "Hello world!"
	})
	http.Handle("/", m)
}

func init() {
	// どれか一つだけコメントを解除
	// initNetHTTP()
	// initGoji()
	initGin()
	// initMartini()
}

それぞれ、http.Handle(string, http.Handler)の第2引数に渡している値が http.Handlerインタフェースを満たしています。

所感:Go+Webアプリケーション・フレームワーク

個人的に、一番大好きなGoのWebアプリケーション・フレームワークはGinです。

Gin

ただ、大好きと言っているくせに最大限の活用はせず、GinをラッピングしたフレームワークとしてGinを最大に活用しています。
Goの依存ポリシー上で全てをフレームワークに任せてしまうと細かくケアしたい場合にやり難さが生じるのである程度の関数オーバーヘッドを許容してラッパーを作成しています。
まだ重量級のRevelや他のフレームワークの開発も進行していますが、先週公開した「net/httpより10倍速いvalyala/fasthttpが面白そうなので調査してみた件」などの対応なども柔軟に進まないのでGo標準のnet/httpだけでWebアプリケーションを組むのもありだと思っています。(net/httpで実装していくと、結局俺々フレームワークが出来上がるんですけどね……)

RevelWhiteLines

そもそも、GAEの場合はGAEが持っている appengine.Context がフレームワークの機能を提供するコンテキストなので、GAEの場合は本当にnet/httpで良いと思っています。そうせずに何かのフレームワークを使用すると Context が2つ存在したりするので、段々苦しくなってくると思います。

3. Cloud SQL

Cloud SQLをGAE上で実行できるドライバーは2種類あり、 go-sql-driver/mysqlziutek/mymysqlのどちらかとなります。

GAE上ではlocalhostへの接続になるので、指定するユーザは % (any) ではなく localhost に接続できるようにしておきましょう。

go-sql-driver/mysql

import "database/sql"
import _ "github.com/go-sql-driver/mysql"

func init() {
	db, err := sql.Open("mysql", "user@cloudsql(project-id:instance-name)/dbname")
}

ziutek/mymysql/godrv

import "database/sql"
import _ "github.com/ziutek/mymysql/godrv"

func init() {
	db, err := sql.Open("mymysql", "cloudsql:instance-name*dbname/user/password")
}

接続後はsqlパッケージの通りに使用できますし、様々なORMが上記のドライバをサポートしていれば問題なく使用することが可能です。
非常に簡単なのですが、Cloud SQL+Goで接続するためのリファレンスを探し出すのに非常に苦労しました。色々なサイトを探したのですが何故か見つけられず…

ref: The cloudsql package

O/Rマッパー

ORMも色々転がっていますが、大体使うものが固定されてきた感じがします。

正直、インターフェースに関してはどれも似通っているので、速度の面や開発のし易さ(マイグレーション機能が組み込みであるか否か)とサポートがしっかり継続しているかで選定すれば良いと思います。

(余談ですが、「ORM」ではなく「O/Rマッパー」が正式なんですよね。)

おまけ

エッジキャッシュ

Google Cloud Platformの魅力として、強力なエッジキャッシュを利用できる点があります。詳しくは「GCP エッジキャッシュ」を参照するとよいでしょう。

Go言語で、レスポンスをキャッシュにのせる方法は下記の様にすればOKです。

import (
	"fmt"
	"net/http"
)

func init() {
	http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Cache-Control", "public, max-age=86400")
		w.Header().Set("Pragma", "Public")
		fmt.Fprint(w, "Hello, world!")
	})
}

これだけでGoogleのコンテンツと同じように自分のコンテンツもキャッシュされるようになります。

Cloud Datastore

Gcp datastore

前の方で少しだけ話題に出しましたが、GAE (appengine.Context) が標準で提供しているデータストアの Cloud Datastoreです。データオブジェクトを格納し、こちらもGAEと同様自動でスケールしてくれます。
RDBMSを使用する用途がない場合はDatastoreを利用するほうが手っ取り早いと思います。

Cloud Storage

Gcp storage

AWSで言えばS3と同じものです。静的ファイルを置いておくこともできますし、作成したディレクトリをそのままHTMLとして配信することも可能です。(ドメインをあてることも可能)

まとめ

Go言語のWebアプリケーション・フレームワークやGCP (GAE, Cloud SQL, …) のことについて書きましたが、まだまだGCP全般の情報が少ないと感じています。
GAEやGloud SQL/Datastoreについて、構築も実装(使用)も非常に簡単なのでもっとGCPは盛り上がって良いんじゃないかなと思っています。
Go言語はかなり認知度があがってきたので、これからはGCPも含めてインプット・アウトプットを深めたいと思います。そして、もう少し新鮮なGoの情報を流すことができればなと。

また、今回は内部の話より表面的な話の方が多かったので、次回は実装の話をしようと思います。

  • このエントリーをはてなブックマークに追加

エウレカでは、一緒に働いていただける方を絶賛募集中です。募集中の職種はこちらからご確認ください!皆様のエントリーをお待ちしております!

Recommend

エウレカが目指す“モダンな情シス”とは?

SlackのSlash CommandsをApp Engineで稼働させる