Приветствую всех,

Сегодняшний пост на тему настройки кластера 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 можно ускорить этапы настройки серверов, далее постараюсь оставить свой след относительно этой темы. =)