Развернём полноценный Kubernetes (не k3s и не microk8s) через kubeadm на трёх серверах с Ubuntu 22.04 LTS. Версия Kubernetes — v1.33.13 (LTS), container runtime — containerd, CNI — Calico через Tigera Operator.
Требования
- 3 сервера на Ubuntu 22.04 LTS: один под control-plane, два под worker.
- Каждый сервер — минимум 2 vCPU и 2 GB RAM (официальный минимум kubeadm; на практике control-plane комфортнее от 4 GB).
- Уникальные hostname, MAC-адрес и product_uuid на каждой ноде — стандартно для отдельных машин, но стоит проверить при клонировании VM.
- Полная сетевая связность между всеми тремя нодами — напрямую или через VPN/туннель.
- Root-доступ или sudo на каждой ноде.
Проверить product_uuid можно так:
cat /sys/class/dmi/id/product_uuidМежду нодами должны быть открыты порты:
- 6443 — Kubernetes API server (control-plane).
- 2379–2380 — etcd client/peer (control-plane).
- 10250 — kubelet API (все ноды).
- 10251, 10252 — scheduler и controller-manager (control-plane).
- 30000–32767 — NodePort Services (worker-ноды).
1. Базовая подготовка ОС (на всех нодах)
Задаём имя хоста — на каждой ноде своё:
hostnamectl set-hostname master1Ставим chrony и отключаем systemd-resolved в пользу статичного /etc/resolv.conf:
apt update
apt install chrony -y
systemctl disable --now systemd-resolved
rm -f /etc/resolv.conf
echo 'nameserver 1.1.1.1' > /etc/resolv.conf
echo 'nameserver 8.8.8.8' >> /etc/resolv.conf
nslookup ya.ruОтключаем ufw, если firewall управляется на другом уровне инфраструктуры:
systemctl disable --now ufwОбновляем систему и перезагружаемся:
apt update
apt upgrade -y
reboot2. Отключаем swap (на всех нодах)
swapoff -aПостоянно — правкой /etc/fstab: комментируем строку со swap.
Важно: если swap не отключить полностью (включая /etc/fstab), при kubeadm join kubelet может не пройти health-check за отведённые 4 минуты и уйти в CrashLoopBackOff с ошибкой unable to load bootstrap kubeconfig — этот случай разобран в разделе «Возможные проблемы» ниже. Проверяйте free -h перед следующим шагом.
3. Модули ядра и sysctl (на всех нодах)
cat <<EOF | tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
modprobe overlay
modprobe br_netfilter
lsmod | egrep "overlay|br_netfilter"
cat <<EOF | tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sysctl --system4. Установка containerd (на всех нодах)
apt install containerd -y
mkdir -p /etc/containerd
containerd config default | tee /etc/containerd/config.toml > /dev/null
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
systemctl restart containerd
systemctl enable containerdБез SystemdCgroup = true kubelet и containerd используют разные cgroup driver — кластер не инициализируется.
5. Установка kubeadm, kubelet и kubectl (на всех нодах)
Старый репозиторий apt.kubernetes.io закрыт с 2023 года — используем pkgs.k8s.io:
apt install -y apt-transport-https ca-certificates curl gpg
KUB_VER=1.33
curl -fsSL https://pkgs.k8s.io/core:/stable:/v${KUB_VER}/deb/Release.key | \
gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v${KUB_VER}/deb/ /" | \
tee /etc/apt/sources.list.d/kubernetes.list
apt update
apt install -y kubelet kubeadm kubectl
apt-mark hold kubelet kubeadm kubectl6. Инициализация control-plane (только на control-plane)
kubeadm init \
--control-plane-endpoint "<IP_CONTROL_PLANE>:6443" \
--apiserver-advertise-address=<IP_CONTROL_PLANE> \
--pod-network-cidr=10.244.0.0/16--apiserver-advertise-address обязателен, если на ноде несколько сетевых интерфейсов — без него kubeadm может выбрать не тот. --control-plane-endpoint закладывает возможность перейти на HA (несколько control-plane нод) без пересоздания кластера.
Настраиваем kubectl:
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/configСохраняем команду kubeadm join из вывода — она понадобится для worker-нод. Токен живёт 24 часа, при необходимости перегенерируем:
kubeadm token create --print-join-command7. Установка Calico через Tigera Operator (только на control-plane)
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.31.4/manifests/tigera-operator.yaml
kubectl get pods -n tigera-operatorСкачиваем файл с настройками и правим CIDR — он должен совпадать с --pod-network-cidr:
wget https://raw.githubusercontent.com/projectcalico/calico/v3.31.4/manifests/custom-resources.yaml
vi custom-resources.yamlВ файле меняем cidr: 192.168.0.0/16 на cidr: 10.244.0.0/16 — дефолт Tigera-оператора не совпадает с CIDR, который задавали в kubeadm init.
Применяем:
kubectl create -f custom-resources.yaml
kubectl get pods -n calico-systemДожидаемся, пока все поды перейдут в Running, а control-plane — в Ready:
kubectl get nodes8. Присоединение worker-нод (на каждом worker)
Токен из вывода kubeadm init живёт 24 часа. Если истёк — генерируем новый на control-plane:
kubeadm token create --print-join-commandНа каждом worker'е:
kubeadm join <IP_CONTROL_PLANE>:6443 --token <токен> \
--discovery-token-ca-cert-hash sha256:<хэш>9. Финальная проверка (на control-plane)
kubectl get nodes
kubectl get pods -n calico-system -o wideОжидаемый результат — все ноды в статусе Ready:
NAME STATUS ROLES AGE VERSION
master1 Ready control-plane 30m v1.33.13
worker1 Ready <none> 16m v1.33.13
worker2 Ready <none> 6m v1.33.13Возможные проблемы
kubelet падает в CrashLoopBackOff после kubeadm join
Команда kubeadm join завершилась таймаутом:
[kubelet-check] Waiting for a healthy kubelet at http://127.0.0.1:10248/healthz. This can take up to 4m0s
[kubelet-check] The kubelet is not healthy after 4m0.000639042sПричина — не был отключён swap перед join. В логе journalctl -xeu kubelet видна конкретная ошибка:
E ... "command failed" err="failed to run Kubelet: unable to load bootstrap kubeconfig: stat /etc/kubernetes/bootstrap-kubelet.conf: no such file or directory"Файл bootstrap-kubelet.conf не успел создаться до того, как join прервался по таймауту — нода осталась в промежуточном состоянии.
Решение:
free -h
swapoff -a
vi /etc/fstab # комментируем строку со swapСбрасываем недоделанное состояние ноды:
kubeadm reset -fПроверяем, что containerd жив:
systemctl status containerdПолучаем свежий токен на control-plane и повторяем join:
kubeadm join <IP_CONTROL_PLANE>:6443 --token <новый_токен> \
--discovery-token-ca-cert-hash sha256:<хэш>На этот раз kubelet проходит health-check за секунды, а не за 4 минуты:
[kubelet-check] The kubelet is healthy after 1.008980757s
This node has joined the cluster
