👏

開発費用の削減: Cloud SQL インスタンスの起動と停止をスケジュールする

2024/09/25に公開

はじめに

こんにちは、クラウドエースの許と申します。

こちらの記事は、2021 年 6 月 21 日に Google Cloud の公式ブログで投稿された「開発費用の削減: Cloud SQL インスタンスの起動と停止をスケジュールする」(以降、2021 年記事)の 2024 年 9 月版(以降、2024 年版)となります。

2021 年記事では、Cloud Run Functions(旧: Cloud Functions)を利用して起動と停止の制御を行なっていたのですが、当時より以下の2点が変わっているため、2021 年記事のリマスター版的な位置付けで当記事を執筆しております。

  • Cloud Run Functions の仕様やUIが大幅に変わっている
  • 2021 年記事で利用していた、SQLAdmin の関数の一部が非推奨になっている

前提

作業者がオーナー権限を持った Google Cloud プロジェクトで操作を行なっていきます。
もし操作の途中で不足している権限がありましたら、適宜必要な権限を付与してください。

SQLインスタンスの作成

[インスタンスを作成]をクリックして、SQL インスタンスを作成します。
sql4

  1. 使用したいデータベースエンジンを選択します。(筆者は MySQL が好みなので、MySQL を選択します。)
    sql1

  2. エディションのプリセット、インスタンスID、パスワード、リージョンを入力していきます。今回は動作するかどうかの確認が目的なので、最低限の設定で進めます。
    sql2
    sql3

  3. [インスタンスを作成]をクリックします。

Cloud Pub/Sub トピックの作成

サブスクリプションはトリガーに設定すると自動で作成されるため、トピックのみ作成します。
手順は少ないですが、以下の操作で作成できます。

  1. Cloud Pub/Sub の画面を開いて、[トピックを作成]を押下します。
  2. トピック ID を記入して、トピックを作成します。(トピック ID はなんでもいいですが、今回は InstanceMgmt で進めていきます。)
    pubsub1

Cloud SQL インスタンスの起動と停止を制御する Cloud Run Functions の作成

このセクションが 2021 年記事と大きく異なる部分です。
2021 年記事では、第一世代を利用していましたが、2024 年時点では第二世代を利用します。
第一世代と異なる点は、イベントの受け取り方が event.Event を利用して受け取るようになったことです。

ただし、画面上での操作は大きく変わらないため、手順に従って進めていただければ問題ありません。
なお、2021 年記事と同様に画面上で関数を作成する予定でしたが、原因不明のエラーが発生したため、今回は gcloud コマンドを利用して関数を作成します。
cloudrun1

Cloud Run Functionsの作成の有効化

  1. Cloud Run Functions の画面を開きます。
  2. [ファンクションを作成]をクリックします。
  3. 以下の画面が表示されたら、[有効にする]をクリックします。(すでに有効になっている場合はこの画面は表示されません。)
    cloudrun2

コードの実装

  1. functions.gogo.mod の2つのファイルを作成します。
    cloudrun3
  2. 以下の内容で functions.go を作成します。
// Package p contains a Pub/Sub Cloud Function.
package p

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/cloudevents/sdk-go/v2/event"
	"google.golang.org/api/option"
	"log"

	"github.com/GoogleCloudPlatform/functions-framework-go/functions"
	sqladmin "google.golang.org/api/sqladmin/v1beta4"
)

func init() {
	functions.CloudEvent("ProcessPubSub", ProcessPubSub)
}

// PubSubMessage is the payload of a Pub/Sub event.
// See the documentation for more details:
// https://cloud.google.com/pubsub/docs/reference/rest/v1/PubsubMessage
type PubSubMessage struct {
	Data []byte `json:"data"`
}

type MessagePublishedData struct {
	Message PubSubMessage
}

type MessagePayload struct {
	Instance string
	Project  string
	Action   string
}

// ProcessPubSub consumes and processes a Pub/Sub message.
func ProcessPubSub(ctx context.Context, e event.Event) error {
	log.Println("ProcessPubSub function invoked")

	// Unmarshal the Pub/Sub message.
	var pubsubData MessagePublishedData
	if err := e.DataAs(&pubsubData); err != nil {
		fmt.Errorf("error unmarshalling Pub/Sub message: %v", err)
	}

	var psData MessagePayload
	err := json.Unmarshal(pubsubData.Message.Data, &psData)
	if err != nil {
		log.Println(err)
	}
	log.Printf("Request received for Cloud SQL instance %s action: %s, %s", psData.Action, psData.Instance, psData.Project)

	// Create the Google Cloud SQL service.
	sqladminService, err := sqladmin.NewService(ctx, option.WithScopes(sqladmin.CloudPlatformScope))

	// Get the requested start or stop Action.
	action := "UNDEFINED"
	switch psData.Action {
	case "start":
		action = "ALWAYS"
	case "stop":
		action = "NEVER"
	default:
		log.Printf("No valid action provided")
	}

	// See more examples at:
	// https://cloud.google.com/sql/docs/sqlserver/admin-api/rest/v1beta4/instances/patch
	rb := &sqladmin.DatabaseInstance{
		Settings: &sqladmin.Settings{
			ActivationPolicy: action,
		},
	}

	resp, err := sqladminService.Instances.Patch(psData.Project, psData.Instance, rb).Context(ctx).Do()
	if err != nil {
		log.Fatal(err)
	}
	log.Printf("%#v\n", resp)
	return nil
}
  1. 以下の内容で go.mod を作成します。
module example.com/gcf

go 1.22.7

require (
	github.com/GoogleCloudPlatform/functions-framework-go v1.7.0
	github.com/cloudevents/sdk-go/v2 v2.15.2
	google.golang.org/api v0.196.0
)

require (
	cloud.google.com/go/auth v0.9.3 // indirect
	cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
	cloud.google.com/go/compute/metadata v0.5.0 // indirect
	github.com/felixge/httpsnoop v1.0.4 // indirect
	github.com/go-logr/logr v1.4.2 // indirect
	github.com/go-logr/stdr v1.2.2 // indirect
	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
	github.com/google/s2a-go v0.1.8 // indirect
	github.com/google/uuid v1.6.0 // indirect
	github.com/googleapis/enterprise-certificate-proxy v0.3.3 // indirect
	github.com/googleapis/gax-go/v2 v2.13.0 // indirect
	github.com/json-iterator/go v1.1.10 // indirect
	github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
	github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect
	go.opencensus.io v0.24.0 // indirect
	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect
	go.opentelemetry.io/otel v1.29.0 // indirect
	go.opentelemetry.io/otel/metric v1.29.0 // indirect
	go.opentelemetry.io/otel/trace v1.29.0 // indirect
	go.uber.org/atomic v1.4.0 // indirect
	go.uber.org/multierr v1.1.0 // indirect
	go.uber.org/zap v1.10.0 // indirect
	golang.org/x/crypto v0.26.0 // indirect
	golang.org/x/net v0.28.0 // indirect
	golang.org/x/oauth2 v0.23.0 // indirect
	golang.org/x/sys v0.24.0 // indirect
	golang.org/x/text v0.17.0 // indirect
	google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed // indirect
	google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
	google.golang.org/grpc v1.66.0 // indirect
	google.golang.org/protobuf v1.34.2 // indirect
)
  1. 以下の内容で関数を作成します。下記のコマンドを実行してください。(コマンドはカレントディレクトリが functions.gogo.mod が入ったディレクトリを前提としています。カレントディレクトリが別の場所にある場合は、移動して実行をお願いします。)
    • 環境名: Cloud Run function
    • 関数名: start-or-stop-cloud-sql-instance
    • リージョン: asia-northeast1
    • トリガー: Cloud Pub/Sub
    • トピック: InstanceMgmt
    • ランタイム: Go 1.22
gcloud functions deploy start-or-stop-cloud-sql-instance \
--gen2 \
--runtime=go122 \
--region=asia-northeast1 \
--source=. \
--entry-point=ProcessPubSub \
--trigger-topic=InstanceMgmt \
--project=<プロジェクト ID>
  1. Cloud Build が動いて、デプロイされるので、しばらく待ちます。
  2. 画像のように関数が作成されていたら成功です。
    cloudrun4

Cloud Run Functions のサービスアカウントに IAM ロールを付与

現状のままだと、SQL を起動、停止する権限がないため、サービスアカウントに IAM ロールを付与します。

  1. IAM と管理の画面を開きます。
  2. Cloud Run Functions で利用しているサービスアカウントのペンマークをクリックします。
  3. Cloud SQL 管理者のロールを付与します。
    cloudrun5
  4. [保存]をクリックします。

Cloud SQL Admin API の有効化

  1. API とサービスの画面を開きます。
  2. Cloud SQL Admin API を検索して、有効化します。
    cloudrun6

これで、Cloud Run Functions を動作させるための準備が整いました。

Cloud Schedulerの設定

ここは 2021 年記事と変わらないため、手順に従って進めていただければ問題ありません。

  1. Cloud Scheduler の画面を開き、[ジョブの作成]をクリックします。

  2. 名前、リージョン、頻度、タイムゾーンを設定し[続行]をクリックします。

  3. [ターゲットタイプ]から Pub/Sub を選択します。

  4. 前章で作成したトピックを選択します。

  5. 以下記載の Pub/Sub メッセージを[メッセージ本文]に記入します。

    • 起動メッセージ
    {
    	"Instance": "<SQLインスタンス名>",
    	"Project": "<プロジェクト名>",
    	"Action": "start"
    }
    
    • 停止メッセージ
    {
    	"Instance": "<SQLインスタンス名>",
    	"Project": "<プロジェクト名>",
    	"Action": "stop"
    }
    

    scheduler1
    scheduler2

  6. [作成]をクリックします。

この手順が終了すると、Cloud SQL インスタンスの起動と停止をスケジュールすることができるようになります。

おわりに

今回は、Cloud SQL インスタンスの起動と停止をスケジュールする方法をご紹介しました。

こちらの記事が開発費用の削減の一助になれば幸いです。

Discussion