~/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 у трёх хостеров. Один — под Discourse-форум, второй — под легаси-форум на PHP с MySQL, третий — под биллинг и Telegram-ботов, четвёртый — VPN/прокси, пятый — статика и всякие мелкие тулзы. И ещё там же где-то n8n крутился, и Vaultwarden, и пара Uptime Kuma, которые мониторили друг друга вкруговую (как же иначе).

Что было плохо:

  • Деньги. Суммарно выходило примерно столько же, сколько и стоит один приличный дедик с такими же характеристиками вместе взятыми (но там Shared, а тут выделенные).
  • Никакой изоляции. Если у одного клиента бота начинает течь память — у меня форум начинает тупить. На том же сервере. Прелестно.
  • Бэкапы — через жопу. На каждой VPS свой rsync-крон в S3, везде разные ретеншны, никакой инкрементальности, восстанавливать — целое приключение.
  • Сеть в виде салата. SSH через разные порты, проброшенные через Cloudflare Tunnels, через WireGuard вручную поднятый между двумя VPS, с конфигами в трёх местах. Каждый раз когда добавлял новую машину — день уходил на интеграцию.
  • Update-апокалипсис. На пяти разных 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
  • Цена: примерно как два средних VPS у того же OVH

Гипервизор

Proxmox VE 9.x. Альтернативы рассматривал, но коротко:

  • Голый Docker — нет изоляции, нет нормальных снапшотов, бэкап только средствами compose, диск — единая помойка.
  • VMware ESXi — больше не бесплатный, лицензия Broadcom прекрасна, спасибо.
  • XCP-ng — норм, но лично я Proxmox знаю наизусть и комьюнити там живее.
  • Kubernetes — нет, я один человек, мне не надо собирать самолёт чтобы съездить в магазин.

Proxmox даёт мне всё, что нужно: KVM-виртуалки для всего, где нужна изоляция (Docker, специфичные ядра, разные дистрибутивы); LXC-контейнеры там, где можно сэкономить ресурсы (БД, мониторинг, маленькие сервисы); встроенный PBS для бэкапов; ZFS из коробки; снапшоты; live-миграция (если когда-нибудь второй дедик появится); веб-морда, в которой можно ткнуть мышкой когда не хочется писать в терминале.

Архитектура: общая схема

Сначала покажу что получилось, потом расскажу как до этого дошёл.

Логика такая:

  1. На дедике живёт Proxmox. Он держит публичный IP Y.Y.Y.Y на vmbr0.
  2. Внутри сделан второй bridge vmbr1 с приватной сетью 10.10.10.0/24. Это аналог "внутренней LAN" — все VM и LXC висят там, наружу через NAT с хоста, входящих публичных портов у них нет.
  3. На самом хосте крутится только три вещи: Caddy (внутренний reverse proxy), 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) и проксирует HTTP внутрь дедика через NetBird-меш. DNS у всех публичных доменов смотрит на этот VPS, не на дедик.
  7. NetBird mesh связывает дедик, выходную ноду и мой ноут (а ещё пару старых серверов с которых ещё не всё переехало). Это WireGuard поверх SSO-аутентификации, без открытых портов снаружи.

Зачем такая изоляция через выходную ноду — отдельный разговор, к которому я ещё вернусь.

Установка Proxmox: как я его поднимал

Заказал дедик, в первый загруз накатил Proxmox через rescue-режим/KVM (у OVH есть и то, и другое — мне удобнее KVM, видно весь процесс). При установке:

  • ZFS RAID1 на оба NVMe (ashift=12, compression=lz4 включил сразу)
  • разметка дефолтная, Proxmox сам делает rpool/ROOT, rpool/data, swap

Дальше — базовая зачистка после установки. Это must-have, без него 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. Убрать nag-окно "No valid subscription"
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. Hostname / timezone
13hostnamectl set-hostname dedik-waw
14timedatectl set-timezone Europe/Warsaw

SSH hardening

Сразу. Не "потом". Потом — никогда не наступает.

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 в фаерволе закрыл, оставил только нестандартный. Это не security through obscurity, это снижение фонового шума в логах — на 22-м порту в первый же час прилетает 50 тыщ попыток брута, в логах ничего не видно. На 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. recordsize=64K для блочных устройств VM — лучший компромисс между производительностью и амплификацией записи.

Внутренняя сеть и фаервол

Создал второй bridge для приватки с 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

Теперь любая VM/LXC в bridge vmbr1 получает локальный адрес 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/firewall и хожу туда только через NetBird. По публичному IP морда вообще не отвечает. То же с PBS UI, дашбордами и всем админским — только через мешу. Снаружи открыты только реально публичные порты сервисов.

NetBird mesh: почему не классический WireGuard

Я долго пользовался обычным WireGuard, и это было нормально, пока серверов не стало больше трёх. После — управлять конфигами стало больно: каждое добавление peer-а превращается в "обнови конфиг на всех нодах, не забудь allowed_ips, рестартни демон, проверь что не сломал маршрут к другому пиру".

NetBird — это менеджмент-плоскость поверх WireGuard. Технически под капотом у тебя всё тот же wg, но:

  • ноды находят друг друга через сигнальный сервер, NAT-traversal автоматический
  • аутентификация через SSO (у меня — через Authentik), без шеринга ключей
  • доступ управляется в веб-морде через политики, можно делать группы машин и правила между ними
  • встроенный SSH-прокси: можно ходить по ssh user@machine.netbird.cloud и получать SSO-аутентификацию (с одобрением в Authentik). На ноде даже port 22 наружу открывать не надо.

Я поднял NetBird-агент:

  • на хосте Proxmox
  • на каждой VM, которая должна быть видна админу или другим VM-кам
  • на выходной ноде
  • на своём ноуте

В итоге я хожу из ноута на любую машину одной командой, без проброса портов, без фаервольных дырок, без дополнительных bastion-хостов. И нет открытого SSH ни на одной из приватных VM — они все только в vmbr1 плюс мешевый wt0.

Виртуалки vs контейнеры: как я это для себя решил

Простое правило, которое работает почти всегда:

КогдаЧто брать
Docker нагрузкиVM (Docker в LXC бывает капризный, особенно с overlay2)
Базы данных без DockerLXC (легче, быстрее, прямой доступ к диску через ZFS)
Системные сервисы (PBS, мониторинг, маленькие тулзы)LXC
Приложения, которым нужно своё ядро / сетевые трюкиVM
VPN-узлы (WG, AmneziaWG)LXC (минимум оверхеда)

В итоге у меня получилось примерно так:

Виртуалки (KVM):

  • forum — Discourse в стандартном launcher-контейнере. Docker внутри.
  • services — куча мелочи: webhook-боты, AI-бот, билинговая панель, Vaultwarden, Uptime Kuma. Все в Docker compose, по папке на сервис.
  • legacy — старый PHP-форум (IPS) с native 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 в зависимости от нагрузки. Cores можно безопасно overprovision'ить — у меня в сумме на VM выписано больше ядер, чем физических, и это работает прекрасно, пока никто одновременно не упирается в CPU.

Reverse proxy: двухэтажный 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, расшифровывается, уходит в NetBird-меш HTTP-ом на дедик, там Proxmox Caddy матчит по Host и роутит во внутреннюю VM. Между выходной нодой и дедиком HTTP по дизайну — мы доверяем мешу, никакого внешнего трафика тут нет.

Зачем вообще выходная нода

Несколько причин:

  1. Обход блокировок. Часть OVH-подсетей в РФ режется. Выходная нода стоит у провайдера, чей IP не блокируется. DNS у публичных доменов смотрит туда.
  2. TLS-терминация в одном месте. Сертификаты выпускает только она, у Let's Encrypt только её IP в challenge. Это упрощает все DNS-cleared check'и и позволяет дедику быть полностью невидимым из интернета по 80/443.
  3. Дополнительный изоляционный слой. Если кто-то начинает DDoS-ить — он DDoS-ит выходную ноду, не дедик. На выходной ноде у меня минимум данных, пересоздать её — 10 минут.
  4. Отделение публичного фронта от бэкенда. Я могу спокойно гонять обновления Caddy / экспериментировать на дедике, при этом реальный интерфейс к интернету у меня отдельно и стабильно.

Минус один: добавляется ~5-15 мс к каждому запросу за счёт лишнего хопа. Для веба это незаметно.

Также для некоторых сервисов есть прямой Caddy на хосте на 80/443 без выходной ноды — для тех доменов, которые мне не нужно прокидывать через выходную точку (например, статусные эндпоинты, мониторинг, сервис, где DNS и так смотрит на дедик). Это работает параллельно: Caddy на хосте слушает и :8080 (внутренний от выходной ноды), и :80/:443 (прямой от интернета), маршруты разные.

Миграция: как я переезжал без слёз и почти без даунтайма

Это был самый стрёмный этап. Пять серверов, штук тридцать сервисов, продакшен-форум с живыми пользователями, биллинг с активными подписками, базы данных, которые ни в коем случае нельзя потерять.

Стратегия — миграция по одному сервису, в порядке "от наименее критичного к самому критичному":

  1. Сначала переехали мелкие тулзы и статические сайты (если что — никто не заметит).
  2. Потом боты и фоновые сервисы (тоже терпят минуту даунтайма).
  3. Потом базы данных (с заранее проверенным восстановлением).
  4. Потом приложения, которые на эти БД смотрят.
  5. В самом конце — основной форум с длинным финальным раундом sync'а и переключением DNS.

Канал миграции

Сначала я просто поднял NetBird на старых серверах. Это решило сразу две вещи: я могу безопасно ходить по SSH во внутренние сети, и rsync идёт по WireGuard'у через NetBird, не светя данные в открытый интернет.

Команда-боец на каждый сервис была примерно такой:

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/

Для баз данных — дамп + restore, не файловая копия. Никогда не копируйте файлы 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. На старой ничего не трогал ещё неделю — на случай "а вдруг".

Реальный даунтайм для пользователей — около двух минут на сервис.

Сюрпризы по дороге

  • Permission'ы при pct restore. LXC под unprivileged 1 мапит UID-ы со сдвигом 100000. Если на старом сервере у тебя файлы под UID 1000 — на новом они будут под UID 101000, и приложение их не увидит. Решается либо chown после restore, либо --unprivileged 0 если ты понимаешь риски.
  • Docker ipv6 в виртуалках OVH иногда поднимался криво — вылечил отключением ipv6 в Docker daemon ("ipv6": false).
  • Время на VM. Несколько раз ловил расхождение по системному времени между хостом и VM, что ломало TLS и подписи. Лекарство — systemd-timesyncd или chrony на каждой VM, и host.use-time в Proxmox-агенте.
  • Discourse миграция между двумя версиями. Если на старом сервере Discourse более свежий, чем тот, который вы подняли на новом — restore не пройдёт. Версия должна совпадать или быть выше на новой инсталляции.
  • Image registry для Dokploy. Когда я перенёс метаданные Dokploy через dokploy-postgres дамп, в БД остались ссылки на старый локальный registry на старом IP. Сервисы при попытке стартануть лезли в 100.76.117.115:5000 и падали с No such image. Решение — пересобрать каждое приложение в Dokploy UI заново; локальная сборка кладёт image уже на новый сервер.

Бэкапы: PBS внутри того же дедика + offsite

Тут была отдельная философская дискуссия с самим собой: бэкапить на том же железе или нет. Ответ: и да, и нет.

Уровень 1 — ZFS-снэпшоты на самом пуле. Они почти бесплатные (copy-on-write), делаются мгновенно, восстанавливаются мгновенно. Я держу автоматические снэпшоты 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 на том же хосте. Это полноценные incremental-бэкапы VM/LXC через vzdump, дедуплицированные на уровне чанков. PBS хранит снимки в отдельном датасете ZFS, видит их как репозиторий, у каждой VM своя цепочка инкрементов.

Настройка в Proxmox: Datacenter → Backup → Add. Расписание: каждый день в 4 утра, ретеншн keep-daily=7 keep-weekly=4 keep-monthly=3. Это всё мышкой.

Размещение PBS в LXC на том же хосте — компромисс. Плюс: не надо отдельную железку, бэкап идёт по localhost, скорость как у локального SSD, retention/dedup работает идеально. Минус: если умирает дедик целиком — умирают и бэкапы. Поэтому есть...

Уровень 3 — синхронизация PBS-репо в offsite S3. PBS умеет sync job в S3-совместимое хранилище. Я каждую ночь сливаю снимки на отдельный bucket в Selectel-овом S3 (можно любой — Backblaze, Wasabi, Cloudflare R2). Ретеншн там — две недели, потому что больше не нужно: для долгосрочки есть локальный PBS, а offsite — это страховка от "сгорел весь ДЦ".

Уровень 4 — данные приложений отдельно. Discourse сам по себе делает свои бэкапы и кладёт их в S3 (это его нативная фича). Так что у меня даже если пропадает PBS и вообще весь дедик — у меня всё ещё есть консистентные дампы Discourse в их облаке.

bash
1# Восстановление целой VM из PBS — буквально одна команда:
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 крутится копия forum-VM на момент 4 утра.

Это критический момент: бэкапы, которые ты не пробовал восстановить — это не бэкапы. Я раз в месяц делаю тестовый restore случайной VM на пустой VMID, проверяю что она поднимается, что приложение в ней работает, и удаляю. Скучно, но один раз именно так я нашёл, что один из cron'ов записывает в /tmp/..., который при бэкапе пропускается, и приложение после restore требует ручного шага.

Мониторинг

Не люблю собирать комбайны типа Prometheus + Grafana + Alertmanager + Loki, когда у меня 15 машин. Поэтому остановился на трёх лёгких инструментах в одном LXC:

  • Beszel — собирает метрики с агентов на каждой VM (CPU, RAM, диски, сетевые интерфейсы, контейнеры Docker). Hub живёт в LXC, агенты — на каждой VM/LXC через systemd unit. Auth у Beszel свой, через PocketBase, что иногда неудобно (см. ниже про граблю), но в целом работает.
  • Uptime Kuma — пробы по HTTP/HTTPS/Ping/TCP. У меня туда заведено всё публичное (домены), все внутренние сервисы (через NetBird), и пинги по всем нодам меша. Алерты — в Telegram.
  • Homarr — стартовая страница со ссылками на все админки. Чтобы не помнить, по какому порту висит PBS UI, по какому Dokploy, по какому Vaultwarden. Просто открываю Homarr и кликаю.

Все три в одном Docker compose, в одном LXC, доступны только через NetBird.

Грабля с Beszel, чтобы вы не наступили: у Beszel две таблицы пользователей в PocketBase — _superusers (для CLI/API) и users (для веб-морды). Если ресетите пароль через CLI superuser upsert — сбрасываете только _superusers, а в UI вы логинитесь через users и пароль не подходит. Лечится PATCH-запросом на /api/collections/users/records/<id> через REST API.

Что я в итоге получил

Через примерно три недели миграции, когда осели последние сервисы, я подвёл черту:

ПараметрБылоСтало
Серверов / счетов5 у 3 хостеров1 дедик + 1 микро-VPS
Платёжки в месяц~$X~$X/2
Свободные ресурсы"ну вроде хватает"8 vCPU и 30 GB RAM в запасе
Бэкапыrsync-крон в S3 на каждой VPSPBS + offsite + ZFS-снэпшоты
Восстановление"ну, день где-то"2-5 минут на VM из PBS
SSH-доступ5 разных портов и ключейодин NetBird-меш с SSO
Изоляция сервисовобщая помойкакаждый в своей VM/LXC
Снапшот перед апгрейдом"помолись"qm snapshot 201 pre-update
Веб-морда для рутинных задачкакая ещё веб-мордаProxmox UI

Главное, что мне это дало эмоционально — я перестал бояться что-то трогать. Любое опасное действие (апгрейд, миграция, эксперимент) теперь начинается со снэпшота и заканчивается либо коммитом, либо откатом за 30 секунд. Я начал чаще пробовать новое, потому что цена ошибки упала на порядок.

Чек-лист, если будете повторять

Если вы ловите себя на тех же симптомах, что и я — вот короткий чеклист, в каком порядке имеет смысл двигаться:

  1. Посчитайте, сколько вы платите за все VPS вместе, и сравните с прайсом дедиков у OVH/Hetzner/LeaseWeb. Удивитесь.
  2. Берите ECC RAM, если планируете ZFS. Не экономьте.
  3. Берите минимум два диска в зеркале. Один диск — не вариант для прода.
  4. Накатите Proxmox через KVM/IPMI, не через rescue + debootstrap. Меньше боли.
  5. Сразу настройте SSH-ключи, нестандартный порт, отключите пароли, поставьте fail2ban.
  6. Сразу ограничьте ZFS ARC. По умолчанию он сожрёт половину RAM.
  7. Сделайте второй bridge vmbr1 для приватной сети с NAT. Все VM/LXC туда. Никаких публичных IP на виртуалках.
  8. Поднимите NetBird (или Tailscale, или Headscale) до того, как начнёте мигрировать. Это ваш канал миграции и админский доступ.
  9. Не открывайте Proxmox UI наружу. Только через VPN-меш.
  10. Один маленький выходной VPS у другого провайдера для TLS-терминации и обхода блокировок — не обязателен, но очень удобен. Цена копеечная, выгод много.
  11. PBS в отдельном LXC + offsite sync в S3. Бэкапы должны быть автоматическими.
  12. Минимум раз в месяц делайте тестовый restore. Иначе у вас не бэкапы, а файлы для самоуспокоения.
  13. Мигрируйте по одному сервису, в порядке возрастания критичности. Не пытайтесь "перевезти всё за выходные".
  14. Снижайте TTL на DNS за день до переключения, не за час.
  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