前回 [2026-11-03-1] では Cloud Run のデプロイ方法を見直しました。今回はその続きです。

タイトルのとおりですが、nginx と Cloudflare の設定を見直したことで、Cloud Run の転送料金が 96% 減りました。前回の記事で減らした Artifact Registry の分と合わせると、ここ 1 年ほど毎月 ¥1,000 を超えていた Google Cloud の料金が、10 月は ¥72 になりました。

なぜ料金を見直したのか

前回の流れで、Google Cloud の料金を確認しました。

このブログは、Cloud Run 上の nginx が静的ファイルを返すだけです。Cloud Run の手前には、CDN の Cloudflare を置いています。Cloud Run の転送料金は、Cloud Run から外へ送ったデータ量で決まります。

料金が徐々に上がっているのは知っていましたが、理由は分かっていませんでした。

2024 年 4 月〜2026 年 8 月の Google Cloud の料金

料金の内訳を見ると、ほぼ Cloud Run の Data Transfer(転送料金、グラフの青)と Artifact Registry Storage(オレンジ)の 2 つです。

Artifact Registry は料金の半分近くを占めていましたが、前回の記事で cleanup policy を設定したので、減ることは分かっていました。

残るは Data Transfer で、9 月は ¥556 でした。

Cloud Run のリクエストログ(run.googleapis.com/requests)の httpRequest.responseSize を、直近 24 時間分、パス別に合計するとこうなりました。

パス 転送量 割合 リクエスト数
/ 0.20 GiB 6.1% 9,067
/index.xml 3.02 GiB 92.5% 1,522
その他 約 0.04 GiB 1.4% 約 3,200

転送量の 9 割は、RSS リーダー向けのフィードである /index.xml でした。全記事の全文が入って 5.4MB あり、1 日で約 3 GiB にもなっていました。

nginx の gzip 圧縮が無効だった

恥ずかしながら、nginx の gzip 圧縮が無効なことに気づいていませんでした。

nginx の設定ファイルは、何年も前に nginx 公式 Docker image から流用したものです。そのときから #gzip on; とコメントアウトされたままでした。

ブラウザには Cloudflare が圧縮して返していたので、見た目では分かりませんでした。無圧縮だったのは、Cloud Run から Cloudflare までの区間です。

Cloud Run (nginx) --(無圧縮)--> Cloudflare --(gzip)--> ブラウザ

Cloud Run の Data Transfer として課金されるのは、この無圧縮の区間です。

こんな感じで gzip 圧縮を有効にしました。

gzip  on;
gzip_comp_level  6;
gzip_proxied  any;
gzip_vary  on;
gzip_types  text/plain text/css text/xml application/javascript application/json image/svg+xml;

gzip_proxied any にしたのは、途中のプロキシが Via ヘッダを付けても圧縮するためです。nginx は Via ヘッダがあるリクエストを、デフォルトでは圧縮しません。

/index.xml は 5.4MB から 1.27MB に減りました。

その後、別件で /index.xml に入れる記事を最新の 100 件に絞りました。昔の慣習のまま全記事を入れていましたが、そもそも必要ないと判断しました。今の /index.xml は約 1MB で、gzip 圧縮すると約 245KB です。

ちなみに、この件で 2001 年の 2ch の閉鎖騒動(8月危機 )を思い出しました。

2001 年 8 月、転送量の増加で運営が困難になり、2ch が閉鎖の危機を迎えました。そのとき UNIX 板の住人が read.cgi を改良 して、転送量を減らしました。改良には gzip 圧縮も含まれていました。

25 年経っても、gzip 圧縮で転送量が減る話をしているとは思いませんでした ( ´∀`)

HTML も Cloudflare でキャッシュするようにした

実は Cloudflare の CDN は、[2024-04-13-1] で DNS を移行したときから有効になっていました。Cloudflare にキャッシュがあれば、Cloud Run へ毎回取りに行かずに Cloudflare から返せるので、転送量が減ります。

ただし、キャッシュされていたのは画像などの静的ファイルだけでした。HTML や /index.xml は、毎回 Cloud Run まで取りに行っていました。

配信元(origin)の Cloud Run は Cache-Control を返していないので、変更前は Cloudflare のデフォルトの挙動です。Edge TTL は Cloudflare がキャッシュを持つ時間、Browser TTL はブラウザが持つ時間です。変更前と変更後はこうなりました。

変更前(Cloudflare のデフォルト) 変更後(Cache Rules)
対象 拡張子で決まる静的ファイル(画像、CSS、JS など)と robots.txt すべて
HTML、/index.xml、/llms.txt、sitemap キャッシュしない キャッシュする
Edge TTL status code ごとのデフォルト(200 なら 120 分) 1 日
Browser TTL 4 時間 なし(bypass。ブラウザに保持させない)

キャッシュはデプロイのたびに全削除(Purge)します。Cache Rules は Terraform の cloudflare_ruleset で管理しています。

resource "cloudflare_ruleset" "cache" {
  zone_id = data.cloudflare_zone.main.zone_id
  name    = "default"
  kind    = "zone"
  phase   = "http_request_cache_settings"

  rules = [
    {
      ref         = "cache_everything"
      description = "Cache everything including HTML for 1 day. deploy.yml purges the cache on every deploy"
      # 外形監視はキャッシュではなく origin を見に行かせたいので除外する
      expression = "not (http.user_agent contains \"GoogleStackdriverMonitoring-UptimeChecks\" or http.user_agent contains \"UptimeRobot\")"
      action     = "set_cache_settings"
      action_parameters = {
        cache = true
        edge_ttl = {
          mode    = "override_origin"
          default = 86400
        }
        # Purge はブラウザのキャッシュを削除できないので、ブラウザには持たせない
        browser_ttl = {
          mode = "bypass"
        }
      }
    }
  ]
}

Edge TTL は 1 日にしました。デプロイのたびに Purge するので、本来は 1 週間でも 1 年でも構いません。ただ、Purge に失敗すると、古い内容が TTL の間ずっと残ってしまいます。1 日なら、最悪でも翌日には直ります。

TTL が短い分、Cloudflare は Cloud Run に再確認しに来ます。実際のログでは、期限切れ後の再確認に 304(変更なし)が返っており、転送量はわずかでした。

Browser TTL を bypass にしたのは、Purge ではブラウザのキャッシュを削除できないからです。画像もブラウザには保持されなくなりますが、Cloudflare から返るので Cloud Run の転送量は増えません。

外形監視(Cloud Monitoring の稼働時間チェックと UptimeRobot)は、User-Agent でキャッシュの対象から外しました。監視したいのは、キャッシュではなく Cloud Run が生きているかどうかなので。

Purge は、Deploy ワークフローで Cloud Run へのデプロイの後に Purge Everything API を呼んでいます。

      - name: Purge Cloudflare cache
        run: |
          curl -sSf -X POST "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/purge_cache" \
            -H "Authorization: Bearer ${CLOUDFLARE_PURGE_TOKEN}" \
            -H "Content-Type: application/json" \
            --data '{"purge_everything":true}' \
            | jq -e .success

どのくらい減ったのか?

2026 年 1〜10 月の Google Cloud の料金

グラフを見れば一目瞭然ですね。毎月 ¥1,000 を超えていたのが、10 月は ¥65(税抜き、税込み ¥72)になりました。今回減らしたのは青の Data Transfer で、オレンジの Artifact Registry Storage は前回の記事で減らしました。

まず、Cloud Run のリクエストログで 1 日の転送量を比べました。

パス 対策前 対策後
/ 0.20 GiB 0.065 GiB
/index.xml 3.02 GiB 0.015 GiB
その他 約 0.04 GiB 約 0.039 GiB
合計 3.26 GiB 0.119 GiB

約 96% 減りました。/index.xml へのリクエストは 1,522 件から 30 件になり、大半を Cloudflare が返すようになりました。

/ が残っているのは、外形監視をキャッシュの対象から外したからです。それでも gzip 圧縮のおかげで、転送量は 1/3 になりました。

次に、9 月と 10 月の料金です。

項目 9 月 10 月
Cloud Run の Data Transfer ¥556(83.01 GiB) ¥24(3.66 GiB)
Artifact Registry Storage ¥442(28.24 GiB/月) ¥41(3.10 GiB/月)
Cloud Storage ¥20 ¥0
小計 ¥1,018 ¥65
税 ¥102 ¥7
合計 ¥1,120 ¥72

10 月分にはフィードを 100 件に絞った効果も少し含まれますが、上のログの比較は絞る前のものです。

減った ¥953 のうち、今回の対策で減ったのは Data Transfer の ¥532 です。残りのうち ¥401 は Artifact Registry で、こちらは前回の記事で cleanup policy を設定した成果です。残る ¥20 は Cloud Storage です。

ちなみに、Cloudflare の CDN は Free プランなので ¥0 のままです。

まとめ

気づいていなかった nginx の gzip 圧縮と、対象が静的ファイルだけだった Cloudflare のキャッシュ。この 2 つを見直したことで、Cloud Run の転送料金が 96% 減りました。前回の Artifact Registry の掃除と合わせて、ここ 1 年ほど毎月 ¥1,000 を超えていた Google Cloud の料金は ¥72 になりました。

どちらも、分かってしまえば簡単な話でした。Cloud Run を使っている人は、一度リクエストログをパス別に集計してみると、思わぬ発見があるかもしれません。

参考情報