Часть 0: Вступление

Всем привет, меня зовут Игорь Шишкин, я руководитель команды R&D облачных сервисов Рунити. Сегодня я хочу устроить вам небольшую экскурсию по тому, что из себя представляет наш сервис KaaS и как он устроен, ведь все мы знаем и любим Kubernetes.

В 2024 это де-факто стандартный оркестратор контейнеров, обеспечивающий работоспособность миллионов сервисов по всему миру и делающий возможным доставку десятков, а то и сотен релизов в день до конечного потребителя. Для нас в Рунити этот инструмент является особенным. Он помогает нам делать сервисы простыми и надежными, как внутри компании, так и для наших клиентов, мы очень давно эксплуатируем его в продакшне. И вот настал момент поделиться с Вами деталями реализации. 

Но прежде чем мы начнем, давайте немного определимся с терминологией. Хитрость тут в том, что KaaS внутри компании — это не просто еще одна отдельно стоящая услуга, это целая платформа. На ней мы разворачиваем наши внутренние приложения, на ней же мы строим архитектуру наших будущих сервисов, и именно эту платформу с незначительными доработками мы предоставляем другим командам под названием Kubernetes as a Service (KaaS) во внутренних облаках компании и вам — нашим клиентам Облака Рег.Ру. Аналогичная история и с Openstack: для нас это стандартный провайдер инфраструктуры по модели IaaS, который мы применяем везде, где нам нужно иметь возможность управлять сетью и вычислительными ресурсами через API.

Часть 1: Компоненты Kubernetes

Чтобы описать, как сделать Kubernetes как сервис, нужно знать, как устроен Kubernetes и как выглядит его деплоймент, хотя бы в базовом варианте. В общем случае кластер Kubernetes состоит из двух типов нод: Worker node’ы и ноды Control Plane (они же мастер-ноды, они же просто Control Plane).

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

Вторые — это более интересная штука, на них размещаются уникальные компоненты: apiserver, scheduler и controller-manager:

Любой сферический кластер Kubernetes в вакууме
Любой сферический кластер Kubernetes в вакууме

В общем, любой сферический кластер Кубера в вакууме можно изобразить вот таким образом. При этом CRI — это интерфейс рантайма, зачастую он представлен containerd; CNI — интерфейс для реализации сетевых функций (связности для Pod- и Service-сетей); CSI — интерфейс взаимодействия с хранилищем. При этом существуют две вариации конфигураций кластера по количеству мастер-нод: HA (он же отказоустойчивый) и не-HA (он же “стандартный кластер” в ЛК Облака Рег.Ру). Отличие тут кроется в количестве мастер-нод и, как следствие — ендпоинтов kube-apiserver’а и реплик etcd (если используется etcd, конечно). В качестве альтернативы etcd существует проект kine — он позволяет сохранить данные кластера в PostgreSQL/MySQL/SQLite или NATS, выступая в качестве прокси к конкретной БД, реализуя протокол etcd и предоставляя свой выделенный ендпоинт для доступа компонентов Kubernetes к данным.

Часть 2: Архитектура KaaS

Итак, у нас есть Kubernetes и мы знаем как его запустить. Вручную или через какое-нибудь средство автоматизации вроде Ansible/Salt/Puppet, — честно признаться, вначале нас даже устраивал и такой метод деплоймента. Но в какой-то момент времени количество кластеров значительно выросло, их конфигурация стала немного различаться, и это «немного» стало достаточным, чтобы задуматься о Control Plane над кластерами Kubernetes. Т.е. единой точкой управления множеством кластеров со своим собственным API, чтобы проблему обслуживания жизненного цикла кластеров и общение с IaaS-слоем локализовать в отдельной абстракции. 

На эту роль в конечном итоге была выбрана связка из трех компонентов:

  • Kamaji — для деплоймента компонентов Control Plane кластеров Kubernetes

  • Cluster API — API для управления кластерами Kubernetes посредством ресурсов Kubernetes.

  • собственный API, — мы называем его МПУ (Модуль Предоставления Услуги) KaaS, который предоставляет высокоуровневое API для взаимодействия с сервисом.

Взаимодействие компонентов
Взаимодействие компонентов

Остается только вопрос, как нам добавлять Worker node’ы. Тут все просто и тривиально: мы используем предподготовленные образы, содержащие в себе все необходимое для join’а в кластер Kubernetes, так что остается только взять из Control Plane’а join token и передать его конкретной виртуальной машине для присоединения к кластеру. PROFIT!

Часть 2.1: Сетевая связность

Теперь, зная, как можно собрать кластер Kubernetes на базе Openstack, давайте немного поговорим про сетевую связность в следующих контекстах:

  • Клиент (сервиса KaaS) ⬌ Kubernetes Control Plane

  • Worker Nodes ⬌ Kubernetes Control Plane

  • Внешний трафик в кластер Kubernetes ⬌ Worker Nodes

Для этого нужно сначала разобраться, какие компоненты Kubernetes у нас куда заселяются. Начнем с Worker Nodes — это обычные виртуальные машины в тенанте (со стороны Openstack этот механизм представлен проектами (Project), позволяя изолировать все ресурсы конкретного клиента) конкретного клиента, от любых других их отличает только образ, из которого они запущены и то, что про них что-то знает МПУ KaaS, работая вместе с Openstack над их жизненным циклом.  Эти виртуальные машины живут в приватных сетях Openstack и по умолчанию не доступны из сети Интернет, но могут туда ходить за, например, образами контейнеров. Т.е. их связность с Интернетом ограничена только доступом в Интернет, но добраться до них из сети Интернет невозможно. При этом у них есть L2-3 связность между собой.

Control Plane — я тут умышленно не использую слово “node”, потому что на самом деле никаких нод там нет :) Помните, в начале статьи я говорил, что KaaS для нас — целая платформа? Так вот, и тут мы используем KaaS, чтобы разворачивать другие кластеры KaaS, запуская компоненты Kubernetes Control Plane в качестве контейнеров в отдельных, специально созданных для этого кластерах. Это позволяет нам более эффективно масштабироваться, следить за ресурсами, потребляемыми Control Plane, обеспечивать скорость запуска кластеров Kubernetes.  И, что немаловажно, легко и просто переживать потенциальные проблемы с инфраструктурой, т.к. Kubernetes сам переселит Pod’ы Control Plane’а на другие ноды, если с одной или несколькими что-то случится.

Схема сетевой связности Control Plane и Worker-нод
Схема сетевой связности Control Plane и Worker-нод

При такой конфигурации возникает не самый очевидный нюанс: как будут работать различные механизмы Kubernetes, требующие установки соединения от Control Plane’а до Worker-нод? Например, kubectl logs, или сессия kubectl exec, или например сессия kubectl run. Чтобы решить эту проблему, существует проект konnectivity, который как раз предназначен для того, чтобы обеспечить связность Worker-нод и Control Plane’а через NAT или Firewall. Принцип его работы предельно простой: требуя однонаправленную связность от Worker-нод в сторону Control Plane’а, он создает пул туннелей, через которые в дальнейшем будет работать, например, apiserver, показывая поток логов конкретного Pod’а.

Часть 2.2: Data plane

Kubernetes поддерживает единственный протокол для доступа к данным кластера — это протокол etcd. Их связка настолько давно и прочно устоялась, что etcd даже указывают в перечне компонентов Kubernetes Суровая реальность же такова, что если можно локализовать стейт где-то на стороне — лучше это сделать. Так подумали мы и не стали использовать etcd в каждом отдельном кластере Kubernetes, применив связку kine + PostgreSQL. В итоге получается, что наши Pod’ы Control Plane’а Kubernetes клиентского кластера Kubernetes (мы его еще называет “тенант-кластером”) становятся stateless, а значит, их можно легко мигрировать между разными нодам Kubernetes кластера, в котором они живут.

Внимательный и осведомленный читатель в этом месте задаст вопрос: “А в чем тогда смысл HA-конфигурации Control Plane’а в cloud.reg.ru?”. Отвечаю: смысл в том, чтобы такие переключения происходили максимально быстро и незаметно для конечного потребителя, будь то kubelet, какой-то оператор внутри кластера или kubectl на стороне рабочего компьютера нашего клиента. Ведь Pod’ы запускаются не мгновенно, у некоторых компонентов есть процедура выбора лидера, и все это занимает время, а пока они там себе выбирают, часть функций Control Plane’а может оказаться недоступной. Что в свою очередь может приводить к самым разным последствиям: от пропуска момента запуска CronJob до фейлов на стороне CI клиента.

Отдельно хочу сказать пару слов про PostgreSQL, который скрывается за этим всем — это база данных, созданная по тем же самым принципам и технологиям, что и в DBaaS (каюсь, мы любим матрешки и переиспользуем собственные наработки:) Ранее мой коллега Макар Кунгуров  подробно рассказывал про устройство этого сервиса, потому не буду повторяться. Скажу лишь, что у нас эта технология используется уже давно в качестве сервиса для наших клиентов и еще дольше внутри компании, доказав свою надежность и масштабируемость.

Часть 3: Получение внешнего трафика внутрь кластеров Kubernetes

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

  • Доступность Worker-нод из сети Интернет

  • Внешний Load Balancer

  • Возможность анонса адресов сервисов через какой-либо механизм (GARP, BGP, etc.)

При этом, напомню,  Worker-ноды живут в приватной сети и из Интернета они недоступны. Идея использовать какой-либо механизм анонса показалась нам “как из пушки по воробьям”, вдобавок, мы уже знали  к этому моменту, что Cloud Controller Manager отлично позволяет решить эту задачу средствами IaaS. В нашем облаке используется компонент Octavia, функциональность которого в части возможности делать L4-балансировку через виртуальные машины с haproxy (Amphora’ы) и поддержка HA-режима нас более чем устраивала.

Схема заведения LB в Octavia с двумя Amphora'ми на VRRP
Схема заведения LB в Octavia с двумя Amphora'ми на VRRP

При этому у Amphora есть сетевая связность с Worker-нодами, так что это решает все проблемы, и получается следующая схема хождения трафика:

Схема прохождения трафика до целевого Pod'а конкретного приложения
Схема прохождения трафика до целевого Pod'а конкретного приложения

Из интересных особенностей такой схемы: у нас автоматически реализованы TCP-стики на уровне балансировки соединений между Amphora’ами, т.к. они для своей отказоустойчивости используют протокол VRRP, не подразумевающий распределение нагрузки. Это отдельная проблема данного механизма, зато со стиками точно проблем нет и кажется, что производительности такой схемы хватит подавляющему большинству :)

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

Часть 4: Заключение

Мы рассмотрели ключевые элементы архитектуры сервиса KaaS в том виде, как мы используем его в Runity, описали сетевую топологию, которая создается в тенанте клиента на уровне IaaS, а также раскрыли некоторые особенности конфигурации, которые должны позволить клиентам выбрать наиболее подходящую для их потребностей конфигурацию. Тем не менее, мне хочется сделать еще одну вещь, а именно: рассказать, куда мы движемся с нашим сервисом и какие новые фичи стоит ожидать в будущем.

Начнем с наиболее важной фичи для построения полноценной инфраструктуры в облаке на базе Kubernetes — Registry (рабочее название). Услуга позволит собрать в одном месте всю инфраструктуру по работе с кодом на базе Gitlab, хранению образов контейнеров и доставке в различные среды, будь то стейджинг или продакшн.

Второе по очереди, но не по важности, — внешние сетевые диски. Фича бесспорно важная и нужная, которая позволит монтировать вольюмы прямо внутрь кластера Kubernetes через PVC или напрямую создавая PV.

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

С вами был Игорь Шишкин, руководитель команды R&D облачных сервисов Рунити, спасибо за внимание и до новых встреч :)

Комментарии (0)