Я в свободное время погружаюсь в 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 crontab
roman@master01:~$
Теперь приступим к выполнению других шагов.
Установка и настройка cri-o
В качестве интерфейса выполнения контейнеров (CRI) я буду использовать cri-o, т.к. начиная с версии Kubernetes 1.24 с docker не все так просто.
Теперь все готово для установки cri-o. Установим необходимые пакеты и подготовим переменные:
sudo apt update
sudo apt install -y apt-transport-https ca-certificates curl gnupg2 software-properties-common
export OS_VERSION=xUbuntu_20.04
export 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.gpg
curl -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.list
echo "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 update
sudo apt install -y cri-o cri-o-runc
Создадим конфигурационные файлы для cri-o:
cat <<EOF | sudo tee /etc/modules-load.d/crio.conf
overlay
br_netfilter
EOF
# Set up required sysctl params, these persist across reboots.
cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF
Загрузим модули для корректной работы сети нашего кластера:
sudo modprobe overlay
sudo modprobe br_netfilter
Перечитаем параметры конфигурации ядра со всех конфигурационных файлов:
sudo sysctl --system
Запустим службы:
sudo systemctl daemon-reload
sudo 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.gpg
sudo apt update -y
sudo apt install -y kubelet kubeadm kubectl
sudo 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/v1beta1
kind: KubeletConfiguration
authentication:
anonymous:
enabled: false
webhook:
enabled: false
authorization:
mode: AlwaysAllow
cgroupDriver: systemd
address: 127.0.0.1
containerRuntimeEndpoint: unix:///var/run/crio/crio.sock
staticPodPath: /etc/kubernetes/manifests
EOF
cat << 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=always
EOF
systemctl daemon-reload
systemctl restart kubelet
Проверим статус сервиса:
systemctl status kubelet
2. Теперь создадим конфигурационный файл для kubeadm. Файл необходимо будет сгенерировать для каждого узла с etcd. Скрипт по генерации конфигурации приведен ниже (только измените IP-адреса и имена узлов). Я сгенерирую все файлы на первом узле (etc01):
# Update HOST0, HOST1 and HOST2 with the IPs of your hosts
export 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 hosts
export 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[@]}"; do
HOST=${HOSTS[$i]}
NAME=${NAMES[$i]}
cat << EOF > /tmp/${HOST}/kubeadmcfg.yaml
---
apiVersion: "kubeadm.k8s.io/v1beta3"
kind: InitConfiguration
nodeRegistration:
name: ${NAME}
localAPIEndpoint:
advertiseAddress: ${HOST}
---
apiVersion: "kubeadm.k8s.io/v1beta3"
kind: ClusterConfiguration
etcd:
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}:2380
EOF
done
Результат работы скрипта:
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.yaml
root@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 host
find /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 -Es
root@HOST $ chown -R root:root pki
root@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=roman
HOST=${HOST2}
scp -r /tmp/${HOST}/* ${USER}@${HOST}:
ssh ${USER}@${HOST}
USER@HOST $ sudo -Es
root@HOST $ chown -R root:root pki
root@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.yaml
root@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.yaml
roman@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 health
root@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 health
https://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 crontab
roman@master01:~$
Теперь приступим к выполнению других шагов.
Установка и настройка cri-o
В качестве интерфейса выполнения контейнеров (CRI) я буду использовать cri-o, т.к. начиная с версии Kubernetes 1.24 с docker не все так просто.
Теперь все готово для установки cri-o. Установим необходимые пакеты и подготовим переменные:
sudo apt update
sudo apt install -y apt-transport-https ca-certificates curl gnupg2 software-properties-common
export OS_VERSION=xUbuntu_20.04
export 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.gpg
curl -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.list
echo "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 update
sudo apt install -y cri-o cri-o-runc
Создадим конфигурационные файлы для cri-o:
cat <<EOF | sudo tee /etc/modules-load.d/crio.conf
overlay
br_netfilter
EOF
# Set up required sysctl params, these persist across reboots.
cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF
Загрузим модули для корректной работы сети нашего кластера:
sudo modprobe overlay
sudo modprobe br_netfilter
Перечитаем параметры конфигурации ядра со всех конфигурационных файлов:
sudo sysctl --system
Запустим службы:
sudo systemctl daemon-reload
sudo 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.gpg
sudo apt update -y
sudo apt install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
Подготовка отказоустойчивого балансировщика
Для того, чтобы балансировщик не был единичной точкой отказа его также нужно подготовить в отказоустойчивой конфигурации. Один из вариантов – это использование haproxy и keepalived. Соответственно, нужно подготовить два балансировщика с haproxy и keepalived. В качестве операционной системы я буду использовать Ubuntu Server 22.04.
Установим необходимые пакеты:
sudo apt update
sudo apt install -y keepalived haproxy
Создадим файл с конфигурацией на первом балансировщике:
/etc/keepalived/keepalived.conf
Содержимое конфигурационного файла:
! /etc/keepalived/keepalived.conf
! Configuration File for keepalived
global_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 keepalived
global_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/sh
errorExit() {
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
Итоговое содержимое моего конфигурационного файла следующее:
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
stats timeout 30s
user haproxy
group haproxy
daemon
# Default SSL material locations
ca-base /etc/ssl/certs
crt-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-tickets
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5000
timeout client 50000
timeout server 50000
option 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 10s
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 408 /etc/haproxy/errors/408.http
errorfile 500 /etc/haproxy/errors/500.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
errorfile 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:6443 check
server kpn02 10.10.10.48:6443 check
server kpn03 10.10.10.50:6443 check
Перезапустим службы и настроем автозапуск:
sudo systemctl enable haproxy --now
sudo systemctl enable keepalived --now
sudo systemctl restart haproxy
sudo systemctl restart keepalived
Последний шаг настройки балансировщиков – проверка доступности виртуального IP-адреса по опубликованному порту. Однако тут есть другой аспект – сам сервер API еще не настроен. Попробуем выполнить вот такую команду с любого сервера плоскости управления:
nc -v 10.10.10.63 6443
Вы должны увидеть следующий вывод:
root@cpn01:/home/roman# nc -v 10.10.10.63 6443
Connection 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/etcd
sudo mv ca.crt /etc/kubernetes/pki/etcd/ca.crt
sudo mv apiserver-etcd-client.crt /etc/kubernetes/pki/apiserver-etcd-client.crt
sudo mv apiserver-etcd-client.key /etc/kubernetes/pki/apiserver-etcd-client.key
3. На первом узле плоскости управления создадим файл манифеста конфигурации для kubeadm:
nano kubeadm-config.yaml
Содержимое файла:
---
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
networking:
podSubnet: "192.168.0.0/16" # --pod-network-cidr
kubernetesVersion: stable
controlPlaneEndpoint: "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/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/
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 13d8f82492aa9c6b9a4fb25eca2484b0a34eec7d95b97a089e62fddebc9826d6
Please 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/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo 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 5m8s
kube-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/etcd
sudo 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 node
roman@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.4
wkn02 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 AGE
calico-kube-controllers-674fff74c8-v5lxz 1/1 Running 0 9h
calico-node-4s27g 1/1 Running 0 9h
calico-node-8dp2h 1/1 Running 0 9h
calico-node-jb7lb 1/1 Running 0 9h
calico-node-lg2lq 1/1 Running 0 9h
calico-node-pl79v 1/1 Running 0 9h
calico-node-z8cb5 1/1 Running 0 9h
coredns-5d78c9869d-fvl64 1/1 Running 0 9h
coredns-5d78c9869d-gvfxc 1/1 Running 0 9h
kube-apiserver-cpn01 1/1 Running 0 9h
kube-apiserver-cpn02 1/1 Running 0 9h
kube-apiserver-cpn03 1/1 Running 0 9h
kube-controller-manager-cpn01 1/1 Running 0 9h
kube-controller-manager-cpn02 1/1 Running 0 9h
kube-controller-manager-cpn03 1/1 Running 0 9h
kube-proxy-f55q8 1/1 Running 0 9h
kube-proxy-hhdsn 1/1 Running 0 9h
kube-proxy-lp584 1/1 Running 0 9h
kube-proxy-p8snc 1/1 Running 0 9h
kube-proxy-snlf7 1/1 Running 0 9h
kube-proxy-z5p2c 1/1 Running 0 9h
kube-scheduler-cpn01 1/1 Running 0 9h
kube-scheduler-cpn02 1/1 Running 0 9h
kube-scheduler-cpn03 1/1 Running 0 9h
metrics-server-754586b847-ktjzz 1/1 Running 0 3m9s
roman@cpn01:~$
Итоговое тестирование отказоустойчивости
Переходим к самой интересной части – тестирование. Предварительно нам нужно будет создать рабочую нагрузку.
Создание рабочей нагрузки для тестирования
Для развертывания тестовой нагрузки я буду использовать следующий манифест из одной моей прошлой публикации:
nano svc.yml
Содержимое файла:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-appv1
spec:
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web-appv1
image: gcr.io/google-samples/hello-app:1.0
---
apiVersion: v1
kind: Service
metadata:
name: web-service-appv1
spec:
selector:
app: web
ports:
- protocol: TCP
port: 50001
targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
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 svc
kubectl 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 10h
web-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 node
kubectl 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 node
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 NotReady 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:~$
Теперь помимо одного из узлов для рабочей нагрузки у нас в кластере “отказал” еще и один из узлов плоскости управления.
Проверим доступность нашего приложения:
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=busybox
roman@cpn01:~$ kubectl run busybox --image=busybox
pod/busybox created
roman@cpn01:~$
Создание дополнительного ресурса прошло успешно. Проверим статусы всех подов:
kubectl get pod
roman@cpn01:~$ kubectl get pod
NAME READY STATUS RESTARTS AGE
busybox 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 list
4f5b17401266f2b5, started, etc01, https://10.10.10.46:2380, https://10.10.10.46:2379, false
a95398094ef8f5e0, started, etc02, https://10.10.10.43:2380, https://10.10.10.43:2379, false
b7fde4d1b69c7cb0, 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:32567
Hello, world!
Version: 1.0.0
Hostname: web-appv1-76765768c6-lxnm7
roman@mintwks:~$
Приложение доступно. Теперь попробуем создать дополнительный ресурс в кластере:
kubectl run ngx --image=nginx
roman@cpn01:~$ kubectl run ngx --image=nginx
pod/ngx created
roman@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 addr
roman@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 forever
roman@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=nginx
pod/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.