💯

Go API を CloudRun と PlanetScale を使ってデプロイ

2023/06/04に公開

はじめに

最近開発合宿を行った際に、Go の API の DB に PlanetScale を利用して、CloudRun にデプロイを行いました

そこで手軽さを感じたので、ハンズオン形式で紹介したいと思います

PlanetScale とは

PlanetScale は、MySQL 互換のクラウドネイティブなデータベースです。基本的に無料枠があるので、個人開発には最適です

GitHub と連携されているようで、もしユーザー名やパスワードの情報がリポジトリに Push されたときに自動で検知して変更するというのがあり、セキュリティ面でも安心です

ただ、この仕様を知らずに適当に Push してしまったので、そのあたりは注意が必要です

https://planetscale.com/sign-up

CloudRun とは

CloudRun は、コンテナを使って、サーバーレスでアプリケーションを実行することができるサービスです

CloudRun は、GCP のサービスなので、GCP のアカウントが必要です。今回はアカウントを作成(クレジット登録)している前提で話を進めていきます。

https://cloud.google.com/run?hl=ja

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 のインストールは、以下の記事を参考にしてください

https://cloud.google.com/sdk?hl=ja

ハンズオン

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 を作成します

main.go
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
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 にログインして、プロジェクトを作成します

https://cloud.google.com/

「コンソールに移動」をクリックして、プロジェクトを作成します

「新しいプロジェクト」をクリックして、プロジェクト名を入力します

「プロジェクト名」に「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 にログインして、プロジェクトを作成します

https://console.planetscale.com/

ここでは「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 を編集します

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 のユーザー名とパスワードを環境変数から読み込むように設定します

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 の環境変数を読み込むように設定します

Dockerfile
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 にすればかなり簡単に個人開発の内容をデプロイできるなと思い記事にしました

ぜひやってみてください

今回作成したリポジトリは以下になります

https://github.com/jinwatanabe/cloudrun-cicd-handson/tree/main/cloudrun-go-handson

参考

GitHubで編集を提案

Discussion