> For the complete documentation index, see [llms.txt](https://utm-1.gitbook.io/utm-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://utm-1.gitbook.io/utm-docs/dokumentaciya/utm-it/resheniya/kubernetes/razvertyvanie-ha-klastera/gaid-dlya-novichkov-po-ustanovke-kubernetes-versiya-03.2025.md).

# Гайд для новичков по установке Kubernetes (версия 03.2025)

{% embed url="<https://habr.com/ru/articles/725640/>" %}

## Гайд для новичков по установке Kubernetes (версия 03.2025)

Средний33 мин122K[DevOps\*](https://habr.com/ru/hubs/devops/)[IT-инфраструктура\*](https://habr.com/ru/hubs/it-infrastructure/)[Kubernetes\*](https://habr.com/ru/hubs/kubernetes/)[Виртуализация\*](https://habr.com/ru/hubs/virtualization/)[Системное администрирование\*](https://habr.com/ru/hubs/sys_admin/)Туториал![](https://habrastorage.org/r/w1560/webt/pf/nj/de/pfnjdeifoiye7rbu8ogfq2lswvi.jpeg)\
\&#xNAN;*© кадр из к/ф «Пираты Карибского моря»*\
Сведения об обновлении1. Гайд обновлен на март 2025 г.\
2\. Предыдущею версию гайда можно скачать [здесь](https://imbasoft.ru/viewtopic.php?t=1928).\
\
С чего начинается практическое освоение любой системы? Правильно, с установки. Данный гайд является компиляцией из народной мудрости, официальной документации, а также собственного опыта и призван помочь новичкам разобраться с тем, как же все таки устанавливать [Kubernetes](https://kubernetes.io/).\
\
Мы потренируемся ставить как вырожденный кластер «все-в-одном», состоящий только из одного узла, так и настоящий высокодоступный (high available) кластер с полным резервированием. В процессе работы мы рассмотрим применение различных [контейнерных движков](https://kubernetes.io/docs/setup/production-environment/container-runtimes/) (Container Runtimes): [cri-o](https://cri-o.io/), [containerd](https://containerd.io/), связки [Docker](https://www.docker.com/) + [cri-dockerd plugin](https://github.com/Mirantis/cri-dockerd). Кроме этого, потренируемся настраивать отказоустойчивый балансировщик нагрузки на базе [keepalived](https://keepalived.org/) и [haproxy](https://www.haproxy.org/).\
\
Весь процесс установки будет детальным образом прокомментирован и разложен по шагам, а в реперных точках мы будем делать снимки состояния виртуальных машин (snapshots), что позволит рассмотреть различные варианты установки без необходимости делать одну и ту же работу по несколько раз.\
\
Оглавление[1. Зачем все это надо?](https://habr.com/ru/articles/725640/#p1)\
[2. Как устроен Kubernetes](https://habr.com/ru/articles/725640/#p2)\
[3. Способы установки Kubernetes](https://habr.com/ru/articles/725640/#p3)\
[4. Схема виртуального стенда](https://habr.com/ru/articles/725640/#p4)\
[5. Постановка задач](https://habr.com/ru/articles/725640/#p5)\
[6. Решение](https://habr.com/ru/articles/725640/#p6)\
[6.1. Предварительная настройка узлов кластера](https://habr.com/ru/articles/725640/#p61)\
[6.1.1. Настройка статических IP адресов узлов кластера](https://habr.com/ru/articles/725640/#p611)\
[6.1.2. Настройка имен узлов кластера](https://habr.com/ru/articles/725640/#p612)\
[6.1.3. Настройка DNS](https://habr.com/ru/articles/725640/#p613)\
[6.1.4. Настройка файла hosts](https://habr.com/ru/articles/725640/#p614)\
[6.1.5. Проверка сетевых настроек](https://habr.com/ru/articles/725640/#p615)\
[6.1.6. Установка вспомогательных пакетов](https://habr.com/ru/articles/725640/#p616)\
[6.1.7. Предварительная подготовка Linux для использования Kubernetes](https://habr.com/ru/articles/725640/#p617)\
[6.1.8. \[ОПЦИОНАЛЬНО\] Разрешение авторизации в SSH от пользователя root](https://habr.com/ru/articles/725640/#p618)\
[Лайфхак. Использование TMUX для одновременного конфигурирования нескольких узлов](https://habr.com/ru/articles/725640/#plh)\
[6.1.9. Установка kubeadm, kubectl и kubelet](https://habr.com/ru/articles/725640/#p619)\
[6.2. Установка контейнерного движка](https://habr.com/ru/articles/725640/#p62)\
[6.2.А. Вариант A. Установка cri-o](https://habr.com/ru/articles/725640/#p62a)\
[6.2.B. Вариант B. Установка containerd](https://habr.com/ru/articles/725640/#p62b)\
[6.2.C. Вариант C. Установка Docker + cri-dockerd](https://habr.com/ru/articles/725640/#p62c)\
[6.3. Развёртывание Kubernetes](https://habr.com/ru/articles/725640/#p63)\
[6.3.A. Вариант A. Установка вырожденного Kubernete кластера](https://habr.com/ru/articles/725640/#p63a)\
[6.3.A.1. Инициализация кластера Kubernetes](https://habr.com/ru/articles/725640/#p63a1)\
[6.3.A.2. Конфигурирование утилиты управления kubectl](https://habr.com/ru/articles/725640/#p63a2)\
[6.3.A.3. Установка сетевого плагина](https://habr.com/ru/articles/725640/#p63a3)\
[6.3.A.4. Настройка управляющего узла для выполнения рабочих нагрузок](https://habr.com/ru/articles/725640/#p63a4)\
[6.3.B. Вариант B. Организация отказоустойчивого кластера Kubernetes](https://habr.com/ru/articles/725640/#p63b)\
[6.3.B.1. Объяснение, за счет чего достигается отказоустойчивость](https://habr.com/ru/articles/725640/#p63b1)\
[6.3.B.2. Настройка балансировщика нагрузки](https://habr.com/ru/articles/725640/#p63b2)\
[6.3.B.2.1. Настройка демона keepalived](https://habr.com/ru/articles/725640/#p63b21)\
[6.3.B.2.2. Настройка демона haproxy](https://habr.com/ru/articles/725640/#p63b22)\
[6.3.B.3. Установка управляющих узлов кластера](https://habr.com/ru/articles/725640/#p63b3)\
[6.3.B.3.1. Установка первого управляющего узла](https://habr.com/ru/articles/725640/#p63b31)\
[6.3.B.3.2. Установка последующих управляющих узлов](https://habr.com/ru/articles/725640/#p63b32)\
[6.3.B.4. Установка рабочих узлов кластера](https://habr.com/ru/articles/725640/#p63b4)\
[6.3.B.5. Настройка kubeсtl](https://habr.com/ru/articles/725640/#p63b5)\
[6.3.B.6. Установка сетевого плагина](https://habr.com/ru/articles/725640/#p63b6)\
[7. Проверка работы кластера Kubernetes](https://habr.com/ru/articles/725640/#p7)\
[8. Тестовые запуски "*подов*" в Kubernetes](https://habr.com/ru/articles/725640/#p8)\
[8.1. Тест 1. Запуск "*пода*" в интерактивном режиме](https://habr.com/ru/articles/725640/#p81)\
[8.2. Тест 2. Запуск NGINX](https://habr.com/ru/articles/725640/#p82)\
[9. Диагностика балансировщика нагрузки](https://habr.com/ru/articles/725640/#p9)\
[X. Заключение](https://habr.com/ru/articles/725640/#px)\
\ <br>

### 1. Зачем все это надо?

\
Одним из золотых правил построения надежной и безопасной информационной инфраструктуры является максимальная изоляция ее компонентов друг от друга. Хорошим признаком достижения этой цели можно считать кейс, когда один сервер выполняет только одну основную функцию. Например, контроллер Active Directory – это только контроллер Active Directory, а не файловый или Интернет-прокси сервер в придачу.\
\
Изначально инфраструктура разделялась с помощью выделенных аппаратных серверов, но это было очень дорогое удовольствие. Потом появилась технология виртуализации. Затраты на изоляцию существенно снизились, но все же оставались довольно высоки: виртуальные машины потребляли значительно количество вычислительных ресурсов, медленно запускались, им требовались отдельные лицензии на системное и прикладное ПО.\
\
Следующим этапом развития стала контейнеризация. Если виртуальная машина — это почти отдельный компьютер со своим BIOS, операционной системой, драйверами и так далее, то контейнер — это изолированная часть операционной системы узла с минимумом прикладного ПО, обеспечивающего его запуск. В результате этого контейнеры занимают мало места, быстро стартуют и существенно минимизируют другие сопутствующие расходы.\
\
Контейнеры идеологически очень похожи на портативный (portable) софт. Они содержат в себе лишь те файлы, что нужны для запуска конкретной программы. Однако, в отличии от обычных портативных программ, технологии контейнеризации отделяют контейнеры друг от друга и от хозяйской (host) операционной системы, позволяя каждому контейнеру считать себя отдельным компьютером, что очень похоже на работу виртуальных машин.\
\
С точки зрения безопасности изоляция контейнеров хуже, нежели изоляция виртуальных машин. Тем не менее, достигаемого уровня безопасности достаточно для большинства сценариев применения.\
\
Контейнеризация получила широкую известность вместе с Docker. Эта система сделала работу с контейнерами чрезвычайно простой и доступной. Она хорошо подходит для управления контейнерами в небольших проектах, но для серьезных задач, когда нужно оперировать большим числом контейнеров, организовывать отказоустойчивые конфигурации, гибко управлять вычислительными ресурсами, ее возможностей недостаточно, и здесь на сцену выходит герой нашей статьи – система управления/оркестрации (orchestration) контейнерами Kubernetes.\ <br>

### 2. Как устроен Kubernetes

\
По факту Kubernetes — очень гибкое решение, которое может быть настроено бесчисленным количеством способов. Это, конечно, очень здорово для работы, но является сущим адом при изучении. Поэтому здесь мы не будем рассматривать все возможные варианты построения системы, а ограничимся лишь базовым, достаточным для её первичного освоения.\
\
Kubernetes кластер состоит из двух типов узлов: управляющих и рабочих.\
\
Управляющие узлы, как видно из названия, предназначены для управления кластером. Они отдают команды рабочим узлам на запуск и остановку рабочих нагрузок (workloads), отслеживают состояние кластера, перераспределяют задачи в случае отказов и совершают множество других управленческих действий. Рабочие узлы — это пчелки, выполняющие всю полезную работу, ради которой функционирует кластер.\
\
На самом деле управляющие узлы тоже могут выполнять рабочие нагрузки, правда с точки зрения безопасности это считается нежелательным. Поскольку, если в одной из рабочих нагрузок будет вредоносный код, то, будучи запущенным на управляющем узле, он сможет натворить гораздо больше бед, нежели будучи запущенным на рабочем узле.\
\
Типовой состав ПО рабочего узла включает в себя (*Рисунок 1*):<br>

1. Служебные компоненты Kubernetes: агент управления узлом [kubelet](https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/), узловой прокси [kube-proxy](https://kubernetes.io/docs/reference/command-line-tools-reference/kube-proxy/)
2. [Сетевой плагин (Container Network Interface, CNI plugin)](https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/).
3. Контейнерный движок: cri-o, containerd или Docker + cri-dockerd plugin.
4. Рабочие нагрузки (workloads), то есть сами контейнеры, из-за которых все и затевалось. Однако, здесь важно уточнить один существенный момент — минимальной единицей управления рабочей нагрузкой в Kuberbetes является ["*под*" (pod)](https://kubernetes.io/docs/concepts/workloads/pods/), состоящий из одного (как правило) или нескольких контейнеров.

\
![](https://habrastorage.org/r/w1560/webt/o8/uv/br/o8uvbrudquu8ifim7_cl4tybr08.jpeg)\
\&#xNAN;*Рисунок 1*\
\
Состав ПО управляющих узлов (*Рисунок 2*) дополнительно включает в себя:<br>

1. Управляющие компоненты Kubernetes: планировщик [kube-scheduler](https://kubernetes.io/docs/reference/command-line-tools-reference/kube-scheduler/), базовый демон управления [kube-controller-manager](https://kubernetes.io/docs/reference/command-line-tools-reference/kube-controller-manager/), REST API сервер управления [kube-apiserver](https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/).
2. Отказоустойчивое хранилище [etcd](https://etcd.io/).
3. Балансировщик нагрузки (для случаев использования нескольких управляющих узлов в кластере).

\
![](https://habrastorage.org/r/w1560/webt/yd/xp/be/ydxpbehkmx4bfaay6bwsk2t4uf0.jpeg)\
\&#xNAN;*Рисунок 2*\
\
Важно отметить, что практически все компоненты, кроме kubelet и kube-proxy, могут функционировать в Kubernetes в качестве рабочих нагрузок. Другими словами, Kubernetes может управлять сам собой.\
\
Читая о Kubernetes, часто можно услышать фразу: «Kubernetes – это просто: всего 5 бинарников». Под пятью бинарниками обычно понимают: kubelet, kubeproxy, kube-scheduler, kube-controller-manager, kube-apiserver. При этом почему-то всегда умалчивается о других обязательных компонентах кластера, хотя бы о том же etcd, так что Kubernetes — это далеко не просто и далеко не пять бинарников.\ <br>

### 3. Способы установки Kubernetes

\
Kubernetes в учебных целях может быть реализован с помощью различных утилит и готовых дистрибутивов:<br>

* [kind (Kubernetes in Docker)](https://kind.sigs.k8s.io/). Кластер, функционирующий на локальном компьютере «внутри» Docker.
* [minikube](https://minikube.sigs.k8s.io/docs/). Кластер в одной утилите для запуска на локальном компьютере.
* [Docker desktop](https://www.docker.com/products/docker-desktop/) – дистрибутив Docker для запуска на локальном компьютере с возможностью включить Kubernetes одной галкой – наверное, самый дружественный вариант для людей, которые не представляют, что такое Kubernetes, и хотят просто на него глянуть.

\
Для промышленного использования Kubernetes должен быть развернут «по-честному». Для этого существуют следующие варианты:<br>

* Развертывание с помощью [kubespay](https://kubernetes.io/docs/setup/production-environment/tools/kubespray/) – набора скриптов (playbook’s) для системы управления инфраструктурой [Ansible](https://www.ansible.com/).
* Полностью ручное развёртывание «hard way». Гайд на английском можно почитать [тут](https://github.com/kelseyhightower/kubernetes-the-hard-way), на русском [тут](https://habr.com/ru/post/699074/).
* Развертывание с помощью утилиты [*kubeadm*](https://kubernetes.io/docs/reference/setup-tools/kubeadm/). Это то, чем мы будем заниматься далее.

\ <br>

### 4. Схема виртуального стенда

\
Согласно официальной [документации](https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/), к машинам, на которых разворачивается Kubernetes, выдвигаются следующие требования:<br>

* 2+ GB ОЗУ;
* 2+ процессорных ядра;
* Linux хост с отключенным файлом подкачки (swap);

\
При разворачивании кластера на нескольких узлах, согласно тем же документам, накладываются дополнительные требования:<br>

* полная сетевая связанность узлов;
* на каждом узле должны быть уникальные:<br>
  * имена узлов (проверка с помощью команды "*hostname*"),
  * MAC-адреса (проверка с помощью команды "*ip link*"),
  * параметр *product\_uuid*, являющийся уникальным идентификатором виртуальной машины (проверка с помощью команды "*cat /sys/class/dmi/id/product\_uui*").

\
В ходе экспериментов выяснились, что дополнительно к этому узлы должны иметь статические IP-адреса и зарегистрированные DNS имена, что требуется для автоматического выпуска сертификатов во время работы *kubeadm*.\
\
Минимальное количество рабочих узлов для схемы с резервированием – 2. Логичное требование: один сломался другой на замену. Минимальное количество управляющих узлов для схемы с резервирование – 3. Данное странное требование продиктовано официальной [документацией](https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/high-availability/#before-you-begin): в Kubernetes должно быть нечетное количество управляющих улов. Минимальное число нечетное число для обеспечения избыточности — 3.\
\
Таким образом, наш виртуальный стенд (*Рисунок 3*) будет состоять из пяти виртуальных машин, находящихся в одноранговой сети, имеющей выход в Интернет, с помощью виртуального маршрутизатора, реализующего NAT.\
\
![](https://habrastorage.org/r/w1560/webt/qx/ep/f0/qxepf0_0aptnj7ccneox4nsm6te.jpeg)\
\&#xNAN;*Рисунок 3*\
\
Виртуальные машины будут работать под управлением ОС [Debian](https://www.debian.org/) 12 x64, установленной с минимальным количеством пакетов. Все необходимое будем явно доставлять.\ <br>

### 5. Постановка задач

\
Задача 1. Организовать на базе узла node1 вырожденный Kubernetes кластер («все-в-одном»).\
Задача 2. Организовать высокодоступный кластер Kubernetes, в котором узлы node1, node2, node3 будут управляющими (control-panel), а node3 и node4 – рабочими (workers).\ <br>

### 6. Решение

\
Договоримся, что все действия в данном гайде будем выполнять от пользователя *root*. Сначала обе задачи будем решать параллельно, а затем в нужных местах сделаем развилки. Для минимизации переделки, в случае выявления ошибок, будем периодически проверять настройки и делать снимки состояния виртуальных машин.\
\
В начальной точке у нас должно быть 5 свежеустановленных виртуальных машин, работающих под ОС Debian 11 x64. Машины должны быть в виртуальной сети с настройками 172.30.0.0/24. Шлюз по умолчанию – 172.30.0.2, он же DHCP, DNS и NAT сервер. Все машины должны иметь доступ в Интернет. Если все так, то делаем снимок состояния виртуальных машин и назовем его «START».\ <br>

#### 6.1. Предварительная настройка узлов кластера

\ <br>

**6.1.1. Настройка статических IP адресов узлов кластера**

\
На узле node1 cодержимое файла */etc/network/interfaces* заменим следующим:\
Скрытый текст

```
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
auto ens33
iface ens33 inet static
address 172.30.0.201
netmask 255.255.255.0
gateway 172.30.0.2
```

\
\
На узле node2 cодержимое файла */etc/network/interfaces* заменим следующим:\
Скрытый текст

```
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
auto ens33
iface ens33 inet static
address 172.30.0.202
netmask 255.255.255.0
gateway 172.30.0.2
```

\
\
\
На узле node3 cодержимое файла */etc/network/interfaces* заменим следующим:\
Скрытый текст

```
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
auto ens33
iface ens33 inet static
address 172.30.0.203
netmask 255.255.255.0
gateway 172.30.0.2
```

\
\
На узле node4 cодержимое файла */etc/network/interfaces* заменим следующим:\
Скрытый текст

```
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
auto ens33
iface ens33 inet static
address 172.30.0.204
netmask 255.255.255.0
gateway 172.30.0.2
```

\
\
\
На узле node5 cодержимое файла */etc/network/interfaces* заменим следующим:\
Скрытый текст

```
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
auto ens33
iface ens33 inet static
address 172.30.0.205
netmask 255.255.255.0
gateway 172.30.0.2
```

\
\ <br>

**6.1.2. Настройка имен узлов кластера**

\
На узле node1 выполним команду:<br>

```
hostnamectl set-hostname node1.internal
```

\
\
На узле node2 выполним команду:<br>

```
hostnamectl set-hostname node2.internal
```

\
\
На узле node3 выполним команду:<br>

```
hostnamectl set-hostname node3.internal
```

\
\
На узле node4 выполним команду:<br>

```
hostnamectl set-hostname node4.internal
```

\
\
На узле node5 выполним команду:<br>

```
hostnamectl set-hostname node5.internal
```

\ <br>

**6.1.3. Настройка DNS**

\
На всех узлах содержимое файла */etc/resolv.conf* заменим следующим:<br>

```
nameserver 172.30.0.2
```

\ <br>

**6.1.4. Настройка файла hosts**

\
Поскольку мы не используем DNS-сервер, то для разрешения важных для нас DNS-имен настроим файлы hosts на всех узлах кластера.\
\
На всех узлах выполним следующую команду:\
Скрытый текст

```
cat > /etc/hosts <<EOF
127.0.0.1       localhost

# The following lines are desirable for IPv6 capable hosts
::1     localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

# Cluster nodes
172.30.0.201 node1.internal
172.30.0.202 node2.internal
172.30.0.203 node3.internal
172.30.0.204 node4.internal
172.30.0.205 node5.internal
EOF
```

\
\ <br>

**6.1.5. Проверка сетевых настроек**

\
Убедимся, что все сетевые настройки сделаны правильно.\
Для этого на каждом узле по очереди выполним следующие тестовые команды:\
Скрытый текст

```
# Проверка доступности шлюза по умолчанию
ping 172.30.0.2 -c 1

# Проверка доступности node1
ping 172.30.0.201 -c 1
ping node1.internal -c 1

# Проверка доступности node2
ping 172.30.0.202 -c 1
ping node2.internal -c 1

# Проверка доступности node3 
ping 172.30.0.203 -c 1
ping node3.internal -c 1

# Проверка доступности node4
ping 172.30.0.204 -c 1
ping node4.internal -c 1

# Проверка доступности node5
ping 172.30.0.205 -c 1
ping node5.internal -c 1

# Проверка «видимости» Интернета
ping 8.8.8.8 -c 1
```

\
\
\
Все тесты должны проходить без ошибок. Если все так, то делаем снимок состояния виртуальных машин и называем его «NETWORK».\ <br>

**6.1.6. Установка вспомогательных пакетов**

\
Вариант A. Самый простой и быстрый вариант — это установить все и везде, не зависимо от того, нужно оно там или нет.\
\
На всех узлах выполним команду:<br>

```
apt install -y curl wget gnupg sudo iptables tmux keepalived haproxy
```

\
\
Вариант B. Более трудоемкий вариант — это на каждом узле поставить только то, что нужно.\
\
На всех узлах выполним команду:<br>

```
apt install -y curl wget gnupg sudo iptables
```

\
\
На узле node1 выполним команду:<br>

```
apt install -y tmux
```

\
\
На узлах node1, node2, node3 выполним команду:<br>

```
apt install -y keepalived haproxy
```

\ <br>

**6.1.7. Предварительная подготовка Linux для использования Kubernetes**

\
Согласно официальной [документации](https://kubernetes.io/docs/setup/production-environment/container-runtimes/), для работы Kubernetes необходимо разрешить маршрутизацию IPv4 трафика, настроить возможность iptables видеть трафик, передаваемый в режиме моста, а также отключить файлы подкачки.\
\
На всех узлах выполним команды:\
Скрытый текст

```
# Настройка автозагрузки и запуск модуля ядра br_netfilter и overlay
cat <<EOF | tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

modprobe overlay
modprobe br_netfilter

# Разрешение маршрутизации IP-трафика
echo -e "net.bridge.bridge-nf-call-ip6tables = 1\nnet.bridge.bridge-nf-call-iptables = 1\nnet.ipv4.ip_forward = 1" > /etc/sysctl.d/10-k8s.conf
sysctl -f /etc/sysctl.d/10-k8s.conf

# Отключение файла подкачки
swapoff -a
sed -i '/ swap / s/^/#/' /etc/fstab
```

\
\
\
Проверка корректности настройки\
Чтобы убедиться, что все требуемые параметры настроены правильно, рекомендуется перезагрузить виртуальную машину.\
\
Для проверки автоматической загрузки модулей *br\_netfilter* и *overlay* выполним команды:\
Скрытый текст

```
lsmod | grep br_netfilter
lsmod | grep overlay

## Ожидаемый результат должен быть следующим (цифры могут отличаться):
# br_netfilter           32768  0
# bridge                258048  1 br_netfilter
# overlay               147456  0
```

\
\
Для проверки успешности изменения настроек в параметрах сетевого стека выполним команду:\
Скрытый текст

```
sysctl net.bridge.bridge-nf-call-iptables net.bridge.bridge-nf-call-ip6tables net.ipv4.ip_forward

## Ожидаемый результат:
# net.bridge.bridge-nf-call-iptables = 1
# net.bridge.bridge-nf-call-ip6tables = 1
# net.ipv4.ip_forward = 1
```

\
\
Для проверки отключения файла подкачки выполним команду:\
Скрытый текст

```
swapon -s

## Ожидаемый вывод команды – пустой. Она ничего не должна отобразить.
```

\
\ <br>

**6.1.8. \[ОПЦИОНАЛЬНО] Разрешение авторизации в SSH от пользователя root**

<br>

> Внимание! Данный шаг снижает безопасность узлов. Выполнять его можно только в учебной среде, когда весь виртуальный стенд работает на вашем локальном компьютере, и виртуальные машины не доступны из сети.

\
При выполнении этой работы нам придется часто подключаться по SSH к машинам нашего стенда. По умолчанию настройки безопасности Debian запрещают нам напрямую подключаться под *root*, и приходится выполнять двойную авторизацию: сначала заходить под пользователем, а потом переключаться на root. Упростим себе жизнь и разрешим SSH сразу пускать нас под *root*.\
\
На всех узлах выполним следующие команды:\
Скрытый текст

```
echo "PermitRootLogin yes" > /etc/ssh/sshd_config.d/01-permitroot.conf
service sshd restart
```

\
\
Восстановление запрета авторизации под root выглядит следующим образом.\
\
На всех узлах нужно будет выполнить команды:\
Скрытый текст

```
rm /etc/ssh/sshd_config.d/01-permitroot.conf
service sshd restart
```

\
\ <br>

**Лайфхак. Использование TMUX для одновременного конфигурирования нескольких узлов**

\
Еще одним способом упростить себе жизнь будет использование программы [tmux](https://github.com/tmux/tmux) для совершения одинаковых действий (например, установки программ) на нескольких узлах. Магия работает за счет того, что *tmux* позволяет одновременно открыть несколько окон, а затем включить синхронизацию, и консольные команды, введенные в одном окне, будут автоматически транслироваться во все другие окна.\
\
В частности, для проведения одинаковых работ на всех узлах стенда (например, как в следующем шаге) необходимо сделать следующее:<br>

1. На узле node1 запустить *tmux*. Напомню, что именно на этот узел мы ранее поставили *tmux*.
2. С помощью интерфейса *tmux* сделать 4 дополнительных окна.
3. В полученных окнах поочередно совершить подключение по *ssh* к оставшимся узлам: node2, node3, node4, node5.
4. Перейти в окно, соответствующее узлу node1.
5. Активировать режим синхронизации команд между окнами
6. Провести необходимые работы.

\
По окончании работ:<br>

1. Отключить режим синхронизации команд.
2. Закрыть все созданные окна.
3. Выйти из *tmux*.

\
Программа *tmux* может управляться как горячими клавишами, так и текстовыми командами. Принцип управления горячими клавишами заключается в нажатии префикса Ctrl-B (одновременно Ctrl и кнопку «B»), а затем кнопки соответствующей команды. Например, \<Ctrl-B %> – разделит окно по вертикали. Аналогичная ей текстовая команда «split-window -h» сделает тоже самое. Для перехода в командный режим необходимо нажать \<Ctrl-B :>. Перечень всех горячих клавиш можно узнать, нажав \<Ctrl-B ?>.\
\
С теорией разобрались, рассмотрим пример использования.\
Пример проверки доступности Интернет одновременно на всех узлах кластера<br>

> ВНИМАНИЕ! Использование *tmux* для установок ПО может привести к психологической зависимости от чувства азарта, вызванного переживанием за скорость выполнения процесса в том или ином окне. Не надо расстраиваться и удивляться, когда в каждом конкретном случае окно победитель будет отличаться от ваших ожиданий. Помните, азартные игры до добра не доводят.

\ <br>

**6.1.9. Установка kubeadm, kubectl и kubelet**

\
[kubeadm](https://kubernetes.io/docs/reference/setup-tools/kubeadm/) – утилита для развертывания кластера Kubernetes, [kubectl](https://kubernetes.io/docs/reference/kubectl/) – основная утилита командной строки для управления кластером Kubernetes, [kubelet](https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/) – агент управления узлом кластера Kubernetes. Установка данных утилит осуществляется в соответствии с официальным [руководством](https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/#installing-kubeadm-kubelet-and-kubectl).\
\
На всех узлах выполним следующие команды:\
Скрытый текст

```

curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.32/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
chmod 644 /etc/apt/keyrings/kubernetes-apt-keyring.gpg 

echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.32/deb/ /' | tee /etc/apt/sources.list.d/kubernetes.list
chmod 644 /etc/apt/sources.list.d/kubernetes.list  

apt-get update
apt-get install -y kubeadm kubectl kubelet
```

\
\
На этом первый этап завершен. Наши узлы готовы к дальнейшим экспериментам. Делаем снимок состояния виртуальных машин и называем его «HOST IS PREPARED». К этому снимку мы будем неоднократно возвращаться.\ <br>

#### 6.2. Установка контейнерного движка

\
Kubernetes модульная система и может работать с различными контейнерными движками. При проведении экспериментов будем устанавливать по одному движку за раз. Хотя, как вы наверное догадались, на хосте одновременно может быть несколько движков и на разных узлах используемые движки могут отличаться, но это уже совсем другая история.\
\
По окончанию установки движка будем делать снимок состояния виртуальных машин, затем откатываться на предыдущий снимок («HOST IS PREPARED») и ставить следующий движок.\ <br>

**6.2.А. Вариант A. Установка cri-o**

\
Установка по осуществляется по официальной [документации](https://github.com/cri-o/cri-o/blob/main/install.md).\
\
На всех узлах выполним следующие команды:\
Скрытый текст

```
KUBERNETES_VERSION=v1.32
CRIO_VERSION=v1.32

apt-get install -y software-properties-common

curl -fsSL https://download.opensuse.org/repositories/isv:/cri-o:/stable:/$CRIO_VERSION/deb/Release.key |
    gpg --dearmor -o /etc/apt/keyrings/cri-o-apt-keyring.gpg

echo "deb [signed-by=/etc/apt/keyrings/cri-o-apt-keyring.gpg] https://download.opensuse.org/repositories/isv:/cri-o:/stable:/$CRIO_VERSION/deb/ /" |
    tee /etc/apt/sources.list.d/cri-o.list

apt-get update
apt-get install -y cri-o

systemctl daemon-reload
systemctl enable --now crio.service
```

\
\
\
6.2.А.1. Проверка доступности сокета cri-o\
Для тестов мы будем использовать утилиту [crictl](https://github.com/kubernetes-sigs/cri-tools/blob/master/docs/crictl.md), которая автоматически установилась вместе с *kubeadm*.\
\
На всех узлах запустим команду:\
Скрытый текст

```
crictl --runtime-endpoint unix:///var/run/crio/crio.sock version

## Ожидаемый результат:
# Version:  0.1.0
# RuntimeName:  cri-o
# RuntimeVersion:  1.32.2
# RuntimeApiVersion:  v1
```

\
\
6.2.А.2. Проверка запуска контейнеров с помощью cri-o<br>

> *Обновление 03.2025. Данная проверка не будет работать до развёртывания кластера Kubernetes.*

\
Кроме доступности сокета мы можем проверить фактическую возможность запуска контейнеров.\
\
На всех узлах запустим команды:\
Скрытый текст

```
export CONTAINER_RUNTIME_ENDPOINT=unix:///run/crio/crio.sock

cat > pod.config << _EOF_
{
    "metadata": {
        "name": "test-pod",
        "namespace": "default",
        "attempt": 1,
        "uid": "18fbfef14ae3a43"
    },
    "log_directory": "/tmp",
    "linux": {
    }
}
_EOF_

cat > container.config << _EOF_
{
  "metadata": {
    "name": "hello-world-container"
  },
  "image":{
    "image": "hello-world"
  },
  "log_path":"hello-world.log",
  "linux": {
  }
}
_EOF_

crictl pull hello-world
#crictl run container.config pod.config

POD_ID=$(crictl runp pod.config)
CONTAINER_ID=$(crictl create $POD_ID container.config pod.config)
crictl start $CONTAINER_ID

cat /tmp/hello-world.log

## Ожидаемый ответ
# …
# 2023-03-28T20:06:48.436017504+03:00 stdout F
# 2023-03-28T20:06:48.436017504+03:00 stdout F Hello from Docker!
# …
```

\
\
Если все тесты прошли успешно, то делаем снимок состояния виртуальных машин и называем его «CRI-O».\ <br>

**6.2.B. Вариант B. Установка containerd**

\
Откатываемся к состоянию виртуальных машин «HOST IS PREPARED». Установка осуществляется в соответствии с официальным [руководством](https://github.com/containerd/containerd/blob/main/docs/getting-started.md).\
\
На всех узлах выполним команды:\
Скрытый текст

```
# Установка containerd
wget https://github.com/containerd/containerd/releases/download/v2.0.4/containerd-2.0.4-linux-amd64.tar.gz
tar Cxzvf /usr/local containerd-2.0.4-linux-amd64.tar.gz
rm containerd-2.0.4-linux-amd64.tar.gz

# Создание конфигурации по умолчанию для containerd
mkdir /etc/containerd/
containerd config default > /etc/containerd/config.toml

# Установка systemd сервиса для containerd
wget https://raw.githubusercontent.com/containerd/containerd/main/containerd.service
mv containerd.service /etc/systemd/system/

# Установка компонента runc
wget https://github.com/opencontainers/runc/releases/download/v1.2.6/runc.amd64
install -m 755 runc.amd64 /usr/local/sbin/runc
rm runc.amd64

# Установка сетевых плагинов:
wget https://github.com/containernetworking/plugins/releases/download/v1.6.2/cni-plugins-linux-amd64-v1.6.2.tgz
mkdir -p /opt/cni/bin
tar Cxzvf /opt/cni/bin cni-plugins-linux-amd64-v1.6.2.tgz
rm cni-plugins-linux-amd64-v1.6.2.tgz

# Запуск сервиса containerd
systemctl daemon-reload
systemctl enable --now containerd

# Настройка конфигурации crictl
cat > /etc/crictl.yaml << _EOF
runtime-endpoint: unix:///var/run/containerd/containerd.sock
_EOF
```

\
\
\
6.2.B.1. Проверка доступности сокета containerd\
На всех узлах выполним команду:\
Заголовок спойлера

```
crictl --runtime-endpoint unix:///var/run/containerd/containerd.sock version

## Ожидаемый результат:
# Version:  0.1.0
# RuntimeName:  containerd
# RuntimeVersion:  v2.0.4
# RuntimeApiVersion:  v1
```

\
\
6.2.B.2. Проверка возможности запуска контейнеров с помощью containerd\
На всех узлах выполним команды:\
Скрытый текст

```
ctr images pull docker.io/library/hello-world:latest
ctr run docker.io/library/hello-world:latest hello-world

## Ожидаемый результат:
# …
# Hello from Docker!
# This message shows that your installation appears to be working correctly.
# …
```

\
\
Если все тесты прошли успешно, то делаем снимок состояния виртуальных машин и называем его «CONTAINERD».\ <br>

**6.2.C. Вариант C. Установка Docker + cri-dockerd**

\
Откатываемся к состоянию виртуальных машин «HOST IS PREPARED». В начале следует установить Docker. Для этого воспользуется официальным [руководством](https://docs.docker.com/engine/install/debian/).\
\
На всех узлах выполним следующие команды:\
Скрытый текст

```
# Добавляем официальный GPG ключ Docker
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc


# Настраиваем apt репозитории
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  tee /etc/apt/sources.list.d/docker.list > /dev/null
apt-get update

# Устанавливаем Docker
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# Устанавливаем плагин cri-docker
wget https://github.com/Mirantis/cri-dockerd/releases/download/v0.3.16/cri-dockerd_0.3.16.3-0.debian-bookworm_amd64.deb
dpkg -i cri-dockerd_0.3.16.3-0.debian-bookworm_amd64.deb
rm cri-dockerd_0.3.16.3-0.debian-bookworm_amd64.deb

# Настраиваем образ (image) для песочницы (sandbox)
mkdir /etc/systemd/system/cri-docker.service.d/
cat > /etc/systemd/system/cri-docker.service.d/10-pause.conf <<EOF
# File: /etc/systemd/system/cri-docker.service.d/10-pause.conf
[Service]
ExecStart=
ExecStart=/usr/bin/cri-dockerd --container-runtime-endpoint fd:// --pod-infra-container-image "registry.k8s.io/pause:3.10"
EOF

systemctl daemon-reload
systemctl restart cri-docker.service

# Конфигурируем crictl
cat > /etc/crictl.yaml << _EOF
runtime-endpoint: unix:///var/run/cri-dockerd.sock
_EOF
```

\
\
\
6.2.C.1. Проверка доступности сокета cri-dockerd\
На всех узлах выполним следующую команду:\
Скрытый текст

```
crictl --runtime-endpoint unix:///var/run/cri-dockerd.sock version

## Ожидаемый результат:
# Version:  0.1.0
# RuntimeName:  docker
# RuntimeVersion:  28.0.2
# RuntimeApiVersion:  v1
```

\
\
6.2.C.2. Проверка возможности Docker запускать контейнеры\
На всех узлах выполним следующую команду:\
Скрытый текст

```
docker run hello-world

## Ожидаемый результат:
# …
# Hello from Docker!
# This message shows that your installation appears to be working correctly.
# …
```

\
\
Если все тесты прошли успешно, то делаем снимок состояния виртуальных машин и называем его «CRI-DOCKERD».\
\
На этом этап завершен. В зависимости от выбранного варианта на всех узлах должен быть установлен один из контейнерных движков, причем везде он должен быть одинаковым.\ <br>

#### 6.3. Развёртывание Kubernetes

\ <br>

**6.3.A. Вариант A. Установка вырожденного Kubernete кластера**

\
Кластер «все-в-одном» будем разворачивать на узле node1, соответственно все приведенные команды будут выполняться на нем же. Процесс разворачивания кластера состоит из следующих этапов:<br>

1. Инициализация кластера Kubernetes.
2. Конфигурирование утилиты управления kubectl.
3. Установка сетевого плагина.
4. Настройка управляющего узла для выполнения рабочих нагрузок.

\ <br>

**6.3.A.1. Инициализация кластера Kubernetes**

\
Сначала рассмотрим инициализацию кластера на базе cri-o или containerd. Как это делать для Docker + cri-dockerd, расскажем чуть дальше. Инициализация кластера проводится одной командой:<br>

```
kubeadm init --pod-network-cidr=10.244.0.0/16
```

\
Здесь в параметре --pod-network-cidr мы явно указываем IP-подсеть, которая будет использоваться "*подами*". Конкретный диапазон 10.244.0.0/16 выбран таким образом, чтобы совпадать с диапазоном по умолчанию для сетевого плагина [flannel](https://github.com/flannel-io/flannel#deploying-flannel-manually), который мы будем устанавливать чуть позже.\
\
Для варианта с Docker + cri-dockerd рассмотренную ранее команду нужно чуть-чуть изменить:<br>

```
kubeadm init \
               --pod-network-cidr=10.244.0.0/16 \
               --cri-socket unix:///var/run/cri-dockerd.sock
```

\
Здесь мы добавили параметр --cri-socket. Он нужен нам, чтобы указать, через какой Unix сокет Kubernetes должен общаться с контейнерным движком. Для cri-o или containerd мы этого не делали, так как доступный сокет там был всего один, и *kubeadm* об этом знал. Связка Docker + cri-dockerd создает на узле два сокета: один для Docker, а второй для cri-dockerd, поэтому Kubernetes требуется явно указать, какой из них использовать.\ <br>

**6.3.A.2. Конфигурирование утилиты управления kubectl**

\
kubectl – основной рабочий инструмент по управлению кластером Kubernetes. По окончанию инициализации кластера необходимо настроить ее конфигурацию. Поскольку в начале статьи мы договорились, что будем работать от root, то для конфигурирования kubectl выполним следующие команды:<br>

```
echo "export KUBECONFIG=/etc/kubernetes/admin.conf" > /etc/environment
export KUBECONFIG=/etc/kubernetes/admin.conf
```

\
Выполнять подобную операцию нужно на всех узлах, с которых мы хотим управлять Kubernetes. Для этого, конечно, требуется убедиться, что существует конфигурационный файл */etc/kubernetes/admin.conf*. В текущем случае данный файл был автоматически создан утилитой *kubeadm*, в других случаях его нужно явно поместить на требуемый узел.\ <br>

**6.3.A.3. Установка сетевого плагина**

\
Как уже упоминалось выше, в качестве сетевого плагина мы будем использовать flannel. Тема сетевого взаимодействия в Kubernetes довольно обширна, и ей посвящено большое количество различных статей и документации. Лезть в эти дебри на данном этапе противопоказано, поэтому просто поставим один из самых распространённых сетевых плагинов.\
\
На node1 выполним команду:<br>

```
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
```

\ <br>

**6.3.A.4. Настройка управляющего узла для выполнения рабочих нагрузок**

\
Для управления тем, какая нагрузка (рабочая / управляющая) может выполняться на узле, используется механизм [«заражения и толерантности»](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) (taint and toleration). Если говорить простыми словами, то это механизм меток. С помощью «заражений» узлу присваиваются определенные метки. Механизм толерантности показывает, с какими метками "*под*" готов мириться, а с какими он работать не будет. По умолчанию на управляющих узлах Kubernetes устанавливается метка *«node-role.kubernetes.io/control-plane»*. Рабочие "*поды*" без дополнительный настроек на узлах с подобной меткой запускаться не будут.\
\
Для того чтобы разрешить выполнение рабочих "*подов*" на управляющем узле, с последнего нужно снять метку *node-role.kubernetes.io/control-plane*, что может быть сделано командой:<br>

```
kubectl taint nodes --all node-role.kubernetes.io/control-plane-
```

<br>

> Примечание. Для простоты данная команда снимает метку со всех узлов кластера. Для случая кластера из одной машины это нормально, но в многомашинном варианте рекомендуется все же явно указывать узлы, с которых будут сниматься метки.

\
Вырожденный кластер «все-в-одном» готов. На узле node1 можно сделать снимок состояния виртуальной машины и назвать «ALL IN ONE».\ <br>

**6.3.B. Вариант B. Организация отказоустойчивого кластера Kubernetes**

\
Настройку данного варианта следует начать с отката состояния виртуальных машин на момент завершения установки любого из контейнерных движков.\ <br>

**6.3.B.1. Объяснение, за счет чего достигается отказоустойчивость**

\
Организация отказоустойчивости в кластере Kubernetes базируется на решении двух стратегических задач:<br>

1. Отказоустойчивое выполнение рабочих нагрузок.
2. Отказоустойчивое управление кластером.

\
Решение первой задачи основывается на логике управления кластером. В отличии от Docker, здесь администратор не запускает контейнеры явным образом. Вместо этого он описывает желаемое состояние (desired state) кластера, в котором указывает какие "*поды*" должны быть запущены. Система управления кластером сравнивает текущее состояние с желаемым. Если эти состояния различаются, то система выполняет действия по приведению желаемого к текущему с учетом доступных ресурсов.\
\
Поясним на примере. Администратор во время конфигурации кластера указал, что желает видеть запущенными два "*пода*" с контейнерами [nginx](https://nginx.org/). После получения воли высшего существа система управления кластером смотрит – "*подов*" нет, nginx нет – не дела, скачивает nginx из репозитория, настраивает по конфигурации админа "*поды*", затем запускает их. Вот теперь желаемое = текущее. Если далее по каким-то мистическим причинам узел кластера, на котором крутились "*поды*", откажет, то система управления увидит, что текущее состояние отличается от желаемого, и автоматически запустит недостающие "*поды*" на другом доступном узле кластера, вновь делая желаемое = текущее. Вот таким интересным способом и достигается отказоустойчивость выполнения рабочих нагрузок.\
\
Решение второй задачи разбивается на подзадачи:<br>

1. Организация отказоустойчивого хранения и использования конфигурации кластера.
2. Организация отказоустойчивого доступа к API системы управления кластером.

\
Первая задача решается путем применения системы распределенного хранения данных etcd. Данная система хранит конфигурацию кластера одновременно на нескольких узлах и обеспечивает непротиворечивость имеющихся копий. Она позволяет продолжать нормальную работу кластера при выходе из строя некоторых узлов с репликами данных, а потом их горячее переподключение после восстановления. При использовании *kubeadm* для настройки кластера вся предварительная настройка etcd полностью ложится на его кремниевые плечи, и нам ничего делать не нужно.\
\
Решение второй задачи базируется на использовании внешнего компонента – балансировщика нагрузки. С его помощью создается виртуальный IP-адрес, запросы к которому автоматически транслируются на один из управляющих узлов кластера. Вся магия заключается в том, что все управляющие узлы кластера равноправны, и кластер может находиться под управлением любого из них. Поэтому и создается виртуальный IP-адрес, через который будет доступен как минимум один управляющий узел, а иногда и все управляющие узлы кластера в режиме очередности (round robin).\
К сожалению, настроить балансировщик нагрузки *kubeadm* не может, и эту работу нам придется делать самим.\ <br>

**6.3.B.2. Настройка балансировщика нагрузки**

\
Для создания виртуального IP адреса и перераспределение нагрузки между управляющими узлами будем использовать комбинацию двух демонов: keepalived и haproxy. Этих ребят мы с вами установили ранее на узлы: node1, node2, node3.\
\
Реализуемый нами балансировщик нагрузки будет работать следующим образом (Рисунок 12):<br>

1. Демон keepalived обеспечит функционирование виртуального IP-адреса и его привязку к одному из управляющих узлов. Виртуальный IP будет вторым адресом на сетевом интерфейсе узла. Если данный узел откажет, то keepalived обнаружит это и перекинет виртуальный IP-адрес на другой доступный узел.
2. Поступающие на управляющий узел запросы будут обрабатываться демоном haproxy, который, выполняя роль реверс-прокси (reverse proxy), будет поочередно (round robin) пересылать их на API сервера управляющих узлов Kubernetes.

\
![](https://habrastorage.org/r/w1560/webt/8z/eh/wq/8zehwqxz1ujgpxkazyvwqtsvwng.jpeg)\
\&#xNAN;*Рисунок 12*\
\
С одной стороны, одного keepalived должно хватить для построения полноценного балансировщика нагрузки, но официальный [гайд](https://github.com/kubernetes/kubeadm/blob/main/docs/ha-considerations.md) регламентирует его использование в паре с haproxy. [Народная мудрость](https://stackoverflow.com/questions/29292393/advantage-of-using-haproxy-and-keepalived-vs-just-keepalived) мотивирует использование сразу двух демонов тем, что так якобы лучше балансируется нагрузка. Сложно сказать, так это или нет, на самом деле, но в учебных целях мы все же прислушаемся к рекомендациям и построим балансировщик с использованием обоих демонов.\
\
Как во всем, что касается Kubernetes, демонов можно внедрить различными способами:<br>

1. Демоны могут быть реализованы в виде "*подов*" Kubernetes.
2. Демоны могут устанавливаться отдельно в операционную систему управляющих узлов.

\
Для большей наглядности выберем второй вариант. При развертывания балансировщика нагрузки будем использовать следующие сетевые настройки:<br>

* в качестве виртуального адреса будет использоваться 172.30.0.210;
* связанное с виртуальным адресом DNS имя: k8s-cp.internal;
* TCP порт для доступа к системе управления: 8888;
* в качестве бэкендов будут использоваться порты 6443 на управляющих узлах, другими словами, 172.30.0.201:6443, 172.30.0.202:6443, 172.30.0.203:6443;

\
При проведении работ все параметры будут жестко вбиты в конфигурационные файлы и скрипты.\ <br>

**6.3.B.2.1. Настройка демона keepalived**

\
На узлах node1, node2, node3 создадим и отредактируем основной конфигурационный файл демона keepalived */etc/keepalived/keepalived.conf* следующим образом:\
Скрытый текст

```
# File: /etc/keepalived/keepalived.conf

global_defs {
    enable_script_security
    script_user nobody
}

vrrp_script check_apiserver {
  script "/etc/keepalived/check_apiserver.sh"
  interval 3
}

vrrp_instance VI_1 {
    state BACKUP
    interface ens33
    virtual_router_id 5
    priority 100
    advert_int 1
    nopreempt
    authentication {
        auth_type PASS
        auth_pass ZqSj#f1G
    }
    virtual_ipaddress {
        172.30.0.210
    }
    track_script {
        check_apiserver
    }
}
```

\
\
\
На узлах node1, node2, node3 создадим и отредактируем скрипт */etc/keepalived/check\_apiserver.sh*, предназначенный для проверки доступности серверов.\
Скрытый текст

```
#!/bin/sh
# File: /etc/keepalived/check_apiserver.sh

APISERVER_VIP=172.30.0.210
APISERVER_DEST_PORT=8888
PROTO=http

errorExit() {
    echo "*** $*" 1>&2
    exit 1
}

curl --silent --max-time 2 --insecure ${PROTO}://localhost:${APISERVER_DEST_PORT}/ -o /dev/null || errorExit "Error GET ${PROTO}://localhost:${APISERVER_DEST_PORT}/"
if ip addr | grep -q ${APISERVER_VIP}; then
    curl --silent --max-time 2 --insecure ${PROTO}://${APISERVER_VIP}:${APISERVER_DEST_PORT}/ -o /dev/null || errorExit "Error GET ${PROTO}://${APISERVER_VIP}:${APISERVER_DEST_PORT}/"
fi
```

\
\
На узлах node1, node2, node3 установим атрибут, разрешающий исполнение скрипта, и запустим демона keepalived.\
Скрытый текст

```
chmod +x /etc/keepalived/check_apiserver.sh
systemctl enable keepalived
systemctl start keepalived
```

\
\ <br>

**6.3.B.2.2. Настройка демона haproxy**

\
На узлах node1, node2, node3 отредактируем основной конфигурационный файл демона haproxy */etc/haproxy/haproxy.cfg* следующим образом:\
Скрытый текст

```
# File: /etc/haproxy/haproxy.cfg
#---------------------------------------------------------------------
# Global settings
#---------------------------------------------------------------------
global
    log /dev/log local0 info alert
    log /dev/log local1 notice alert
    daemon

#---------------------------------------------------------------------
# common defaults that all the 'listen' and 'backend' sections will
# use if not designated in their block
#---------------------------------------------------------------------
defaults
    mode                    http
    log                     global
    option                  httplog
    option                  dontlognull
    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

#---------------------------------------------------------------------
# apiserver frontend which proxys to the control plane nodes
#---------------------------------------------------------------------
frontend apiserver
    bind *:8888
    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 node1 172.30.0.201:6443 check
        server node2 172.30.0.202:6443 check
        server node3 172.30.0.203:6443 check
```

\
На узлах node1, node2, node3 запустим демона haproxy, выполнив команды:\
Скрытый текст

```

systemctl enable haproxy
systemctl restart haproxy
```

\ <br>

> Примечание. Демон будет ругаться, что не обнаружены backend сервера. Это нормально, так как Kubernetes API еще не запущен.

\ <br>

**6.3.B.3. Установка управляющих узлов кластера**

\
Установка производится по официальной [документации](https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/high-availability/), выбран режим stacked control plane.<br>

> Примечание! При использовании Docker + cri-dockerd к строке инициализации нужно добавить параметр: --cri-socket unix:///var/run/cri-dockerd.sock

\ <br>

**6.3.B.3.1. Установка первого управляющего узла**

\
На node1\
При использовании контейнерных движков cri-o и containerd выполняем следующую команду:<br>

```
kubeadm init \
               --pod-network-cidr=10.244.0.0/16 \
               --control-plane-endpoint "172.30.0.210:8888" \
               --upload-certs
```

<br>

> Примечание 1. --pod-network-cidr=10.244.0.0/16 выбрано для упрощения дальнейшей установки сетевого плагина flannel.

<br>

> Примечание 2. --control-plane-endpoint «172.30.0.210:8888» указывает на виртуальный IP адрес, используемый для управления кластером.

\
\
На случай использования Docker + cri-dokerd команда инициализации будет выглядеть так:<br>

```
kubeadm init \
               --cri-socket unix:///var/run/cri-dockerd.sock \
               --pod-network-cidr=10.244.0.0/16 \
               --control-plane-endpoint "172.30.0.210:8888" \
               --upload-certs
```

\
\
По окончанию процедуры должна появиться строка для добавления управляющих узлов в кластер.<br>

```
….
You can now join any number of the control-plane node running the following command on each as root:

  kubeadm join 172.30.0.210:8888 --token 4uvhjf.pmq742i3rofly0qr \
        --discovery-token-ca-cert-hash sha256:9cf1614b335f50f8a0014d45534f4ab702319c32111d2124285655cc7cbcdf60 \
        --control-plane --certificate-key 15489535ff4f00324eb23808585d3b9acddf38801069f92bf39f8404c677ffa9
….
```

\
\
Кроме указанной строки, будет показана строка для добавления рабочих узлов в кластер.<br>

```

...
Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 172.30.0.210:8888 --token 4uvhjf.pmq742i3rofly0qr \
        --discovery-token-ca-cert-hash sha256:9cf1614b335f50f8a0014d45534f4ab702319c32111d2124285655cc7cbcdf60
...
```

\ <br>

> Примечание. Приведенные выше строки будут изменяться от инсталляции к инсталляции. Здесь они приведены для примера, копировать их отсюда не имеет смысла.

\ <br>

**6.3.B.3.2. Установка последующих управляющих узлов**

\
На node2, node3\
Используем строку подключения, полученную после создания первого управляющего узла кластера. При использовании в качестве движка связки Docker + cri-dokerd не забудьте добавить к этой строке параметр --cri-socket unix:///var/run/cri-dockerd.sock<br>

> Внимание! В строке содержится конфиденциальная информация. Сертификаты для подключения будут автоматически удалены после 2-х часов с момента первичной инициализации кластера.

\
В случае успешного добавления узла среди вывода *kubeadm* должна быть строка:<br>

```
…
This node has joined the cluster and a new control plane instance was created:
…
```

\ <br>

**6.3.B.4. Установка рабочих узлов кластера**

\
На узлах node4, node5 запускаем команду добавления рабочих узлов, полученную при установке первого управляющего узла.<br>

> Примечание. Если по каким-то причинам вы ее потеряли, то на любом узле кластера введите команду «kubeadm token create --print-join-command», и она отобразится снова.

\ <br>

**6.3.B.5. Настройка kubeсtl**

\
На узлах node1, node2, node3 выполним команду:<br>

```
echo "export KUBECONFIG=/etc/kubernetes/admin.conf" > /etc/environment
export KUBECONFIG=/etc/kubernetes/admin.conf
```

<br>

> Примечание. Выполнение этой команды на рабочих узлах не имеет смысла, так как на них отсутствует файл */etc/kubernetes/admin.conf*, автоматически создаваемый *kubeadm* при добавлении управляющего узла.

\ <br>

**6.3.B.6. Установка сетевого плагина**

\
Данная процедура полностью повторяет аналогичную для случая вырожденного кластера.\
\
На node1 запускаем команду:<br>

```
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
```

\
\
Отказоустойчивый кластер Kubernetes готов. Рекомендуется на всех узлах стенда сделать снимок состояния виртуальных машины и назвать его «HA CLUSTER».\ <br>

### 7. Проверка работы кластера Kubernetes

\
Приведенные ниже проверки можно запускать на любом узле кластера, на котором настроена работа kubectl.\
\
Проверка включения узлов в кластер:\
Скрытый текст

```
kubectl get nodes

## Ожидаемый ответ:
# NAME             STATUS   ROLES           AGE   VERSION
# node1.internal   Ready    control-plane   61m   v1.32.3
# node2.internal   Ready    control-plane   59m   v1.32.3
# node3.internal   Ready    control-plane   59m   v1.32.3
# node4.internal   Ready    <none>          58m   v1.32.3
# node5.internal   Ready    <none>          58m   v1.32.3
```

\ <br>

> Примечание. Если узел в кластер добавлен недавно, то ответ команды может отличаться от ожидаемого. Необходимо некоторое время, чтобы новый узел «освоился».

\
\
При использовании *kubeadm* все управляющее ПО кластера работает на нем в виде "*подов*". Очень важно, чтобы все "*поды*" работали правильным образом, и не было их циклических перезапусков (restarts).\
\
Проверить состояние "*подов*" можно с помощью команды:\
Скрытый текст

```
kubectl get pods -A

# Ожидаемый результат
# NAMESPACE      NAME                                     READY   STATUS    RESTARTS      AGE
# kube-flannel   kube-flannel-ds-2p64k                    1/1     Running   0             2m58s
# kube-flannel   kube-flannel-ds-77vxc                    1/1     Running   0             17m
# kube-flannel   kube-flannel-ds-9pk6s                    1/1     Running   0             12m
# kube-flannel   kube-flannel-ds-dhlzp                    1/1     Running   0             11m
# kube-flannel   kube-flannel-ds-sts89                    1/1     Running   0             2m58s
# kube-system    coredns-787d4945fb-sx2kd                 1/1     Running   0             34m
# kube-system    coredns-787d4945fb-xj2kq                 1/1     Running   0             34m
# kube-system    etcd-node1.internal                      1/1     Running   0             34m
# kube-system    etcd-node2.internal                      1/1     Running   0             12m
# kube-system    etcd-node3.internal                      1/1     Running   0             12m
# kube-system    kube-apiserver-node1.internal            1/1     Running   0             34m
# kube-system    kube-apiserver-node2.internal            1/1     Running   0             12m
# kube-system    kube-apiserver-node3.internal            1/1     Running   0             12m
# kube-system    kube-controller-manager-node1.internal   1/1     Running   1 (12m ago)   34m
# kube-system    kube-controller-manager-node2.internal   1/1     Running   0             12m
# kube-system    kube-controller-manager-node3.internal   1/1     Running   0             12m
# kube-system    kube-proxy-5m2lt                         1/1     Running   0             2m58s
# kube-system    kube-proxy-bwvk6                         1/1     Running   0             2m58s
# kube-system    kube-proxy-d4f89                         1/1     Running   0             12m
# kube-system    kube-proxy-gx9fd                         1/1     Running   0             11m
# kube-system    kube-proxy-w2skj                         1/1     Running   0             34m
# kube-system    kube-scheduler-node1.internal            1/1     Running   1 (12m ago)   34m
# kube-system    kube-scheduler-node2.internal            1/1     Running   0             12m
# kube-system    kube-scheduler-node3.internal            1/1     Running   0             12m
```

\
\
Типовые проблемы:<br>

1. Часть "*подов*", например, coredns, запустившись, не переходит в состояние готовности. Это может происходить из-за проблем с сетевым плагином. Например, вы забыли его установить.
2. "*Поды*" циклически перезагружаются. Одной из вероятных причин подобного события при использовании в качестве контейнерного движка containerd является неправильная настройка cgroup драйвера в нем. Переставьте containerd заново, в точности выполнив все указания настоящего гайда.

\ <br>

### 8. Тестовые запуски "*подов*" в Kubernetes

\ <br>

#### 8.1. Тест 1. Запуск "*пода*" в интерактивном режиме.

\
Данный тест позаимствован из официальной [документации](https://kubernetes.io/ru/docs/reference/kubectl/cheatsheet/).\
Скрытый текст

```
kubectl run -i --tty busybox --image=busybox -- sh

## Ожидаемый результат:
# If you don't see a command prompt, try pressing enter.
# / #
# / #
```

\
\
Вспомогательные команды:\
\
Переподключение к "*поду*" при выходе из интерактивного режима:<br>

```
kubectl attach busybox -i
```

\
\
Удаление "*пода*":<br>

```
kubectl delete pod busybox
```

\ <br>

#### 8.2. Тест 2. Запуск NGINX

\
Тест заключается в запуске Web-сервера nginx и обращения к нему после этого.\
Скрытый текст

```

kubectl create deployment nginx-app --image=nginx
kubectl expose deployment nginx-app --type=NodePort --port=80 --external-ip=10.10.10.10
sleep 5s
curl http://10.10.10.10

## Ожидаемый результат
# <!DOCTYPE html>
# <html>
# <head>
# <title>Welcome to nginx!</title>
# ...
```

\ <br>

> Примечание. Зафиксированы случаи, когда репозиторий накладывает ограничения на возможность скачивания базовых образов, из-за чего тесты проваливаются. Если вы попали в подобную ситуацию, попробуйте сменить IP-адрес, например, с помощью VPN.

\ <br>

### 9. Диагностика балансировщика нагрузки

\
Для того чтобы определить узел, на котором сейчас активен виртуальный IP-адрес, поочередно запускаем на управляющих узлах команду:\
Скрытый текст

```
ip a

## Ожидаемый ответ:
# 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 pfifo_fast state UP group #default qlen 1000
#   link/ether 00:0c:29:a5:2b:48 brd ff:ff:ff:ff:ff:ff
#    altname enp2s1
#    inet 172.30.0.203/24 brd 172.30.0.255 scope global ens33
#       valid_lft forever preferred_lft forever
### !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
### Строка ниже показывает присвоение виртуального IP-адреса
### vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
#    inet 172.30.0.210/32 scope global ens33
#       valid_lft forever preferred_lft forever
#    inet6 fe80::20c:29ff:fea5:2b48/64 scope link
#    valid_lft forever preferred_lft forever
# ….
```

\
\
\
Для проверки открытия сетевых сокетов воспользуемся следующей командой:\
Скрытый текст

```
ss -lt

## Ожидаемый результат:
# State   Recv-Q  Send-Q   Local Address:Port    Peer Address:Port Process
# LISTEN  0       4096         127.0.0.1:10248        0.0.0.0:*
# LISTEN  0       4096         127.0.0.1:10249        0.0.0.0:*
# LISTEN  0       4096      172.30.0.202:2379         0.0.0.0:*
# LISTEN  0       4096         127.0.0.1:2379         0.0.0.0:*
# LISTEN  0       4096      172.30.0.202:2380         0.0.0.0:*
# LISTEN  0       4096         127.0.0.1:2381         0.0.0.0:*
# LISTEN  0       4096         127.0.0.1:10257        0.0.0.0:*
# LISTEN  0       4096         127.0.0.1:10259        0.0.0.0:*
# LISTEN  0       128            0.0.0.0:ssh          0.0.0.0:*
# LISTEN  0       4096           0.0.0.0:8888         0.0.0.0:*
# LISTEN  0       4096         127.0.0.1:35099        0.0.0.0:*
# LISTEN  0       4096                 *:10250              *:*
# LISTEN  0       4096                 *:6443               *:*
# LISTEN  0       4096                 *:10256              *:*
# LISTEN  0       128               [::]:ssh             [::]:*
```

\
\
\
Для фактической проверки доступности API по виртуальному IP-адресу можно воспользоваться командой:\
Скрытый текст

```
curl --silent --max-time 2 --insecure https://172.30.0.210:8888/

## Ожидаемый результат:
# {
#  "kind": "Status",
#  "apiVersion": "v1",
#  "metadata": {},
#  "status": "Failure",
#  "message": "forbidden: User \"system:anonymous\" cannot get path \"/\"",
#  "reason": "Forbidden",
#  "details": {},
#  "code": 403
# }
```

\
\ <br>

### X. Заключение

\
Kubernetes — большая и довольно сложная тема, но маленький первый шаг на пути его покорения вы уже сделали — у вас появился работающий стенд для экспериментов. Дело осталось за малым — продолжать учиться. В этом вам могут помочь следующие статьи:<br>

* [Основы Kubernetes](https://habr.com/ru/post/258443/)
* [Just-in-Time Kubernetes: Руководство начинающим для понимания основных концепций Kubernetes](https://habr.com/ru/company/otus/blog/650231/)
* [Различия между Docker, containerd, CRI-O и runc](https://habr.com/ru/company/domclick/blog/566224/)
* [Визуальное руководство по диагностике неисправностей в Kubernetes](https://habr.com/ru/company/flant/blog/484954/)
* [Записки о containerd](https://habr.com/ru/post/568274/)
* [Зачем нужен контейнер pause в Kubernetes](https://habr.com/ru/company/southbridge/blog/715402/)
* [Как я клонировал Томми Версетти, или запускаем GUI/GPU приложения в Kubernetes](https://habr.com/ru/post/715886/)
* [Отказоустойчивый кластер с балансировкой нагрузки с помощью keepalived](https://habr.com/ru/post/524688/)

Теги:

* [kubernetes](https://habr.com/ru/search/?target_type=posts\&order=relevance\&q=\[kubernetes])
* [keepalived](https://habr.com/ru/search/?target_type=posts\&order=relevance\&q=\[keepalived])
* [haproxy](https://habr.com/ru/search/?target_type=posts\&order=relevance\&q=\[haproxy])
* [cri-o](https://habr.com/ru/search/?target_type=posts\&order=relevance\&q=\[cri-o])
* [docker](https://habr.com/ru/search/?target_type=posts\&order=relevance\&q=\[docker])
* [cri-dockerd](https://habr.com/ru/search/?target_type=posts\&order=relevance\&q=\[cri-dockerd])
* [containerd](https://habr.com/ru/search/?target_type=posts\&order=relevance\&q=\[containerd])
* [debian](https://habr.com/ru/search/?target_type=posts\&order=relevance\&q=\[debian])
* [crictl](https://habr.com/ru/search/?target_type=posts\&order=relevance\&q=\[crictl])

Хабы:

* [DevOps](https://habr.com/ru/hubs/devops/)
* [IT-инфраструктура](https://habr.com/ru/hubs/it-infrastructure/)
* [Kubernetes](https://habr.com/ru/hubs/kubernetes/)
* [Виртуализация](https://habr.com/ru/hubs/virtualization/)
* [Системное администрирование](https://habr.com/ru/hubs/sys_admin/)

+24389[24](https://habr.com/ru/articles/725640/comments/)

#### Редакторский дайджест

Присылаем лучшие статьи раз в месяц

Оставляя свою почту, я принимаю [Политику конфиденциальности](https://account.habr.com/ru/info/confidential) и даю согласие на получение рассылок[![](https://habrastorage.org/getpro/habr/avatars/e65/6a4/381/e656a43814e9087f0393ce30582ed2af.jpg)](https://habr.com/ru/users/imbasoft/)82Карма0Общий рейтинг[@imbasoft](https://habr.com/ru/users/imbasoft/)

Информационная безопасность

ПодписатьсяХабр доступен 24/7 благодаря поддержке друзей![Хабр Карьера Курсы](https://habrastorage.org/webt/qq/ey/pn/qqeypn-py71suynxbusbakjdfjw.png)Хабр Курсы для всехРЕКЛАМАПрактикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать\![Перейти](https://career.habr.com/courses/?erid=2VSb5wDLYUH\&utm_source=habr\&utm_medium=sponsorship_hub)[При поддержкеРЕКЛАМА](https://career.habr.com/courses/?erid=2VSb5wyoqShr\&utm_source=hab\&utm_medium=sponsorship_hub_2)

### Комментарии 24

[![](https://assets.habr.com/habr-web/img/avatars/115.png)](https://habr.com/ru/users/daniilrysakov_1990/)[daniilrysakov\_1990](https://habr.com/ru/users/daniilrysakov_1990/)[11 апр 2023 в 12:55](https://habr.com/ru/articles/725640/#comment_25431562)

При всех достоинствах K8s эта система управления контейнерными кластерами имеет следующие недостатки, которые значительно усложняют ее использование на практике&#x20;

· большое число специфических понятий и взаимозависимых сущностей (под, сервис, кубелет, контроллер и т.д.);

· скудная документация, которая недостаточно подробно описывает систему;

· добавление дополнительного уровня абстракции увеличивает сложность и хрупкость системы;

· недостаток и высокая стоимость опытных специалистов, в совершенстве владеющих этой DevOps-технологией.

[![](https://habrastorage.org/r/w48/getpro/habr/avatars/e65/6a4/381/e656a43814e9087f0393ce30582ed2af.jpg)](https://habr.com/ru/users/imbasoft/)[imbasoft](https://habr.com/ru/users/imbasoft/)[11 апр 2023 в 14:38](https://habr.com/ru/articles/725640/#comment_25432096)

Это да - система действительно очень сложна, но как говорится: "хочешь быть в высшей лиге, играй как в высшей лиге".

Для честной высокой зарплаты нужны и честные высокие знания.

[![](https://habrastorage.org/r/w48/getpro/habr/avatars/062/c85/607/062c85607fbe4aaa29e1ad2076cd13a0.jpg)](https://habr.com/ru/users/shurup/)[shurup](https://habr.com/ru/users/shurup/)[14 апр 2023 в 07:28](https://habr.com/ru/articles/725640/#comment_25444006)

Ваш комментарий — это скопированный фрагмент [этой статьи](https://medium.com/@bigdataschool/%D0%B1%D0%BB%D0%B5%D1%81%D0%BA-%D0%B8-%D0%BD%D0%B8%D1%89%D0%B5%D1%82%D0%B0-kubernetes-%D0%B4%D0%BE%D1%81%D1%82%D0%BE%D0%B8%D0%BD%D1%81%D1%82%D0%B2%D0%B0-%D0%B8-%D0%BD%D0%B5%D0%B4%D0%BE%D1%81%D1%82%D0%B0%D1%82%D0%BA%D0%B8-%D1%81%D0%B0%D0%BC%D0%BE%D0%B9-%D0%BF%D0%BE%D0%BF%D1%83%D0%BB%D1%8F%D1%80%D0%BD%D0%BE%D0%B9-devops-%D1%82%D0%B5%D1%85%D0%BD%D0%BE%D0%BB%D0%BE%D0%B3%D0%B8%D0%B8-%D0%B4%D0%BB%D1%8F-big-data-60efa84e61a3) 4-летней давности. Зачем?

[![](https://habrastorage.org/r/w48/getpro/habr/avatars/ee2/191/3ca/ee21913ca57cf225cc3b6e0c0c332176.jpg)](https://habr.com/ru/users/waxtah/)[waxtah](https://habr.com/ru/users/waxtah/)[14 авг 2023 в 17:10](https://habr.com/ru/articles/725640/#comment_25857768)

Специфические понятия не является недостатком, это скорее необходимость (докер он попроще, но тем не менее тоже присутствует своя терминология)

Про скудную документацию - не соглашусь.

Единственный серьезный недостаток - изменения API ломающие обратную совместимость, но это вызвано бурным ростом продукта.

[![](https://assets.habr.com/habr-web/img/avatars/104.png)](https://habr.com/ru/users/Eldalex/)[Eldalex](https://habr.com/ru/users/Eldalex/)[10 мая 2023 в 14:25](https://habr.com/ru/articles/725640/#comment_25533918)

День добрый! На распишете подроблее про балансировщик? 172.30.0.210:8888 кто он, где он его надо настраивать отдельно или он создатся при инициализации? у меня инициализация заканчивается неудачей:

```
Unfortunately, an error has occurred:
        timed out waiting for the condition

This error is likely caused by:
        - The kubelet is not running
        - The kubelet is unhealthy due to a misconfiguration of the node in some way (required cgroups disabled)

If you are on a systemd-powered system, you can try to troubleshoot the error with the following commands:
        - 'systemctl status kubelet'
        - 'journalctl -xeu kubelet'
```

но при этом kubelet имеет статус запущен, но сыпет ошибки что API недоступен,

`Unable to register node with API server" err="Post "https://172.30.0.210:8888`

upd. Всё, разобрался, это я слепой, не заметил имя интерфейса в конфиге keepalived<br>

[![](https://habrastorage.org/r/w48/getpro/habr/avatars/e65/6a4/381/e656a43814e9087f0393ce30582ed2af.jpg)](https://habr.com/ru/users/imbasoft/)[imbasoft](https://habr.com/ru/users/imbasoft/)[11 мая 2023 в 12:54](https://habr.com/ru/articles/725640/#comment_25537372)

Бывает :)

Лично я начинал с игр просто с балансировщиком (сначала просто keepalived и haproxy а потом их вместе) поверх nginx, а потом проецировал полученные результаты на kubernetes. Так конечно дольше, но зато лучше понимаешь "внутреннюю природу вещей".

[![](https://assets.habr.com/habr-web/img/avatars/104.png)](https://habr.com/ru/users/Eldalex/)[Eldalex](https://habr.com/ru/users/Eldalex/)[16 мая 2023 в 13:08](https://habr.com/ru/articles/725640/#comment_25552626)

Можно ещё вопрос? Всё рботает, всё красиво. тесты проходят. Можно ли как то получить данные с nginx непосредственно с хостовой машины? или это влечёт за собой допнастройки?

ЗЫ. я вообще ansible плейбук пилю для воспроизводства всей статьи, можно потом оформить статьеё и сослаться на вас как на основной источник вдохновения?)

[![](https://habrastorage.org/r/w48/getpro/habr/avatars/e65/6a4/381/e656a43814e9087f0393ce30582ed2af.jpg)](https://habr.com/ru/users/imbasoft/)[imbasoft](https://habr.com/ru/users/imbasoft/)[16 мая 2023 в 19:32](https://habr.com/ru/articles/725640/#comment_25554104)

Красиво получить доступ с хостовой машины, равно как и с любой другой сетевой машины в текущем конфиге невозможно. Нужна доп. настройка. Думаю запилить вторую часть статьи, где будет это рассмотрено, но когда время будет не знаю.

Если хотите можете самостоятельно поковыряться. Нужно пилить в сторону ingress и Load Balancer.

Ссылка на статью приветствуются)

[![](https://assets.habr.com/habr-web/img/avatars/154.png)](https://habr.com/ru/users/ingiboy/)[ingiboy](https://habr.com/ru/users/ingiboy/)[29 июн 2023 в 13:15](https://habr.com/ru/articles/725640/#comment_25700514)

Большое спасибо за статью, тоже пилю плейбук, одновременно изучая и ансибл, и кубер)

Подскажите, плз, зачем в п.6.2.B ставятся сетевые плагины в /opt/cni/bin? Не нашел, чтобы они где-то использовались в конфигах.

[![](https://assets.habr.com/habr-web/img/avatars/154.png)](https://habr.com/ru/users/ingiboy/)[ingiboy](https://habr.com/ru/users/ingiboy/)[30 июн 2023 в 14:59](https://habr.com/ru/articles/725640/#comment_25704974)

в yaml фланнеля, пардон

[![](https://assets.habr.com/habr-web/img/avatars/154.png)](https://habr.com/ru/users/ingiboy/)[ingiboy](https://habr.com/ru/users/ingiboy/)[30 июн 2023 в 11:58](https://habr.com/ru/articles/725640/#comment_25704318)

Еще момент - для проведения выборов keepalived проверяет доступность управляющих нод выполнением скрипта с опросом порта 8888, где находится контролплейн кубера. Но по сценарию на момент настройки keppalived и haproxy контролплейна еще нет, скрипт выдает ошибку и выборы для назначения виртуального ИП не проходят.

Решит добавлением в конфиг хапрокси /etc/haproxy/haproxy.cfg:

`#---------------------------------------------------------------------`\
`# keepalived frontend for reply HTTP/200`\
`#---------------------------------------------------------------------`\
`frontend http_200`\
&#x20;   `bind *:1200`\
&#x20;   `mode tcp`\
&#x20;   `option tcplog`\
&#x20;   `default_backend http_200`\
`backend http_200`\
&#x20;   `http-request return status 200 content-type "text/plain" lf-string "OK"`

И в скрипте /etc/keepalived/check\_apiserver.sh

`APISERVER_DEST_PORT=1200`

[![](https://habrastorage.org/r/w48/getpro/habr/avatars/e65/6a4/381/e656a43814e9087f0393ce30582ed2af.jpg)](https://habr.com/ru/users/imbasoft/)[imbasoft](https://habr.com/ru/users/imbasoft/)[1 июл 2023 в 18:24](https://habr.com/ru/articles/725640/#comment_25708012)

Когда появляется control-plane выборы проходят успешно и ошибка исчезает, обрабатывать ее не имеет смысла. Можно сказать, что это не баг, а фича :) Так сделано для упрощения конфига.

[![](https://assets.habr.com/habr-web/img/avatars/154.png)](https://habr.com/ru/users/ingiboy/)[ingiboy](https://habr.com/ru/users/ingiboy/)[1 июл 2023 в 18:45](https://habr.com/ru/articles/725640/#comment_25708044)

Да, я уже разобрался, спасибо, в конфиге хапрокси адреса 6а свои не поменял)

Но решил оставить так, для порядка, чтобы точно все работало

[![](https://habrastorage.org/r/w48/getpro/habr/avatars/f59/b34/7c6/f59b347c6fabcd02fa42c5958dfed6bf.jpg)](https://habr.com/ru/users/JBMurloc/)[JBMurloc](https://habr.com/ru/users/JBMurloc/)[18 мар в 13:59](https://habr.com/ru/articles/725640/#comment_28054480)

[@imbasoft](https://habr.com/users/imbasoft), я понимаю, что статья уже старая, но, всё же, у Вас есть такой текст в статье.

> `# Настройка deb-репозитория Kubernetescurl -fsSLo /etc/apt/trusted.gpg.d/kubernetes-archive-keyring.gpg` [`https://packages.cloud.google.com/apt/doc/apt-key.gpg`](https://packages.cloud.google.com/apt/doc/apt-key.gpg)\
> \
> `echo "deb [signed-by=/etc/apt/trusted.gpg.d/kubernetes-archive-keyring.gpg]` [`https://apt.kubernetes.io/`](https://apt.kubernetes.io/) `kubernetes-xenial main" | tee /etc/apt/sources.list.d/kubernetes.list`

\
Но дело в том, что <https://apt.kubernetes.io/> официально устарел и следует использовать другой репозиторий <https://pkgs.k8s.io/core>. Подробно описано тут: <https://kubernetes.io/blog/2023/08/15/pkgs-k8s-io-introduction/>\
\
У меня, например, с <https://apt.kubernetes.io/> ничего не получилось поставить. Добавьте, пожалуйста, пояснение в статью. Я думаю, что по ней не один я kubernetes настраиваю.

[![](https://habrastorage.org/r/w48/getpro/habr/avatars/e65/6a4/381/e656a43814e9087f0393ce30582ed2af.jpg)](https://habr.com/ru/users/imbasoft/)[imbasoft](https://habr.com/ru/users/imbasoft/)[18 мар в 14:15](https://habr.com/ru/articles/725640/#comment_28054558)

Давайте так, как закончите изучение статьи скиньте мне все найденные проблемы и ваши предложения по их обходу. Я их перепроверю и обновлю статью.

[![](https://habrastorage.org/r/w48/getpro/habr/avatars/f59/b34/7c6/f59b347c6fabcd02fa42c5958dfed6bf.jpg)](https://habr.com/ru/users/JBMurloc/)[JBMurloc](https://habr.com/ru/users/JBMurloc/)[18 мар в 15:22](https://habr.com/ru/articles/725640/#comment_28054960)

Пока что это всё. Я настроил и вырожденный кластер по Вашей статье и полноценный. Всё отлично, за исключением нюанса с устаревшим ppa репозиторием.

[![](https://habrastorage.org/r/w48/getpro/habr/avatars/e65/6a4/381/e656a43814e9087f0393ce30582ed2af.jpg)](https://habr.com/ru/users/imbasoft/)[imbasoft](https://habr.com/ru/users/imbasoft/)[18 мар в 21:30](https://habr.com/ru/articles/725640/#comment_28056412)

Хорошо, будут тогда ману копить на обновление)

[![](https://assets.habr.com/habr-web/img/avatars/169.png)](https://habr.com/ru/users/Andy1899135/)[Andy1899135](https://habr.com/ru/users/Andy1899135/)[18 мар в 20:12](https://habr.com/ru/articles/725640/#comment_28056138)

автор, при всем уважении к вашему труду в написании неплохой статьи, вот это " Упростим себе жизнь и разрешим SSH сразу пускать нас под root/ " - очень вас принижает. слабо верится что вы не знаете о таких инструментах как sudo & ssh-keygen и так далее. не стоит учить плохому...

[![](https://habrastorage.org/r/w48/getpro/habr/avatars/e65/6a4/381/e656a43814e9087f0393ce30582ed2af.jpg)](https://habr.com/ru/users/imbasoft/)[imbasoft](https://habr.com/ru/users/imbasoft/)[18 мар в 21:29](https://habr.com/ru/articles/725640/#comment_28056404)

Да, я знаю про sudo и авторизацию по ключам. Приведена эта фраза именно для упрощения материала. Debian, в отличии от Ubuntu, не содержит (по умолчанию) в себе sudo. Эту утилиту нужно ставить дополнительно, и кроме того настраивать пользователю на неё использование. Поэтому про sudo было решено не писать.

С точки зрения безопаности. Debian, по умолчанию, использует парадигму "su", то есть вы входите под юзером, а потом перепрыгиваете на root. По факту подобная схема при двойной нагрузке на пользователя (нужно помнить два пароля) дает очень маленький выхлоп безопасности, с точки зрения подбора паролей можно просто увеличить пароль root на один символ и будет безопасней (математика не даст соврать).

Теперь к sudo. Сам по себе sudo дает только два положительных эффекта: 1 - он позволяет логгировать действия суперпользователей когда их несколько и они не читят типа (sudo -i или sudo bash); 2 - он (как и su) позволяет держать в секрете логин администратора (пользователя с правами sudo), что может быть актуально если у вас настроена блокировка авторизации при входе (интерактивно или ssh). Все, больше она ничего не дает. Хотя конечно можно придумать про случайный запуск вредоноса сразу root'ом, но это фэнтази при работе на серверах.

Резюмируя все сказанное, для учебных машин, на которых будет работать только один человек с правами администратора использование sudo не имеет ни малейшего смысла, равно как и su. Поскольку блокировка аккаунта не используется и вся безопасность держится на конфиденциальности пароля.

Заметьте я не говорю, что sudo - это бесполезная вещь. Просто все должно использоваться под задачу и с учетом специфики (модели актуальных угроз), а не просто, из-за того, что кто-то сказал что так правильно.

Карго культ must die.

[![](https://assets.habr.com/habr-web/img/avatars/169.png)](https://habr.com/ru/users/Andy1899135/)[Andy1899135](https://habr.com/ru/users/Andy1899135/)[18 мар в 22:30](https://habr.com/ru/articles/725640/#comment_28056570)

1. по sudo. скажем так, ваше мнение по sudo слегка занижено. sudo - это прежде всего возможность ограничения прав пользователей и выдача им разрешений на выполнение каких либо действий от имени суперпользователя.
2. по паролям. я не зря вам указал про ssh и ключи. если уж вам так хочется ходить под рутом - бога ради, это только ваши проблемы. запретите парольный вход и ходите с ключом. это сильно безопасней чем пароль.
3. учебная/боевая машина - нет разницы. сегодня вы научите человека ходить под рутом - ну так же легче/проще, завтра ему сломают сервак и у него и массы других людей будет куча неприятностей. зачем?

[![](https://habrastorage.org/r/w48/getpro/habr/avatars/e65/6a4/381/e656a43814e9087f0393ce30582ed2af.jpg)](https://habr.com/ru/users/imbasoft/)[imbasoft](https://habr.com/ru/users/imbasoft/)[19 мар в 10:59](https://habr.com/ru/articles/725640/#comment_28057976)

Если люди не умеют читать и не видят фразу:

> **Внимание!** Данный шаг снижает безопасность узлов. Выполнять его можно только в учебной среде, когда весь виртуальный стенд работает на вашем локальном компьютере, и виртуальные машины не доступны из сети.

их ничто не спасет.

[![](https://assets.habr.com/habr-web/img/avatars/031.png)](https://habr.com/ru/users/Konwin/)[Konwin](https://habr.com/ru/users/Konwin/)[9 июн в 01:46](https://habr.com/ru/articles/725640/#comment_28413646)

Прежде всего спасибо большое автору за труд. Ну и как маленькое дополнение - если кто решит ставиться через containerd рекомендую обратить внимание, что у него есть зависимость от версии jlibc, что в свою очередь можно обойти использовав версию со статической сборкой (containerd-static-\<VERSION>-\<OS>-\<ARCH>.tar.gz вместо containerd-\<VERSION>-\<OS>-\<ARCH>.tar.gz).

[![](https://habrastorage.org/r/w48/getpro/habr/avatars/970/9fc/e61/9709fce61d19b36b3029c5ed9eab95a6.jpeg)](https://habr.com/ru/users/IronDoor/)[IronDoor](https://habr.com/ru/users/IronDoor/)[19 июл в 12:27](https://habr.com/ru/articles/725640/#comment_28593824)

Небольшое замечание по-поводу настройки демона keepalived на трёх master-нодах. Автор пишет что на всех трёх узлах необходимо указать:

> `state BACKUP`
>
> `priority 100`

Это не совсем верно. При такой конфигурации при "холодном старте" kubernetes-кластера (полное выключение всех нод кластера и последующий запуск) - регулярно будут возникать ситуации, когда виртуальный IP-адрес кластера отвечает на ping, но вот подключение к управляющему интерфейсу кластера с внешнего компьютера (не входящего в кластер) будет невозможно, с получением ошибок TLS-соединения.

Более правильным будет на одной из нод указать **state MASTER** и дать ей больший приоритет, например **priority 101**, после чего нодам кластера будет понятно что в первую очередь кластерный IP необходимо назначать на эту ноду, и только в случае её недоступности - на одну из BACKUP-нод.

Подробнее можно прочитать [в документации](https://github.com/kubernetes/kubeadm/blob/main/docs/ha-considerations.md) по High Availability Considerations.

К слову, из этого также стаёт понятно, почему в связке с keeplived рекомендуется использовать haproxy. Т.к. без него все соединения будет обрабатывать только та нода, которая в данный момент держит IP-адрес кластера и чаще всего это будет нода отмеченая как MASTER, а остальные ноды будут простаивать. Haproxy решает эту проблему за счёт банального round-robin, пересылая каждый следующий запрос на следующую ноду, вне зависимости от того какая из нод сейчас обслуживает кластерный IP-адрес.

Ну и напоследок, в той-же Ubuntu 22.04 вместо интерфейса **ens33** будет **eth0** (но это уже просто маленькое дополнение).

[![](https://habrastorage.org/r/w48/getpro/habr/avatars/e65/6a4/381/e656a43814e9087f0393ce30582ed2af.jpg)](https://habr.com/ru/users/imbasoft/)[imbasoft](https://habr.com/ru/users/imbasoft/)[19 июл в 13:18](https://habr.com/ru/articles/725640/#comment_28593984)

Спасибо за уточнение.

[Зарегистрируйтесь на Хабре](https://habr.com/kek/v1/auth/habrahabr/?back=/ru/articles/725640/\&hl=ru), чтобы оставить комментарий
