Инструменты установлены (см. главу про подготовку рабочего места) — пора поднять настоящий Kubernetes-кластер прямо на ноутбуке. Через пару минут у вас будет работающий кластер dev, в который мы дальше заедем с сервисом myapp (HTTP API на Python 3.12 + FastAPI, порт 8080, зависит от PostgreSQL). Делать это мы будем с помощью k3d — и сначала разберёмся, что это вообще такое.

Что такое k3d и почему он быстрый и лёгкий

Начнём с терминов, чтобы не путаться в похожих названиях.

  • k3s — это полноценный, но минималистичный дистрибутив Kubernetes от Rancher Labs. Весь control plane (управляющие компоненты кластера: API-сервер, планировщик, контроллеры) упакован в один бинарник, а внешние зависимости сведены к минимуму — нужны лишь современное ядро Linux и смонтированные cgroups.
  • k3d — это, по официальному определению, «лёгкая обёртка для запуска k3s в Docker». То есть k3d сам по себе не Kubernetes; он запускает узлы (ноды) кластера k3s как обычные Docker-контейнеры и управляет ими.

Откуда берётся лёгкость? Обычный Kubernetes хранит состояние кластера в etcd — распределённой базе данных «ключ-значение». k3s по умолчанию вместо etcd использует встроенный SQLite — по сути обычный файл. В документации k3s это сформулировано прямо: «SQLite — хранилище по умолчанию, и оно будет использовано, если не настроено другое». Поднимать и поддерживать etcd-кластер не нужно, поэтому старт получается быстрым, а потребление памяти — скромным.

Насколько быстрым? По единичному бенчмарку 2024 года (все инструменты на Docker-драйвере) k3d стартует порядка нескольких секунд и потребляет заметно меньше памяти, чем kind или minikube. Точные числа сильно зависят от железа, ОС (Apple Silicon, WSL2) и версий, поэтому относитесь к ним как к ориентиру, а не как к гарантии. Главный практический вывод: кластер k3d поднимается и удаляется настолько быстро и дёшево, что пересоздать его с нуля — рутинная, а не пугающая операция.

Один нюанс на будущее: SQLite не работает с несколькими server-нодами. Если захотите имитировать высокодоступный (HA) control plane из нескольких серверов, k3s автоматически переключится на встроенный etcd. Для одиночного локального кластера это неважно, но знать полезно.

Когда вместо k3d брать kind или minikube

k3d — отличный дефолт для ежедневной разработки, но не единственный вариант. Коротко, кто для чего:

ИнструментДля чего
k3dПро скорость и лёгкость. Идеален для inner dev loop (см. главу 1) и бюджетного CI, где важно быстро поднять-погасить кластер.
kindПро паритет с продом. kind (Kubernetes IN Docker) запускает те же upstream-бинарники, что и «большой» Kubernetes, поэтому ближе всего к проду; именно на kind гоняется CI самого проекта Kubernetes и conformance-проверки (тесты соответствия). Берите kind, когда нужен строгий паритет или вы тестируете тонкие внутренности K8s.
minikubeПро обучение и реалистичную симуляцию. Даёт VM-изоляцию на уровне ОС и богатый набор аддонов, удобен, когда осваиваешь Kubernetes.

Важная оговорка про k3d: поскольку это k3s, а не полный upstream-Kubernetes, отдельные пограничные случаи и продвинутые функции/политики могут вести себя иначе. Большинство приложений (включая наш myapp) разницы не заметят, но для строгого policy-паритета (например, audit logging) понадобится явная донастройка. Если ваша цель — максимально воспроизвести прод, см. главу про приближение к проду.

k3d cluster create: основные флаги

Итоговая команда — выполните её один раз и используйте дальше.

Эта команда сразу поднимает кластер dev со встроенным registry и проброшенным портом — всё, что нужно для глав 6–11. Выполнив её, вам не придётся пересоздавать кластер по ходу статьи:

bash
1k3d cluster create dev \
2  --servers 1 --agents 2 \
3  --registry-create k3d-registry.localhost:5000 \
4  -p "8081:80@loadbalancer"

Ниже разбираем флаги по шагам — но рабочая команда, с которой мы идём через всю статью, именно эта. Все остальные примеры в этой главе показаны для объяснения отдельных флагов.

Самая простая команда создаёт кластер с именем по умолчанию:

bash
1k3d cluster create mycluster

Но мы сразу создадим наш рабочий кластер dev осмысленно. Вот ключевые флаги из официальной справки:

ФлагЧто делает
--servers, -sСколько server-нод (control plane) создать
--agents, -aСколько agent-нод (воркеров) создать
--port, -pПробросить порт с нод наружу, на хост
--api-portЗафиксировать порт API-сервера Kubernetes
--registry-createСоздать встроенный registry и подключить к кластеру
--registry-useПодключить уже существующий k3d-registry
--image, -iЗадать образ k3s (то есть версию Kubernetes)
--k3s-argПередать дополнительные аргументы самому k3s
--volume, -vСмонтировать тома (volumes) внутрь нод
--waitЖдать готовности server-нод (включён по умолчанию)

Создадим dev с одним control plane и двумя воркерами:

bash
1k3d cluster create dev --servers 1 --agents 2

Если для паритета с продом нужна конкретная версия Kubernetes — задайте её образом k3s:

bash
1k3d cluster create dev --image rancher/k3s:v1.31.5-k3s1

(номер версии здесь — пример; подставьте ту, что соответствует вашему проду.)

Контексты kubectl: где мы сейчас и как переключаться

kubectl — клиент Kubernetes — должен знать, к какому кластеру обращаться. За это отвечает контекст. По документации Kubernetes контекст — это тройка: кластер, пользователь и namespace (логический раздел внутри кластера).

Когда k3d создаёт кластер, он автоматически добавляет контекст с именем k3d-<имя-кластера> и переключает kubectl на него. То есть для нашего dev контекст называется k3d-dev, а не просто dev — на этом легко споткнуться. А если создать кластер вообще без имени, он будет называться k3s-default с контекстом k3d-k3s-default.

Полезные команды:

bash
1kubectl config current-context        # где мы сейчас
2kubectl config get-contexts           # список контекстов (* = текущий)
3kubectl config use-context k3d-dev    # переключиться на наш кластер
4kubectl config view --minify          # показать конфиг только текущего контекста

Если по какой-то причине контекст не переключился сам, можно слить kubeconfig вручную и сразу переключиться:

bash
1k3d kubeconfig merge dev --kubeconfig-switch-context

Привыкните проверять current-context перед опасными командами — это спасает от случайного kubectl delete не в том кластере.

Встроенный registry образов в k3d

Чтобы запустить myapp в кластере, его Docker-образ (соберём его в главе про контейнеризацию) надо где-то хранить так, чтобы кластер мог его скачать. Гонять образы через публичный registry на каждой итерации — медленно и неудобно. k3d умеет поднимать собственный локальный registry прямо рядом с кластером.

Наш сквозной registry называется k3d-registry.localhost:5000 — это имя мы используем во всех главах статьи. Самый простой путь — создать его вместе с кластером (документация по registries):

bash
1k3d cluster create dev --registry-create k3d-registry.localhost:5000

Здесь k3d-registry.localhost — имя registry, а 5000 — порт, который пробрасывается на хост (0.0.0.0:5000). Можно создать registry и отдельно — тогда он переживёт пересоздание кластера и его можно подключать к разным кластерам:

bash
1# создать отдельный registry на порту 5000
2k3d registry create registry.localhost --port 5000
3
4# подключить его при создании кластера (обратите внимание на префикс k3d-!)
5k3d cluster create dev --registry-use k3d-registry.localhost:5000

Важная деталь: k3d добавляет к имени registry префикс k3d-. Поэтому хотя в k3d registry create мы указали registry.localhost, в --registry-use (и в image: манифестов) фигурирует полное имя k3d-registry.localhost:5000 — иначе подключение не найдёт registry. Порт 5000 здесь не магический: можете взять любой свободный, главное — использовать одно и то же значение везде.

Дальше схема такая: с локальной машины вы пушите образ на проброшенный порт localhost:5000, а containerd внутри кластера тянет его оттуда (k3d сам генерирует нужный registries.yaml):

bash
1docker tag myapp:dev localhost:5000/myapp:dev
2docker push localhost:5000/myapp:dev

На Windows и macOS резолв имени registry по имени контейнера иногда не работает — в этом случае добавьте в hosts-файл строку вроде 127.0.0.1 k3d-registry.localhost, а пуш с хоста надёжнее всегда делать через localhost:5000. На практике, если вы пользуетесь Tilt, он берёт всю эту возню со сборкой и пушем образов на себя.

Проброс портов наружу (--port)

Кластер живёт в Docker, и по умолчанию его внутренние порты с хоста не видны. Чтобы достучаться до myapp из браузера, порт надо пробросить флагом --port ещё при создании кластера.

Рекомендуемый способ (документация по доступу к сервисам) — пробросить порт через serverlb (встроенный балансировщик-прокси перед server-нодами) с помощью фильтра @loadbalancer. k3s по умолчанию ставит Traefik как ingress-контроллер, поэтому такой проброс — естественный путь для входа через Ingress (подробно — в главе про сеть):

bash
1k3d cluster create dev \
2  --api-port 6550 \
3  -p "8081:80@loadbalancer" \
4  --agents 2

Здесь порт 80 внутри кластера (куда смотрит Traefik) проброшен на 8081 на хосте. После заезда сервиса откроете http://localhost:8081.

Формат значения --port такой: [ХОСТ:][ПОРТ_ХОСТА:]ПОРТ_КОНТЕЙНЕРА[/ПРОТОКОЛ][@НОДА-ФИЛЬТР].

Альтернатива — пробросить порт прямо на NodePort одного из агентов (NodePort — это способ Kubernetes открыть сервис на фиксированном порту ноды):

bash
1k3d cluster create dev -p "8082:30080@agent:0"

Главная грабля: пробросы фиксируются в момент создания кластера. Добавить новый порт на лету нельзя — придётся пересоздать кластер. Поэтому лучше сразу заложить порты, которые понадобятся (в нашей итоговой команде выше порт 8081 уже проброшен). Благо пересоздание у k3d дешёвое — об этом ниже.

В статье фигурирует несколько разных портов — чтобы не путаться, держите перед глазами шпаргалку:

ПортГде используетсяГлава
8080порт приложения myapp внутри контейнераглавы 6–8
80порт Service (ClusterIP) перед myappглава 7
8081вход снаружи через Ingress/loadbalancer на хостеглавы 5, 11
8000локальный docker run -p 8000:8080 для проверки образаглава 6
5000встроенный registry k3d (k3d-registry.localhost:5000)главы 5, 6

Проверка, удаление и пересоздание кластера

После k3d cluster create убедимся, что кластер живой. Две команды-минимум:

bash
1kubectl get nodes        # ноды должны быть в статусе Ready
2kubectl cluster-info     # API-сервер и базовые сервисы доступны

Для нашего dev --servers 1 --agents 2 в выводе kubectl get nodes вы увидите три ноды в состоянии Ready(одна server-нода и две agent-ноды). Если так — кластер готов принимать myapp.

Список и удаление кластеров делаются командами k3d:

bash
1k3d cluster list             # все кластеры на машине
2k3d cluster delete dev       # удалить кластер dev (ноды-контейнеры и сеть)
3k3d cluster delete --all     # удалить вообще все кластеры

k3d cluster delete (справка) убирает контейнеры-ноды и Docker-сеть кластера. Нюанс про registry: тот, что создан вместе с кластером (--registry-create), удаляется вместе с ним, а созданный отдельно (k3d registry create) живёт сам по себе — его придётся удалять руками.

Поскольку старт быстрый, типичный приём при «грязном» состоянии или смене конфигурации портов — просто пересоздать кластер с нуля:

bash
1k3d cluster delete dev && k3d cluster create dev --agents 2 -p "8081:80@loadbalancer"

Это и есть одно из главных удобств k3d: чистое окружение в одну строку. В следующей главе упакуем myapp в Docker-образ, чтобы было что в этот кластер заезжать.

Источники

Хотите удобную локальную настройку k3d для команды?
Нужна помощь с быстрым локальным Kubernetes-кластером на k3d — registry, проброс портов и чистый dev-процесс? Помогу настроить всё правильно.