1. はじめに
個人開発でCloud Runを使ったAPIサーバーをテストしていると、処理が始まるのが遅い、という問題に当たりました。
調べてみると、しばらくアクセスがないとCloud Runのインスタンスが自動的に停止し、次のリクエスト時に起動するまで数秒かかる「コールドスタート」という現象が起きていました。
最初は「インスタンス数の最小値を1に設定すれば解決するのでは」と考えましたが、それでは常時起動状態になり、料金が跳ね上がってしまいます。
調べていくうちに、Cloud Schedulerを使って定期的に軽いリクエストを送ることで、コンテナを温かい状態(Keep Warm)に保ちつつ、コストをほぼ無料に抑える方法があることを知りました。
この記事では、Cloud Schedulerを使ったKeep Warm戦略の仕組みと、セキュリティを考慮した実装方法を解説します。
2. 直面した課題
コールドスタートによるレスポンス遅延
Cloud Runは、アクセスがない時間が続くとインスタンス数が0になります。
これはコスト削減には優れていますが、久しぶりにアクセスしたユーザーは、サーバーが起動するまでの数秒間待たされることになります。
私のアプリケーションでは、起動に約3〜5秒かかっていました。
さらにAIコンテナのような重いライブラリをimportして処理を担当するものでは、さらに遅い立ち上がりになります。
ユーザー体験としては、明らかに遅すぎます。
常時起動のコスト問題
最初に考えた解決策は、Cloud Runの「最小インスタンス数」を1に設定することでした。
しかし、これでは常にコンテナが起動している状態になり、「CPUを常に割り当てる」設定にしていなくても、メモリ使用料が発生し続けます。
個人開発のプロジェクトでは、できるだけコストを抑えたいという思いがありました。
3. 解決策:Cloud Schedulerによる定期アクセス
Keep Warmの仕組み
解決策として採用したのが、Cloud Schedulerを使って定期的に軽いリクエストを送る方法です。
Cloud Runは、最後のリクエストから一定時間(通常15分程度、ただし環境や設定により変わり得る)が経過するとインスタンスを停止します。
逆に言えば、15分以内に1回でもアクセスがあれば、インスタンスは起動し続けます。
そこで、10分に1回程度の頻度で自動的にアクセスを送ることで、コンテナを常に温かい状態に保つことができます。
コストの心配は不要
「頻繁にアクセスさせて、料金は高額になりませんか?」という疑問を持つ方もいるかもしれません。
しかし、Cloud Runのデフォルト設定(CPUの割り当て:リクエスト処理中のみ)であれば、ほぼ無料です。
Cloud Runは「実際に処理を行っている時間」にしか課金されません。
待機中のコンテナは、メモリに残っていても0円です。
1回0.1秒程度の軽い処理なら、月間数千回実行しても無料枠(月間18万vCPU秒、2026年2月時点)に余裕で収まります。
また、Cloud Scheduler自体も、月に3つのジョブまでは無料です(2026年2月時点の料金体系)。
4つ目以降も1ジョブあたり月額約15円($0.10)と非常に安価です。
ここがポイントですが、実行回数ではなくジョブ数で課金される仕組みです。
10分に1回実行しても、1ヶ月に1回実行しても、料金は同じです。
4. 実装手順
FastAPI側の準備
ただURLを公開するだけでは、第三者に勝手にアクセスされて負荷をかけられるリスクがあります。
そこで、「合言葉(Secret Key)」を知っているアクセスだけを通すようにします。
import os
from fastapi import FastAPI, Header, HTTPException, status
app = FastAPI()
# 環境変数から合言葉を取得
SECRET_KEY = os.getenv("SCHEDULER_SECRET_KEY")
# include_in_schema=False でSwagger UIから隠す
@app.get("/keep-warm-db-check", include_in_schema=False)
async def keep_warm(x_scheduler_secret: str = Header(None)):
"""
Cloud Scheduler専用の生存確認・DB接続チェック用エンドポイント
"""
# 合言葉のチェック
if x_scheduler_secret != SECRET_KEY:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Invalid Secret Key"
)
# ここに軽いDB確認処理などを記述
# try:
# await db.execute("SELECT 1")
# except Exception as e:
# print(f"Health Check Failed: {e}")
# return {"status": "error", "message": str(e)}
return {"status": "alive", "message": "Keep warm successful"}
このコードは、まず環境変数SCHEDULER_SECRET_KEYから合言葉を取得します。
この環境変数は、Cloud Runの設定画面で設定します。
予測困難な文字列(例:my-secret-key-xyz123)を使用してください。
エンドポイントにはinclude_in_schema=Falseを指定しています。
これにより、Swagger UI(/docs)からこのエンドポイントが隠され、一般ユーザーには見えなくなります。
リクエストヘッダーからX-Scheduler-Secretを取得し、環境変数の値と比較します。
一致しない場合は、403エラーを返して即座に拒否します。
合言葉が正しければ、軽い処理(例:データベースへの接続確認)を行い、成功レスポンスを返します。
この処理は非常に軽量なので、0.1秒以内に完了します。
「認証方式についての補足」
この記事の方法は、Cloud Runが「未認証のリクエストを許可」(Public)に設定されている場合に機能します。
もしCloud Runが「認証が必要」(Private)になっている場合、リクエストはアプリのコードに到達する前にGoogle Front Endで拒否されるため、このヘッダーチェック自体が動作しません。Cloud Schedulerには、OIDCトークンを自動生成してリクエストに付与する機能がネイティブで用意されています。
サービスを「認証が必要」に設定している場合は、Cloud Schedulerのジョブ作成時に「認証ヘッダー」で「OIDCトークンを追加」を選択し、Cloud Run Invoker権限を持つサービスアカウントを指定する方法が推奨されるとのことです。どちらの方式を使うかは、Cloud Runサービスの認証設定に応じて選んでください。
Cloud Runの環境変数設定
Cloud Runコンソールで、環境変数を設定します。
- Cloud Runのサービス詳細画面を開く
- 「新しいリビジョンの編集とデプロイ」をクリック
- 「変数とシークレット」セクションで「変数を追加」をクリック
- 以下のように設定する
- 名前:
SCHEDULER_SECRET_KEY - 値:予測困難な文字列(例:
my-secret-key-xyz123)
- 名前:
- 「デプロイ」をクリック
この合言葉は、後述するCloud Schedulerの設定でも使用します。
Cloud Schedulerの設定
Google Cloud コンソールで「Cloud Scheduler」を開き、ジョブを作成します。
- 「ジョブを作成」をクリック
- 以下のように設定する
| 設定項目 | 入力値 |
|---|---|
| 名前 |
keep-warm-app-job(任意の分かりやすい名前) |
| リージョン | Cloud Runと同じ場所(例:asia-northeast1) |
| 頻度 |
*/10 * * * *(10分ごと) |
| タイムゾーン | Asia/Tokyo |
| ターゲットタイプ | HTTP |
| URL | https://あなたのアプリのURL/keep-warm-db-check |
| HTTPメソッド | GET |
-
「ヘッダーを追加」をクリックし、以下を設定する
- 名前:
X-Scheduler-Secret - 値:Cloud Runの環境変数に設定した合言葉
- 名前:
-
「作成」をクリック
頻度の*/10 * * * *は、Cron形式で「10分ごと」を意味します。
より確実性を求めるなら、*/5 * * * *(5分ごと)に変更することもできます。
ただし、10分間隔でも多くの場合はコンテナが維持されるので、まずは10分で様子を見るのがよさそうです。
5. よくある疑問と回答
アクセス集中でインスタンスが増えた場合、全部生き残り続けませんか?
心配ありません。
定期的なアクセス(Ping)は、複数のインスタンスのうち「どれか1つ」にしか届きません。
アクセス集中が収まれば、定期アクセスを受け取っていない残りのインスタンスは自動的に削除(スケールダウン)されます。
また、仮に残っていたとしても、「処理をしていない待機中のインスタンス」は0円です。
意図的にアイドリングさせるのは規約違反になりませんか?
定期的な生存確認やヘルスチェックは、クラウド運用における正当な利用法です。
Google Cloud公式もこの方法をコールドスタート対策として認めており、アカウント停止などのペナルティを受けることはありません。
規約やポリシーは更新される可能性があるので、気になる方はCloud Runの公式ドキュメント(利用規約やQuotas & Limitsのページ)で最新情報を確認してください。
CPUコア数を増やしても大丈夫ですか?
大丈夫です。
2コアでも8コアでも、この程度の短時間処理(0.1秒程度)なら無料枠内で収まります。
気をつけたいのは「処理時間」であり、「CPUコア数」ではありません。
ただし、「CPUの割り当て」設定が「リクエストの処理中のみ」になっていることを確認してください。
これがデフォルトですが、もし「常に割り当てる」に変更していると、待機中も課金されてしまいます。
6. 運用上のポイント
セキュリティ対策
- Swagger UIから非表示にする(
include_in_schema=False) - ヘッダーで合言葉をチェックする
- 合言葉は予測困難な文字列を使用する
これらの対策により、第三者に勝手にアクセスされて負荷をかけられるリスクを軽減できます。
ただし、サービスが「未認証許可」設定になっている場合、エンドポイント自体には到達可能な点に留意してください。
DDoS等のリスクが気になる場合は、Cloud Runを「認証が必要」に設定し、Cloud SchedulerのOIDCトークン認証を使う構成も検討してください。
頻度の調整
最初は10分間隔で様子を見て、もし「遅い」と感じる場合にのみ5分間隔に変更してください。
頻度を上げても料金は変わりませんが、あまりに頻繁すぎると無駄なリクエストが増えるだけです。
ヘルスチェックの活用
Keep Warmのエンドポイントでは、単に200を返すだけでなく、データベースへの接続確認などのヘルスチェックも同時に行うことをお勧めします。
これにより、システムの健全性を定期的に監視できます。
もしデータベース接続に失敗した場合は、エラーレスポンスを返すようにすれば、Cloud Schedulerのログでエラーを検知できます。
7. まとめ
Cloud SchedulerとCloud Runを組み合わせることで、コールドスタートを回避しつつ、コストをほぼ無料に抑えることができました。
ポイントは以下の3つです。
- 定期的に軽いリクエストを送ることで、コンテナを温かい状態に保つ
- 認証方式はCloud Runの設定に合わせて選ぶ(合言葉方式 or OIDC トークン方式)
- 「CPUの割り当て」設定を「リクエストの処理中のみ」にする
この構成は、個人開発者から企業のプロダクション環境まで幅広く使われている定石です。
最初は「定期的にアクセスさせて大丈夫なのか」と不安でしたが、実際に運用してみると、料金はほぼ無料のまま、レスポンス速度が改善しました。
コールドスタートに悩んでいる方は、ぜひこの方法を試してみてください。

