【Cloud SQL】GolangでDBインスタンスの開始停止をスケジュール実行して節約する

2866 語
14 分
【Cloud SQL】GolangでDBインスタンスの開始停止をスケジュール実行して節約する

はじまり#

リサちゃん avatar
リサちゃん
1日で250円・・・?!
135ml avatar
135ml
1年で91250円・・・!?

なんだこの金額はァァ#

以前にCloud SQLにDBインスタンスを作成して数日ぐらい泳がせておきました。

そして、月初に先月の請求額が届いたので、確認すると・・・。

えっっっ。高すぎない

Cloud SQLの金額が嵩んだ原因#

この記事を参考に、Cloud SQLの費用を抑える手段を3つ挙げられました。

  1. インスタンスのリージョンを安いところにする
  2. マシンを一番安いものにする
  3. 使わないときはインスタンスを止める

DBのインスタンスは基本的に「常にオン」にするか、オフにしたい時に「オフ」にするかしかないようです。まあ実際にDBインスタンスはずっと稼働している状態なんですよね。

そして、Cloud SQLを使うためにGoogle Compute EngineのAPIを有効化したので、確かに先程のような膨大な金額になることは想像が付くことでしたか・・・。

これは間違いなくテコ入れしなければ。

Cloud SQLインスタンスを開始および停止させる#

そこで今回は、Cloud SQLのDBインスタンスを開始および停止させるCloud Run Functionsを作成して、その関数をPub/SubおよびCloud Schedularでスケジューリング実行させていこうと思います。

このGoogle Cloud公式のガイドを参考にしています。

DBのインスタンスを作成する。#

これは以前の記事で行ったのでそちらを参照。

先程見せたようなこんな感じのDBインスタンスが作成されていればOKです。今回はDBMエンジンはPostgreSQLです。

Cloud SQLインスタンスを開始停止できる権限を設定する。#

次に、IAM上でCloud SQLインスタンスを開始停止できる権限を設定します。

Google Cloud コンソールのIAM セクションに移動し、Cloud Functions で使用される「App Engine デフォルト サービスアカウント」を見つけます。このサービスアカウントのサフィックスは@appspot.gserviceaccount.comです。鉛筆アイコンをクリックして編集します。

権限の編集ダイアログ ウィンドウで 別のロールを追加ボタンをクリックします。追加するCloud SQL 管理者ロールを選択し、保存ボタンをクリックします。

IAMを設定したら、サービスアカウントのアドレス(プリンシパル)をメモっておきます。

これから作る関数をデプロイする際に使います。

Pub/Subのトピックを作成する。#

次に、Cloud Pub/Subのトピックを作成します。トピックを作成をクリックします。

今回のトピックIDはDbInstanceMgmtとしておきます。トピックを作成したら、トピックIDをメモっておきます。

これから作る関数をデプロイする際に使います。

関数を作成する。#

それでは早速、関数を作成していきます。言語はGoです。

// Package p contains a Pub/Sub Cloud Function.
package p
import (
"context"
"encoding/json"
"log"
"golang.org/x/oauth2/google"
"google.golang.org/api/option"
sqladmin "google.golang.org/api/sqladmin/v1beta4"
)
// 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 MessagePayload struct {
Instance string
Project string
Action string
}
// ProcessPubSub consumes and processes a Pub/Sub message.
func ProcessPubSub(ctx context.Context, m PubSubMessage) error {
var psData MessagePayload
err := json.Unmarshal(m.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 an http.Client that uses Application Default Credentials.
hc, err := google.DefaultClient(ctx, sqladmin.CloudPlatformScope)
if err != nil {
return err
}
// Create the Google Cloud SQL service.
service, err := sqladmin.NewService(ctx, option.WithHTTPClient(hc))
if err != nil {
return err
}
// Get the requested start or stop Action.
action := "UNDEFINED"
switch psData.Action {
case "start":
action = "ALWAYS"
case "stop":
action = "NEVER"
default:
log.Fatal("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 := service.Instances.Patch(psData.Project, psData.Instance, rb).Context(ctx).Do()
if err != nil {
log.Fatal(err)
}
log.Printf("%#v\n", resp)
return nil
}

ちなみに、上記のコードは参照した公式のガイドと1行だけ違います。以下の行です。sqladmin.Newはdeprecatedでした。

// Before
service, err := sqladmin.New(hc)
// After
service, err := sqladmin.NewService(ctx, option.WithHTTPClient(hc))

gcloudでデプロイする場合はこう書きます。(GUIでやる場合は公式のガイドを参照。)

Terminal window
gcloud functions deploy cloudsql-launcher \
--gen2 \
--runtime=go123 \
--region={MY_REGION} \
--source=. \
--entry-point=ProcessPubSub \
--trigger-topic=DbInstanceMgmt \
--allow-unauthenticated \
--timeout=180s \

初めて--trigger-topicの引数を指定してデプロイするとこのようなメッセージが表示されるかもしれません。その時は「y」を入力しておきます。

API [eventarc.googleapis.com] not enabled on project []. Would you like to enable and retry (this will take a few minutes)? (y/N)?

このデプロイするときのサービスアカウントが悩みどころです。なぜなら、サービスアカウントを紐づけられそうなフィールドというか引数が何種類もあるからです。

  • -service-account: 実行時に関数に関連付けられている IAM サービス アカウントの電子メール アドレス。サービス アカウントは、実行中の関数の ID を表し、関数が持つ権限を決定します。 指定しない場合は、関数はプロジェクトのデフォルトのサービス アカウントを使用します。
  • -run-service-account: 関数の Cloud Run サービスに関連付けられている IAM サービス アカウントの電子メール アドレス。サービス アカウントは、実行中の関数の ID を表し、関数が持つ権限を決定します。 指定しない場合は、関数は Compute Engine のプロジェクトのデフォルトのサービス アカウントを使用します。
  • -trigger-service-account: 関数の Eventarc トリガーに関連付けられている IAM サービス アカウントの電子メール アドレス。これは、 認証された呼び出し に使用されます。 指定しない場合は、関数は Compute Engine のプロジェクトのデフォルトのサービス アカウントを使用します。
  • -build-service-account: ビルドステップ で使用される資格情報となる、IAM サービス アカウント。以下の形式で指定する必要があります。projects/${PROJECT_ID}/serviceAccounts/${ACCOUNT_EMAIL_ADDRESS}指定しない場合は、関数は Cloud Build のプロジェクトのデフォルトのサービス アカウントを使用します。

今回は、--trigger-service-accountを指定することで後続の作業を進めることが出来ました。デプロイした関数が、先程設定したトピックIDとサービスアカウントに紐付いている状態で反映されていることが確認できます。

ちなみにgcloudでCloud Run Functionsをデプロイする際の引数の一覧は、この公式リファレンスで確認することが出来ます。(Cloud Run Functionsは、「今後数か月で、Cloud Functions を Cloud Run UI に統合する予定です。」らしいので、もしかしたらこのサービスアカウントを記入する引数が変わるかもしれません。)

Cloud Functionsの関数にPub/Subの権限を追加する#

関数を作成してコンソール上で確認すると、Eventarcトリガーのところにこのような注意書きがされていました。

「このプロジェクトのサービスアカウントにロールroles/iam.serviceAccountTokenCreatorが付与されている必要があります。これは後で変更できます。」と記載されていますので、付与しておきます。

Cloud Functions の関数が想定どおりに動作することを確認する#

それでは関数の挙動を確認します。 Google CloudのコンソールのPub/Sub セクションに移動し、DbInstanceMgmtトピックを選択します。メッセージをパブリッシュボタンをクリックします。

そしたら、次の JSON メッセージを貼り付けて、メッセージを公開します。(<your-db-instance-id>は実際のDBインスタンス名、<your-project-id>は実際のプロジェクト ID に置き換えます)

{
"Instance": "<your-db-instance-id>",
"Project": "<your-project-id>",
"Action": "stop"
}

関数の実行をしばらく待ってからDBインスタンスを見ると、停止していることが確認できました。

次に、別の Pub/Sub メッセージをパブリッシュしてインスタンスを起動します。 Cloud Console のPub/Sub セクションに移動し、InstanceMgmtトピックを選択します。[メッセージのパブリッシュ] ボタンをクリックし、次の JSON メッセージを貼り付けます。今回はアクションをstartにします。(<your-db-instance-id>は実際のDBインスタンス名、<your-project-id>は実際のプロジェクト ID に置き換えます)。

{
"Instance": "<your-db-instance-id>",
"Project": "<your-project-id>",
"Action": "start"
}

関数の実行をしばらく待ってからDBインスタンスを見ると、再び開始していることが確認できました。

Cloud Scheduler ジョブを作成する#

Cloud Functions の関数が想定通りに動作することが確認できたので、最後のステップとして、インスタンスを自動的に起動して停止する 2 つの Cloud Scheduler ジョブを作成します。

Cloud Console のCloud Schedulerセクションに移動し、[ジョブのスケジュール設定] ボタンをクリックします。Cloud Schedular APIが有効になっている必要があります。

start-db-instanceという指定したCloud SQLインスタンスを開始するためのジョブを、スケジュール実行できるように作成します。 ジョブを実行するタイミングを指定する頻度には0 4 * * 0-6と入力します。これで、ジョブが日曜日から土曜日の毎日午前4時に実行されるようになります。

ターゲットタイプにPub/Sub、そして先程設定したトピックIDを設定して、先程試したDBインスタンス開始用のメッセージと同じものをメッセージ本文に設定します。

そしたら次に、stop-db-instanceという指定したCloud SQLインスタンスを停止するためのジョブを、スケジュール実行できるように作成します。

ジョブを実行するタイミングを指定する頻度には0 7 * * 0-6と入力します。これで、ジョブが日曜日から土曜日の毎日午前7時に実行されるようになります。

ターゲットタイプに「Pub/Sub」、そして先程設定したトピックIDを設定して、先程試したDBインスタンス停止用のメッセージと同じものをメッセージ本文に設定します。

以上の設定が反映されると、2つのジョブスケジュールが作成されました。

ちなみに、上記の設定で作成したジョブスケジュールと同じものを、gcloudからでも作成することが出来ます。

こちらはDBインスタンス開始用のジョブスケジュール。

Terminal window
gcloud scheduler jobs create pubsub start-{MY_DB_INSTANCE}-instance \
--schedule="0 4 * * 0-6" \
--description="Trigger Cloud Functions to start Cloud SQL instance." \
--location="{MY_LOCATION}" \
--time-zone="Asia/Tokyo" \
--topic=DbInstanceMgmt \
--message-body="{ \
"Instance": "{MY_DB_INSTANCE_ID}", \
"Project": "{MY_PROJECT_ID}", \
"Action": "start" \
}" \

こちらはDBインスタンス停止用のジョブスケジュール。

Terminal window
gcloud scheduler jobs create pubsub stop-{MY_DB_INSTANCE}-instance-02 \
--schedule="0 7 * * 0-6" \
--description="Trigger Cloud Functions to stop Cloud SQL instance." \
--location="{MY_LOCATION}" \
--time-zone="Asia/Tokyo" \
--topic=DbInstanceMgmt \
--message-body="{ \
"Instance": "{MY_DB_INSTANCE_ID}", \
"Project": "{MY_PROJECT_ID}", \
"Action": "stop" \
}" \

同様に作成できました。

これでCloud SQLを節約できるはず・・・。#

後日、Cloud SQLを節約できているかどうかを確認しました。

おおっ、使用料が減っています! 1日あたり50円ぐらいに収まりました。(しかし、1日3時間だけの起動でも18,250円か・・・。けっこう掛かるもんなんだな・・・。)

まとめ#

今回は、Go言語でCloud SQLに作成したDBインスタンスを自動で開始および停止するCloud Run Functionsを実装する手順を紹介しました。

以下、まとめです。

  • Cloud SQLのDBインスタンスは、基本的にはデフォルトでずーっと稼働している状態になってしまう。なので使用料金が嵩む。
  • Cloud Run FunctionsでCloud SQLの稼働の開始および停止を制御できる。
  • さらに、Cloud SchedularおよびCloud Pub/Subで、Cloud Run Functionsを自動実行できる。
  • Cloud Run Functionsの制御には、「App Engine default service account」に、「Cloud SQL 管理者」ロールを付与する必要がある。
  • Cloud FunctionsがCloud Run UI に統合されたら、また何かしらの別の設定が必要になるかもしれない。(2024-12-02時点では不明。)

これで年に91,250円も払う必要が無くなると思います。早めに気付いて良かったぁぁ。

おしまい#

リサちゃん avatar
リサちゃん
怖かったぁぁ
135ml avatar
135ml
ちょっと使うからクラウドは安くなるんだよな。

以上になります!

記事を共有

この記事が役に立ったなら、ぜひ他の人と共有してください!

【Cloud SQL】GolangでDBインスタンスの開始停止をスケジュール実行して節約する
https://endorphinbath.com/posts/golang-cloud-sql-saving/
著者
kinkinbeer135ml
公開日
2024-12-04
ライセンス
CC BY-NC-SA 4.0
関連記事 スマート
1
【Cloud SQL】GolangでDBインスタンスへのオペレーション完了まで待つ
Code Go言語でCloud SQLに作成したDBインスタンスに対する処理が完了するまで待つようにします。「Error 409: Operation failed because another operation was already in progress. Try your request after the current operation is complete., operationInProgress」というエラーを回避します。
2
【Cloud SQL】GolangでDBインスタンスの開始停止をDiscordで通知する
Code Go言語でCloud SQLに作成したDBインスタンスを自動で開始および停止するCloud Run Functionsが実行された時に、Discord上で通知を飛ばす機能を実装する手順を紹介します。
3
【Cloud Functions】デプロイ直前にランタイム環境変数を利用した関数のテストは出来ない、のかもしれない
Code Google Cloud Functionsを使ってデプロイ直前にランタイム環境変数を利用した関数のテストができない事象に関する記事です。PythonとGoでテストしましたが一部のキーの環境変数以外は取得できませんでした。
4
【Go、Docker】「api」という名前のパッケージを作るとビルド出来なくなる
Code Goでアプリケーションを開発する際、「api」というパッケージ名を含んだモジュールをDockerコンテナ内でビルドしようとするとバグります。そんな奇妙な事象に遭遇して沼りました。
5
【GitHub】Goでリポジトリの情報を取得するCloud Functionsを作って、Pythonと比較する
Code Go言語でGitHubのリポジトリ情報を取得するCloud Functionsを開発し、Pythonで作成した同様の機能と比較した記事です。使ったツールや、並行処理の比較も行っています。
ランダム記事 ランダム
Profile Image of the Author
kinkinbeer135ml
SIerをやめて、プログラミングを勉強しています。※Amazonアソシエイトに参加しています。
お知らせ
私のブログへようこそ!これはサンプルのお知らせです。
音楽
カバー

音楽

再生中なし

0:00 0:00
歌詞なし
カテゴリ
タグ
サイト統計
記事
287
カテゴリー
8
タグ
93
総文字数
486,174
運用日数
0
最終活動
0 日前

目次