こんにちは、新卒エンジニアのわたなべです。
このブログは、「RDBとgo言語についてちょっとわかってきたから、RDBを使ったWebAPIをデプロイしてみたい!」という人に向け、GCPのCloud RunとCloud SQLをつかった方法をご紹介します。
GCP Cloud Runはサーバーレスで簡単にWeb APIを公開することができるサービスで、料金もリクエストの処理中にしかかからないため、極めて安価に実装することができます。(例えば今回のように個人開発として簡単なものをデプロイする場合は、リクエストの回数にもよりますが、無料枠の範囲内で利用できるかと思います。料金について詳しくはこちら。)
GCP Cloud SQLは、GCPのデータベースサービスでCloud Runと簡単に接続でき、MySQL、PostgresQL、SQL Server(MS SQL)といったRDBシステムに対応しています。Cloud SQLはCloud Runと違って、起動していないときもストレージを使う以上、ある程度コストはかかってしまいますが、個人開発なら十分安価に利用できます。(今回、一番安いプランを選択し、起動時間をほぼ最小限で運用したところ、月$2.7に抑えることができました。)
今回は、このCloud RunとCloud SQL for MySQLを使って、簡単な在庫管理システムのWeb APIを実装します。
参考にしたサイト
- クイックスタート: Cloud Run に Go サービスをデプロイする
- クイックスタート: Cloud Run から Cloud SQL for MySQL に接続する
- Cloud SQL Go Connector を使用して接続を構築する
Cloud RunにGo言語をデプロイ
こちらの公式ドキュメントを参照してwslからデプロイしました。
早速手順を紹介していきます。
- 課金が有効なGoogle Cloudプロジェクトを用意する。
- Google Cloud CLIをインストールする。
Google Cloud CLI とは、Google Cloud リソースの作成と管理を行う一連のツールです。
ご自身のOSに合わせて、こちらのドキュメントの手順に従ってインストールしてください。
- gcloudを初期化してプロジェクトを設定する。
gcloud init
gcloud config set project <PROJECT_ID>
<project_id>にはプロジェクトIDを置き換えて入力します。
- デプロイ用のディレクトリを用意して、以下の内容のmain.goを作成する。
// Sample run-helloworld is a minimal Cloud Run service.
package main
import (
"fmt"
"log"
"net/http"
"os"
)
func main() {
log.Print("starting server...")
http.HandleFunc("/", handler)
// Determine port for HTTP service.
port := os.Getenv("PORT")
if port == "" {
port = "8080"
log.Printf("defaulting to port %s", port)
}
// Start HTTP server.
log.Printf("listening on port %s", port)
if err := http.ListenAndServe(":"+port, nil); err != nil {
log.Fatal(err)
}
}
func handler(w http.ResponseWriter, r *http.Request) {
name := os.Getenv("NAME")
if name == "" {
name = "World"
}
fmt.Fprintf(w, "Hello %s!\n", name)
}
こちらは公式から提供されているgoのサンプルコードで、リクエストを受け取ると”Hello World”を返す内容となっています。
- go.modを作成する。
main.goで使われているモジュールのパスとバージョンを記録したgo.modを作成します。
go mod init <module名>
ここまでで準備完了です。Cloud Runへデプロイしていきます。
- ソースコードをデプロイする。
gcloud run deploy
- ソースコードの場所を求められたらパスを入力してエンター
- サービス名を求められたら入力してエンター
- リージョンを求められたら任意のリージョンを選択(東京はasia-northeast1)
- 最終確認に対して”Y”を入力してエンター
- サービスに対して未認証の呼び出しを許可するかどうか選択(許可するとWeb APIを外部へ公開することになる)
以上を選択すればデプロイスタート。
- helloworldを確認
gcpの管理画面からCloudRunを開くとデプロイしたサービスがあるので、詳細からURLを取得します。
ブラウザからURLにアクセスして、”Hello World!”が表示されれば成功です。
ソースコードを更新してデプロイするときは、同じようにコマンドを打ってもできますが、Cloud Run詳細ページの「継続的デプロイを編集します」からgithubを連携させると便利です。
Cloud SQLのインスタンスの作成と各設定
インスタンスの作成とCloudRunからの接続はこちらの公式ドキュメントのパブリックIPを用いた接続手順を参考に行いました。
ドキュメントを参考に、Cloud SQLの管理画面もしくはコマンドラインから任意の項目を選択して、インスタンス、データベース、ユーザーを作成してください。
Cloud SQLで用意したインスタンスは、Cloud Shellで下記コマンドから直接SQL文を扱うことができます。
gcloud sql connect <インスタンス名> --user=<ユーザー名>
Cloud ShellはGoogle Cloud コンソール画面右上のこちらから開けます。
Cloud RunとCloud SQLを接続
ここからは、Cloud RunとCloud SQLを接続するための設定です。
内容は先ほどと同じ公式ドキュメントからの引用ですが、一部スキップしています。
- Cloud RunページからCloud SQLへの接続情報を設定する。
goをデプロイしたCloud Runの詳細から、「新しいリビジョンの編集とデプロイ」を選択します。
環境変数を設定します。設定する変数は以下の通り。
name | value |
INSTANCE_CONNECTION_NAME | <Cloud SQL インスタンスの接続名> |
DB_NAME | <データベース名> |
DB_USER | <DBのユーザー名> |
DB_PASS | <DBのパスワード> |
- Cloud SQLへの接続を有効にする。
先ほどと同じページの下の方に画像のようなメニューがあるので、プルダウンメニューからSQLのインスタンスを選択します。
go言語からCloud SQL for MySQLへ接続
あとは環境変数を使ってgoからSQLへ接続をするだけです。
こちらのドキュメントを参考に接続を行います。
今回はgoのORM、gormを使って接続します。
ORMを使えば、SQLを全文記述する必要がなく、MySQL以外へコードを流用するのも接続文のみ変更するだけで行えます。
実際にgormを使って接続してみましょう。
func connectWithConnector() (*gorm.DB, error) {
mustGetenv := func(k string) string {
v := os.Getenv(k)
if v == "" {
log.Fatalf("Fatal Error in connect_connector.go: %s environment variable not set.", k)
}
return v
}
dbUser := mustGetenv("DB_USER") // e.g. 'my-db-user'
dbPwd := mustGetenv("DB_PASS") // e.g. 'my-db-password'
dbName := mustGetenv("DB_NAME") // e.g. 'my-database'
instanceConnectionName := mustGetenv("INSTANCE_CONNECTION_NAME") // e.g. 'project:region:instance'
d, err := cloudsqlconn.NewDialer(context.Background())
if err != nil {
return nil, fmt.Errorf("cloudsqlconn.NewDialer: %w", err)
}
var opts []cloudsqlconn.DialOption
sqlcon.RegisterDialContext("cloudsqlconn",
func(ctx context.Context, addr string) (net.Conn, error) {
return d.Dial(ctx, instanceConnectionName, opts...)
})
dbURI := fmt.Sprintf("%s:%s@cloudsqlconn(localhost:3306)/%s?parseTime=true&loc=Asia%%2FTokyo",dbUser, dbPwd, dbName)
db, err := gorm.Open(gormcon.Open(dbURI), &gorm.Config{})
return db, err
}
これでソースコードからCloud SQLへのアクセスができるようになりました。
おまけ ~作成したAPIとその各機能の簡単なイメージ~
実際に実装したAPIのソースコードはこちらです。
このAPIは“stocks”という名前のテーブルを作成し、商品名と数量(name,amount)を管理します。
以下の機能のWebAPIをそれぞれ実装します。
入庫記録機能 /receipt
入庫APIでは、新たに登録する商品かどうかで処理が分岐します。
例1:「新たな商品”aaa”を10個」入庫を記録
例2:「既に登録された商品”aaa”を5個」入庫を記録
出庫記録機能 /shipment
例:「既に登録された商品”aaa”を7個」出庫を記録
全件取得 /get
現在のすべての在庫を一覧で取得するAPI
まとめ
今回は、新卒エンジニアでも簡単にWebAPIを実装できそうという理由で、Cloud Runを使ってみました。
このようにCloud Runは簡単で安価にAPIをデプロイできますし、実際にモバイルアプリのバックエンドAPIなどで利用されているケースもあるようです。
皆さんも是非使ってみてください。