Привет! Меня зовут Денис, и я отвечаю за контейнерную инфраструктуру в Тензоре. Начну с начала. Когда-то Kubernetes-кластеров у нас не было. Зато были 2 дата-центра и 20 тыс. виртуальных машин на тысяче железных серверов. На этом великолепии и «крутились» продукты компании Тензор.

И появилась задача — перенести весь софт в контейнеры и в кубер. 

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

Ах, да, на данный момент у нас 30 K8s-кластеров на 4 площадках, 350+ нод. И мы активно мигрируем приложения с ВМ в K8s. 

Как мы подошли к задаче

Выявили несколько базовых принципов:

  • Модульность.  Структурируем функционал и даем инженерам параллельно работать с разными модулями. 

  • Экземпляр из шаблона. Кластеров много, но все построены идентично, можно лишь выключить второстепенные модули.

  • Кластер — это runtime-сущность. Все данные, что чувствительны к потере, лежат за пределами кластера. С кластерами расстаемся легко.

  • Самодостаточность кластера. Каждый кластер — это мини-инфраструктура, в нем есть все для работы приложений.

Потом был опытный образец k8s-кластера, работа над ошибками и вот он — наш набор модулей на сегодняшний день.

Ядро кластера

К ядру относим всё, что реализует API и стандартные ресурсы Kubernetes — весь ванильный control plane k8s. Для отказоустойчивости перед control plane мы поставили Keepalived, а для балансировки запросов — HAProxy. Осознав необходимость следить за сроками действия сертификатов, добавили в подсистему X.509 Certificate Exporter и написали триггеры.

Среда запуска контейнеров

Начинали с docker, но, однажды, при обновлении k8s, перешли на containerd.

Сеть передачи данных

Начали с Cisco ACI CNI-plugin, ибо были планы строить на Cisco ACI сеть ЦОД. Хорошо, что не успели. Не понравилось — изменения из k8s-кластера доходили до ACI-фабрики почти с минутной задержкой, да и багов хватало. Стали искать альтернативный CNI-плагин, выбирали между Cilium и Calico. Задумали в каждом кластере выделить router-ноды с двумя функциями:

  • анонсировать во внешнюю СПД маршруты к кластеру,

  • балансировать трафик, предназначенный LoadBalancer-сервисам.

От внешнего маршрутизатора требовалось принимать BGP-анонсы и раскидывать трафик по router-нодам (ECMP).

Ключевое требование — выход из строя или плановый вывод router-ноды не должны отражаться на приложениях. Для нашего облака массовая переустановка TCP-сессий — это большая проблема. И тут Cilium "заиграл" благодаря поддержке алгоритма балансировки Maglev. При распределении входящих соединений по эндпоинтам router-ноды независимо друг от друга принимают одинаковые решения. При выходе одной из router-нод из строя оставшиеся будут направлять пакеты так же, как это делала бы упавшая. Поэтому TCP-сессии не рвутся.

Через 300 миллисекунд, после падения router-ноды, внешний маршрутизатор перестает направлять на нее трафик, так как у нас настроен BFD. Встроенный в Cilium механизм BGP не поддерживает BFD, поэтому используем Bird с самописным конфигуратором.

DNS

Наш Kubernetes встроен в существующую схему DNS компании. В ее центре  — кэширующие серверы. Они хранят данные из всех наших DNS-зон. Это позволяет ослабить зависимость от DNS-серверов, обслуживающих отдельные DNS-зоны и при необходимости "холодного" старта ЦОД иметь работающий DNS уже на раннем этапе.

Изначально для интеграции с кэшами использовали связку ExternalDNS↔PowerDNS↔кэши. Почему? Она быстро взлетела. Позже в плагинах CoreDNS появилась поддержка zone-transfer и работы с views. Мы смогли работать из CoreDNS с кэшами напрямую. Для этого разработали плагин, который добавил возможность экспорта/импорта DNS-зон вместе с DNS-записями Ingress'ов.

Балансировка HTTP-трафика

Без изысков — используем стандартный Kubernetes Ingress NGINX Controller. Аннотации на Ingress позволяют многое сделать. Нам, например, пригодилась маршрутизация по заголовкам и аутентификация через ADFS. Так что пока не стали переходить на более модные варианты.

Интеграция с СХД

Для приложений с высокими требованиями к дисковой системе используем FC-тома. Для этого «подружили» k8s с СХД от IBM через проприетарный provisioner. Для приложений попроще — Kubernetes NFS Subdir External Provisioner.

Предоставление прав пользователям

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

Кстати, стоит обратить внимание на возможности по агрегации ролей в K8s. Агрегатные роли сами по себе не содержат никаких прав, а только собирают в себя «фрагменты» (сниппеты). Разработчику модуля достаточно подготовить сниппеты и указать в какие роли их надо агрегировать.

В качестве OIDC-провайдера используем Active Directory + ADFS.

Интерфейсы управления ресурсами

Для широкого круга пользователей эксплуатируем штатный Kubernetes Dashboard. Через связку "oauth2-proxy ↔ ADFS ↔ AD" реализовали прозрачную аутентификацию. Для инженеров — kubectl. С плагинами krew и kubelogin можно аутентифицироваться через тот же ADFS. Так как кластеров много, написали интерфейс, который позволяет инженеру быстро настроить подключения к нужным.

Проверка манифестов ресурсов

По началу неочевидный, но в итоге очень мощный модуль! Во-первых, позволяет задавать правила для манифестов, чтобы автоматически выявлять манифесты-нарушители. Это помогает избежать попадания в k8s-кластер «неправильной» нагрузки. Например, запретить использовать для приложений hostPath, а образы разрешить использовать только из внутреннего хранилища. Во-вторых, позволяет автоматизировать процедуры — избавляет от рутинного труда. Например, мы автоматически создаём VPA-ресурс для всех ресурсов вроде Deployment и StatefulSet (далее расскажу зачем).

Для реализации выбрали Kyverno.  Дополнительно написали GUI, где разработчик видит нарушения только по своим ресурсам, а администратор безопасности — по всем.

Новую политику сначала включаем в режиме уведомления (audit) и через GUI выявляем нарушителей. Как только все нарушения устранены, переводим политику в принудительный (enforce) режим, после чего новые нарушители уже не могут попасть в кластер. 

Масштабирование приложений

Для горизонтального используем HPA, для вертикального — VPA. Сперва про HPA. Не устраивает пока только то, что для масштабирования вниз максимальное время оценки - 1 час. Хотелось бы 1 месяц и даже более, ибо циклы нагрузки у нас меряются кварталами и годами. Будем искать замену стандартному HPA. В качестве источника метрик с MetricsServer перешли на Prometheus Adapter, чтобы иметь возможность принимать решение на основании любой метрики.

Теперь про VPA. При развёртывании Deployment, StatefulSet и подобных ресурсов "автоматом" создаём для них VPA-ресурс в режиме рекомендаций. Для удобной визуализации разработали GUI, где разработчик приложения может быстро понять сколько ресурсов надо добавить или убавить его приложению. А администратор ресурсов легко находит тех, кто создает overbooking в кластере

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

Поначалу можно не обратить внимания, что в k8s нет встроенных средств группировки нод. Есть метки. Но, во-первых, чтобы ими оперировать, нужны права на ноды, а во-вторых, ввод текста руками — это место для очепяток. Мы решили хранить состав пула в custom-ресурсе, туда органично вписались и параметры пула (например, границы для триггеров по пулам). Разработали оператора, который по данным CR изменяет атрибуты нод. Без интерфейса не обошлось.

Секреты

Секреты храним в Vault. В кластере используем External Secrets Operator. Ему на входе в ресурсе ExternalSecret надо сообщить откуда брать секретные данные и в какой ресурс Secret их поместить. А дальше он сам. Начиная с версии 0.7.0 у этого оператора появилась возможность не только получать секреты из Vault, но и загружать в него свои. Планируем использовать этот метод для интеграции внешних систем с K8s.

Управление настройками компонентов кластера

По мере роста количества кластеров встала задача централизованного управления их конфигурациями. Мы пошли по пути "экземпляр следует рожать из шаблона". Шаблон содержит объявление и дефолтные настройки всех возможных модулей. Описание экземпляра индивидуализирует шаблон до конкретного кластера (имя, адресация и т.п.) и может его переопределить (отключить модуль, к примеру). Все храним в git, а доставляем в кластеры через ArgoCD.

Архитектор рулит платформой через шаблон (1), CI/CD "копипастит" шаблон в конфигурации (2), администратор может уточнить конфигурацию (3), ArgoCD следит за изменениями и применяет настройки в кластере (4).

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

Изменения в шаблон вносим через релизный цикл. Релизим раз в неделю, в основе что-то близкое к GitFlow.

Мониторинг и алертинг

Для сбора и хранения метрик в кластере используем достаточно распространённую пару Prometheus и VictoriaMetrics.

Дабы сберечь ресурсы, мы препятствуем попыткам пересылать бессмысленные метрики уже на этапе их сбора: каждая метрика должна быть явно описана в PodMonitor/ServiceMonitor-ресурсе и снабжена комментариями (по ним мы автоматически создаём документацию на метрики).

У нас есть отдельные Prometheus для инфраструктурных и прикладных метрик — так изменения прикладной нагрузки не влияют на сбор системных метрик.

Основное хранилище — долгосрочное. А ещё есть краткосрочное, оно хранит данные в ОЗУ на случай критических сбоев в долгосрочном. Ещё есть шлюз, который позволяет из Grafana получить доступ к метрикам сразу группы k8s-кластеров, не прописывая при этом каждый источник метрик в Grafana. Количество кластеров непостоянно, поэтому мы добавили в систему компонент, который актуализирует список кластеров на шлюзе.

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

Логирование

Временным решением была связка Filebeat с внешним кластером Elasticsearch. Но недавно всё изменилось: перешли на Loki. Теперь логи можно смотреть прямо в Grafana вместе с метриками. Без доработок не обошлось:

  • мы не нашли готового решения, чтобы показывать логи только тем, у кого есть доступ к namespace. Поэтому написали свой компонент — он по заголовкам HTTP-запросов от Grafana проверяет, есть ли у данного пользователя права;

  • решили задавать ограничения на объём хранения и поток логов по приложениям — для этого написали k8s-оператора. Он записывает fluentbit'у в throttleFilter желаемый rate, а в конфиг Loki передает время хранения логов для Namespace.

Резервное копирование

Используем Velero и пока нам его хватает на 100% :)

Сбор дампов 

Через DaemonSet запускаем Core Dump Handler на всех нодах. Он собирает дампы и закидывает их в S3-хранилище, а триггер уведомляет об этом эксплуатацию. Его мы немного пропатчили — запретили снимать более 1 дампа одновременно, поскольку это сильно загружало ноду и бывало, пока один дамп отправлялся, второй уже записывался на его место —  получали битые дампы. После патча работает ровно.

Управление приложениями

Приложения заказчика мы запускаем по SaaS-модели. Для этого у нас есть несколько модулей, в основе которых CR и операторы. Но эта тема тянет на отдельную статью :)

Итак, теперь вы знаете как мы строили (и построили) "свой Кубер". Но, как говорится "бесконечность - не предел" и у нас уже много новых планов, например:

  • Интегрировать платформу с NetBox, который мы используем как CMDB для всей инфраструктуры. Планируем написать контроллер, который при обнаружении изменений в CMDB создаст merge request в конфигурацию кластера. Администратору кластера останется проверить и принять изменения.

  • Реализовать идею эфемерности нод: на ноде оставить минималистичную read-only ОС, шаблон конфигурации хранить в git, а конфигурацию в CMDB.

  • Перейти с морально устаревшего NFS на Linstor.

Так что не переключайтесь, друзья :) 


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


  1. chemtech
    30.08.2023 08:35
    +2

    Спасибо за интересный пост.

    Встроенный в Cilium механизм BGP не поддерживает BFD.

    Не делали issue?

    Для реализации выбрали Kyverno.  Дополнительно написали GUI.

    Планируете ли выложить в open source?

    При развёртывании Deployment, StatefulSet и подобных ресурсов "автоматом" создаём для них VPA-ресурс в режиме рекомендаций. Для удобной визуализации разработали GUI.

    Планируете ли выложить в open source?

    Через DaemonSet запускаем Core Dump Handler на всех нодах. Он собирает дампы и закидывает их в S3-хранилище, а триггер уведомляет об этом эксплуатацию. Его мы немного пропатчили.

    Планируете ли отправить PR с исправлением?


    1. arkhipovds Автор
      30.08.2023 08:35

      Спасибо за интересный пост.

      Рады, что вам понравилось!

      Не делали issue?

      делали, но не мы :)

      https://github.com/cilium/cilium/issues/22394

      Планируете ли выложить в open source?

      такие мысли есть, но нет определенных сроков

      Планируете ли отправить PR с исправлением?

      нет, мы, скорее, урезали функционал, чем расширили :)