Local Kubernetes Dev — Part 5: Spinning up a local cluster with k3d
Поднимаем настоящий Kubernetes-кластер на ноутбуке за пару минут с помощью k3d — со встроенным registry, пробросом портов, контекстами kubectl и дешёвым пересозданием.
Инструменты установлены (см. главу про подготовку рабочего места) — пора поднять настоящий 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. Выполнив её, вам не придётся пересоздавать кластер по ходу статьи:
1k3d cluster create dev \
2 --servers 1 --agents 2 \
3 --registry-create k3d-registry.localhost:5000 \
4 -p "8081:80@loadbalancer"Ниже разбираем флаги по шагам — но рабочая команда, с которой мы идём через всю статью, именно эта. Все остальные примеры в этой главе показаны для объяснения отдельных флагов.
Самая простая команда создаёт кластер с именем по умолчанию:
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 и двумя воркерами:
1k3d cluster create dev --servers 1 --agents 2Если для паритета с продом нужна конкретная версия Kubernetes — задайте её образом k3s:
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.
Полезные команды:
1kubectl config current-context # где мы сейчас
2kubectl config get-contexts # список контекстов (* = текущий)
3kubectl config use-context k3d-dev # переключиться на наш кластер
4kubectl config view --minify # показать конфиг только текущего контекстаЕсли по какой-то причине контекст не переключился сам, можно слить kubeconfig вручную и сразу переключиться:
1k3d kubeconfig merge dev --kubeconfig-switch-contextПривыкните проверять current-context перед опасными командами — это спасает от случайного kubectl delete не в том кластере.
Встроенный registry образов в k3d
Чтобы запустить myapp в кластере, его Docker-образ (соберём его в главе про контейнеризацию) надо где-то хранить так, чтобы кластер мог его скачать. Гонять образы через публичный registry на каждой итерации — медленно и неудобно. k3d умеет поднимать собственный локальный registry прямо рядом с кластером.
Наш сквозной registry называется k3d-registry.localhost:5000 — это имя мы используем во всех главах статьи. Самый простой путь — создать его вместе с кластером (документация по registries):
1k3d cluster create dev --registry-create k3d-registry.localhost:5000Здесь k3d-registry.localhost — имя registry, а 5000 — порт, который пробрасывается на хост (0.0.0.0:5000). Можно создать registry и отдельно — тогда он переживёт пересоздание кластера и его можно подключать к разным кластерам:
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):
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 (подробно — в главе про сеть):
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 открыть сервис на фиксированном порту ноды):
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 убедимся, что кластер живой. Две команды-минимум:
1kubectl get nodes # ноды должны быть в статусе Ready
2kubectl cluster-info # API-сервер и базовые сервисы доступныДля нашего dev --servers 1 --agents 2 в выводе kubectl get nodes вы увидите три ноды в состоянии Ready(одна server-нода и две agent-ноды). Если так — кластер готов принимать myapp.
Список и удаление кластеров делаются командами k3d:
1k3d cluster list # все кластеры на машине
2k3d cluster delete dev # удалить кластер dev (ноды-контейнеры и сеть)
3k3d cluster delete --all # удалить вообще все кластерыk3d cluster delete (справка) убирает контейнеры-ноды и Docker-сеть кластера. Нюанс про registry: тот, что создан вместе с кластером (--registry-create), удаляется вместе с ним, а созданный отдельно (k3d registry create) живёт сам по себе — его придётся удалять руками.
Поскольку старт быстрый, типичный приём при «грязном» состоянии или смене конфигурации портов — просто пересоздать кластер с нуля:
1k3d cluster delete dev && k3d cluster create dev --agents 2 -p "8081:80@loadbalancer"Это и есть одно из главных удобств k3d: чистое окружение в одну строку. В следующей главе упакуем myapp в Docker-образ, чтобы было что в этот кластер заезжать.
Источники
- k3d — homepage (определение и базовые команды)
- Cluster Datastore | K3s (SQLite по умолчанию)
- k3d cluster create | справка по флагам
- Using Image Registries | k3d
- Exposing Services | k3d
- k3d cluster delete | справка
- Configure Access to Multiple Clusters | Kubernetes
- minikube vs kind vs k3d benchmark | Oilbeater