Установка Kubernetes в отказоустойчивой конфигурации

Я в свободное время погружаюсь в 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.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *