В современном мире Kubernetes уже стал промышленным стандартом оркестрации контейнеров и повсеместно используется во многих инфраструктурах. В нашей компании мы тоже активно используем K8s и нежно любим.
За годы работы с ним у нас было множество различных задач: от самых типовых развёртываний до крайне специфичных конфигураций и установок. Однако недавно в наших стенах решалась задача, которая заставила нас проявить творческий подход, выйти за рамки Kubernetes, а где-то даже немного сжульничать.
Всем привет, на связи Пётр. Сегодня мы рассмотрим автоматическое развёртывание ванильного Kubernetes на Astra Linux через Kubespray + Helm.
Часть первая: знай своего врага
Для начала сформулирируем задачи:
Развернуть Kubernetes и все базовые инфраструктурные элементы последней стабильной версии на Astra Linux Special Edition;
Развёртывание не должно зависеть от типа сервера: инфраструктура должна работать как на железном сервере, изолированном от внешней сети, так и на виртуальном сервере с использованием облачных ресурсов, например, облачного балансировщика нагрузки. Единственная константа в этом уравнении — это Astra Linux;
Предполагается, что развёртывание может проходить в закрытом контуре без прямого доступа в интернет. Из способов получения необходимых артефактов — только Image Registry с необходимыми образами в том же контуре, в котором проходит развёртывание Kubernetes. Сами образы загружаются заранее по нашему запросу через службу безопасности;
Развёртывание должно предполагать переносимость и автоматическое масштабирование: по кнопке можно добавить или убрать ноду из кластера, обновить кластер до актуальной версии Kubernetes на любом Astra Linux. Вариант с ручным развёртыванием Kubernetes не подходит. Также стоит учитывать, что кластер может состоять только из одной ноды. В этом случае она будет совмещать в себе роль и мастера, и воркера.
На первый взгляд задача кажется более чем типовой: собрать максимально универсальную сборку Kubernetes с необходимыми приложениями и раскатать её. Однако, нам предстоит учесть некоторые особенности Astra Linux.
Итак, что из себя представляет эта ОС? Если взглянуть на страницу Astra в Википедии, то мы увидим следующее:
There are two available editions of the OS: the main one is called "Special Edition" and the other one is called "Common Edition". The main differences between the two are the fact that the former is paid, while the latter is free; the former is available for x86-64 architecture, ARM architecture and Elbrus architecture, while the latter is only available for x86-64 architecture; the former has a security certification and provides 3 levels of OS security (which are named after Russian cities and which from the lowest to the highest are: Oryol, Voronezh and Smolensk), while the latter doesn't have the security certification and only provides the lowest level of OS security (Oryol).
Что мы можем из этого понять?
Существует два основных издания Astra Linux: бесплатный Common Edition и платный Special Edition, который предоставляет более защищённую сертифицированную версию. Так как мы будем раскатываться на промышленных контурах, то в рамках данной статьи мы будем рассматривать только Special Edition;
По сути, Astra — это дистрибутив, основанный на Debian. В частности, мы как правило будем работать с дистрибутивом, который основан на Debian 10 Buster. Из этого делаем вывод, что работать нам предстоит с .deb пакетами и пакетным менеджером apt. Это позволяет использовать внешние репозитории Debian для установки приложений, что, кстати, описано в официальной документации Astra и по сути не является жульничеством;
Неприятный факт, который вскрылся в процессе развертывания: расширенная документация с описанием часто встречающихся проблем доступна только в платном виде. При попытке найти ответ на свой даже самый банальный вопрос вы чаще всего будете лицезреть следующее:
Несмотря на то, что Astra основана на привычном всем Debian, система наполнена изменениями, с которыми придется разбираться. При этом вся инфраструктура должна быть настроена так, чтобы не ослабить защиту системы, потому что иначе вся эта затея лишена какого-либо смысла.
Все вводные задачи получены, приступаем к выбору инструментов.
Часть вторая: выбираем оружие
В нашей сборке мы решили использовать следующие утилиты:
Kubespray — инструмент для автоматического развертывания Kubernetes. Под капотом — сборник Ansible-ролей, каждая из которых устанавливает отдельный элемент K8s. Kubespray также может устанавливать дополнительные инструменты, вроде CNI, Storage Provisioner и много чего ещё. Но мы настоятельно не рекомендуем этого делать. Большая часть этих инструментов устанавливается через ванильные манифесты, и версии приложений могут быть далеко не самыми последними (к примеру, Cilium в качестве CNI, который сейчас рекомендуется раскатывать через Helm-чарт). На момент написания статьи Kubespray устанавливает Cilum версии 1.12.0 через манифесты, хотя последняя версия — 1.16.0. Неприятно и то, что по умолчанию Kubespray не поддерживает православный Astra Linux, однако мы внесем несколько изменений в код ролей, чтобы обойти это ограничение;
Cilium — CNI для Kubernetes, который предоставляет инструменты для безопасной работы сети внутри кластера на базе eBPF. Важный нюанс: прежде чем использовать Cilium, проверьте поддержку BGP протокола на ваших серверах. Если её не будет (как например, в некоторых облачных платформах), то Cilium не будет работать корректно. Об этом мы поговорим подробнее в следующей части;
Rancher Local Path Provisioner — Storage Provisioner для Kubernetes. Был выбран как простой Provisioner, который предоставляет тома ReadWriteOnce. Так как наша установка будет проходить на одной ноде, этот вариант является наиболее оптимальным. Это единственный инструмент, который мы будем устанавливать через Kubespray, так как установка Rancher Local Provisioner через манифесты нас вполне устраивает;
Metallb — реализация балансировщика нагрузки для металлических кластеров Kubernetes. Так как мы не знаем, где будет подниматься наш кластер, нужно озаботиться тем, чтобы мы смогли создавать сервисы типа Load Balancer с выделенным IP-адресом в кластерах, которые не запускаются у облачного провайдера. Следовательно, такие сервисы не смогут подключиться к облачному балансировщику нагрузки и получить свой IP, в таком случае нам и поможет Metallb.
Ingress Nginx — это контроллер Ingress для Kubernetes, использующий NGINX в качестве обратного прокси-сервера;
CertManager — стандартный инструмент для выпуска SSL-сертификатов. Если наша установка будет происходить в закрытом контуре, то CertManager будет выпускать самоподписанные сертификаты для сервисов, которые требуют защищённого соединения. Например, kubernetes-dashboard может работать только в защищённом режиме;
Vector — инструмент для сбора логов, быстрое и легковесное решение;
Loki — инструмент для агрегации логов, использовать будем Single Binary инсталляцию. Был выбран как универсальное решение: Elasticsearch выигрывает у Loki, когда речь идет о кластеризации и глубине хранения данных. Но мы решили, что в большинстве случаев нам хватит Loki: он легче, проще и менее прожорлив, чем Elasticsearch. Плюс у нас будет поднята Grafana в рамках KubePrometheusStack, и в ней же мы будем выстраивать дашборды логирования. Тем самым, мы сохраняем эффективность логирования и при этом не множим сущности;
KubePrometheusStack — классический набор для мониторинга системы, состоящий из Prometheus + Grafana + AlertManager.
Всё указанное выше мы будем устанавливать через Helm-чарты, которые будут передаваться в закрытый контур и располагаться в системе локально.
Инструменты выбраны, теперь переходим непосредственно к развёртыванию.
Часть третья: спускаемся в преисподнюю
Для начала мы решили проверить официальную информацию и попробовать установить Kubepray на Astra Linux без каких-либо изменений. Но сперва мы взглянули на параметры ядра и нас ждала первая неприятность: часть параметров была изменена. Это проблема, потому что, например, если параметр sysctl net.ipv4.ip_forward на хосте Linux установлен в значение 0 (отключено), то пересылка пакетов IPv4 отключается. В результате на узлах Kubernetes работа сети в подах нарушается, причем в разных форматах: из подов могут быть недоступны IP других подов или внешняя сеть.
Мы не будем подробно расписывать все параметры, которые могут нарушить работу сети Kubernetes или других его элементов. С ними вы можете ознакомиться в других материалах, например, в блоге команды Teleport есть статья про troubleshooting сети через параметры ядра. Здесь мы приведём только итоговый список параметров в созданном файле /etc/sysctl.d/kubernetes.conf:
net.ipv4.ip_forward=1
net.ipv4.ip_local_reserved_ports=30000-32767
net.bridge.bridge-nf-call-iptables=1
net.bridge.bridge-nf-call-arptables=1
net.bridge.bridge-nf-call-ip6tables=1
net.ipv4.conf.default.rp_filter = 0
net.ipv4.conf.all.rp_filter = 0
Также мы удалили файл /etc/sysctl.d/999-cve-2019-14899.conf, потому что в нём хранились правила, которые были изменены выше:
net.ipv4.conf.default.rp_filter = 0
С ядром разобрались, переходим к Kubespray. Скачиваем его из официального GitHub репозитория, выбирая последнюю релизную, не master, ветку:
git clone -b release-2.26 git@github.com:kubernetes-sigs/kubespray.git
Затем мы вспоминаем, что вообще-то у нас может не быть интернета в контуре и будет необходима offline-установка. Проблема с образами решается двумя способами. Первый: необходимо поднять локальное registry на ваш вкус и залить в него все образы, которые нужны для установки (или, что будет чаще, залить образы в уже имеющийся в контуре registry). Второй: поднять прокси сервер, через который эти образы будут скачиваться.
С заливкой образов в registry проблем возникнуть не должно. Идём дальше: в файле kubespray/roles/kubespray-defaults/defaults/main/download.yml мы добавляем путь до нашей прокси либо registry в переменные типа *_image_repo.
Бинарные файлы Kubernetes тоже не проблема. Их можно скачать или локально, или в хранилище артефактов вроде Nexus. Мы решили использовать первый вариант, чтобы упростить жизнь для тех, у кого в системе подобного хранилища нет.
Переходим в директорию kubespray/contrib/offline и запускаем скрипт generate_list.sh. На выходе мы получим список всех файлов, которые нужны при установке. Скачиваем полученные файлы в любую директорию, например /var/www/kubernetes/offline
и передаём на рабочий сервер через scp, флешку, дискету или любым другим неприятным способом.
Остаётся дело за малым — научить наш Kubespray забирать файлы локально. Можем пойти по классическому пути, описанному в документации, а можем пойти использовать извращённый вариант: поднять локальный веб-сервер и обращаться за файлами напрямую к localhost. Для этого необходимо установить Nginx и создать следующий здоровый location нездорового человека:
server {
listen 80;
server_name localhost;
access_log logs/localhost.access.log main;
location / {
root /var/www/kubernetes/offline;
index index.html index.htm index.php;
}
}
Далее мы идём в файл kubespray/roles/kubespray-defaults/defaults/main/download.yml и меняем в нём пути до сайтов на http://127.0.0.1 в переменных формата *_url.
Хорошо, теперь двигаемся по стандартному пути: на все хосты необходимо поставить Python не ниже версии 3.10. Далее заходим в директорию с kubespray и устанавливаем необходимые зависимости для Python:
python3 -m pip -r requirements.txt
Не забываем настроить установку дополнительных элементов в kubespray/inventory/sample/group_vars/k8s_cluster/addons.yml, нам необходимо включить Helm и Rancher Local Path Provisioner:
helm_enabled: true
local_path_provisioner_enabled: true
local_path_provisioner_namespace: "local-path-storage"
local_path_provisioner_storage_class: "local-path"
local_path_provisioner_reclaim_policy: Delete
local_path_provisioner_claim_root: /opt/local-path-provisioner/
local_path_provisioner_debug: false
local_path_provisioner_image_repo: "{{ docker_image_repo }}/rancher/local-path-provisioner"
local_path_provisioner_image_tag: "v0.0.24"
local_path_provisioner_helper_image_repo: "busybox"
local_path_provisioner_helper_image_tag: "latest"
Так как мы будем использовать MetalLB, настраиваем параметры arp_ignore в файле kubespray/inventory/sample/group_vars/k8s_cluster/k8s-cluster.yml, чтобы избежать ответа на ARP-запросы из интерфейса kube-ipvs0 и MetalLB работал:
kube_proxy_strict_arp: true
Здесь же меняем CNI на желаемый, если вы хотите получить рабочий сетевой плагин из коробки:
kube_network_plugin: calico
Если же вы хотите поставить плагин отдельно после установки, то установите это значение в “cni”:
kube_network_plugin: cni
Создаем инвентарный файл для дальнейшего развёртывания:
cp -rfp inventory/sample inventory/mycluster (создаем директорию для inventory файла из дефолтной директории)
declare -a IPS=(10.10.1.3) (объявляем IP адреса серверов)
CONFIG_FILE=inventory/mycluster/hosts.yaml python3 contrib/inventory_builder/inventory.py ${IPS[@]} (создаем inventory файл из объявленных адресов)
Важный нюанс работы с Kubespray: при указании IP адресов нельзя использовать 127.0.0.1, даже если у нас однонодовая инсталяция. Kubespray использует адреса из declare не только как пункты назначения развертывания роли, но и как часть настроек Kubernetes для определения хостов. Если использовать localhost как адрес инсталляции, то эту ноду банально не смогут увидеть остальные ноды, если впоследствии они появятся.
И наконец развертыванием кластер через роль:
ansible-playbook -i inventory/mycluster/hosts.yaml --become --become-user=root cluster.yml
И … получаем ошибку OS types are not supported:
Неприятно, но ожидаемо. Можно выставить параметр allow_unsupported_distribution_setup: true в kubespray/inventory/sample/group_vars/all/all.yml и разрешить устанавливать Kubespray на незнакомых системах. Но это непредсказуемая галочка напугала нас, и мы решили явно указать Kubespray, что делать.
А сделаем мы это так: зная, что Astra — это система, которая фактически основана на Debian и работает на том же пакетном менеджере, мы можем заставить Kubespray думать, что Astra Linux — это Debian. Так и сделаем, поехали.
Внезапно начнём с самого Ansible:
Получаем путь до установленных пакетов Python в директиве Location:
python3.10 -m pip show pip
Name: pip
Version: 24.0
Summary: The PyPA recommended tool for installing Python packages.
Home-page:
Author:
Author-email: The pip developers <distutils-sig@python.org>
License: MIT
Location: /usr/local/lib/python3.10/site-packages
Requires:
Required-by:
Переходим в файл distribution.py с описанием дистрибутивов:
/usr/local/lib/python3.10/site-packages/ansible/modules_utils/facts/system/distribution.py
Находим параметр OS_FAMILY_MAP и нагло добавляем в него Astra Linux:
# keep keys in sync with Conditionals page of docs
OS_FAMILY_MAP = {'RedHat': ['RedHat', 'RHEL', 'Fedora', 'CentOS', 'Scientific', 'SLC', 'Ascendos', 'CloudLinux', 'PSBM', 'OracleLinux', 'OVS', 'OEL', 'Amazon', 'Virtuozzo', 'XenServer', 'Alibaba', 'EulerOS', 'openEuler', 'AlmaLinux', 'Rocky', 'TencentOS', 'EuroLinux', 'Kylin Linux Advanced Server'],
'Debian': ['Debian', 'Ubuntu', 'Raspbian', 'Neon', 'KDE neon', 'Linux Mint', 'SteamOS', 'Devuan', 'Kali', 'Cumulus Linux', 'Pop!_OS', 'Parrot', 'Pardus GNU/Linux', 'Uos', 'Deepin', 'OSMC', 'Astra Linux'],
Далее переходим к самому Kubespray:
Находим файл kubespray/roles/container-engine/containerd/defaults/main.yml и вносим в список containerd_supported_distributions дистрибутив Astra Linux:
containerd_supported_distributions:
- 'RedHat'
- 'CentOS'
- 'Fedora'
- 'Ubuntu'
- 'Debian'
- 'Flatcar'
- 'Flatcar Container Linux by Kinvolk'
- 'Suse'
- 'openSUSE Leap'
- 'openSUSE Tumbleweed'
- 'ClearLinux'
- 'OracleLinux'
- 'AlmaLinux'
- 'Rocky'
- 'Amazon'
- 'Kylin Linux Advanced Server'
- 'UnionTech'
- 'UniontechOS'
- 'openEuler'
- 'Astra Linux'
Находим файл kubespray/roles/kubernetes/preinstall/defaults/main.yml и вносим в список debian_os_family_extensions дистрибутив Astra Linux:
supported_os_distributions:
- 'RedHat'
- 'CentOS'
- 'Fedora'
- 'Ubuntu'
- 'Debian'
- 'Flatcar'
- 'Flatcar Container Linux by Kinvolk'
- 'Suse'
- 'openSUSE Leap'
- 'openSUSE Tumbleweed'
- 'ClearLinux'
- 'OracleLinux'
- 'AlmaLinux'
- 'Rocky'
- 'Amazon'
- 'Kylin Linux Advanced Server'
- 'UnionTech'
- 'UniontechOS'
- 'openEuler'
- 'Astra Linux'
Находим файл kubespray/roles/kubernetes/preinstall/defaults/main.yml и вносим в список supported_distribution точную версию нашего дистрибутива Astra Linux:
debian_os_family_extensions:
- "Astra Linux 1.7.5"
Поздравляю, мы беспардонно внесли новую операционную систему.
Однако после запуска мы столкнулись с новой проблемой:
TASK [container-engine/containerd : Containerd | Ensure containerd is started and enabled] ***fatal: [k8s]: FAILED! => {"changed": false, "msg": "failure 1 during daemon-reload: Failed to reload daemon: Access denied\n"}
NO MORE HOSTS LEFT *************************************************************
RUNNING HANDLER [kubernetes/preinstall : Preinstall | reload kubelet] **********
fatal: [slt-test-pac]: FAILED! => {"changed": false, "msg": "Unable to start service kubelet: Failed to start kubelet.service: Access denied\nSee system logs and 'systemctl status kubelet.service' for details.\n"}
У Kubespray, который был запущен через удаленный сервер, не хватало прав на перезапуск systemd. Все попытки увеличить привилегии не увенчались успехом. И тут мы вспомнили про флаг для команды ansible-playbook --connection=local, который позволяет запустить ansible-роль через локальное подключение и формально через локального пользователя. Фактически это тоже некий обман системы, однако, так как конкретно в нашем случае задача состояла в развертывании однонодового решения, оно нам подошло. Не забываем, что даже в таком случае мы должны использовать приватный IP-адрес из внутренней сети, а не localhost!
После этого всё запустилось корректно, осталось только проверить несколько моментов. В первую очередь необходимо выполнить команду kubectl get nodes
и убедиться, что все ноды являются частью кластера. Затем, если установка идет в контуре с упором на безопасность, то надо проверить, что образы скачаны не из сети, а из вашего registry:
kubectl describe pod -A | grep "Image:" | grep -v <адрес_registry> | sort | uniq | wc -l
Вы должны получить в ответ 0.
Напоследок поговорим о забавном нюансе установки Kubespray через официальный Docker image. На главной странице репозитория Kubespray есть рекомендация устанавливать Kubernetes не через репозиторий, а через заранее собранный docker образ с Kubespray и всеми необходимыми библиотеками. По сути всё, что нам нужно, это скачать репозиторий для получения конфигов, спулить необходимый образ и запустить его с конфигами и SSH-ключами серверов:
git checkout v2.26.0
docker pull quay.io/kubespray/kubespray:v2.26.0
docker run --rm -it --mount type=bind,source="$(pwd)"/inventory/sample,dst=/inventory \ --mount type=bind,source="${HOME}"/.ssh/id_rsa,dst=/root/.ssh/id_rsa \ quay.io/kubespray/kubespray:v2.26.0 bash#
(Внутри контейнера выполянем) playbooks:ansible-playbook -i /inventory/inventory.ini --private-key /root/.ssh/id_rsa cluster.yml
Выглядит удобно. Но есть нюанс, про который частенько забывают: при установке Kubespray один из начальных шагов — это удаление всех container runtime с серверов. Поэтому, если вы решили выбрать этот путь установки Kubernetes, то вам необходимо раскатываться с сервера, который не входит в будущий кластер K8s. Иначе вы окажетесь в ситуации, при которой (по абсолютно неведомой причине) ваша установка будет заканчиваться в самом начале, а контейнер с Kubespray — загадочно пропадать с сервера.
На этом первая часть закончена. Мы разобрали автоматизированное поднятие Kubernetes через пропатченный Kubespray. В следующей статье разберём установку основных элементов системы через локальные Helm-чарты и пострадаем на настройке сети. Увидимся!
Кстати, подписывайтесь на наши соц.сети: Telegram, vc.ru и YouTube. Везде публикуется разный интересный контент.