Третья статья из цикла ретроспектив об использовании в нашем проекте разных облачных технологий о том как планировали переходить с docker-compose на Docker Swarm написано в первой статье, о том как перешли на Swarm во второй, а в этой статье хочу поделиться опытом и впечатлениями после переноса нашего сервиса на Kubernetes. Сразу скажу, что этот перенос оказался непростым, так как, всё таки, в этих технологиях существенно отличается дизайн управления, а во вторых при переносе открывались новые возможности которые хотелось использовать сразу.

Для сравнения, полный переход с docker-compose на Swarm занял не больше месяца, а при переносе инфраструктуры на k8s прошло уже больше половины года, но ещё нельзя сказать что всё перенесено на все сто процентов.

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

Введение

Изначально, когда мы только планировали этот проект в 2023 году я, как главный архитектор, знал что k8s рекомендуется для оркерстрации контейнеров и слышал о том что Swarm заброшен и т.д.. Но всё равно начал делать на нем, так как погрузится в k8s в одиночку тогда не получилось, слишком много времени уходило даже на небольшие шаги. Тогда я сказал себе, что "эта штука мне не по зубам" и в надежде на свои навыки программирования решил, что нам ведь нужен только оркестратор контейнеров, Docker это стандартная технология, а Swarm идет у него из коробки. То что Swarm такой минималистичный по сравнению с k8s в то время казалось преимуществом, потому что логично предположить, что раз он проще, то его должно быть проще подстроить под свои специфичные нужды. Но если вы соберетесь пойти по этому пути, то вам скорей всего, придется писать свой k8s, потому что для полноценной работы большого облака сто процентов понадобится много чего из того на что Swarm не рассчитан.

Рассказать подробно обо всём о чем тотелось бы в одной статье не удастся, постараюсь пройтись по самым ключевым, на мой взгляд, моментам.

Концептуальная разница

На самом деле, по хорошему, сравнивать эти два инструмента нельзя, по причине того, что у них совершенно разный класс технологии. Это как сравнивать мопед и дом на колесах с встроенным гаражем в котором есть ещё и легковой автомобиль. Возможно слишком гротескное сравнение, но это именно так. Если Swarm создан для того, чтобы можно было запускать реплики приложений не испытывая сложностей с организацией сервисов для обеспечения доступности приложения и распределения запросов между ними, то k8s создан для того, чтобы вывести управление облаком на совершенно иной уровень и максимально покрыть все возможные потребности. Благодаря своей архитектуре, он развился так стремительно и, де факто, стал стандартом в области. Секрет успешной архитектуры k8s обязательно опишу ниже, а пока пойдем по порядку. Пока только оговорюсь, что не стоит считать k8s таким швейцарским ножом который один делает всё сам, это не так. На самом деле, благодаря, как минимум, двум архитектурным решениям он стал домом для множества других сопутствующих технологий из-за своей стабильности и возможности удобной интеграции.

Веб сервер уже не тот что был раньше

В продолжение предыдущего пункта, приведу пример как можно круто настроить веб сервер. Раньше у нас был скрипт, который по запросу создавал файлы конфигурации nginx из шаблонов, проверял их с помощью 'nginx -t' и делал перезагрузку конфига 'nginx -s reload' - стандартная практика. А в инфраструктуре k8s есть такой инструмент как 'ingress nginx' который делает то же самое, но он интегрирован с k8s и с ним не нужно настраивать отдельные соединения он прослушивает такие ресурсы k8s как Ingress и вы просто создаете манифест этого ресурса или создаете его через апи и ingress-nginx проделывает всю работу и у вас уже сервис отвечает по домену. Это позволило удалить множество ненужного кода, теперь эту работу делает проверенный сообществом инструмент, а не наши костыли.

С выпуском сертификатов, такая же история. Раньше были скрипты, которые с помощью certbot выпускали сертификаты Letsencrypt, теперь эту работу выполняет cert-manager, записываем пару строк в тот же самый Ingress и cert-manager не только создает сертификат и следит за его продлением, а и безопасно и удобно хранит их в секретах k8s, так что их при необходимости можно монтировать в любые контейнеры или если нужно можно в любой момент получить информацию о нем через коммандный интерфейс k8s. Соответственно удаляем лишний код и выпуском сертификатов теперь у нас занимается стандартный инструмент.

С DNS сервером такая же красота. В инфраструктуру k8s подключается такой инструмент как external-dns, настраиваем его на работу с нашим DNS сервером и также добавляем одну запись во всё тот же Ingress и external-dns сам добавит нужные DNS записи и увеличит serial, а если понадобится удалить запись с DNS сервера, то external-dns также безопасно это сделает. Ещё одна куча кода уходит в историю, а её место занимает готовое решение.

Таким образом, через один ресурс k8s и интегрированным в него сторонним инструментам мы решаем сразу несколько вопросов стандартным способом.

Управление сервером стало удобнее

Одним из крутых архитектурных решений, о которых я упоминал ранее, является то что k8s для внутри утилиты kubectl использует библиотеку Cobra. Она написана на Golang, имеет хорошую поддержку создания подкоманд, а также встроенную поддержку bash-completion для всех популярных оболочек. То есть, при наборе команд в клавиатуре мы можем нажать Tab и если эта команда есть в спецификации программы, то bash-completion выполнит автодополнение. Благодаря этому инструменту, разработчикам k8s удалось создать по истине одну из лучших, на данный момент, с точки зрения удобства использования, командных утилит.Для понимании важности в необходимости стабильной работе консольной утилиты проведем небольшое погружение в то как сторонние инструменты интегрируются в инфраструктуру k8s через CRD. Вот вы создаете свой CRD ресурс в вашем кластере k8s, запускаете контейнер, который слушает через API k8s события связанные с этим ресурсом и выполняете нужные операции. Пользователь, при помощи kubectl или через API выполняет операции над этим ресурсом и ваш обработчик делает нужную работу. Для многих сценариев уже не придется пилить свою консольную утилиту, управление будет выполняться через стандартные команды kubectl, а настройка через манифесты, так сказать в декларативном стиле.

Главное архитектурное преимущество

Вот мы довольно быстро пришли к тому о чем хотелось поговорить раньше. А именно, в чем же секрет такого головокружительно успеха Kubernetes. Этот секрет - etcd.Когда я пилил свое облако на Docker мне постоянно нехватало такого инструмента для обеспечения стопроцентной согласованности между control-plane и нодами. Я делал постоянные соединения через WS чтобы обеспечивать коммуникацию между узлами, всвязи с этим часто сталкивался с потерей контекста соединения и срывами операций. Естесственно пробовал и брокер сообщений Rabbit и key-value базу Redis, но для такой задачи они не приспособлены работать без костылей. А etcd именно разработан как распределенная key-value база данных, что позволяет на любой ноде поддерживать актуальное состояние кластера без танцев с бубном.Etcd по факту является главным архитектурным решением k8s, которое заложило прочный фундамент в k8s как оркестратора контейнеров. С помощью инструмента etcd Кубернетес стал таким каким мы его знаем, сейчас я объясню этот принцип и вам всё станет понятно. Если рассматривать фундаментально, то можно назвать k8s оберткой над etcd, с точки зрения принципа его работы. Это в частности касается kube-apiserver. Вот смотрите, вместо того чтобы держать соединения между узлами по сети или обмениваться данными через запросы kube использует подписки на etcd, то есть например апи сервер получает информацию от пользователей, сервисов или тот же kubelet, который, в свою очередь, общается с движком контейнерного рантайма меняют данные в etcd и весь кластер практически одновременно узнает об этом. То те сервисы которым необходимо обработать это событие, делают это, а те которых это не касается просто игнорируют.Что понимание этого может нам дать? Это дает нам практически совершенный контроль над кластером. Вместо того чтобы лазить по бесконечным конфигурационным файлам мы можем просто посмотреть необходимый ресурс через kubectl, более того, мы можем его отредактировать с помощью kubectl edit, откроется ваш редактор указанный в env.EDITOR и там действуем как мы редактируем обычные yaml файлы. После сохранения, все связанные ресурсы сразу начнут отрабатывать это событие. Кстати там встроена защита от дурака, если ресурс содержит данные, которые приводят к сбою в работе сервиса, то сервисом подается команда и kube-apiserver восстанавливает последнюю успешную конфигурацию.Как можете видеть, это очень надежная система с точки зрения архитектуры, поэтому многие уверены, что у k8s большое будущее и уверенно связывают свои активы с ним.

Безопасность

С точки зрения защиты данных в k8s тоже уделяется много внимания. Например kube-apiserver имеет встроенную поддержку шифрования ресурсов. Например даже если взломщик получил полный доступ к одной из нод, то он не сможет прочитать секреты (ключи, пароли, сертификаты) потому что они хранятся на диске в зашифрованном виде. Для того чтобы прочитать их ему понадобятся сертификаты kubeconfig для доступа к kubeapi, но они могут быть зашифрованы или храниться на другой ноде. Понятное дело, от root пользователя сложно что-то утаить, но с использованием дополнительных мер можно обеспечить безопасность данных на довольно высоком уровне.При помощи serviceaccounts работает система RBAC, так что можно значительно снизить риски доступа к чувствительным данным, назначая только необходимые для отдельного кейса роли. Огромную роль в обеспечении безопасности играют сетевые настройки. В Кубернетес сетевой уровень управляется только через плагины реализующие интерфейс CNI. Встроенной системы организации сети нет, при установке требуется выбрать и установить сетевой плагин CNI.

Сеть без костылей

Когда впервые устанавливал Кубернетес я удивился, - "как это из коробки нет сетевой связности". Теперь я на деле убедился, что это абсолютно правильное решение и подругому был бы кошмар. Организация сетевого соединения это сложная и низкоуровневая система. Так-же как kubelet использует готовые контейнерные движки поддерживающие интерфейс CRI так и управление сетью делегировано специализированным инструментам.Из всех поддерживаемых сетевых плагинов наиболее мощным является Cilium. С его помощью нам удалось обеспечить сетевую связанность между нодами в разных физических сетях. Но это отдельная история. Здесь вкрадце, своими словами, расскажу что делает сетевой плагин и какие возможности дает в частности Cilium.Когда я пилил на Swarm, то столкнулся с такой проблемой как обеспечение связанности контейнеров, которые не объединены в одну сеть, в docker-compose это делается через правило depends_on что имеет ограниченное действие только на уровне одного compose файла, а в swarm можно соединять контейнеры из разных стеков через networks, но данный механизм имеет существенный недостаток, что при каждом подключении новой сети контейнер перезапускается и это критично, когда вы например динамически добавляете или удаляете сети к контейнеру nginx который является общей точкой доступа ко всем сервисам ноды. Это недопустимо, потому что при добавлении очередного сервиса остальные сервисы будут недоступны извне пока nginx контейнер не перезапустится. Это можно было решить при помощи включения нескольких реплик. Но когда эта задача стояла передо мной ещё не использовали swarm и для docker-compose пришлось пробрасывать в nginx host.docker.internal для доступа с сети хоста, а контейнеры слушали на хосте, каждый свои порты. Тому кто дочитал до этого места не нужно объяснять, что это плохое решение и с точки зрения изоляции и с точки зрения удобства.CNI решает эту проблему, выделяя отдельные IP адреса не только сервисам, но и подам таким образом каждый контейнер изначально имеет статичную точку доступа, а значит ни ему ни другим связанным с ним по сети контейнерам не придется перезапускать свои процессы, чтобы они загрузились в новой конфигурации сети. Вместо этого процессы контейнера имеют неизменное подключение к виртуальной сети, которая направляет его в зависимости от своих внутренних установок.Например в swarm, когда нужно было ограничить определенным контейнерам какие-то соединения. Например запретить из контейнера делать запросы в хост систему по локальнрму адресу хоста, то нужно было создавать правила в iptables, дублировать их на каждой ноде и это было жутко неудобно, а в k8s есть как стандартный механизм NetworkPolicies так и дополнительные политики в зависимости от используемого драйвера. По сетям, сейчас не буду углубляться, чтобы не уйти от основной темы. Поэтому как-нибудь в отдельной статье изложу эту тему более подробно, если накопиться материал на целую не скучную статью.

Обзор инфраструктуры

Ну вот мы наконец добрались до самых интересных фишек k8s. А самые классные штуки, как вы уже могли заметить из данного повествования приходят в Кубернетес из сторонних инструментов, которые интегрируясь в него делают его использование ещё совершеннее.
В первую очередь, нельзя не упомянуть сборщик конфигураций. Самым популярным и рекомендуемым является Helm. С помощью него, управление манифестами k8s как и в целом DevOps сфера выходят на совершенно иной уровень. Рассказывать о нем можно много, но если коротко, то это такая штука, которая условно говоря может одновременно обрабатывать множество файлов манифестов, использовать шаблонизатор для подстановки значений, имеет вспомогательные базовые фукции для вариаций подстановки, возможность написаний своих функций и использование их прямо в файлах манифестов, а также включение скриптовых файлов в ConfigMap для монтирования их в контейнер без использования отдельных томов. Если вы ещё не работали с Helm, обязательно попробуйте, даже в маленьких проектах это облегчит вам жизнь.

Раз уж коснулись темы бекапирования, стоит отметить такой инструмент как Velero, который обеспечивает создание снапшотов кластера. Бекапировать и восстанавливать можно как весь кластер целиком, так и ресурсы по отдельности. Кстати, тяжелые данные такие как volumes Velero с помощью kopia хранит не в исходном виде а реализует механизм дедупликации, разбивая хранимые данные на слои, при каждом новом бекапе добавляются только измененные слои а метадата журналируется для возможности восстановления из слоев в исходный вид данных на момент создания бекапа.

Ещё не могу не упомянуть о таком проекте как CNPG. Последнее время, из реляционных баз данных, я использую в основном Postgres, и на этом проекте я всегда боялся даже думать о том, что когда-то настанет тот момент, когда нужно будет настроить репликацию и подружить всё это дело с облаком. Но бдагодаря CNPG этот процесс прошел совершенно безболезненно и как оказалось для CNPG есть отличный плагин Barman который создает физические бекапы базы с поддержкой WAL журналирования.

Раз вы дочитали до сюда, значит вам действительно интересна эта тема и тогда вам полезно будет знать где находить такие инструменты. Есть некоммерческая организация CNCF, она служит для объединения Cloud Native инструментов в общую среду. Так вот они разрабатыват критерии по которым инструмент можно считать CN и назначают соответсвующим проектам степени соответствия. В качестве ориентира очень полезный фонд, но не всегда содержит самые лучшие решения. Например в CNCF на хорошем счету из раздела сбора метрик расположен Peometeus, однако уже давно известно что Victoria Metrics превосходит его по производительности на порядок, но её там нет вообще. Про VM стоит рассказать более подробно тем более это как раз по теме переноса нашего проекта на k8s.

Сбор метрик как самый тяжелый процесс

О том что собирать метрики контейнеров тяжело, в плане нагрузки на базу данных и апи сервер, я рассказывал во второй статье, которая про переход от докер к сварм. И у самого популярного для этого инструмента в CNCF ландшафте, а именно Prometheus, как сообщают многие пользователи, наблюдаются проблемы с производительностью при разрастании кластера. А проект Victoria Metrics в значительной степени решает эту проблему. Там ребята разрабоиали и свою супер быструю базу данных и свой супер быстрый веб сервер. Зная на своем опыте, как важно чтобы сбор метрик был быстрым, я естесственно выбрал её тем более что она полностью совместима с апи Прометея.

Заключение

Заключительную часть статьи пишу спустя 10 месяцев после того, как написал всё что выше. В то время не нашёл времени дописать статью и опубликовать, а сейчас вернулся к работе над проектом, вспомнил эту статью и решил её опубликовать. Сам немного подзабыл контекст и перечитывая статью, отметил, что без наглядных материалов сложновато увидеть описанные схемы, но если читать внимательно, то понять можно. Если у вас появятся вопросы, пожалуйста пишите в комментариях.