家里的 PVE 已经通过一个独立的 Tailscale 子网路由 LXC 接入 Tailnet。接下来要解决的问题,是把 PVE、虚拟机、LXC 和其他异地服务器的 CPU、内存、磁盘、网络及容器状态集中到一个长期在线的监控面板里。本文中的主机名、内网地址和容器编号均已改成占位符,避免公开家庭网络拓扑。
这次选择 Beszel 作为监控面板,并把 Beszel Hub 放进 PVE 的独立 Debian LXC。Tailscale 负责私网连接,Beszel 负责指标、历史图表和告警,整个面板不需要开放公网端口。
最终架构
Mac / 手机 / 其他管理端
|
| Tailscale
v
PVE LXC:beszel
|
| Beszel Hub :8090
|
+-- PVE / Linux / macOS Agent
+-- Docker / Podman 容器指标
+-- CPU / 内存 / 磁盘 / 网络 / 告警Beszel 由 Hub 和 Agent 两部分组成。Hub 保存配置和历史数据并提供 Web 界面,Agent 安装在每台需要监控的机器上。Hub 与 Agent 之间走 Tailscale 或家庭内网,不暴露到公网。
为什么使用独立 LXC
不建议把 Beszel 直接安装在 PVE 宿主机,也不建议和现有的 Tailscale 子网路由容器混装。独立 LXC 有几个好处:
- PVE 升级、监控服务升级和子网路由故障互不影响。
- 可以单独备份、快照和恢复 Beszel 数据。
- Beszel Hub 是 Go 单文件程序,不需要为了运行它在 LXC 里再套一层 Docker。
- 资源占用很低,1 vCPU、1 GB 内存和 8 GB 磁盘已经足够家庭环境使用。
部署前检查
先登录 PVE,确认容器 ID、存储、内存和网桥,避免与现有资源冲突:
ssh root@<pve-lan-ip>
pveversion
pct list
qm list
pvesm status
pvesh get /nodes/<pve-node>/status --output-format json
ip -brief address show vmbr0本次检查到的实际环境如下:
- PVE 版本:9.2.9。
- 现有 LXC、VM 与计划使用的容器编号没有冲突。
local-lvm的剩余空间足够创建 8 GB 根磁盘。- 可用内存满足 1 GB 内存和 512 MB Swap 的配置。
- 网桥为
vmbr0,静态地址没有占用且已避开 DHCP 地址池。
LXC 参数
这次使用下面的配置:
VMID: <unused-ctid>
Hostname: beszel
OS: Debian 13
CPU: 1 vCPU
Memory: 1024 MB
Swap: 512 MB
Disk: 8 GB / local-lvm
Network: vmbr0
IPv4: <beszel-lan-ip>/<cidr>
Gateway: <lan-gateway>
Container: unprivileged
On boot: yes地址使用前应检查 DHCP 静态租约和现有设备,不能只依赖一次 Ping 判断地址一定空闲。
下载 Debian 13 模板
先查看 PVE 当前提供的 Debian 模板:
pveam available --section system | grep debian-13本次使用的是:
debian-13-standard_13.6-1_amd64.tar.zst正常情况下直接下载到 local 存储:
pveam download local debian-13-standard_13.6-1_amd64.tar.zst如果 PVE 到 Proxmox 下载源速度很慢,可以从另一台网络较快的机器下载同一个官方文件,再上传到 /var/lib/vz/template/cache/。上传完成后必须比较两端 SHA-256,校验通过后再创建容器。不要把未完成的临时文件当作模板使用。
创建 Beszel LXC
下面的命令在 PVE 宿主机执行。先创建非特权容器,再把 TUN 设备映射进去,供 Tailscale 使用:
pct create <unused-ctid> local:vztmpl/debian-13-standard_13.6-1_amd64.tar.zst \
--hostname beszel \
--ostype debian \
--arch amd64 \
--cores 1 \
--memory 1024 \
--swap 512 \
--rootfs local-lvm:8 \
--net0 name=eth0,bridge=vmbr0,firewall=1,ip=<beszel-lan-ip>/<cidr>,gw=<lan-gateway> \
--nameserver <dns-server> \
--unprivileged 1 \
--onboot 1
pct set <unused-ctid> --dev0 path=/dev/net/tun
pct start <unused-ctid>Debian 13 使用 systemd 257。本次第一次启动后,dev-mqueue.mount、run-lock.mount 和 tmp.mount 失败,系统状态为 degraded。PVE 创建容器时也给出了 nesting 相关提示。停机后开启 nesting 再启动,失败单元清零:
pct stop <unused-ctid>
pct set <unused-ctid> --features nesting=1
pct start <unused-ctid>
pct exec <unused-ctid> -- systemctl is-system-running
pct exec <unused-ctid> -- systemctl --failed
pct exec <unused-ctid> -- ip -brief address
pct exec <unused-ctid> -- test -c /dev/net/tunnesting=1 会放宽一部分容器隔离限制,不应把它当成所有 LXC 的默认配置;这里只对出现 systemd 挂载失败的 Beszel 容器启用。
下载慢时的模板处理
这次 PVE 直接连接下载源非常慢,从 Mac 经不稳定的 Tailscale 上传又留下了一个只有 1,044,480 字节的残缺文件。最终做法是删除这个明确确认过的残缺文件,让 PVE 从清华镜像下载到临时文件,校验 SHA-256 后再原子改名:
cd /var/lib/vz/template/cache
curl -fL \
https://mirrors.tuna.tsinghua.edu.cn/proxmox/images/system/debian-13-standard_13.6-1_amd64.tar.zst \
-o debian-13-standard_13.6-1_amd64.tar.zst.download
echo '1a5e43d088b430a1fca0531a2283ae2949ff0b40d383d0836d4a309e192b3244 debian-13-standard_13.6-1_amd64.tar.zst.download' \
| sha256sum -c -
mv debian-13-standard_13.6-1_amd64.tar.zst.download \
debian-13-standard_13.6-1_amd64.tar.zst关键点不是使用哪一个镜像,而是始终先写入 .download,校验通过后才改成模板文件名。这样即使 SSH 或网络中断,PVE 也不会误用半个模板。
安装 Beszel Hub
进入容器,安装基础工具:
pct enter <unused-ctid>
export LC_ALL=C
apt update
apt install -y curl ca-certificatesBeszel 官方提供单文件安装脚本。比起直接执行 curl | sh,我习惯先下载并检查脚本,再运行:
curl -sL https://get.beszel.dev/hub -o /tmp/install-hub.sh
chmod +x /tmp/install-hub.sh
less /tmp/install-hub.sh
# GitHub 下载慢时使用安装器支持的镜像参数
/tmp/install-hub.sh -c https://ghfast.top/ --auto-update本次安装得到 Beszel 0.18.8。程序位于 /opt/beszel/beszel,数据目录是 /opt/beszel/beszel_data,服务以专用的 beszel 用户运行。安装器还启用了每日更新定时器。
systemctl status beszel-hub --no-pager
systemctl status beszel-hub-update.timer --no-pager
/opt/beszel/beszel health --url http://127.0.0.1:8090
ss -lntp | grep 8090实测健康检查返回 ok,局域网访问 http://<beszel-lan-ip>:8090/ 返回 HTTP 200。
第一次安装时还遇到一个容易忽略的问题:GitHub 下载很慢,旧的安装器进程没有及时退出。重新运行安装器前应先用 ps 核对进程命令和临时目录,只结束已确认属于旧安装器的 PID,并删除它对应的临时目录,不能用宽泛的 pkill 或通配符删除。
在 LXC 内安装 Tailscale
TUN 设备已经由 PVE 映射进容器。仍然先下载并检查官方脚本:
curl -fsSL https://tailscale.com/install.sh -o /tmp/install-tailscale.sh
less /tmp/install-tailscale.sh
sh /tmp/install-tailscale.sh
tailscale up --hostname=beszel --accept-dns=false命令会输出一次性登录地址。在浏览器中确认目标设备名为 beszel,选择正确的 Tailnet,再点击 Connect。本次安装的 Tailscale 版本是 1.102.3。
这里刻意没有增加 --ssh、--advertise-routes 或 --advertise-exit-node。这个 LXC 的职责只是运行监控面板,不承担远程 SSH、子网路由或出口节点职责,权限应保持最小化。
tailscale status --self
tailscale ip -4
tailscale debug prefs最终核对结果是:设备已登录,主机名为 beszel,RunSSH=false,AdvertiseRoutes=null,也没有配置 Exit Node。
从 Tailnet 访问 Beszel
如果 Tailnet 已启用 MagicDNS,可直接访问:
http://beszel:8090/也可以使用容器分配到的 Tailscale IP:
http://<tailscale-ip>:8090/本次从 Mac 验证:
tailscale ping beszel
curl -I http://<tailscale-ip>:8090/Ping 成功,Hub 返回 HTTP 200。当前链路经 DERP 中继而不是直连,这不会影响管理面板使用,但延迟会高于同一局域网或 UDP 直连。
用 Tailscale Serve 提供 Tailnet HTTPS
直接访问 http://<tailscale-ip>:8090/ 已经只在 Tailnet 内可达,但浏览器仍会显示 HTTP。可以让 Tailscale Serve 为 Hub 提供自动 HTTPS 入口:
tailscale serve --bg 8090首次运行可能会打开 Tailscale 管理确认页。本次确认页默认同时勾选了 Funnel,因此先取消 Funnel,只启用 HTTPS。Funnel 会把服务发布到公网,不符合本次私网监控的目标。
完成后,Beszel 可以通过下面的地址访问:
https://beszel.<tailnet-name>.ts.net/最后检查 Serve 状态和 HTTPS 响应:
tailscale serve status
curl -I https://beszel.<tailnet-name>.ts.net/实测 HTTPS 返回 HTTP 200,tailscale serve status 显示 tailnet only,请求被反向代理到 http://127.0.0.1:8090。这表示只有 Tailnet 成员能访问,没有启用 Funnel 公网发布。
首次登录与在 PVE 添加二进制 Agent
打开 Hub 后先创建管理员账号。密码不要写进命令、截图或博客。然后点击右上角的 Add System,在要监控的 PVE、Linux、macOS 或 Docker 主机上安装 Agent。
PVE 宿主机这里选择二进制,不选 Docker。二进制 Agent 直接作为 systemd 服务运行,能采集宿主机本身的 CPU、内存、磁盘和网络数据;为了装一个轻量 Agent 而额外在 PVE 宿主机引入 Docker 没有必要。Hub 继续留在独立 LXC,不与 Agent 混装。
先在 PVE 宿主机取得 Hub 的 SSH 公钥。下面的命令只从 Hub 私钥推导公钥,不读取或输出私钥内容:
HUB_PUBLIC_KEY="$(pct exec <beszel-ctid> -- \
ssh-keygen -y -f /opt/beszel/beszel_data/id_ed25519)"这次固定安装与 Hub 相同的 Beszel 0.18.8。官方安装脚本先下载到本地并核对本次审阅过的 SHA-256,再执行:
curl -fsSL \
https://raw.githubusercontent.com/henrygd/beszel/v0.18.8/supplemental/scripts/install-agent.sh \
-o /tmp/install-agent.sh
echo '1045acdf1fa29ff675a8d54218343241a987401e5572ae2beba557ec8c0463d4 /tmp/install-agent.sh' \
| sha256sum -c -
chmod +x /tmp/install-agent.sh
/tmp/install-agent.sh \
-k "$HUB_PUBLIC_KEY" \
-p 45876 \
-v 0.18.8 \
--auto-update true \
--mirror https://ghfast.top
unset HUB_PUBLIC_KEY如果以后安装不同版本,不能继续照抄上面的 SHA-256;应重新下载、审阅并校验对应版本的脚本。
回到 Hub 的 Add System 页面,选择 Binary,然后填写:
- Name:
PVE,或自定义一个便于识别的名称。 - Host / IP:
<pve-lan-ip>,即 Beszel LXC 能直达的宿主机地址。 - Port:
45876。 - Public Key / Token:保留页面为当前系统生成的值,不把它们复制到公开文章。
点击 Add System 后,分别在 PVE 宿主机和 Hub 侧核验:
# PVE 宿主机
systemctl is-active beszel-agent
systemctl is-enabled beszel-agent
systemctl is-enabled beszel-agent-update.timer
ss -lntp | grep 45876
journalctl -u beszel-agent -n 50 --no-pager
# Beszel Hub LXC
systemctl is-active beszel-hub
journalctl -u beszel-hub -n 50 --no-pager本次实测 Agent 为 active、enabled,自动更新 timer 已启用,端口 45876 正常监听。点击 Add System 后,Agent 日志出现 SSH connected 和 SSH connection established,证明 Hub 已经连上 PVE Agent。部分裸盘随后出现 no valid SMART data found,这表示对应磁盘没有返回可用 SMART 数据,不等同于 Agent 离线;基础 CPU、内存、磁盘容量和网络指标仍可正常采集。
继续添加其他 Tailnet Linux 节点
PVE 验证正常后,我又把一台家庭 Ubuntu、一台 Tailscale 路由 LXC 和一台 ARM64 云主机纳入监控。三台机器都已经加入同一个 Tailnet,因此 Agent 不需要监听所有网卡,只绑定各自的 Tailscale 地址:
<node-tailscale-ip>:45876有 root 或免密 sudo 的 Debian、Ubuntu 节点继续使用前文的二进制安装脚本,只需要把 -p 改成对应的 Tailscale 地址。安装器会自动选择 AMD64 或 ARM64 程序:
/tmp/install-agent.sh \
-k "$HUB_PUBLIC_KEY" \
-p <node-tailscale-ip>:45876 \
-v 0.18.8 \
--auto-update true \
--mirror https://ghfast.top家庭 Ubuntu 的 SSH 账号需要交互式 sudo 密码,但账号本身已经在 docker 组中,因此这台机器改用官方 Docker Agent。Docker Hub 当时无法路由,改从 Beszel 官方 GHCR 地址拉取同一版本:
docker pull ghcr.io/henrygd/beszel/beszel-agent:0.18.8
docker run -d \
--name beszel-agent \
--network host \
--restart unless-stopped \
-v beszel-agent-data:/var/lib/beszel-agent \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
-e "KEY=$HUB_PUBLIC_KEY" \
-e LISTEN=<node-tailscale-ip>:45876 \
ghcr.io/henrygd/beszel/beszel-agent:0.18.8--network host 用于采集宿主机网络接口统计;docker.sock 只读挂载用于采集 Docker 容器指标。Agent 只配置 SSH 公钥时,日志可能提示没有设置 HUB_URL,这是因为未启用 Agent 主动连接 Hub 的 WebSocket 模式,不影响 Hub 主动连接 Agent 的 SSH 模式。
每台机器安装后,都从 Hub LXC 主动连接其 Tailscale 地址验证 SSH 握手。三台返回的版本横幅均为:
SSH-2.0-beszel_0.18.8最后在 Hub 中分别添加系统,Host / IP 使用对应的 Tailscale 地址,端口统一填写 45876。真实节点名称、地址、公钥和 Token 不应出现在公开文章中。
一次真实的 Tailscale 路由故障恢复
部署过程中,原有 Tailscale 路由容器的数据面卡住,Mac 和我都无法通过原路径连接家庭内网。此时不能在同一条已断的隧道上反复 SSH。我的备用路径是一台仍在线的异地云主机:
- 在备用节点临时接受现有子网路由。
- 以它作为 SSH Jump Host 进入 PVE 的局域网地址。
- 用
systemd-run延迟重启路由容器,让重启命令不依赖当前 SSH 会话继续存活。 - 连接恢复后,立即关闭备用节点的
accept-routes。
# 备用节点
sudo tailscale set --accept-routes=true
# 从管理端经备用节点进入 PVE
ssh -J <jump-host> root@<pve-lan-ip>
# 在 PVE 上延迟 3 秒重启路由容器
systemd-run \
--unit=restart-router-lxc \
--on-active=3s \
/usr/sbin/pct reboot <router-ctid>
# 路由恢复并验证后,在备用节点撤销临时设置
sudo tailscale set --accept-routes=false恢复后应验证路由容器为 running、tailscaled 为 active、局域网子网路由重新可达,并再次检查备用节点的 RouteAll=false。这类临时改路由的操作要有明确的回收步骤,否则故障恢复本身会留下新的流量路径。
最终验收清单
- Beszel LXC 为 running,开机自启,系统没有 failed unit。
/dev/net/tun在非特权 LXC 内可用。beszel-hub与自动更新 timer 已启用。- Hub 本机健康检查为
ok,LAN、Tailscale HTTP 与 Tailnet HTTPS 访问均为 HTTP 200。 - Tailscale Serve 状态为
tailnet only,没有启用 Funnel 公网发布。 - Tailscale 设备名为
beszel,没有开启 SSH、子网路由或 Exit Node。 - PVE 上的
beszel-agent与更新 timer 已启用,Hub 日志确认 SSH 连接建立。 - 其他 Tailnet 节点的 Agent 只监听各自的 Tailscale 地址,Hub 侧 SSH 握手版本一致。
- PVE 没有新增公网端口映射。
- 定期备份
/opt/beszel/beszel_data,并把 Beszel LXC 纳入 PVE 备份计划。