Привет! Я Даниил, DevOps-инженер в KTS.

Недавно я поработал со Stackland. Концепция облачноподобной платформы с кучей коробочных сервисов на своем железе в закрытом контуре давно была любопытна нам (и нашим заказчикам), так что пройти мимо готового решения от Яндекса было бы преступлением. По горячим следам я решил написать эту статью, чтобы поделиться опытом развертывания Stackland на голом железе. Так что ниже вас ждет скорее не обзор, а туториал.

Коротко о том, как это было: сначала я подготовил L2-сеть, бастион с NAT, DNS и NTP, три сервера, образ Stackland и YAML-конфиги. Затем запустил sladm install и через час получил консоль, готовый мониторинг с визуализацией в Grafana и возможность по кнопке в UI поднять managed PostgreSQL, Kafka, ClickHouse и другие сервисы.

Подробнее о том, как это было, рассказываю ниже.

Оглавление

Немного о том, кому и зачем нужен Stackland

Платформа решает узкую задачу: Kubernetes и managed-сервисы Яндекса внутри вашего периметра без сборки из Helm-чартов.

Смотреть на нее имеет смысл, если данные должны оставаться в своем ЦОДе или арендованном bare metal. Или если команда уже работает с PostgreSQL, Kafka и ClickHouse в экосистеме Яндекса, а легаси живет отдельно в on-prem.

При этом Stackland вряд ли уместен, если вам хватает одного маленького кластера без PaaS. Тогда проще обойтись k3s или managed K8s в облаке. Или если Kubernetes вообще не нужен.

Что нужно подготовить

Чтобы не плодить серверы, я решил не разделять control-plane и worker-ноды, сразу пошел через combined. Согласно доке, на каждую ноду с ролью combined нужно:

  • 32 vCPU;

  • 64 GB RAM;

  • два SSD от 100 GB.

Отдельно нужен бастион на Ubuntu 22.04+, для него хватит минимальной конфигурации. Между нодами нужна L2-связность в приватной /24, адреса можно раздать DHCP или прописать статикой. Еще нужны лицензия key.json от Яндекса, Docker на бастионе и sladm из дистрибутива Stackland. И baseDomain кластера, само собой. У нас это kts.internal, поэтому дальше по тексту я буду везде подставлять его. Если будете следовать моему мануалу, подставляйте свой.

Схема сети

Делим /24 на три зоны (рекомендация из доки):

10.0.0.0/24
├── 10.0.0.0/25     — IP хостов (ноды, бастион)
├── 10.0.0.127      — VIP API Kubernetes (свободный IP)
└── 10.0.0.128/25   — пул LoadBalancer (Cilium L2)

Pod CIDR:     172.16.0.0/16
Service CIDR: 10.96.0.0/12

                    [ Internet ]
                         |
                   [ Бастион ]
                   NAT + BIND + Chrony + VPN
                   10.0.0.3
                         |
        +----------------+----------------+
        |                |                |
    node1            node2            node3
  10.0.0.4         10.0.0.5         10.0.0.6
   combined          combined          combined

Шаг 1. Подсеть и серверы в BareMetal

1.1. VRF (виртуальный сетевой сегмент)

Сначала создаем VRF. Это изолированный L2-домен для кластера.

BMS: создание VRF stackland
BMS: создание VRF stackland

В консоли Yandex BareMetal открываем раздел сетей и жмем Создать VRF. Мы назвали сегмент stackland, описание оставили пустым. Все серверы кластера потом вешаем только на этот VRF.

1.2. Приватная подсеть

BMS: список приватных подсетей 10.0.0.0/24
BMS: список приватных подсетей 10.0.0.0/24

BareMetal → Приватные подсети → Создать подсеть. На скриншоте уже есть 10.0.0.0/24 в пуле m4, привязанная к VRF stackland. При создании указываем CIDR, шлюз (первый IP подсети), включаем DHCP и задаем диапазон для нод.

У нас получилось так: пул ru-central1-m4, CIDR 10.0.0.0/24, шлюз 10.0.0.1, DHCP до .120, VRF с прошлого шага.

1.3. Бастион

Берем минимальную готовую конфигурацию. Нагрузка на бастионе небольшая.

BMS: каталог конфигураций серверов
BMS: каталог конфигураций серверов

BareMetal → Заказать сервер → выбираем пул и тариф из каталога. Для бастиона хватает минимального. Он проксирует трафик, отдает DNS/NTP и с него же запускаем sladm.

BMS: заказ бастиона BA-I207-H
BMS: заказ бастиона BA-I207-H

На шаге Доступ и сеть подключаем приватную подсеть из п. 1.2 и включаем эфемерный публичный IP. На шаге ОС ставим Ubuntu 24.04, добавляем SSH-ключ и пароль root. Жмем Заказать и ждем провижининг.

1.4. Три ноды кластера

Как я уже говорил, для прода нужны 32 vCPU, 64 GB RAM и 2 SSD. На тестовом стенде CPU можно урезать, но второй диск обязателен. Заказываем три сервера без ОС, в той же подсети, без публичного IP, все с ролью combined. Потом грузим их с ISO Stackland.

1.5. Загрузочный образ Stackland

Актуальные релизы лежат тут.

Мы ставили 26.1.5, чтобы потом сравнить с апгрейдом до 26.2.x. ISO лежит в публичном бакете:

https://storage.yandexcloud.net/stackland-public/stackland/26.1.5/images/stackland-amd64-26.1.5.iso

Загружаем образ в BareMetal → Пользовательские образы.

Шаг 2. Настройка бастиона

2.1. VPN и сетевые доступы

Ставим VPN-клиент (WireGuard или Firezone), чтобы ходить в приватную сеть и к DNS на бастионе. Про Firezone я, к слову, подробно рассказывал в этой статье. Многое там до сих пор актуально, так что можете свериться с ней при необходимости.

В security groups открываем подсеть BareMetal 10.0.0.0/24 и подсеть VPN-клиентов.

2.2. UFW и NAT

Без NAT ноды не скачают образы при install. В /etc/ufw/before.rules до блока *filter добавляем:

*nat
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -s 10.0.0.0/24 -j MASQUERADE
-A POSTROUTING -s 100.64.0.0/10 -j MASQUERADE
COMMIT
Бастион: NAT MASQUERADE в /etc/ufw/before.rules
Бастион: NAT MASQUERADE в /etc/ufw/before.rules

По SSH заходим на бастион, открываем /etc/ufw/before.rules и вставляем блок nat с MASQUERADE, как на скриншоте. Так ноды и VPN-клиенты выходят в интернет через бастион. После правки: ufw disable && ufw enable.

Правила UFW:

ufw disable
ufw allow 22/tcp comment 'SSH'
ufw allow from 10.0.0.0/24 to any port 53,123 proto udp
ufw allow from 10.0.0.0/25 to any port 53 proto tcp
ufw allow from 172.16.0.0/16 to any port 53 proto tcp
ufw allow from 172.16.0.0/16 to any port 53 proto udp
ufw enable

Последние две строки критичны. Они пускают pod-сеть 172.16.0.0/16 к DNS на бастионе. Без них CoreDNS не достучится до BIND, и IAM/YDB упадут на install.

2.3. Пакеты

sudo apt update
sudo apt install -y bind9 bind9utils dnsutils chrony unzip docker.io

2.4. BIND

/etc/bind/named.conf.options:

options {
    directory "/var/cache/bind";
    forwarders { 77.88.8.8; 77.88.8.1; };
    listen-on { 10.0.0.3; };
    dnssec-validation auto;
    listen-on-v6 { any; };
};
Бастион: BIND — named.conf.options
Бастион: BIND — named.conf.options

В /etc/bind/named.conf.options BIND слушает только IP бастиона (listen-on), внешние запросы уходят на DNS Яндекса (77.88.8.8). Дальше создаем зоны с именами нод и домена кластера.

Зона internal в /etc/bind/db.internal. NS на бастионе, делегирование домена кластера:

$TTL 1H
@       IN      SOA     ns1.internal. admin.internal. (
                        2025091801 3H 30M 1W 5M)
@       IN      NS      ns1.internal.
ns1.internal.           IN      A       10.0.0.3

kts.internal.           IN      NS      node1.baremetal.internal.
kts.internal.           IN      NS      node2.baremetal.internal.
kts.internal.           IN      NS      node3.baremetal.internal.

В /etc/bind/db.baremetal.internal прописываем A-записи нод и сервисов YDB:

$TTL 15M
@       IN      SOA     ns1.internal. admin.internal. (
                        2025091801 3H 30M 1W 5M)
@       IN      NS      ns1.internal.

node1               IN      A       10.0.0.4
node2               IN      A       10.0.0.5
node3               IN      A       10.0.0.6

ydb-storage-0       IN      A       10.0.0.4
ydb-storage-1       IN      A       10.0.0.5
ydb-storage-2       IN      A       10.0.0.6
Бастион: файлы зон BIND (internal, baremetal)
Бастион: файлы зон BIND (internal, baremetal)

db.internal делегирует kts.internal на ноды. В db.baremetal.internal лежат node1..3 и обязательные ydb-storage-0..2. Подключаем зоны в named.conf.local, гоняем named-checkconf и перезапускаем bind9.

/etc/bind/named.conf.local:

zone "internal" {
    type master;
    file "/etc/bind/db.internal";
};

zone "baremetal.internal" {
    type master;
    file "/etc/bind/db.baremetal.internal";
};
sudo named-checkconf
sudo systemctl restart bind9

2.5. Chrony

В /etc/chrony/chrony.conf:

allow 10.0.0.0/24
bindaddress 10.0.0.3
Бастион: chrony.conf — allow и bindaddress
Бастион: chrony.conf — allow и bindaddress

В chrony.conf разрешаем запросы времени с подсети кластера (allow 10.0.0.0/24) и привязываем сервис к IP бастиона (bindaddress). В cluster.yaml ноды Talos указывают этот адрес в timeservers.

sudo systemctl restart chrony

Шаг 3. ISO на каждую ноду

Через iKVM/IPMI монтируем ISO Stackland как виртуальный CD/DVD.

iKVM: монтирование ISO Stackland (Virtual Media)
iKVM: монтирование ISO Stackland (Virtual Media)

В консоли BareMetal открываем сервер → KVM / iKVM → Virtual Media → подключаем ISO → Reboot to cdrom. Так делаем на каждой из трех нод. В Talos жмем F3 → Network Configuration, задаем hostname, IP, gateway, interface и записываем MAC для hosts.yaml. MAC также виден в обзоре сервера в BMS. Ждем Talos Maintenance на всех трех нодах.

Шаг 4. Проверка DNS перед install

Перед sladm install проверяем, что BIND слушает 10.0.0.3:53, UFW пускает pod-сеть 172.16.0.0/16 на порт 53, а dig @10.0.0.3 node1.baremetal.internal и dig @10.0.0.3 ydb-storage-0.baremetal.internal отвечают. VPN-клиент должен ходить в DNS бастиона для *.kts.internal.

Шаг 5. Конфигурация sladm

На бастионе заводим каталог stackland/:

stackland/
├── cluster.yaml      # StacklandClusterConfig
├── hosts.yaml        # StacklandHostsList
└── secrets.yaml      # секреты, хранить отдельно

cluster.yaml

cluster.yaml
apiVersion: stackland.yandex.cloud/v1alpha1
kind: StacklandClusterConfig
metadata:
  name: main
spec:
  platform:
    type: "baremetal"
    loadBalancer:
      type: "cilium-l2"
      ipPools:
        - cidrs:
            - 10.0.0.128/25

  cluster:
    baseDomain: "kts.internal"

    networking:
      hostsNetwork:
        - cidr: 10.0.0.0/25
      clusterNetwork:
        - cidr: 172.16.0.0/16
      servicesNetwork:
        - cidr: 10.96.0.0/12
      virtualIPs:
        api: 10.0.0.127

    storage:
      defaultStorageClass: "stackland-ssd"

  genericHostConfig:
    disksConfig:
      - installDisk:
          name: "/dev/sda"          # проверьте через lslbk на ноде
      - dataDisk:
          name: "/dev/sdb"          # обязательно второй диск
    networkConfig:
      addresses:
        - interface: "eth0"
          dhcp: true
      routes:
        - to: "0.0.0.0/0"
          via: "10.0.0.3"
          iface: "eth0"
      resolvers:
        - "10.0.0.3"
      timeservers:
        - "10.0.0.3"

hosts.yaml

hosts.yaml
apiVersion: stackland.yandex.cloud/v1alpha1
kind: StacklandHostsList
metadata:
  name: main
spec:
  hosts:
    - hostname: "node1.baremetal.internal"
      role: "combined"
      networkConfig:
        interfaces:
          - macaddress: "d0:0d:2a:7b:a4:f0"   # MAC из BMS / Talos F3
            name: "eth0"
        addresses:
          - interface: eth0
            ip: 10.0.0.4/24

    - hostname: "node2.baremetal.internal"
      role: "combined"
      networkConfig:
        interfaces:
          - macaddress: "d0:0d:13:78:ed:9d"
            name: "eth0"
        addresses:
          - interface: eth0
            ip: 10.0.0.5/24

    - hostname: "node3.baremetal.internal"
      role: "combined"
      networkConfig:
        interfaces:
          - macaddress: "d0:0d:62:20:cf:db"
            name: "eth0"
        addresses:
          - interface: eth0
            ip: 10.0.0.6/24

Шаблон в репозитории: stackland-configs/stackland-baremetal.yaml.example.

Перед install:

./sladm validate stackland/

Шаг 6. sladm install

./sladm install stackland/ --installation-timeout 4h0m0s

iKVM лучше держать открытым на всех нодах весь install. Он занимает около часа, в течение которого устанавливаются Talos, Cilium, LoadBalancer, IAM, консоль и готовый мониторинг. Talos иногда зависает на сети, и без консоли не видно, на какой ноде это случается. Если падает IAM, чаще всего CoreDNS не резолвит ydb-storage-*, и Job ydb-storage-pdisk-set-active уходит в timeout. Детальнее я разберу этот нюанс ниже, в разделе про ошибки.

Шаг 7. Проверка

Проверка: kubectl get nodes — 3 Ready
Проверка: kubectl get nodes — 3 Ready

С бастиона через VPN запускаем kubectl get nodes. Ждем три ноды в Ready. Если статус другой, смотрим iKVM и логи sladm install.

Дальше проверяем kubeconfig и DNS:

cat ~/.kube/config
nslookup auth.sys.kts.internal
Проверка: kubeconfig и nslookup DNS кластера
Проверка: kubeconfig и nslookup DNS кластера

sladm install кладет kubeconfig в ~/.kube/config. nslookup auth.sys.kts.internal с бастиона должен отвечать. Если NXDOMAIN или timeout, возвращаемся к BIND и UFW.

Открываем https://grafana.sys.kts.internal. На дашбордах Node Exporter и etcd должны быть все три ноды.

Grafana: дашборд Node Exporter — node1
Grafana: дашборд Node Exporter — node1

В Grafana (логин из секретов кластера) смотрим Node Exporter: CPU, память, диски. Метрики должны идти со всех нод.

Grafana: дашборд etcd — 3 ноды up
Grafana: дашборд etcd — 3 ноды up

На дашборде etcd все три подняты. Если одна нода упадет, кластер продолжит жить, но установку и апгрейд провести не получится: они будут падать по таймауту, потому что ожидают все три ноды в строю.

Консоль Stackland: https://console.sys.kts.internal, логин admin@stackland.

Консоль: компоненты платформы Ready 26.2.0
Консоль: компоненты платформы Ready 26.2.0

В разделе Компоненты IAM, Ingress, Monitoring, Volumes и остальное в статусе Ready. Install на этом можно считать завершенным.

Консоль: системный дашборд — 3/3 узла online
Консоль: системный дашборд — 3/3 узла online

На главном экране 3 из 3 узлов работают, версия 26.2.0. Отсюда же заказываем managed PostgreSQL, Kafka и ClickHouse.

Самое сладкое: managed-сервисы из консоли

После install можно поднять PaaS из UI без отдельной установки операторов.

PostgreSQL 18

Консоль: создание кластера PostgreSQL kts-pg18
Консоль: создание кластера PostgreSQL kts-pg18

Консоль → Managed PostgreSQL → Создать кластер. Задаем имя, версию, количество хостов, storage class, лимиты CPU/RAM и размер диска. Оператор сам развернет StatefulSet.

Консоль: кластер kts-pg18 в статусе running
Консоль: кластер kts-pg18 в статусе running

Через несколько минут кластер в статусе running. У нас три инстанса, storage class stackland-other, лимиты от 250m/512Mi до 2 CPU/2Gi, диск 2Gi с авторасширением.

Apache Kafka 4.1

Консоль: создание кластера Kafka kafka-kts
Консоль: создание кластера Kafka kafka-kts

Консоль → Managed Kafka → Создать кластер. Задаем имя, количество брокеров, storage class и ресурсы на брокер и контроллер. Все так же, платформа поднимет оператор Kafka сама.

Консоль: Kafka — события PVC provision
Консоль: Kafka — события PVC provision

На вкладке События сначала может мелькнуть timeout на PVC, пока TopoLVM готовит том. Если он висит долго, проверьте свободное место на data-дисках.

ClickHouse 26.3

Консоль: создание кластера ClickHouse ch-kts
Консоль: создание кластера ClickHouse ch-kts

Консоль → Managed ClickHouse → Создать кластер. По уже сложившейся традиции задаем имя, версию, шарды, ресурсы и диск. Для демонстрации я взял минимальную конфигурацию без Keeper и бэкапов.

Что получилось не с первого раза

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

  • CoreDNS направляет запросы на бастион. UFW должен пускать 172.16.0.0/16 на порт 53. Иначе в логах появится dial tcp 10.0.0.3:53: i/o timeout, а IAM/YDB не поднимутся.

  • В зоне baremetal.internal мало прописать только node1..3. Без A-записей ydb-storage-0..2 IAM не стартует.

  • В disksConfig нужны и installDisk, и dataDisk. С одним диском YDB не поднимет storage pool, в логах будет ReasonBootBSError.

  • На bare metal диски обычно /dev/sda и /dev/sdb, не /dev/vda, как на VM. Проверяем lsblk в Maintenance.

  • При делении /24 не ставьте IP нод в пул LoadBalancer 128/25 и зарезервируйте VIP API отдельно.

  • iKVM лучше не закрывать на весь install. Talos может зависнуть на сети, и без консоли непонятно, где именно.

Подробнее про первый пункт. Install может сломаться на IAM из-за DNS или дисков. Job ydb-storage-pdisk-set-active уходит в timeout, если CoreDNS не достучится до BIND. В этом случае проверьте UFW для 172.16.0.0/16 и A-записи ydb-storage-*. Unresolved host ydb-storage-0.baremetal.internal может означать, что зона BIND дописана не до конца. BS_PROXY_DISCOVER и ReasonBootBSError почти всегда про отсутствие dataDisk в конфиге. Если через час видим Reconciliation failed, открываем логи CoreDNS и stackland-iam.

kubectl logs -n kube-system -l k8s-app=kube-dns --tail=50
kubectl logs -n stackland-iam -l app=ydb-storage-0 --tail=100
dig @10.0.0.3 ydb-storage-0.baremetal.internal

Первое приложение (Ingress)

На этом пункте не буду останавливаться подробно, благо документация полностью его покрывает. По туториалу expose-app-domain разворачиваем hello-world за Ingress. Когда у балансировщика появится внешний IP, добавляем DNS-запись *.sys.main.kts.internal.

Что еще есть в Stackland и не вошло в статью

Вместе с Talos и Cilium при sladm install поднимаются IAM, Object Storage, Ingress с cert-manager, Prometheus, Grafana, Loki, OpenBao, Kyverno, TopoLVM и Cilium L2 Load Balancer. Полный список в документации. Я в статье описал мониторинг и косвенно TopoLVM (когда Kafka ждала PVC).

Помимо PostgreSQL, Kafka и ClickHouse, в PaaS еще входят Trino, YTsaurus, Iceberg REST Catalog и векторные БД под RAG. DataLens и SpeechSense идут отдельными лицензиями. OpenSearch и расширение Trino Яндекс обещают подвезти в недалеком будущем.

Итог

Надеюсь, туториал получился не слишком сумбурным, и при необходимости вы сможете с его помощью развернуть Stackland на своем железе.

На BareMetal мы подняли VRF и подсеть, настроили бастион с NAT/BIND/Chrony, поставили три combined-ноды через ISO и sladm, разобрались с DNS и storage, проверили кластер и заказали PostgreSQL, Kafka и ClickHouse из консоли. Для старта работы этого более чем достаточно, а с остальным сложностей возникнуть не должно. Разве что харденинг под продакшен заслуживает отдельной статьи, но это уже выходит за рамки моего туториала.

Если столкнетесь с трудностями, пишите в комментарии, постараюсь подсказать. А если есть желание почитать о других проектах и экспериментах нашей команды, предлагаю вашему вниманию:

Комментарии (1)


  1. r_nastuha
    31.07.2026 20:29

    Вау! Как интересно. Спасибо за статью!