前回 [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 から外へ送ったデータ量で決まります。
料金が徐々に上がっているのは知っていましたが、理由は分かっていませんでした。

料金の内訳を見ると、ほぼ 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
どのくらい減ったのか?

グラフを見れば一目瞭然ですね。毎月 ¥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 を使っている人は、一度リクエストログをパス別に集計してみると、思わぬ発見があるかもしれません。