Go API を CloudRun と PlanetScale を使ってデプロイ
はじめに
最近開発合宿を行った際に、Go の API の DB に PlanetScale を利用して、CloudRun にデプロイを行いました
そこで手軽さを感じたので、ハンズオン形式で紹介したいと思います
PlanetScale とは
PlanetScale は、MySQL 互換のクラウドネイティブなデータベースです。基本的に無料枠があるので、個人開発には最適です
GitHub と連携されているようで、もしユーザー名やパスワードの情報がリポジトリに Push されたときに自動で検知して変更するというのがあり、セキュリティ面でも安心です
ただ、この仕様を知らずに適当に Push してしまったので、そのあたりは注意が必要です
CloudRun とは
CloudRun は、コンテナを使って、サーバーレスでアプリケーションを実行することができるサービスです
CloudRun は、GCP のサービスなので、GCP のアカウントが必要です。今回はアカウントを作成(クレジット登録)している前提で話を進めていきます。
CloudRun では、Artifact Registry(AWS でいう ECR)にコンテナを Push して、CloudRun にデプロイすることができます
CloudRun で起動するときにイメージを起動して、その中で CMD で Go のバイナリを実行するという形になります
開発環境
- go version go1.20.3 linux/amd64
- VSCode
- Docker version 23.0.4
- Google Cloud SDK 418.0.0
Google Cloud SDK のインストールは、以下の記事を参考にしてください
ハンズオン
1. Go(gin)の API を作成する
まずは、Go の API を作成します
$ mkdir cloudrun-go-handson
$ cd cloudrun-go-handson
$ go mod init gin-ping
$ go get github.com/gin-gonic/gin
$ touch main.go
main.go に Ping の API を作成します
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run() // listen and serve on 0.0.0.0:8080
}
サーバーを起動して、動作確認をします
$ go run main.go
$ curl localhost:8080/ping
{"message":"pong"}
2. Dockerfile を作成する
CloudRun では、コンテナを使ってデプロイを行うので、Dockerfile を作成します
$ touch Dockerfile
FROM golang:1.20.4-alpine3.18
WORKDIR /app
COPY ./ ./
RUN go mod download
# バイナリファイルにビルド
RUN GOOS=linux GOARCH=amd64 go build -mod=readonly -v -o server
EXPOSE 8080
# バイナリファイルを実行
CMD ./server
3. CloudRun にデプロイする
CloudRun にデプロイするために、GCP のプロジェクトを作成します
Google Cloud Platform にログインして、プロジェクトを作成します
「コンソールに移動」をクリックして、プロジェクトを作成します
「新しいプロジェクト」をクリックして、プロジェクト名を入力します
「プロジェクト名」に「cloudrun-go-handson」と入力して、「作成」をクリックします
(名前は各自で変更してください、読み替えをして先を進めます)
作成したに変更します(ここでプロジェクト ID が表示されるので、メモしておきます)
3-1. Artifact Registry にコンテナを Push する
Artifact Registry にコンテナを Push するために、API を有効にします
左メニューから「API とサービス」「ライブラリ」をクリックします
「Artifact Registry API」を検索して、API を有効にします
「有効」をクリックします
左メニューから「その他のプロダクト」「CI/CD」「Artifact Registry」「リポジトリ」をクリックします
「リポジトリを作成」をクリックします
「名前」に「go-api」、「リージョン」に「us-west1」を入力して、「作成」をクリックします
ターミナルに戻ります
$ gcloud auth configure-docker us-west1-docker.pkg.dev
$ docker build -t us-west1-docker.pkg.dev/cloudrun-go-handson/go-api/api-image:latest .
$ docker push us-west1-docker.pkg.dev/cloudrun-go-handson/go-api/api-image:latest
Push ができました
3-2. CloudRun にデプロイする
左メニューから「その他のプロダクト」「サーバーレス」「Cloud Run」「サービスを作成」をクリックします
「コンテナイメージの URL」の「選択」をクリック
「Artifact Registry」から Push したイメージを選択します
「選択」をクリックします
「リージョン」に「us-west1」を選択します
「認証」を「未承認の呼び出しを許可」に変更して「作成」をクリックします
3-3. CloudRun にデプロイした API を確認する
デプロイが完了したら、URL にアクセスして動作確認をします
URL をコピーして Curl で確認します
$ curl https://api-image-lot457qdqq-uw.a.run.app/ping
{"message":"pong"}w
4. DB(PlanetScale)接続を gorm で行う
4-1. PlanetScale の設定を行う
PlanetScale にログインして、プロジェクトを作成します
ここでは「neko」というデータベース名にしてみました。適宜読み替えてハンズオンを進めてください
メニューから「Console」をクリックして「Connect」をクリックします
ユーザーテーブルを作成します
CREATE TABLE `users` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`age` int(11),
PRIMARY KEY (`id`)
) ENGINE InnoDB,
CHARSET utf8mb4,
COLLATE utf8mb4_0900_ai_ci;
サンプルデータも作っておきます
INSERT INTO `users` (id, name, age) VALUES (1, 'watanabe', 20);
つぎに接続のための情報を取得します
メニューから「Settings」をクリックします
左メニューから「Password」をクリックします
「New Password」をクリックします
「Create Password」をクリックします
接続情報が表示されます
「Copy」をクリックして、接続情報をコピーします
4-2. ユーザー取得 API を作成する
Gorm と MySQL ドライバをインストールします
$ go get -u gorm.io/gorm
$ go get -u gorm.io/driver/mysql
main.go を編集します
package main
import (
"github.com/gin-gonic/gin"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Id int `gorm:"primaryKey" json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
dsn := "[ユーザー名]:[パスワード]@tcp(aws.connect.psdb.cloud)/[DB名]?tls=true"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.GET("/users", func(c *gin.Context) {
var users []User
db.Unscoped().Find(&users)
c.JSON(200, users)
})
r.Run() // listen and serve on 0.0.0.0:8080
}
dsn
は先程コピーした接続情報をもとにそれぞれで書き換えます
接続できるか確認します
$ go run main.go
$ curl localhost:8080/users | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 131 100 131 0 0 740 0 --:--:-- --:--:-- --:--:-- 740
[
{
"ID": 0,
"CreatedAt": "0001-01-01T00:00:00Z",
"UpdatedAt": "0001-01-01T00:00:00Z",
"DeletedAt": null,
"id": 1,
"name": "watanabe",
"age": 20
}
]
4-3. CloudRun にデプロイする
接続情報のユーザー名とパスワードをシークレットに設定してサイドデプロイをしてみます
まずは main.go のユーザー名とパスワードを環境変数から読み込むように設定します
package main
import (
"fmt"
"os"
"github.com/gin-gonic/gin"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Id int `gorm:"primaryKey" json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
user := os.Getenv("DB_USER")
password := os.Getenv("DB_PASSWORD")
dsn := fmt.Sprintf("%s:%s@tcp(aws.connect.psdb.cloud)/[DB名]?tls=true", user, password)
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.GET("/users", func(c *gin.Context) {
var users []User
db.Unscoped().Find(&users)
c.JSON(200, users)
})
r.Run() // listen and serve on 0.0.0.0:8080
}
Dockerfile では CloudRun の環境変数を読み込むように設定します
FROM golang:1.20.4-alpine3.18
ARG DB_USER
ARG DB_PASSWORD
ENV DB_USER=${DB_USER}
ENV DB_PASSWORD=${DB_PASSWORD}
WORKDIR /app
COPY ./ ./
RUN go mod download
RUN GOOS=linux GOARCH=amd64 go build -mod=readonly -v -o server
EXPOSE 8080
CMD DB_USER=${DB_USER} DB_PASSWORD=${DB_PASSWORD} ./server
新しいイメージを Push します
$ docker build -t us-west1-docker.pkg.dev/プロジェクトID/go-api/api-image:latest .
$ docker push us-west1-docker.pkg.dev/プロジェクトID/go-api/api-image:latest
検索から「Secret Manager」を選択します
「Secret Manager API」を有効にします
「シークレットを作成」をクリックします
「シークレット名」に「DB_USER」、「シークレットの値」に「接続情報のユーザー名」を入力します
「シークレットを作成」をクリックします
同じ要領で「DB_PASSWORD」、「接続情報のパスワード」も作成してください
CloudRun にデプロイします
先程作成したアプリケーションをクリックして「新しいリビジョンの編集とデプロイ」をクリック
「コンテナイメージの URL」を新しいものに変更します
「シークレット」から「シークレット参照を追加」をクリック
「シークレット」に「DB_USER」、「参照の方法」に「環境変数として公開」、「名前 1」に「DB_USER」を選択します
「付与」を押します
※ 完了は押さないでください
同じ要領で「DB_PASSWORD」も設定します
「シークレット参照を追加」をクリックして設定してください
「デプロイ」をクリックします
4-4. CloudRun にデプロイしたアプリケーションにアクセスしてみる
$ curl https://api-image-lot457qdqq-uw.a.run.app/users | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 131 100 131 0 0 363 0 --:--:-- --:--:-- --:--:-- 363
[
{
"ID": 0,
"CreatedAt": "0001-01-01T00:00:00Z",
"UpdatedAt": "0001-01-01T00:00:00Z",
"DeletedAt": null,
"id": 1,
"name": "watanabe",
"age": 20
}
]
うまくデプロイできました
片付け
以下のリソースを削除します
- Artifact Registry
- CloudRun
- Secret Manager
- プロジェクト
おわりに
CloudRun で Go のアプリケーションをデプロイする方法を紹介しました
かなり簡単にバックエンドがデプロイできたので、フロントは Firebase にすればかなり簡単に個人開発の内容をデプロイできるなと思い記事にしました
ぜひやってみてください
今回作成したリポジトリは以下になります
Discussion