Workers route をデプロイしても本番は切り替わらなかった — cutover の実体は DNS だった
Cloud Run 上で動いていた本番サービスを Cloudflare Workers へ切り替える作業で、手順どおりに Worker のルートを有効化したのに、本番のリクエストが いつまでも旧環境に流れ続けた。原因はコードでもルート設定でもなく、DNS レコードの種類だった。
route を足しても何も起きない
移行の準備は済んでいた。本番の Worker はデプロイ済み、データベースも作成してデータの移行リハーサルも通っている。あとは Worker のルートを本番ホストに向けるだけ、のはずだった。
ところがルートを有効化してデプロイしても、本番ホストのレスポンスは何も変わらない。ヘッダーを見ても旧環境のままで、Worker のログには1件もリクエストが届いていない。設定を何度見直しても間違っていない。
本番ホストが Cloudflare を経由していなかった
原因は、その本番ホストの DNS レコードが CNAME で外部サービスを指すDNS-onlyだったことだ。DNS-only のホストへのリクエストは Cloudflare のエッジを経由しない。Cloudflare を通らないのだから、Workers のルートはそもそも評価される機会がない。
Workers の route は「Cloudflare を通過するリクエスト」に対するマッチングルール。トラフィックがエッジに来ていなければ、どんなに正しい route を書いても発火しない。ステージング環境が最初から proxied で作られていたため、この前提の違いに気づくのが遅れた。
ステージングは検証用に新しく作ったサブドメインで、最初から proxied にしていた。だから同じ手順で動いていた。本番ホストだけが、旧環境をホストしていた頃の DNS-only の CNAME を引きずっていた。
切り替えの実体は DNS レコードの差し替え
本番ホストのレコードを、proxied な AAAA 100::(ステージングと同じ形)に変更したところ、その場で Worker が応答を返し始めた。つまり移行手順のうち、実際にトラフィックを切り替えているのは DNS の1行だった。
# 切り替え前: Cloudflare を経由していない(DNS-only) $ dig +short auth.example.com ghs.googlehosted.com. # 切り替え後: proxied になり Worker が応答する $ curl -sI https://auth.example.com/.well-known/jwks.json | grep -i '^server' server: cloudflare
ロールバック手順が間違っていた
事前に書いておいた手順書には「問題が起きたら Worker の route を削除して戻す」と書いてあった。これは誤りだった。DNS-only のままなら route はそもそも発火していないのだから、route を消しても何も戻らない。
正しい戻し方は、proxied レコードを削除して元の DNS-only CNAME を復元することだ。手順書は実施後に書き直した。ロールバック手順は、切り替え機構と同じ層で書かれていなければ意味がない。切り替えが DNS で起きるなら、戻すのも DNS でなければならない。
この件以降、移行の計画には「可逆性マップ」を先に書くようにしている。今回であれば次のようになる。
- 本番 Worker のデプロイ・DB 作成・シークレット投入(route 無効のまま) — 完全に可逆。トラフィックがゼロなので、失敗しても何も起きない。
- DNS の切り替え — 可逆。元のレコードに戻せば数秒で旧環境へ戻る。ここが実質的な cutover 点。
- 旧環境の退役(コンテナ削除・DB 削除・ゾーン削除) — 不可逆。安定稼働を確認したあとにしか実行しない。
apex ドメインではメールを巻き込みかける
同じ切り替えを apex ドメインでも行ったが、こちらは危険度が一段上がる。apex には旧ホスティング用の A / AAAA レコードが複数ぶら下がっていて、それらを消して proxied レコードに置き換える必要がある。
このとき MX と TXT(SPF / DKIM / DMARC)を巻き込んで消すと、Web の切り替えと同時にメールが止まる。しかも自分宛のメールなので、止まったことに気づくのが遅れる。作業前後に dig MX で名前解決を確認し、メール関連レコードが無傷であることを明示的に検証してから次へ進んだ。
切り替わったことをどう確認したか
認証サーバーの移行では、「動いている」ことより「発行済みのトークンが無効化されていない」ことのほうが重要だ。署名鍵が変わると JWKS が変わり、連携している全アプリケーションのトークン検証が一斉に落ちる。
$ diff \
<(curl -s https://auth-next.example.com/.well-known/jwks.json) \
<(curl -s https://auth.example.com/.well-known/jwks.json)
# 差分なし = 署名鍵が同一 = 発行済みトークンは無効化されない新旧の JWKS を byte 単位で比較して差分がないこと、issuer が変わっていないこと、レスポンスヘッダーが新環境のものになっていること。この3つが揃って初めて「切り替わった」と判断した。
まとめ
移行手順を書くときは、「どの操作が実際にトラフィックを動かしているのか」を一点に特定しておくといい。それが分かっていれば、ロールバック手順は自動的に決まるし、そこに至るまでの操作はすべて安全にやり直せる準備作業だと整理できる。
今回のように、手順書に書いた切り替え点と実際の切り替え点がずれていると、いざというときに戻せない。手順書は一度実地で通したあとに書き直すまでが作業だと考えている。