k3s HA-кластер на Proxmox: Ansible, Rancher и Traefik

Как я превратил четыре узла Proxmox, несколько корпоративных дисков и один свободный вечер в полностью автоматизированный отказоустойчивый кластер k3s с Rancher, Traefik и Ansible — и всё это работает на железе, которое потребляет меньше энергии, чем игровой ПК.

Я довольно давно занимаюсь домашней лабораторией: четырёхузловой кластер Proxmox, на котором крутятся Pi-hole, Nextcloud, Immich (моя замена Google Photos) и ещё несколько сервисов. Всё это работало как LXC-контейнеры или виртуальные машины, управляемые вручную через интерфейс Proxmox. Схема работала, но не масштабировалась, воспроизвести её с нуля было нельзя, а каждый раз, когда нужно было добавить новый сервис, приходилось снова открывать консоль и повторять один и тот же ритуал настройки.

Я хотел Kubernetes. Не управляемый облачный — тот, который строишь сам, понимаешь изнутри и можешь снести и поднять заново за 15 минут. Ниже — история этой сборки.

Железо: используй то, что есть

Мой кластер — это смесь мини-ПК и одного корпоративного сервера:

Первые три узла — компактные, тихие и экономичные: идеально для круглосуточной работы. Четвёртый — сервер Dell с 56 ядрами и половиной терабайта оперативной памяти. Он потребляет 300–400 Вт в режиме простоя, поэтому включается только по необходимости — для тяжёлых вычислений, а потом снова выключается.

Восемь SAS-дисков в pve04 — корпоративные накопители с частотой вращения шпинделя 10 000 об/мин. Я настроил их как пул ZFS raidz2, что даёт 6 ТБ используемого пространства при допустимом отказе двух дисков одновременно.

Выбор архитектуры: k3s

Я выбрал k3s по нескольким причинам. Узлы на N95 имеют всего 4 ядра и 12–16 ГБ оперативной памяти — полноценный kubeadm с отдельным etcd съел бы слишком много ресурсов. k3s упаковывает всё в один бинарный файл: API-сервер, controller-manager, планировщик и встроенный etcd. Серверный узел k3s потребляет около 500 МБ RAM на управляющий слой (control plane), оставляя остальное для рабочих нагрузок.

Топология высокой доступности (HA) строится на встроенном etcd, распределённом между тремя серверными узлами. Три — минимум для консенсуса: etcd использует алгоритм Raft, который требует большинства (кворума) для подтверждения записей. При трёх узлах любой один из них может отказать, и кластер продолжит работу в штатном режиме.

k3s-master-1 (pve01) ──┐
k3s-master-2 (pve02) ──┤── кворум etcd (2 из 3 = работоспособен)
k3s-master-3 (pve03) ──┘
k3s-worker-1 (pve04) ───── агент по требованию (с taint)

Важное архитектурное решение: я не добавлял taint на серверные узлы. В отличие от стандартных кластеров kubeadm, мои мастер-узлы k3s принимают рабочие нагрузки. В трёхузловой домашней лаборатории каждый узел на счету — выделять их исключительно под управляющий слой означало бы впустую потратить 90% доступных ресурсов. Лёгкие сервисы, которые я запускаю (DDNS updater, n8n и другие), спокойно уживаются рядом с etcd.

Узел по требованию: Kubernetes встречает управление питанием

Рабочий узел pve04 — самая интересная часть. На нём установлен taint on-demand=true:NoSchedule, что означает: никакие поды не попадут туда без явного указания допуска (toleration). Когда сервер выключен, его поды остаются в состоянии Pending — никакого сбоя в работе кластера.

При включении агент k3s автоматически переподключается (он сохраняет свою идентичность между перезагрузками), узел переходит в состояние Ready, и планировщик Kubernetes размещает на нём поды с соответствующим допуском. Когда работа завершена, я освобождаю узел (drain) и выключаю его:

kubectl drain k3s-worker-1 --ignore-daemonsets --delete-emptydir-data
# Затем выключить через Proxmox или IPMI

Эта схема отлично подходит для тяжёлых задач: Ollama (для вывода больших языковых моделей нужны все 504 ГБ RAM) и пакетная обработка Immich ML (распознавание лиц в 332 ГБ фотографий выигрывает от 56 ядер).

kube-vip + MetalLB: стабильная сеть без облачного провайдера

Нужно было решить две задачи: у Kubernetes API должна быть единая стабильная точка входа (не привязанная к конкретному узлу), а сервисы должны получать настоящие IP-адреса, доступные с устройств в домашней сети.

kube-vip работает как DaemonSet на узлах управляющего слоя и управляет плавающим виртуальным IP-адресом (VIP, 192.168.1.60) для API-сервера. Один из подов становится лидером через механизм объектов Kubernetes Lease и привязывает VIP с помощью gratuitous ARP. Если лидер падает, другой под перехватывает роль за считаные секунды. Все команды kubectl и подключения агентов направляются на этот VIP.

MetalLB в режиме L2 выдаёт IP-адреса типа LoadBalancer из зарезервированного пула (192.168.1.61–199). Traefik получает первый адрес (192.168.1.61), который становится единой точкой входа для всего HTTP/HTTPS-трафика. Этот диапазон я зарезервировал в DHCP-сервере pfSense, чтобы избежать конфликтов.

Traefik как универсальный Ingress

Я отключил встроенный Traefik в k3s и установил его через Helm с собственной конфигурацией — так я получаю полный контроль над версией чарта и параметрами.

Интересная задача: не все мои сервисы работают внутри Kubernetes. Immich и Nextcloud — это LXC-контейнеры на хостах Proxmox, а не поды. Traefik внутри k3s должен маршрутизировать трафик к этим внешним IP-адресам.

Решение — Kubernetes Service без селектора в паре с ресурсом Endpoints, заполненным вручную:

apiVersion: v1
kind: Service
metadata:
  name: immich
  namespace: external-services
spec:
  ports:
    - port: 2283
      targetPort: 2283
---
apiVersion: v1
kind: Endpoints
metadata:
  name: immich
  namespace: external-services
subsets:
  - addresses:
      - ip: 192.168.1.20    # IP LXC-контейнера
    ports:
      - port: 2283

Для Traefik это ничем не отличается от сервиса, за которым стоят поды. IngressRoute с нужным условием по заголовку Host маршрутизирует трафик, завершает TLS и перенаправляет запросы в LXC-контейнер. Это позволило полностью отказаться от Nginx Proxy Manager — один ingress-контроллер для всего, управляемый декларативно.

Для TLS я использую два ClusterIssuer от cert-manager: самоподписанный CA для внутренних сервисов (Rancher, панель Traefik) и Let’s Encrypt через DNS-01 Cloudflare для сервисов, открытых в интернет. DNS-01 идеален для домашней лаборатории — работает за NAT без необходимости открывать порт 80 и поддерживает wildcard-сертификаты.

Ansible: инфраструктура как код

Каждый ручной шаг — это будущая отладка. Я автоматизировал весь стек с помощью Ansible: от подготовки ОС до развёртывания Rancher. Плейбук идемпотентен: повторный запуск ничего не изменит.

homelab-k3s/
├── inventory/
│   ├── hosts.yml                    # IP-адреса и роли узлов
│   └── group_vars/all.yml           # Версии, IP-адреса, зашифрованный токен
├── roles/
│   ├── common/                      # Подготовка ОС, пакеты, sysctl, модули
│   ├── k3s-server/                  # Начальная загрузка и присоединение с kube-vip
│   ├── k3s-agent/                   # Присоединение агента с taint и метками
│   └── rancher/                     # MetalLB, cert-manager, Traefik, Rancher
├── playbooks/
│   ├── site.yml                     # Полное развёртывание
│   └── reset.yml                    # Полный снос
└── ansible.cfg

Токен кластера зашифрован с помощью ansible-vault — файл с паролем хранится за пределами репозитория. Это позволяет держать проект открытым на GitHub, не раскрывая секреты.

Полное развёртывание от чистых виртуальных машин до работающего кластера с Rancher занимает около 15 минут:

ansible-playbook playbooks/site.yml

Полный снос и повторная сборка с нуля:

ansible-playbook playbooks/reset.yml
ansible-playbook playbooks/site.yml

В процессе разработки я пересобирал кластер четыре раза: исправлял совместимость чарта Traefik, ограничения по версии Rancher и конфликт IP-адресов kube-vip. Каждая пересборка проходила безболезненно, потому что каждое исправление шло в плейбук, а не в конфигурацию узла.

Усвоенные уроки

Фиксируйте версии. Последний Helm-чарт Traefik (v39.x) изменил схему значений — ports.web.redirectTo.port стал недействительным. Rancher v2.13.3 пока не поддерживает Kubernetes 1.35. В итоге я остановился на k3s v1.34.5, Traefik v3.6.11 и Rancher v2.13.3 — комбинация, работоспособность которой я проверил.

kube-vip оставляет устаревшие IP после сброса кластера. Когда я сносил k3s и пересобирал его, VIP (192.168.1.60) по-прежнему был привязан к eth0 от предыдущего экземпляра kube-vip. k3s видел два IP на интерфейсе и отказывался запускаться. Решение — ip addr del 192.168.1.60/32 dev eth0 и добавление --node-ip в флаги установки k3s.

Виртуальным машинам с cloud-init нужно правильное имя сетевого интерфейса. Виртуальные машины Proxmox с virtio NIC в Debian 12 определяются как eth0, а не ens18, как предполагают некоторые руководства. Проверяйте имя командой ip link show перед тем, как писать переменные Ansible.

ZFS: ashift=12 — не обсуждается. Даже если эти SAS-диски сообщают о секторах по 512 байт, они лучше всего работают с выравниванием по 4К. Всегда устанавливайте ashift=12 при создании пула — после этого изменить параметр невозможно.

Что работает сегодня

Постоянно включённый уровень (pve01–03):

  • Pi-hole — DNS для всей сети (LXC, 512 МБ)

  • Immich — фотобиблиотека (LXC, 4 ГБ)

  • Nextcloud — синхронизация и обмен файлами (LXC, 1 ГБ)

  • pfSense — межсетевой экран и маршрутизатор (VM, 1 ГБ)

  • Кластер k3s HA — 3 мастер-узла с Rancher, Traefik, MetalLB, cert-manager

Уровень по требованию (pve04):

  • Рабочий узел k3s — 16 виртуальных ЦП, 128 ГБ RAM для задач AI/ML

  • ZFS raidz2 — 6 ТБ архивного хранилища и резервных копий

Репозиторий

Полный проект Ansible открыт:

Склонируйте, подставьте свои IP-адреса и количество узлов, сгенерируйте токен и запустите ansible-playbook playbooks/site.yml. В README есть полное руководство по быстрому старту.

Итоги

Эта сборка научила меня Kubernetes изнутри лучше, чем любые курсы или сертификации. Когда сам настраиваешь кворум etcd, отлаживаешь выбор лидера kube-vip и отслеживаешь путь пакета от MetalLB через Traefik до LXC-контейнера — вырабатывается интуитивное понимание того, как всё устроено, которое никакая документация не даст.

Домашняя лаборатория никогда не бывает «готова» — и в этом весь смысл. В планах: Longhorn для распределённого хранилища, GitOps через Fleet (уже установлен через Rancher) и в конечном счёте перенос Immich в кластер, как только я буду уверен в надёжности хранилища.

Если вы держите домашнюю лабораторию на Proxmox и сомневаетесь, стоит ли Kubernetes своей сложности — стоит. Но только при условии, что автоматизируете всё с самого начала. Ansible-плейбук ценнее самого кластера.

© 2026 meganuke