日常排查:Minisforum N100 小主机随机掉盘/卡顿失去响应(DNS 服务运行 1 天+)

这是一篇“日常排查记录”模板文章。我会尽量只写事实 + 证据链,避免凭感觉下结论。

TL;DR

  • 问题现象:DNS 服务跑着跑着失去响应;整机出现明显顿卡;偶发(疑似)掉盘/IO 异常。
  • 触发特征:随机出现,通常在连续运行 1 天+ 后发生。
  • 恢复方式关机断电并静置一段时间后恢复正常。
  • 当前结论(概率表达):____(例如:高概率与散热/供电/存储控制器复位相关)。

影响范围

  • 直接影响:____(例如:内网所有设备 DNS 解析超时/失败)。
  • 间接影响:____(例如:依赖域名解析的服务/容器全部异常)。
  • 影响时段:____(开始时间/持续多久/是否自动恢复)。

环境信息(可复现前提)

硬件

  • CPU:Intel® N100(4C/4T,6MB Cache,最高 3.4GHz)
  • GPU:Intel® UHD Graphics
  • 内存:8GB LPDDR5(单通道,板载,4800MHz)
  • 存储:UFS 2.1 256G
  • 无线:Intel AX200/201(Wi‑Fi 6 / Bluetooth 5.2)
  • 有线:2.5G RJ45 ×1(支持 PoE IEEE 802.3at)
  • 接口:USB 3.2 Gen2 Type‑A ×2;USB 3.2 Gen2 Type‑C ×1(Alt DP/PD);HDMI ×1
  • 电源:65W USB‑C Power Delivery 适配器

软件

  • 实际运行系统:Linux(当前内核版本为 6.8.12-5-pve;出厂为 Windows 11 Home)
  • 内核版本(如 Linux)6.8.12-5-pve
  • 引导加载器(GRUB)grub2 2.06-13+pmx2
  • DNS 软件:____(dnsmasq / unbound / AdGuard Home / Pi‑hole / 其它)
  • 部署方式:____(systemd / Docker / 其它)
  • 其它常驻服务:____
  • 日志策略:____(落盘频率/轮转策略/是否写入同一磁盘)

问题描述(只写事实)

  • 首次出现时间:____
  • 复现频率:大约每 ____ 小时/天一次(随机)。
  • 故障时可观测症状
    • DNS:____(超时/拒绝/解析慢)
    • 系统:____(SSH/远程桌面是否可达?CPU/内存是否飙升?)
    • 存储:____(挂载点消失/只读/IO error/设备重置/掉盘)
    • 网络:____(ping 是否丢包?网卡是否 reset?)
  • 故障后的恢复动作:____(关机静置 ____ 分钟后恢复;“重启”是否也能恢复:____)
  • 与负载的关系:____(高 QPS/日志写入/其它任务是否更容易触发)

当场止血(按时间线)

目的:先恢复核心服务,避免数据损坏,再谈定位。

  • T+0:____(例如:将上游 DNS 临时切换到路由器/公网 DNS)
  • T+5m:____(例如:停止 DNS 服务/停止高 IO 服务)
  • T+10m:____(例如:采集日志/抓取指标快照)
  • 结果:____(是否恢复?持续多久?)

我关心的“数据结构”(证据链要对齐时间戳)

  • 一个时间线:故障开始时间、温度峰值、IO 错误、服务超时,必须能对齐。
  • 一个核心问题:是“DNS 服务死了”,还是“机器/存储子系统死了导致 DNS 表现为死”。

假设清单(按优先级)

  • H1:散热/过热导致保护或降频,继而触发系统不稳定
    • 证据:____(温度曲线/thermal throttle log)
  • H2:供电不稳(PD/PoE/适配器)导致存储控制器或系统 reset
    • 证据:____(kernel reset/电源事件/规律性)
  • H3:存储介质或控制器问题(UFS/NVMe/SATA)导致 IO 卡死或设备消失
    • 证据:____(I/O error、timeout、device reset、SMART/health)
  • H4:软件层资源耗尽(FD/内存泄漏/日志写爆/IO 打满)
    • 证据:____(ulimit、内存曲线、iowait、磁盘写入量)
  • H5:驱动/内核 bug(省电策略/设备电源管理)
    • 证据:____(特定模块报错、升级/降级内核后变化)

数据采集(故障发生时我必须抓到什么)

不要靠“感觉像过热/像掉盘”,用日志说话。

  • 温度/风扇:____(例如 Linux:sensors/sys/class/thermal/;Windows:HWiNFO)
  • 系统日志:____(Linux:journalctl;Windows:事件查看器)
  • 存储相关日志:____(I/O error、timeout、reset、file system remount read-only)
  • 资源指标:____(CPU、load、iowait、内存、swap、磁盘吞吐)
  • DNS 指标:____(QPS、缓存命中、上游延迟、失败率)
  • 客户端验证:____(dig/nslookup 的失败表现)

关键证据(粘 3~5 段就够)

  • 证据 1(定性)
text
[time] ____
  • 证据 2(支持链路)
text
[time] ____
  • 证据 3(排除项)
text
[time] ____

排查过程(尝试 → 观察 → 结论)

尝试 A:____

  • 改动:____
  • 观察:____
  • 结论:____(支持/否定哪条假设)

尝试 B:____

  • 改动:____
  • 观察:____
  • 结论:____

尝试 C:____

  • 改动:____
  • 观察:____
  • 结论:____

结论(概率表达,不装确定)

  • 高概率主因:____
  • 次要因素:____
  • 我排除的方向:____(必须给证据)

解决方案(短期止血 vs 长期修复)

短期止血(马上能做)

  • ____(例如:加强散热/开盖/外接风扇)
  • ____(例如:限制功耗/关闭省电策略)
  • ____(例如:降低日志落盘/开启轮转/把日志写到另一块盘)

长期修复(一次到位)

  • ____(例如:更换电源/更换存储/更新 BIOS/固件)
  • ____(例如:调整机箱风道/加导热垫/改善安装位置)
  • ____(例如:升级/更换系统与内核版本)

验证标准

  • 连续运行:____ 天无复现
  • 温度上限:CPU ≤ ____°C;存储 ≤ ____°C
  • 日志:无 I/O error / reset / remount read-only

复盘

  • 这次最值钱的证据:____
  • 下次更早做的事:____(例如:加监控、告警、故障自动抓日志)
  • 仍未解决的问题:____

尝试方案:依赖 192.168.22.12(PVE 宿主)上的监控 LXC(192.168.22.19)持续监测 192.168.22.18(Minisforum)

目标:把“感觉像过热/像掉盘/像 DNS 挂了”变成一条可对齐时间戳的证据链:温度曲线 + IO/内核报错计数 + DNS 可用性探测 + 主机可达性

注意:不要把账号密码写进任何配置文件/仓库。下面所有登录步骤都默认你手动输入密码或(更推荐)使用 SSH key。

额外坑:22 端口通,但 ssh 仍然卡住/断线(跨网段更常见)

这个坑很容易误导人:你看到 nc 显示 22/tcp open,就以为网络没问题;但 ssh 仍然会在握手阶段卡住,最后报超时或 Broken pipe

场景(这次真实遇到的拓扑)

  • 客户端网段192.168.11.0/24
  • 目标主机192.168.22.12(PVE)
  • 同网段跳板/对照192.168.22.18(从这里 ssh.12 通畅)
  • 同时发生的信号:DNS 服务(例如 192.168.22.53:53)出现超时(反查/解析失败会放大 SSH 的握手耗时)

现象(典型表现)

  • nc -vz -G 2 192.168.22.12 22 显示 succeeded(TCP 三次握手没问题)
  • ssh root@192.168.22.12 卡住,随后出现:
    • ssh_dispatch_run_fatal: Connection to 192.168.22.12 port 22: Operation timed out
    • Read from remote host 192.168.22.12: Operation timed out / client_loop: send disconnect: Broken pipe

快速判定(先把“端口通”细分)

  1. 看 SSH banner 是否能立即返回(只测协议,不涉及认证):
bash
nc -v 192.168.22.12 22

正常情况会快速看到类似 SSH-2.0-OpenSSH_...。如果连上但 banner 很久不出/不出,说明问题更像在 sshd/系统层(例如 DNS 反查阻塞、系统负载、网络策略导致后续数据不稳定),不是“单纯端口被挡”。

  1. 抓 SSH 卡在哪一步(留证据用):
bash
ssh -vvv -o ConnectTimeout=5 -o ServerAliveInterval=5 -o ServerAliveCountMax=1 root@192.168.22.12

最小修复(优先消灭 DNS/GSSAPI 造成的特殊情况)

如果你能从同网段主机(例如 .22.18)登录到 .22.12,优先在 服务端做这两个改动(对内网环境通常更稳、更“少坑”):

  1. 编辑 .22.12/etc/ssh/sshd_config,确保存在:
  • UseDNS no
  • GSSAPIAuthentication no
  1. 校验并重启:
bash
sshd -t && systemctl restart ssh
systemctl status ssh --no-pager

直觉解释:当 DNS/反查不稳定时,UseDNS yes 可能让 SSH 在握手/审计阶段等待解析结果;把它关掉能让“特殊情况”消失。

如果仍然不稳:按“跨网段三件套”继续排除

  • 回程路由是否正确(目标机必须知道怎么回 192.168.11.0/24):
bash
ip route get 192.168.11.12
  • 防火墙/ACL:除了放行 22/tcp,还要确保回程方向与状态跟踪规则一致(尤其是网关/NAT/策略路由)。
  • MTU/分片问题:跨网段、隧道、PPPoE 场景容易出现“能握手但数据阶段不稳”;建议在 Linux 上用带 DF 的大包 ping 做一次验证(必要时先临时把 MSS/MTU 调小验证)。

总体架构(最少但够用)

  • 192.168.22.19(监控端 LXC,运行在 192.168.22.12 这台 PVE 上):Prometheus + Grafana + blackbox_exporter(可选 Pushgateway)
  • 192.168.22.18(被监控端):nodeexporter(主机指标)+(可选)smartctlexporter(磁盘健康)+ 自定义 textfile 指标(内核/IO error 计数)

这套东西的“数据结构”很简单:

  • 时序指标(Prometheus):每 15s 抓一次,故障前后至少覆盖 48 小时。
  • 主动探测(blackbox_exporter):从 .19 主动 ping / tcp 探测 .18/.54,区分“机器死了”和“服务死了”。
  • 告警(可选):故障发生时把时间戳钉死(邮件/Telegram/飞书随你,先把规则跑起来)。

尝试 D:监控栈落地(Prometheus + Grafana + blackbox_exporter)

D0. 前置检查(网络与端口)

192.168.22.19 上确认能到达 192.168.22.18

bash
ping 192.168.22.18
Test-NetConnection 192.168.22.18 -Port 22

后续会用到的端口(按默认值):

  • .18:9100 node_exporter
  • .18:9633 smartctl_exporter(可选)
  • .19:9090 Prometheus
  • .19:3000 Grafana
  • .19:9115 blackbox_exporter
  • .19:9091 Pushgateway(可选)

如果你有防火墙/安全组,先放行 .19 -> .18 的 9100/9633/22,以及 .19 -> .54 的 53/3000(只在内网开放就行)。

D1. 在 192.168.22.18 部署 node_exporter(systemd 方式,最稳)

.18 执行(版本号你可以换新,但别用“latest”,排查时要可追溯):

bash
export VER="1.7.0"
curl -fsSL -o /tmp/node_exporter.tar.gz "https://github.com/prometheus/node_exporter/releases/download/v${VER}/node_exporter-${VER}.linux-amd64.tar.gz"
tar -C /tmp -xzf /tmp/node_exporter.tar.gz
install -m 0755 /tmp/node_exporter-${VER}.linux-amd64/node_exporter /usr/local/bin/node_exporter
useradd --system --no-create-home --shell /usr/sbin/nologin nodeexp || true
mkdir -p /var/lib/node_exporter/textfile_collector
chown -R nodeexp:nodeexp /var/lib/node_exporter
cat >/etc/systemd/system/node_exporter.service <<'EOF'
[Unit]
Description=Prometheus Node Exporter
After=network-online.target
Wants=network-online.target
[Service]
User=nodeexp
Group=nodeexp
ExecStart=/usr/local/bin/node_exporter \
--web.listen-address=:9100 \
--collector.textfile.directory=/var/lib/node_exporter/textfile_collector
Restart=on-failure
RestartSec=2
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now node_exporter
systemctl status node_exporter --no-pager

验证(在 .12 或任意内网机器):

bash
curl -fsS http://192.168.22.18:9100/metrics | head

D2.(可选但强烈建议)在 192.168.22.18 增加“内核/IO 错误计数”自定义指标

你要抓的是这种关键字:I/O error|timeout|reset|remount read-only|EXT4-fs error|BTRFS error|blk_update_request

做法:用 node_exporter 的 textfile collector 每分钟吐一个计数指标(避免你事后翻日志翻到吐)。

bash
cat >/usr/local/bin/collect_kernel_error_metrics.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
OUT="/var/lib/node_exporter/textfile_collector/kernel_errors.prom"
TMP="$(mktemp)"
# Count kernel messages in last 10 minutes (adjust if needed)
COUNT="$(journalctl -k --since "10 min ago" --no-pager 2>/dev/null | \
grep -Eic 'I/O error|timeout|reset|remount read-only|EXT4-fs error|BTRFS error|blk_update_request|nvme|ufs' || true)"
NOW="$(date +%s)"
{
echo "# HELP kernel_error_events_10m Number of kernel error-like events in last 10 minutes"
echo "# TYPE kernel_error_events_10m gauge"
echo "kernel_error_events_10m ${COUNT}"
echo "# HELP kernel_error_scrape_time_seconds Last collect time"
echo "# TYPE kernel_error_scrape_time_seconds gauge"
echo "kernel_error_scrape_time_seconds ${NOW}"
} >"${TMP}"
mv "${TMP}" "${OUT}"
EOF
chmod +x /usr/local/bin/collect_kernel_error_metrics.sh
cat >/etc/systemd/system/kernel-error-metrics.service <<'EOF'
[Unit]
Description=Collect kernel error metrics for node_exporter textfile collector
[Service]
Type=oneshot
ExecStart=/usr/local/bin/collect_kernel_error_metrics.sh
EOF
cat >/etc/systemd/system/kernel-error-metrics.timer <<'EOF'
[Unit]
Description=Run kernel error metrics collector every 60s
[Timer]
OnBootSec=30s
OnUnitActiveSec=60s
AccuracySec=1s
[Install]
WantedBy=timers.target
EOF
systemctl daemon-reload
systemctl enable --now kernel-error-metrics.timer
systemctl list-timers --no-pager | grep kernel-error-metrics || true

验证:

bash
curl -fsS http://192.168.22.18:9100/metrics | grep -E '^kernel_error_events_10m|^kernel_error_scrape_time_seconds' || true

D3.(可选)在 192.168.22.18 部署 smartctl_exporter(磁盘健康)

说明:如果你的“UFS 2.1”设备不支持 SMART/health,这一步可能拿不到有效数据;但做了不亏(能拿到就直接中大奖)。

bash
apt-get update
apt-get install -y smartmontools
export VER="0.12.0"
curl -fsSL -o /tmp/smartctl_exporter.tar.gz "https://github.com/prometheus-community/smartctl_exporter/releases/download/v${VER}/smartctl_exporter-${VER}.linux-amd64.tar.gz"
tar -C /tmp -xzf /tmp/smartctl_exporter.tar.gz
install -m 0755 /tmp/smartctl_exporter-${VER}.linux-amd64/smartctl_exporter /usr/local/bin/smartctl_exporter
useradd --system --no-create-home --shell /usr/sbin/nologin smartctl-exp || true
cat >/etc/systemd/system/smartctl_exporter.service <<'EOF'
[Unit]
Description=Prometheus Smartctl Exporter
After=network-online.target
Wants=network-online.target
[Service]
User=smartctl-exp
Group=smartctl-exp
ExecStart=/usr/local/bin/smartctl_exporter --web.listen-address=:9633
Restart=on-failure
RestartSec=2
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now smartctl_exporter

验证:

bash
curl -fsS http://192.168.22.18:9633/metrics | head

D4. 在 192.168.22.12(PVE)用 LXC 部署 Prometheus + Grafana + blackbox_exporter(推荐)

你说 .12 也是 PVE 主机,那就别在宿主机上堆 Docker 了:直接建一个 LXC 容器跑监控栈,数据/配置独立、可迁移、也更符合“排查期可控变更”的原则。

D4.1 在 PVE(.12)创建一个 Debian LXC 容器

你可以用 PVE Web UI 创建,也可以用命令行。下面是命令行示例(按你的环境替换 <CTID><GW>;监控 LXC IP 固定为 192.168.22.19):

bash
# 在 PVE 宿主机 192.168.22.12 执行
pct create <CTID> local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst \
--hostname monitoring \
--cores 2 --memory 2048 --swap 512 \
--rootfs local-lvm:8 \
--net0 name=eth0,bridge=vmbr0,ip=192.168.22.19/24,gw=<GW> \
--unprivileged 1
# 让容器内 systemd 工作更顺滑
pct set <CTID> -features keyctl=1,nesting=1
pct start <CTID>

这篇文章里我们把监控 LXC 的 IP 固定为:192.168.22.19

D4.2 在容器内安装 Prometheus(systemd)

进入容器:

bash
pct exec <CTID> -- bash

在容器内执行:

bash
apt-get update
apt-get install -y ca-certificates curl tar
useradd --system --no-create-home --shell /usr/sbin/nologin prometheus || true
mkdir -p /etc/prometheus /var/lib/prometheus
chown -R prometheus:prometheus /etc/prometheus /var/lib/prometheus
export VER="2.54.1"
curl -fsSL -o /tmp/prometheus.tar.gz "https://github.com/prometheus/prometheus/releases/download/v${VER}/prometheus-${VER}.linux-amd64.tar.gz"
tar -C /tmp -xzf /tmp/prometheus.tar.gz
install -m 0755 /tmp/prometheus-${VER}.linux-amd64/prometheus /usr/local/bin/prometheus
install -m 0755 /tmp/prometheus-${VER}.linux-amd64/promtool /usr/local/bin/promtool
cp -r /tmp/prometheus-${VER}.linux-amd64/consoles /etc/prometheus/
cp -r /tmp/prometheus-${VER}.linux-amd64/console_libraries /etc/prometheus/
chown -R prometheus:prometheus /etc/prometheus

创建 /etc/prometheus/prometheus.yml(抓 .18 的 exporter + 主动探测;DNS 用 .54 的 AdGuardDNS):

yaml
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: "minisforum-node"
static_configs:
- targets: ["192.168.22.18:9100"]
- job_name: "minisforum-smartctl"
static_configs:
- targets: ["192.168.22.18:9633"]
- job_name: "blackbox-icmp"
metrics_path: /probe
params:
module: [icmp]
static_configs:
- targets:
- 192.168.22.18
- 192.168.22.54
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: 127.0.0.1:9115
- job_name: "blackbox-tcp"
metrics_path: /probe
params:
module: [tcp_connect]
static_configs:
- targets:
- 192.168.22.18:22
- 192.168.22.18:9100
- 192.168.22.54:53
- 192.168.22.54:3000
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: 127.0.0.1:9115

创建 systemd 服务:

bash
cat >/etc/systemd/system/prometheus.service <<'EOF'
[Unit]
Description=Prometheus
After=network-online.target
Wants=network-online.target
[Service]
User=prometheus
Group=prometheus
ExecStart=/usr/local/bin/prometheus \
--config.file=/etc/prometheus/prometheus.yml \
--storage.tsdb.path=/var/lib/prometheus \
--storage.tsdb.retention.time=30d
Restart=on-failure
RestartSec=2
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now prometheus
systemctl status prometheus --no-pager
D4.3 在容器内安装 blackbox_exporter(systemd)
bash
useradd --system --no-create-home --shell /usr/sbin/nologin blackbox || true
mkdir -p /etc/blackbox_exporter
chown -R blackbox:blackbox /etc/blackbox_exporter
export VER="0.25.0"
curl -fsSL -o /tmp/blackbox.tar.gz "https://github.com/prometheus/blackbox_exporter/releases/download/v${VER}/blackbox_exporter-${VER}.linux-amd64.tar.gz"
tar -C /tmp -xzf /tmp/blackbox.tar.gz
install -m 0755 /tmp/blackbox_exporter-${VER}.linux-amd64/blackbox_exporter /usr/local/bin/blackbox_exporter
cat >/etc/blackbox_exporter/blackbox.yml <<'EOF'
modules:
icmp:
prober: icmp
timeout: 5s
tcp_connect:
prober: tcp
timeout: 5s
EOF
chown -R blackbox:blackbox /etc/blackbox_exporter
cat >/etc/systemd/system/blackbox_exporter.service <<'EOF'
[Unit]
Description=Prometheus Blackbox Exporter
After=network-online.target
Wants=network-online.target
[Service]
User=blackbox
Group=blackbox
ExecStart=/usr/local/bin/blackbox_exporter \
--config.file=/etc/blackbox_exporter/blackbox.yml \
--web.listen-address=:9115
Restart=on-failure
RestartSec=2
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now blackbox_exporter
systemctl status blackbox_exporter --no-pager
D4.4 在容器内安装 Grafana(systemd)

不要把 Grafana 的 admin 密码写进任何配置文件/仓库。建议安装完后手动设置。

bash
apt-get install -y apt-transport-https software-properties-common wget gpg
mkdir -p /etc/apt/keyrings
wget -qO- https://apt.grafana.com/gpg.key | gpg --dearmor >/etc/apt/keyrings/grafana.gpg
echo "deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main" >/etc/apt/sources.list.d/grafana.list
apt-get update
apt-get install -y grafana
systemctl enable --now grafana-server
systemctl status grafana-server --no-pager

设置 admin 密码(在容器内执行):

bash
grafana-cli admin reset-admin-password
D4.5 访问地址与最小验证
  • Prometheus:http://192.168.22.19:9090
  • Grafana:http://192.168.22.19:3000
  • blackbox_exporter:http://192.168.22.19:9115

最小验证:

bash
curl -fsS "http://192.168.22.19:9090/-/ready"
curl -fsS "http://192.168.22.19:9115/probe?module=icmp&target=192.168.22.18" | head

D5. Grafana 面板与关键观察点(对应 H1~H5)

你不需要“花里胡哨的大盘”。就盯下面这些就够了:

  • H1 过热/降频
    • CPU 温度(如果可见):node_thermal_zone_tempnode_hwmon_temp_celsius
    • CPU load/iowait:node_load1rate(node_cpu_seconds_total{mode="iowait"}[5m])
  • H2 供电/复位
    • uptime/重启点:node_boot_time_seconds(突然变化 = reboot)
    • blackbox 探测:probe_success{job=~"blackbox-.*"}
  • H3 存储/控制器异常
    • 关键:kernel_error_events_10m(你自己定义的)
    • IO time:rate(node_disk_io_time_seconds_total[5m])
    • 文件系统只读(如果发生):node_filesystem_readonly
  • H4 资源耗尽
    • 内存:node_memory_MemAvailable_bytes
    • FD(如果启用):node_filefd_allocated/node_filefd_maximum(视 exporter 版本)
  • H5 驱动/省电策略/网卡 reset
    • 网络错误:rate(node_network_receive_errs_total[5m])rate(node_network_transmit_errs_total[5m])
    • 同样看 kernel_error_events_10m 的变化点

D6. 告警规则(把时间戳钉死)

你至少要有三条告警(其余都是锦上添花):

  • 机器不可达(icmp probe 失败)
  • DNS 端口不可达(tcp probe 到 53 失败)
  • 内核/IO 错误突增kernel_error_events_10m 超过阈值)

告警接收渠道先别纠结,先让 Prometheus “能触发告警”这件事成立;否则你复现那一刻还是会错过。


尝试 E:在 192.168.22.18 用 LXC 部署 AdGuardDNS(IP 固定为 192.168.22.54)+ DNS 真实查询探测

目标:把 DNS 业务从宿主机里隔离出来,并且把 DNS 的 IP 固定到 192.168.22.54,这样监控/告警的 target 不会因为容器漂移而失效。

重点:排查“宿主机是否先死”,所以 nodeexporter / 自定义 IO 错误计数 / smartctlexporter 仍然部署在宿主 .18;DNS 服务放到容器里只是为了更可控地观测“业务侧”。

E0. 在 PVE(.18)创建 AdGuardDNS 的 LXC 容器(192.168.22.54)

示例(按你的环境替换 <CTID><GW>):

bash
# 在 PVE 宿主机 192.168.22.18 执行
pct create <CTID> local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst \
--hostname adguarddns \
--cores 1 --memory 1024 --swap 256 \
--rootfs local-lvm:4 \
--net0 name=eth0,bridge=vmbr0,ip=192.168.22.54/24,gw=<GW> \
--unprivileged 1
pct set <CTID> -features keyctl=1,nesting=1
pct start <CTID>

E1. 在容器内安装 AdGuard Home(作为 AdGuardDNS)

进入容器:

bash
pct exec <CTID> -- bash

在容器内执行(固定版本,排查期可追溯):

bash
apt-get update
apt-get install -y ca-certificates curl tar
export VER="0.107.57"
curl -fsSL -o /tmp/adguardhome.tar.gz "https://github.com/AdguardTeam/AdGuardHome/releases/download/v${VER}/AdGuardHome_linux_amd64.tar.gz"
tar -C /opt -xzf /tmp/adguardhome.tar.gz
/opt/AdGuardHome/AdGuardHome -s install
systemctl status AdGuardHome --no-pager

访问初始化页面(首次配置):

  • 管理口:http://192.168.22.54:3000
  • DNS:192.168.22.54:53(UDP/TCP)

你要把内网客户端 DNS 指向 192.168.22.54。如果你有 DHCP/路由器下发 DNS,就统一从那里改,别一个个设备手动改。

E1. DNS 真实查询探测(不靠 blackbox 的“端口可达”,直接用 dig 验证解析链路)

做法:在 192.168.22.19(监控端 LXC)定时执行 dig,把“成功/失败、延迟”推送到 Pushgateway。 这一步不依赖 AdGuard 管理口账号密码,可观测性更干净。

  1. 在监控端(.12 的 monitoring LXC)里可选安装 Pushgateway(systemd):
bash
apt-get update
apt-get install -y ca-certificates curl tar
useradd --system --no-create-home --shell /usr/sbin/nologin pushgateway || true
mkdir -p /etc/pushgateway /var/lib/pushgateway
chown -R pushgateway:pushgateway /etc/pushgateway /var/lib/pushgateway
export VER="1.10.0"
curl -fsSL -o /tmp/pushgateway.tar.gz "https://github.com/prometheus/pushgateway/releases/download/v${VER}/pushgateway-${VER}.linux-amd64.tar.gz"
tar -C /tmp -xzf /tmp/pushgateway.tar.gz
install -m 0755 /tmp/pushgateway-${VER}.linux-amd64/pushgateway /usr/local/bin/pushgateway
cat >/etc/systemd/system/pushgateway.service <<'EOF'
[Unit]
Description=Prometheus Pushgateway
After=network-online.target
Wants=network-online.target
[Service]
User=pushgateway
Group=pushgateway
ExecStart=/usr/local/bin/pushgateway --web.listen-address=:9091
Restart=on-failure
RestartSec=2
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now pushgateway
systemctl status pushgateway --no-pager
  1. Prometheus 增加一个 scrape(在同一台监控容器里就用 127.0.0.1):
yaml
- job_name: "pushgateway"
honor_labels: true
static_configs:
- targets: ["127.0.0.1:9091"]
  1. 在监控端新增探测脚本(把 <DNS_IP> 固定为 192.168.22.54):
bash
cat >/opt/monitoring/dns_probe_push.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
DNS_IP="192.168.22.54"
NAME="www.baidu.com"
PUSH_URL="http://127.0.0.1:9091/metrics/job/dns_probe/instance/${DNS_IP}"
OUT="$(dig +tries=1 +time=2 +stats @"${DNS_IP}" "${NAME}" A 2>/dev/null || true)"
# success=1 if we got an ANSWER section with at least one A record
if echo "${OUT}" | grep -qE '^;; ANSWER SECTION:'; then
SUCCESS=1
else
SUCCESS=0
fi
# Parse query time in ms from "Query time: 12 msec"
LAT_MS="$(echo "${OUT}" | awk -F': ' '/^;; Query time:/{print $2}' | awk '{print $1}' || true)"
LAT_MS="${LAT_MS:-0}"
cat <<METRICS | curl -fsS --data-binary @- "${PUSH_URL}" >/dev/null
# TYPE dns_probe_success gauge
dns_probe_success ${SUCCESS}
# TYPE dns_probe_latency_ms gauge
dns_probe_latency_ms ${LAT_MS}
METRICS
EOF
chmod +x /opt/monitoring/dns_probe_push.sh
  1. 用 cron/systemd timer 每 15s~60s 跑一次(排查期建议 15s):
bash
crontab -e
# 每分钟 4 次
*/1 * * * * /opt/monitoring/dns_probe_push.sh
*/1 * * * * sleep 15; /opt/monitoring/dns_probe_push.sh
*/1 * * * * sleep 30; /opt/monitoring/dns_probe_push.sh
*/1 * * * * sleep 45; /opt/monitoring/dns_probe_push.sh

这一步的意义:当你复现“DNS 不通”时,你能立刻看到是 解析延迟逐步升高、还是 直接成功率掉到 0,并且与宿主机 iowait/错误计数 对齐。

E2.(可选)AdGuard Home 业务指标(需要管理口凭据,务必不要进仓库)

如果你确实想看 AdGuard 自身的统计(blocked/allowed/qps/upstream latency),可以在 .12 上跑一个 AdGuard exporter,读取 AdGuard 的管理 API 再暴露成 Prometheus 指标。

关键原则:

  • 凭据只放在 .12 本机的 root-only 文件里(例如 /opt/monitoring/secrets/adguard.envchmod 600
  • 不要写进本文仓库、更不要写进 compose 文件

(这里不强制指定 exporter,你用哪个就以其文档为准;排查阶段我更看重 E1 的 DNS 真实探测 + 宿主机指标。)

验证标准(这套监控是否“抓得住问题”)

  • 连续运行:至少 7 天(你说通常 1 天+ 触发,7 天才有统计意义)
  • 当复现发生时必须能回答
    • 先断的是 icmp 还是 tcp/53
    • kernel_error_events_10m 是否在故障前 0~10 分钟上升?
    • iowait 是否先飙升(cpu iowait/disk io_time)?
    • node_boot_time_seconds 是否变化(是否实际重启)?
    • dns_probe_success/dns_probe_latency_ms 在故障前是“逐步变坏”还是“瞬断”?

Comments