Artifact Registry・Cloud SQL・Cloud Runの費用削減術
IT技術
はじめに
こやまんこんにちは、最近プライベートのアプリケーション開発がマイブームの "こやまん" です。
Google Cloudを利用していると、時間の経過と共にリソースの費用が上がっていく問題に直面することかと思います。
今回は、Google Cloudの検証環境の費用削減について執筆していこうと思います。
Artifact Registryの費用削減
Artifact Registryでは、アーティファクト1GBあたり、0.1$ほどの料金が発生します。
検証環境では、頻繁にアーティファクトがpushされるため、気が付いたら容量がそこそこ大きなものになっていたりということが多々あります。
すごく高い料金という訳ではありませんが、ちりつもで不要なリソースは削除していくのが望ましいでしょう。
Google Cloud の Artifact Registryの管理画面からリポジトリごとにクリーンアップポリシーというものが設定できます。
以下は、私がよく設定するクリーンアップポリシー例です。
最新の5件のアーティファクトを保持するポリシー
| 名前 | keep-top-5-versions |
| ポリシータイプ | 最新バージョンを保持 |
| 保持数 | 5 |
アップロードして3日を経過したアーティファクトを削除するポリシー
| 名前 | cleanup-3days |
| ポリシータイプ | 条件付き削除 |
| アップロード後の期間(最小) | 3d |
上記のクリーンアップポリシーを設定することで、検証環境に不必要なアーティファクトが残り続け、無駄な費用の発生を防止できます。
Cloud SQLの費用削減
Cloud SQL は、Google Cloudのサービスの中で割と高額になりがちのサービスの一つです。
夜間は利用しない、などの状況であれば、インスタンスを停止することで大幅に費用を削減できます。
Cloud SQLを毎回手動で停止する...といった人海戦術に頼るプロジェクトは令和のこの時代に流石にないとは思います。
自動で停止・起動させる方法は様々ですが、今回は「Workflows」と「Cloud Scheduler」を利用した方法で実現していきます。
Workflowsは、Cloud Schedulerと簡単に連携して、低コストで様々な処理を自動化できるサービスです。
CloudSQLを夜に停止、朝に起動...ということで「cloudsql-night-stop-morning-start」という名前でワークフローを作成します。
1# Cloud SQL インスタンス群の起動・停止を一括制御するワークフロー
2# --------------------------------------------------------------------
3# この Workflow は、複数 Cloud SQL インスタンスの activationPolicy を
4# "ALWAYS"(起動)または "NEVER"(停止)へまとめて変更するために使用する。
5# Cloud Scheduler から引数 state を受け取り、環境変数 TARGET_INSTANCES に
6# 設定された複数インスタンスを順に処理する。
7# --------------------------------------------------------------------
8
9main:
10 params: [args]
11
12 steps:
13 # -----------------------------------------------------
14 # ステップ1: 初期化および対象インスタンスの抽出
15 # -----------------------------------------------------
16 - init:
17 assign:
18 # 現在実行中のプロジェクト ID を環境変数から取得
19 - project_id: ${sys.get_env("GOOGLE_CLOUD_PROJECT_ID")}
20
21 # 環境変数 TARGET_INSTANCES をカンマで分割して配列化
22 # 例: "db1,db2" → ["db1", "db2"]
23 - instance_list: ${text.split(sys.get_env("TARGET_INSTANCES"), ",")}
24
25 # Cloud Scheduler から渡される "ALWAYS" / "NEVER"
26 - target_state: ${args.state}
27
28 # 処理結果を格納する配列を初期化
29 - execution_results: []
30
31
32 # -----------------------------------------------------
33 # ステップ2: インスタンスごとの処理
34 # -----------------------------------------------------
35 - process_instances:
36 for:
37 value: instance_name
38 in: ${instance_list}
39
40 steps:
41 # -------------------------------------------------
42 # Cloud SQL Admin API を使って activationPolicy を変更する
43 # ・成功 → operation_result に API レスポンスを格納
44 # ・失敗 → operation_result に error 情報を格納
45 # -------------------------------------------------
46 - update_instance_state:
47 try:
48 call: googleapis.sqladmin.v1.instances.patch
49 args:
50 project: ${project_id}
51
52 # 環境変数から取得したインスタンス名をそのまま利用
53 instance: ${instance_name}
54
55 body:
56 settings:
57 activationPolicy: ${target_state}
58
59 result: operation_result # ※成功時
60
61 except:
62 as: e
63 assign:
64 # 例外内容をオブジェクト化して後続の集計処理に渡す
65 - operation_result:
66 error: ${e.message}
67 status: "Failed"
68
69
70 # -------------------------------------------------
71 # API の実行結果(成功/失敗)を集計用配列に追記する
72 #
73 # status, operation_name, error_message の 3 点を統一形式にまとめて返却することで、
74 # ・後続の監視/通知処理に流用しやすい
75 # ・実行ログとして確認しやすい
76 # ・管理画面から json を見れば全体の成功/失敗が一目でわかる
77 # -------------------------------------------------
78 - collect_result:
79 assign:
80 - result_summary:
81 instance: ${instance_name}
82
83 # 成功時は API のレスポンスに status が存在しないため Success をデフォルトに設定
84 status: ${default(map.get(operation_result, "status"), "Success")}
85
86 # 成功時は operation の名前を、失敗時は "N/A"
87 operation_name: ${default(map.get(operation_result, "name"), "N/A")}
88
89 # 失敗時のみエラーメッセージが入る(成功時は null)
90 error_message: ${default(map.get(operation_result, "error"), null)}
91
92 # execution_results へ結果を append
93 - execution_results: ${list.concat(execution_results, [result_summary])}
94
95
96 # -----------------------------------------------------
97 # ステップ3: 全体結果の返却
98 # -----------------------------------------------------
99 - return_result:
100 return:
101 # 実行指示された状態(ALWAYS/NEVER)
102 target_state: ${target_state}
103
104 # 全インスタンス分の成功/失敗結果を返却
105 # Cloud Workflows の UI で実行結果をひと目で確認できる
106 details: ${execution_results}環境変数には、停止・起動を行いたいCloud SQLのインスタンス名を複数(一つでも可)設定します。
| TARGET_INSTANCES | インスタンス名1,インスタンス名2 |
まずは、手動で実行してみましょう。
- 起動させたい場合
{"state": "ALWAYS"}
- 停止させたい場合
{"state": "NEVER"}
処理が成功した際の出力は下記のような形となります。
1{
2 "details": [
3 [
4 {
5 "error_message": null,
6 "instance": "インスタンス名1",
7 "operation_name": "xxxe3503-ae6e-43d0-8cb2-xxx",
8 "status": "DONE"
9 }
10 ],
11 [
12 {
13 "error_message": null,
14 "instance": "インスタンス名2",
15 "operation_name": "xxx85086-5c95-4948-98de-xxx",
16 "status": "DONE"
17 }
18 ]
19 ],
20 "target_state": "NEVER"
21}手動実行で動作することは確認できたので、Workflowsの編集画面からトリガーを追加します。
Cloud Scheduler と Eventarc を選択できますが、今回は、Cloud Schedulerを選択します。
月曜日から金曜日の午前7時にCloud SQL を起動するスケジューラー
| トリガーの名前 | cloudsql-night-stop-morning-start-scheduler-start |
| リージョン | asia-northeast1 |
| 頻度 | 0 7 * * 1-5 |
| タイムゾーン | 日本標準時(JST) |
| ワークフロー引数 | {"state": "ALWAYS"} |
月曜日から金曜日の午後10時にCloud SQL を停止するスケジューラー
| トリガーの名前 | cloudsql-night-stop-morning-start-scheduler-stop |
| リージョン | asia-northeast1 |
| 頻度 | 0 22 * * 1-5 |
| タイムゾーン | 日本標準時(JST) |
| ワークフロー引数 | {"state": "NEVER"} |
Cloud Runの費用削減
Cloud SQLと同様に、Cloud Run も最小インスタンスを0にすることで、利用しない時間帯の費用削減を行うことができます。
「cloudrun-night-down-morning-up」という名前でワークフローを作成します。
1# Cloud Run サービス群の minInstanceCount を一括制御するワークフロー
2# --------------------------------------------------------------------
3# この Workflow は、複数 Cloud Run サービスの template.scaling.minInstanceCount を
4# 任意の値(例: 0 でスケールダウン、1 以上で常駐インスタンス確保)に変更する。
5# Cloud Scheduler から引数 minInstanceCount を受け取り
6# 環境変数 TARGET_SERVICES に設定された複数サービスを順に処理する
7# --------------------------------------------------------------------
8
9main:
10 params: [args]
11
12 steps:
13 # -----------------------------------------------------
14 # ステップ1: 初期化および対象サービスの抽出
15 # -----------------------------------------------------
16 - init:
17 assign:
18 # 現在実行中のプロジェクト ID を環境変数から取得
19 - project_id: ${sys.get_env("GOOGLE_CLOUD_PROJECT_ID")}
20
21 # Cloud Run のリージョン
22 - location: "asia-northeast1"
23
24 # 対象サービス名リスト(カンマ区切り → 配列)
25 # 例: "hello,backend,api" → ["hello", "backend", "api"]
26 - service_list: ${text.split(sys.get_env("TARGET_SERVICES"), ",")}
27
28 # Cloud Scheduler 等から渡される minInstanceCount(数値)
29 # 例: 夜間は 0、日中は 1 など
30 - min_instance_count: ${args.minInstanceCount}
31
32 # 処理結果を格納する配列を初期化
33 - execution_results: []
34
35
36 # -----------------------------------------------------
37 # ステップ2: サービスごとの処理
38 # -----------------------------------------------------
39 - process_services:
40 for:
41 value: service_name
42 in: ${service_list}
43
44 steps:
45 # -------------------------------------------------
46 # Cloud Run Admin API(v2)を使って minInstanceCount を変更
47 # ・成功 → api_result に API レスポンスを格納
48 # ・失敗 → api_result に error 情報を格納
49 # -------------------------------------------------
50 - update_min_instances:
51 try:
52 steps:
53 - call_api:
54 call: googleapis.run.v2.projects.locations.services.patch
55 args:
56 # 更新対象の完全なリソース名
57 # projects/{project}/locations/{location}/services/{service}
58 name: ${"projects/" + project_id + "/locations/" + location + "/services/" + service_name}
59
60 # 更新するフィールドを指定
61 updateMask: "template.scaling.minInstanceCount"
62
63 # 変更後の minInstanceCount を指定
64 body:
65 template:
66 scaling:
67 minInstanceCount: ${min_instance_count}
68
69 result: api_result
70
71 # 成功時の結果サマリを作成
72 - set_success_summary:
73 assign:
74 - result_summary:
75 service: ${service_name}
76 status: "Success"
77 error_message: null
78
79 except:
80 as: e
81 steps:
82 # 失敗時の結果サマリを作成
83 - set_failure_summary:
84 assign:
85 - result_summary:
86 service: ${service_name}
87 status: "Failed"
88 error_message: ${e.message}
89
90 # -------------------------------------------------
91 # サマリ結果を全体の配列に追加
92 # -------------------------------------------------
93 - collect_result:
94 assign:
95 - execution_results: ${list.concat(execution_results, [result_summary])}
96
97
98 # -----------------------------------------------------
99 # ステップ3: 全体結果の返却
100 # -----------------------------------------------------
101 - return_result:
102 return:
103 # 指定された minInstanceCount
104 minInstanceCount: ${min_instance_count}
105
106 # 各サービスごとの成功/失敗結果を返却
107 details: ${execution_results}環境変数には、最小インスタンスの変更を行いたいCloud Runのサービス名を複数(一つでも可)設定します。
| TARGET_SERVICES | サービス名1,サービス名2 |
こちらも手動で実行してみましょう。
- 最小インスタンスを1にしたい場合
{"minInstanceCount": 1}
- 最小インスタンスを0にしたい場合
{"minInstanceCount": 0}
Cloud SQL と同様にスケジューラーを設定します。
月曜日から金曜日の午前7時に最小インスタンスを1にするスケジューラー
| トリガーの名前 | cloudrun-night-down-morning-up-scheduler-up |
| リージョン | asia-northeast1 |
| 頻度 | 0 7 * * 1-5 |
| タイムゾーン | 日本標準時(JST) |
| ワークフロー引数 | {"minInstanceCount": 1} |
月曜日から金曜日の午後10時に最小インスタンスを0にするスケジューラー
| トリガーの名前 | cloudrun-night-down-morning-up-scheduler-down |
| リージョン | asia-northeast1 |
| 頻度 | 0 22 * * 1-5 |
| タイムゾーン | 日本標準時(JST) |
| ワークフロー引数 | {"minInstanceCount": 0} |
まとめ
今回紹介した内容をざっくり振り返ると、次の3つのポイントに集約できます。
- Artifact Registry では、クリーンアップポリシーを設定して不要なアーティファクトを自動削除することで、ストレージ費用の無駄な増加を防ぐ。
- Cloud SQL では、Workflows + Cloud Scheduler を組み合わせて、夜間にインスタンスを停止・朝に起動する仕組みを用意し、常時起動を避けることでコストを大きく削減する。
- Cloud Run では、同様に Workflows + Cloud Scheduler を使って minInstanceCount を時間帯に応じて増減させ、検証環境などでの常駐インスタンス数を最適化する。
どれも「ちょっとした自動化」を加えるだけで、日々じわじわ効いてくるランニングコストを抑えられる工夫です。
この記事が、みなさんの Google Cloud 利用コストの見直しや、インフラ運用の自動化を考えるきっかけになれば幸いです。
GoogleCloud関連記事はこちら
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!カジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ





