DataLife Engine / Разворачиваем кластер Kubernetes (kubeadm) на Ubuntu 22.04

Разворачиваем кластер Kubernetes (kubeadm) на Ubuntu 22.04


Развернём полноценный Kubernetes (не k3s и не microk8s) через kubeadm на трёх серверах с Ubuntu 22.04 LTS. Версия Kubernetes — v1.33.13 (LTS), container runtime — containerd, CNI — Calico через Tigera Operator.

Требования




Проверить product_uuid можно так:
cat /sys/class/dmi/id/product_uuid


Между нодами должны быть открыты порты:


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
reboot


2. Отключаем 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 --system


4. Установка 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 kubectl


6. Инициализация 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-command


7. Установка 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 nodes


8. Присоединение 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
Вчера, 23:44
Вернуться назад