• トップ
  • ブログ一覧
  • Artifact Registry・Cloud SQL・Cloud Runの費用削減術
  • Artifact Registry・Cloud SQL・Cloud Runの費用削減術

    はじめに

    こやまん

    こんにちは、最近プライベートのアプリケーション開発がマイブームの "こやまん" です。
    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_INSTANCES6# 設定された複数インスタンスを順に処理する。
    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                      # 失敗時のみエラーメッセージが入る(成功時は null90                      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/NEVER102          target_state: ${target_state}
    103
    104          # 全インスタンス分の成功/失敗結果を返却
    105          # Cloud WorkflowsUI で実行結果をひと目で確認できる
    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.minInstanceCount4# 任意の値(例: 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関連記事はこちら

    ライトコードでは、エンジニアを積極採用中!

    ライトコードでは、エンジニアを積極採用しています!カジュアル面談等もございますので、くわしくは採用情報をご確認ください。

    採用情報へ

    おすすめ記事

    エンジニア大募集中!

    ライトコードでは、エンジニアを積極採用中です。

    特に、WEBエンジニアとモバイルエンジニアは是非ご応募お待ちしております!

    また、フリーランスエンジニア様も大募集中です。

    background