💨

Cloud Run に Golang のAPI サービスをデプロイしてみた

2023/04/26に公開

こんにちは、クラウドエース SRE ディビジョン の小堀内です。
最近、Google Cloud の学習の一環として、Golang で作成した API サービスを Cloud Run にデプロイしてみました。
その結果のアウトプットとして、本記事を執筆することにしました。

Cloud Run とは


Cloud Run の特徴を簡単にまとめると次のものが挙げられます。

  • サーバーレス の コンピューティングサービス
    • サーバー管理が不要なので開発者はアプリケーションのコードに集中することができる
  • 自動スケーリング機能
    • アプリケーションの負荷に合わせて自動的にコンテナーを起動/停止することができる
  • コンテナーイメージのビルド と Cloud Run へのデプロイ
    • 開発者はローカル環境でアプリケーションを開発し、簡単にデプロイすることができる
  • HTTP リクエストに対応
    • Web アプリケーションや API サービスを構築することができる

詳細については、Cloud Run 公式ドキュメント を参照してください。

Cloud Functions との違い

Google Cloud には、Cloud Run に似たサービスとして、Cloud Functions というものが存在します。
両者の違いについても調べてみました。

Cloud Run Cloud Functions
トリガー HTTP リクエスト Google Cloud 内の特定イベント/HTTP リクエスト
デプロイ単位 コンテナーイメージ 関数
対応言語
カスタマイズ性

Cloud Run は、コンテナーイメージを使用するため、高度なカスタマイズが可能です。
Cloud Functions は、特定のイベントに対してトリガーされるため、サーバーレスのバックエンド処理に適しています。

デプロイするAPIサービスの概要

Cloud Spanner からデータを JSON 形式で取得します。

イメージ図


クライアント端末から Cloud Run に対して HTTP リクエストを送信して、Cloud Spanner からデータを受け取ります。

本来であれば、

  • Cloud Run の冗長化
  • ロードバランサー の設置
  • Cloud Armor による DDos 対策 / IP 制限 / 地理制限

などの対策が必要となりますが、本記事では省略させていただきます。

また、ローカル環境の Golang のソースコードの build, push, deploy には Google Cloud Buildpacks を使用します。

Google Cloud Buildpacks とは

ソースコードからコンテナーイメージを自動的に生成するためのツールです。

特徴

  • アプリケーションのビルドとデプロイが容易になり、迅速なアプリケーション開発が可能
  • 柔軟なカスタマイズが可能
  • Golang や Java など、複数の言語をサポート

デプロイ手順

それでは、ここからデプロイの手順を紹介していきます。

デプロイ対象のソースコードを用意

まずは、デプロイ対象のソースコードを用意します。
今回作成したソースコードは次のものになります。

package main

import (
	"cloud.google.com/go/spanner"
	"context"
	"encoding/json"
	"google.golang.org/api/iterator"
	"log"
	"net/http"
	"time"
)

func main() {
	/// パスと Spanner 読取ハンドラ の紐付け
	http.HandleFunc("/", readHandler)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

// Spanner 読取ハンドラ
func readHandler(w http.ResponseWriter, r *http.Request) {
	// 結合クエリによって取得したレコードを格納するための構造体
	type user struct {
		UserId                string
		Name                  string
		CreatedAtFromUser     time.Time
		ItemId                *string
		Qty                   *int64
		CreatedAtFromUserItem *time.Time
	}

	// Spanner DB の用意
	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
	defer cancel()
	client, err := getDatabaseClient(ctx)
	if err != nil {
		http.Error(w, "InternalServerError", http.StatusInternalServerError)
		log.Println(err)
		return
	}
	defer client.Close()

	// Statement によって SQL 文を実行
	iter := client.ReadOnlyTransaction().Query(ctx, spanner.Statement{
		SQL: `select User.UserId, User.Name, User.CreatedAt as CreatedAtFromUser, 
                        UserItem.ItemId, UserItem.Qty, UserItem.CreatedAt as CreatedAtFromUserItem
		      from User left join UserItem
			on User.UserId = UserItem.UserId`,
		Params: nil,
	})

	// 上記クエリによって取得したレコードを DTO に格納する
	var users []user
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			iter.Stop()
			break
		}
		if err != nil {
			http.Error(w, "InternalServerError", http.StatusInternalServerError)
			log.Println(err)
			iter.Stop()
			return
		}
		var user user
		if err := row.ToStructLenient(&user); err != nil {
			http.Error(w, "InternalServerError", http.StatusInternalServerError)
			log.Println(err)
			iter.Stop()
			return
		}
		users = append(users, user)
	}

	// HTTP レスポンスとして JSON を返却
	resp := map[string][]user{"Users": users}
	if err := json.NewEncoder(w).Encode(resp); err != nil {
		http.Error(w, "InternalServerError", http.StatusInternalServerError)
		log.Println(err)
		return
	}
}

// Spanner の DB クライアントを取得する
func getDatabaseClient(ctx context.Context) (*spanner.Client, error) {
	databaseName := "DATABASE_NAME"
	client, err := spanner.NewClient(ctx, databaseName)
	if err != nil {
		return nil, err
	}
	return client, nil
}

Google Cloud Buildpacks の利用

次に、Google Cloud Buildpacks を利用して、

  • build

  • push

  • deploy

を行います。

build & push

ローカル環境で作成した Golang のソースコードを build して、コンテナーイメージ を作成します。
さらに、作成した コンテナーイメージ を Artifact Registry に push します。

# 1. ソースコードディレクトリに移動
$ cd path/to/your/app

# 2. プロジェクト ID を変数に格納
PROJECT_ID=your-project-id

# 3. Artifact Registry リポジトリ名を変数に格納
AR_REPO_NAME=your-repo-name

# 4. Artifact Registry パス/イメージ名:タグを変数に格納
$ IMAGE=asia-northeast1-docker.pkg.dev/$PROJECT_ID/$AR_REPO_NAME/sample:v1

# 5. Cloud Build で build して Artifact Registry に push
$ gcloud builds submit --pack image=$IMAGE

Artifact Registry への push に成功するとイメージの一覧に表示されるようになります。

deploy

次に、Artifact Registry に push したコンテナーイメージを元に、次の手順で Cloud Run へデプロイします。

Cloud Run サービスの作成

Artifact Registry に push されたコンテナーイメージを選択


コンテナーイメージを選択後、サービスの作成画面下部にある作成ボタンを押下します。

作成が完了すると Cloud Run サービスの詳細が表示されます。

deploy されたことを確認する

今回は JSON 形式でデータを取得する API サービスを作成したので、curl コマンド を使用して結果を確認してみます。

データを取得できることが確認できました。

おわりに

最近入社して、Google Cloud に関する学習を始めたところですが、便利なサービスの数に驚いています。
今後も様々なサービスを触り、そのアウトプットとして記事を執筆していく予定ですので、引き続きよろしくお願いいたします。
最後まで読んでいただきありがとうございました。

参考記事

Discussion