Kubernetes для самых маленьких. Часть 42: Как поднять кластер на ноутбуке: kind, k3s, minikube?

Все части серии «Kubernetes для самых маленьких»

Учебный кластер серии мы подняли в части 4: три виртуальные машины, containerd, kubeadm init и join, Calico, kubeconfig на ноутбуке. Так выглядит настоящий кластер, и на нём мы прошли всю серию — до GitOps на Argo CD в предыдущей части. Но для многих задач три машины — слишком много: проверить манифест перед выкаткой, прогнать тесты в CI, показать коллеге, как работает Deployment, поиграть с новой версией Kubernetes, не трогая стенд. Для этого есть кластеры, которые поднимаются на ноутбуке за минуту и удаляются одной командой: kind, k3s и minikube. Это бонусная часть в конце серии, единственная про них: основные примеры серии были на кластерах kubeadm. Поэтому здесь разберём каждый всерьёз — как он устроен изнутри, чем его кластер отличается от того, что мы собрали руками, и где он подведёт.

Все трое запускают Kubernetes: API у них тот же, что описан в части 3, обычные манифесты Deployment и Service работают без правок. Для примеров с Windows-подами, CRD и внешними системами нужны соответствующие узлы и компоненты. Отличаются они тем, что вокруг API: где живут узлы, чем хранится состояние, какой CNI, откуда берутся LoadBalancer и тома.


kind: узлы — это контейнеры

kind (Kubernetes IN Docker) появился как инструмент для тестов самого Kubernetes: проекту нужно было в CI поднимать кластер из только что собранного кода, быстро и много раз в день. Идея — каждый «узел» кластера сделать контейнером в Docker или Podman. Docker — движок контейнеров с демоном dockerd, Podman — совместимый с ним по командам движок без демона: контейнеры запускаются как дочерние процессы пользователя. kind работает с обоими.

Внутри контейнера-узла — почти полноценная машина: systemd первым процессом, под ним containerd и kubelet, те же, что в части 4 мы ставили пакетами. Образ узла kindest/node собирается под конкретную версию Kubernetes и уже содержит kubeadm, kubelet, kubectl и образы компонентов control plane, так что при создании кластера из интернета почти ничего не скачивается. kind запускает контейнеры-узлы, а затем внутри первого выполняет kubeadm init, внутри остальных — kubeadm join. То есть kind — это kubeadm, у которого вместо виртуальных машин контейнеры.

 Ноутбук (Linux, или ВМ Docker Desktop на macOS/Windows)
 ┌────────────────────────────────────────────────────────────────────┐
 │ dockerd / podman                                                   │
 │                                                                    │
 │  сеть docker «kind» 172.18.0.0/16                                  │
 │  ┌──────────────────────┐ ┌──────────────────────┐ ┌─────────────┐ │
 │  │ lab-control-plane    │ │ lab-worker           │ │ lab-worker2 │ │
 │  │ 172.18.0.2           │ │ 172.18.0.3           │ │ 172.18.0.4  │ │
 │  │ systemd              │ │ systemd              │ │ systemd     │ │
 │  │  ├ containerd        │ │  ├ containerd        │ │  ├ ...      │ │
 │  │  │  ├ kube-apiserver │ │  │  ├ kindnet        │ │             │ │
 │  │  │  ├ etcd           │ │  │  ├ kube-proxy     │ │             │ │
 │  │  │  └ ...            │ │  │  └ поды           │ │             │ │
 │  │  └ kubelet           │ │  └ kubelet           │ │             │ │
 │  └──────────▲───────────┘ └──────────────────────┘ └─────────────┘ │
 │             │ порт 6443 опубликован на 127.0.0.1:<случайный>       │
 └─────────────┼──────────────────────────────────────────────────────┘
               │ kubectl на ноутбуке

Контейнер узла запускается привилегированным: kubelet внутри должен создавать cgroups, сетевые namespaces, монтировать тома, а containerd — запускать вложенные контейнеры. Поды кластера kind — это контейнеры внутри контейнера, с общим ядром ноутбука.

Вокруг kubeadm kind ставит свой минимальный набор:

  • kindnet — собственный CNI kind. Каждому узлу достаётся подсеть из сети подов (по умолчанию 10.244.0.0/16), а демон kindnetd на каждом узле прописывает маршруты к подсетям соседей через их адреса в сети Docker — без инкапсуляции, потому что все узлы в одном L2-сегменте. Начиная с kind 0.24 в комплекте есть и минимальная реализация NetworkPolicy — контроллер kube-network-policies от авторов kind; он работает внутри того же kindnetd и пишет правила nftables (в журнале пода kindnet — строки Starting controller name="kube-network-policies" и Syncing nftables rules).
  • local-path-provisioner — провижинер томов от Rancher: на запрос тома создаёт каталог на диске узла и отдаёт его поду. В кластере есть StorageClass standard, помеченный как класс по умолчанию.
  • kube-proxy в режиме iptables (по умолчанию) или nftables — включается в конфигурации кластера.
  • CoreDNS — DNS-сервер кластера, тот же, что ставит kubeadm.

Установка и первый кластер

kind — один статический бинарь на Go. На Linux:

$ curl -Lo kind https://kind.sigs.k8s.io/dl/v0.33.0/kind-linux-amd64
$ curl -Lo kind.sha256 https://github.com/kubernetes-sigs/kind/releases/download/v0.33.0/kind-linux-amd64.sha256sum
$ sed 's/kind-linux-amd64/kind/' kind.sha256 | sha256sum -c
kind: OK
$ chmod +x kind && sudo mv kind /usr/local/bin/
$ kind version
kind v0.33.0 go1.26.7 linux/amd64

На macOS — brew install kind, на Windows — winget install Kubernetes.kind или бинарь с той же страницы. Docker или Podman должны быть уже установлены.

Версия 0.33 по умолчанию поднимает Kubernetes 1.37.0 — ту же минорную версию, что у стенда после части 35 (1.37.2; в списке образов для kind 0.33.0 ветка 1.37 представлена версией 1.37.0). Для другой версии образ узла указывают явно. Список готовых образов с дайджестами есть в описании каждого релиза kind; авторы рекомендуют брать образ с дайджестом @sha256:…, потому что образ под одним и тем же тегом пересобирается с новыми релизами kind и со старой версией kind может не работать. Закрепим так и версию по умолчанию — тогда обновление kind не поменяет кластер незаметно:

$ kind create cluster --name lab \
    --image kindest/node:v1.37.0@sha256:a1ed56cfb0e7b93589bdf97c8cd566405a265939e3620fc4f5de89adff580ae5
Creating cluster "lab" ...
 ✓ Ensuring node image (kindest/node:v1.37.0) 🖼
 ✓ Preparing nodes 📦
 ✓ Writing configuration 📜
 ✓ Starting control-plane 🕹️
 ✓ Installing CNI 🔌
 ✓ Installing StorageClass 💾
Set kubectl context to "kind-lab"
You can now use your cluster with:

kubectl cluster-info --context kind-lab

Have a nice day! 👋

На стенде команда работала 33 секунды, из них большую часть — скачивание образа узла.

kind дописал в ~/.kube/config кластер, пользователя и контекст kind-lab и сделал его текущим. Контекст — тройка «кластер, пользователь, неймспейс» из kubeconfig, которую мы разбирали в части 4; переключение между стендом и kind — kubectl config use-context. Адрес API-сервера в kubeconfig — https://127.0.0.1:<порт>: порт 6443 контейнера опубликован только на loopback ноутбука, снаружи кластер недоступен, и так и надо оставить.

$ kubectl get nodes
NAME                STATUS     ROLES           AGE   VERSION
lab-control-plane   NotReady   control-plane   2s    v1.37.0

NotReady сразу после создания — нормально: kind возвращает управление, не дожидаясь, пока kindnet настроит сеть узла, и через несколько секунд узел становится Ready. Один узел — и control plane, и рабочий: в кластере без отдельных рабочих узлов kind снимает с него taint node-role.kubernetes.io/control-plane:NoSchedule, который kubeadm ставит на мастер, чтобы поды пользователя могли туда планироваться. В многоузловом кластере ниже taint на мастере остаётся. О taints подробно — в части 13.

Многоузловой кластер

Состав узлов, сеть и патчи kubeadm задаются файлом конфигурации:

# kind-lab.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: lab
networking:
  podSubnet: 172.20.0.0/16      # как на стенде, чтобы не путаться
  serviceSubnet: 10.96.0.0/12
  kubeProxyMode: nftables       # как на стенде
nodes:
- role: control-plane
  image: kindest/node:v1.37.0@sha256:a1ed56cfb0e7b93589bdf97c8cd566405a265939e3620fc4f5de89adff580ae5
  extraPortMappings:            # порт 30080 узла → 127.0.0.1:8080 ноутбука
  - containerPort: 30080
    hostPort: 8080
    listenAddress: 127.0.0.1
- role: worker
  image: kindest/node:v1.37.0@sha256:a1ed56cfb0e7b93589bdf97c8cd566405a265939e3620fc4f5de89adff580ae5
- role: worker
  image: kindest/node:v1.37.0@sha256:a1ed56cfb0e7b93589bdf97c8cd566405a265939e3620fc4f5de89adff580ae5
  labels:
    disk: ssd
$ kind delete cluster --name lab
Deleting cluster "lab" ...
Deleted nodes: ["lab-control-plane"]
$ kind create cluster --config kind-lab.yaml
$ kubectl get nodes
NAME                STATUS   ROLES           AGE   VERSION
lab-control-plane   Ready    control-plane   29s   v1.37.0
lab-worker          Ready    <none>          20s   v1.37.0
lab-worker2         Ready    <none>          20s   v1.37.0
$ kubectl -n kube-system get cm kube-proxy -o jsonpath='{.data.config\.conf}' | grep '^mode'
mode: nftables

Образ уже в кеше Docker, и три узла создаются за 20 секунд. Поды получили адреса из 172.20.0.0/16, а сами узлы — из сети Docker 172.18.0.0/16.

Если в конфигурации больше одного control-plane, kind добавит ещё один контейнер — балансировщик перед API-серверами (в свежих версиях это Envoy — прокси-сервер уровней L4 и L7), и kubeconfig будет смотреть на него. Так можно потрогать отказоустойчивый control plane, но надёжности это не прибавляет: все «мастера» — контейнеры одного ноутбука. Как такой control plane строится на настоящих машинах, разобрано в серии про Kubernetes на BGP.

Поле extraPortMappings — это обычная публикация порта контейнера (docker run -p). Сервис типа NodePort с портом 30080 будет доступен на 127.0.0.1:8080 ноутбука. Публикация задаётся только при создании кластера: контейнеру Docker нельзя добавить порт на ходу.

Поле kubeadmConfigPatches позволяет передать в kubeadm куски ClusterConfiguration, KubeletConfiguration и других типов, которые мы писали руками в части 4, — например, включить аудит API-сервера или поменять параметры kubelet.

Локальный образ: kind load

Главная радость kind в разработке — образ, собранный на ноутбуке, можно отдать кластеру без реестра. Собственное приложение hello мы собирали в части 5, а здесь возьмём двухстрочный Dockerfile: базовый образ nginx и своя страница. Как устроен Dockerfile и что такое слои образа — тоже в части 5.

$ cat Dockerfile
FROM nginx:1.29
COPY index.html /usr/share/nginx/html/
$ docker build -t demo/web:dev .
$ kind load docker-image demo/web:dev --name lab
Image: "demo/web:dev" with ID "sha256:902178ca06f85d0e937b22447962845b19804f53e648bc93d6f24a355eb5c04b" not yet present on node "lab-worker", loading...
Image: "demo/web:dev" with ID "sha256:902178ca06f85d0e937b22447962845b19804f53e648bc93d6f24a355eb5c04b" not yet present on node "lab-worker2", loading...
Image: "demo/web:dev" with ID "sha256:902178ca06f85d0e937b22447962845b19804f53e648bc93d6f24a355eb5c04b" not yet present on node "lab-control-plane", loading...

kind load docker-image делает docker save — выгружает образ в tar-архив — и импортирует его в containerd каждого узла (ctr images import в пространстве имён k8s.io, где containerd держит образы kubelet). Для Podman и для готовых архивов есть kind load image-archive файл.tar.

Тонкость, на которой спотыкаются почти все: у контейнера в поде есть поле imagePullPolicy — когда kubelet должен скачивать образ из реестра. Если тег образа latest или не указан, по умолчанию стоит Always: kubelet пойдёт в реестр, не найдёт там demo/web:latest и под зависнет в ErrImagePull, хотя образ лежит на узле. С тегом dev по умолчанию IfNotPresent — образ с узла будет использован. Поэтому образы для kind помечают осмысленным тегом, а не latest; подробно политика разобрана в части 5.

$ kubectl create deployment web --image=demo/web:dev --replicas=2
deployment.apps/web created
$ kubectl create service nodeport web --tcp=80:80 --node-port=30080
service/web created
$ kubectl get deployment web -o jsonpath='{.spec.template.spec.containers[0].imagePullPolicy}{"\n"}'
IfNotPresent
$ curl -s http://127.0.0.1:8080/
<h1>hello from kind</h1>

Запрос пришёл на опубликованный порт контейнера lab-control-plane, kube-proxy внутри него перенаправил его на один из подов — оба пода при этом на рабочих узлах. Если curl сразу после выкатки вернул пустой ответ, kube-proxy ещё не успел записать правила для нового сервиса: через секунду-другую запрос проходит. Deployment и Service здесь — заготовка, они разобраны в части 7 и части 15.

LoadBalancer в kind

Сервис типа LoadBalancer в kind по умолчанию навсегда остаётся с EXTERNAL-IP <pending>: в Kubernetes адреса балансировщикам выдаёт облачный контроллер (cloud-controller-manager), а у kind облака нет. Решение от авторов — cloud-provider-kind: отдельная программа, которая запускается на ноутбуке (ей нужны права на Docker и на открытие портов), следит за сервисами LoadBalancer во всех кластерах kind и на каждый создаёт контейнер-балансировщик в сети kind. Его адрес из сети Docker и становится EXTERNAL-IP.

$ curl -LO https://github.com/kubernetes-sigs/cloud-provider-kind/releases/download/v0.12.0/cloud-provider-kind_0.12.0_linux_amd64.tar.gz
$ curl -LO https://github.com/kubernetes-sigs/cloud-provider-kind/releases/download/v0.12.0/cloud-provider-kind_0.12.0_checksums.txt
$ grep linux_amd64 cloud-provider-kind_0.12.0_checksums.txt | sha256sum -c
cloud-provider-kind_0.12.0_linux_amd64.tar.gz: OK
$ tar xzf cloud-provider-kind_0.12.0_linux_amd64.tar.gz cloud-provider-kind
$ sudo install -m 0755 cloud-provider-kind /usr/local/bin/
$ sudo cloud-provider-kind        # оставить работать в отдельном терминале
$ kubectl expose deployment web --name=web-lb --type=LoadBalancer --port=80
service/web-lb exposed
$ kubectl get svc web-lb
NAME     TYPE           CLUSTER-IP    EXTERNAL-IP   PORT(S)        AGE
web-lb   LoadBalancer   10.97.254.5   172.18.0.5    80:30933/TCP   20s
$ docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Ports}}' | grep kindccm
kindccm-e1b23e1f7834   envoyproxy/envoy:v1.33.2   0.0.0.0:32768->10000/tcp, [::]:32768->10000/tcp
$ curl -s http://172.18.0.5/
<h1>hello from kind</h1>

Балансировщик — контейнер с прокси-сервером Envoy, адрес 172.18.0.5 выдан ему в сети kind. Обратите внимание на колонку PORTS: Docker публикует порт Envoy на всех адресах ноутбука, так что сервис становится доступен из локальной сети по случайному порту — в общественной сети это стоит иметь в виду. В версии 0.12 cloud-provider-kind заодно реализует Ingress и Gateway API (флаги --enable-default-ingress и --gateway-channel в начале его журнала): kind без отдельного ingress-контроллера понимает объекты из части 18.

На Linux адреса сети kind доступны с ноутбука напрямую. На macOS и Windows Docker живёт внутри виртуальной машины, и до адресов 172.18.0.x с хоста не достучаться без дополнительной переадресации — это главная сетевая грабля kind на этих системах, и она же касается NodePort без extraPortMappings. На стенде LoadBalancer даёт MetalLB из части 17.


k3s: один бинарь вместо кластера пакетов

k3s сделала компания Rancher (теперь SUSE) для узлов с малыми ресурсами: граничные устройства, Raspberry Pi, CI, небольшие серверы. Сейчас это проект CNCF — фонда Cloud Native Computing Foundation, в котором живёт и сам Kubernetes. Это не «Kubernetes в контейнерах», а настоящий дистрибутив для Linux-машин — физических или виртуальных, — только собранный иначе, чем kubeadm.

Один бинарь. API-сервер, планировщик, controller-manager, kubelet, kube-proxy, containerd, runc, CNI-плагины, kubectl, crictl, ctr — всё внутри одного исполняемого файла /usr/local/bin/k3s размером около 80 МБ. Компоненты control plane работают не подами, как у kubeadm, а горутинами одного процесса k3s server; на рабочих узлах работает k3s agent. Одна служба systemd на узел: k3s.service на сервере, k3s-agent.service на агенте.

Хранилище состояния. По умолчанию k3s хранит объекты API не в etcd, а в SQLite — встраиваемой базе данных в одном файле, /var/lib/rancher/k3s/server/db/state.db. Между API-сервером и SQLite стоит прослойка kine («kine is not etcd»): она реализует ту часть протокола etcd, которой пользуется kube-apiserver (get, list, watch, транзакции), поверх SQL-базы. Через ту же kine k3s умеет хранить состояние во внешних PostgreSQL или MySQL. Третий вариант — встроенный etcd: флаг --cluster-init на первом сервере, и k3s запустит etcd внутри своего процесса; тогда к нему можно присоединить ещё серверы и получить отказоустойчивый control plane. Серверов при этом нечётное число — три, пять: etcd держит кворум большинством, и четвёртый сервер не прибавляет устойчивости. Кластер на SQLite можно перевести на встроенный etcd, перезапустив сервер с --cluster-init.

Встроенные компоненты. То, что в кластере kubeadm мы ставим сами, k3s приносит с собой:

Компонент Что это Как отключить
Flannel CNI от CoreOS: подсеть на узел, между узлами VXLAN --flannel-backend=none (и ставить свой CNI)
CoreDNS DNS кластера --disable=coredns
Traefik 3 ingress-контроллер: обратный прокси, принимающий HTTP(S) снаружи и раскладывающий по сервисам --disable=traefik
ServiceLB (klipper-lb) реализация сервисов LoadBalancer без облака --disable=servicelb
local-path-provisioner тома в каталоге на диске узла, StorageClass local-path --disable=local-storage
metrics-server сбор CPU и памяти подов для kubectl top и автомасштабирования --disable=metrics-server
контроллер сетевых политик кусок kube-router (маршрутизатора для Kubernetes), который превращает NetworkPolicy в правила iptables --disable-network-policy
helm-controller контроллер ресурса HelmChart: ставит чарты Helm (пакетов приложений для Kubernetes, о них — в части 27) из манифестов --disable-helm-controller
CRD Gateway API типы Gateway, HTTPRoute и другие из части 18, их понимает встроенный Traefik --disable=gateway-api-crd

Flannel — один из первых CNI-плагинов: каждому узлу выдаёт подсеть из 10.42.0.0/16 (у k3s по умолчанию), а пакеты между узлами заворачивает в VXLAN — L2-кадры внутри UDP-датаграмм на порт 8472. Сам VXLAN подробно разобран в части 18 сетевой серии. Сеть сервисов у k3s — 10.43.0.0/16, адрес DNS — 10.43.0.10.

ServiceLB (бывший klipper-lb) устроен просто. На каждый сервис типа LoadBalancer он создаёт DaemonSet в kube-system — по поду на каждом подходящем узле со свободным портом. Под объявляет hostPort с портом сервиса, то есть занимает этот порт на адресе узла, и правилами iptables перенаправляет входящее на ClusterIP сервиса. В EXTERNAL-IP сервиса попадают адреса узлов. Отсюда ограничения: на одном узле не может быть двух LoadBalancer с одинаковым портом. Второй сервис останется в Pending, если нужный порт занят на всех подходящих узлах, а «внешний адрес» — это адрес узла, который исчезает вместе с узлом.

Установка

Официальный способ — скрипт с get.k3s.io. Перед тем как запускать скрипт из интернета под sudo, его стоит скачать и прочитать; переменные окружения задают версию и роль:

user@k3s-1:~$ curl -sfL https://get.k3s.io -o install-k3s.sh
user@k3s-1:~$ less install-k3s.sh
user@k3s-1:~$ sudo INSTALL_K3S_CHANNEL=v1.37 sh install-k3s.sh
[INFO]  Finding release for channel v1.37
[INFO]  Using v1.37.2+k3s1 as release
[INFO]  Downloading hash https://github.com/k3s-io/k3s/releases/download/v1.37.2%2Bk3s1/sha256sum-amd64.txt
[INFO]  Downloading binary https://github.com/k3s-io/k3s/releases/download/v1.37.2%2Bk3s1/k3s
[INFO]  Verifying binary download
[INFO]  Installing k3s to /usr/local/bin/k3s
[INFO]  Skipping installation of SELinux RPM
[INFO]  Creating /usr/local/bin/kubectl symlink to k3s
[INFO]  Creating /usr/local/bin/crictl symlink to k3s
[INFO]  Creating /usr/local/bin/ctr symlink to k3s
[INFO]  Creating killall script /usr/local/bin/k3s-killall.sh
[INFO]  Creating uninstall script /usr/local/bin/k3s-uninstall.sh
[INFO]  env: Creating environment file /etc/systemd/system/k3s.service.env
[INFO]  systemd: Creating service file /etc/systemd/system/k3s.service
[INFO]  systemd: Enabling k3s unit
Created symlink '/etc/systemd/system/multi-user.target.wants/k3s.service' → '/etc/systemd/system/k3s.service'.
[INFO]  systemd: Starting k3s

Здесь k3s-1 (192.168.122.11) и k3s-2 (192.168.122.12) — две учебные виртуальные машины с Ubuntu Server 26.04 на ноутбуке, в сети libvirt по умолчанию, отдельно от стенда. Канал v1.37 выбирает последний выпуск ветки 1.37. На стенде он дал v1.37.2+k3s1. Для повторения примера с тем же выпуском передайте INSTALL_K3S_VERSION=v1.37.2+k3s1 на обоих узлах. Ссылки kubectl, crictl и ctr скрипт создаёт, только если таких команд ещё нет: на машине, где стоит пакет kubectl, он напишет Skipping /usr/local/bin/kubectl symlink to k3s, command exists in PATH, и kubectl будет смотреть не в k3s.

Через полминуты:

user@k3s-1:~$ sudo k3s kubectl get nodes
NAME    STATUS   ROLES           AGE   VERSION
k3s-1   Ready    control-plane   29s   v1.37.2+k3s1

user@k3s-1:~$ sudo k3s kubectl get pods -A
NAMESPACE     NAME                                      READY   STATUS      RESTARTS      AGE
kube-system   coredns-577d995dff-wnd5r                  1/1     Running     0             65s
kube-system   helm-install-gateway-api-crd-brl9s        0/1     Completed   0             66s
kube-system   helm-install-traefik-6pmh2                0/1     Completed   1 (47s ago)   66s
kube-system   helm-install-traefik-crd-nfj49            0/1     Completed   0             66s
kube-system   local-path-provisioner-56c6584955-g4qvf   1/1     Running     0             65s
kube-system   metrics-server-778c4649b4-zj7fw           1/1     Running     0             65s
kube-system   svclb-traefik-59103bc3-cp4bx              2/2     Running     0             45s
kube-system   traefik-68df944ff7-jlbh2                  1/1     Running     0             45s

Ни одного пода control plane: API-сервер, планировщик и controller-manager живут внутри процесса k3s server, поэтому их нет ни в kube-system, ни в /etc/kubernetes/manifests — каталоге, из которого kubelet в кластере kubeadm запускает статические поды control plane (поды, описанные файлами на узле, а не объектами в API). Поды helm-install-* — это helm-controller, который из файлов в /var/lib/rancher/k3s/server/manifests/ поставил чарты Traefik и CRD Gateway API; первая попытка helm-install-traefik падает и перезапускается, пока CRD Traefik не зарегистрированы, — это нормально. Под svclb-traefik-… — ServiceLB, занявший порты 80 и 443 узла для сервиса Traefik. Роль у сервера одна, control-plane: прежняя метка master в 1.37 больше не ставится.

Kubeconfig администратора k3s пишет в /etc/rancher/k3s/k3s.yaml, файл доступен только root. Чтобы работать с ноутбука, файл копируют и меняют в нём server: https://127.0.0.1:6443 на адрес узла:

$ ssh user@k3s-1 sudo cat /etc/rancher/k3s/k3s.yaml | sed 's/127.0.0.1/192.168.122.11/; s/: default/: k3s-lab/' > ~/.kube/k3s-lab.yaml
$ chmod 600 ~/.kube/k3s-lab.yaml
$ KUBECONFIG=~/.kube/k3s-lab.yaml kubectl get nodes

После sed кластер, пользователь и контекст в файле называются k3s-lab, а kubectl с ноутбука видит те же узлы, что k3s kubectl на сервере.

В этом файле — клиентский сертификат с правами cluster-admin, тот же уровень, что admin.conf у kubeadm; обращаться с ним нужно как с паролем root.

Второй узел

Агент присоединяется по токену, который сервер положил в /var/lib/rancher/k3s/server/node-token:

user@k3s-1:~$ sudo cat /var/lib/rancher/k3s/server/node-token
K10276633…::server:63e0…

user@k3s-2:~$ curl -sfL https://get.k3s.io -o install-k3s.sh
user@k3s-2:~$ less install-k3s.sh
user@k3s-2:~$ sudo INSTALL_K3S_CHANNEL=v1.37 K3S_URL=https://192.168.122.11:6443 \
    K3S_TOKEN='K10276633…::server:63e0…' sh install-k3s.sh
...
[INFO]  Creating uninstall script /usr/local/bin/k3s-agent-uninstall.sh
[INFO]  env: Creating environment file /etc/systemd/system/k3s-agent.service.env
[INFO]  systemd: Creating service file /etc/systemd/system/k3s-agent.service
[INFO]  systemd: Enabling k3s-agent unit
Created symlink '/etc/systemd/system/multi-user.target.wants/k3s-agent.service' → '/etc/systemd/system/k3s-agent.service'.
[INFO]  systemd: Starting k3s-agent

user@k3s-1:~$ sudo k3s kubectl get nodes
NAME    STATUS   ROLES           AGE   VERSION
k3s-1   Ready    control-plane   71s   v1.37.2+k3s1
k3s-2   Ready    <none>          18s   v1.37.2+k3s1

Наличие K3S_URL превращает установку в агента. В полном токене за префиксом K10 идёт хеш CA кластера. Агент проверяет по нему CA при первом подключении и проверяет сертификат сервера до отправки учётных данных, так же, как kubeadm join проверяет --discovery-token-ca-cert-hash. Агент поднимает у себя локальный балансировщик на 127.0.0.1:6444, через который kubelet и kube-proxy ходят к серверам: если серверов несколько, агент сам переключается между ними.

user@k3s-2:~$ sudo ss -lntp | grep 6444
LISTEN 0      4096       127.0.0.1:6444       0.0.0.0:*    users:(("k3s-agent",pid=2129,fd=4))
LISTEN 0      4096           [::1]:6444          [::]:*    users:(("k3s-agent",pid=2129,fd=7))
user@k3s-2:~$ sudo grep server: /var/lib/rancher/k3s/agent/kubelet.kubeconfig
    server: https://127.0.0.1:6444

Чем k3s отличается от кластера kubeadm

  • Компоненты control plane — не поды, их не видно через kubectl; логи — в journalctl -u k3s, параметры — флаги k3s server или /etc/rancher/k3s/config.yaml.
  • containerd встроенный, со своим сокетом /run/k3s/containerd/containerd.sock и каталогом /var/lib/rancher/k3s/agent/containerd; настройки реестров — в /etc/rancher/k3s/registries.yaml, а не в /etc/containerd/certs.d. crictl и ctr из комплекта k3s уже смотрят в нужный сокет.
  • Состояние в SQLite, а не в etcd: etcdctl и снапшоты etcd из части 34 к такому кластеру неприменимы, для резервной копии сохраните весь каталог /var/lib/rancher/k3s/server/db/ и файл токена /var/lib/rancher/k3s/server/token.
  • Сети подов и сервисов — 10.42.0.0/16 и 10.43.0.0/16, а не наши 172.20.0.0/16 и 10.96.0.0/12; меняются флагами --cluster-cidr и --service-cidr.
  • Обновление — заменой бинаря (или тем же скриптом с новым каналом), а не kubeadm upgrade.

Грабли k3s

  • Traefik и ServiceLB заняли 80 и 443. Если на узле уже есть веб-сервер или вы хотите MetalLB и свой ingress-контроллер, их нужно отключить при установке: INSTALL_K3S_EXEC="--disable=traefik --disable=servicelb". У существующего кластера с --disable=traefik k3s удалит встроенное дополнение Traefik. Для отключения ServiceLB задайте --disable=servicelb на всех серверах кластера.
  • Встроенный Traefik перезаписывается. Править его манифест в /var/lib/rancher/k3s/server/manifests/traefik.yaml бесполезно: k3s перепишет файл при старте. Настройки — отдельным ресурсом HelmChartConfig.
  • Порты между узлами. Для Flannel нужен UDP 8472, для kubelet — TCP 10250, для API — TCP 6443. Файрвол хоста, закрывший 8472, даёт кластер, где поды на разных узлах не видят друг друга, а на одном — видят.
  • local-path привязывает данные к узлу. Том — каталог /var/lib/rancher/k3s/storage/… конкретного узла: под с таким томом не переедет на другой узел, а данные пропадут вместе с диском. Для тестов это нормально, для данных — нет; хранилище подробно — в части 21.
  • Один сервер на SQLite — точка отказа. Для учебного кластера это нормально; для чего-то большего — встроенный etcd на трёх серверах.

Есть ещё k3d — обёртка, которая запускает k3s в контейнерах Docker по образцу kind. Если нужен именно k3s на ноутбуке без виртуальных машин, это он.


minikube: кластер в виртуальной машине или контейнере

minikube — самый старый из троих, проект самого Kubernetes, задуманный как «кластер для разработчика на рабочей станции». Его отличие — драйверы: minikube умеет создать узел как виртуальную машину в гипервизоре или как контейнер.

  • Виртуальные машины: KVM (гипервизор ядра Linux, драйвер kvm2), QEMU (эмулятор и гипервизор, работает на всех ОС), Hyper-V (гипервизор Windows), VFkit (обёртка над Virtualization.framework macOS), VirtualBox, Parallels, VMware. В ВМ загружается собственный образ minikube ISO — минимальный Linux с containerd и kubelet.
  • Контейнеры: docker и podman — узел, как у kind, контейнер с systemd внутри (образ kicbase).
  • none и ssh — поставить кластер прямо на текущую или удалённую Linux-машину; это для особых случаев.

Предпочтительные драйверы: на Linux — docker или kvm2, на macOS — docker или vfkit, на Windows — hyperv или docker. ВМ даёт настоящее ядро, отдельное от ноутбука, — важно, когда нужны модули ядра, свои параметры sysctl или проверка того, как приложение ведёт себя на «чистой» машине. Контейнер — быстрее и легче.

Внутри узла minikube тоже работает kubeadm. Начиная с v1.39 runtime по умолчанию — containerd, как у нас (раньше был Docker Engine с адаптером cri-dockerd); выбрать можно флагом --container-runtime.

Установка и первый кластер

$ curl -LO https://github.com/kubernetes/minikube/releases/download/v1.39.0/minikube-linux-amd64
$ curl -LO https://github.com/kubernetes/minikube/releases/download/v1.39.0/minikube-linux-amd64.sha256
$ echo "$(cat minikube-linux-amd64.sha256)  minikube-linux-amd64" | sha256sum -c
minikube-linux-amd64: OK
$ sudo install minikube-linux-amd64 /usr/local/bin/minikube
$ minikube start --driver=docker --kubernetes-version=v1.37.2 \
    --container-runtime=containerd --cpus=2 --memory=3g --nodes=2 --cni=calico
* minikube v1.39.0 on Ubuntu 26.04
! Specified Kubernetes version 1.37.2 is newer than the newest supported version: v1.37.0. Use `minikube config defaults kubernetes-version` for details.
! Specified Kubernetes version 1.37.2 not found in Kubernetes version list
* Searching the internet for Kubernetes version...
* Kubernetes version 1.37.2 found in GitHub version list
* Using the docker driver based on user configuration
* Using Docker driver with root privileges
* Starting "minikube" primary control-plane node in "minikube" cluster
* Pulling base image v0.0.51 ...
* Downloading Kubernetes v1.37.2 preload ...
    > gcr.io/k8s-minikube/kicbase:  507.60 MiB / 507.61 MiB  100.00% 21.92 MiB
* Preparing Kubernetes v1.37.2 on containerd 2.3.4 ...
* Configuring Calico (Container Networking Interface) ...
* Verifying Kubernetes components...
  - Using image gcr.io/k8s-minikube/storage-provisioner:v5
* Enabled addons: default-storageclass, storage-provisioner

* Starting "minikube-m02" worker node in "minikube" cluster
* Pulling base image v0.0.51 ...
* Found network options:
  - NO_PROXY=192.168.49.2
* Preparing Kubernetes v1.37.2 on containerd 2.3.4 ...
  - env NO_PROXY=192.168.49.2
* Verifying Kubernetes components...
* Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default

Стенд серии на 1.37.2, а minikube 1.39 знает версии только до 1.37.0. Он предупредил об этом, нашёл 1.37.2 в выпусках Kubernetes на GitHub и поставил её — с более новой версией, чем проверяли авторы minikube, это работает, но без гарантий. Вместе со скачиванием образа узла kicbase (полгигабайта) старт занял почти три минуты. Узлы — контейнеры с Debian 12 внутри, на ядре ноутбука:

$ kubectl get nodes -o wide
NAME           STATUS     ROLES           AGE   VERSION   INTERNAL-IP    EXTERNAL-IP   OS-IMAGE                         KERNEL-VERSION             CONTAINER-RUNTIME
minikube       Ready      control-plane   27s   v1.37.2   192.168.49.2   <none>        Debian GNU/Linux 12 (bookworm)   7.0.0-42-generic (amd64)   containerd://2.3.4
minikube-m02   NotReady   <none>          8s    v1.37.2   192.168.49.3   <none>        Debian GNU/Linux 12 (bookworm)   7.0.0-42-generic (amd64)   containerd://2.3.4

--cni=calico ставит тот же CNI, что на стенде (по умолчанию minikube выбирает CNI сам: bridge для одного узла, kindnet для нескольких). --cpus и --memory задаются на каждый узел: на машине с 4 ядрами и 8 ГБ два узла по 3 ГБ — разумный предел. Профили (-p имя) позволяют держать несколько кластеров одновременно; контекст в kubeconfig называется по имени профиля.

Дополнения

Сильная сторона minikube — addons: готовые компоненты, которые включаются одной командой.

$ minikube addons list | grep -E 'dashboard |ingress |metrics|registry |metallb'
│ dashboard                   │ minikube │ disabled │ Kubernetes                             │
│ ingress                     │ minikube │ disabled │ Kubernetes                             │
│ metallb                     │ minikube │ disabled │ 3rd party (MetalLB)                    │
│ metrics-server              │ minikube │ disabled │ Kubernetes                             │
│ registry                    │ minikube │ disabled │ minikube                               │
$ minikube addons enable metrics-server
...
  - Using image registry.k8s.io/metrics-server/metrics-server:v0.9.0
* The 'metrics-server' addon is enabled
$ minikube addons enable ingress
...
  - Using image registry.k8s.io/ingress-nginx/controller:v1.15.1
  - Using image registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.6.9
* Verifying ingress addon...
* The 'ingress' addon is enabled

В minikube 1.39 есть также дополнение traefik, которое включается командой minikube addons enable traefik.

ingress — контроллер ingress-nginx на основе веб-сервера nginx (сам проект ingress-nginx с марта 2026 года больше не сопровождается, для нового лучше Traefik или Gateway API, о них — в части 18), metrics-server — для kubectl top, registry — реестр образов внутри кластера, metallb — тот же MetalLB, что на стенде, dashboard — веб-интерфейс Kubernetes Dashboard (minikube dashboard откроет его в браузере). storage-provisioner — провижинер minikube, создающий тома каталогами на узле (класс standard, аналог local-path).

Таблица addons list не во всём верна: после minikube start --nodes=2 строки default-storageclass и storage-provisioner в ней значатся disabled, хотя minikube start написал Enabled addons: default-storageclass, storage-provisioner, а StorageClass standard и под storage-provisioner в кластере есть и работают. Что на самом деле включено, надёжнее смотреть в самом кластере.

Образы и доступ к сервисам

Свой образ — без реестра, как у kind:

$ minikube image load demo/web:dev
$ minikube image ls | grep demo
docker.io/demo/web:dev

Можно и собрать прямо внутри узла: minikube image build -t demo/web:dev ..

Сервис NodePort открывается командой minikube service web --url: на Linux с драйвером docker она просто печатает адрес узла, а на macOS и Windows, где адрес узла с хоста недоступен, поднимает туннель и печатает http://127.0.0.1:<порт>, работающий, пока команда не остановлена.

$ kubectl create deployment web --image=demo/web:dev --replicas=2
$ kubectl expose deployment web --type=NodePort --port=80
$ minikube service web --url
http://192.168.49.2:30180

Для LoadBalancer — minikube tunnel в отдельном терминале. Она добавляет на ноутбуке маршрут к сети сервисов через адрес узла minikube (поэтому ей нужен sudo) и проставляет сервисам LoadBalancer EXTERNAL-IP, равный их ClusterIP:

$ kubectl expose deployment web --name=web-lb --type=LoadBalancer --port=80
$ minikube tunnel
Status:
	machine: minikube
	pid: 21867
	route: 10.96.0.0/12 -> 192.168.49.2
	minikube: Running
	services: [web-lb]
    errors:
		minikube: no errors
		router: no errors
		loadbalancer emulator: no errors
$ kubectl get svc web-lb
NAME     TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)        AGE
web-lb   LoadBalancer   10.107.252.59   10.107.252.59   80:32623/TCP   16s
$ curl -s http://10.107.252.59/
<h1>hello from kind</h1>

Страница всё та же — её отдаёт образ demo/web:dev, собранный для kind. Туннель работает, пока запущен. Если его процесс убит, маршрут 10.96.0.0/12 на ноутбуке остаётся, и minikube delete его тоже не убирает — после удаления кластера он висит со статусом linkdown. minikube tunnel --cleanup удаляет записи о прежних туннелях и тут же запускает новый, то есть это не разовая команда очистки. Надёжнее всего после экспериментов проверить ip route и удалить лишнее руками: sudo ip route del 10.96.0.0/12.

Грабли minikube

  • Ресурсы по умолчанию скромные: 2 CPU и память, вычисленная от объёма ОЗУ хоста, — обычно несколько гигабайт. Для чего-то тяжелее пары подов задайте --cpus и --memory при первом старте: у существующего кластера они не меняются, только пересозданием.
  • Драйвер выбирается раз. Сменить драйвер у существующего профиля нельзя — minikube delete и заново.
  • Сеть до узла. С драйвером docker на macOS и Windows адрес узла недоступен с хоста; всё через minikube service и minikube tunnel.
  • Дополнения определяют, какие манифесты заработают. Для Ingress с аннотациями Traefik включите Traefik. Класс standard использует локальный провижинер minikube. Для томов из серии установите соответствующий CSI-драйвер.

Чем все трое отличаются от кластера kubeadm

Всё, что касается API и объектов, одинаково. Расходится то, что ниже API:

LoadBalancer. В кластере на «железе» сервису типа LoadBalancer адрес выдаёт отдельный компонент: у нас это MetalLB из части 17, отвечающий на ARP-запросы за адреса своего пула. У kind — cloud-provider-kind с контейнерами-балансировщиками, у k3s — ServiceLB с hostPort на адресах узлов, у minikube — туннель с маршрутом на ноутбуке. Манифест одинаковый, а путь пакета и поведение при отказе — нет. Проверять на ноутбуке, как сервис переживает падение узла, бессмысленно.

Хранилище. Все трое по умолчанию дают тома-каталоги на узле. Тома NFS или SMB, с которыми работает стенд, ведут себя иначе: том доступен с любого узла, есть права на стороне сервера, блокировки, задержки сети. Ошибки с fsGroup, root_squash и правами на каталог на ноутбуке не воспроизводятся.

Сеть. kindnet и Flannel не реализуют NetworkPolicy так, как Calico (у k3s политики делает отдельный контроллер kube-router, у kind — свой минимальный); Calico-специфичные ресурсы GlobalNetworkPolicy в них отсутствуют. В kind по умолчанию kube-proxy работает через iptables, у стенда — nftables. Сеть узлов kind — мост Docker, и MTU, маршрутизация между подсетями, VXLAN поверх реальной сети в нём не встречаются.

Ядро и runtime. Поды kind и minikube (с драйвером docker) работают на ядре ноутбука, вложенными в контейнер-узел. Ограничения cgroups, sysctl, seccomp и AppArmor — те, что разрешил Docker. На macOS и Windows это вообще ядро служебной ВМ Docker Desktop.

Устойчивость. Кластер kind переживает перезапуск Docker не всегда: адреса контейнеров в сети kind могут смениться, и сертификаты, выписанные на старые адреса, перестанут подходить. Это одноразовые кластеры — их не лечат, а пересоздают, и это правильный способ работы с ними: конфигурация кластера в файле, создание за минуту.


Сравнение

kind k3s minikube
Узел контейнер Docker/Podman Linux-машина (ВМ, железо) ВМ или контейнер, по драйверу
Установка Kubernetes внутри kubeadm свой бинарь, без kubeadm kubeadm
Хранилище состояния etcd SQLite (kine), встроенный etcd, PostgreSQL/MySQL etcd
Control plane статические поды горутины процесса k3s server статические поды
CNI по умолчанию kindnet Flannel (VXLAN) bridge / kindnet, выбор --cni
LoadBalancer cloud-provider-kind (отдельно) ServiceLB, встроен minikube tunnel
Ingress через cloud-provider-kind Traefik, встроен дополнения traefik или ingress
Тома local-path, класс standard local-path, класс local-path hostpath, класс standard
Несколько узлов да, файлом конфигурации да, агентами на других машинах да, --nodes
Свой образ без реестра kind load импорт в containerd (k3s ctr images import) minikube image load
Время создания ~1 мин ~30 с на узел 1–3 мин
Годится для постоянной работы нет да нет

Когда какой

  • kind — CI и проверка манифестов. Кластер поднимается в любом раннере, где есть Docker, конфигурация в одном файле, версия Kubernetes выбирается образом. Тесты самого Kubernetes и большинства операторов гоняют на kind. Многоузловой кластер на одном ноутбуке — тоже к нему.
  • k3s — когда нужен настоящий, долго живущий кластер на скромном железе: домашний сервер, филиал, граничное устройство, учебный кластер на одной ВМ. Это полноценный дистрибутив, его используют и в эксплуатации. Для ноутбука — через k3d или в ВМ.
  • minikube — разработка на рабочей станции, особенно на macOS и Windows: драйверы ВМ, addons, туннели решают большинство проблем «как достучаться до сервиса». Если нужно отдельное ядро — тоже к нему.

Для полноты: есть MicroK8s — дистрибутив Canonical, ставится snap-пакетом и, как minikube, включает компоненты командой microk8s enable; k0s — ещё один дистрибутив «одним бинарём», от Mirantis; и встроенный Kubernetes в Docker Desktop и Rancher Desktop — галочка в настройках, поднимающая одноузловой кластер в служебной ВМ. Устроены они по тем же образцам, что разобраны выше.


Смотрим руками

Узлы kind — это контейнеры:

$ docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Ports}}'
NAMES               IMAGE                  PORTS
lab-worker          kindest/node:v1.37.0
lab-worker2         kindest/node:v1.37.0
lab-control-plane   kindest/node:v1.37.0   127.0.0.1:37483->6443/tcp, 127.0.0.1:8080->30080/tcp

$ docker inspect lab-control-plane --format '{{.HostConfig.Privileged}}'
true

$ docker exec lab-worker ps -e -o pid,comm | head -12
    PID COMMAND
      1 systemd
     95 systemd-journal
    139 containerd
    239 kubelet
    290 containerd-shim
    321 containerd-shim
    333 pause
    352 pause
    409 kube-proxy
    584 kindnetd
    788 containerd-shim

PID 1 — systemd, под ним те же процессы, что на k8s-node-1. Внутрь узла можно зайти и пользоваться теми же инструментами, что на стенде:

$ docker exec -it lab-worker bash
root@lab-worker:/# crictl ps
CONTAINER           IMAGE               CREATED              STATE               NAME                ATTEMPT             POD ID              POD                    NAMESPACE
f560e14023694       50bfe947daf23       59 seconds ago       Running             web                 0                   317fd06885853       web-85c764c6f7-fkfpz   default
2be56b80e9552       4626fe10df5b9       About a minute ago   Running             kindnet-cni         0                   0b114641a368f       kindnet-skbsr          kube-system
39934b0c1394f       d6a28daf3e6b0       About a minute ago   Running             kube-proxy          0                   fb7cf17ed540e       kube-proxy-lznlh       kube-system

root@lab-worker:/# ip route
default via 172.18.0.1 dev eth0
172.18.0.0/16 dev eth0 proto kernel scope link src 172.18.0.3
172.20.0.0/24 via 172.18.0.2 dev eth0
172.20.1.0/24 via 172.18.0.4 dev eth0
172.20.2.2 dev veth25d6c26d scope host

Так работает kindnet: подсети соседних узлов — обычные маршруты через их адреса в сети Docker, у пода — veth с маршрутом /32 на узле. Никакой инкапсуляции.

Образ, загруженный kind load, в containerd узла:

root@lab-worker:/# ctr -n k8s.io images ls -q | grep demo
docker.io/demo/web:dev

Имя дополнилось до docker.io/demo/web:dev: короткое имя без реестра containerd считает образом с Docker Hub. Если бы imagePullPolicy был Always, kubelet пошёл бы именно туда.

k3s — один процесс вместо статических подов:

user@k3s-1:~$ ps -e -o pid,rss,args | grep -E 'k3s (server|agent)|containerd ' | grep -v grep
   3650 970152 /usr/local/bin/k3s server
   3666 192072 containerd

user@k3s-1:~$ ls /etc/kubernetes/manifests
ls: cannot access '/etc/kubernetes/manifests': No such file or directory

user@k3s-1:~$ sudo ls -la /var/lib/rancher/k3s/server/db/
total 23800
drwx------ 2 root root     4096 Nov  7 15:13 .
drwx------ 8 root root     4096 Nov  7 15:13 ..
-rw-r--r-- 1 root root 12087296 Nov  7 15:13 state.db
-rw-r--r-- 1 root root    32768 Nov  7 15:14 state.db-shm
-rw-r--r-- 1 root root 12236432 Nov  7 15:14 state.db-wal

API-сервер, планировщик, controller-manager и kubelet — в одном процессе k3s server (около 950 МБ памяти на всё через минуту после запуска), containerd — отдельный дочерний процесс, и аргументов у него в ps не видно. На агенте k3s agent занимает около 190 МБ. Состояние кластера — файл SQLite с журналом WAL. Внутрь можно заглянуть обычным sqlite3: kine хранит объекты в таблице kine с ключами вида /registry/pods/kube-system/coredns-… — та же схема ключей, что у etcd в кластере kubeadm.

user@k3s-1:~$ sudo sqlite3 /var/lib/rancher/k3s/server/db/state.db \
    "select name from kine where name like '/registry/services/specs/%' group by name;"
/registry/services/specs/default/kubernetes
/registry/services/specs/kube-system/kube-dns
/registry/services/specs/kube-system/metrics-server
/registry/services/specs/kube-system/traefik

ServiceLB на узле:

user@k3s-1:~$ sudo k3s kubectl get svc -n kube-system traefik
NAME      TYPE           CLUSTER-IP     EXTERNAL-IP                     PORT(S)                      AGE
traefik   LoadBalancer   10.43.65.154   192.168.122.12,192.168.122.11   80:31293/TCP,443:30503/TCP   58s

user@k3s-1:~$ sudo iptables -t nat -S | grep 'CNI-DN.*dport 80 .*DNAT'
-A CNI-DN-11cd7dd21d93ed04fed3d -p tcp -m tcp --dport 80 -j DNAT --to-destination 10.42.0.8:80

EXTERNAL-IP — адреса самих узлов. Цепочка CNI-DN-… — работа CNI-плагина portmap, который реализует hostPort пода svclb-traefik: пакет на порт 80 узла уходит в под ServiceLB, а оттуда — на ClusterIP Traefik.

minikube — узел и его маршрут:

$ minikube ssh -- sudo crictl pods --name storage
POD ID              CREATED              STATE               NAME                  NAMESPACE           ATTEMPT             RUNTIME
76dbfd8e2ed2f       About a minute ago   Ready               storage-provisioner   kube-system         0                   (default)

$ ip route | grep -E '10.96|192.168.49'
10.96.0.0/12 via 192.168.49.2 dev br-a52b9036e7e3
192.168.49.0/24 dev br-a52b9036e7e3 proto kernel scope link src 192.168.49.1

minikube tunnel добавил маршрут к сети сервисов через адрес узла minikube в мосту Docker — именно так пакеты на «внешний» адрес LoadBalancer попадают в кластер.

Удаление — одной командой у каждого: kind delete cluster --name lab, minikube delete, /usr/local/bin/k3s-uninstall.sh на сервере и k3s-agent-uninstall.sh на агентах.


Кластеры на одной машине пригодятся и после всей серии: манифесты, чарты и конфигурацию для GitOps удобно сперва проверить на kind, k3s или minikube — создать кластер за минуту и выбросить без сожаления. А как поведут себя LoadBalancer, сетевые тома и сеть между узлами, покажет только настоящий кластер вроде нашего стенда.


Источники

Оставить комментарий могут только зарегистрированные пользователи.

Войдите на сайт или зарегистрируйтесь, чтобы оставить комментарий.