Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Дано: домашний сервер под управлением Debian 11 с установленным гипервизором Xen.
Требуется: развернуть Kubernetes-кластер для получения опыта, связанного с настройкой и управлением Kubernetes-кластера, и дальнейшего его использования для разработки и хостинга персональных проектов.
Развёртывание Debian и Xen, а также миграция с Hyper-V на Xen нескольких Windows-виртуальных машин описана в моей статье Миграция домашнего сервера с Hyper-V на Xen Project на Debian. В ней также описана конфигурация моей сети.
В данной статье я исхожу из того, что читатель имеет представление о том, что такое Kubernetes, как он работает, из каких компонентов состоит и знаком с необходимой терминологией. Многие идеи и код я брал из статьи Разворачиваем среду для работы с микросервисами. Часть 1 установка Kubernetes HA на bare metal (Debian), но местами адаптировал под свои нужды и окружение. Задача данной статьи дать читателям готовое решение, требующее минимальных усилий для повторения и, вместе с тем, не требующее дополнительных инструментов (вроде Ansible или Terraform), а также показать новичкам некоторые моменты работы с Linux, Kubernetes и используемыми пакетами. Повествование разбито на несколько шагов, каждый из которых заключается в запуске скрипта и нескольких ручных командах.
Конфигурация кластера
Минимальная конфигурация кластера может состоять из одного-двух узлов: либо один master, которому разрешено хостить пользовательские нагрузки, либо один master и один worker. Однако, подобный кластер не будет, даже близко, похож на типичный промышленный high available (HA) кластер, а значит, полученный опыт будет сильно ограничен как в процессе развёртывания, так и в процессе эксплуатации. Конечно, ни о каком HA-кластере на одной физической машине (как в моём случае) речи быть не может, но, напомню, главная цель — получение опыта и площадки для экспериментов. К слову, если для разработки нужен кластер на рабочей станции, скажем для разработки cloud native приложений, или для изучения простейших команд kubectl
, отличным выбором будет minikube. Я уже его использую для TDD через end-to-end и интеграционные тесты с использованием skaffold, о чём, возможно, расскажу в одной из следующих статей.
Как выглядит минимально-достаточный промышленный HA Kubernetes-кластер?
от двух master-узлов,
от трёх etcd-узлов и load balancer для них,
от двух worker-узлов.
Если мы будем хостить какие-либо web-приложения в кластере, доступ к которым необходимо обеспечить извне, может потребоваться либо внешний load balancer, либо ingress. Однако, это отдельная большая тема, достойная отдельной статьи.
Нам потребуется 5 виртуальных машин. На первых трёх будут развёрнуты master-узлы Kubernetes и etcd-серверы. На двух оставшихся worker-узлы Kubernetes и узлы haproxy-кластера.
Важный момент, почему мастеров 3? Дело в том, что etcd-узлов в кластере должно быть не менее трёх. Если их будет 2, в случае если один из них выйдет из строя или будет отключён, голосование по выбору лидера etcd-кластера не сможет завершиться, и единственный оставшийся etcd-узел будет недоступен, а вместе с ним и функциональность Kubernetes-кластера. Таким образом, узлов в etcd-кластере должно быть не менее 3. etcd-узлы я планирую размещать вместе с master-узлами Kubernetes, чтобы не плодить виртуалки. Поэтому master-узлов должно быть 3.
Worker-узлов может быть любое количество. Я выбрал 2, чтобы можно было симулировать выход одного из них из строя и наблюдать за миграцией подов на оставшийся. Добавить новые worker-узлы в кластер можно в любой момент.
В состав кластера будут входить следующие виртуалки:
Имя узла | IP-адрес узла | Роли узла | Процессор | Объём ОЗУ |
master01 | 10.44.44.11 | etcd, k8s-master | 2vcpu | 4GB |
master02 | 10.44.44.12 | etcd, k8s-master | 2vcpu | 4GB |
master03 | 10.44.44.13 | etcd, k8s-master | 2vcpu | 4GB |
worker01 | 10.44.44.14 | haproxy, k8s-worker | 2vcpu | 4GB |
worker02 | 10.44.44.15 | haproxy, k8s-worker | 2vcpu | 4GB |
Создание PV-доменов с помощью xen-tools
Для начала необходимо создать виртуалки для нашего кластера. Xen поддерживает несколько техник виртуализации. Одна из них называется paravirtualization. Если кратко, эта техника не требует от процессора встроенной поддержки виртуализации. На первый взгляд это может показаться незначительным, ведь большинство современных процессоров поддерживают специальные расширения для виртуализации. На практике же это означает ускорение работы виртуалок, так как не требует перехода между пользовательским контекстом и контекстом ядра ОС. Для Linux-виртуалок нужно пользоваться именно этим видом виртуализации. Каких-либо специальных драйверов, в отличии от Windows HVM виртуалок, ставить не нужно, всё уже есть в ядре Linux.
В Xen PV-домены можно создать вручную или с помощью набора инструментов xen-tools. Это набор скриптов, которые позволяют создать из шаблона виртуалку одного из дистрибутивов Linux (и не только), настроив её с помощью параметров командной строки и «скелета» файловой системы (набор папок и файлов, которые должны быть скопированы в файловую систему виртуалки). Я буду использовать LVM для хранения дисков и Debian 11 в качестве гостевой ОС. Поскольку мне нужно будет создать 5 подобных друг другу виртуалок, я решил немного автоматизировать этот процесс и создал следующий простой скрипт (я назвал его create-debian-pv.sh
):
#!/bin/bash
hostname=$1
ip=10.44.44.$2
mac=00:16:3e:44:44:$2
xen-create-image \
--hostname=$hostname --memory=2gb --vcpus=2 \
--lvm=vg0 --ip=$ip --mac=$mac \
--pygrub --dist=bullseye --noswap --noaccounts \
--noboot --nocopyhosts --extension=.pv \
--fs=ext4 --genpass=0 --passwd --nohosts \
--bridge=xenlan44 --gateway=10.44.44.1 --netmask=255.255.255.0
Скрипт необходимо сделать запускаемым:
chmod 777 create-debian-pv.sh
В первом параметре указывается имя хоста, во втором последняя цифра IP-адреса и MAC-адреса. Для простоты я принял следующее соглашение к формированию MAC-адреса: «00:16:3e» — стандартный префикс MAC-адреса Xen-виртуалок, «44:44» — взято из подсети, последний сегмент одинаков у MAC-адреса и IP-адреса. Мне так проще ориентироваться, но вы можете придумать что-то своё и скорректировать скрипт.
«vg0» — название моей LVM volume group, «bullseye» — название одного из поддерживаемых данной командой дистрибутивов (это Debian 11, полный список можно посмотреть в папке /usr/share/xen-tools/
). Создание раздела swap
запрещено, так как этого требует Kubernetes, и, чтобы не расходовать место на диске, я отключил создание этого раздела полностью. Более подробно о доступных параметрах xen-create-image
можно ознакомиться на его man-странице.
Но перед тем, как использовать данный скрипт, на хосте Xen нужно установить необходимый пакет:
apt install -y xen-tools
Настройка SSH в создаваемых виртуалках
Я предпочитаю сразу настроить SSH в новой виртуалке аналогично тому, как это сделано в ОС гипервизора (dom0). Для этого необходимо создать в папке (папка «скелета» файловой системы xen-tools
, о которой я говорил ранее) /etc/xen-tools/skel/
необходимую структуру папок и скопировать файлы (также на хосте Xen). Я использую авторизацию с помощью пользовательского сертификата с установленным на нём паролем для доступа.
mkdir -p /etc/xen-tools/skel/root/.ssh
cp /root/.ssh/authorized_keys /etc/xen-tools/skel
mkdir -p /etc/xen-tools/skel/etc/ssh
cp /etc/ssh/sshd_config /etc/xen-tools/skel/etc/ssh
Создание виртуалок
Последовательно запускаем создание необходимых виртуалок:
./create-debian-pv.sh master01 11
./create-debian-pv.sh master02 12
./create-debian-pv.sh master03 13
./create-debian-pv.sh worker01 14
./create-debian-pv.sh worker02 15
В процессе своей работы скрипт будет запрашивать пароль для учётной записи root
. Вводим его дважды и вскоре виртуалка создана. Для каждой из них будет создан соответствующий файл конфигурации в папке /etc/xen
. Например для master01 будет создан скрипт /etc/xen/master01.pv
. Позднее, по мере необходимости, можно настроить количество памяти, количество виртуальных ядер процессора и другие параметры виртуальных машин.
В каждом из созданных файлов конфигурации домена необходимо добавить следующую строку: localtime = 1
. Это скажет Xen, что в виртуалке нужно использовать ваш часовой пояс, а не время по Гринвичу.
Файл настроек
Создайте новую папку на машине, имеющей сетевой доступ к виртуалкам, и настроенный SSH-клиент для доступа к хосту Xen. В данной папке будут размещаться скрипты, которые будут запускаться на виртуалках. Далее по тексту я буду называть эту папку рабочей.
Все скрипты будут использовать общие настройки, которые я разместил в отдельном файле variables.sh
. Создайте его в рабочей папке.
#!/bin/bash
# IP-адреса и имена хостов мастер-узлов Kubernetes. Замените на свои.
master01_IP=10.44.44.11
master02_IP=10.44.44.12
master03_IP=10.44.44.13
master01_Hostname=master01
master02_Hostname=master02
master03_Hostname=master03
# IP-адрес и имя хоста, на котором выполняется скрипт.
thisHostname=$(hostname)
thisIP=$(hostname -i)
# etcd-token. Замените на свой. Может быть любой строкой.
etcdToken=my-etcd-cluster-token
# CRI-socket.
containerdEndpoint=unix:///run/containerd/containerd.sock
# Версии пакетов. Замените на текущие актуальные.
etcdVersion=3.5.3
containerdVersion=1.6.2
runcVersion=1.1.1
cniPluginsVersion=1.1.1
kubernetesVersion=1.23.6
calicoVersion=3.22
# Пространство адресов подов. Зависит от CNI-плагина. В данном случае используется Calico.
podSubnet=192.168.0.0/16
# Пространство адресов сервисов. Замените на своё. Может быть любое, но должно быть достаточно большим.
serviceSubnet=10.46.0.0/16
Параметры, которые можно/нужно заменить помечены соответствующим комментарием. Назначение параметров должно быть понятно из их имён.
Развёртывание etcd
etcd используется в качестве основного хранилища информации о состоянии Kubernetes-кластера. Считайте, что это его база данных. Для устойчивости к отказам этого хранилища, как было сказано ранее, необходимо создать etcd-кластер. etcd можно развернуть различными способами, но я выбрал самый простой: развернуть на том же «виртуальном железе», что и master-узлы.
Запускаем виртуалки master-узлов из консоли dom0 Xen:
xl create /etc/xen/master01.pv
xl create /etc/xen/master02.pv
xl create /etc/xen/master03.pv
Создаём скрипт развёртывания etcd-узла install-etcd.sh
в рабочей папке:
#!/bin/bash
source variables.sh
etcdEnvironmentFilePath=/etc/etcd.env
serviceFilePath=/etc/systemd/system/etcd.service
function installAndConfigurePrerequisites {
apt install curl -y
}
function downloadAndInstallEtcd {
curl -L https://github.com/etcd-io/etcd/releases/download/v${etcdVersion}/etcd-v${etcdVersion}-linux-amd64.tar.gz --output etcd-v${etcdVersion}-linux-amd64.tar.gz
tar -xvf etcd-v${etcdVersion}-linux-amd64.tar.gz -C /usr/local/bin/ --strip-components=1
}
function createEnvironmentFile {
cat > ${etcdEnvironmentFilePath} <<EOF
${thisHostname} > ${etcdEnvironmentFilePath}
${thisIP} >> ${etcdEnvironmentFilePath}
EOF
}
function createServiceFile {
cat > $serviceFilePath <<EOF
[Unit]
Description=etcd
Documentation=https://github.com/coreos/etcd
Conflicts=etcd.service
Conflicts=etcd2.service
[Service]
EnvironmentFile=/etc/etcd.env
Type=notify
Restart=always
RestartSec=5s
LimitNOFILE=40000
TimeoutStartSec=0
ExecStart=/usr/local/bin/etcd \\
--name ${thisHostname} \\
--data-dir /var/lib/etcd \\
--listen-peer-urls http://${thisIP}:2380 \\
--listen-client-urls http://0.0.0.0:2379 \\
--advertise-client-urls http://${thisIP}:2379 \\
--initial-cluster-token ${etcdToken} \\
--initial-advertise-peer-urls http://${thisIP}:2380 \\
--initial-cluster ${master01_Hostname}=http://${master01_IP}:2380,${master02_Hostname}=http://${master02_IP}:2380,${master03_Hostname}=http://${master03_IP}:2380 \\
--initial-cluster-state new
[Install]
WantedBy=multi-user.target
EOF
}
function removeDownloads {
rm -f etcd-v${etcdVersion}-linux-amd64.tar.gz
}
installAndConfigurePrerequisites
downloadAndInstallEtcd
createEnvironmentFile
createServiceFile
removeDownloads
systemctl enable --now etcd
И копируем все скрипты из рабочей папки в папку /root
каждой виртуалки master-узлов Kubernetes (здесь и далее показано только для одной виртуалки):
scp *.sh root@10.44.44.11:/root
SSH-клиент запросит пароль (если вы его установили) доступа к приватному SSH-ключу и скопирует скрипты.
Последовательно подключаемся по SSH к каждому master-узлу и запускаем скрипт из папки /root
:
./install-etcd.sh
Стоит отметить, что пока не отработает скрипт на втором узле, запущенный скрипт на первом узле не завершит своё выполнение. Это связано с тем, что etcd не может завершить голосование по выбору лидера пока не будет хотя бы двух узлов в кластере.
После того как скрипт на всех трёх master-узлах будет запущен, статус etcd-узлов должен стать active (running)
:
root@master01:~# systemctl status etcd
* etcd.service - etcd
Loaded: loaded (/etc/systemd/system/etcd.service; disabled; vendor preset:
enabled)
Active: active (running) since Sat 2022-04-23 03:51:23 UTC; 14s ago
Docs: https://github.com/coreos/etcd
Main PID: 3129 (etcd)
Tasks: 7 (limit: 2250)
Memory: 11.2M
CPU: 250ms
CGroup: /system.slice/etcd.service
`-3129 /usr/local/bin/etcd --name master01 --data-dir /var/lib/etcd
…а статус всего etcd-кластера должен выглядеть примерно так:
root@master01:~# etcdctl --write-out=table --endpoints=10.44.44.11:2379,10.44.44.12:2379,10.44.44.13:2379 endpoint status
+------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| ENDPOINT | ID | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS |
+------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| 10.44.44.11:2379 | 5f81cb6fe79d652f | 3.5.3 | 20 kB | true | false | 2 | 9 | 9 | |
| 10.44.44.12:2379 | 836005ecbe98da1a | 3.5.3 | 20 kB | false | false | 2 | 9 | 9 | |
| 10.44.44.13:2379 | ae73e256fd5aa97c | 3.5.3 | 20 kB | false | false | 2 | 9 | 9 | |
+------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
Для более подробного ознакомления можно почитать в документации kubernetes.
Установка containerd и runc
После того, как Kubernetes перестал поддерживать Dockershim (да и не нужен Docker, по большому счёту, в Kubernetes-кластере), осталось два довольно равнозначных варианта среды исполнения: containerd и CRI-O. Я выбрал первый. Поставить его можно двумя способами (компиляция из исходников не в счёт): воспользоваться репозиторием пакетов ОС или скачать релиз с GitHub. На момент написания статьи версия в репозитории Debian — 1.4.13, версия релиза в GitHub — 1.6.2. Разница существенная, поэтому я выбрал вариант с GitHub.
Для того, чтобы понять, что такое среда выполнения, для чего она нужна, а также осознать, что Docker — не наше всё, рекомендую почитать статью Различия между Docker, containerd, CRI-O и runc.
containerd нужно установить на всех узлах кластера, поэтому запускаем и виртуалки worker-узлов:
xl create /etc/xen/worker01.pv
xl create /etc/xen/worker02.pv
Для установки containerd в рабочей папке создаём скрипт install-containerd.sh
следующего содержания:
#!/bin/bash
source variables.sh
function installAndConfigurePrerequisites {
apt install curl -y
cat <<EOF | tee /etc/modules-load.d/containerd.conf
overlay
br_netfilter
EOF
modprobe overlay
modprobe br_netfilter
# Настройка обязательных параметров sysctl.
cat <<EOF | 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
# Применяем изменения без перезагрузки.
sysctl --system
}
function downloadAndInstallContainerd {
# Загружаем релиз containerd.
curl -L https://github.com/containerd/containerd/releases/download/v${containerdVersion}/containerd-${containerdVersion}-linux-amd64.tar.gz --output containerd-${containerdVersion}-linux-amd64.tar.gz
tar -xvf containerd-${containerdVersion}-linux-amd64.tar.gz -C /usr/local
# Создаём файл конфигурации containerd.
mkdir /etc/containerd/
containerd config default > /etc/containerd/config.toml
# Разрешаем использование systemd cgroup.
sed -i "s/SystemdCgroup = false/SystemdCgroup = true/g" /etc/containerd/config.toml
# Создаём файл сервиса containerd.
cat > /etc/systemd/system/containerd.service <<EOF
[Unit]
Description=containerd container runtime
Documentation=https://containerd.io
After=network.target local-fs.target
[Service]
ExecStartPre=-/sbin/modprobe overlay
ExecStart=/usr/local/bin/containerd
Type=notify
Delegate=yes
KillMode=process
Restart=always
RestartSec=5
LimitNPROC=infinity
LimitCORE=infinity
LimitNOFILE=infinity
TasksMax=infinity
OOMScoreAdjust=-999
[Install]
WantedBy=multi-user.target
EOF
# Разрешаем и запускаем сервис containerd.
systemctl daemon-reload
systemctl enable --now containerd
}
function downloadAndInstallRunc {
# Загружаем и устанавливаем низкоуровневую службу запуска контейнеров.
curl -L https://github.com/opencontainers/runc/releases/download/v${runcVersion}/runc.amd64 --output runc.amd64
install -m 755 runc.amd64 /usr/local/sbin/runc
}
function downloadAndInstallCniPlugins {
curl -L https://github.com/containernetworking/plugins/releases/download/v${cniPluginsVersion}/cni-plugins-linux-amd64-v${cniPluginsVersion}.tgz --output cni-plugins-linux-amd64-v${cniPluginsVersion}.tgz
mkdir -p /opt/cni/bin
tar Cxzvf /opt/cni/bin cni-plugins-linux-amd64-v${cniPluginsVersion}.tgz
}
function removeDownloads {
rm -f containerd-${containerdVersion}-linux-amd64.tar.gz
rm -f runc.amd64
rm -f cni-plugins-linux-amd64-v${cniPluginsVersion}.tgz
}
installAndConfigurePrerequisites
downloadAndInstallContainerd
downloadAndInstallRunc
downloadAndInstallCniPlugins
removeDownloads
systemctl restart containerd
Снова копируем скрипты на все master- и worker-узлы:
scp *.sh root@10.44.44.11:/root
…и запускаем его на каждом узле:
./install-containerd.sh
После его установки проверяем, что сервис работает (должно быть Active: active (running)
):
root@master01:~# systemctl status conatinerd
* containerd.service - containerd container runtime
Loaded: loaded (/etc/systemd/system/containerd.service; enabled; vendor pre
set: enabled)
Active: active (running) since Sat 2022-04-23 20:59:47 UTC; 20s ago
Docs: https://containerd.io
Process: 3526 ExecStartPre=/sbin/modprobe overlay (code=exited, status=0/SUC
CESS)
Main PID: 3528 (containerd)
Tasks: 9
Memory: 20.1M
CPU: 121ms
CGroup: /system.slice/containerd.service
`-3528 /usr/local/bin/containerd
Развёртывание первого master-узла Kubernetes
В рабочей папке создаём скрипт install-master-node.sh
:
#!/bin/bash
source variables.sh
function installAndConfigurePrerequisites {
apt install -y apt-transport-https curl gnupg2 apparmor apparmor-utils
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF
# Установить необходимые пакеты и зафиксировать их версию.
apt update
apt install -y kubelet=$kubernetesVersion-00
apt install -y kubeadm=$kubernetesVersion-00
apt install -y kubectl=$kubernetesVersion-00
apt-mark hold kubelet kubeadm kubectl
}
function createKubeadmConfig {
cat > kubeadm-init.yaml <<EOF
---
apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: "${thisIP}"
nodeRegistration:
criSocket: "${containerdEndpoint}"
---
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: v${kubernetesVersion}
apiServer:
certSANs:
- ${master01_IP}
- ${master02_IP}
- ${master03_IP}
- 127.0.0.1
controlPlaneEndpoint: ${thisIP}
etcd:
external:
endpoints:
- http://${master01_IP}:2379
- http://${master02_IP}:2379
- http://${master03_IP}:2379
networking:
podSubnet: "${podSubnet}"
serviceSubnet: "${serviceSubnet}"
dnsDomain: "cluster.local"
EOF
}
function initializeMasterNode {
kubeadm init --config=kubeadm-init.yaml
}
function installCalicoCNI {
export KUBECONFIG=/etc/kubernetes/admin.conf
kubectl apply -f https://docs.projectcalico.org/v${calicoVersion}/manifests/calico.yaml
}
function archiveCertificates {
tar -zcvf certificates.tar.gz -C /etc/kubernetes/pki .
}
function extractCertificates {
mkdir -p /etc/kubernetes/pki
tar -xvf certificates.tar.gz -C /etc/kubernetes/pki
}
installAndConfigurePrerequisites
createKubeadmConfig
if [[ $thisIP == $master01_IP ]]; then
# На первом мастер-узле устанавливаем CNI-плагин и архивируем сертификаты
# для последующего использования на других мастер-узлах.
initializeMasterNode
installCalicoCNI
archiveCertificates
fi
if [[ $thisIP != $master01_IP ]]; then
# На не первом мастер-узле используем сертификаты, полученные с первого мастер-узла.
extractCertificates
initializeMasterNode
fi
Этот скрипт будет использоваться также и для остальных master-узлов, поэтому копируем его также и на них.
Запускаем скрипт на первом master-узле master01
:
./install-master-node.sh
После успешного запуска в папке /root
появится архив с сертификатами кластера certificates.tar.gz
, который необходимо скопировать в папки /root
остальных master-узлов.
В результате успешного выполнения вы должны получить примерно такой вывод:
Then you can join any number of worker nodes by running the following on each as root:
kubeadm join 10.44.44.11:6443 --token 635j2y.7ar0dbhq7l77jne0 \
--discovery-token-ca-cert-hash sha256:8fab070cc51d4449d8fa0fc86348aeccc0554ca5e473da3a5138c6fc5a3f5e77
Необходимо сохранить данный текс, так как далее нам понадобятся два параметра из него.
После установки проверяем, что master-узел работает:
root@master01:~# kubectl get nodes
NAME STATUS ROLES AGE VERSION
master01 Ready control-plane,master 32s v1.23.6
Обеспечение доступа kubectl к кластеру
kubectl используется в качестве основного инструмента администрирования Kubernetes-кластера. Мы его установим на всех узлах кластера, однако, если попробовать его использовать, мы получим ошибку с достаточно непонятным текстом сообщения.
The connection to the server localhost:8080 was refused - did you specify the right host or port?
Для того, чтобы kubectl работал необходимо сказать ему, где искать конфигурацию для доступа к кластеру. Проще всего это сделать с помощью переменной окружения KUBECONFIG
, указывающей на файл конфигурации. В зависимости от разных условий, файл этот может располагаться в разных местах.
На master-узлах при работе из-под учётки root:
/etc/kubernetes/admin.conf
.На worker-узлах при работе из-под учётки root:
/etc/kubernetes/kubelet.conf
.На любых узлах при работе из-под другой учётки:
$HOME/.kube/config
.
В последнем случае этот файл необходимо создать или скопировать с master-узлов. Устройство этого файла — тема достаточно обширная, поэтому детально описывать её здесь я не буду. Ознакомиться можно здесь: Organizing Cluster Access Using kubeconfig Files.
После того, как файл найден/скопирован/создан в файле .bashrc
(или аналогичном, вашей командной оболочки) добавляем следующую строку (показано для случая master-узел из-под root):
export KUBECONFIG=/etc/kubernetes/admin.conf
И перезагружаем оболочку (показано для bash):
exec bash
Рекомендую установить kubectl на вашей рабочей машине. Дистрибутивы есть для всех основных ОС, включая Windows. Хотя на Windows я всё же предпочитаю работать не из PowerShell, а из bash в WSL-виртуалке.
Развёртывание остальных master-узлов Kubernetes
Для развёртывания оставшихся master-узлов используется тот же скрипт install-master-node.sh
. Помимо скриптов, как было сказано ранее, необходимо скопировать в папку скриптов на целевые машины архив с сертификатами. Без этого архива скрипт не отработает. Проще всего распространить данный архив на все master-узлы можно, скопировав его вначале в рабочую папку с master01, а затем из рабочей папки на master02 и master03.
После установки посредством скрипта install-master-node.sh
проверяем, что все master-узлы работают:
~ kubectl get nodes
NAME STATUS ROLES AGE VERSION
master01 Ready control-plane,master 1h5s v1.23.6
master02 Ready control-plane,master 13m v1.23.6
master03 Ready control-plane,master 14s v1.23.6
Развёртывание worker-узлов Kubernetes-кластера
Worker-узлы развёртываем с помощью следующего скрипта install-worker-node.sh
, который необходимо создать в рабочей папке:
#!/bin/bash
source variables.sh
function installAndConfigurePrerequisites {
apt install -y apt-transport-https curl gnupg2 apparmor apparmor-utils
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF
apt update
apt install -y kubelet=$kubernetesVersion-00
apt install -y kubeadm=$kubernetesVersion-00
apt install -y kubectl=$kubernetesVersion-00
apt-mark hold kubelet kubeadm kubectl
}
function joinMasterToCluster {
read -p "Enter token: " token
read -p "Enter SHA256 without 'sha256:' prefix: " sha
kubeadm join 10.44.44.11:6443 --token $token --discovery-token-ca-cert-hash sha256:$sha
}
installAndConfigurePrerequisites
joinMasterToCluster
Как и в предыдущих шагах копируем все скрипты на worker-узлы:
scp *.sh root@10.44.44.14:/root
Подключаемся по SSH к узлу и запускаем скрипт:
./install-worker-node.sh
В ходе его выполнения будут заданы два вопроса про токен и SHA его сертификата. Их значения нужно взять из вывода, полученного при развёртывании первого master-узла. В моём случае это были: 635j2y.7ar0dbhq7l77jne0
и 8fab070cc51d4449d8fa0fc86348aeccc0554ca5e473da3a5138c6fc5a3f5e77
соответственно.
Время жизни токена ограничено 24 часами. Поэтому если между развёртыванием первого master-узла и worker-узла пройдёт более 24 часов, вам необходимо повторно сгенерировать этот токен. Для этого, подключившись к кластеру, необходимо выполнить следующую команду и использовать полученный токен и его хэш-сумму для скрипта install-worker-node.sh
.
kubeadm token create --print-join-command
Развёртывание узлов haproxy-кластера
Последним штрихом будет создание haproxy-кластера для доступа worker-узлов к master-узлам. Зачем это нужно? Дело в том, что в конфигурации worker-узла жёстко прописывается адрес одного из master-узлов. В этом можно убедиться, найдя в файле /etc/kubernetes/kubelet.conf
на worker-узле параметр server:
. Если этот master-узел «приляжет», worker-узел «отвалится» от кластера. Чтобы этого не происходило, и кластер не терял свои worker-узлы, необходимо обеспечить защиту от подобных проблем. Одно из возможных решений реализовать доступ к control-plane через HA-proxy.
Создаём в рабочей папке скрипт install-haproxy.sh
со следующим содержимым:
#!/bin/bash
source variables.sh
function installAndConfigurePrerequisites {
apt install -y apt-transport-https curl gnupg2 apparmor apparmor-utils
}
function downloadAndInstallHaproxy {
curl https://haproxy.debian.net/bernat.debian.org.gpg \
| gpg --dearmor > /usr/share/keyrings/haproxy.debian.net.gpg
echo deb "[signed-by=/usr/share/keyrings/haproxy.debian.net.gpg]" \
http://haproxy.debian.net bullseye-backports-2.5 main \
> /etc/apt/sources.list.d/haproxy.list
apt update
apt install -y haproxy=2.5.\*
}
function configureHaproxy {
cat > /etc/haproxy/haproxy.cfg <<EOF
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin
stats timeout 30s
user haproxy
group haproxy
daemon
# Default SSL material locations
ca-base /etc/ssl/certs
crt-base /etc/ssl/private
# Default ciphers to use on SSL-enabled listening sockets.
# For more information, see ciphers(1SSL). This list is from:
# https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
# An alternative list with additional directives can be obtained from
# https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=haproxy
ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS
ssl-default-bind-options no-sslv3
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5000
timeout client 50000
timeout server 50000
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
frontend k8s-api
bind ${thisIP}:6443
bind 127.0.0.1:6443
mode tcp
option tcplog
default_backend k8s-api
backend k8s-api
mode tcp
option tcplog
option tcp-check
balance roundrobin
default-server port 6443 inter 10s downinter 5s rise 2 fall 2 slowstart 60s maxconn 250 maxqueue 256 weight 100
server apiserver1 ${master01_IP}:6443 check
server apiserver2 ${master02_IP}:6443 check
server apiserver3 ${master03_IP}:6443 check
EOF
systemctl restart haproxy
}
function connectNodeToLocalHaproxy {
# Заменить адрес мастер узла на локальный адрес узла Haproxy.
sed -i --regexp-extended "s/(server: https:\/\/)[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}/\1127.0.0.1/g" /etc/kubernetes/kubelet.conf
systemctl restart kubelet
}
installAndConfigurePrerequisites
downloadAndInstallHaproxy
configureHaproxy
connectNodeToLocalHaproxy
И, как обычно, копируем все скрипты из рабочей папки на worker-узлы:
scp *.sh root@10.44.44.14:/root
Запускаем скрипт на worker-узлах:
./install-haproxy.sh
После этого проверяем, что кластер работает и доступен:
~ kubectl get nodes
NAME STATUS ROLES AGE VERSION
master01 Ready control-plane,master 1d v1.23.6
master02 Ready control-plane,master 1d v1.23.6
master03 Ready control-plane,master 1d v1.23.6
worker01 Ready <none> 1d v1.23.6
worker02 Ready <none> 1d v1.23.6
Что ещё осталось сделать
Кластер работает, но, в моём случае, пока не готов к использованию. Необходимо реализовать ещё, как минимум, две задачи:
обеспечить доступ извне к web-приложениям с помощью ingress и настройки роутера,
обеспечить хранение данных на дисках.
Однако, это выходит за рамки данной статьи. Я планирую осветить реализацию этих задач, а также развёртывание одного web-приложения в кластере в будущих статьях.
Благодарю за внимание. Надеюсь моя статья поможет вам разобраться с развёртыванием собственного кластера.