← Kubernetes локально
9 мин · просто · Урок 3 из 8

kind: кластер из контейнеров

Как kind поднимает Kubernetes внутри Docker, как сделать multi-node, пробросить порты наружу и получить работающий Ingress.

kind расшифровывается как Kubernetes IN Docker. Идея простая: то, что в обычном кластере является отдельной машиной, здесь становится обычным контейнером. Один контейнер - одна нода. Внутри контейнера крутится containerd, kubelet и всё остальное, что положено ноде.

Из этого следует главное свойство инструмента. Кластер поднимается за десятки секунд и удаляется одной командой, потому что удалить контейнер дёшево. Ничего не остаётся на диске, кроме образа ноды.

Проект родился как тестовый стенд для самого Kubernetes, и это до сих пор видно по темпу релизов: v0.31.0 в декабре 2025, v0.32.0 в июне 2026, v0.33.0 в августе 2026.

Версии и образы нод

Актуальная версия kind - v0.33.0 от 26 августа 2026. По умолчанию она создаёт кластер на Kubernetes 1.37.0, образ kindest/node:v1.37.0.

Под v0.33.0 собраны образы для четырёх версий Kubernetes:

Есть требование, о которое спотыкаются на Apple Silicon: платформа образа ноды обязана совпадать с платформой хоста. На arm64-машине нужен arm64-образ. Эмуляция здесь не спасает.

Рантайм по умолчанию - Docker версии 20.10 и выше. Podman от 3.0 и nerdctl от 1.7 тоже подходят, выбор переключается переменной окружения:

KIND_EXPERIMENTAL_PROVIDER=podman kind create cluster

У Podman лимит pids по умолчанию 4096. Для кластера этого мало: процессы упираются в лимит, и поды перестают подниматься.

Значения переменной: docker, podman, nerdctl. Отдельно стоит сказать про память. В документации встречаются 6-8 GB RAM, но это требование к сборке собственных образов ноды. Обычная работа с готовым образом столько не просит.

Установка

brew install kind                                                     # macOS
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.33.0/kind-linux-amd64  # Linux
chmod +x ./kind && sudo mv ./kind /usr/local/bin/kind
winget install Kubernetes.kind                                        # Windows

Жизненный цикл кластера

kind create cluster
kind create cluster --name lab --image kindest/node:v1.35.8
kind delete cluster --name lab
kind get clusters

Без флагов кластер получает имя kind, контекст в kubeconfig прописывается автоматически. --image задаёт версию Kubernetes, kind get clusters показывает, что сейчас живёт на машине.

Несколько нод

Одна нода закрывает большинство сценариев, но планировщик на ней изучать неудобно. Чтобы получить несколько нод, нужен конфиг-файл:

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: worker

Запуск с этим файлом:

kind create cluster --config kind-multi.yaml

Каждая запись в nodes превращается в отдельный контейнер.

Порты наружу

браузер localhost:30080 hostPort нода kind containerPort 30080 nodePort Service nodePort 30080 targetPort Pod targetPort 8080
Число одно и то же: containerPort в конфиге kind равен nodePort в Service.

Кластер живёт в контейнерах, а контейнеры по умолчанию не отдают свои порты хосту. На macOS и Windows это обязательный шаг, без него до сервисов не достучаться. Пробрасывают порты через extraPortMappings в конфиге:

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 80
    hostPort: 80
    listenAddress: "127.0.0.1"
    protocol: TCP

Отдельный случай - NodePort. Тут есть тонкость, на которой теряют время: containerPort в конфиге kind и nodePort в Service должны совпадать. Проброс делается один раз при создании кластера, а Service появляется позже, и подогнать придётся именно Service под уже открытый порт.

LoadBalancer и Ingress

Service типа LoadBalancer в kind сам по себе повиснет без внешнего адреса: раздавать адреса некому. Эту роль берёт отдельный бинарь cloud-provider-kind, текущая версия v0.11.1 от 26 июня 2026.

Начиная с v0.9.0 (28 октября 2025) он умеет обслуживать не только LoadBalancer, но и Ingress. Документация kind после этого была переписана и теперь прямо говорит: «No third-party ingress controllers are required by default». Отдельный ingress-контроллер ставить не нужно. Gateway API там тоже включён, канал выбирается флагом --gateway-channel со значениями standard, experimental или disabled.

brew install cloud-provider-kind
kind create cluster
cloud-provider-kind        # держать запущенным
kubectl get ingress        # ADDRESS заполнится

Процесс cloud-provider-kind должен работать всё время, пока нужен внешний адрес. Закрыли терминал - адреса перестали выдаваться.

Полный пример из документации kind:

kind create cluster
kubectl apply -f https://kind.sigs.k8s.io/examples/ingress/usage.yaml
INGRESS_IP=$(kubectl get ingress example-ingress -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
curl ${INGRESS_IP}/foo

Одна деталь про однонодовый кластер. По умолчанию в kind поды садятся на control-plane, а балансировщик такие ноды исключает. Метку нужно снять:

kubectl label node kind-control-plane node.kubernetes.io/exclude-from-external-load-balancers-

Дефис в конце имени метки - это синтаксис удаления, не опечатка.

На macOS и Windows ноды kind недостижимы с хоста напрямую, поэтому cloud-provider-kind включает режим --enable-lb-port-mapping. Он поднимает вспомогательный контейнер с именем вида kindccm-… и вешает на него эфемерный порт хоста. Ходить нужно на localhost:<этот порт>, а не на адрес, который показывает kubectl get svc.

Хранилище

StorageClass в kind есть из коробки: standard, помеченный как дефолтный. PersistentVolumeClaim без указания класса подхватит именно его.

Что обычно ломается

Не хватает inotify. Симптом расплывчатый: поды не поднимаются, в логах компонентов мелькают ошибки про слежение за файлами. Лечится на хосте:

fs.inotify.max_user_watches=524288
fs.inotify.max_user_instances=512

Rootless-режим. Требует cgroup v2 и делегирования cpu-контроллера. С systemd версии 252 и выше делегирование включается само, на более старых системах его надо настраивать руками.

Не загружены модули ядра. В rootless-конфигурациях часто не подгружены ip_tables и iptable_nat. Сеть кластера при этом ломается целиком, а сообщение об ошибке на причину не указывает.

WSL2 и xt_recent. В ядре WSL2 нет модуля xt_recent. Из-за этого не работает sessionAffinity: ClientIP, и ломается не только сам Service с этой настройкой, но и Service, созданные после него.

Коротко

Прошли материал до конца? Отметьте урок пройденным.