~/ziphub — zsh
ZipHub·~/catalog~/articles~/feed~/sellers~/hire
auth login
[article]category · network

如何从五个 VPS 迁移到一个带有 Proxmox 的独立服务器,并停止为托管商付费

@dignezzz · author17 min read2026-05-05free

TL;DR: TL;DR:将三个托管商处的五个 VPS 组成的碎片化基础设施 → 合并到一台 Ryzen 7 9700X / 64GB ECC / 2× NVMe ZFS-mirror 的独立服务器上,运行 Proxmox VE。内部按角色划分 VM/LXC,使用 NetBird 网状网络代替端口转发,将出口节点放在单独的 VPS 上以绕过封锁和进行 TLS 终止,并使用 PBS 进行备份。资金、控制和精力都得到了提升。本文将详细介绍原因、方法以及我在过程中遇到的坑,以便您避免重蹈覆辙。

如何从五个 VPS 迁移到一个带有 Proxmox 的独立服务器,并停止为托管商付费

TL;DR:将三个托管商处的五个 VPS 组成的碎片化基础设施 → 合并到一台 Ryzen 7 9700X / 64GB ECC / 2× NVMe ZFS-mirror 的独立服务器上,运行 Proxmox VE。内部按角色划分 VM/LXC,使用 NetBird 网状网络代替端口转发,将出口节点放在单独的 VPS 上以绕过封锁和进行 TLS 终止,并使用 PBS 进行备份。资金、控制和精力都得到了提升。本文将详细介绍原因、方法以及我在过程中遇到的坑,以便您避免重蹈覆辙。

我为什么一开始就要这么做

去年年底,我发现自己为三个托管商提供的 五个不同的 VPS 付费。一个用于 Discourse 论坛,第二个用于基于 PHP 和 MySQL 的遗留论坛,第三个用于计费和 Telegram 机器人,第四个用于 VPN/代理,第五个用于静态文件和各种小工具。此外,n8n、Vaultwarden 和几个互相监控的 Uptime Kuma 也在那里运行(还能怎样呢)。

存在的问题:

  • 费用。 总计约等于一台具有相同配置的优质独立服务器的价格(但那里是共享的,这里是独立的)。
  • 没有任何隔离。 如果一个客户的机器人开始耗尽内存,我的论坛就会开始卡顿。还在同一个服务器上。太棒了。
  • 备份 — 麻烦。 每个 VPS 都有自己的 rsync-cron 定时任务发送到 S3,备份保留策略各不相同,没有增量备份,恢复起来简直是一场冒险。
  • 网络乱成一锅粥。 通过 Cloudflare Tunnels 转发的 SSH 端口,通过手动在两台 VPS 之间建立的 WireGuard,配置分散在三个地方。每次添加新机器时,集成都要花上一天时间。
  • 更新的灾难。 在五台不同的 Ubuntu/Debian 上,版本和软件包各不相同,升级必须手动进行,而且不能同时进行。每半年我都会英勇地完成一次“更新一切,不破坏任何东西”的任务。
  • CPU 和 RAM 闲置。 每个 VPS 都为“峰值”预留了资源,但平均使用率仅为 15-20%。钱白白浪费了。

总而言之,这是一个经典案例,基础设施的增长遵循“哦,还需要另一个服务 — 买个 VPS”的原则。是时候进行整合了。

我选择了什么以及为什么

硬件

我选择了一台 独立服务器

  • CPU: AMD Ryzen 7 9700X (8 核 / 16 线程, Zen 5)
  • RAM: 64 GB DDR5 ECC 5200 MHz
  • 磁盘: 2× NVMe 512 GB enterprise → ZFS mirror
  • 网络: 1 Gbps unmetered
  • 价格: 大约是同一家 OVH 两台中等 VPS 的价格

虚拟化平台

Proxmox VE 9.x。 我也考虑过其他替代方案,但简而言之:

  • 纯 Docker — 没有隔离,没有正常的快照,只能通过 compose 方式备份,磁盘是一个统一的垃圾场。
  • VMware ESXi — 不再免费,Broadcom 的许可政策很精彩,谢谢。
  • XCP-ng — 还行,但我个人非常熟悉 Proxmox,而且社区更活跃。
  • Kubernetes — 不,我一个人,没必要造一架飞机去趟趟商店。

Proxmox 提供了我需要的一切:KVM 虚拟机,用于需要隔离的所有场景(Docker、特定内核、不同发行版);LXC 容器,用于节省资源(数据库、监控、小型服务);内置 PBS 进行备份;开箱即用的 ZFS;快照;实时迁移(如果将来有第二台独立服务器);以及一个可以通过鼠标操作的 Web 界面,当我不想在终端里敲命令时很有用。

架构:总体图

我先展示最终结果,然后再解释我是如何做到的。

逻辑如下:

  1. 在独立服务器上 运行 Proxmox。它通过 vmbr0 拥有公共 IP 地址 Y.Y.Y.Y
  2. 内部创建了 第二个桥接网络 vmbr1,使用私有网络 10.10.10.0/24。这是“内部 LAN”的模拟 — 所有 VM 和 LXC 都连接到这里,并通过主机的 NAT 出去,它们本身没有公共入站端口。
  3. 主机本身 只运行三样东西:Caddy(内部反向代理)、nftables(防火墙)和 NetBird(VPN 代理)。主机上没有 Docker,没有应用程序逻辑。主机是神圣不可侵犯的,它的任务是作为虚拟化平台并保持稳定。
  4. 虚拟机 按角色划分:一个用于论坛,一个用于后台服务和机器人,一个用于 Dokploy 和用户应用程序,依此类推。它们之间通过 10.10.10.x 直接可见,对外只能通过主机的 Caddy 访问。
  5. LXC 容器 — 用于所有“轻量级”服务:多个版本的 Postgres 实例、Proxmox Backup Server、监控仪表板。它们也连接到 vmbr1
  6. 单独的出口节点(“Peer Caddy”)—— 这是位于另一家托管商处的一个小型 VPS,网络环境适合我的目标受众。它运行 Caddy,负责终止 TLS(Let's Encrypt)并通过 NetBird 网状网络将 HTTP 代理到独立服务器内部。所有公共域名的 DNS 都指向这个 VPS,而不是独立服务器。
  7. NetBird 网状网络 连接独立服务器、出口节点和我的笔记本电脑(还有几个尚未迁移完的老服务器)。它基于 SSO 认证的 WireGuard,没有外部开放端口。

为什么通过出口节点进行如此隔离——这是另一个话题,我稍后会回到这里。

Proxmox 安装:我是如何搭建的

订购了独立服务器,第一次启动时,我通过 救援模式/KVM 安装了 Proxmox(OVH 同时提供这两种方式 — KVM 对我来说更方便,可以看到整个过程)。安装过程中:

  • ZFS RAID1 在两个 NVMe 上(ashift=12,立即启用了 compression=lz4
  • 分区默认,Proxmox 会自行创建 rpool/ROOTrpool/data 和 swap

之后是安装后的基本清理。这是必须的,否则 Proxmox 会弹出订阅错误,并且性能不佳。

bash
1# 1. 移除 enterprise repo (无订阅)
2sed -i 's/^deb/#deb/' /etc/apt/sources.list.d/pve-enterprise.list
3echo "deb http://download.proxmox.com/debian/pve bookworm pve-no-subscription" \
4  > /etc/apt/sources.list.d/pve-no-subscription.list
5apt update && apt full-upgrade -y
6
7# 2. 移除 "No valid subscription" 的 nag 窗口
8sed -Ezi.bak "s/(Ext\.Msg\.show\(\{[^}]+?title: gettext\('No valid sub)/void\(\{ \/\/\1/g" \
9  /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js
10systemctl restart pveproxy
11
12# 3. 主机名 / 时区
13hostnamectl set-hostname dedik-waw
14timedatectl set-timezone Europe/Warsaw

SSH 加固

立即进行。不是“稍后”。稍后——永不到来。

bash
1cat > /etc/ssh/sshd_config.d/hardening.conf << 'EOF'
2Port 22222
3PermitRootLogin prohibit-password
4PasswordAuthentication no
5MaxAuthTries 3
6ClientAliveInterval 300
7ClientAliveCountMax 2
8EOF
9
10mkdir -p ~/.ssh
11echo "ssh-ed25519 AAAA... your-key" >> ~/.ssh/authorized_keys
12chmod 600 ~/.ssh/authorized_keys
13systemctl restart sshd

在防火墙中关闭了 22 端口,只留下非标准端口。这不是“通过晦涩来保证安全”,而是减少日志中的背景噪音 — 22 端口在第一个小时就会收到 50,000 次暴力破解尝试,日志里一片混乱。而在 22222 端口——安静,可以真正监控异常。

ZFS — 针对 NVMe 和 64GB RAM 的配置

ZFS 默认会占用一半的 RAM 作为 ARC 缓存。这对于文件服务器来说是正常的,但对于虚拟机需要内存的虚拟化平台来说则完全不合适。我们将 ARC 限制在合理的 16 GB:

bash
1cat > /etc/modprobe.d/zfs.conf << 'EOF'
2options zfs zfs_arc_max=17179869184
3options zfs zfs_arc_min=4294967296
4EOF
5update-initramfs -u

我的内存分配:

  • ~16 GB — ZFS ARC
  • ~44 GB — VM/LXC
  • ~4 GB — 系统、缓冲区、开销

有用的池本身配置:

bash
1zfs set compression=lz4 rpool
2zfs set atime=off rpool
3zfs set xattr=sa rpool
4zfs set dnodesize=auto rpool
5zfs set relatime=on rpool
6
7zfs set recordsize=64K rpool/data    # 适用于 VM 磁盘
8zfs set sync=standard rpool/data
9
10zpool set autotrim=on rpool          # 对 NVMe 至关重要

compression=lz4 几乎免费地节省了 20-30% 的空间,CPU 开销很小。对于 VM 的块设备,recordsize=64K 是读写性能和写入放大率的最佳折衷。

内部网络和防火墙

创建了第二个桥接网络用于内部通信,并配置了 NAT 出去:

1auto vmbr1
2iface vmbr1 inet static
3    address 10.10.10.1/24
4    bridge-ports none
5    bridge-stp off
6    bridge-fd 0
7    post-up   echo 1 > /proc/sys/net/ipv4/ip_forward
8    post-up   iptables -t nat -A POSTROUTING -s 10.10.10.0/24 -o vmbr0 -j MASQUERADE
9    post-down iptables -t nat -D POSTROUTING -s 10.10.10.0/24 -o vmbr0 -j MASQUERADE

现在,vmbr1 桥接网络中的任何 VM/LXC 都会获得一个本地地址 10.10.10.x,并通过主机访问互联网,但从外部无法看到它们。

nftables 配置为 policy drop。只开放必须开放的端口,其余的静默丢弃。最小化配置集:

nft
1table inet filter {
2    chain input {
3        type filter hook input priority 0; policy drop;
4
5        iif lo accept
6        ct state established,related accept
7        ip protocol icmp accept
8        ip6 nexthdr icmpv6 accept
9
10        tcp dport 22222 accept           # SSH
11        tcp dport 8006 accept            # Proxmox UI (仅限 NetBird 访问,见下文)
12        tcp dport { 80, 443, 8080 } accept   # Caddy
13        udp dport 29899 accept           # NetBird
14
15        iif vmbr1 accept                 # 内部网络 — 全部允许
16        iif wt0 accept                   # NetBird 接口 — 也受信任
17    }
18
19    chain forward {
20        type filter hook forward priority 0; policy drop;
21        iif vmbr1 oif vmbr0 accept
22        ct state established,related accept
23    }
24}

Proxmox UI (端口 8006) 的访问被我通过 CF/防火墙物理上关闭了对互联网的访问,并且我 只通过 NetBird 访问它。公共 IP 地址的界面根本不响应。同样,PBS UI、仪表板和所有管理界面——都只通过网状网络访问。外部只开放真正公共的服务端口。

NetBird 网状网络:为什么不选择经典的 WireGuard

我长期使用普通的 WireGuard,这在服务器数量少于三个时还算正常。之后——管理配置变得很痛苦:每次添加一个 peer 节点都意味着“更新所有节点的配置文件,不要忘记 allowed_ips,重启守护进程,检查是否破坏了到另一个 peer 的路由”。

NetBird 是 WireGuard 之上的管理层。技术上,它底层还是 WireGuard,但是:

  • 节点通过信令服务器相互查找,NAT 穿透是自动的
  • 认证通过 SSO(我使用的是 Authentik),无需共享密钥
  • 访问通过 Web 界面上的策略进行管理,可以创建机器组和它们之间的规则
  • 内置 SSH 代理:你可以通过 ssh user@machine.netbird.cloud 访问,并获得 SSO 认证(需要 Authentik 的批准)。节点甚至不需要在外部开放 22 端口。

我部署了 NetBird 代理:

  • 在 Proxmox 主机上
  • 在每个需要被管理员或其他 VM 访问的 VM 上
  • 在出口节点上
  • 在我的笔记本电脑上

结果是我无需端口转发、防火墙漏洞或额外的堡垒主机,就可以从笔记本电脑通过一条命令访问任何机器。并且没有一个私有 VM 开放 SSH — 它们都只连接到 vmbr1 和网状网络中的 wt0

虚拟机 vs. 容器:我是如何为自己决定的

一个几乎总是适用的简单规则:

何时选择什么
Docker 负载VM (Docker 在 LXC 中有时会很顽固,尤其是使用 overlay2)
非 Docker 数据库LXC (更轻量,更快,通过 ZFS 直接访问磁盘)
系统服务 (PBS, 监控, 小工具)LXC
需要自己内核/网络技巧的应用VM
VPN 节点 (WG, AmneziaWG)LXC (开销最小)

最终结果大致如下:

虚拟机 (KVM):

  • forum — Discourse 标准的 launcher 容器。内部使用 Docker。
  • services — 许多小服务:webhook 机器人、AI 机器人、计费面板、Vaultwarden、Uptime Kuma。所有都使用 Docker compose,每个服务一个文件夹。
  • legacy — 旧的 PHP 论坛 (IPS),原生 PHP-FPM 和 Caddy 在 VM 中运行。我不想打包成 Docker — 太多的遗留问题,不如保持原样。
  • dokploy — Docker Swarm + Dokploy 用于用户应用程序(Next.js, Postgres, Redis),通过 Git 部署。

LXC:

  • pbs — Proxmox Backup Server (见下文)。
  • pg16, pg17, pg18 — 三个独立的、不同主版本的 Postgres。原因:不同的应用程序需要不同的主版本,我不想每次都升级。
  • dashboards — 一个容器,包含 Homarr (启动页面)、Uptime Kuma 和 Beszel hub。这三个都在同一个 compose 文件中,因为它们在逻辑上属于同一类 — 观察。

每个 VM 都分配了静态 IP 10.10.10.x(按范围划分,这样查找更方便),2-8 个 vCPU 内核和 2-8 GB RAM,具体取决于负载。内核数量可以安全地超额分配 — 我为 VM 分配的总内核数超过了物理内核数,而且效果很好,只要没有人同时达到 CPU 瓶颈。

反向代理:双层 Caddy

在 Proxmox 内部,我将 Caddy 安装在 主机上,而不是 VM 中。为什么?因为它需要监听独立服务器的公共端口,并通过 Host-header 路由到正确的 VM,为它启动一个单独的 VM 并进行端口转发没有好处。Caddy 轻量,用 Go 编写,有 systemd 服务单元,配置在一个文件中。

这个主机上的 Caddy 只监听 8080 HTTP 端口,并根据 10.10.10.x 路由到后端。它上面没有任何 TLS。

caddy
1:8080 {
2    @forum host forum.example.com www.forum.example.com
3    handle @forum {
4        reverse_proxy http://10.10.10.111:80 {
5            header_up X-Forwarded-Proto https
6            header_up X-Real-IP {header.X-Real-IP}
7            header_up Host {host}
8        }
9    }
10
11    @panel host panel.example.com api.panel.example.com
12    handle @panel {
13        reverse_proxy http://10.10.10.111:8081 {
14            header_up X-Forwarded-Proto https
15            header_up X-Real-IP {header.X-Real-IP}
16            header_up Host {host}
17        }
18    }
19
20    @dokploy host app1.example.com app2.example.com
21    handle @dokploy {
22        reverse_proxy http://10.10.10.114:80 {
23            header_up X-Forwarded-Proto https
24            header_up X-Real-IP {header.X-Real-IP}
25            header_up Host {host}
26        }
27    }
28}

TLS 终止和证书 — 在单独的出口节点上,在 Docker 中,遵循相同的原理:

caddy
1{
2    email me@example.com
3}
4
5forum.example.com {
6    reverse_proxy http://100.x.y.z:8080 {
7        header_up X-Forwarded-Proto {scheme}
8        header_up X-Forwarded-For {remote_host}
9        header_up X-Real-IP {remote_host}
10        header_up Host {host}
11    }
12}

100.x.y.z — 这是独立服务器的 NetBird 地址。请求通过 HTTPS 到达出口节点,解密,以 HTTP 形式通过 NetBird 网状网络 发送到独立服务器,然后 Proxmox Caddy 根据 Host 匹配并路由到内部 VM。独立服务器和出口节点之间的通信 默认是 HTTP — 我们信任网状网络,这里没有外部流量。

为什么需要出口节点

几个原因:

  1. 绕过封锁。 OVH 的部分 IP 段在中国被屏蔽。出口节点位于未被屏蔽的托管商处。公共域名的 DNS 指向那里。
  2. 单一 TLS 终止点。 只有它颁发证书,Let's Encrypt 的挑战只在这里进行。这简化了所有 DNS 验证,并允许独立服务器在 80/443 端口上对互联网完全不可见。
  3. 额外的隔离层。 如果有人发起 DDoS 攻击,他们攻击的是出口节点,而不是独立服务器。出口节点上的数据很少,重新创建一个只需 10 分钟。
  4. 分离公共前端和后端。 我可以在独立服务器上安全地更新 Caddy / 进行实验,而与互联网的实际接口是独立且稳定的。

缺点是:每个请求的延迟会增加约 5-15 毫秒,因为多了一个跳跃。对于 Web 来说,这不明显。

此外,对于某些服务,主机上直接运行的 Caddy 在 80/443 端口上运行,无需出口节点 — 用于那些我不想通过出口点路由的域名(例如,状态端点、监控、DNS 本来就指向独立服务器的服务)。这并行运行:主机上的 Caddy 监听(出站节点内部的 :8080)和(来自互联网的直接 :80/:443),路由规则不同。

迁移:如何在几乎不停机的情况下顺利迁移

这是最棘手的部分。五个服务器,大约三十个服务,生产环境的论坛有活跃用户,计费系统有有效订阅,数据库绝对不能丢失。

策略是逐个服务迁移,按“从最不关键到最关键”的顺序进行:

  1. 首先迁移小型工具和静态网站(如果出现问题,没人会注意到)。
  2. 然后是机器人和后台服务(也可以忍受一分钟的停机时间)。
  3. 然后是数据库(事先已验证恢复)。
  4. 然后是依赖于这些数据库的应用程序。
  5. 最后是主论坛,进行一次长期的最终同步和 DNS 切换。

迁移通道

首先,我在旧服务器上启用了 NetBird。这立即解决了两个问题:我可以安全地通过 SSH 访问内部网络,并且 rsync 通过 NetBird 上的 WireGuard 进行,不暴露数据到公共互联网。

每个服务的命令大致如下:

bash
1# 从新独立服务器,通过 NetBird IP 连接到旧服务器:
2rsync -avzP --delete \
3  -e "ssh -p 5322" \
4  root@100.76.108.210:/var/discourse/shared/standalone/ \
5  /target/discourse/shared/standalone/

对于数据库 — 备份后恢复,而不是文件复制。切勿“热”复制 Postgres/MySQL 文件,那是找死。

bash
1# 在旧服务器上
2docker exec -it shm-vsem-mysql mysqldump -u root -p shm-vsem | \
3  gzip > /tmp/shm-vsem.sql.gz
4
5# 在新服务器上,通过 NetBird
6scp -P 5322 root@100.76.108.210:/tmp/shm-vsem.sql.gz /tmp/
7zcat /tmp/shm-vsem.sql.gz | docker exec -i shm-vsem-mysql mysql -u root -p shm-vsem

对于 Discourse — 使用原生的 discourse backup / discourse restore 命令,通过 ./launcher enter app。它本身会正确地收集 Postgres 转储 + uploads + 配置文件,并同样地进行恢复。

DNS 切换

在新的位置启动服务并验证其通过内部地址正常工作后 — 我将两套安装并行运行 5-15 分钟,以观察数据同步情况,然后切换 DNS 到新服务器。我提前一天将记录的 TTL 降低到 60-300 秒。

对于 Discourse,用户一直在发帖,我是这样做的:

  1. 在新位置启动所有东西。
  2. 进行验证(登录、发帖、上传文件到 S3、搜索)。
  3. 在旧安装中启用 read_only_mode (Discourse 原生支持)。
  4. 进行最后的 rsync uploads + 最后的数据库转储。
  5. 在新服务器上启动,关闭 read-only 模式。
  6. 切换 DNS。
  7. 旧服务器上什么都没动,保持一周 — 以防万一。

每个服务的实际停机时间 — 大约两分钟。

过程中的意外情况

  • pct restore 时的权限问题。 LXC 在 unprivileged 1 模式下,UID 会偏移 100000。如果旧服务器上的文件是 UID 1000 — 新服务器上将是 UID 101000,应用程序将无法识别。解决方案是恢复后执行 chown,或者使用 --unprivileged 0(如果你明白风险)。
  • OVH VM 中的 Docker ipv6 有时会出错 — 我通过在 Docker daemon 中禁用 ipv6 ("ipv6": false) 来解决。
  • VM 的时间。 有几次发现主机和 VM 之间的系统时间不一致,导致 TLS 和签名出错。解决方法是在每个 VM 上安装 systemd-timesyncdchrony,并在 Proxmox 代理中设置 host.use-time
  • Discourse 在两个版本之间的迁移。 如果旧服务器上的 Discourse 版本比你在新服务器上安装的版本更新 — restore 会失败。新安装上的版本必须相同或更高
  • Dokploy 的镜像仓库。 当我通过 dokploy-postgres 转储迁移 Dokploy 元数据时,数据库中仍然保留了指向旧服务器上的本地 registry 的链接。服务尝试启动时会连接到 100.76.117.115:5000 并因 No such image 而失败。解决方案是在 Dokploy UI 中重新构建每个应用程序;本地构建会将镜像推送到新服务器。

备份:PBS 部署在同一台独立服务器上 + 异地备份

关于是否在同一硬件上备份,我曾有过一番激烈的思想斗争。答案是:既要,也要。

级别 1 — 池本身的 ZFS 快照。 几乎免费(写时复制),瞬间创建,瞬间恢复。我保持 rpool/data 每 15 分钟自动快照,保留 24 小时,外加每天保留一周的快照。使用 zfs-auto-snapshot。用于防止“糟糕,我刚刚删除了生产数据库”的情况:

bash
1apt install zfs-auto-snapshot
2# 之后它会自动通过 cron 创建 frequent/hourly/daily/weekly/monthly 快照

级别 2 — Proxmox Backup Server 部署在同一主机上的独立 LXC 容器中。 这是通过 vzdump 对 VM/LXC 进行完整的增量备份,在块级别进行去重。PBS 将快照存储在单独的 ZFS 数据集中,将其视为一个仓库,每个 VM 都有自己的增量链。

在 Proxmox 中设置:Datacenter → Backup → Add。计划:每天凌晨 4 点,保留策略为 keep-daily=7 keep-weekly=4 keep-monthly=3。这些都可以通过鼠标操作。

将 PBS 部署在同一主机上的 LXC 容器中是一个折衷。优点:无需单独的硬件,备份通过 localhost 进行,速度如同本地 SSD,保留/去重效果完美。缺点:如果独立服务器整体损坏 — 备份也会随之丢失。因此,有...

级别 3 — 将 PBS 仓库同步到异地 S3。 PBS 支持 sync job 到 S3 兼容的存储。我每晚将快照上传到 Selectel 的 S3 存储(也可以是 Backblaze、Wasabi、Cloudflare R2 等)。那里保留两天,因为不需要更长的保留期:长期备份有本地 PBS,异地备份是防止“整个数据中心被烧毁”的保险。

级别 4 — 应用程序数据单独备份。 Discourse 本身会进行自己的备份并将其存储在 S3 中(这是其原生功能)。所以即使 PBS 和整个独立服务器都丢失了 — 我仍然拥有 Discourse 在其云端的完整备份。

bash
1# 从 PBS 恢复整个 VM — 实际上只需一条命令:
2pvesm list backup-pbs    # 查看可用备份
3qmrestore backup-pbs:backup/vzdump-qemu-201-2026_04_06-04_00_03.vma.zst 999 \
4  --storage local-zfs
5# 一分钟后,你就可以在 VMID 为 999 的 VM 上运行 4 点时的 forum-VM 副本。

这一点至关重要:你没有尝试恢复的备份,就不是备份。我每月进行一次随机 VM 的测试恢复到空的 VMID,检查它是否能启动,应用程序是否正常运行,然后删除。这很无聊,但有一次我正是通过这种方式发现,某个 cron 任务将数据写入了 /tmp/...,而 /tmp 在备份时会被跳过,导致应用程序在恢复后需要手动干预。

监控

我不喜欢在有 15 台机器的情况下收集 Prometheus + Grafana + Alertmanager + Loki 这种大型系统。因此,我选择在单个 LXC 容器中部署三个轻量级工具:

  • Beszel — 收集每台 VM 上的代理指标(CPU、RAM、磁盘、网络接口、Docker 容器)。Hub 运行在 LXC 中,代理 — 在每台 VM/LXC 上通过 systemd unit 运行。Beszel 的认证是自有的,通过 PocketBase,这有时不太方便(见下文的“坑”),但总体上还能工作。
  • Uptime Kuma — 通过 HTTP/HTTPS/Ping/TCP 进行探测。我将所有公共域名、所有内部服务(通过 NetBird)以及所有网状网络节点的 ping 都设置了进去。告警 — 发送到 Telegram。
  • Homarr — 启动页面,包含所有管理界面的链接。这样我就不必记住 PBS UI、Dokploy、Vaultwarden 分别在哪个端口上。只需打开 Homarr 并点击即可。

这三个工具都放在同一个 Docker compose 文件中,运行在同一个 LXC 容器里,只能通过 NetBird 访问。

Beszel 的坑,以便您避免:Beszel 在 PocketBase 中有两个用户表 — _superusers (用于 CLI/API) 和 users (用于 Web 界面)。如果您通过 CLI superuser upsert 重置密码 — 只会重置 _superusers,而您通过 users 登录 UI 时密码会不匹配。可以通过向 /api/collections/users/records/<id> 发送 PATCH 请求通过 REST API 来修复。

我最终获得了什么

经过大约三周的迁移,当最后一个服务稳定下来后,我做了总结:

参数过去现在
服务器/账单3 家托管商的 5 台1 台独立服务器 + 1 台微型 VPS
月度费用~$X~$X/2
剩余资源"好像够用"8 vCPU 和 30 GB RAM 剩余
备份每台 VPS 的 rsync-cron 到 S3PBS + 异地备份 + ZFS 快照
恢复"嗯,大概一天"从 PBS 恢复 VM 2-5 分钟
SSH 访问5 个不同的端口和密钥一个带 SSO 的 NetBird 网状网络
服务隔离共享垃圾场每个服务一个 VM/LXC
升级前快照"祈祷吧"qm snapshot 201 pre-update
用于日常任务的 Web 界面还有什么 Web 界面?Proxmox UI

最重要的是,它在情感上给予了我——我不再害怕去修改任何东西。任何危险的操作(升级、迁移、实验)现在都始于一个快照,并以 30 秒内完成提交或回滚结束。我开始更频繁地尝试新事物,因为错误的代价降低了数量级。

如果您打算重复,请参考此清单

如果您发现自己有和我一样的症状 — 这是一个简短的清单,按有意义的顺序排列:

  1. 计算所有 VPS 的总花费,并与 OVH/Hetzner/LeaseWeb 的独立服务器价格进行比较。您会感到惊讶。
  2. 如果计划使用 ZFS,请务必购买 ECC RAM。不要节省。
  3. 至少购买两块磁盘组成镜像。单块磁盘不适合生产环境。
  4. 通过 KVM/IPMI 安装 Proxmox,而不是通过 rescue + debootstrap。这样麻烦更少。
  5. 立即 设置 SSH 密钥,非标准端口,禁用密码,安装 fail2ban。
  6. 立即 限制 ZFS ARC。默认情况下,它会吃掉一半的 RAM。
  7. 创建第二个桥接网络 vmbr1 用于内部私有网络和 NAT。所有 VM/LXC 都连接到这里。虚拟机上不应有公共 IP。
  8. 开始迁移之前,先搭建好 NetBird (或 Tailscale, 或 Headscale)。这是您的迁移通道和管理访问入口。
  9. 不要将 Proxmox UI 暴露在外部。只能通过 VPN 网状网络访问。
  10. 在另一个托管商处放置一个小型出口 VPS 用于 TLS 终止和绕过封锁 — 非必需,但非常方便。价格便宜,收益很多。
  11. 在独立容器中部署 PBS + 异地 S3 同步。备份必须自动化。
  12. 至少每月进行一次测试恢复。否则,您的备份只是安慰剂。
  13. 逐个服务迁移,按关键程度递增。不要试图“在周末搬完所有东西”。
  14. 提前一天降低 DNS TTL,而不是一小时。
  15. 为每个服务维护文档:配置文件位置、启动方式、备份方法、恢复方法。半年后,您会感谢自己。

还有哪些需要完成

为了完整性:我的迁移尚未 100% 完成。还剩下:

  • 几个旧服务器上的 Remnawave 面板(计划下周末完成)。
  • 迁移 Caddy(有自定义插件,目前是临时方案)。
  • 旧的 CrowdSec — 单独从头安装,不迁移。

但所有关键服务(论坛、计费、机器人、数据库) — 已经部署在独立服务器上,运行稳定,已备份并监控。

如果您对具体步骤、配置或我遇到的坑有任何疑问,欢迎在评论区提问,我会尽量详细解答。特别想听听您在其他虚拟化平台(XCP-ng、ESXi,或者纯 Docker Swarm)上是如何解决类似问题的 — 也许我遗漏了什么。

祝您在整合基础设施的过程中好运。更少的账单,更多的控制 — 这是值得的。

~17 min read · scroll to continue ↓

## discussion

$ topics --entity=article0
sign in to start or join a discussion
No discussions yet — start one to break the ice.
↑↓ nav open⌘K palettei install? help