Зачем я вообще это затеял
К концу прошлого года я обнаружил, что плачу за пять разных 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-миграция (если когда-нибудь второй дедик появится); веб-морда, в которой можно ткнуть мышкой когда не хочется писать в терминале.
Архитектура: общая схема
Сначала покажу что получилось, потом расскажу как до этого дошёл.
Логика такая:
- На дедике живёт Proxmox. Он держит публичный IP
Y.Y.Y.Yнаvmbr0. - Внутри сделан второй bridge
vmbr1с приватной сетью10.10.10.0/24. Это аналог "внутренней LAN" — все VM и LXC висят там, наружу через NAT с хоста, входящих публичных портов у них нет. - На самом хосте крутится только три вещи:
Caddy(внутренний reverse proxy),nftables(фаервол) иNetBird(VPN-агент). Никакого Docker на хосте, никакой логики приложений на хосте. Хост — священная корова, его задача быть гипервизором и не падать. - Виртуалки разделены по ролям: одна под форум, одна под фоновые сервисы и боты, одна под Dokploy с пользовательскими приложениями, и т.д. Между ними прямая видимость по
10.10.10.x, наружу — только через хостовый Caddy. - LXC-контейнеры — для всего "лёгкого": Postgres-инстансов нескольких версий, Proxmox Backup Server, дашбордов мониторинга. Они тоже в
vmbr1. - Отдельная выходная нода ("Peer Caddy") — это маленький VPS у другого провайдера, в адекватной для моей аудитории сети. На нём стоит Caddy, который терминирует TLS (Let's Encrypt) и проксирует HTTP внутрь дедика через NetBird-меш. DNS у всех публичных доменов смотрит на этот VPS, не на дедик.
- 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 долбит подписочными ошибками и работает не оптимально.
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
Сразу. Не "потом". Потом — никогда не наступает.
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:
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 — система, буферы, оверхед
Полезные настройки самого пула:
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. Открыто только то, что должно быть открыто, остальное молча дропается. Минимальный набор:
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) |
| Базы данных без 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) с 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 на нём нет.
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-е, по такому же принципу:
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 по дизайну — мы доверяем мешу, никакого внешнего трафика тут нет.
Зачем вообще выходная нода
Несколько причин:
- Обход блокировок. Часть OVH-подсетей в РФ режется. Выходная нода стоит у провайдера, чей IP не блокируется. DNS у публичных доменов смотрит туда.
- TLS-терминация в одном месте. Сертификаты выпускает только она, у Let's Encrypt только её IP в challenge. Это упрощает все DNS-cleared check'и и позволяет дедику быть полностью невидимым из интернета по 80/443.
- Дополнительный изоляционный слой. Если кто-то начинает DDoS-ить — он DDoS-ит выходную ноду, не дедик. На выходной ноде у меня минимум данных, пересоздать её — 10 минут.
- Отделение публичного фронта от бэкенда. Я могу спокойно гонять обновления Caddy / экспериментировать на дедике, при этом реальный интерфейс к интернету у меня отдельно и стабильно.
Минус один: добавляется ~5-15 мс к каждому запросу за счёт лишнего хопа. Для веба это незаметно.
Также для некоторых сервисов есть прямой Caddy на хосте на 80/443 без выходной ноды — для тех доменов, которые мне не нужно прокидывать через выходную точку (например, статусные эндпоинты, мониторинг, сервис, где DNS и так смотрит на дедик). Это работает параллельно: Caddy на хосте слушает и :8080 (внутренний от выходной ноды), и :80/:443 (прямой от интернета), маршруты разные.
Миграция: как я переезжал без слёз и почти без даунтайма
Это был самый стрёмный этап. Пять серверов, штук тридцать сервисов, продакшен-форум с живыми пользователями, биллинг с активными подписками, базы данных, которые ни в коем случае нельзя потерять.
Стратегия — миграция по одному сервису, в порядке "от наименее критичного к самому критичному":
- Сначала переехали мелкие тулзы и статические сайты (если что — никто не заметит).
- Потом боты и фоновые сервисы (тоже терпят минуту даунтайма).
- Потом базы данных (с заранее проверенным восстановлением).
- Потом приложения, которые на эти БД смотрят.
- В самом конце — основной форум с длинным финальным раундом sync'а и переключением DNS.
Канал миграции
Сначала я просто поднял NetBird на старых серверах. Это решило сразу две вещи: я могу безопасно ходить по SSH во внутренние сети, и rsync идёт по WireGuard'у через NetBird, не светя данные в открытый интернет.
Команда-боец на каждый сервис была примерно такой:
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 "горячими", это билет на тот свет.
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, у которого активные пользователи постоянно постят, я делал так:
- Поднял всё на новом месте.
- Прогнал валидацию (логин, постинг, аплоад файлов в S3, поиск).
- На старой инсталляции включил
read_only_mode(Discourse это умеет из коробки). - Сделал финальный rsync uploads + финальный дамп БД.
- Поднял на новом, выключил read-only.
- Переключил DNS.
- На старой ничего не трогал ещё неделю — на случай "а вдруг".
Реальный даунтайм для пользователей — около двух минут на сервис.
Сюрпризы по дороге
- 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. Защита от "ой, я только что снёс продакшен-БД":
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 в их облаке.
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(для веб-морды). Если ресетите пароль через CLIsuperuser 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 на каждой VPS | PBS + offsite + ZFS-снэпшоты |
| Восстановление | "ну, день где-то" | 2-5 минут на VM из PBS |
| SSH-доступ | 5 разных портов и ключей | один NetBird-меш с SSO |
| Изоляция сервисов | общая помойка | каждый в своей VM/LXC |
| Снапшот перед апгрейдом | "помолись" | qm snapshot 201 pre-update |
| Веб-морда для рутинных задач | какая ещё веб-морда | Proxmox UI |
Главное, что мне это дало эмоционально — я перестал бояться что-то трогать. Любое опасное действие (апгрейд, миграция, эксперимент) теперь начинается со снэпшота и заканчивается либо коммитом, либо откатом за 30 секунд. Я начал чаще пробовать новое, потому что цена ошибки упала на порядок.
Чек-лист, если будете повторять
Если вы ловите себя на тех же симптомах, что и я — вот короткий чеклист, в каком порядке имеет смысл двигаться:
- Посчитайте, сколько вы платите за все VPS вместе, и сравните с прайсом дедиков у OVH/Hetzner/LeaseWeb. Удивитесь.
- Берите ECC RAM, если планируете ZFS. Не экономьте.
- Берите минимум два диска в зеркале. Один диск — не вариант для прода.
- Накатите Proxmox через KVM/IPMI, не через rescue + debootstrap. Меньше боли.
- Сразу настройте SSH-ключи, нестандартный порт, отключите пароли, поставьте fail2ban.
- Сразу ограничьте ZFS ARC. По умолчанию он сожрёт половину RAM.
- Сделайте второй bridge
vmbr1для приватной сети с NAT. Все VM/LXC туда. Никаких публичных IP на виртуалках. - Поднимите NetBird (или Tailscale, или Headscale) до того, как начнёте мигрировать. Это ваш канал миграции и админский доступ.
- Не открывайте Proxmox UI наружу. Только через VPN-меш.
- Один маленький выходной VPS у другого провайдера для TLS-терминации и обхода блокировок — не обязателен, но очень удобен. Цена копеечная, выгод много.
- PBS в отдельном LXC + offsite sync в S3. Бэкапы должны быть автоматическими.
- Минимум раз в месяц делайте тестовый restore. Иначе у вас не бэкапы, а файлы для самоуспокоения.
- Мигрируйте по одному сервису, в порядке возрастания критичности. Не пытайтесь "перевезти всё за выходные".
- Снижайте TTL на DNS за день до переключения, не за час.
- Ведите доку на каждый сервис: где конфиги, как стартовать, как бэкапить, как восстанавливать. Через полгода вы сами себе скажете спасибо.
Что осталось доделать
Полноты ради: миграция у меня ещё не на 100% закончена. Осталось:
- Пара Remnawave-панелей с старого сервера (запланировано на следующие выходные).
- Перенос Caddy с плагинами (там кастомная сборка), пока работает временно.
- Старый CrowdSec — ставлю отдельно с нуля, не переношу.
Но всё критичное (форумы, биллинг, боты, базы) — уже на дедике, работает стабильно, бэкапится и мониторится.
Если у вас есть вопросы по конкретным шагам, конфигам или граблям, на которые я наступил подробнее — пишите в комментариях, постараюсь развернуть. Особенно интересно услышать, кто как решает похожие задачи на других гипервизорах (XCP-ng, ESXi, или вообще на голом Docker Swarm) — может, я что-то упускаю.
Удачи с консолидацией ваших инфраструктур. Меньше счетов, больше контроля — это того стоит.