
Введение
Давайте рассмотрим нестандартный вариант установки Kubernetes, а именно установим кластер без control-plane ноды как таковой в стандартном ее восприятии. Сделаем некий аналог установки кластера Kubernetes где слой control-plane будет просто хостом без CRI, только базове systemd сервисы на поддержке одной команды, а worker ноды предоставляются как сервис командам без прямого взаимодействия этих команд с управляющим слоем. Для реализации возьмем 3 сервера.
Стенд
Три ВМ в VirtualBox (host-only сеть ‘192.168.56.0/24’):
|
Роль |
Hostname |
IP |
Что крутится |
|---|---|---|---|
|
Master |
node1 |
192.168.56.230 |
etcd, apiserver, controller-manager, scheduler (+ kubectl) |
|
Worker |
node2 |
192.168.56.231 |
containerd, kubelet, kube-proxy, Cilium |
|
Worker |
node3 |
192.168.56.232 |
containerd, kubelet, kube-proxy, Cilium |
ОС: Ubuntu 20.04 2 CPU, 4 Gb Ram, 30 Gb HDD
Версии компонентов для установки:
-
Kubernetes v1.30.14
-
etcd v3.5.21
-
containerd 1.7.27
-
Cilium v1.16.10
-
CoreDNS v1.11.3
Сеть кластера:
-
Pod CIDR: ‘10.244.0.0/16’
-
Service CIDR: ‘10.96.0.0/12’
-
Cluster DNS: ‘10.96.0.10’
Процесс установки
Для начала необходимо подготовить сервера. Тут все по классике отключаем фаил подкачки, включаем модуля, настраиваем sysctl и ставим необходимые пакеты.
# На каждой ноде (под sudo):swapoff -ased -i '/ swap /s/^/#/' /etc/fstabcat >/etc/modules-load.d/k8s.conf <<EOFoverlaybr_netfilterEOFmodprobe overlaymodprobe br_netfiltercat >/etc/sysctl.d/99-kubernetes.conf <<EOFnet.bridge.bridge-nf-call-iptables = 1net.bridge.bridge-nf-call-ip6tables = 1net.ipv4.ip_forward = 1EOFsysctl --systemapt-get updateapt-get install -y apt-transport-https ca-certificates curl gnupg \ conntrack ipset iptables socat nfs-commonsystemctl disable --now ufw || true
После чего на машине, с которой будет происходить установка, необходимо скачать необходимые бинарники: apiserver, controller-manager, scheduler, kubelet, kube-proxy, kubectl, etcd, containerd.
K8S_VER=v1.30.14ETCD_VER=v3.5.21CONTAINERD_VER=1.7.27RUNC_VER=v1.2.6CNI_VER=v1.6.2curl -LO https://dl.k8s.io/${K8S_VER}/kubernetes-server-linux-amd64.tar.gzcurl -LO https://github.com/etcd-io/etcd/releases/download/${ETCD_VER}/etcd-${ETCD\\_VER}-linux-amd64.tar.gzcurl -LO https://github.com/containerd/containerd/releases/download/v${CONTAINERD_VER}/containerd-${CONTAINERD\\_VER}-linux-amd64.tar.gzcurl -LO https://github.com/opencontainers/runc/releases/download/${RUNC_VER}/runc.amd64curl -LO https://github.com/containernetworking/plugins/releases/download/${CNI_VER}/cni-plugins-linux-amd64-${CNI\\_VER}.tgzcurl -LO https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.30.1/crictl-v1.30.1-linux-amd64.tar.gz
На ней же мы генерируем необходимые сертификаты. В принципе тут сложного быть ничего не должно, все это подробно и не один раз описывалось в статьях Kubernetes the Hard Way по этому смысла особенно зацикливаться на данных вопросах я не вижу.
А вот дальше начинается самое интересное… На сервер с control-plane мы ставим ‘etcd’, ‘etcdctl’, ‘kube-apiserver’, ‘kube-controller-manager’, ‘kube-scheduler’, ‘kubectl’ а так же копируем необходимые сертификаты. На worker ноды мы устанавливаем ‘containerd’, ‘containerd-shim-runc-v2’, ‘ctr’, ‘runc’, ‘crictl’, ‘kubelet’, ‘kube-proxy’ (ставил я его как systemd сервис и при установке cilium не менял kube-proxy. Тут каждый может на будущее решить что ему нужно, но в рамках лабораторного стенда вопрос не принципиальный).
И после запуска сервисов на сервере с control-plane и worker нодах вы получаете кластер Kubernetes, выполнив к которому команду kubectl get nodes вы получите в выводе только worker ноды.
kubectl get nodesNAME STATUS ROLES AGE VERSIONnode2 Ready worker 12h v1.30.14node3 Ready worker 12h v1.30.14kubectl get pods -ANAMESPACE NAME READY STATUS RESTARTS AGEkube-system cilium-7wmn4 1/1 Running 0 12hkube-system cilium-operator-66d-wb6x2 1/1 Running 0 12hkube-system cilium-vcpg2 1/1 Running 0 12hkube-system coredns-8df57d68f-gzbx9 1/1 Running 0 12hkube-system coredns-8df57d68f-hks76 1/1 Running 0 12h
Почему же так получается? Регистрацию ноды в Kubernetes API выполняет kubelet а не kubeadm. Kubeadm только выпускает токен на основании которого kubelet запрашивает постоянный сертификат. Регистрация ноды — создание объекта Node. Когда у kubelet есть сертификат, он делает следующее:
-
Отправляет запрос на регистрацию (если —register-node=true, что по умолчанию).
-
Передаёт данные о ноде: имя, IP, CPU, RAM, ОС, версии kubelet/container runtime, метки (labels), тэйнты (taints) и т.п.
-
API-сервер создаёт объект Node (или обновляет существующий, если имя уже занято).
С этого момента control plane знает о ноде и ее видно в выводе команды kubectl get nodes. Ну и так как мы не ставили на сервер с компонентами control plane kubelet соответственно мы и не регистрировали данный сервер как ноду kubernetes кластра. Возникает вопрос как же тогда удалить worker ноду из кластера, ведь если мы удалим ноду командой kubectl delete node и перезапустим kubelet нода снова появится в кластере? Ответ так же прост. После удаления из кластера worker ноду необходимо остановить kubelet или отозвать ранее выданный сертификат то бы никто не смог запустить новую ноду и не украсть ваш workload.
Нюанс для тех, кто будет повторять в данном сетапе
-
Устанавливал cilium через cilium-cli:
cilium install --version 1.16.10 \ --set ipam.mode=kubernetes \--set kubeProxyReplacement=false \--set hubble.enabled=false \--set envoy.enabled=false \ --set bpf.preallocateMaps=false
Почему так:
kubeProxyReplacement=false - kube-proxy остаётся systemd-сервисом на воркерах.Hubble/Envoy/preallocateMaps - экономия RAM на нодах.
Через несколько минут:
DaemonSet cilium 2/2 Readycilium-operator 1/1 ReadyNodes Ready
2. При старте CoreDNS с фиксированным IP в сервисе совпадающим с тем, что прописан в kubelet получаю ошибку вида
[FATAL] plugin/loop: Loop ... detected for zone "."
Почему так происходит? На хостах ‘/etc/resolv.conf’ смотрит в systemd-resolved (‘127.0.0.53’). CoreDNS с плагином ‘loop’ обнаруживает петлю и умирает.
Фикс для лабы: убрать ‘loop’ из Corefile и форвардить наружу явно:
forward . 8.8.8.8 1.1.1.1
После рестарта Deployment — обе реплики Running, ‘nslookup kubernetes.default’ из busybox отдаёт ‘10.96.0.1’, внешний DNS тоже отвечает.
Что же дальше?
А дальше самое интересное. Подобные реализации помогают разграничивать зоны ответственности в кластере, выдавать заказчику только worker ноды без доступа к управляющему слою Kubernetes, защищают от ряда CVE например CVE-2023-5408 и другие.
В плане развития — в будущем сервер с control plane может спокойно оказаться не отдельным или отдельными серверами, а всего лишь подами в неком кластере Kubernetes который будет управлять ими (и в котором по мимо данного кластера могут в отдельных неймспейсах храниться другие поды которые будут являться control plane других кластеров), в случае необходимости скейлить api, автоматизировать выдачу сертификатов и многое другое. Дальше все зависит от целей, задач и полета фантазий.
ссылка на оригинал статьи https://habr.com/ru/articles/1061228/