Отказоустойчивый Kubernetes на BGP. Часть 2: готовим узлы Ubuntu 24.04
Во второй части серии подготовим узлы. Всё в этой статье выполняется на каждом из восьми узлов (и на мастерах, и на воркерах), если не сказано иное.
Серия «Отказоустойчивый Kubernetes на BGP»
- Схема и план
- Готовим узлы Ubuntu 24.04 (эта статья)
- BGP на роутере (FRR)
- kube-vip в режиме BGP и первый мастер
- Calico с eBPF и рабочие узлы
- Адреса LoadBalancer из Calico по BGP
- Traefik за BGP и externalTrafficPolicy Local
- Проверка отказоустойчивости и разбор полётов
Имя и адрес узла
Имя узла в Kubernetes берётся из hostname, поэтому сразу делаем его полным (FQDN) и одинаковым с DNS:
sudo hostnamectl set-hostname k8s-master-1.example.com
Статический адрес
В моём случае адреса сначала выдавались по DHCP, и это обернулось неприятностью. После обновления glibc пакет needrestart перезапустил systemd-networkd, мастер потерял аренду и не получил её обратно — узел пролежал недоступным 17 часов. С тех пор все адреса в кластере статические.
# /etc/netplan/50-cloud-init.yaml
network:
version: 2
ethernets:
eth0:
dhcp4: false
dhcp6: false
addresses: [192.168.10.17/24] # у каждого узла свой
routes:
- to: default
via: 192.168.10.1
metric: 100
Если применяете файл на живом узле по SSH, делайте это с откатом, иначе рискуете остаться без доступа:
sudo cp /etc/netplan/50-cloud-init.yaml /root/netplan-backup.yaml
sudo netplan generate
sudo bash -c 'nohup bash -c "netplan apply; sleep 6; ping -c 2 -W 2 192.168.10.1 >/dev/null || { cp /root/netplan-backup.yaml /etc/netplan/50-cloud-init.yaml; netplan apply; }" >/root/netplan-apply.log 2>&1 &'
DNS
На узлах должно быть два DNS-сервера в /etc/resolv.conf, и сам файл должен быть обычным файлом, а не ссылкой в /run/systemd/resolve/. Если systemd-resolved выключен (так надо, если CoreDNS пересылает внешние имена в resolv.conf узла, — заглушка 127.0.0.53 создаст петлю), то ссылка после перезагрузки повиснет, и kubelet не сможет создать ни одного пода: Failed to create pod sandbox: open /etc/resolv.conf: no such file. Я наступил на это на одном из узлов при плановой перезагрузке.
sudo systemctl disable --now systemd-resolved
sudo rm /etc/resolv.conf
printf 'nameserver 192.168.10.15\nnameserver 192.168.10.10\n' | sudo tee /etc/resolv.conf
Имя control plane в /etc/hosts
Рабочие узлы подключаются к API по имени control.k8s.example.com. Если DNS пропадёт, kubelet не найдёт API, и узлы перейдут в NotReady — хотя сам кластер при этом цел. У меня это один раз случилось на самом деле: упал единственный DNS-сервер сайта, и семь узлов из восьми стали NotReady. Лечится одной строкой на каждом узле:
echo '192.168.20.1 control.k8s.example.com' | sudo tee -a /etc/hosts
echo '192.168.10.17 k8s-master-1.example.com k8s-master-1' | sudo tee -a /etc/hosts # имя самого узла
Платить за это приходится одним: при смене адреса VIP запись надо поправить на всех узлах.
Swap, модули ядра, sysctl
С версии 1.32 kubelet не стартует при включённом swap, поэтому выключаем его сразу и навсегда:
sudo swapoff -a
sudo sed -i '/\sswap\s/ s/^/#/' /etc/fstab
Модули и параметры, которые проверяет kubeadm при предполётной проверке:
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes.conf
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF
sudo sysctl --system
containerd
Ставлю containerd.io из репозитория Docker (в Ubuntu он заметно старше):
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" \
| sudo tee /etc/apt/sources.list.d/docker.list
sudo apt-get update
sudo apt-get install -y containerd.io
Конфигурация по умолчанию, в которой включаем systemd в роли драйвера cgroup:
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
sudo systemctl restart containerd
Образ pause (sandbox) в конфигурации должен совпадать с тем, который ожидает kubeadm; сверяем после установки пакетов Kubernetes:
kubeadm config images list | grep pause
grep -n sandbox /etc/containerd/config.toml
О том, как я когда-то ставил containerd, у меня есть и более ранние заметки: Готовим чистый Ubuntu Server к тому, чтобы быть узлом Kubernetes кластера с CRI: ContainerD.
kubeadm, kubelet, kubectl
Репозиторий Kubernetes привязан к минорной версии, поэтому номер версии есть прямо в URL:
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.36/deb/Release.key \
| sudo 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:/v1.36/deb/ /' \
| sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl containerd.io
sudo systemctl enable --now kubelet
apt-mark hold здесь не декорация. Когда у containerd.io hold нет, он обновляется вместе с остальными пакетами при первом же apt upgrade, и версии на узлах разъезжаются. Обновлять кластер надо по плану, по одной минорной версии: перед каждым шагом apt-mark unhold, после него — снова hold.
Время
etcd и сертификаты чувствительны к времени, а в домене Active Directory рассинхронизация ещё и ломает Kerberos. Я не стал полагаться на внешние серверы: три мастера — NTP-серверы кластера, воркеры берут время у них.
На мастерах — chrony, в отдельном файле, стоковый chrony.conf не трогаем:
# /etc/chrony/conf.d/cluster.conf на k8s-master-1
peer 192.168.10.18
peer 192.168.10.19
allow 192.168.10.0/24
local stratum 10 orphan
local stratum 10 orphan оставляет службу полезной при потере интернета: мастера договариваются между собой, вместо того чтобы разойтись. Ещё одна мелочь: в штатном юните chrony нет Restart=, и если OOM-killer убьёт chronyd, он сам не поднимется. Добавляем:
# /etc/systemd/system/chrony.service.d/override.conf
[Service]
Restart=always
На воркерах хватает systemd-timesyncd:
# /etc/systemd/timesyncd.conf.d/cluster.conf
[Time]
NTP=192.168.10.17 192.168.10.18 192.168.10.19
FallbackNTP=ntp.ubuntu.com
Автообновления: только списки пакетов
Узел кластера не должен сам себя обновлять. Я уже упоминал needrestart, который перезапускает службы после обновления библиотек. Запрещаем ему трогать то, на чём держится узел:
# /etc/needrestart/conf.d/50-kubernetes-needrestart.conf
$nrconf{override_rc}{qr(^systemd-networkd)} = 0;
$nrconf{override_rc}{qr(^kubelet)} = 0;
$nrconf{override_rc}{qr(^containerd)} = 0;
И оставляем apt только обновление списков, без установки обновлений:
# /etc/apt/apt.conf.d/20auto-upgrades
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "0";
Раз в месяц, в окно, обновляем узлы руками по одному: apt-get update && apt-get upgrade, needrestart -r l показывает, что ждёт перезапуска, затем перезагрузка с drain и uncordon.
Особенность Hyper-V: nr_cpus
Если ваши узлы — виртуальные машины Hyper-V, сразу добавьте в параметры ядра nr_cpus. Гостю объявляется 240 «возможных» процессоров (smpboot: Allowing 240 CPUs, 236 hotplug CPUs) при четырёх реальных, и ядро выделяет per-CPU память на все 240. Обычному узлу это безразлично, но у Calico в режиме eBPF карты с per-CPU значениями, и у меня 1,3–1,5 ГБ на каждом узле уходило впустую.
sudo sed -i 's/^GRUB_CMDLINE_LINUX_DEFAULT="/&nr_cpus=4 /' /etc/default/grub
sudo update-grub
sudo reboot
Проверка после перезагрузки:
grep Percpu /proc/meminfo # десятки мегабайт, а не гигабайт
При добавлении виртуальных процессоров число в GRUB надо менять.
Проверка перед kubeadm
На каждом узле:
swapon --show # пусто
lsmod | grep -E 'overlay|br_netfilter'
sysctl net.ipv4.ip_forward # = 1
systemctl is-active containerd # active
kubeadm version -o short # v1.36.x
getent hosts control.k8s.example.com
Узлы готовы. Но прежде чем создавать кластер, нужен роутер, который умеет принимать маршруты: в части 3 настроим FRR.
Оставить комментарий могут только зарегистрированные пользователи.
Войдите на сайт или зарегистрируйтесь, чтобы оставить комментарий.