Я в свободное время погружаюсь в Kubernetes. На текущий момент я решил на практике разобраться, как выполняется установка Kubernetes в отказоустойчивой конфигурации. Этому и будет посвящена данная публикация.
Я использовал конфигурацию ниже для знакомства с HA k8s и тестирования различных сценариев. Не уверен, что конфигурация ниже оптимальна для использования в продуктивной среде. Если вы решили использовать её в продуктивной среде, то только на свой страх и риск.
Архитектура решения
Общая архитектура отказоустойчивого решения приведена на странице документации.

Конкретно в моем случае архитектура была следующая:

Аппаратная конфигурация всех узлов идентичная — 4 vCPU, 4 GB RAM, 40 GB system drive.

Подготовка серверов
В качестве операционной системы я буду использовать Ubuntu Server 22.04. Материал ниже я взял со страницы документации, но немного адаптировал его под мое окружение.
Отключение swap
Также перед выполнение последующий действий необходимо отключить файл подкачки на всех узлах кластера:
sudo swapoff -a
Чтобы при последующий перезагрузках узлов кластера файл подкачки не активировался вновь необходимо добавить соответствующее задание в файл crontab на всех узлах кластера:
sudo crontab -e
Добавим следующее простое задание:
@reboot /sbin/swapoff -a

Сохраним внесенные изменения и закроем файл crontab. В случае успешного добавления задания должен отобразиться соответствующий вывод на консоль:
crontab: installing new crontabroman@master01:~$
Теперь приступим к выполнению других шагов.
Установка и настройка cri-o
В качестве интерфейса выполнения контейнеров (CRI) я буду использовать cri-o, т.к. начиная с версии Kubernetes 1.24 с docker не все так просто.
Теперь все готово для установки cri-o. Установим необходимые пакеты и подготовим переменные:
sudo apt updatesudo apt install -y apt-transport-https ca-certificates curl gnupg2 software-properties-commonexport OS_VERSION=xUbuntu_20.04export CRIO_VERSION=1.23
Установим gpg ключи:
curl -fsSL https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS_VERSION/Release.key | sudo gpg —dearmor -o /usr/share/keyrings/libcontainers-archive-keyring.gpgcurl -fsSL https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/$CRIO_VERSION/$OS_VERSION/Release.key | sudo gpg —dearmor -o /usr/share/keyrings/libcontainers-crio-archive-keyring.gpg
Добавим файлы репозитория:
echo «deb [signed-by=/usr/share/keyrings/libcontainers-archive-keyring.gpg] https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS_VERSION/ /» | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.listecho «deb [signed-by=/usr/share/keyrings/libcontainers-crio-archive-keyring.gpg] https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/$CRIO_VERSION/$OS_VERSION/ /» | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable:cri-o:$CRIO_VERSION.list
Запустим установку пакетов для cri-o:
sudo apt updatesudo apt install -y cri-o cri-o-runc
Создадим конфигурационные файлы для cri-o:
cat <<EOF | sudo tee /etc/modules-load.d/crio.confoverlaybr_netfilterEOF# Set up required sysctl params, these persist across reboots.cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.confnet.bridge.bridge-nf-call-iptables = 1net.ipv4.ip_forward = 1net.bridge.bridge-nf-call-ip6tables = 1EOF
Загрузим модули для корректной работы сети нашего кластера:
sudo modprobe overlaysudo modprobe br_netfilter
Перечитаем параметры конфигурации ядра со всех конфигурационных файлов:
sudo sysctl —system
Запустим службы:
sudo systemctl daemon-reloadsudo systemctl enable crio —now
Установка kubeadm, kubelet и kubectl
Установим gpg ключи и добавим репозитории:
echo «deb [signed-by=/etc/apt/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main» | sudo tee /etc/apt/sources.list.d/kubernetes.list
Установим kubelet, kubeadm и kubectl:
curl -fsSL https://dl.k8s.io/apt/doc/apt-key.gpg | sudo gpg —dearmor -o /etc/apt/keyrings/kubernetes-archive-keyring.gpgsudo apt update -ysudo apt install -y kubelet kubeadm kubectlsudo apt-mark hold kubelet kubeadm kubectl
Предварительная настройка узлов
1. Сначала настроим сервис kubelet, чтобы он имел более высший приоритет, чем тот, который был настроен автоматически при установке kubelet выше. Это действие необходимо выполнить для каждого узла, где будет размешаться etcd:
cat << EOF > /etc/systemd/system/kubelet.service.d/kubelet.conf# Replace «systemd» with the cgroup driver of your container runtime. The default value in the kubelet is «cgroupfs».# Replace the value of «containerRuntimeEndpoint» for a different container runtime if needed.#apiVersion: kubelet.config.k8s.io/v1beta1kind: KubeletConfigurationauthentication: anonymous: enabled: false webhook: enabled: falseauthorization: mode: AlwaysAllowcgroupDriver: systemdaddress: 127.0.0.1containerRuntimeEndpoint: unix:///var/run/crio/crio.sockstaticPodPath: /etc/kubernetes/manifestsEOFcat << EOF > /etc/systemd/system/kubelet.service.d/20-etcd-service-manager.conf[Service]ExecStart=ExecStart=/usr/bin/kubelet —config=/etc/systemd/system/kubelet.service.d/kubelet.conf
Restart=alwaysEOF
systemctl daemon-reload
systemctl restart kubelet
Проверим статус сервиса:
systemctl status kubelet
2. Теперь создадим конфигурационный файл для kubeadm. Файл необходимо будет сгенерировать для каждого узла с etcd. Скрипт по генерации конфигурации приведен ниже (только измените IP-адреса и имена узлов). Я сгенерирую все файлы на первом узле (etc01):
# Update HOST0, HOST1 and HOST2 with the IPs of your hostsexport HOST0=10.10.10.46
export HOST1=10.10.10.43
export HOST2=10.10.10.39
# Update NAME0, NAME1 and NAME2 with the hostnames of your hostsexport NAME0=»etc01″export NAME1=»etc02″export NAME2=»etc03″
# Create temp directories to store files that will end up on other hosts
mkdir -p /tmp/${HOST0}/ /tmp/${HOST1}/ /tmp/${HOST2}/HOSTS=(${HOST0} ${HOST1} ${HOST2})NAMES=(${NAME0} ${NAME1} ${NAME2})
for i in «${!HOSTS[@]}»; doHOST=${HOSTS[$i]}NAME=${NAMES[$i]}cat << EOF > /tmp/${HOST}/kubeadmcfg.yaml—api
Version: «kubeadm.k8s.io/v1beta3»
kind: Init
ConfigurationnodeRegistration: name: ${NAME}localAPIEndpoint: advertiseAddress: ${HOST}—apiVersion: «kubeadm.k8s.io/v1beta3″kind: ClusterConfigurationetcd: local: serverCertSANs: — «${HOST}» peerCertSANs: — «${HOST}» extraArgs: initial-cluster: ${NAMES[0]}=https://${HOSTS[0]}:2380,${NAMES[1]}=https://${HOSTS[1]}:2380,${NAMES[2]}=https://${HOSTS[2]}:2380 initial-cluster-state: new name: ${NAME} listen-peer-urls: https://${HOST}:2380 listen-client-urls: https://${HOST}:2379 advertise-client-urls: https://${HOST}:2379 initial-advertise-peer-urls: https://${HOST}:2380EOFdone
Результат работы скрипта:
ls -l /tmp/10*root@etc01:/home/roman# ls -l /tmp/10*/tmp/10.10.10.39:total 4-rw-r—r— 1 root root 772 Aug 12 11:39 kubeadmcfg.yaml/tmp/10.10.10.43:total 4-rw-r—r— 1 root root 772 Aug 12 11:39 kubeadmcfg.yaml/tmp/10.10.10.46:total 4-rw-r—r— 1 root root 772 Aug 12 11:39 kubeadmcfg.yamlroot@etc01:/home/roman# 
3. Сгенерируем необходимые сертификаты:
kubeadm init phase certs etcd-ca
Результат работы команды:
ls -l /etc/kubernetes/pki/etcd/
root@etc01:/home/roman# ls -l /etc/kubernetes/pki/etcd/
total 8
-rw-r—r— 1 root root 1094 Aug 12 11:41 ca.crt
-rw——- 1 root root 1675 Aug 12 11:41 ca.key

4. Сгенерируем сертификаты для остальных узлов:
kubeadm init phase certs etcd-server —config=/tmp/${HOST2}/kubeadmcfg.yaml
kubeadm init phase certs etcd-peer —config=/tmp/${HOST2}/kubeadmcfg.yaml
kubeadm init phase certs etcd-healthcheck-client —config=/tmp/${HOST2}/kubeadmcfg.yaml
kubeadm init phase certs apiserver-etcd-client —config=/tmp/${HOST2}/kubeadmcfg.yaml
cp -R /etc/kubernetes/pki /tmp/${HOST2}/
# cleanup non-reusable certificates
find /etc/kubernetes/pki -not -name ca.crt -not -name ca.key -type f -delete
kubeadm init phase certs etcd-server —config=/tmp/${HOST1}/kubeadmcfg.yaml
kubeadm init phase certs etcd-peer —config=/tmp/${HOST1}/kubeadmcfg.yaml
kubeadm init phase certs etcd-healthcheck-client —config=/tmp/${HOST1}/kubeadmcfg.yaml
kubeadm init phase certs apiserver-etcd-client —config=/tmp/${HOST1}/kubeadmcfg.yaml
cp -R /etc/kubernetes/pki /tmp/${HOST1}/find /etc/kubernetes/pki -not -name ca.crt -not -name ca.key -type f -delete
kubeadm init phase certs etcd-server —config=/tmp/${HOST0}/kubeadmcfg.yaml
kubeadm init phase certs etcd-peer —config=/tmp/${HOST0}/kubeadmcfg.yaml
kubeadm init phase certs etcd-healthcheck-client —config=/tmp/${HOST0}/kubeadmcfg.yaml
kubeadm init phase certs apiserver-etcd-client —config=/tmp/${HOST0}/kubeadmcfg.yaml
# No need to move the certs because they are for HOST0
# clean up certs that should not be copied off this host
find /tmp/${HOST2} -name ca.key -type f -delete
find /tmp/${HOST1} -name ca.key -type f -delete
Листинг работы команд выше:
# No need to move the certs because they are for HOST0
# clean up certs that should not be copied off this hostfind /tmp/${HOST2} -name ca.key -type f -delete
find /tmp/${HOST1} -name ca.key -type f -delete
[certs] Generating «etcd/server» certificate and key
[certs] etcd/server serving cert is signed for DNS names [etc03 localhost] and IPs [10.10.10.39 127.0.0.1 ::1]
[certs] Generating «etcd/peer» certificate and key
[certs] etcd/peer serving cert is signed for DNS names [etc03 localhost] and IPs [10.10.10.39 127.0.0.1 ::1]
[certs] Generating «etcd/healthcheck-client» certificate and key[certs] Generating «apiserver-etcd-client» certificate and key
[certs] Generating «etcd/server» certificate and key
[certs] etcd/server serving cert is signed for DNS names [etc02 localhost] and IPs [10.10.10.43 127.0.0.1 ::1]
[certs] Generating «etcd/peer» certificate and key
[certs] etcd/peer serving cert is signed for DNS names [etc02 localhost] and IPs [10.10.10.43 127.0.0.1 ::1]
[certs] Generating «etcd/healthcheck-client» certificate and key[certs] Generating «apiserver-etcd-client» certificate and key
[certs] Generating «etcd/server» certificate and key
[certs] etcd/server serving cert is signed for DNS names [etc01 localhost] and IPs [10.10.10.46 127.0.0.1 ::1]
[certs] Generating «etcd/peer» certificate and key
[certs] etcd/peer serving cert is signed for DNS names [etc01 localhost] and IPs [10.10.10.46 127.0.0.1 ::1]
[certs] Generating «etcd/healthcheck-client» certificate and key
[certs] Generating «apiserver-etcd-client» certificate and key

5. Скопируем сгенерированные файлы в соответствующие директории на всех хостах etcd (только скорректируйте имя пользователя для подключения):
USER=roman
HOST=${HOST1}
scp -r /tmp/${HOST}/* ${USER}@${HOST}:ssh ${USER}@${HOST}USER@HOST
$ sudo -Esroot@HOST
$ chown -R root:root pkiroot@HOST
$ mv pki /etc/kubernetes/
Листинг команды выше:
root@etc01:/home/roman# USER=roman
root@etc01:/home/roman# HOST=${HOST1}
root@etc01:/home/roman# scp -r /tmp/${HOST}/* ${USER}@${HOST}:
roman@10.10.10.43’s password:
root@etc01:/home/roman# ssh ${USER}@${HOST}
roman@10.10.10.43’s password:
roman@etc02:~$ sudo -Es
[sudo] password for roman:
root@etc02:~# chown -R root:root pki
root@etc02:~# mv pki /etc/kubernetes/
Выполняем аналогичное копирование и для последнего узла etcd:
USER=romanHOST=${HOST2}scp -r /tmp/${HOST}/* ${USER}@${HOST}:ssh ${USER}@${HOST}USER@HOST
$ sudo -Es
root@HOST
$ chown -R root:root pkiroot@HOST
$ mv pki /etc/kubernetes/
6. Убедитесь, что в директории /tmp у вас подготовлен конфигурационный файл kubeadmcfg.yaml для каждого из узлов:
ls -l /tmp/10.10.10.*/kubeadmcfg.yaml
root@etc01:/home/roman# ls -l /tmp/10.10.10.*/kubeadmcfg.yaml
-rw-r—r— 1 root root 772 Aug 12 11:39 /tmp/10.10.10.39/kubeadmcfg.yaml
-rw-r—r— 1 root root 772 Aug 12 11:39 /tmp/10.10.10.43/kubeadmcfg.yaml
-rw-r—r— 1 root root 772 Aug 12 11:39 /tmp/10.10.10.46/kubeadmcfg.yaml
root@etc01:/home/roman#

Также убедитесь, что на каждый узел были скопированы необходимые файлы в директорию /etc/kubernetes/pki:
tree /etc/kubernetes/pki
root@etc01:/home/roman#
tree /etc/kubernetes/pki
/etc/kubernetes/pki
├── apiserver-etcd-client.crt
├── apiserver-etcd-client.key
└── etcd ├── ca.crt
├── ca.key
├── healthcheck-client.crt
├── healthcheck-client.key
├── peer.crt ├── peer.key
├── server.crt
└── server.key
1 directory, 10 files
root@etc01:/home/roman#

7. Теперь необходимо создать манифесты. Выполним вот эту команду на первом узле:
kubeadm init phase etcd local —config=/tmp/${HOST0}/kubeadmcfg.yamlroot@etc01:/home/roman# kubeadm init phase etcd local —config=/tmp/${HOST0}/kubeadmcfg.yaml[etcd] Creating static Pod manifest for local etcd in «/etc/kubernetes/manifests»
А вот такой командой создаем манифесты на двух других узлах etcd:
kubeadm init phase etcd local —config=$HOME/kubeadmcfg.yamlroman@etc02:~$ sudo kubeadm init phase etcd local —config=$HOME/kubeadmcfg.yaml[sudo] password for roman: [etcd] Creating static Pod manifest for local etcd in «/etc/kubernetes/manifests»
8. Теперь можно установить etcdctl и проверить состояние здоровье кластера etcd:
ETCDCTL_API=3 etcdctl —cert /etc/kubernetes/pki/etcd/peer.crt —key /etc/kubernetes/pki/etcd/peer.key —cacert /etc/kubernetes/pki/etcd/ca.crt —endpoints https://${HOST0}:2379 endpoint healthroot@etc01:/home/roman/etcd-v3.5.9-linux-amd64# ETCDCTL_API=3 etcdctl —cert /etc/kubernetes/pki/etcd/peer.crt —key /etc/kubernetes/pki/etcd/peer.key —cacert /etc/kubernetes/pki/etcd/ca.crt —endpoints https://${HOST0}:2379 endpoint healthhttps://10.10.10.46:2379 is healthy: successfully committed proposal: took = 11.053467ms
Команда выше должна выполниться успешно для каждого узла — ${HOST0}, ${HOST1} и ${HOST2}.
Подготовка узлов плоскости управления и рабочей нагрузки
Нетеперь для узлов, на которых будет размещена нагрузка плоскости управления и рабочей нагрузки необходимо выполнить предварительную подготовку серверов. Установить среду выполнения контейнеров, kubeadm и kubectl. Подготовка выполняется аналогично подготовке узлов для etcd. В качестве операционной системы я буду использовать Ubuntu Server 22.04.
Отключение swap
Также перед выполнение последующий действий необходимо отключить файл подкачки на всех узлах кластера:
sudo swapoff -a
Чтобы при последующий перезагрузках узлов кластера файл подкачки не активировался вновь необходимо добавить соответствующее задание в файл crontab на всех узлах кластера:
sudo crontab -e
Добавим следующее простое задание:
@reboot /sbin/swapoff -a
Сохраним внесенные изменения и закроем файл crontab. В случае успешного добавления задания должен отобразиться соответствующий вывод на консоль:
crontab: installing new crontabroman@master01:~$
Теперь приступим к выполнению других шагов.
Установка и настройка cri-o
В качестве интерфейса выполнения контейнеров (CRI) я буду использовать cri-o, т.к. начиная с версии Kubernetes 1.24 с docker не все так просто.
Теперь все готово для установки cri-o. Установим необходимые пакеты и подготовим переменные:
sudo apt updatesudo apt install -y apt-transport-https ca-certificates curl gnupg2 software-properties-commonexport OS_VERSION=xUbuntu_20.04export CRIO_VERSION=1.23
Установим gpg ключи:
curl -fsSL https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS_VERSION/Release.key | sudo gpg —dearmor -o /usr/share/keyrings/libcontainers-archive-keyring.gpgcurl -fsSL https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/$CRIO_VERSION/$OS_VERSION/Release.key | sudo gpg —dearmor -o /usr/share/keyrings/libcontainers-crio-archive-keyring.gpg
Добавим файлы репозитория:
echo «deb [signed-by=/usr/share/keyrings/libcontainers-archive-keyring.gpg] https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS_VERSION/ /» | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.listecho «deb [signed-by=/usr/share/keyrings/libcontainers-crio-archive-keyring.gpg] https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/$CRIO_VERSION/$OS_VERSION/ /» | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable:cri-o:$CRIO_VERSION.list
Запустим установку пакетов для cri-o:
sudo apt updatesudo apt install -y cri-o cri-o-runc
Создадим конфигурационные файлы для cri-o:
cat <<EOF | sudo tee /etc/modules-load.d/crio.confoverlaybr_netfilterEOF# Set up required sysctl params, these persist across reboots.cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.confnet.bridge.bridge-nf-call-iptables = 1net.ipv4.ip_forward = 1net.bridge.bridge-nf-call-ip6tables = 1EOF
Загрузим модули для корректной работы сети нашего кластера:
sudo modprobe overlaysudo modprobe br_netfilter
Перечитаем параметры конфигурации ядра со всех конфигурационных файлов:
sudo sysctl —system
Запустим службы:
sudo systemctl daemon-reloadsudo systemctl enable crio —now
Установка kubeadm, kubelet и kubectl
Установим gpg ключи и добавим репозитории:
echo «deb [signed-by=/etc/apt/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main» | sudo tee /etc/apt/sources.list.d/kubernetes.list
Установим kubelet, kubeadm и kubectl:
curl -fsSL https://dl.k8s.io/apt/doc/apt-key.gpg | sudo gpg —dearmor -o /etc/apt/keyrings/kubernetes-archive-keyring.gpgsudo apt update -ysudo apt install -y kubelet kubeadm kubectlsudo apt-mark hold kubelet kubeadm kubectl
Подготовка отказоустойчивого балансировщика
Для того, чтобы балансировщик не был единичной точкой отказа его также нужно подготовить в отказоустойчивой конфигурации. Один из вариантов — это использование haproxy и keepalived. Соответственно, нужно подготовить два балансировщика с haproxy и keepalived. В качестве операционной системы я буду использовать Ubuntu Server 22.04.
Установим необходимые пакеты:
sudo apt updatesudo apt install -y keepalived haproxy
Создадим файл с конфигурацией на первом балансировщике:
/etc/keepalived/keepalived.conf
Содержимое конфигурационного файла:
! /etc/keepalived/keepalived.conf! Configuration File for keepalivedglobal_defs { router_id LVS_DEVEL}vrrp_script check_apiserver { script «/etc/keepalived/check_apiserver.sh» interval 3 weight -2 fall 10 rise 2}vrrp_instance VI_1 { state MASTER interface ens33 virtual_router_id 51 priority 101 authentication { auth_type PASS auth_pass 42 } virtual_ipaddress { 10.10.10.63 } track_script { check_apiserver }}
Сохраним внесенные изменения.

На скриншоте выше я выделил те параметры, которые вам нужно будет изменить в соответствии с вашим окружением. Вот тут в документации есть подробное описание всех параметров.
Создадим конфигурационный файл на втором балансировщике:
/etc/keepalived/keepalived.conf
Но его конфигурация будет немного отличаться:
! /etc/keepalived/keepalived.conf! Configuration File for keepalivedglobal_defs { router_id LVS_DEVEL}vrrp_script check_apiserver { script «/etc/keepalived/check_apiserver.sh» interval 3 weight -2 fall 10 rise 2}vrrp_instance VI_1 { state BACKUP interface ens33 virtual_router_id 51 priority 100 authentication { auth_type PASS auth_pass 42 } virtual_ipaddress { 10.10.10.63 } track_script { check_apiserver }}
Второй балансировщик будет резервный, т.е. его статус будет BACKUP. Также изменен приоритет.
Теперь необходимо настроить скрипт проверки состояния сервера api, который мы указали в конфигурации keepalived (script «/etc/keepalived/check_apiserver.sh»):
sudo nano /etc/keepalived/check_apiserver.sh
Содержимое скрипта:
#!/bin/sherrorExit() { echo «*** $*» 1>&2 exit 1}curl —silent —max-time 2 —insecure https://localhost:6443/ -o /dev/null || errorExit «Error GET https://localhost:6443/»if ip addr | grep -q 10.10.10.63; then curl —silent —max-time 2 —insecure https://10.10.10.63:6443/ -o /dev/null || errorExit «Error GET https://10.10.10.63:6443/»fi
Аналогичный скрипт необходимо создать и на втором балансировщике.
Теперь отредактируем конфигурационный файл haproxy:
sudo nano /etc/haproxy/haproxy.cfg
Итоговое содержимое моего конфигурационного файла следующее:
globallog /dev/loglocal0log /dev/loglocal1 noticechroot /var/lib/haproxystats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listenersstats timeout 30suser haproxygroup haproxydaemon# Default SSL material locationsca-base /etc/ssl/certscrt-base /etc/ssl/private# See: https://ssl-config.mozilla.org/#server=haproxy&server-version=2.0.3&config=intermediate ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256 ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-ticketsdefaultslogglobalmodehttpoptionhttplogoptiondontlognull timeout connect 5000 timeout client 50000 timeout server 50000option http-server-close option forwardfor except 127.0.0.0/8 option redispatch retries 1 timeout http-request 10s timeout queue 20s timeout connect 5s timeout client 20s timeout server 20s timeout http-keep-alive 10s timeout check 10serrorfile 400 /etc/haproxy/errors/400.httperrorfile 403 /etc/haproxy/errors/403.httperrorfile 408 /etc/haproxy/errors/408.httperrorfile 500 /etc/haproxy/errors/500.httperrorfile 502 /etc/haproxy/errors/502.httperrorfile 503 /etc/haproxy/errors/503.httperrorfile 504 /etc/haproxy/errors/504.http#———————————————————————# apiserver frontend which proxys to the control plane nodes#———————————————————————frontend apiserver bind *:6443 mode tcp option tcplog default_backend apiserver#———————————————————————# round robin balancing for apiserver#———————————————————————backend apiserver option httpchk GET /healthz http-check expect status 200 mode tcp option ssl-hello-chk balance roundrobin server kpn01 10.10.10.47:8443 checkserver kpn02 10.10.10.48:8443 checkserver kpn03 10.10.10.50:8443 check
Перезапустим службы и настроем автозапуск:
sudo systemctl enable haproxy —nowsudo systemctl enable keepalived —nowsudo systemctl restart haproxysudo systemctl restart keepalived
Последний шаг настройки балансировщиков — проверка доступности виртуального IP-адреса по опубликованному порту. Однако тут есть другой аспект — сам сервер API еще не настроен. Попробуем выполнить вот такую команду с любого сервера плоскости управления:
nc -v 10.10.10.63 6443
Вы должны увидеть следующий вывод:
root@cpn01:/home/roman# nc -v 10.10.10.63 6443Connection to 10.10.10.63 6443 port [tcp/*] succeeded!
Да, api-server еще не настроен, но haproxy уже должен принимать входящие подключения. Можно так же попробовать выполнить запрос с браузера:

Ответы вида «ERR_CONNECTION_REFUSED» и «ERR_EMPTY_RESPONSE» говорят, что мы с вами на верном пути — haproxy принял наше подключение, но ему некуда его транслировать далее.
Однако, если вы получили ответ вида «ERR_CONNECTION_TIMED_OUT», то что-то пошло не так и балансировщики или VIP (Virtual IP) недоступны. Перед тем, как идти далее необходимо разобраться в первопричинах и устранить ошибку.
Настройка узлов плоскости управления и рабочей нагрузки
Мы с вами подготовили кластер etcd и haproxy в отказоустойчивой конфигурации. Теперь перейдем к настройке серверов плоскости управления и серверов для рабочей нагрузки.
1. Скопируем сертификаты с любого сервера etcd на первый сервер плоскости управления:
export CONTROL_PLANE=»roman@10.10.10.47″scp /etc/kubernetes/pki/etcd/ca.crt «${CONTROL_PLANE}»:scp /etc/kubernetes/pki/apiserver-etcd-client.crt «${CONTROL_PLANE}»:sudo scp /etc/kubernetes/pki/apiserver-etcd-client.key «${CONTROL_PLANE}»:
2. Подключимся на первый узел плоскости управления и переместим сертификаты в нужное расположение:
sudo mkdir -p /etc/kubernetes/pki/etcdsudo mv ca.crt /etc/kubernetes/pki/etcd/ca.crtsudo mv apiserver-etcd-client.crt /etc/kubernetes/pki/apiserver-etcd-client.crtsudo mv apiserver-etcd-client.key /etc/kubernetes/pki/apiserver-etcd-client.key
3. На первом узле плоскости управления создадим файл манифеста конфигурации для kubeadm:
nano kubeadm-config.yaml
Содержимое файла:
—apiVersion: kubeadm.k8s.io/v1beta3kind: ClusterConfigurationnetworking: podSubnet: «192.168.0.0/16» # —pod-network-cidrkubernetesVersion: stablecontrolPlaneEndpoint: «10.10.10.63:6443» # change this (see below)etcd: external: endpoints: — https://10.10.10.46:2379 # change ETCD_0_IP appropriately — https://10.10.10.43:2379 # change ETCD_1_IP appropriately — https://10.10.10.39:2379 # change ETCD_2_IP appropriately caFile: /etc/kubernetes/pki/etcd/ca.crt certFile: /etc/kubernetes/pki/apiserver-etcd-client.crt keyFile: /etc/kubernetes/pki/apiserver-etcd-client.key
Подробное описание всех частей манифеста приведено в документации.
4. Теперь подготовим первый узел плоскости управления:
sudo kubeadm init —config kubeadm-config.yaml —upload-certs
Пример вывода на консоль в случае успешного завершения процедуры инициализации первого узла плоскости управления:
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/configAlternatively, if you are the root user, you can run: export KUBECONFIG=/etc/kubernetes/admin.confYou 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/You can now join any number of the control-plane node running the following command on each as root: kubeadm join 10.10.10.63:6443 —token 9cmrgy.vr51qagnitsnsa8p —discovery-token-ca-cert-hash sha256:c422ac3a716f26df82716573c55c5d90a55735c30c1bee361bf5486c776873ec —control-plane —certificate-key 13d8f82492aa9c6b9a4fb25eca2484b0a34eec7d95b97a089e62fddebc9826d6Please note that the certificate-key gives access to cluster sensitive data, keep it secret!As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use»kubeadm init phase upload-certs —upload-certs» to reload certs afterward.Then you can join any number of worker nodes by running the following on each as root:kubeadm join 10.10.10.63:6443 —token 9cmrgy.vr51qagnitsnsa8p —discovery-token-ca-cert-hash sha256:c422ac3a716f26df82716573c55c5d90a55735c30c1bee361bf5486c776873ec
Запишите команды для присоединения дополнительных узлов плоскости управления и узлов рабочей нагрузки — они нам пригодятся в дальнейшем.
5. Выполним настройку окружения для подключения к кластеру:
mkdir -p $HOME/.kubesudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/configsudo chown $(id -u):$(id -g) $HOME/.kube/config
6. Проверим состояние служебных подов:
kubectl get pod -n kube-system
roman@cpn01:~$ kubectl get pod -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-5d78c9869d-fvl64 1/1 Running 0 2m39s
coredns-5d78c9869d-gvfxc 1/1 Running 0 2m39s
kube-apiserver-cpn01 1/1 Running 0 2m50s
kube-controller-manager-cpn01 1/1 Running 0 2m50s
kube-proxy-p8snc 1/1 Running 0 2m40s
kube-scheduler-cpn01 1/1 Running 0 2m50s
roman@cpn01:~$

7. Установим CNI. Я буду использовать плагин Calico.
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.25.1/manifests/calico.yaml
Проверяем поды:
kubectl get pod -n kube-system
roman@cpn01:~$ kubectl get pod -n kube-system
NAME READY STATUS RESTARTS AGE
calico-kube-controllers-674fff74c8-v5lxz 1/1 Running 0 68s
calico-node-z8cb5 1/1 Running 0 68s
coredns-5d78c9869d-fvl64 1/1 Running 0 5m7s
coredns-5d78c9869d-gvfxc 1/1 Running 0 5m7s
kube-apiserver-cpn01 1/1 Running 0 5m18s
kube-controller-manager-cpn01 1/1 Running 0 5m18s
kube-proxy-p8snc 1/1 Running 0 5m8skube-scheduler-cpn01 1/1 Running 0 5m18s
roman@cpn01:~$

8. Скопируем сертификаты с первого узла плоскости управления на осnальные узлы плоскости управления:
USER=roman
# customizable
CONTROL_PLANE_IPS=»10.10.10.48 10.10.10.50″
for host in ${CONTROL_PLANE_IPS}; do sudo scp /etc/kubernetes/pki/ca.crt «${USER}»@$host: sudo scp /etc/kubernetes/pki/ca.key «${USER}»@$host: sudo scp /etc/kubernetes/pki/sa.key «${USER}»@$host: sudo scp /etc/kubernetes/pki/sa.pub «${USER}»@$host: sudo scp /etc/kubernetes/pki/front-proxy-ca.crt «${USER}»@$host: sudo scp /etc/kubernetes/pki/front-proxy-ca.key «${USER}»@$host: sudo scp /etc/kubernetes/pki/etcd/ca.crt «${USER}»@$host:etcd-ca.crt
done
9. Затем на каждом дополнительном узле управления переместите скопированные сертификаты в нужное расположение:
USER=roman
# customizable
sudo mkdir -p /etc/kubernetes/pki/etcdsudo
mv /home/${USER}/ca.crt /etc/kubernetes/pki/
sudo mv /home/${USER}/ca.key /etc/kubernetes/pki/
sudo mv /home/${USER}/sa.pub /etc/kubernetes/pki/
sudo mv /home/${USER}/sa.key /etc/kubernetes/pki/
sudo mv /home/${USER}/front-proxy-ca.crt /etc/kubernetes/pki/
sudo mv /home/${USER}/front-proxy-ca.key /etc/kubernetes/pki/
sudo mv /home/${USER}/etcd-ca.crt /etc/kubernetes/pki/etcd/ca.crt
10. Теперь попробуем присоединить второй узел плоскости управления:
sudo kubeadm join 10.10.10.63:6443 —token 9cmrgy.vr51qagnitsnsa8p —discovery-token-ca-cert-hash sha256:c422ac3a716f26df82716573c55c5d90a55735c30c1bee361bf5486c776873ec —control-plane —certificate-key 13d8f82492aa9c6b9a4fb25eca2484b0a34eec7d95b97a089e62fddebc9826d6
11. Аналогично командой из предыдущего пункта присоединяем к кластера третий узел плоскости управления.
12. После успешного присоединения всех узлов плоскости управления наш кластер должен насчитывать три узла:
kubectl get node
roman@cpn02:~$ kubectl get node
NAME STATUS ROLES AGE VERSION
cpn01 Ready control-plane 20m v1.27.4
cpn02 Ready control-plane 2m38s v1.27.4
cpn03 Ready control-plane 87s v1.27.4
roman@cpn02:~$

13. Осталось добавить в кластер узлы рабочей нагрузки. Добавление узлов нагрузки выполняется вот такой командой:
sudo kubeadm join 10.10.10.63:6443 —token 9cmrgy.vr51qagnitsnsa8p —discovery-token-ca-cert-hash sha256:c422ac3a716f26df82716573c55c5d90a55735c30c1bee361bf5486c776873ec
Эту команду необходимо выполнить на всех трех узлах для рабочей нагрузки.
14.Проверим состав нашего кластера после добавления узлов под рабочую нагрузку:
kubectl get noderoman@cpn02:~$ kubectl get node
NAME STATUS ROLES AGE VERSION
cpn01 Ready control-plane 25m v1.27.4
cpn02 Ready control-plane 7m31s v1.27.4
cpn03 Ready control-plane 6m20s v1.27.4
wkn01 Ready <none> 42s v1.27.4wkn02 Ready <none> 12s v1.27.4
wkn03 Ready <none> 2s v1.27.4
roman@cpn02:~$

15. Еще я бы добавил сервер метрики:
kubectl apply -f https://raw.githubusercontent.com/techiescamp/kubeadm-scripts/main/manifests/metrics-server.yaml
16. И еще добавлю Ingress контроллер:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.5.1/deploy/static/provider/baremetal/deploy.yaml
17. Итоговый перечень pod, который у меня получился. Для Ingress контроллера:
kubectl get pods -n ingress-nginx
roman@cpn01:~$ kubectl get pods -n ingress-nginx
NAME READY STATUS RESTARTS AGE
ingress-nginx-admission-create-6ph8k 0/1 Completed 0 57s
ingress-nginx-admission-patch-jcst6 0/1 Completed 0 57s
ingress-nginx-controller-846649f84c-vsx5w 0/1 Running 0 57s

Для плоскости управления:
kubectl get pods -n kube-system
roman@cpn01:~$ kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGEcalico-kube-controllers-674fff74c8-v5lxz 1/1 Running 0 9hcalico-node-4s27g 1/1 Running 0 9hcalico-node-8dp2h 1/1 Running 0 9hcalico-node-jb7lb 1/1 Running 0 9hcalico-node-lg2lq 1/1 Running 0 9hcalico-node-pl79v 1/1 Running 0 9hcalico-node-z8cb5 1/1 Running 0 9hcoredns-5d78c9869d-fvl64 1/1 Running 0 9hcoredns-5d78c9869d-gvfxc 1/1 Running 0 9hkube-apiserver-cpn01 1/1 Running 0 9hkube-apiserver-cpn02 1/1 Running 0 9hkube-apiserver-cpn03 1/1 Running 0 9hkube-controller-manager-cpn01 1/1 Running 0 9hkube-controller-manager-cpn02 1/1 Running 0 9hkube-controller-manager-cpn03 1/1 Running 0 9hkube-proxy-f55q8 1/1 Running 0 9hkube-proxy-hhdsn 1/1 Running 0 9hkube-proxy-lp584 1/1 Running 0 9hkube-proxy-p8snc 1/1 Running 0 9hkube-proxy-snlf7 1/1 Running 0 9hkube-proxy-z5p2c 1/1 Running 0 9hkube-scheduler-cpn01 1/1 Running 0 9hkube-scheduler-cpn02 1/1 Running 0 9hkube-scheduler-cpn03 1/1 Running 0 9hmetrics-server-754586b847-ktjzz 1/1 Running 0 3m9s
roman@cpn01:~$

Итоговое тестирование отказоустойчивости
Переходим к самой интересной части — тестирование. Предварительно нам нужно будет создать рабочую нагрузку.
Создание рабочей нагрузки для тестирования
Для развертывания тестовой нагрузки я буду использовать следующий манифест из одной моей прошлой публикации:
nano svc.yml
Содержимое файла:
apiVersion: apps/v1kind: Deploymentmetadata: name: web-appv1spec: selector: matchLabels: app: web template: metadata: labels: app: web spec: containers: — name: web-appv1 image: gcr.io/google-samples/hello-app:1.0—apiVersion: v1kind: Servicemetadata: name: web-service-appv1spec: selector: app: web ports: — protocol: TCP port: 50001 targetPort: 8080—apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: web-ingress-appv1 annotations: nginx.ingress.kubernetes.io/ssl-redirect: «false» nginx.ingress.kubernetes.io/force-ssl-redirect: «false»spec: ingressClassName: nginx rules: — host: web.itproblog.ru http: paths: — path: / pathType: Prefix backend: service: name: web-service-appv1 port: number: 50001
Создадим необходимые ресурсы:
kubectl apply -f svc.yaml
Проверим созданные ресурсы:
kubectl get pod
kubectl get svc
kubectl get ingress
roman@cpn01:~$ kubectl get pod
kubectl get svckubectl get ingress
NAME READY STATUS RESTARTS AGE
web-appv1-76765768c6-v6f59 1/1 Running 0 49s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 10
hweb-service-appv1 ClusterIP 10.108.77.85 <none> 50001/TCP 49s
NAME CLASS HOSTS ADDRESS PORTS AGE
web-ingress-appv1 nginx web.itproblog.ru 10.10.10.54 80 49s
roman@cpn01:~$

Также в файле hosts мне нужно будет добавить записи на имя web.itproblog.ru на IP-адреса серверов рабочей нагрузки. Или зарегистрировать DNS имя. Я отредактирую файл hosts на своей рабочей станции, чтобы в последующем выполнять тестирование непосредственно с неё:
sudo nano /etc/hosts
10.10.10.51 web.itproblog.ru
10.10.10.54 web.itproblog.ru
10.10.10.56 web.itproblog.ru
Теперь обратимся к нашему ingress контроллеру, чтобы понять, какой порт он прослушивает:
kubectl get svc -n ingress-nginx
roman@cpn01:~$ kubectl get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx-controller NodePort 10.107.17.12 <none> 80:32567/TCP,443:31751/TCP 10m
ingress-nginx-controller-admission ClusterIP 10.104.23.189 <none> 443/TCP 10m
roman@cpn01:~$ 
Как видно из листинга выше — для HTTP запросов прослушивается порт 32567.
Вот теперь попробуем обратиться к нашему тестовому приложению:
curl http://web.itproblog.ru:32567
roman@mintwks:~$ curl http://web.itproblog.ru:32567
Hello, world!Version: 1.0.0
Hostname: web-appv1-76765768c6-v6f59
roman@mintwks:~$

Аналогичную проверку вы можете запустить непосредственно в браузере:
http://web.itproblog.ru:32567
Приложение доступно. И это хорошо — значит у нас по крайней мере получилось корректно развернуть Kubernetes. Теперь нужно протестировать его на отказоустойчивость.
Тестирование отказа узла рабочей нагрузки
Начнем с самого верхнего уровня — узлы рабочей нагрузки. Давайте проверим, на каком из узлов развернута pod с нашим приложением:
kubectl get pod -o=wide
roman@cpn01:~$ kubectl get pod -o=wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-appv1-76765768c6-v6f59 1/1 Running 0 14m 192.168.83.66
wkn03 <none> <none>
roman@cpn01:~$

Как видно из скриншота выше — pod развернут на узле wkn03. Я отключу сетевой адаптер на узле wkn03 и проверим, что произойдет.
curl http://web.itproblog.ru:32567
roman@mintwks:~$ curl http://web.itproblog.ru:32567
<html><head><title>503 Service Temporarily Unavailable</title></head><body><center><h1>503 Service Temporarily Unavailable</h1></center><hr><center>nginx</center></body></html>
Ответ очевиден — что то пошло не так.
Посмотрим статусы узлов и подов:
kubectl get nodekubectl get pod -o=wide
roman@cpn01:~$ kubectl get node
NAME STATUS ROLES AGE VERSION
cpn01 Ready control-plane 10h v1.27.4
cpn02 Ready control-plane 10h v1.27.4
cpn03 Ready control-plane 10h v1.27.4
wkn01 Ready <none> 10h v1.27.4
wkn02 Ready <none> 10h v1.27.4
wkn03 NotReady <none> 10h v1.27.4
roman@cpn01:~$ kubectl get pod -o=wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-appv1-76765768c6-lxnm7 1/1 Running 0 27s 192.168.243.2 wkn01 <none> <none>
web-appv1-76765768c6-v6f59 1/1 Terminating 0 41m 192.168.83.66 wkn03 <none> <none>
roman@cpn01:~$

Как видно на скриншоте выше, узел wkn03 недоступен. По истечении определенного времени (по умолчанию 5 минут) Kubernetes создаст новую поду на узле wkn01 и удалил старую. Чтобы избежать задержек можно в определении развертывания указать нужно количество подов. Например, 2 или 3.
Проверим приложение теперь:
roman@mintwks:~$ curl http://web.itproblog.ru:32567
Hello, world!Version: 1.0.0
Hostname: web-appv1-76765768c6-lxnm7
roman@mintwks:~$

Работает. Отказ узла рабочей нагрузки с активной подой кластер пережил. Настало время для дальнейших проверок.
Тестирование отказа узла плоскости управления
Я оставлю узел рабочей нагрузки wkn03 вне сети. Пусть пока он будет недоступен. Смоделируем еще одну ситуацию — отказ одного из узлов плоскости управления. Теперь я отключу сетевой интерфейс на сервере cpn03.
Спустя буквально минуту проверим статус узлов кластера Kubernetes:
kubectl get noderoman@cpn01:~$ kubectl get node
NAME STATUS ROLES AGE VERSION
cpn01 Ready control-plane 10h v1.27.4
cpn02 Ready control-plane 10h v1.27.4
cpn03 NotReady control-plane 10h v1.27.4
wkn01 Ready <none> 10h v1.27.4wkn02 Ready <none> 10h v1.27.4
wkn03 NotReady <none> 10h v1.27.4
roman@cpn01:~$

Теперь помимо одного из узлов для рабочей нагрузки у нас в кластере «отказал» еще и один из узлов плоскости управления.
Проверим доступность нашего приложения:
roman@mintwks:~$ curl http://web.itproblog.ru:32567
Hello, world!Version: 1.0.0
Hostname: web-appv1-76765768c6-lxnm7
roman@mintwks:~$
Хорошо, приложение доступно. Попробуем создать дополнительную рабочую нагрузку в нашем кластере:
kubectl run busybox —image=busyboxroman@cpn01:~$ kubectl run busybox —image=busyboxpod/busybox createdroman@cpn01:~$
Создание дополнительного ресурса прошло успешно. Проверим статусы всех подов:
kubectl get pod
roman@cpn01:~$ kubectl get pod
NAME READY STATUS RESTARTS AGEbusybox 0/1 Completed 3 (37s ago) 65s
web-appv1-76765768c6-lxnm7 1/1 Running 0 21m
web-appv1-76765768c6-v6f59 1/1 Terminating 0 62m
roman@cpn01:~$

Поскольку никаких дополнительных параметров при создании под мы не передавали, то она сразу завершилась, что ожидаемо.
Мы проверили основные моменты — кластер Kubernetes продолжает обслуживать уже имеющуюся нагрузку. Более того, кластер успешно принимает запросы на создание дополнительных ресурсов и успешно их обрабатывает.
Тестирование отказа узла etcd
Два узла кластера мы уже «потеряли» — один узел рабочей нагрузки и один узел плоскости управления.
Всего у нас три узла в кластере с etcd:
sudo ETCDCTL_API=3 etcdctl —cacert=/etc/kubernetes/pki/etcd/ca.crt —cert=/etc/kubernetes/pki/etcd/server.crt —key=/etc/kubernetes/pki/etcd/server.key —endpoints=10.10.10.46:2379 member list4f5b17401266f2b5, started, etc01, https://10.10.10.46:2380, https://10.10.10.46:2379, falsea95398094ef8f5e0, started, etc02, https://10.10.10.43:2380, https://10.10.10.43:2379, falseb7fde4d1b69c7cb0, started, etc03, https://10.10.10.39:2380, https://10.10.10.39:2379, false

Продолжим «терять» узлы. Теперь я отключу сетевой адаптер на втором узле с etcd.
Проверим здоровье узлов etcd:
sudo ETCDCTL_API=3 etcdctl —cacert=/etc/kubernetes/pki/etcd/ca.crt —cert=/etc/kubernetes/pki/etcd/server.crt —key=/etc/kubernetes/pki/etcd/server.key —endpoints=10.10.10.46:2379,10.10.10.43:2379,10.10.10.39:2379 endpoint health
10.10.10.46:2379 is healthy: successfully committed proposal: took = 9.228848ms
10.10.10.39:2379 is healthy: successfully committed proposal: took = 12.114634ms
10.10.10.43:2379 is unhealthy: failed to commit proposal: context deadline exceeded
Error: unhealthy cluster

Проверим доступность приложения:
roman@mintwks:~$ curl http://web.itproblog.ru:32567Hello, world!Version: 1.0.0Hostname: web-appv1-76765768c6-lxnm7roman@mintwks:~$
Приложение доступно. Теперь попробуем создать дополнительный ресурс в кластере:
kubectl run ngx —image=nginxroman@cpn01:~$ kubectl run ngx —image=nginxpod/ngx createdroman@cpn01:~$
Ресурс создан. Проверим его статус:
kubectl get pod -o=wide
roman@cpn01:~$ kubectl get pod -o=wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
busybox 0/1 CrashLoopBackOff 7 (3m23s ago) 14m 192.168.212.66 wkn02 <none> <none>
ngx 1/1 Running 0 55s 192.168.243.3 wkn01 <none> <none>
web-appv1-76765768c6-lxnm7 1/1 Running 0 34m 192.168.243.2 wkn01 <none> <none>
web-appv1-76765768c6-v6f59 1/1 Terminating 0 75m 192.168.83.66 wkn03 <none> <none>
roman@cpn01:~$

Как видно из скриншота выше, вся рабочая нагрузка распределяется между оставшимися узлами рабочей нагрузки — wkn01 и wkn02.
Резюмирую, как и в случае с тестирование отказа узла плоскости управления, все текущие ресурсы доступны и кластер принимает о обрабатывает запросы на создание новых ресурсов.
Тестирование отказа одного из балансировщиков
Последняя проверка. В нашем развертывании остался еще один компонент, который мы не проверяли — балансировщик. У нас их два, работают они в режиме active-pasive. Активный узел — lb01, пассивный lb02. Я отключу сетевой адаптер на узле lb01.
Если теперь посмотреть на конфигурацию сетевых адаптеров сервера lb02, то можно заметить, что VIP был добавлен в перечень IP-адресов адаптера:
ip addrroman@lb02:~$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000 link/ether 00:0c:29:b7:69:ef brd ff:ff:ff:ff:ff:ff altname enp2s1 inet 10.10.10.62/24 metric 100 brd 10.10.10.255 scope global dynamic ens33 valid_lft 399sec preferred_lft 399sec inet 10.10.10.63/32 scope global ens33 valid_lft forever preferred_lft forever inet6 fe80::20c:29ff:feb7:69ef/64 scope link valid_lft forever preferred_lft foreverroman@lb02:~$

Проверим доступность приложения:
roman@mintwks:~$ curl http://web.itproblog.ru:32567
Hello, world!
Version: 1.0.0
Hostname: web-appv1-76765768c6-lxnm7
roman@mintwks:~$
Приложение доступно. Теперь попробуем создать еще один дополнительный ресурс в кластере:
kubectl run ngx2 —image=nginx
roman@cpn02:~$ kubectl run ngx2 —image=nginxpod/ngx2 created
roman@cpn02:~$
Ресурс создан. Проверим его статус:
kubectl get pod
roman@cpn02:~$ kubectl get pod
NAME READY STATUS RESTARTS AGE
busybox 0/1 CrashLoopBackOff 10 (98s ago) 28m
ngx 1/1 Running 0 14m
ngx2 1/1 Running 0 26s
web-appv1-76765768c6-lxnm7 1/1 Running 0 48m
web-appv1-76765768c6-v6f59 1/1 Terminating 0 89m
roman@cpn02:~$

Наш последний тест завершился успешно. Текущие и новые ресурсы обрабатываются кластером корректно.
Вместо заключения
Публикация получилась довольно объемная. Вероятнее всего — эта самая большая публикация в моем блоге, но я старался документировать практически каждый шаг для того, чтобы коллеги, кто только начинает свой путь в Kubernetes могли разобраться в параметрах развертывания решения. Если же вы с высоты своего опыта работы увидили моменты, где можно оптимизировать параметры какого-то компонента или выполнить более оптимальные настройки, то буду рад вашим комментариям. Думаю, что остальным коллегам по цеху комментарии тоже помог выполнить грамотный деплой всех компонентов отказоустойчивого кластера Kubernetes.