跳至正文
小莱沃 253 篇手记
← 返回首页

技术

在 PVE LXC 部署 Beszel + Tailscale 私网监控平台

家里的 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.mountrun-lock.mounttmp.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/tun

nesting=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-certificates

Beszel 官方提供单文件安装脚本。比起直接执行 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

最终核对结果是:设备已登录,主机名为 beszelRunSSH=falseAdvertiseRoutes=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 connectedSSH 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 不应出现在公开文章中。

在同一 LXC 增加 OpenObserve

对于只有几台主机、每天日志量很小的家庭环境,OpenObserve 不必再占用一个独立 VM 或 LXC。这次直接与 Beszel Hub 共用容器:Beszel 继续处理轻量主机指标,OpenObserve 只收集关键系统与应用日志。

共用容器的主要风险是日志写满磁盘或 OpenObserve 占满内存后连带影响 Beszel。为此先创建 PVE 快照,把容器扩为 2 vCPU、4 GB 内存和 1 GB Swap,并为 OpenObserve 增加一个会随 PVE 备份的 32 GB 独立挂载盘:

pct snapshot <beszel-ctid> pre-openobserve \
  --description 'Before OpenObserve co-location'

pct shutdown <beszel-ctid> --timeout 60
pct set <beszel-ctid> \
  --cores 2 \
  --memory 4096 \
  --swap 1024 \
  --mp0 local-lvm:32,mp=/var/lib/openobserve,backup=1
pct start <beszel-ctid>

部署时 OpenObserve 的 latest 已经指向没有二进制资产的 v1.0.0-rc1,因此没有盲目追随 latest,而是固定安装当时的 OSS 稳定版 v0.92.2

curl -fL --connect-timeout 15 --retry 3 \
  https://downloads.openobserve.ai/releases/openobserve/v0.92.2/openobserve-v0.92.2-linux-amd64.tar.gz \
  -o /tmp/openobserve-v0.92.2-linux-amd64.tar.gz

echo '2b9d35034a6810a6a2043447055cfa493f9302c0402f5a83728efc9f848b68a9  /tmp/openobserve-v0.92.2-linux-amd64.tar.gz' \
  | sha256sum -c -

tar -tzf /tmp/openobserve-v0.92.2-linux-amd64.tar.gz

本次压缩包中只有一个 openobserve 文件。创建专用的不可登录用户,把程序与数据目录分开:

useradd --system \
  --home-dir /var/lib/openobserve \
  --shell /usr/sbin/nologin \
  openobserve

install -d -o root -g root -m 0755 /opt/openobserve /etc/openobserve
install -d -o openobserve -g openobserve -m 0750 /var/lib/openobserve/data
tar -xzf /tmp/openobserve-v0.92.2-linux-amd64.tar.gz -C /opt/openobserve
chown root:root /opt/openobserve/openobserve
chmod 0755 /opt/openobserve/openobserve

配置文件 /etc/openobserve/openobserve.env 权限设为 600。管理员密码应随机生成,不能复制下面的占位符:

ZO_ROOT_USER_EMAIL=<admin-email>
ZO_ROOT_USER_PASSWORD=<long-random-password>
ZO_DATA_DIR=/var/lib/openobserve/data
ZO_LOCAL_MODE=true
ZO_LOCAL_MODE_STORAGE=disk
ZO_HTTP_PORT=5080
ZO_HTTP_ADDR=127.0.0.1
ZO_WEB_URL=https://beszel.<tailnet-name>.ts.net:8443
ZO_COOKIE_SECURE_ONLY=true
ZO_COMPACT_DATA_RETENTION_DAYS=7
ZO_MEMORY_CACHE_MAX_SIZE=256
ZO_MEMORY_CACHE_DATAFUSION_MAX_SIZE=512
ZO_DISK_CACHE_MAX_SIZE=2048
ZO_TELEMETRY=false
RUST_LOG=info

OpenObserve 由 systemd 管理,并用 cgroup 限制最多 2 GB 内存和 1.5 核 CPU,防止日志查询或压缩任务拖垮同容器内的 Beszel:

[Service]
User=openobserve
Group=openobserve
WorkingDirectory=/var/lib/openobserve
EnvironmentFile=/etc/openobserve/openobserve.env
ExecStart=/opt/openobserve/openobserve
Restart=on-failure
RestartSec=5
LimitNOFILE=65535
MemoryMax=2G
CPUQuota=150%
NoNewPrivileges=true
PrivateTmp=true
ProtectHome=true
ProtectSystem=strict
ReadWritePaths=/var/lib/openobserve
systemctl daemon-reload
systemctl enable --now openobserve
curl -fsS http://127.0.0.1:5080/healthz
systemctl status openobserve --no-pager

Beszel 已经占用 Tailscale Serve 的默认 HTTPS 443,因此给 OpenObserve 增加第二个、仍然只在 Tailnet 内可见的 HTTPS 8443 入口:

tailscale serve --bg --yes \
  --https=8443 \
  http://127.0.0.1:5080

tailscale serve status
curl -fsS https://beszel.<tailnet-name>.ts.net:8443/healthz

最终 OpenObserve v0.92.2 为 active、enabled,健康检查返回 {"status":"ok"},启动后的空载内存约 95 MB。端口 5080 只监听 127.0.0.1;Tailscale Serve 显示 443 仍代理 Beszel 8090,8443 代理 OpenObserve 5080,两者均为 tailnet only

用 Vector 接入四台服务器的 journald 日志

OpenObserve 服务正常后,再把 PVE、家庭 Ubuntu、Tailscale 路由 LXC 和 ARM64 云主机的系统日志接入同一个 infra 流。每条记录额外写入 node 字段用于区分来源。OpenObserve 所在 LXC 暂不采集自己的 journald,避免错误时形成“OpenObserve 日志进入 OpenObserve”的自摄取循环。

本次使用 Vector 0.58.0:有 root 或免密 sudo 的三台节点使用官方 APT 包和 systemd;家庭 Ubuntu 没有免密 sudo,但当前账号已经能使用 Docker,因此运行官方 GHCR 的 Debian 镜像。Docker 组权限接近 root,只适用于已经明确授权的管理账号。

在 OpenObserve 的摄取页面取得当前组织的摄取用户名和 Token。采集器只保存摄取凭据,不保存管理员登录密码。将下面内容写入 /etc/vector/openobserve.env,权限必须为 600;公开文章中的主机名和凭据都只是占位符:

VECTOR_DANGEROUSLY_ALLOW_ENV_VAR_INTERPOLATION=true
VECTOR_NODE_NAME=<node-name>
OPENOBSERVE_INGEST_URI=https://beszel.<tailnet-name>.ts.net:8443/api/default/infra/_json
OPENOBSERVE_HEALTH_URI=https://beszel.<tailnet-name>.ts.net:8443/healthz
OPENOBSERVE_INGEST_USER=<ingestion-user>
OPENOBSERVE_INGEST_TOKEN=<ingestion-token>

Vector 0.58 默认禁止配置文件中的环境变量插值,因此需要第一行开关。因为插值内容包含 Token,环境文件只允许 root 读取;通用的 vector.yaml 本身不包含秘密:

data_dir: /var/lib/vector

sources:
  journald:
    type: journald
    since_now: true
    exclude_units:
      - vector.service

transforms:
  add_node_metadata:
    type: remap
    inputs: [journald]
    source: |
      .node = "${VECTOR_NODE_NAME}"
      .environment = "homelab"
      .collector = "vector"

sinks:
  openobserve:
    type: http
    inputs: [add_node_metadata]
    uri: "${OPENOBSERVE_INGEST_URI}"
    method: post
    auth:
      strategy: basic
      user: "${OPENOBSERVE_INGEST_USER}"
      password: "${OPENOBSERVE_INGEST_TOKEN}"
    compression: gzip
    encoding:
      codec: json
      timestamp_format: rfc3339
    healthcheck:
      enabled: true
      uri: "${OPENOBSERVE_HEALTH_URI}"
    batch:
      max_bytes: 1048576
      timeout_secs: 5

since_now: true 防止第一次启动时把全部历史 journal 灌进来;排除 vector.service 则可以避免采集器把自己的发送日志再次发送。Vector 会在 /var/lib/vector 保存读取检查点,重启后从上次位置继续。

Debian、Ubuntu 节点先把官方仓库脚本下载到本地、审阅后执行,再锁定安装同一版本。仓库脚本会配置 apt.vector.dev 及其签名密钥:

curl --proto '=https' --tlsv1.2 -fsSL \
  https://setup.vector.dev \
  -o /tmp/setup-vector.sh

sha256sum /tmp/setup-vector.sh
less /tmp/setup-vector.sh
sudo bash /tmp/setup-vector.sh
sudo apt-get install -y vector=0.58.0-1

sudo install -m 0644 vector.yaml /etc/vector/vector.yaml
sudo usermod -aG systemd-journal vector
sudo install -d -m 0755 /etc/systemd/system/vector.service.d
printf '%s\n' \
  '[Service]' \
  'EnvironmentFile=/etc/vector/openobserve.env' \
  | sudo tee /etc/systemd/system/vector.service.d/openobserve.conf

sudo systemctl daemon-reload
sudo systemctl enable --now vector
sudo systemctl is-active vector
sudo journalctl -u vector -n 30 --no-pager

家庭 Ubuntu 使用带 journalctl 的 Debian 镜像,不使用 distroless 镜像。宿主机 journal、machine-id 和 Vector 配置均只读挂载,只有 Vector 数据卷可写,并把容器限制在 256 MB 内存和 0.5 核 CPU:

docker pull ghcr.io/vectordotdev/vector:0.58.0-debian

docker run -d \
  --name vector-openobserve \
  --restart unless-stopped \
  --memory 256m \
  --cpus 0.50 \
  --env-file "$HOME/vector-openobserve/openobserve.env" \
  -v "$HOME/vector-openobserve/vector.yaml:/etc/vector/vector.yaml:ro" \
  -v /var/log/journal:/var/log/journal:ro \
  -v /run/log/journal:/run/log/journal:ro \
  -v /etc/machine-id:/etc/machine-id:ro \
  -v vector-openobserve-data:/var/lib/vector \
  ghcr.io/vectordotdev/vector:0.58.0-debian

如果宿主机能解析 MagicDNS、Docker bridge 内却不能解析,可为容器增加一条精确映射,而不是改用不校验证书的 HTTP:

--add-host beszel.<tailnet-name>.ts.net:<openobserve-tailscale-ip>

PVE 宿主机本身没有安装 Tailscale。为了继续使用 Tailnet HTTPS,而不是把 OpenObserve 的 5080 暴露到整个局域网,本次让 PVE 仅把 OpenObserve 的一个 /32 地址交给现有 ts-router,并在 ts-router 上仅对“PVE 源地址 + OpenObserve 目标地址”做 SNAT:

# PVE:只路由 OpenObserve 这一个 Tailscale 地址
ip route replace <openobserve-tailscale-ip>/32 \
  via <ts-router-lan-ip> dev <pve-lan-bridge>

# PVE 与 ts-router:为 HTTPS 证书保留真实 Tailnet 主机名
echo '<openobserve-tailscale-ip> beszel.<tailnet-name>.ts.net' \
  >> /etc/hosts

# ts-router:只转换 PVE 到 OpenObserve 的这一条路径
iptables -t nat -I POSTROUTING 1 \
  -s <pve-lan-ip>/32 \
  -d <openobserve-tailscale-ip>/32 \
  -o tailscale0 \
  -j MASQUERADE

路由和 NAT 规则都应写成 Type=oneshotRemainAfterExit=yes 的 systemd 服务并启用开机启动;不要只保留临时命令。规则必须同时限制源和目标,不能把 ts-router 变成整个局域网任意访问 Tailnet 的网关。

最后不要只检查进程状态。四台机器分别写入带同一标记的测试日志:

logger -t openobserve-onboarding \
  'openobserve-verification node=<node-name>'

在 OpenObserve 的 infra 流查询:

SELECT node, message, _timestamp
FROM "infra"
WHERE message LIKE '%openobserve-verification%'
ORDER BY _timestamp DESC

本次端到端验证一次返回 4 条记录,node 分别为 PVE、家庭 Ubuntu、ts-router 和云主机。三个 systemd Agent 均为 active、enabled,Docker Agent 的 healthcheck 也通过,说明 journald、Vector、Tailscale HTTPS 和 OpenObserve 摄取接口整条链路正常。

保存用户登录日志查询

常用查询可以保存成 OpenObserve Saved View。下面的 SQL 同时覆盖 Debian、Ubuntu 上可能出现的 sshdsshd-session 标识,并显示公钥/密码登录成功、失败、无效用户名以及会话开关记录:

SELECT _timestamp, node, host, syslog_identifier, message
FROM "infra"
WHERE syslog_identifier IN ('sshd', 'sshd-session')
  AND (
    LOWER(message) LIKE 'accepted publickey%'
    OR LOWER(message) LIKE 'accepted password%'
    OR LOWER(message) LIKE 'failed password%'
    OR LOWER(message) LIKE 'invalid user %'
    OR LOWER(message) LIKE '%authentication failure%'
    OR LOWER(message) LIKE '%session opened for user%'
    OR LOWER(message) LIKE '%session closed for user%'
  )
ORDER BY _timestamp DESC

在日志页选择 infra,打开 SQL 模式,把时间范围设为最近 24 小时并运行查询;然后进入“更多 → 创建保存的视图”,名称填写 SSH Login Logs。Saved View 保存的是数据流、SQL、时间范围和显示配置,不会额外复制一份日志。以后从“更多 → 列出保存的视图”直接打开即可。

一次真实的 Tailscale 路由故障恢复

部署过程中,原有 Tailscale 路由容器的数据面卡住,Mac 和我都无法通过原路径连接家庭内网。此时不能在同一条已断的隧道上反复 SSH。我的备用路径是一台仍在线的异地云主机:

  1. 在备用节点临时接受现有子网路由。
  2. 以它作为 SSH Jump Host 进入 PVE 的局域网地址。
  3. systemd-run 延迟重启路由容器,让重启命令不依赖当前 SSH 会话继续存活。
  4. 连接恢复后,立即关闭备用节点的 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 握手版本一致。
  • OpenObserve 使用独立 32 GB 数据挂载盘,保留期为 7 天,并受 2 GB 内存和 1.5 核 CPU 限制。
  • OpenObserve 5080 只监听本机,Tailnet HTTPS 8443 健康检查返回 {"status":"ok"}
  • 四台服务器都将 journald 发送到 infra 流,测试标记查询可按 node 区分全部来源。
  • 采集端只保存权限为 600 的摄取 Token,不保存 OpenObserve 管理员密码。
  • PVE 到 OpenObserve 的路由与 SNAT 只匹配单一源和单一目标,没有开放通用 LAN 到 Tailnet 转发。
  • PVE 没有新增公网端口映射。
  • 定期备份 /opt/beszel/beszel_data,并把 Beszel LXC 纳入 PVE 备份计划。

参考资料

发表评论