Миграция k3s на VLAN и восстановление etcd

TL;DR

Сегодня выдался самый насыщенный день в работе над инфраструктурой. Я перевёл весь k3s-кластер с плоской сети на полноценную VLAN-архитектуру: Server VLAN 20 для узлов и сервисов k3s, Storage VLAN 30 для NAS и существующий VLAN 1 по умолчанию для клиентских устройств. Это потребовало смены IP-адресов на всех виртуальных машинах, обновления MetalLB, перенастройки Traefik и восстановления после потери кворума etcd — случилось это потому, что я одновременно перенёс слишком много узлов. Заодно я развернул медиастек (Jellyfin, Radarr, Sonarr, Prowlarr, Jellyseerr) и настроил инфраструктуру проброса Intel iGPU.

Сеть до миграции

Все устройства находились в единой плоской сети 192.168.1.0/24:

192.168.1.0/24 (VLAN 1 — Default)
├── Client devices
├── Proxmox hosts
├── k3s VMs
├── NAS
├── IoT devices
└── Everything else

Такая схема работает, но порождает ряд проблем:

  • Нет изоляции трафика между рабочими нагрузками k3s и клиентскими устройствами

  • Невозможно задать разные правила брандмауэра для разных типов трафика

  • Широковещательный домен охватывает все устройства сети

  • Трафик хранилища конкурирует с обычным сетевым трафиком

Сеть после миграции

VLAN 1 — Default (192.168.1.0/24)
├── Client devices
├── IoT devices
└── Management traffic
VLAN 20 — Server (192.168.20.0/24)
├── Proxmox hosts (.105-.108)
├── k3s servers (.20-.22)
├── k3s agents (.30-.33)
├── MetalLB pool (.200-.220)
└── Service load balancers
VLAN 30 — Storage (192.168.30.0/24)
├── NAS (TrueNAS)
├── Seedbox
└── Backup targets

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

Миграция должна была пройти без остановки кластера — длительный простой меня не устраивал. План был такой:

  • Настроить VLAN 20 и VLAN 30 на коммутаторе

  • Настроить межсетевую маршрутизацию (inter-VLAN routing) на брандмауэре

  • Создать Proxmox-бридж для VLAN 20 на каждом хосте

  • Переносить по одному узлу k3s за раз: сменить IP, убедиться в работоспособности, переходить к следующему

  • Обновить пул IP-адресов MetalLB

  • Обновить DNS-записи

  • Перенести NAS на VLAN 30

Что произошло на самом деле

Шаги 1–3 прошли без сюрпризов. На шаге 4 начались приключения.

Катастрофа с etcd

Первый серверный узел я перенёс успешно: изменил его IP в Terraform, применил конфигурацию, и виртуальная машина поднялась уже в новой сети. Два оставшихся сервера удерживали кворум etcd.

Потом терпение мне изменило. Вместо того чтобы переносить узлы по одному, я одновременно перенёс server-2 и server-3. Когда оба поднялись с новыми IP-адресами, etcd не смог сформировать кворум — все три члена кластера имели адреса, не совпадающие с теми, что были зафиксированы в существующем состоянии кластера.

etcd: cluster ID mismatch
etcd: member not found
kube-apiserver: connection refused

Управляющий слой (control plane) упал. kubectl возвращал ошибки подключения. Кластер находился в неработоспособном состоянии.

Восстановление

Меня спас флаг --cluster-reset в k3s:

# На k3s-server-1 (первый перенесённый узел, с наиболее актуальными данными)
sudo systemctl stop k3s
sudo k3s server --cluster-reset
# Это переинициализирует etcd как однозлоузловой кластер
# После запуска — подключить остальные серверы заново

# На k3s-server-2
sudo systemctl stop k3s
sudo rm -rf /var/lib/rancher/k3s/server/db
sudo systemctl start k3s

# На k3s-server-3
sudo systemctl stop k3s
sudo rm -rf /var/lib/rancher/k3s/server/db
sudo systemctl start k3s

Восстановление заняло около 20 минут. Все прикладные данные уцелели — тома Longhorn и данные PostgreSQL от etcd не зависят. Состояние etcd (объекты Kubernetes API) было восстановлено с узла, на котором выполнялся сброс.

Правильный порядок действий

Для будущих миграций — верная последовательность для HA-кластера k3s:

  • Переносить по одному серверному узлу за раз

  • После каждого переноса проверять кворум etcd: etcdctl member list

  • Не переходить к следующему узлу, пока не подтверждён кворум

  • Агентные (agent) узлы можно переносить параллельно — они не участвуют в работе etcd

Миграция пула MetalLB

После переноса всех узлов на VLAN 20 MetalLB потребовался новый пул IP-адресов:

apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: server-vlan
  namespace: metallb-system
spec:
  addresses:
  - 192.168.20.200-192.168.20.220

Все сервисы типа LoadBalancer получили новые внешние IP. Я обновил DNS-записи для всех сервисов, указав новые адреса.

Изоляция NAS во VLAN

NAS был перенесён на VLAN 30 (Storage). Для этого потребовалось настроить порты коммутатора, подключённые к NAS, как access-порты на VLAN 30, а также задать правила межсетевой маршрутизации:

  • VLAN 20 (k3s) → VLAN 30 (NAS): разрешить трафик NFS/SMB

  • VLAN 1 (клиенты) → VLAN 30 (NAS): разрешить SMB для доступа к медиафайлам

  • VLAN 30 → Интернет: запретить (NAS не нужен доступ в интернет)

Приложения k3s обращаются к NAS через NFS-монтирования, которые теперь пересекают границу VLAN через брандмауэр. Влияние на производительность ничтожно — брандмауэр выполняет межсетевую маршрутизацию аппаратно.

Развёртывание медиастека

Разобравшись с сетевой архитектурой, я развернул медиастек.

Jellyfin

Медиасервер с аппаратным перекодированием через проброс Intel UHD 630 iGPU. Инфраструктура для проброса GPU была настроена сегодня: на pve-3 включён IOMMU, загружены модули VFIO, драйвер i915 занесён в чёрный список, а iGPU пробрасывается в виртуальную машину k3s-agent-3.

Стек *arr

Radarr: управление фильмами и отслеживание качества
Sonarr: управление сериалами и отслеживание качества
Prowlarr: управление индексерами (передаёт данные в Radarr и Sonarr)
Jellyseerr: портал запросов медиаконтента для пользователей

Всё развёрнуто в пространстве имён media с NFS-монтированиями к NAS для хранения медиафайлов:

volumes:
- name: media
  nfs:
    server: 192.168.30.10
    path: /mnt/pool/media

Мониторинг медиастека

Рядом с Radarr и Sonarr я развернул sidecar-контейнеры Exportarr. Exportarr экспортирует метрики приложений (длина очереди, статус загрузок, размер библиотеки) в формате Prometheus. Сопутствующие дашборды Grafana отображают:

  • Размер медиабиблиотеки и скорость её роста

  • Длину очереди загрузок и процент завершения

  • Распределение по профилям качества

  • Динамику использования дискового пространства

Обновление правил UFW

На каждом узле потребовалось обновить правила брандмауэра UFW под новые диапазоны IP:

# Разрешить трафик k3s API из нового диапазона VLAN 20
sudo ufw allow from 192.168.20.0/24 to any port 6443
# Разрешить Flannel VXLAN
sudo ufw allow from 192.168.20.0/24 to any port 8472
# Разрешить kubelet
sudo ufw allow from 192.168.20.0/24 to any port 10250

Применение на всех узлах выполнил Ansible.

Выводы

Никогда не переносите несколько членов etcd одновременно. По одному, с проверкой кворума после каждого шага — это главный урок дня.

Знайте эту команду. Проверьте её заранее. Когда etcd выходит из строя, именно она спасает: k3s server --cluster-reset.

VLAN кардинально улучшают безопасность инфраструктуры. Изоляция трафика хранилища означает, что скомпрометированный pod k3s не сможет перехватить трафик NAS. Правила межсетевой маршрутизации обеспечивают принцип минимально необходимых привилегий на уровне сети.

NFS через VLAN работает без проблем. Накладные расходы на межсетевую маршрутизацию для NFS-трафика с современными брандмауэрами пренебрежимо малы.

Сначала спланируйте сетевую миграцию на бумаге. Нарисуйте схемы «до» и «после», составьте список всех IP, которые изменятся, и выстройте порядок изменений так, чтобы кворум не нарушался.

Это был самый масштабный день для инфраструктуры на сегодняшний момент. Кластер теперь работает на нормальной сетевой архитектуре, а медиастек запущен. Завтра: автоматизация получения и синхронизации медиаконтента.

© 2026 meganuke