Гайд для новичков по установке Kubernetes

pfnjdeifoiye7rbu8ogfq2lswvi.jpeg
© кадр из к/ф «Пираты Карибского моря»

С чего начинается практическое освоение любой системы? Правильно, с установки. Данный гайд является компиляцией из народной мудрости, официальной документации, а также собственного опыта и призван помочь новичкам разобраться с тем, как же все таки устанавливать Kubernetes.

Мы потренируемся ставить как вырожденный кластер «все-в-одном», состоящий только из одного узла, так и настоящий высокодоступный (high available) кластер с полным резервированием. В процессе работы мы рассмотрим применение различных контейнерных движков (Container Runtimes): cri-o, containerd, связки Docker + cri-dockerd plugin. Кроме этого, потренируемся настраивать отказоустойчивый балансировщик нагрузки на базе keepalived и haproxy.

Весь процесс установки будет детальным образом прокомментирован и разложен по шагам, а в реперных точках мы будем делать снимки состояния виртуальных машин (snapshots), что позволит рассмотреть различные варианты установки без необходимости делать одну и ту же работу по несколько раз.

Оглавление


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


Одним из золотых правил построения надежной и безопасной информационной инфраструктуры является максимальная изоляция ее компонентов друг от друга. Хорошим признаком достижения этой цели можно считать кейс, когда один сервер выполняет только одну основную функцию. Например, контроллер Active Directory — это только контроллер Active Directory, а не файловый или Интернет-прокси сервер в придачу.

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

Следующим этапом развития стала контейнеризация. Если виртуальная машина — это почти отдельный компьютер со своим BIOS, операционной системой, драйверами и так далее, то контейнер — это изолированная часть операционной системы узла с минимумом прикладного ПО, обеспечивающего его запуск. В результате этого контейнеры занимают мало места, быстро стартуют и существенно минимизируют другие сопутствующие расходы.

Контейнеры идеологически очень похожи на портативный (portable) софт. Они содержат в себе лишь те файлы, что нужны для запуска конкретной программы. Однако, в отличии от обычных портативных программ, технологии контейнеризации отделяют контейнеры друг от друга и от хозяйской (host) операционной системы, позволяя каждому контейнеру считать себя отдельным компьютером, что очень похоже на работу виртуальных машин.

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

Контейнеризация получила широкую известность вместе с Docker. Эта система сделала работу с контейнерами чрезвычайно простой и доступной. Она хорошо подходит для управления контейнерами в небольших проектах, но для серьезных задач, когда нужно оперировать большим числом контейнеров, организовывать отказоустойчивые конфигурации, гибко управлять вычислительными ресурсами, ее возможностей недостаточно, и здесь на сцену выходит герой нашей статьи — система управления/оркестрации (orchestration) контейнерами Kubernetes.

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


По факту Kubernetes — очень гибкое решение, которое может быть настроено бесчисленным количеством способов. Это, конечно, очень здорово для работы, но является сущим адом при изучении. Поэтому здесь мы не будем рассматривать все возможные варианты построения системы, а ограничимся лишь базовым, достаточным для её первичного освоения.

Kubernetes кластер состоит из двух типов узлов: управляющих и рабочих.
Управляющие узлы, как видно из названия, предназначены для управления кластером. Они отдают команды рабочим узлам на запуск и остановку рабочих нагрузок (workloads), отслеживают состояние кластера, перераспределяют задачи в случае отказов и совершают множество других управленческих действий. Рабочие узлы — это пчелки, выполняющие всю полезную работу, ради которой функционирует кластер.

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

Типовой состав ПО рабочего узла включает в себя (Рисунок 1):

  1. Служебные компоненты Kubernetes: агент управления узлом kubelet, узловой прокси kube-proxy
  2. Сетевой плагин (Container Network Interface, CNI plugin).
  3. Контейнерный движок: cri-o, containerd или Docker + cri-dockerd plugin.
  4. Рабочие нагрузки (workloads), то есть сами контейнеры, из-за которых все и затевалось. Однако, здесь важно уточнить один существенный момент — минимальной единицей управления рабочей нагрузкой в Kuberbetes является »под» (pod), состоящий из одного (как правило) или нескольких контейнеров.


o8uvbrudquu8ifim7_cl4tybr08.jpeg
Рисунок 1

Состав ПО управляющих узлов (Рисунок 2) дополнительно включает в себя:

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


ydxpbehkmx4bfaay6bwsk2t4uf0.jpeg
Рисунок 2

Важно отметить, что практически все компоненты, кроме kubelet и kube-proxy, могут функционировать в Kubernetes в качестве рабочих нагрузок. Другими словами, Kubernetes может управлять сам собой.

Читая о Kubernetes, часто можно услышать фразу: «Kubernetes — это просто: всего 5 бинарников». Под пятью бинарниками обычно понимают: kubelet, kubeproxy, kube-scheduler, kube-controller-manager, kube-apiserver. При этом почему-то всегда умалчивается о других обязательных компонентах кластера, хотя бы о том же etcd, так что Kubernetes — это далеко не просто и далеко не пять бинарников.

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


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

  • kind (Kubernetes in Docker). Кластер, функционирующий на локальном компьютере «внутри» Docker.
  • minikube. Кластер в одной утилите для запуска на локальном компьютере.
  • Docker desktop — дистрибутив Docker для запуска на локальном компьютере с возможностью включить Kubernetes одной галкой — наверное, самый дружественный вариант для людей, которые не представляют, что такое Kubernetes, и хотят просто на него глянуть.


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

  • Развертывание с помощью kubespay — набора скриптов (playbook«s) для системы управления инфраструктурой Ansible.
  • Полностью ручное развёртывание «hard way». Гайд на английском можно почитать тут, на русском тут.
  • Развертывание с помощью утилиты kubeadm. Это то, чем мы будем заниматься далее.


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


Согласно официальной документации, к машинам, на которых разворачивается Kubernetes, выдвигаются следующие требования:

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


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

  • полная сетевая связанность узлов;
  • на каждом узле должны быть уникальные:
    • имена узлов (проверка с помощью команды »hostname»),
    • MAC-адреса (проверка с помощью команды »ip link»),
    • параметр product_uuid, являющийся уникальным идентификатором виртуальной машины (проверка с помощью команды »cat /sys/class/dmi/id/product_uui»).


В ходе экспериментов выяснились, что дополнительно к этому узлы должны иметь статические IP-адреса и зарегистрированные DNS имена, что требуется для автоматического выпуска сертификатов во время работы kubeadm.

Минимальное количество рабочих узлов для схемы с резервированием — 2. Логичное требование: один сломался другой на замену. Минимальное количество управляющих узлов для схемы с резервирование — 3. Данное странное требование продиктовано официальной документацией: в Kubernetes должно быть нечетное количество управляющих улов. Минимальное число нечетное число для обеспечения избыточности — 3.

Таким образом, наш виртуальный стенд (Рисунок 3) будет состоять из пяти виртуальных машин, находящихся в одноранговой сети, имеющей выход в Интернет, с помощью виртуального маршрутизатора, реализующего NAT.

qxepf0_0aptnj7ccneox4nsm6te.jpeg
Рисунок 3

Виртуальные машины будут работать под управлением ОС Debian 11×64, установленной с минимальным количеством пакетов. Все необходимое будем явно доставлять.

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


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

6. Решение


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

В начальной точке у нас должно быть 5 свежеустановленных виртуальных машин, работающих под ОС Debian 11×64. Машины должны быть в виртуальной сети с настройками 172.30.0.0/24. Шлюз по умолчанию — 172.30.0.2, он же DHCP, DNS и NAT сервер. Все машины должны иметь доступ в Интернет. Если все так, то делаем снимок состояния виртуальных машин и назовем его «START».

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


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


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


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

hostnamectl set-hostname node1.internal

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

hostnamectl set-hostname node2.internal

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

hostnamectl set-hostname node3.internal

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

hostnamectl set-hostname node4.internal

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

hostnamectl set-hostname node5.internal


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


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

nameserver 172.30.0.2


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


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

На всех узлах выполним следующую команду:

Скрытый текст
cat > /etc/hosts <


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».

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


Вариант A. Самый простой и быстрый вариант — это установить все и везде, не зависимо от того, нужно оно там или нет.

На всех узлах выполним команду:

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

Вариант B. Более трудоемкий вариант — это на каждом узле поставить только то, что нужно.

На всех узлах выполним команду:

apt install -y curl wget gnupg sudo iptables

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

apt install -y tmux

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

apt install -y keepalived haproxy


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


Согласно официальной документации, для работы Kubernetes необходимо разрешить маршрутизацию IPv4 трафика, настроить возможность iptables видеть трафик, передаваемый в режиме моста, а также отключить файлы подкачки.

На всех узлах выполним команды:

Скрытый текст
# Настройка автозагрузки и запуск модуля ядра br_netfilter и overlay
cat < /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

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


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


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


При выполнении этой работы нам придется часто подключаться по 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


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


Еще одним способом упростить себе жизнь будет использование программы tmux для совершения одинаковых действий (например, установки программ) на нескольких узлах. Магия работает за счет того, что tmux позволяет одновременно открыть несколько окон, а затем включить синхронизацию, и консольные команды, введенные в одном окне, будут автоматически транслироваться во все другие окна.

В частности, для проведения одинаковых работ на всех узлах стенда (например, как в следующем шаге) необходимо сделать следующее:

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


По окончании работ:

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


Программа tmux может управляться как горячими клавишами, так и текстовыми командами. Принцип управления горячими клавишами заключается в нажатии префикса Ctrl-B (одновременно Ctrl и кнопку «B»), а затем кнопки соответствующей команды. Например, — разделит окно по вертикали. Аналогичная ей текстовая команда «split-window -h» сделает тоже самое. Для перехода в командный режим необходимо нажать. Перечень всех горячих клавиш можно узнать, нажав .

С теорией разобрались, рассмотрим пример использования.

Пример проверки доступности Интернет одновременно на всех узлах кластера
  1. На узле node1 запускаем tmux (Рисунок 4).
    h-mef3fiowhetcofj9dphs_a4a0.jpeg
    Рисунок 4
  2. Нажимая 3 раза, разделяем окно на 3 горизонтальных окна (Рисунок 5).
    8e6uldo7hvqrrs7bwugc9jia42i.jpeg
    Рисунок 5
  3. Нажимая разделяем нижнее окно на 3 вертикальных окна (Рисунок 6).
    p91hirlk959puohpuidx7coy-wq.jpeg
    Рисунок 6
  4. Используя навигацию между окнами с помощью , а также возможности изменения размера окон , можно добиться более красивого размера окон (Рисунок 7).
    _bhwtpesjgajrgs-buaw0mlym0u.jpeg
    Рисунок 7

    Если не хочется мудрить с размерами окон, можно просто нажать , тогда появится окно, равномерно разделенное на 5 горизонтальных частей (Рисунок 8).
    yopdq2auouty_8uyzapdkof4zdg.jpeg
    Рисунок 8

  5. Поочередно в каждом окне, кроме самого верхнего, с помощью команды »ssh root@nodeX.internal», где X — номер узла (например, node4.internal), подключимся ко всем узлам кластера (Рисунок 9).
    t5rriekv-wwa6tzw4ufpdeaissg.jpeg
    Рисунок 9
    Примечание. Для отчистки экрана от лишнего вывода можно воспользоваться командой »clear».

  6. Теперь перейдем в самое верхнее окно, в этом примере оно будет у нас командным. Затем перейдем в режим ввода команд, нажав и в командной строке введем команду »set synchronize-panes on» (Рисунок 10)
    5fqdpwtdggqeumwukraoerfwsy0.jpeg
    Рисунок 10
  7. Теперь введем команду »ping 8.8.8.8 -c 1», и она одновременно выполнится на всех панелях (Рисунок 11)
    z7knhazfdi5x_9yqggutyq-r7vq.jpeg
    Рисунок 11
  8. Для завершения режима синхронизации необходимо в командном режиме tmux ввести команду »set synchronize-panes off». Закрытие панелей производится клавишами . Другой вариант в режиме активной синхронизации просто набрать команду »exit».


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


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


kubectl — основная утилита командной строки для управления кластером Kubernetes, kubeadm — утилита для развертывания кластера Kubernetes. Установка данных утилит осуществляется в соответствии с официальным руководством.

На всех узлах выполним следующие команды:

Скрытый текст
# Настройка deb-репозитория Kubernetes
curl -fsSLo /etc/apt/trusted.gpg.d/kubernetes-archive-keyring.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/ kubernetes-xenial main" | tee /etc/apt/sources.list.d/kubernetes.list

# Обновление перечня доступных пакетов
apt update

# Установка пакетов kubeadm и kubectl
apt install -y kubeadm kubectl


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

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


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

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

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


Установка по осуществляется по официальной документации.

На всех узлах выполним следующие команды:

Скрытый текст
export VERSION=1.26
export OS=Debian_11

echo "deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/ /" > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
echo "deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/$VERSION/$OS/ /" > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable:cri-o:$VERSION.list

curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/$VERSION/$OS/Release.key | apt-key add -
curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/Release.key | apt-key add -

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

mkdir /var/lib/crio

systemctl daemon-reload
systemctl enable --now crio

6.2.А.1. Проверка доступности сокета cri-o
Для тестов мы будем использовать утилиту crictl, которая автоматически установилась вместе с kubeadm.

На всех узлах запустим команду:

Скрытый текст
crictl --runtime-endpoint unix:///var/run/crio/crio.sock version

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


6.2.А.2. Проверка запуска контейнеров с помощью cri-o
Кроме доступности сокета мы можем проверить фактическую возможность запуска контейнеров.

На всех узлах запустим команды:

Скрытый текст
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».

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


Откатываемся к состоянию виртуальных машин «HOST IS PREPARED». Установка осуществляется в соответствии с официальным руководством.

На всех узлах выполним команды:

Скрытый текст
# Установка containerd
wget https://github.com/containerd/containerd/releases/download/v1.7.0/containerd-1.7.0-linux-amd64.tar.gz
tar Cxzvf /usr/local containerd-1.7.0-linux-amd64.tar.gz
rm containerd-1.7.0-linux-amd64.tar.gz

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

# Настройка cgroup драйвера
sed -i 's/SystemdCgroup \= false/SystemdCgroup \= true/g' /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.1.4/runc.amd64
install -m 755 runc.amd64 /usr/local/sbin/runc
rm runc.amd64

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

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

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

Заголовок спойлера
crictl --runtime-endpoint unix:///var/run/containerd/containerd.sock version

## Ожидаемый результат:
# Version:  0.1.0
# RuntimeName:  containerd
# RuntimeVersion:  v1.7.0
# 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».

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


Откатываемся к состоянию виртуальных машин «HOST IS PREPARED». В начале следует установить Docker. Для этого воспользуется официальным руководством.

На всех узлах выполним следующие команды:

Скрытый текст
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/trusted.gpg.d/docker.gpg

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/trusted.gpg.d/docker.gpg] https://download.docker.com/linux/debian \
  $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null

apt update

apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin


После установки Docker установим плагин cri-dockerd. Делать это будем по размещённому в сети гайду.

На всех узлах выполним следующие команды:

Скрытый текст
wget https://github.com/Mirantis/cri-dockerd/releases/download/v0.3.1/cri-dockerd-0.3.1.amd64.tgz
tar xvf cri-dockerd-0.3.1.amd64.tgz
mv cri-dockerd/cri-dockerd /usr/local/bin/

wget https://raw.githubusercontent.com/Mirantis/cri-dockerd/master/packaging/systemd/cri-docker.service
wget https://raw.githubusercontent.com/Mirantis/cri-dockerd/master/packaging/systemd/cri-docker.socket

mv cri-docker.socket cri-docker.service /etc/systemd/system/
sed -i -e 's,/usr/bin/cri-dockerd,/usr/local/bin/cri-dockerd,' /etc/systemd/system/cri-docker.service

systemctl daemon-reload
systemctl enable cri-docker.service
systemctl enable --now cri-docker.socket

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

Скрытый текст
crictl --runtime-endpoint unix:///var/run/cri-dockerd.sock version

## Ожидаемый результат:
# Version:  0.1.0
# RuntimeName:  docker
# RuntimeVersion:  23.0.1
# 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».

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

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


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


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

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


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


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

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


Здесь в параметре --pod-network-cidr мы явно указываем IP-подсеть, которая будет использоваться »подами». Конкретный диапазон 10.244.0.0/16 выбран таким образом, чтобы совпадать с диапазоном по умолчанию для сетевого плагина flannel, который мы будем устанавливать чуть позже.

Для варианта с Docker + cri-dockerd рассмотренную ранее команду нужно чуть-чуть изменить:

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 требуется явно указать, какой из них использовать.

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


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

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


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

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


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

На node1 выполним команду:

kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml


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


Для управления тем, какая нагрузка (рабочая / управляющая) может выполняться на узле, используется механизм «заражения и толерантности» (taint and toleration). Если говорить простыми словами, то это механизм меток. С помощью «заражений» узлу присваиваются определенные метки. Механизм толерантности показывает, с какими метками »под» готов мириться, а с какими он работать не будет. По умолчанию на управляющих узлах Kubernetes устанавливается метка «node-role.kubernetes.io/control-plane». Рабочие »поды» без дополнительный настроек на узлах с подобной меткой запускаться не будут.

Для того чтобы разрешить выполнение рабочих »подов» на управляющем узле, с последнего нужно снять метку node-role.kubernetes.io/control-plane, что может быть сделано командой:

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


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


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

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


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

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


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

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


Решение первой задачи основывается на логике управления кластером. В отличии от Docker, здесь администратор не запускает контейнеры явным образом. Вместо этого он описывает желаемое состояние (desired state) кластера, в котором указывает какие »поды» должны быть запущены. Система управления кластером сравнивает текущее состояние с желаемым. Если эти состояния различаются, то система выполняет действия по приведению желаемого к текущему с учетом доступных ресурсов.

Поясним на примере. Администратор во время конфигурации кластера указал, что желает видеть запущенными два »пода» с контейнерами nginx. После получения воли высшего существа система управления кластером смотрит — »подов» нет, nginx нет — не дела, скачивает nginx из репозитория, настраивает по конфигурации админа »поды», затем запускает их. Вот теперь желаемое = текущее. Если далее по каким-то мистическим причинам узел кластера, на котором крутились »поды», откажет, то система управления увидит, что текущее состояние отличается от желаемого, и автоматически запустит недостающие »поды» на другом доступном узле кластера, вновь делая желаемое = текущее. Вот таким интересным способом и достигается отказоустойчивость выполнения рабочих нагрузок.

Решение второй задачи разбивается на подзадачи:

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


Первая задача решается путем применения системы распределенного хранения данных etcd. Данная система хранит конфигураци

© Habrahabr.ru