Привет! Меня зовут Денис, и я отвечаю за контейнерную инфраструктуру в Тензоре. Начну с начала. Когда-то 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.
Так что не переключайтесь, друзья :)
chemtech
Спасибо за интересный пост.
Не делали issue?
Планируете ли выложить в open source?
Планируете ли выложить в open source?
Планируете ли отправить PR с исправлением?
arkhipovds Автор
Рады, что вам понравилось!
делали, но не мы :)
https://github.com/cilium/cilium/issues/22394
такие мысли есть, но нет определенных сроков
нет, мы, скорее, урезали функционал, чем расширили :)