stylesheet

2021-01-22

AppEngineで課金設定をするにあたってすべきこと

Google AppEngineさん、今後は課金設定しないと使わせてくれないようで。

App Engine スタンダード アプリケーションを引き続きご利用いただくために、2021 年 1 月 31 日までにお支払い情報の追加をお願いいたします。
  
  2019 年 11 月 30 日以降、App Engine スタンダード環境ではデプロイ時に Cloud Build を使用しております。Cloud Build には、他のすべての Google Cloud 同様に、有効なお支払い方法が必要です。当時、今後もお客様にプロジェクトのデプロイを継続していただくために、App Engine プロジェクトを有効なお支払い方法を設定した請求先アカウントにリンクしていただくようお願いいたしました。

2021 年 1 月 31 日以降、すべての App Engine プロジェクトの継続には、有効なお支払い方法を設定した請求先アカウントとのリンクが必要です。

2021 年 1 月 31 日までに、有効なお支払い方法が設定された請求先アカウントとリンクされなかった App Engine アプリは、シャットダウンされます。運用中のインスタンスは終了し、プロジェクトにお支払い方法が追加されるまで新しいインスタンスを開始することはできません。

この変更による既存の App Engine の無料枠への影響はございません。また、使用量が無料枠の割り当ての範囲に収まっている場合は、App Engine の料金が発生することはございません。ただし、使用量が無料枠の使用限度を超えた場合は、料金が発生いたします。このたびの変更前は、無料枠を超えて使用した場合、1 日の割り当てがリセットされるまでアプリの稼働が止まりました。

ありがとうございます。
今後は破産のリスクに晒されることになりました。

無料枠を費用管理方法としてご利用の場合は、以下の方法のうちいずれかの方法でApp Engine の費用を管理されることをおすすめします。

- app.yamlで max_instances 設定に 1 を指定して、無料枠の超過リスクを減らします。この設定により、アプリのスケーリング能力が制限されますが、ハードリミットではないため、使用量と請求額が増える可能性があります。
- Cloud の予算アラートを設定して、予算上限に近づいたときに通知を受け取ります。
- アプリを手動で無効にします。ただし、この場合、無効にできるのはインスタンス時間やネットワーキングなどアプリの実行に関連した料金のみです。アプリを無効にした間も、Cloud Storage の容量に対する料金などの固定費は引き続き発生します。
- アプリをプログラムで無効にすると、指定したしきい値以上の費用が発生しないよう上限を設定できます。

親切にありがとうございます。ところで、一日単位で予算制限はできないのしょうか?G〇〇gle BotとかのBotが大挙して押し寄せるとコストが嵩むので。
ない?
なるほど、予算制限機能は先立って廃止されたのですね。

それでは、おすすめに従って、max_instanceの制限プログラムで無効にする対策を行うように致します。

max_instanceの制限

稼働インスタンスを最小限にして課金を減らす。

F1インスタンスの場合、毎日28時間分の無料枠があるので計算上は無課金運用できるはず。ただ、1インスタンスに制限しても、状況によってはもう一つ起動していることがあるので油断できない。

設定はAppEngine Pythonの場合、app.yamlに記載することで行う。

# app.yaml

env: standard
instance_class: F1
automatic_scaling:
  max_instances: 1
  min_instances: 0
  max_idle_instances: 1
  min_idle_instances: 0
  target_cpu_utilization: 0.95
  target_throughput_utilization: 0.95
  max_concurrent_requests: 80
  min_pending_latency: 10s
  max_pending_latency: automatic

関連しそうな部分も明記しておいた。

ちなみに基本スケーリングや手動スケーリングのほうが良さそうだと欲を出してはいけない。それらはBタイプのインスタンスを要求する。無料枠はわずか9時間だ。

アプリをプログラムで無効にする

こちらの方法、流れとしてはこうだ。

  1. 予算通知がPub/Subトピックへパブリッシュ。
  2. それを監視するCloud Functionが起動。
  3. スクリプトでCloud Build APIをコール。
  4. Cloud Buildがアプリを無効にする。

これらを自分で設定していかなければいけない。PaaSとは何だったのかを見つめ直すいいきっかけとなるかもしれない。

予算アラートを作る

お支払い > 予算とアラート > 予算を作成 から新しく予算アラートを作る。

予算タイプは指定額で、目標金額は任意の金額で。
無料枠は毎日リセットされるのに、なぜか予算は月単位でしか設定できない不思議な仕様。
通知の管理から「Pub/Subトピックをこの予算に接続する」をチェックして、通知先のトピックを選ぶ。その場で新しくトピックを作成できるので、作ってしまうと良い。
Pub/Subトピックには、トリガー条件に関係なく一定間隔で通知が来るので、アクション項目は特になくても動作するようだ。ただ、通知間隔は実測で25分毎だったので注視したいしきい値はトリガーを作っておくほうが安心できる。

Cloud Functions で停止スクリプトを作成

Cloud Functions > 関数の作成 から新しい関数を作る。

トリガーのタイプは「Cloud Pub/Sub」を選び、予算アラートを設定したPub/Subトピックを選ぶ。
また、詳細でメモリとインスタンスは最小構成にしておいた。工数を掛けてまでコスト対策をしているのに逆にコスト源となったら残念すぎる。

ランタイムはPython 3.8を選んでエントリポイントは「stop_appengine」を入力。実際のコードは以下の通り。

# main.py

import base64
import json
import os
import logging
from googleapiclient import discovery

# 操作対象アプリのリスト
APP_NAMES = [os.getenv('GCP_PROJECT')]

def stop_appengine(data, context):
  pubsub_data = base64.b64decode(data['data']).decode('utf-8')
  pubsub_json = json.loads(pubsub_data)

  # 予算以上で停止、予算未満で開始
  cost = pubsub_json['costAmount']
  budget = pubsub_json['budgetAmount']
  f = _stop_app if cost >= budget else _start_app
  list(map(f, APP_NAMES))

def _stop_app(app_id):
  _patch_app(app_id, 'SERVING', 'USER_DISABLED')

def _start_app(app_id):
  _patch_app(app_id, 'USER_DISABLED', 'SERVING')

def _patch_app(app_id, current, serving):
  apps = discovery.build('appengine', 'v1', cache_discovery=False).apps()
  current_status = apps.get(appsId=app_id).execute().get('servingStatus')
  if (current_status == current):
    apps.patch(appsId=app_id, updateMask='serving_status', body={'servingStatus': serving})
    logging.info(f'App patching. app_id={app_id}, current={current_status}, serving={serving}')
  else:
    logging.info(f'App does not need patch. app_id={app_id}, current={current_status}, serving={serving}')

予算超過でアプリの停止、予算未満でアプリの起動を行うスクリプトになる。

# requirements.txt

google-api-python-client==1.12

google-api-python-clientパッケージの追加が必要。

プロジェクト外のアプリも記入してやれば停止できるが、権限は自動で追加されない。追加したプロジェクトのIAMと管理で、関数を実行するメンバーへ権限をあたえること。

動作確認

Pub/Sub > トピック > メッセージのパブリッシュ からメッセージを配信して動作確認する。

アプリを停止するために予算超過状態のメッセージを送信。うまく止まってくれるとOK。

{
    "budgetDisplayName": "name-of-budget",
    "alertThresholdExceeded": 1.0,
    "costAmount": 123.45,
    "costIntervalStart": "2019-01-01T00:00:00Z",
    "budgetAmount": 100.00,
    "budgetAmountType": "SPECIFIED_AMOUNT",
    "currencyCode": "USD"
}

アプリを開始するために予算未満状態のメッセージを送信。有効になるはず。

{
    "budgetDisplayName": "name-of-budget",
    "alertThresholdExceeded": 1.0,
    "costAmount": 0.01,
    "costIntervalStart": "2019-01-01T00:00:00Z",
    "budgetAmount": 100.00,
    "budgetAmountType": "SPECIFIED_AMOUNT",
    "currencyCode": "USD"
}

App Engine > 設定 ページでそれぞれ有効か無効になっているか確認する。

以上。
なかなか一筋縄では安心・安全とはいかないようで。