Cloudflare Tunnel の replica で blog を active-active 化した
このブログの origin は Cloudflare Tunnel の先に 1 台だけ立っていて、そこが落ちたら全断でした。
これまで 無料 VPS や 無料のクラウド VM を渡り歩いてきましたが、移設や再起動のたびに気を使うのが面倒でした。
自宅では Raspberry Pi 5 台を Nomad で束ねたクラスタを常時走らせているので、そこに同じ blog サーバーを並べて Cloudflare Tunnel の replica 機能で active-active 構成に組み直しました。
Cloudflare Tunnel replica
Cloudflare Tunnel は、1 つの tunnel token を複数の cloudflared プロセスに同時に持たせると、CF 側がそれらを全部 origin pool に入れて active-active に振り分ける仕様です。
DNS レコードや Load Balancer の追加は要りません。
無料プランでも動きます。
これら 5 台にも Nomad の system job として cloudflared を配置し、既存の cloudflared と同じ token を渡しました。
CF の tunnel 情報を覗くと、connector が 6 個並びます。
$ cloudflared tunnel info <tunnel-id>
CONNECTOR ID ARCHITECTURE EDGE
72a1b96c... linux_amd64 1xsea08, 1xsea09, 2xsea10
bd4256da... linux_arm 1xnrt14
79d6a95a... linux_arm 1xnrt12
42005c31... linux_arm 1xnrt14
a48a2d38... linux_arm 1xnrt01
d45c6706... linux_arm 1xnrt05
上の 1 行は US のクラウド VM が Seattle エッジに繋いだもの、下の 5 行は自宅の Raspberry Pi が Narita エッジに繋いだものです。
副次的に latency が縮まる
CF は edge → tunnel connector の経路を近さで選びます。
Narita エッジに着いた日本からのリクエストは、自然に Narita connector(自宅の Pi)へ流れます。
US オリジン単独時と比べて、日本から origin までの RTT は短くなります。
キャッシュミスのリクエストほど差が大きくなります。
Failover 実測
edge cache が効いていると、どの origin が返したか判別できません。
CF の Development Mode を有効にすると 3 時間だけ cache が無効化されます。
この状態で US 側 origin の cloudflared を停止しました。
--- Phase 1: baseline (6 replicas) ---
20 200 NRT
--- Phase 2: stop free-vm6 cloudflared ---
30 200 NRT
--- Phase 3: restore ---
10 200 NRT
30 発の probe は全部 HTTP 200 のまま、Narita エッジ経由で自宅クラスタが受け続けました。
US 側 origin を戻したところ、CF が heartbeat を検知して connector が pool に戻りました。
ダウンタイムはゼロでした。
自宅クラスタ側の Nomad にも check_restart {limit=3 grace=30s} を書いてありますが、接続断は CF 側の tunnel heartbeat が検知します。
Nomad の役割は、プロセスが SIGTERM で終了した場合に再起動することだけです。
複数 origin による副作用
origin が 6 個並ぶと、そのまま運用したときに起きる問題が 3 つあります。
articles の反映タイミングは、各 origin が独立に git pull で同期する構造上、最大 5 分ずれます。
記事更新の頻度は月に数回で、300 秒の edge cache TTL に埋もれるので許容範囲と割り切りました。
新着記事の Discord 通知は、素朴に 6 origin に配ると 1 記事あたり 6 発飛びます。
自宅クラスタ側の webhook 用環境変数を意図的に空にして、通知担当を US 側 1 台に固定しました。
デプロイは、片方の origin だけを更新すると 2 origin が別バージョンを配信する期間が生まれます。
CI では両方に並列でデプロイし、片方が失敗してももう一方は続行するようにしました。
まとめ
自宅の Raspberry Pi と US のクラウド VM が同じ tunnel に replica として並ぶ構成にしたので、片方が落ちても blog が生き続けるようになりました。
静的サイトジェネレータなら origin の冗長化そのものが要りませんが、動的にレンダリングしているのはロマンです。