🌊

Workflows と Pub/Sub で少し楽してメンテナンスモードを実装する

2023/08/22に公開

こんにちは、クラウドエース SRE ディビジョンの松島です。

早速ですが、多くのシステムでは夜間メンテナンス中など、メンテナンスモードに切り替えたいことがあるかと思います。
が、現時点では Google Cloud のロードバランサや CDN にはメンテナンスモード機能は存在しません。

私の経験したパターンですと、実装したければ LB のトラフィックを全てメンテナンスページに向けて終わったらルーティングをもとに戻す作業を実施する、アプリ側でメンテナンスモードを実装する、といった対応がありましたが、どちら実装及び運用がやや大変な印象です。

本記事では、Workflows と Pub/Sub を利用して比較的に楽にアプリケーションロードバランサのメンテナンスモード切り替えを実現する手段を紹介します。

Workflows と Pub/Sub を利用した LB メンテナンスモード切り替えの自動化

Google Cloud に対する一連の操作を自動化できる Workflows と、一時的に情報を保持しておける Pub/Sub を組み合わせて利用することで、ボタン一つでロードバランサのメンテナンスモード切り替えを実現する仕組みを比較的簡単に作ることができます。

構成概要

下図のように、LB のルーティングを、通常状態とメンテナンスページのみに向ける状態とを切り替えることでメンテナンスモードを実装します。
switch-maintenance1.png

これを、次のような2つのワークフローにより実現します。

  • メンテナンスモード起動用 Workflow
    • 下図① - ③のように切り替え前に通常状態の設定情報を pull 型の Pub/Sub サブスクリプションに保持した上で LB をメンテナンスモードに切り替える
  • メンテナンスモード終了用 Workflow
    • 下図④ - ⑥のように保持した情報を Pub/Sub から読み取って適用することで LB を元の状態に戻す

switch-maintenance2.png

一見実装が面倒そうに見えますが、Workflows にはコネクタという、Google API を簡易なyamlの記述で実行できる機能が備わっているため、上記のようなワークフローを比較的少ない手数で実装することができます。
Functions を利用する方法に比べ、プログラムの実装が不要でワークフローを定義した yaml ファイルのみで実装可能なところがこの方法の良いところです。

設定手順

上記の構成概要で説明したリソース群を作成していきます

1. メンテナンスページ用 GCE バケット、バックエンドバケットの作成

まず、GCS バケットを作成し、メンテナンス中は常に「maintenance.html」を返すよう設定、GCS バケットを一般に公開します

gcloud storage buckets create gs://<メンテナンスページ用バケット名> \
--location=<バケット配置リージョン> \
--uniform-bucket-level-access

gcloud storage buckets update gs://<メンテナンスページ用バケット名> \
--web-main-page-suffix=maintenance.html \
--web-error-page=maintenance.html 

gcloud storage buckets add-iam-policy-binding gs://<メンテナンスページ用バケット名> \
--member=allUsers --role=roles/storage.objectViewer

作成したら、メンテナンスページ用の静的ファイル「maintenance.html」をアップロードします。

次に、このメンテナンスページを使用するバックエンドサービスを作成します。

gcloud compute backend-buckets create maintenance-page \
--gcs-bucket-name=<メンテナンスページ用バケット名> 

2. Pub/Sub トピック、サブスクリプションの作成

Pub/Sub トピック、pull サブスクリプションを作成します

gcloud pubsub topics create hold-lb-settings
gcloud pubsub subscriptions create hold-lb-settings --topic=hold-lb-settings --expiration-period=never

3. サービスアカウントの作成

Workflows に付与するサービスアカウントを作成の上、権限を付与します。
今回、Workflows からは Pub/Sub への読み書きと LB の設定変更を行うため、作成したサービスアカウントには Compute 管理者、Pub/Sub サブスクライバー、Pub/Sub パブリッシャーをを付与します。

gcloud iam service-accounts create workflow

gcloud projects add-iam-policy-binding <プロジェクトID> --member='serviceAccount:<workflow 用サービスアカウント>' --role='roles/compute.loadBalancerAdmin'

gcloud projects add-iam-policy-binding <プロジェクトID> --member='serviceAccount:<workflow 用サービスアカウント>' --role='roles/pubsub.publisher'

gcloud projects add-iam-policy-binding <プロジェクトID> --member='serviceAccount:<workflow 用サービスアカウント>' --role='roles/pubsub.subscriber'

gcloud projects add-iam-policy-binding <プロジェクトID> --member='serviceAccount:<workflow 用サービスアカウント>' --role='roles/logging.logWriter'

4. workflowsの作成

まず、メンテナンスモードに切り替える下記のワークフローを記載した set-maintenance-mode.yaml ファイルを作成します。

set-maintenance-mode.yaml
main:
    steps:
    - getURLMapSettings: # LB のルーティング設定を取得
        call: googleapis.compute.v1.urlMaps.get
        args:
            project: <プロジェクトID>
            urlMap: <LB(URL map)名>
        result: urlMapSetting
    - output:
        call: sys.log
        args:
            json: ${urlMapSetting}
    - holdLBSettingInPubSub: # 取得した LB のルーティング設定をPub/Subで一時保持
        call: googleapis.pubsub.v1.projects.topics.publish
        args:
            topic: projects/<プロジェクトID>/topics/hold-lb-settings
            body:
                messages:
                    - data: ${base64.encode(text.encode(json.encode_to_string(urlMapSetting)))}
    - setMaintenanceMode: # メンテナンスモードに切り替える
        call: googleapis.compute.v1.urlMaps.patch
        args:
            project: <プロジェクトID>
            urlMap: <LB(URL map)名>
            body:
                defaultService: https://www.googleapis.com/compute/v1/projects/<>/global/backendBuckets/maintenance-page
                hostRules: []
                pathMatchers: []

この yaml ファイルを指定して workflow を作成します。

gcloud workflows deploy set-maintenance-mode \
--source=set-maintenance-mode.yaml \
--call-log-level=log-errors-only \
--location=<リージョン> \
--service-account=<作成したサービスアカウント>

同様の手順で、メンテナンスモードから通常モードに戻すワークフローを記載した end-maintenance-mode.yaml ファイルを作成します

end-maintenance-mode.yaml
main:
    steps:
    - GetLBSetting: # Pub/Subで一時保持中の LB のルーティング設定を取得
        call: googleapis.pubsub.v1.projects.subscriptions.pull
        args:
            subscription: projects/<プロジェクトID>/subscriptions/hold-lb-settings
            body:
                maxMessages: 1
        result: pullResult
    - SetLBSettingsToVar:
        assign: 
        - lb_settings: ${json.decode(base64.decode(pullResult.receivedMessages[0].message.data))}
        - ackID: ${pullResult.receivedMessages[0].ackId}
    - OutPutLBSettings:
        call: sys.log
        args:
            json: ${lb_settings}
    - RestoreLBSetting: # 取得した元の LB のルーティング設定を適用し、通常モードに戻す
        call: googleapis.compute.v1.urlMaps.patch
        args:
            project: <プロジェクトID>
            urlMap: <LB(URL map)名>
            body:
                defaultService: ${lb_settings.defaultService}
                hostRules: ${lb_settings.hostRules}
                pathMatchers: ${lb_settings.pathMatchers}
    - AckKnowledge: # Pub/Sub に Ack を返す
        call: googleapis.pubsub.v1.projects.subscriptions.acknowledge
        args:
            subscription: projects/<プロジェクトID>/subscriptions/hold-lb-settings
            body:
                ackIds: 
                    - ${ackID}
        result: acknowledgeResult
    - AckResult:
        call: sys.log
        args:
            json: ${acknowledgeResult}

この yaml ファイルを指定してメンテナンスモードから通常モードに戻すワークフロー 「end-maintenance-mode」を作成します。

gcloud workflows deploy end-maintenance-mode \
--source=end-maintenance-mode.yaml \
--call-log-level=log-errors-only \
--location=<リージョン> \
--service-account=<作成したサービスアカウント>

LB メンテナンスモード切り替えを動かしてみる

ここまでで作成したメンテナンスモード切り替えの仕組みを動かしてみます

次のようなLBが通常モードで動いています。

noraml-lb-settings.png

workflows のページから作成した「set-maintenance-mode」を実行します。

start-maint.png

実行が完了したのち、LB を確認すると、想定通り全てのトラフィックがメンテナンス用ページを配置した GCS バケットに流れるようになっています。

maintenance-mode-lb.png

次に、通常モードに戻すため、同様の手順で「end-maintenance-mode」を実行すると、次のように LB が元の設定に戻ります。

noraml-lb-settings.png

以上で動作が確認できました。

補足

一律固定ページ返却で機能が不足する場合

メンテナンスモード中の LB の設定を変えると、任意のメンテナンスモードの動作を実現させることが可能になっています。
今回はメンテナンスモード中のルーティング設定を、一律メンテナンスページを返すものとしていますが、これだと不備のある場合は別途用意した Cloud Run への転送や別エンドポイントへのリダイレクトを検討するのも良いと思います。

なぜ Pub/Sub か?

設定情報を一時的に保持したいだけであれば Cloud Storage も候補になりうると思いますが、Workflows 上ではローカルでファイルを作成、編集することができず、設定情報を保持するファイルを作成できないため、GCS は利用できません。
そのため、簡易に情報を保持するための機能として Pub/Subを選択しています。

参考公式ドキュメント

Worklows 概要
コネクタのリファレンス

終わりに

メンテナンスモードを簡易に実装したいケースがあれば、本手法をお試しいただければと思います。
また、Workflows はコネクタが対応している Google API であれば簡易な yaml 設定で実行することが可能なので、日頃の定常作業で置き換えられるものが他にもあるかもしれません。ぜひ検討してみてください。

Discussion