この記事は Zenn にも投稿しています。内容は同じです。
結論
冗長化は「別ハード」でないと意味がありません。
同じホスト上に2台立てても、ホストが落ちれば両方死にます。当たり前のようですが、私は自分のバックログにこう書いていました。
TODO: セカンダリdnsmasqを .109 に立てる
.102(プライマリ)も .109(セカンダリ予定)も、同じProxmoxホスト上のLXCでした。
以下は、これに気づくまでと、気づいた後にやったことの記録です。
何が問題だったか
自宅のネットワークは、宅内DNSに dnsmasq を使っています。*.example.com を宅内のリバースプロキシに解決させる、いわゆる split-horizon DNS です。
address=/example.com/10.5.27.103
local=/example.com/
これがProxmox上のLXC(.102)で動いていました。そしてルーターが配っていたDNSは、この1本だけでした。
つまり:
Proxmoxが落ちると、宅内全端末の名前解決が止まる。
Proxmoxが落ちる状況は、珍しくありません。
- ホストの再起動(カーネル更新など)
- ディスクの移設
- 停電
どれも「いつか必ず来る」ものです。
そして症状が分かりにくい
これが厄介でした。インターネット接続自体は生きています。 ルーターのランプは正常、ping 1.1.1.1 も通る。しかし名前が引けないので、体感は「ネットが死んだ」になります。
家族に「ネット止まってる?」と聞かれて、ルーターを見に行っても異常が無い、という状態です。
宅内で最大の単一障害点でした。
「2台にする」では解決しない
最初の計画は「セカンダリのdnsmasqをもう1台のLXCに立てる」でした。
Proxmox ホスト
├── LXC .102 dnsmasq(プライマリ)
└── LXC .109 dnsmasq(セカンダリ)← 同じ船の上
これは冗長化ではありません。 ホストが落ちる障害では、両方が同時に死にます。
「2台ある」という事実だけを見て安心してしまう構造です。台帳上も、監視上も、2台に見えます。
「単一障害点を潰す」と言うときは、何と何が同時に死ぬのかを先に数えてください。
「2台あるから大丈夫」ではなく、「この2台は同時に死ぬか?」 を問う。同じホスト・同じ電源・同じスイッチなら、それは1台です。
別ハードに置いた
セカンダリDNSを、NAS上のDockerに立てました。Proxmoxとは別筐体です。
services:
dnsmasq:
image: dockurr/dnsmasq
network_mode: host
restart: unless-stopped
volumes:
- ./dnsmasq.conf:/etc/dnsmasq.conf:ro
ルーターのDHCP設定はこうなります。
priDns: 10.5.27.102 # Proxmox上のLXC
sndDns: 10.5.27.10 # NAS
絶対条件: 同じ答えを返すこと
セカンダリには、プライマリと同一の split-horizon 設定を入れます。ここが重要です。
⚠️ セカンダリに公開DNSを書いてはいけない
ルーターの設定画面に「セカンダリDNS」の欄があると、1.1.1.1 や 8.8.8.8 を書きたくなります。
書いてはいけません。
priDns: 10.5.27.102
sndDns: 1.1.1.1 ← ❌ これをやると壊れる
なぜか
クライアントは、プライマリの応答を待たずにセカンダリへ問い合わせることがあります。 順番に試すとは限りません。
そして 1.1.1.1 に grafana.example.com を聞くと、NXDOMAIN(そんな名前は無い) が返ります。公開DNSに内部名のレコードは当然ありません。
結果として、内部名がランダムに引けたり引けなかったりするという、最も切り分けにくい症状になります。
「予備があったほうが安心」という直感が、そのまま罠になります。 予備が正しい答えを返せないなら、予備は無いほうがマシです。
これは「セカンダリを持つな」という意味ではありません。同じ答えを返せるセカンダリなら持つべきです。
つまり、セカンダリは公開DNSではなく、プライマリの複製である必要があります。
検証: 実際に止めてみる
「たぶん動く」では意味がないので、プライマリを実際に停止して確認しました。
# プライマリを止める
pct stop 102
# 宅内の端末から名前を引く
dig +short pve.example.com
# → 10.5.27.103 ✅ セカンダリが答えた
さらに、ホストごと再起動するテストも実施しました。
ダウンタイム: 79秒
その間の宅内DNS: 一度も切れず
これでようやく「冗長化した」と言えます。
リハーサルできる障害は、リハーサルしておくべきです。
ホストの再起動はいつか必ず起きます。それを「事故」として経験するか「テスト」として経験するかは選べます。
NASに立てるときに踏んだ罠
Docker で dnsmasq を NAS に立てる、という構成には固有の落とし穴が2つありました。同じ構成を組む方の参考までに。
① NASのOSが 127.0.0.1:53 を既に握っている
systemd-resolved は停止しているのに、NASのOS独自のプロセスが53番を使っていました。
0.0.0.0:53 で待ち受けようとすると address already in use で起動できません。
listen-address=10.5.27.10 # LAN側IPだけにバインドして共存
bind-dynamic
② 🔥 composeの独自ブリッジから外に出られない
これが厄介でした。コンテナから上流DNS(1.1.1.1)へ一切到達できません。 pingもTCPも通りません。
原因は、composeが作る独自ブリッジ(172.18.x)に MASQUERADE のiptablesルールが入らないことでした。NASのOSがDockerのネットワーク周りを完全には面倒みてくれていない、ということです。
症状が分かりにくいのが最悪でした。
内部名(example.com) → 引ける ← 自分で答えているから
外部名(google.com) → タイムアウト ← 上流に行けないから
「半分動いている」ので、設定ミスを疑って延々と dnsmasq.conf を見直すことになります。
対処は network_mode: host です。独自ブリッジを使わず、NASのネットワークスタックを直接使えば MASQUERADE の問題自体が発生しません。
ついでに: bind-interfaces は使わない
dnsmasq の設定でもう1つ。bind-interfaces を使うと、起動タイミングで死にます。
LXCの起動時、eth0 が上がる前に dnsmasq が起動すると、bind に失敗して落ちます。宅内DNSが全滅します。
bind-dynamic # 後からインターフェースが現れても追従する
systemd側にも保険を入れています。
After=network-online.target
Restart=on-failure
この対処が効いていることを、後日確認する機会がありました。 ホストを別筐体に移設したとき、ログに警告が出ていたのです。
warning: interface eth0 does not currently exist
Active: active (running) ← 落ちずに動き続けている
bind-interfaces のままだったら、移設直後に宅内DNSが全滅していました。
まとめ
| やったこと | 効果 |
|---|---|
| セカンダリDNSを 別筐体(NAS) に置いた | ホストが落ちても名前解決が継続する |
| セカンダリにプライマリの複製を置いた(公開DNSではなく) | 内部名がランダムに引けなくなる問題を回避 |
| 実際にプライマリを止めて検証した | 「たぶん動く」を「動く」にした |
bind-dynamic にした |
起動順の問題で死ななくなった |
一番の学びは、自分のバックログに書いてあった計画が間違っていたことに気づけたことでした。「セカンダリを立てる」というタスクは正しく見えます。どこに立てるかまで考えて初めて意味を持ちます。
同じような失敗を57件ぶん書きました
この「冗長化しているつもり」も含めて、自宅ラボを組むなかで踏んだ地雷を57件、症状から逆引きできる形で本にまとめました。
📕 自宅ラボ 0→100 ── Proxmoxで作る、止まらない自宅インフラ
Proxmox + LXC 13台の環境で、台帳の自動化・監視・バックアップ・VPNまでを組んだ記録です。手順ではなく、判断の根拠と失敗の記録が中心になっています。
第0章(設計の5原則)と更新履歴は無料で読めます。