Приветствую всех,
Сегодняшний пост на тему настройки кластера kubernetes при помощи утилиты kubeadm. На текущий момент kubeadm является native инструментом для работы с кластерами. В отличие от kubespray, данный инструмент на выходе дает нам простой, не перегруженный софтами кластер. Если же запускаете куб на голом железе или в виртуальной инфрастуктуре, то 8 из 10 рекомендаций будет в пользу kubeadm. С точки зрения прокачки опыта работы с кубами, тоже плюс к понимаю как его готовить.
Обратная сторона медали это достаточно много ручного труда в процессе инсталяции. Для продакшен сетапа, вам самостоятельно нужно будет проработать вопрос отказаустойчивости вашего кластера. Хотя в kubespray это задача решается из коробки.
Инфраструктура
Перейдем к задаче. В своей виртуальной среде подготовил две небольшие виртулки (4-core, 8-ram, 50-disk) с установленным из образа AlmaLinux 8.9 (Minimal). Данный сетап будет состоять из одного control plane инстанса и одной worker-ноды. Вопрос отказоустойчивости проработаем в следующих заметках.
Подготовка серверов
Ранее я упомянул, что при сетапе кластера понадобится провернуть достаточное ручных действий. Большинство операция выполняются на этапе подготовки сервером.
Следующие действия нужно будет сделать на всех серверах, независимо от роли в кластере.
Первым же делом отключаем/перенастраиваем SELinux, это нужно сделать на серверах с установленным rhel-образным дистрибутивом.
[All servers] sed -i 's/^SELINUX[^ ]*/SELINUX=permissive/' /etc/selinux/config
В данном случаи мы перенастраиваем SELinux на вывод предупреждений. Если же используемый вами дистрибутив debian/ubuntu, то можете скипнуть эту команду. Вроде бы не потребуется действий для перенастройки AppArmor.
Далее на серверах выключаем swap, и отключаем из монтирования swap-раздел:
[All servers] sed -i '/swap/s/^/#/' /etc/fstab
[All servers] swapoff -a
Для корректной работы kubelet
, необходимо выключать swap
. Начиная с версии v1.28
включили поддержку swap
, но эта фича только на стадии бета-тестирования. И согласно докумендации работает только при использовании драйвера cgroup v2
.
Следующим этапом нужно включить два дополнительным модуля ядра. Модуль overlay
нужен для поддержки overlayfs
, с помощью этой файловой системы появляется возможность наложение read/write слоев одного дерева каталогов на другое read-only дерево. Модуль br_netfilter
нужен для поддержки линуксовых bridge(мостов) и работы VxLAN.
Для загрузки этих модулей при старте системы, нужно создать файлик в директории /etc/modules-load.d
:
[All servers] cat <<EOF | tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
К параметрам ядра требуется включить две опции - bridge-nf-call-iptables
, ip_forward
:
[All servers] cat <<EOF | tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
EOF
Данные параметры включают форвард пакетов между интерфейсами. И bridge-nf-call-iptables
определяет будут ли пакеты проходящие через bridge обрабатываться правилами в iptables.
На фаерволе также требуется открыть несколько портов. На стороне master-ноды открываем порты:
[root@k8s-master ~]# firewall-cmd --add-port={6443,2379,2379,10250,10259,10257}/tcp --permanent
[root@k8s-master ~]# firewall-cmd --reload
А на стороне воркер-ноды открываем порты:
[root@k8s-worker ~]# firewall-cmd --add-port={10250,30000-32767}/tcp --permanent
[root@k8s-worker ~]# firewall-cmd --reload
Для применения настроек можем перезапустить оба сервера:
[All servers] reboot
Теперь приступим к настройке runtime. Для начала установим runC
, это низкоуровневая среда для запуска наших контейнеров, согласно OCI
спецификациям. Переходим по ссылке на репозиторий, и качаем последний релиз.
[All servers] cd /opt/
[All servers] wget https://github.com/opencontainers/runc/releases/download/v1.1.12/runc.amd64
Устанавливаем скачанный бинарь:
[All servers] install -m 755 runc.amd64 /usr/local/sbin/runc
Для взаимодействия с runc, будем использовать приложение более высокого уровня. сontrainerd
- это системный демон для менелжмента контейнеров, с его помощью скачиваются образа контейнеров, настраивается сетка и дисковое пространство. Ну также он участвует в запуске контейнеров и мониторит их состояние.
С гитхаба качаем бинарь:
[All servers] cd /opt/
[All servers] wget https://github.com/containerd/containerd/releases/download/v1.7.13/containerd-1.7.13-linux-amd64.tar.gz
Скачанный архив распакуем в директорию для иполняемых файлов:
[All servers] tar Cxzvf /usr/local containerd-1.7.13-linux-amd64.tar.gz
Что бы запустить containerd
также с гитхаба скачаем systemd-юнит файл:
[All servers] curl -o /etc/systemd/system/containerd.service https://raw.githubusercontent.com/containerd/containerd/main/containerd.service
Перечитываем конфигурацию systemd и запускаем containerd:
[All servers] systemctl daemon-reload
[All servers] systemctl enable --now containerd
# Чекнуть после запуска
[All servers] systemctl status containerd
Собственно для работы с контейнерами мы все поставили, как можете заметить мы не устанавливали doсker
. В нашем сетапе kubelet напрямую будет взаимодействовать с рантаймом.
Как мы знаем для контроля выделяемых ресурсов в Linux используется одна из функций ядра - Cgroups
(Control Groups). Чтобы установленный нами runtime и kubelet мог взаимодействовать с cgroups
нужно установить драйвер.
Есть два пути:
cgroupfs driver
systemd cgroup driver
В более ранних релизах куба, по умолчанию kubelet использовал драйвер - cgroupfs driver
. Он был не совместим с системами инициализации systemd
.
Поэтому мы для корретной работы кластера нужно переконфигурить containerd
. Конфигурацию для kubelet
править не нужно, так как начиня с релиза v1.22
и выше, по умолчанию устанавливается systemd cgroup driver
.
Генерим конфигу для containerd командой:
[All servers] mkdir -p /etc/containerd/
[All servers] containerd config default > /etc/containerd/config.toml
Затем нужно будет в текстовом редакторе открывать конфигу, найти параметр SystemdCgroup
и изменить его значение на true
:
[All servers] vi /etc/containerd/config.toml
---
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
...
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
....
SystemdCgroup = true
Для применения настроек, перезапускаем systemd-юнит:
[All servers] systemctl restart containerd
Есть еще один момент, он опциональный. Если же в вашей инфре используется proxy, то нам дополнительно потребуется добавить адреса прокси в окружение containerd. Просто создаем каталог:
[All servers] mkdir /etc/systemd/system/containerd.service.d
Далее создаем файлик, в котором определяем настройки прокси:
[All servers] vi /etc/systemd/system/containerd.service.d/http-proxy.conf
---
[Service]
Environment="HTTP_PROXY=http://10.8.10.17:8080"
Environment="HTTPS_PROXY=http://10.8.10.17:8080"
Environment="NO_PROXY=*.domain.local,127.0.0.1,10.12.0.0/14"
Перечитываем конфиг systemd и перезапускаем containerd:
[All servers] systemctl daemon-reload
[All servers] systemctl restart containerd
Инициализация кластера
На этом все, предварительные подготовка серверов выполнена. Теперь остается выполнить парочку простых действий, и кластер будет готов к работе. Сначала установим утилиты для работы с кластером. Ставить будем из специального репозитория, через стандартный пакетный менеджер.
Добавляем репозиторий:
[All servers] cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.29/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.28/rpm/repodata/repomd.xml.key
exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
EOF
Устанавливаем kubelet
, kubeadm
, kubectl
:
[All servers] yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes
Демон kubelet ставим во автозагрузку, на случай перезапуска системы:
[All servers] systemctl enable --now kubelet
Теперь идем на мастер ноду, и инициализируем новый кластер:
[root@k8s-master ~]# kubeadm init --pod-network-cidr=10.12.0.0/16
Опцией --pod-network-cidr
указывается сеть для будущих подов. В течении 3-5 минут, после успешной установки нас будет ждать вывод в консольке:
Your Kubernetes control-plane has initialized successfully!
To start using your cluster, you need to run the following as a regular user:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Alternatively, if you are the root user, you can run:
export KUBECONFIG=/etc/kubernetes/admin.conf
You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
https://kubernetes.io/docs/concepts/cluster-administration/addons/
Then you can join any number of worker nodes by running the following on each as root:
kubeadm join 192.168.173.166:6443 --token 6pa16s.588414k6q5i843qo \
--discovery-token-ca-cert-hash sha256:6215219f43aa2508382e9889bc0b554d7c518839f7e705d59b994c6bb5eff1f8
В этом выводе представлено парочка полезных рекомендаций.
Для подключения к нашему кластеру нужно в домашнем каталоге создать скрытую папку и переместить туда конфиг:
[root@k8s-master ~]# mkdir -p $HOME/.kube
[root@k8s-master ~]# cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
[root@k8s-master ~]# chown $(id -u):$(id -g) $HOME/.kube/config
После чего пробуем обратится к кластеру утилитой kubectl
:
[root@k8s-master ~]# kubectl get no
NAME STATUS ROLES AGE VERSION
k8s-master.domain.local NotReady control-plane 4m57s v1.29.2
Собственно успех, идем далее.
Посмотрим на состояние подов:
[root@k8s-master ~]# kubectl get po -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-76f75df574-7vbvq 0/1 Pending 0 7m29s
kube-system coredns-76f75df574-hlc74 0/1 Pending 0 7m29s
kube-system etcd-k8s-master.domain.local 1/1 Running 0 7m43s
kube-system kube-apiserver-k8s-master.domain.local 1/1 Running 0 7m43s
kube-system kube-controller-manager-k8s-master.domain.local 1/1 Running 0 7m43s
kube-system kube-proxy-p79hr 1/1 Running 0 7m29s
kube-system kube-scheduler-k8s-master.domain.local 1/1 Running 0 7m43s
Как можно понять из вывода все работает, за исключением подов с coredns. Скорее всего они не поднялись из-за отсутствия сетевого плагина в кластере.
Убедится в этом можно командой:
[root@k8s-master ~]# kubectl -n kube-system describe po coredns-76f75df574-7vbvq
В конце вывода нас будет ждать сообшение:
Warning FailedScheduling 4m50s (x2 over 10m) default-scheduler 0/1 nodes are available: 1 node(s) had untolerated taint {node.kubernetes.io/not-ready: }. preemption: 0/1 nodes are available: 1 Preemption is not helpful for scheduling.
Это может означать, что а нашем кластере нет подходящих нод для размещения этих подов.
Давайте капнем далее и посмотрим ивенты нашей мастер ноды:
[root@k8s-master ~]# kubectl -n kube-system describe no k8s-master.domain.local
В начале вывода, раздел Conditions
последнее сообщение будет таким:
NetworkReady=false reason:NetworkPluginNotReady message:Network plugin returns error: cni plugin not initialized
И вот то самое сообщение, в котором нам говорится об не инициализированном cni плагине.
Установка сетевого плагина
Установим сетевой плагин, для этого сетапа буду использовать flannel
. С гитхаба качаю семп манифеста:
[root@k8s-master opt]# wget https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
Манифест нужно подредактировать, и изменить подсеть. Я укажу подсеть 10.12.0.0/16
, так как ее использовал для иницилизации кластера.
[root@k8s-master opt]# vi kube-flannel.yml
---
net-conf.json: |
{
"Network": "10.12.0.0/16",
"Backend": {
"Type": "vxlan"
}
}
Применяем манифест:
[root@k8s-master opt]# kubectl apply -f kube-flannel.yml
Проверяем теперь состояние подов:
[root@k8s-master opt]# kubectl get po -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-flannel kube-flannel-ds-n8knf 1/1 Running 0 30s
kube-system coredns-76f75df574-7vbvq 1/1 Running 0 23m
kube-system coredns-76f75df574-hlc74 1/1 Running 0 23m
kube-system etcd-k8s-master.domain.local 1/1 Running 0 23m
kube-system kube-apiserver-k8s-master.domain.local 1/1 Running 0 23m
kube-system kube-controller-manager-k8s-master.domain.local 1/1 Running 0 23m
kube-system kube-proxy-p79hr 1/1 Running 0 23m
kube-system kube-scheduler-k8s-master.domain.local 1/1 Running 0 23m
Все в рабочем состоянии.
Добавление worker-ноды
Теперь мы можем добавить нашего воркера в кластер:
kubeadm join 192.168.173.166:6443 --token 6pa16s.588414k6q5i843qo \
--discovery-token-ca-cert-hash sha256:6215219f43aa2508382e9889bc0b554d7c518839f7e705d59b994c6bb5eff1f8
Сообщение об успешном добавлении будет таким,
This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.
На мастере также проверим текущие ноды:
[root@k8s-master opt]# kubectl get no
NAME STATUS ROLES AGE VERSION
k8s-master.domain.local Ready control-plane 30m v1.29.2
k8s-worker.domain.local Ready <none> 2m23s v1.29.2
Отлично нода была добавлена.
Запустим тестовый под для проверки работы:
[root@k8s-master opt]# kubectl run nginx --image=nginx
pod/nginx created
Контейнер был запущен:
[root@k8s-master opt]# kubectl get po
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 19s
Далее через порт-форвард попробуем обратиться к нашему поду. На мастере выполняем проброс портов, командой:
[root@k8s-master opt]# kubectl port-forward pod/nginx 8080:80
На машине мастера откроется порт 8080/tcp, на который обратившись через curl, получим ответ от nginx:
[root@k8s-master ~]# curl http://localhost:8080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
На этом этапе задача выполнена, теперь у нас есть рабочая песочница для последующих экспериментов. Как и говорил ранее большую часть времени отнимает подготовительный процесс, само поднятие кластера дело пяти минут. С помошью puppet или ansible можно ускорить этапы настройки серверов, далее постараюсь оставить свой след относительно этой темы. =)