writing / 2026-09-16
Moving DNS off the crashing host
DNS and DHCP left a Docker host with a panic habit for a pair of Pi-hole containers on two Proxmox nodes, and the same pattern now carries the control plane, the uptime monitor and the metrics stack.
DNS and DHCP used to run as a Pi-hole in Docker on nemesis, the main Docker host, with a second Pi-hole on devastator answering DNS only. When the host holding that container went away, nothing on the network could resolve a name until it came back.
Why the host was the problem
nemesis is a virtual machine on the Proxmox node sienar, and it has a crash history. Through August and September it logged a run of kernel panics with different signatures. A crash dump showed a page’s mapping pointer exactly one bit away from the real value. The verdict was flaky memory on a host without ECC, not a kernel bug. Each panic costs eight to ten minutes of reboot.
The move itself was forced earlier, in June, by a different failure. A cluster link flap split the two Proxmox nodes, one lost quorum and wedged, and it had to be power-cycled. While it was down there was no DNS anywhere.
The pair
DNS and DHCP now run on two small containers, one on each Proxmox node: dns-incomm and dns-sienar. Each is a privileged Debian 12 LXC running Pi-hole v6 natively, not in Docker. keepalived holds a virtual address between them. The incomm member is the master; if its Pi-hole stops answering, the address moves to sienar.
DHCP follows the same address. keepalived’s notify script turns DHCP on for whichever member holds the address and off on the other, and it only acts on a real state change, so the two never hand out leases at the same time. The router’s own DHCP is off. Clients get the shared address as their DNS server, so a failover changes nothing on their side.
Records and reservations are edited on the primary only. nebula-sync copies them to the secondary.
The old Docker Pi-holes stayed up for a soak period as a rollback, then were decommissioned.
One record for every service
Internal DNS now carries a single wildcard: every name under the homelab’s domain resolves to the reverse proxy host. A new web service needs a routing rule on the proxy and nothing else: no per-service record to add, forget or leave behind. Outside the network the same names resolve through the public DNS provider.
Pi-hole v6 also registers the hostnames of machines that take a lease, so every host gets a name without anyone writing it down.
The mechanism that still looked alive
The Docker-host repository still holds an Ansible playbook that writes DNS entries to custom.list, the Pi-hole v5 way to add local records. Pi-hole v6 keeps them in pihole.toml under dns.hosts, and the old container it targeted no longer exists. The playbook is a no-op. It does not fail; it just publishes nothing.
A record was once added there, looked finished, and had to be reverted when it turned out to reach no resolver. When a service moves, the old way of changing it has to be deleted or loudly marked, because a mechanism that runs clean and does nothing looks exactly like one that works.
Names, not addresses
The desktop that holds the note vaults has a DHCP reservation, so its address should never change. Twice it did. In June an SSH connection to the reserved address returned “No route to host” while the same connection by name went straight through; the name followed the machine’s real lease and the pinned address was stale. In September it happened again, and vault syncs that used the address directly failed silently. The cause is still open.
The rule: reach hosts by name. A hardcoded address is only as good as the reservation behind it, and nothing warns when that goes stale.
The control plane
The DNS pair became the template. There are now four pairs of LXCs, each pinned to physical nodes rather than living inside a VM that can crash:
- Control: tarkin on incomm (primary) and pellaeon on sienar (failover), carrying OpenTofu, Ansible and a clone of the infrastructure repo. pellaeon pulls the repo every ten minutes.
- DNS: the Pi-hole pair above.
- Uptime monitor: Uptime Kuma on both nodes. Only the member holding the shared address sends alerts, and its container monitors are reconciled every two minutes against what is actually running.
- Observability: Prometheus with 30 days of retention, Grafana, and a two-member Alertmanager cluster that gossips so each alert arrives in Discord once. Exporters cover hosts, disk SMART data and the Proxmox API.
The monitor and observability masters sit on sienar, the node with the redundant power supply; a watcher should not share fate with what it watches. Fleet changes run from tarkin out of the infrastructure repo, not as ad-hoc SSH from wherever the session happens to be, so every change stays written down.
What did not work
- The first temperature alert. It fired on the sensor’s own “high” threshold. The label varies per chip: the i7-12700K in incomm reports 80 C as “high”, which is its normal running temperature, so it paged constantly. The rule now uses absolute thresholds on the hottest core, warning at 95 C and critical at 98 C.
- Then the alert was right. incomm hard-crashed on a thermal fault in July, and later ran at 92 to 100 C at about 11 percent CPU load. That is cooling, not load, and it still needs a hardware fix.
- Packaged services in LXC. Pi-hole FTL, Prometheus and Grafana ship hardened systemd units that need a drop-in before they start in a container.
- The Pi-hole configuration is still imperative. The wildcard and the keepalived setup were applied by hand on both members and are not yet in Ansible.
What carries forward
Every new core service gets the same shape: two LXCs on two nodes, one shared address, one writer syncing to the other, and alerts from whichever member holds the address. Names over addresses, one wildcard over many records, and a replaced mechanism gets removed so it cannot pretend to work.
DNSをクラッシュするホストから移行する
DNS と DHCP は、2 つの Proxmox ノード上の Pi-hole コンテナペアに依存する Docker ホストを離れざるを得なかった。同じパターンは今や、コントロールプレーン、アップタイムモニタ、メトリクススタックにも適用されている。
DNS と DHCP は、メインの Docker ホストである nemesis 上で Docker を使用した Pi-hole として実行されていました。また、devastator というホスト上には、DNS のみを処理する 2 番目の Pi-hole がありました。そのコンテナを実行していたホストが停止すると、ネットワーク上のすべてのデバイスが名前解決ができなくなり、再び起動するまで名前を解決できませんでした。
なぜホストが問題だったのか
nemesis は Proxmox ノード sienar 上の仮想マシンであり、クラッシュの歴史がある。8月と9月にかけて、異なる署名を持つカーネルパニックの連続を記録している。クラッシュダンプの分析では、ページのマッピングポインタが実際の値からちょうど1ビットだけずれていることが確認された。結論としては、ECCを備えていないホストの不安定なメモリの問題であり、カーネルのバグではなかった。各パニックは、再起動に8から10分かかる。
この移動は、6月に別の障害によって強制された。クラスターリンクのフラップにより、2つの Proxmox ノードが分離され、1つはクォーラムを失い、停止してしまった。そのノードは電源を再投入する必要があった。その間、どこにもDNSは存在しなかった。
ペア
DNS と DHCP は今や、2つの小さなコンテナで動作しており、それぞれ Proxmox ノードの1つに配置されています: dns-incomm と dns-sienar。それぞれは特権付きの Debian 12 LXC で、Docker ではなく Pi-hole v6 をネイティブに実行しています。keepalived は、この2つのノードの間で仮想アドレスを保持しています。incomm メンバーがマスターであり、もし Pi-hole が応答しなくなると、アドレスは sienar に移動します。
DHCP も同じアドレスを追跡します。keepalived の notify スクリプトは、仮想アドレスを保持しているメンバーで DHCP を有効にし、もう一方のメンバーでは無効にします。これは、実際の状態変化にのみ反応するため、2つのノードが同時にリースを配布することはありません。ルーター自身の DHCP は無効になっています。クライアントは、共有アドレスを DNS サーバーとして設定しているため、フェールオーバーが発生してもクライアント側に変更は必要ありません。
レコードや予約はプライマリノードでのみ編集されます。nebula-sync はそれらをセカンダリノードにコピーします。
古い Docker 上の Pi-hole は、ロールバック用に一定期間稼働させた後、廃止されました。
サービスごとに1つのレコード
内部 DNS は現在、単一のワイルドカードを採用しています。homelab のドメインの下にあるすべての名前は、リバースプロキシのホストに解決されます。新しい Web サービスは、プロキシにルーティングルールを設定するだけで済み、それ以外の処理は必要ありません。各サービスごとのレコードを追加したり、忘れたり、後回しにしたりする必要がありません。ネットワークの外側では、同じ名前はパブリック DNS プロバイダを通じて解決されます。
Pi-hole v6 は、リースを取得する機械のホスト名を登録するため、各ホストが名前を持つように自動的に設定されます。これにより、誰も名前を手動で記録する必要がありません。
まだ生きているように見えたメカニズム
Docker-host レポジトリには、まだ Ansible playbook が含まれており、これは custom.list に DNS エントリを書き込むという、Pi-hole v5 におけるローカルレコードの追加方法です。Pi-hole v6 では、これらは pihole.toml の下の dns.hosts に保存され、以前にターゲットにしていたコンテナはもはや存在しません。この playbook は、何の処理も行いません。失敗することはありません。ただ、何も公開しません。
かつてその場所にレコードを追加し、完了したように見えたものの、最終的にどのリゾルバにも到達できないことが判明し、戻す必要がありました。サービスが移動した場合、古い方法で変更を加えることは削除するか、明確にマークする必要があります。なぜなら、何も処理を行わないクリーンなメカニズムは、実際に動作しているように見えるからです。
名前、住所ではなく
ノートのボックスを保持しているデスクトップにはDHCPの予約が設定されており、そのIPアドレスは決して変更されないはずでした。しかし、2回その予約が変更されました。6月には、予約されたアドレスにSSH接続を試みると「No route to host」というエラーが返されましたが、同じ接続を名前で行うと問題なく通りました。この場合、名前はマシンの実際のリースに従っており、固定されたアドレスは古くなっていたのです。9月にも同様のことが再発し、アドレスを直接使用して行われていたボックスの同期は静かに失敗しました。原因はまだ特定されていません。
ルール:ホストは名前でアクセスする。ハードコードされたアドレスは、その背後にある予約の信頼性だけしか保証できず、それが古くなったときに警告するものはありません。
コントロールプレーン
DNSペアはテンプレートとなりました。現在、4つのLXCペアがあります。これらはすべて、仮想マシン(VM)ではなく、物理ノードにピン留めされています。VMはクラッシュする可能性があるためです。
- 制御: incomm上のtarkin(プライマリ)とsienar上のpellaeon(フェイルオーバー)が担当しています。OpenTofu、Ansible、およびインフラストラクチャリポジトリのクローンを保持しています。pellaeonは10分ごとにリポジトリをプルします。
- DNS: 上記のPi-holeペア。
- 稼働時間監視: 両方のノードにUptime Kumaがインストールされています。共有アドレスを持つノードのみがアラートを送信し、そのコンテナの監視は実際の稼働状況と2分ごとに整合性が取られます。
- 観測性: Prometheus(30日間の保持期間)、Grafana、および2ノード構成のAlertmanagerクラスタ(gossip通信により、アラートがDiscordに1回だけ届くように設計)。エクスポータはホスト、ディスクSMARTデータ、およびProxmox APIをカバーしています。
監視および観測性のマスターは、冗長電源を持つsienarノード上に配置されています。監視対象と監視者とは運命を共有してはいけません。Fleetの変更は、インフラストラクチャリポジトリからtarkinから実行され、セッションがどこであれ、即席のSSHによる変更ではなく、すべての変更は記録されます。
うまくいかなかったこと
- 最初の温度アラート。 これはセンサー独自の「high」しきい値で発火しました。ラベルはチップごとに異なります: incomm に使われている i7-12700K では 80 C が「high」と表示され、これは通常の動作温度なので、アラートが頻繁に発生しました。現在のルールでは、最も高温のコアに絶対しきい値を適用しており、95 C で警告、98 C で重大な問題と判定しています。
- その後アラートは正しいものでした。 incomm は7月に熱故障によりハングしました。その後、約11%のCPU負荷で92〜100 Cで動作していました。これは冷却不良によるものであり、負荷ではなく、ハードウェアの修正が必要です。
- LXCでのパッケージ化されたサービス。 Pi-hole FTL、Prometheus、Grafana は、コンテナで起動する前に drop-in が必要な強化された systemd ユニットを提供しています。
- Pi-holeの設定は依然として命令型です。 ワイルドカードと keepalived の設定は、両方のノードで手動で適用されており、Ansible にはまだ含まれていません。
何が引き継がれるか
すべての新しいコアサービスは同じ構造になります:2つのLXCを2つのノード上に配置し、1つの共有アドレス、1つのライターがもう1つと同期し、アドレスを持つメンバーからアラートが送られます。名前をアドレスに置き換え、1つのワイルドカードを多くのレコードに代えて、置き換えられたメカニズムは削除され、動作しているように偽装することはできません。
DNS를 충돌하는 호스트에서 이전했다.
DNS 및 DHCP는 두 개의 Proxmox 노드에 있는 Pi-hole 컨테이너 쌍에 대해 패닉 습관이 있는 Docker 호스트를 떠났고, 같은 패턴은 이제 컨트롤 플레인, 업타임 모니터 및 메트릭스 스택에도 적용되고 있다.
DNS 및 DHCP는 기존에 nemesis, 메인 Docker 호스트에서 Docker로 실행되는 Pi-hole에서 실행되고 있었으며, devastator라는 두 번째 Pi-hole는 DNS만 응답했습니다. 해당 컨테이너를 실행하고 있던 호스트가 사라지면 네트워크상의 모든 것이 다시 돌아올 때까지 이름을 해석할 수 없었습니다.
호스트가 문제였던 이유
nemesis는 Proxmox 노드 sienar에 있는 가상 머신이며, 이전에 충돌 이력이 있다. 8월과 9월 동안에는 서로 다른 서명을 가진 커널 패닉이 반복적으로 기록되었다. 충돌 덤프를 보면, 페이지의 매핑 포인터가 실제 값에서 정확히 하나의 비트만큼 떨어져 있었다. 진단 결과는 ECC가 없는 호스트의 불안정한 메모리 문제였으며, 커널 버그는 아니었다. 각 패닉은 재부팅에 8~10분이 소요되었다.
이 이전 이동은 6월에 다른 실패로 인해 강제적으로 이루어졌다. 클러스터 링크의 불안정으로 인해 두 Proxmox 노드가 분리되었고, 하나는 쿼럼을 잃고 멈춰버렸다. 이 노드는 전원을 재시작해야 했으며, 다운되었을 때는 DNS가 전혀 없었다.
쌍
DNS 및 DHCP는 이제 두 개의 작은 컨테이너에서 실행되고 있으며, 각각은 Proxmox 노드 하나에 배치되어 있습니다: dns-incomm 및 dns-sienar. 각 컨테이너는 특권이 부여된 Debian 12 LXC이며, Docker가 아닌 Pi-hole v6을 네이티브로 실행하고 있습니다. keepalived는 이 두 컨테이너 사이에서 가상 주소를 유지합니다. incomm 멤버는 마스터이며, 만약 Pi-hole가 응답하지 않게 되면 주소는 sienar로 이전됩니다.
DHCP는 동일한 주소를 따릅니다. keepalived의 notify 스크립트는 주소를 보유한 멤버에 DHCP를 활성화하고, 다른 멤버에서는 비활성화합니다. 이 스크립트는 실제 상태 변경만을 기준으로 작동하므로, 두 컨테이너가 동시에 라이스를 배포하는 일은 발생하지 않습니다. 라우터 자체의 DHCP는 비활성화되어 있습니다. 클라이언트는 공유 주소를 DNS 서버로 설정하므로, 장애 조치(failover)가 발생하더라도 클라이언트 측에서는 변경 사항이 없습니다.
기록 및 예약은 주로 primary에서만 편집됩니다. nebula-sync는 이를 secondary로 복사합니다.
기존의 Docker 기반 Pi-hole은 롤백을 위해 일정 기간 유지되었고, 이후에 해체되었습니다.
서비스 하나당 하나의 기록
내부 DNS는 이제 단일의 와일드카드를 사용합니다: 홈랩 도메인 하위의 모든 이름은 리버스 프록시 호스트로 해석됩니다. 새로운 웹 서비스는 프록시에 라우팅 규칙만 추가하면 됩니다. 다른 것은 필요 없습니다: 서비스별 레코드를 추가하거나 삭제하거나 남겨둘 필요가 없습니다. 네트워크 외부에서는 동일한 이름이 공용 DNS 제공업체를 통해 해석됩니다.
Pi-hole v6는 또한 IP 대여를 받는 기계의 호스트명을 등록하므로, 모든 호스트는 누가 기록하지 않아도 이름을 받게 됩니다.
여전히 살아 있는 메커니즘
Docker-host 저장소는 여전히 custom.list에 DNS 항목을 작성하는 Ansible 플레이북을 포함하고 있다. 이는 Pi-hole v5에서 로컬 레코드를 추가하는 방법이다. Pi-hole v6은 pihole.toml의 dns.hosts 하위에 이를 유지하며, 이전에 타겟으로 삼았던 컨테이너는 더 이상 존재하지 않는다. 이 플레이북은 아무것도 하지 않는 명령이다. 실패하지 않으며, 단지 아무것도 게시하지 않을 뿐이다.
이곳에 레코드가 한 번 추가되었고, 완료된 것으로 보였지만, 결국 모든 리졸버에 도달하지 못한다는 것이 밝혀지면서 다시 되돌려야 했다. 서비스가 이동할 때, 이전 방식으로 변경한 내용은 삭제하거나 명확히 표시해야 한다. 왜냐하면 아무것도 하지 않는 메커니즘은 작동하는 것처럼 보일 수 있기 때문이다.
이름, 아닌 주소
노트 보관소를 저장하는 데스크탑에는 DHCP 예약이 설정되어 있으므로 IP 주소는 절대 변하지 않아야 합니다. 하지만 두 번이나 변했습니다. 6월에 예약된 주소로 SSH 연결을 시도했더니 “No route to host”라는 오류가 발생했고, 동일한 호스트 이름으로 연결하면 정상적으로 연결되었습니다. 이름은 기계의 실제 레이스를 따르지만, 고정된 주소는 오래된 정보였습니다. 9월에도 같은 일이 발생했고, 보관소 동기화가 주소를 직접 사용했기 때문에 조용히 실패했습니다. 원인은 아직 밝혀지지 않았습니다.
규칙: 호스트를 이름으로 접근하세요. 하드코딩된 주소는 뒤에 있는 예약만큼만 신뢰할 수 있으며, 예약이 오래된 경우 아무런 경고도 없습니다.
제어 평면
DNS 쌍은 템플릿이 되었다. 이제 LXCs로 구성된 네 쌍이 있으며, 이들은 가상 머신(VM)에 포함되어 있을 수 있는 충돌 위험보다는 물리적 노드에 고정되어 있다:
- 제어: incomm 노드의 tarkin(주 노드)과 sienar 노드의 pellaeon(대체 노드)에 OpenTofu, Ansible 및 인프라 구조 저장소의 복제본이 있다. pellaeon은 매 10분마다 저장소를 끌어올린다.
- DNS: 위에서 언급한 Pi-hole 쌍.
- 업타임 모니터링: 두 노드 모두에 Uptime Kuma가 설치되어 있다. 공유 주소를 보유한 노드만 경고를 전송하며, 컨테이너 모니터링은 실제로 실행되고 있는 상태와 매 2분마다 동기화된다.
- 관찰성: 30일의 데이터 보존 기능을 가진 Prometheus, Grafana, 그리고 두 개의 구성원으로 이루어진 Alertmanager 클러스터가 있다. 이 클러스터는 각 경고가 디스코드에 한 번씩 도달하도록 소문을 나눈다. Exporter는 호스트, 디스크 SMART 데이터 및 Proxmox API를 커버한다.
모니터링 및 관찰성 마스터는 백업 전원 공급 장치가 있는 sienar 노드에 위치한다. 감시자는 감시 대상과 운명을 함께해서는 안 된다. 펌트는 인프라 구조 저장소에서 tarkin에서 실행되며, 세션의 위치와 관계없이 임의의 SSH를 통해 실행되는 것이 아니라, 모든 변경 사항은 기록되어 있다.
작동하지 않은 사항
- 첫 번째 온도 경고. 이 경고는 센서 자체의 “높음” 임계값을 기준으로 발생했습니다. 라벨은 칩에 따라 다릅니다: incomm에서 사용하는 i7-12700K는 80°C를 “높음”으로 표시하며, 이는 정상 작동 온도이므로 계속해서 경고를 발생시켰습니다. 현재 규칙은 가장 뜨거운 코어에 절대 임계값을 사용하며, 95°C에서 경고하고 98°C에서 비상으로 표시합니다.
- 그 후 경고는 정확했습니다. incomm은 7월에 열 오류로 하드 크래시했고, 이후 약 11%의 CPU 부하에서 92~100°C의 온도를 기록했습니다. 이는 부하가 아니라 냉각 문제이며, 여전히 하드웨어 수정이 필요합니다.
- LXC에서 포장된 서비스. Pi-hole FTL, Prometheus 및 Grafana는 컨테이너에서 시작하기 전에 drop-in이 필요한 강화된 systemd 단위를 제공합니다.
- Pi-hole 설정은 여전히 명령형입니다. 와일드카드 및 keepalived 설정은 두 개의 구성 요소 모두에 수동으로 적용되었으며, Ansible에 아직 포함되어 있지 않습니다.
이어나가는 것
각 새로운 핵심 서비스는 동일한 구조를 갖는다: 두 개의 LXC가 두 개의 노드에 배치되며, 하나의 공유 주소, 하나의 writer가 다른 하나와 동기화되고, 주소를 보유한 구성원에서 경고가 발생한다. 이름 대 주소, 하나의 와일드카드 대 여러 레코드, 그리고 대체된 메커니즘은 제거되어 더 이상 작동한다고 속일 수 없다.
将 DNS 从崩溃的主机上迁移
DNS 和 DHCP 之前运行在一台有崩溃习惯的 Docker 主机上,用于两个 Proxmox 节点上的 Pi-hole 容器,而现在相同的模式也承载了控制平面、运行时间监控和指标堆栈。
DNS 和 DHCP 以前是在 nemesis 主机(主要的 Docker 主机)上的 Docker 容器中运行 Pi-hole,而 devastator 主机上运行了一个 Pi-hole,仅处理 DNS 请求。当承载该容器的主机宕机时,网络上的所有设备都无法解析名称,直到该主机恢复。
为什么是主机的问题
nemesis 是运行在 Proxmox 节点 sienar 上的一个虚拟机,它有崩溃的历史。在 8 月和 9 月期间,它记录了一系列带有不同签名的内核恐慌。崩溃转储显示,页面的映射指针与真实值仅相差一个位。结论是主机内存有问题,而该主机没有 ECC 内存,不是内核的错误。每次恐慌都需要 8 到 10 分钟的重启时间。
这次迁移早在 6 月就迫于另一种故障而进行。集群链路波动导致两个 Proxmox 节点分离,其中一个节点失去了 quorum 并卡住,不得不进行断电重启。在它宕机期间,整个网络都没有 DNS 服务。
双节点部署
现在 DNS 和 DHCP 在两个小型容器中运行,每个 Proxmox 节点上各一个:dns-incomm 和 dns-sienar。每个容器都是运行在特权模式下的 Debian 12 LXC,直接运行 Pi-hole v6,而不是在 Docker 中。keepalived 在它们之间维护一个虚拟地址。incomm 节点是主节点;如果它的 Pi-hole 停止响应,虚拟地址将转移到 sienar。
DHCP 也遵循相同的地址。keepalived 的通知脚本会在持有地址的节点上启用 DHCP,在另一个节点上禁用 DHCP,并且它只在实际状态变化时起作用,因此这两个节点不会同时分配 IP 地址。路由器本身的 DHCP 已关闭。客户端将共享地址作为其 DNS 服务器,因此故障切换对其一侧没有任何影响。
记录和保留仅在主节点上进行编辑。nebula-sync 会将它们复制到从节点。
旧的 Docker Pi-hole 容器在一段时间内保持运行,作为回滚的后备,之后被退役。
每个服务一个记录
内部 DNS 现在只使用一个通配符:homelab 域名下的所有名称都会解析到反向代理主机。一个新的 Web 服务只需要在代理上添加一个路由规则,无需其他操作:无需添加、忘记或遗留每个服务的记录。在外部网络中,这些名称通过公共 DNS 提供商解析。
Pi-hole v6 也会注册获取租约的机器的主机名,因此每台主机都会获得一个名称,而无需任何人手动记录。
仍然看似有效的机制
Docker 主机的仓库中仍然保留着一个 Ansible 剧本,它将 DNS 条目写入 custom.list,这是 Pi-hole v5 添加本地记录的方式。Pi-hole v6 则在 pihole.toml 的 dns.hosts 下保存这些记录,而它之前所针对的容器已经不存在了。这个剧本现在是一个无操作(no-op)。它不会失败;只是不发布任何内容。
曾经在那里添加过一个记录,看起来已经完成,但后来发现它无法被解析器访问,因此必须撤销。当服务迁移时,旧的更改方式必须被删除或明确标记,因为一个运行干净且不执行任何操作的机制看起来和一个正常工作的机制完全一样。
使用名称,而非地址
保存笔记库的桌面电脑有 DHCP 预留,因此它的地址应该永远不会改变。但地址两次发生了变化。6 月时,通过预留地址的 SSH 连接返回了 “No route to host”,而通过名称的相同连接却可以直接通过;名称跟随了机器的实际租约,而固定的地址已经过时。9 月时又发生了同样的情况,直接使用地址的 vault 同步操作静默失败。原因目前仍在调查中。
规则:通过名称访问主机。一个硬编码的地址只有在它背后的预留有效时才是可靠的,而当预留过期时,没有任何警告。
控制平面
DNS 对应的节点组成为了模板。现在有四个 LXCs 节点组,每个都绑定到物理节点上,而不是运行在可能崩溃的 VM 中:
- 控制节点: tarkin 在 incomm(主节点)和 pellaeon 在 sienar(备用节点)上运行,承载 OpenTofu、Ansible 以及基础设施仓库的克隆。pellaeon 每十分钟拉取一次仓库。
- DNS: 上述的 Pi-hole 节点组。
- 运行状态监控: 两个节点上都运行 Uptime Kuma。只有持有共享地址的节点会发送警报,其容器监控每两分钟与实际运行情况进行一次同步。
- 可观测性: Prometheus 保留 30 天的数据,Grafana,以及一个包含两个成员的 Alertmanager 集群,通过 gossip 协议确保每个警报只在 Discord 中发送一次。导出器覆盖了主机、磁盘 SMART 数据以及 Proxmox API。
监控和可观测性主节点位于 sienar,这个节点配备了冗余电源;监控不应与被监控的对象共享相同的故障风险。舰队变更从 tarkin 通过基础设施仓库进行,而不是通过任意会话位置的临时 SSH 命令执行,因此每次变更都会被记录下来。
未成功的工作
- 第一次温度警报。 它在传感器自身的“高”阈值上触发。标签因芯片而异:在 incomm 中,i7-12700K 的“高”温度为 80 C,这是其正常运行温度,因此它不断发出警报。现在规则在最热的核心上使用绝对阈值,95 C 时发出警告,98 C 时为严重。
- 然后警报是正确的。 incomm 在七月因热故障而硬崩溃,之后在约 11% 的 CPU 负载下运行温度在 92 到 100 C 之间。这是冷却问题,而不是负载问题,仍然需要硬件修复。
- LXC 中的打包服务。 Pi-hole FTL、Prometheus 和 Grafana 提供了加固的 systemd 单元,它们在容器中启动前需要 drop-in。
- Pi-hole 的配置仍然是声明式的。 通配符和 keepalived 的设置在两个成员上都是手动应用的,尚未集成到 Ansible 中。
前进的方向
每一个新的核心服务都采用相同的结构:两个节点上的两个 LXC 容器,一个共享地址,一个写入器同步到另一个,以及由持有地址的成员发出警报。使用名称而不是地址,一个通配符覆盖多个记录,替换机制被移除,以防止它假装正常工作。
Moviendo DNS del host que se caía
DNS y DHCP dejaron a un host de Docker con el hábito de caídas para un par de contenedores Pi-hole en dos nodos Proxmox, y el mismo patrón ahora afecta al plano de control, al monitor de tiempo de actividad y al stack de métricas.
DNS y DHCP se usaban anteriormente como un Pi-hole en Docker en nemesis, la máquina principal de Docker, con un segundo Pi-hole en devastator que solo respondía DNS. Cuando el host que alojaba ese contenedor se fue, nada en la red podía resolver un nombre hasta que volvió.
¿Por qué el host era el problema
nemesis es una máquina virtual en el nodo Proxmox sienar, y tiene un historial de fallos. A través de agosto y septiembre, registró una serie de fallos del kernel con diferentes firmas. Un volcado de memoria mostró que un puntero de mapeo de página estaba exactamente un bit alejado del valor real. El veredicto fue memoria defectuosa en un host sin ECC, no un error del kernel. Cada fallo cuesta entre ocho y diez minutos de reinicio.
El movimiento mismo fue forzado anteriormente, en junio, por un fallo diferente. Un salto en el enlace del clúster dividió los dos nodos Proxmox, uno perdió el quórum y se atascó, y tuvo que ser reiniciado. Mientras estaba caído, no había DNS en ninguna parte.
El par
DNS y DHCP ahora se ejecutan en dos pequeños contenedores, uno en cada nodo Proxmox: dns-incomm y dns-sienar. Cada uno es un LXC Debian 12 con privilegios ejecutando Pi-hole v6 nativamente, no en Docker. keepalived mantiene una dirección virtual entre ellos. El miembro incomm es el maestro; si su Pi-hole deja de responder, la dirección se mueve a sienar.
DHCP sigue la misma dirección. El script de notificación de keepalived activa DHCP en el miembro que posee la dirección y lo desactiva en el otro, y solo actúa ante un cambio real de estado, por lo que los dos nunca asignan concesiones al mismo tiempo. El propio DHCP del router está apagado. Los clientes obtienen la dirección compartida como su servidor DNS, por lo que un fallo de conmutación no cambia nada en su lado.
Los registros y las reservas se editan solo en el primario. nebula-sync los copia al secundario.
Los antiguos Pi-holes en Docker permanecieron activos durante un período de prueba como punto de rollback, luego fueron descomisionados.
Un registro para cada servicio
El DNS interno ahora lleva un solo comodín: cada nombre bajo el dominio del homelab se resuelve al host del proxy inverso. Un nuevo servicio web necesita una regla de enrutamiento en el proxy y nada más: no es necesario agregar, olvidar o dejar atrás un registro por servicio. Fuera de la red, los mismos nombres se resuelven a través del proveedor de DNS público.
Pi-hole v6 también registra los nombres de host de las máquinas que toman un contrato de alquiler, por lo que cada host obtiene un nombre sin que nadie lo escriba a mano.
El mecanismo que aún parecía estar activo
El repositorio del anfitrión de Docker aún contiene un playbook de Ansible que escribe entradas DNS en custom.list, la manera de Pi-hole v5 para agregar registros locales. Pi-hole v6 los mantiene en pihole.toml bajo dns.hosts, y el antiguo contenedor al que se dirigía ya no existe. El playbook es una operación sin efecto. No falla; simplemente publica nada.
Una vez se añadió un registro allí, parecía terminado, pero tuvo que revertirse cuando resultó que no alcanzaba a ningún resolutor. Cuando un servicio se mueve, el viejo método de cambiarlo debe eliminarse o marcarse claramente, porque un mecanismo que corre limpio y hace nada se ve exactamente como uno que funciona.
Nombres, no direcciones
La computadora de escritorio que alberga los almacenes de notas tiene una reserva de DHCP, por lo que su dirección nunca debería cambiar. Dos veces lo hizo. En junio, una conexión SSH a la dirección reservada devolvió “No hay ruta al host”, mientras que la misma conexión por nombre pasó directamente; el nombre siguió al contrato real de la máquina y la dirección fija era antigua. En septiembre ocurrió nuevamente, y las sincronizaciones de los almacenes que usaban directamente la dirección fallaron silenciosamente. La causa aún está abierta.
La regla: alcanzar hosts por nombre. Una dirección codificada es tan buena como la reserva detrás de ella, y nada advierte cuando esta se vuelve obsoleta.
La plataforma de control
El par DNS se convirtió en la plantilla. Ahora hay cuatro pares de LXCs, cada uno asignado a nodos físicos en lugar de vivir dentro de un VM que pueda fallar:
- Control: tarkin en incomm (primario) y pellaeon en sienar (failover), llevando OpenTofu, Ansible y un clon del repositorio de infraestructura. pellaeon extrae el repositorio cada diez minutos.
- DNS: el par de Pi-hole mencionado anteriormente.
- Monitor de tiempo de actividad: Uptime Kuma en ambos nodos. Solo el miembro que posee la dirección compartida envía alertas, y sus contenedores de monitoreo se reconcilian cada dos minutos con lo que realmente está en ejecución.
- Observabilidad: Prometheus con 30 días de retención, Grafana y un clúster de dos miembros de Alertmanager que se comunican entre sí para que cada alerta llegue una vez a Discord. Los exportadores cubren hosts, datos SMART de disco y la API de Proxmox.
Los maestros del monitor y la observabilidad residen en sienar, el nodo con suministro de energía redundante; un watcher no debe compartir su destino con lo que está vigilando. Los cambios de la flota se ejecutan desde tarkin a partir del repositorio de infraestructura, no como SSH ad-hoc desde dondequiera que se realice la sesión, por lo que cada cambio permanece documentado.
¿Qué no funcionó
- La primera alerta de temperatura. Se activó en el umbral propio del sensor “high”. La etiqueta varía según el chip: el i7-12700K en incomm reporta 80 C como “high”, que es su temperatura normal de funcionamiento, por lo que paginaba constantemente. La regla ahora usa umbrales absolutos en el núcleo más caliente, advirtiendo a 95 C y crítico a 98 C.
- Luego la alerta fue correcta. incomm sufrió un fallo grave por un problema térmico en julio, y más tarde funcionó a 92 a 100 C con una carga del CPU del 11 por ciento. Eso es enfriamiento, no carga, y aún necesita una corrección de hardware.
- Servicios empaquetados en LXC. Pi-hole FTL, Prometheus y Grafana envían unidades de systemd endurecidas que necesitan un drop-in antes de iniciar en un contenedor.
- La configuración de Pi-hole sigue siendo imperativa. El wildcard y la configuración de keepalived se aplicaron manualmente en ambos miembros y aún no están en Ansible.
¿Qué se lleva adelante
Cada nuevo servicio de núcleo recibe la misma forma: dos LXCs en dos nodos, una dirección compartida, un escritor sincronizando con el otro, y alertas desde el miembro que posee la dirección. Nombres sobre direcciones, un wildcard sobre muchas entradas, y un mecanismo reemplazado se elimina para que no pueda fingir funcionar.
DNS von dem abstürzenden Host entfernen
DNS und DHCP verließen einen Docker-Host mit einem Panik-Verhalten für ein Paar Pi-hole-Container auf zwei Proxmox-Knoten, und dasselbe Muster übernimmt nun die Steuerungsebene, den Uptime-Monitor und die Metrics-Stack.
DNS und DHCP liefen früher als Pi-hole in Docker auf nemesis, dem Haupt-Docker-Host, mit einer zweiten Pi-hole auf devastator, die nur DNS-Anfragen beantwortete. Als der Host, der diesen Container hostete, nicht mehr erreichbar war, konnte nichts auf dem Netzwerk einen Namen auflösen, bis er wieder verfügbar war.
Warum der Host das Problem war
nemesis ist eine virtuelle Maschine auf dem Proxmox-Knoten sienar und hat eine Crash-Geschichte. Im August und September zeigte sie eine Serie von Kernel-Panics mit unterschiedlichen Signaturen. Ein Crash-Dump zeigte, dass der Mapping-Pointer einer Seite genau einen Bit-Abstand vom tatsächlichen Wert hatte. Das Urteil lautete auf fehlerhaues Speicher auf einem Host ohne ECC, nicht auf einen Kernel-Bug. Jeder Panic kostet 8 bis 10 Minuten für einen Neustart.
Der Umzug selbst wurde bereits früher, im Juni, durch einen anderen Fehler erzwungen. Ein Cluster-Link-Flap teilte die beiden Proxmox-Knoten, einer verlor das Quorum und hing fest, und musste daher neu gestartet werden. Während dieser Zeit gab es keinen DNS-Service irgendwo.
Das Paar
DNS und DHCP laufen nun in zwei kleinen Containern, jeweils auf einem Proxmox-Knoten: dns-incomm und dns-sienar. Jeder ist ein privilegierter Debian 12 LXC, der Pi-hole v6 nativ ausführt, nicht in Docker. keepalived hält eine virtuelle Adresse zwischen ihnen. Der incomm-Member ist der Master; wenn sein Pi-hole aufhört, auf Anfragen zu antworten, wechselt die Adresse zu sienar.
DHCP folgt der gleichen Adresse. Das notify-Skript von keepalived schaltet DHCP für den Member ein, der die Adresse hält, und für den anderen aus, und es handelt sich nur bei einem echten Zustandswechsel, sodass die beiden nie gleichzeitig Leases vergeben. Der eigene DHCP-Server des Routers ist ausgeschaltet. Die Clients erhalten die gemeinsame Adresse als ihre DNS-Server, sodass ein Failover auf ihrer Seite nichts verändert.
Einträge und Reservierungen werden nur auf dem Primärsystem bearbeitet. nebula-sync kopiert sie auf das Sekundärsystem.
Die alten Docker-Pi-holes blieben noch eine Zeit lang online, um als Rollback zu dienen, und wurden anschließend abgeschaltet.
Eine Eintragung für jeden Dienst
Der interne DNS trägt nun einen einzigen Wildcard: Jeder Name unter dem homelab-Domain wird auf den Reverse Proxy-Host gelöst. Ein neuer Webdienst benötigt nur eine Routing-Regel auf dem Proxy und nichts weiter: keine pro-Dienst-Eintragung, die vergessen oder zurückgelassen werden könnte. Außerhalb des Netzwerks lösen die gleichen Namen sich über den öffentlichen DNS-Anbieter.
Pi-hole v6 registriert auch die Hostnamen der Maschinen, die einen Lease erhalten, sodass jedem Host ein Name zugewiesen wird, ohne dass jemand dies manuell notieren müsste.
Das Mechanismus, der immer noch lebendig aussah
Das Docker-Host-Repository enthält immer noch ein Ansible-Playbook, das DNS-Einträge in custom.list schreibt, die Art und Weise, wie Pi-hole v5 lokale Einträge hinzufügt. Pi-hole v6 speichert sie in pihole.toml unter dns.hosts, und das alte Container-Image, das es zielte, existiert nicht mehr. Das Playbook ist ein no-op. Es schlägt nicht fehl; es veröffentlicht einfach nichts.
Ein Eintrag wurde dort einmal hinzugefügt, schien fertig zu sein und musste später rückgängig gemacht werden, als sich herausstellte, dass er keinen Resolver erreichte. Wenn ein Dienst migriert wird, muss die alte Methode, ihn zu ändern, gelöscht oder deutlich markiert werden, denn ein Mechanismus, der sauber läuft und nichts tut, sieht genauso aus wie einer, der funktioniert.
Namen, nicht Adressen
Der Desktop, der die Notizschätze enthält, hat eine DHCP-Reservierung, sodass seine Adresse nie ändern sollte. Zweimal änderte sie sich jedoch. Im Juni gab es bei einer SSH-Verbindung zur reservierten Adresse die Meldung „No route to host“, während die gleiche Verbindung über den Namen problemlos funktionierte; der Name folgte der tatsächlichen Lease des Rechners, und die festgelegte Adresse war veraltet. Im September geschah es erneut, und die Schatz-Synchronisationen, die die Adresse direkt verwendeten, schlugen stumm fehl. Die Ursache ist bis heute ungeklärt.
Die Regel: erreiche Hosts über ihren Namen. Eine hartcodierte Adresse ist nur so gut wie die hinter ihr stehende Reservierung, und nichts warnt, wenn diese veraltet.
Die Steuerungsebene
Die DNS-Paarung wurde zur Vorlage. Es gibt nun vier Paare von LXCs, die jeweils an physische Knoten gebunden sind, anstatt in einem VM zu leben, das abstürzen kann:
- Steuerung: tarkin auf incomm (primär) und pellaeon auf sienar (Backup), die OpenTofu, Ansible und eine Kopie des Infrastruktur-Repositories beherbergen. pellaeon zieht das Repository alle zehn Minuten.
- DNS: das oben genannte Pi-hole-Paar.
- Uptime-Überwachung: Uptime Kuma auf beiden Knoten. Nur der Mitglied, das die gemeinsame Adresse hält, sendet Warnmeldungen, und seine Container-Überwachungen werden alle zwei Minuten mit dem tatsächlich laufenden System abgeglichen.
- Beobachtbarkeit: Prometheus mit einer Speicherzeit von 30 Tagen, Grafana und ein Alertmanager-Cluster mit zwei Mitgliedern, der sich so abstimmt, dass jede Warnung einmal in Discord ankommt. Exporter überwachen Hosts, Festplatten-SMART-Daten und die Proxmox-API.
Die Überwachung und Beobachtbarkeit-Master-Systeme befinden sich auf sienar, dem Knoten mit der redundanten Stromversorgung; ein Watcher sollte nicht das gleiche Schicksal teilen wie das Objekt, das er überwacht. Änderungen der Flotte werden von tarkin aus dem Infrastruktur-Repository durchgeführt, nicht als ad-hoc SSH von irgendeinem Ort, wo die Sitzung gerade stattfindet, damit jede Änderung schriftlich festgehalten bleibt.
Was nicht funktioniert hat
- Der erste Temperaturalarm. Er löste sich aus, sobald der Sensor seine eigene “hohen” Schwellenwert überschritt. Die Bezeichnung variiert je nach Chip: bei dem i7-12700K in incomm wird 80 C als “hoch” gemeldet, was seine normale Betriebstemperatur ist, wodurch es ständig Alarm auslöste. Die Regel verwendet jetzt absolute Schwellenwerte auf dem heißesten Kern, mit einer Warnung bei 95 C und einem kritischen Schwellenwert bei 98 C.
- Dann war der Alarm richtig. incomm stürzte im Juli aufgrund eines thermischen Fehlers ab und lief später bei einer CPU-Auslastung von etwa 11 Prozent bei Temperaturen zwischen 92 und 100 C. Das ist Kühlung, nicht Last, und es benötigt immer noch eine Hardware-Behebung.
- Verpackte Dienste in LXC. Pi-hole FTL, Prometheus und Grafana liefern verstärkte systemd-Einheiten, die vor dem Start in einem Container mit einem drop-in-Datei bearbeitet werden müssen.
- Die Pi-hole-Konfiguration ist immer noch imperativ. Die Wildcard und die keepalived-Konfiguration wurden manuell auf beiden Mitgliedern angewendet und sind noch nicht in Ansible enthalten.
Was weitergeführt wird
Jeder neue Kernservice erhält die gleiche Struktur: zwei LXCs auf zwei Knoten, eine gemeinsame Adresse, ein Schreiber, der mit dem anderen synchronisiert, und Warnungen von dem Mitglied, das die Adresse hält. Namen statt Adressen, eine Wildcard statt vielen Einträgen, und ein ersetzteter Mechanismus wird entfernt, damit er nicht vorgibt zu funktionieren.