--- title: vds-kzntsv network-stack mismatch 2026-05-28 — postmortem + recovery procedure type: concept tags: [vds, rusonyx, network, dhcp, outage, postmortem, recovery, runbook, ifupdown, netplan] sources: [../sources/vds-kzntsv-incident-2026-05-28.md] updated: 2026-05-28 --- # vds-kzntsv network-stack mismatch 2026-05-28 Постмортем + переиспользуемый runbook. Конкретный инцидент: [[../entities/vds-kzntsv]] (89.253.255.94) был недоступен снаружи примерно с **28 мая 2026, ~05:45–08:30 MSK** (~2.5 часа активного outage), плюс ещё ~5.5 часов работы на эфемерной статике до окончательного fix хостером в ~13:52 MSK. Подробная хроника — в [[../sources/vds-kzntsv-incident-2026-05-28]]. **Root cause (по итогу resolution хостером):** наш Ubuntu 24.04 VDS с момента активации (2026-05-20) работал на **двух параллельно активных** сетевых стеках — `netplan` + `systemd-networkd` (от cloud-init + наши манипуляции) **поверх** `ifupdown` + `networking` (provider's expected stack). Их provisioning кладёт конфиг в `/etc/network/interfaces.d/ifcfg-eth0` через `start/ipadd/ipdel` procedure, и ожидает что ifupdown подхватит при boot. networkd на eth0 этому мешает. 8 дней «работало», потому что networkd сам получал DHCP lease и обходил проблему. Когда DHCP-binding слетел на их стороне (28.05 ~05:45-08:00 MSK), их `start/ipadd` procedure не могла восстановить IP через ifupdown, потому что eth0 был «захвачен» systemd-networkd → VM осталась без IP. Окончательное решение: mask `netplan` + `systemd-networkd`, переход на чистый `networking` stack. ## Симптомокартина | Симптом | Где видно | |---|---| | SSH с любого хоста — `No route to host` / `Destination Host Unreachable` | workstation, sibling-VPS [[../entities/books-vds]] | | Hoster gateway `89.253.192.40` отвечает `Destination Host Unreachable` на ARP | внешний `traceroute` падает на 3-м hop | | VM **жива** (через VNC), процессы и docker контейнеры работают | Rusonyx panel → Консоль | | `ip -br link` показывает `eth0 UP LOWER_UP` с MAC | физический NIC ОК | | `ip a show eth0` — **только** `inet6 fe80::*/64 scope link`, **нет IPv4** | DHCP lease отсутствует | | `ip route` — только docker-сети, **нет default** | следствие отсутствия IPv4 | | `networkctl status eth0` → `State: degraded (configuring)`, `Online state: online` | networkd ждёт ответ от DHCP | | `journalctl -u systemd-networkd -b` — повторяющийся `eth0: DHCPv6 lease lost`, **никаких** DHCPv4 offer'ов | DHCP-сервер хостера не отвечает | | LLDP-сосед видим: `Connected To: hw80.rusonyx.ru on port fe:54:00:9c:63:01` | L2 со свитчем здоров → проблема выше | **Анти-симптомы** (что отметает наши конфиг-ошибки): - netplan не менялся днями (см. `git log` если бы был — у нас не versioned, но `stat /etc/netplan/*.yaml`). - Тот же шаблон netplan работает на соседнем VPS [[../entities/books-vds]] (89.253.255.133) у того же хостера. - Reboot VM не помогает (uptime после ребута 1 час, IP так и не получен). - Daily backup завершился успешно за ~3 часа до обнаружения отказа → не «давно сломалось», именно острый отказ. ## Диагностика — алгоритм ```dot digraph diag { "Внешний SSH/ping не доходит" [shape=diamond]; "Открыть VNC → ip -br link" [shape=box]; "Link UP?" [shape=diamond]; "Без NIC — driver/hardware issue" [shape=box]; "ip a show " [shape=box]; "Есть IPv4?" [shape=diamond]; "Сеть работает иначе" [shape=box]; "networkctl status " [shape=box]; "LLDP видит свитч?" [shape=diamond]; "Cable/L2 issue — тикет в DC" [shape=box]; "Соседний VPS у того же хостера работает?" [shape=diamond]; "Глобальный outage — ждать или тикет" [shape=box]; "DHCP server проблема — статика + тикет" [shape=doublecircle]; "Внешний SSH/ping не доходит" -> "Открыть VNC → ip -br link"; "Открыть VNC → ip -br link" -> "Link UP?"; "Link UP?" -> "Без NIC — driver/hardware issue" [label="нет"]; "Link UP?" -> "ip a show " [label="да"]; "ip a show " -> "Есть IPv4?"; "Есть IPv4?" -> "Сеть работает иначе" [label="да"]; "Есть IPv4?" -> "networkctl status " [label="нет"]; "networkctl status " -> "LLDP видит свитч?"; "LLDP видит свитч?" -> "Cable/L2 issue — тикет в DC" [label="нет"]; "LLDP видит свитч?" -> "Соседний VPS у того же хостера работает?" [label="да"]; "Соседний VPS у того же хостера работает?" -> "Глобальный outage — ждать или тикет" [label="нет"]; "Соседний VPS у того же хостера работает?" -> "DHCP server проблема — статика + тикет" [label="да"]; } ``` Ключевые команды для каждого узла: ```bash ip -br link # NIC + state одной строкой на интерфейс ip a show eth0 # есть ли IPv4 sudo networkctl status eth0 # State + LLDP-сосед (Connected To: ...) sudo journalctl -u systemd-networkd -b | grep -iE "dhcp|discover|offer|nack" | tail -20 sudo ls /run/systemd/netif/leases/ # есть ли свежий lease (пусто — нет ни одного) ``` ## Recovery procedure (эфемерная статика — для немедленного восстановления) Используется когда сеть лежит **прямо сейчас** и нужно срочно поднять доступ. Маска **/18** (как прописывает хостер) — это самая чистая форма; gw в той же подсети, дополнительный link-route не требуется: ```bash sudo ip addr add 89.253.255.94/18 dev eth0 sudo ip route add default via 89.253.192.1 dev eth0 ``` DNS (если `/etc/resolv.conf` не сохранился): ```bash sudo bash -c 'echo -e "nameserver 89.253.252.30\nnameserver 89.253.252.31" > /etc/resolv.conf' ``` Альтернативная форма с **/24** (если по какой-то причине /18 не приемлемо — например провайдер изменил routing) — тогда нужны 3 команды с link-route к gw: ```bash sudo ip addr add 89.253.255.94/24 dev eth0 sudo ip route add 89.253.192.1 dev eth0 sudo ip route add default via 89.253.192.1 dev eth0 ``` Проверка: ```bash ip a show eth0 # inet 89.253.255.94/... ip route # default via 89.253.192.1 dev eth0 curl -s -m 5 https://api.ipify.org # должно вернуть 89.253.255.94 ``` **Эфемерность:** прописано через `ip` — на reboot стирается. Для persistence см. ниже § «Permanent fix через ifupdown». ## Permanent fix — через ifupdown stack (то что прописывает хостер) **Это и есть financial state после resolution 2026-05-28**. Это конфигурация **которую хостер ставит/восстанавливает через свой `start/ipadd` procedure** — наш `ifcfg-eth0` это просто слепок того что они кладут. ### /etc/network/interfaces.d/ifcfg-eth0 ```bash # Autoconfigured by Provider's start/ipadd/ipdel procedure. # !!! DO NOT MANUALLY EDIT OR DELETE THIS FILE !!! auto eth0 allow-hotplug eth0 iface eth0 inet static address 89.253.255.94 netmask 255.255.192.0 post-up ip ro add 169.254.0.0/16 dev eth0 metric 400 post-up ip ro add 89.253.192.1 dev eth0 metric 400 post-up ip ro add default via 89.253.192.1 dev eth0 metric 400 post-up ip ro add 89.253.192.0/18 via 89.253.192.1 dev eth0 src 89.253.255.94 metric 400 post-up ip ro del 89.253.192.0/18 dev eth0 proto kernel scope link src 89.253.255.94 ``` **Не редактировать руками** — хостер перезапишет через `start/ipadd` при следующих manipulations с VM (resize, network reset). Если нужны user-specific routes — отдельный файл рядом, **не** редактировать ifcfg-eth0. ### Прибить netplan + systemd-networkd ```bash sudo systemctl disable systemd-networkd.service systemd-networkd.socket systemd-networkd-wait-online.service sudo systemctl mask systemd-networkd.service systemd-networkd.socket systemd-networkd-wait-online.service sudo systemctl mask netplan # preventive — netplan.service unit not exist в Ubuntu, но mask не повредит # verify systemctl is-enabled netplan systemd-networkd networking # expected: masked, masked, enabled ``` **Безопасно на running system** — `disable`/`mask` блокируют только автозапуск на **следующий boot**. Текущий networkd-процесс продолжает работать до явного `stop` или reboot. SSH-сессии и L3-конфиг (IP/маршруты) переживают. ### Запросить provider-side reset Если IP по DHCP не приходит и `/etc/network/interfaces.d/ifcfg-eth0` отсутствует или пустой — тикет в Rusonyx с запросом «настроить дефолтную конфигурацию сети». Они через свою `start/ipadd` procedure пропишут `ifcfg-eth0` и ребутнут VM. После ребута сеть поднимается на ifupdown. ## Kernel cmdline (для понимания почему `eth0`, а не `ens3`) Rusonyx прописывает в GRUB `net.ifnames=0 biosdevname=0` — это **выключает** systemd predictable interface naming. Поэтому интерфейс всегда `eth0`, не `ens3`/`enp0s3` (хотя udev знает их как altnames для `ip a show`). Менять эти флаги в cmdline нельзя — провайдерский `ifcfg-eth0` рассчитан именно на `eth0`. ## Где взять gateway если он неизвестен У Rusonyx и многих российских VDS-хостингов используется **proxy-ARP / unnumbered routing** — gateway сидит в `/18` или `/19` надсети, а наш `/24` гостирует через неё. **Не угадывать `.1` в своей /24** — это не сработает. Источники: 1. **Соседний VPS у того же хостера** — самый надёжный. `ssh sibling 'ip route'` покажет `default via X.X.X.X dev eth0`. Так мы и сделали в этот раз — взяли gw из [[../entities/books-vds]] (89.253.192.1). 2. **Cached lease** на самой VM (если уцелел): `cat /run/systemd/netif/leases/*`, `cat /var/lib/dhcp/dhclient.leases` — поля `ROUTER=`, `DNS=`. В этот раз не уцелело. 3. **Тикет в support** — последний resort, тратит часы. 4. **Активационное письмо** при выдаче VPS — обычно содержит netmask + gw. Но если VPS активирован годы назад — письмо может быть утеряно. ## Root cause (по итогам resolution) **Корректировка относительно нашей первоначальной гипотезы** (которую мы вынесли в тикет на основе наших observations): Первоначально мы атрибутировали отказ полностью инфраструктуре хостера (DHCP server / MAC binding). Это было **частично** верно — что-то в их инфре действительно потеряло binding нашей VM 28.05 в окне ~05:45-08:15 MSK, и их `start/ipadd` procedure пыталась его восстановить. Но **их procedure не смогла справиться** потому что наш сетевой стек был сконфигурирован неправильно: - Поверх их ожидаемого `ifupdown + networking` (под который рассчитан их `start/ipadd`) у нас активны параллельно `netplan + systemd-networkd` (от cloud-init). - `systemd-networkd` «захватывал» eth0 на уровне routing и DHCP, не давая ifupdown'у нормально применить provider's static config. - 8 дней с этим conflict'ом всё работало, потому что networkd сам получал DHCP lease (там где их `ipadd` не успевал) и сеть жила. - Когда что-то на стороне хостера разорвало DHCP binding — networkd не смог получить новый lease (потому что binding с их стороны был broken), а ifupdown с готовой статикой не смог взять интерфейс (потому что networkd ещё держал его в configuring state). **Окончательное решение** хостера (после нашего тикета): `mask netplan + systemd-networkd`, reboot — после boot ifupdown подхватил `ifcfg-eth0` сразу, конфликта нет, сеть работает. **Атрибуция ответственности:** - **Их сторона:** что-то в их сетевой инфре спровоцировало DHCP-binding loss 28.05 (точная причина не указана в ответе — отписка через сутки). Это **trigger** инцидента. - **Наша сторона:** мы наколхозили `netplan + systemd-networkd` поверх их `ifupdown`-stack (отчасти от cloud-init, отчасти при попытках восстановить сеть через VNC). Эта конфигурация **усугубила** инцидент: их auto-recovery не смогла сработать. Это **prolonging factor**. Без conflict'а — инцидент длился бы минуты до auto-recovery. С conflict'ом — длился до нашего тикета + ручного fix хостером. ## Lessons learned (revised) 1. **Уважать provider's expected stack.** Rusonyx — ifupdown. Не накатывать netplan поверх, даже если он «по умолчанию в Ubuntu 24.04». Чистый netplan был бы менее проблемным чем mixed setup, но безопаснее всего — то что хостер использует. 2. **Hostprovider auto-recovery — реальная вещь.** У Rusonyx есть `start/ipadd` procedure которая инжектит IP при reboot/start. Если она работает корректно — DHCP lease loss восстанавливается прозрачно для клиента. Не мешать её работе. 3. **Соsed (sibling VPS) — главный диагностический инструмент.** [[../entities/books-vds]] подтвердил отсутствие глобального outage и дал референс на сетевые параметры (gw, DNS resolvers). 4. **Готовый тикет-шаблон важен.** Хороший initial-ticket с конкретикой (LLDP, journalctl, ping/traceroute) сократил процесс. Хостер увидел что мы не зовём «оно не работает» а уже сделали половину RCA. 5. **Когда хостер просит configuration change — оценить trade-off.** Их framing «у вас неправильный стек» был корректен (по последствиям), но мы первоначально подозревали что они переводят разговор от своего RCA. Reality — **и то и другое одновременно**: их инфра glitched, наш стек не дал auto-recovery, оба фактора важны. 6. **`disable + mask` без `stop` — безопасно на running system.** Не падает SSH, не падает IP. Изменение вступает только на reboot. Это полезный паттерн для «приготовить состояние к чистому reboot, не трогая текущее». ## Anti-pattern **Не делать**: persistent статику через `netplan` для VDS у Rusonyx. Это противоречит их provisioning. Использовать только ifupdown (`/etc/network/interfaces.d/*`), а лучше — попросить их `start/ipadd` procedure сделать это (тикет). ## Lessons learned 1. **DHCP — единая точка отказа на стороне хостера.** Если у них падает DHCP-привязка нашей VM, и lease истёк — мы выпадаем независимо от состояния нашего стека. Mitigation: статика в netplan, **но** это требует договорённости с хостером (они могут autosocket pool — статический IP перестанет работать когда они ребалансируют). 2. **Soсед — главный диагностический инструмент.** [[../entities/books-vds]] был ключом: подтвердил что L2/глобальный outage отсутствует, дал точный gw для статики. **Иметь второй VPS у того же хостера** — это **бесплатный** мониторинг сетевой инфры хостера. 3. **VNC console — обязательный backup-канал.** Без [[rusonyx-vps-onboarding-quirks]] §1-2 и доступа в VNC мы бы не смогли ни диагностировать, ни поднять статику. 4. **Activation-email с netmask+gw — критичный артефакт.** Хранить (в pass-store / pinned email folder). 5. **Cached lease file** — мог бы сократить recovery на 2-3 шага. Не уцелел. Можно периодически бэкапить `/run/systemd/netif/leases/` куда-то persistent? — minor follow-up, vs стоимость не оправдан. 6. **Daily backup-лог = upper-bound оценки downtime.** Если backup прошёл успешно в 05:45 — VDS был жив. Это поможет хостеру в их RCA найти точное окно. ## Cross-refs - [[../entities/vds-kzntsv]] — host details, обновлено note про gw в /18. - [[../entities/books-vds]] — sibling VPS у того же хостера, использован для gw discovery. - [[rusonyx-vps-onboarding-quirks]] — VNC + sshd quirks; quirk #9 добавлен про proxy-ARP gw layout. - [[../sources/vds-kzntsv-incident-2026-05-28]] — полная хроника сессии. - `.tasks/STATUS.md` § disk-89-followups — follow-up GC после recovery.