記事一覧に戻る
Cloud Runのコールドスタートがなぜかぴったり10秒かかっていた件

Cloud Runのコールドスタートがなぜかぴったり10秒かかっていた件

AWSのLambdaの気分でCloud Runを使っていたら、ヘルスチェックの仕様の違いでハマった話。ECSとLambdaの中間のような存在だった。

普段AWSを使っていて、Google Cloudは最近触り始めたばかり。Cloud Runを「Lambdaみたいなもんでしょ」と思って使っていたら、コールドスタートで盛大にハマりました。

発端:体感めっちゃ遅い

動作検証をしていると、たまに画面が固まってしまうことに気づきました。DevToolsのNetworkタブを確認すると、APIレスポンスが異常に遅い。普段は一瞬で返ってくるのに、たまにめっちゃ待たされる。

Cloud Runのメトリクスを見てみたら、「コンテナの起動時のレイテンシ」という項目があり、これがきれいに10秒前後になっていました。

Cloud Runのコンテナ起動時のレイテンシ

コールドスタートが発生するたびにぴったり10秒。なんでこんなにきれいな数字なのか。

コンテナ起動が遅い?

最初は、コンテナ自体の起動やイメージのpullが遅いせいなんじゃないかと思いました。

ただ、セキュリティとイメージサイズ削減のため、distrolessベースのイメージを使い、tsupによるツリーシェイクでできる限り小さくしていたため、イメージサイズは150MB程度。pullに10秒かかるとは考えにくい。

試しにローカルで起動検証してみたところ、ローカルでは1秒以下で起動することが確認できました。Cloud Run側の問題っぽい。

ログを見てみる

万策尽きかけて、Cloud Runのログを見てみました。

Cloud Runのログ:サーバー起動からヘルスチェック成功まで約10秒

サーバー自体はローカルと同じく1秒以内に起動している(Server started on port 8080)。

でも、起動から約10秒経過したタイミングで「STARTUP HTTP probe succeeded after 2 attempts」というログが出ていることに気づきました。ヘルスチェックが2回目で成功している。

仮説:ヘルスチェック待ち?

Cloud Runではヘルスチェックが通ってからじゃないとリクエストの処理が始まらないのでは?という仮説を立てて、ドキュメントを調べてみました。

公式ドキュメントにこう書いてありました:

Once the startup probe succeeds, Cloud Run would mark the container as being available to handle the traffic.

やっぱり。startup probeが成功するまで、コンテナはトラフィックを受け付けない。

原因特定

Terraformの設定を確認したところ、startup_probeperiod_secondsがデフォルトの10秒になっていました。

startup_probe {
  http_get {
    path = "/health"
    port = 8080
  }
  initial_delay_seconds = 0
  period_seconds        = 10  # ← これがデフォルト
  timeout_seconds       = 3
  failure_threshold     = 3
}

initial_delay_seconds = 0なので、コンテナ起動後すぐにプローブが始まる。でも最初のプローブでサーバーがまだ起動中だと失敗して、次のプローブまで10秒待つ。サーバーが1秒で起動しても、最低でも10秒はヘルスチェック待ちになるわけです。

解決策

period_secondsを最小値の1秒に変更しました。

startup_probe {
  http_get {
    path = "/health"
    port = 8080
  }
  initial_delay_seconds = 0
  period_seconds        = 1   # 最小値
  timeout_seconds       = 1
  failure_threshold     = 30  # 1秒×30回=30秒の猶予
}

「ヘルスチェックの回数が増えたら課金増えるんじゃ?」と心配しましたが、ヘルスチェックはリクエスト数課金に含まれないとのこと。CPU/メモリ使用量のみ。なので間隔は短いほど良い。

残念ながら最小値は1秒で、100msとかは設定できませんでした。

結果

設定を修正したところ、1秒ちょいで起動してくれるようになりました。10秒から大幅改善。

ただし、period_secondsの最小値が1秒なので、コールドスタートは最速でも1秒以上になります。Lambdaのように数百msでリクエストを処理し始める、というわけにはいかない点は注意が必要です。

LambdaとCloud Runの違い

よく考えたら、Cloud RunってECSとLambdaの中間みたいな存在なんですよね。

特徴LambdaCloud RunECS
コンテナ管理不要不要必要
ヘルスチェックなしありあり
スケールイン自動自動設定次第
最小インスタンス0はいはい設定次第

Lambdaはコードを渡すだけでいい感じにやってくれる。ECSはコンテナを自分で管理する代わりに細かく制御できる。Cloud Runはその中間で、コンテナを渡すけど管理はお任せ、でもヘルスチェックとかはちゃんとある。

Lambdaの気分で「デプロイしたら即動く」と思っていたのが間違いでした。

逆に言えば、Cloud RunはECSのようにデプロイ時もヘルスチェックを完了させてからトラフィックがルーティングされるので、Lambdaと比べて安全なリリースがしやすいという面もあります。ヘルスチェックが通らない壊れたコンテナにトラフィックが流れることがない。

慣れてくると「ちょうどいい」感じがしてきました。