# Cloudflare Tunnel の replica で blog を active-active 化した

2026-09-02

このブログの origin は Cloudflare Tunnel の先に 1 台だけ立っていて、そこが落ちたら全断でした。
これまで [無料 VPS](https://vps.xserver.ne.jp/free.php) や [無料のクラウド VM](https://docs.cloud.google.com/free/docs/free-cloud-features?hl=ja#compute) を渡り歩いてきましたが、移設や再起動のたびに気を使うのが面倒でした。
自宅では 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 個並びます。

```sh
$ 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 の冗長化そのものが要りませんが、動的にレンダリングしているのはロマンです。
