Важно понимать, что мы не просто взяли набор приложений из open source и положили его в наш UI, чтобы вы могли им пользоваться. Всё сложнее. Мы заранее настроили их для совместной работы, интегрировали с сервисами Yandex Cloud, протестировали и защитили от изменений. Это позволило сделать Kubernetes® центральным ядром управления инфраструктурой.
Обновления в Managed Service for Kubernetes®
Рассказываем о том, как развивался сервис Managed Service for Kubernetes® за последний год, что появилось нового и куда мы движемся.
Managed Service for Kubernetes® позволяет управлять кластерами и предоставляет инструменты для диагностики и восстановления работоспособности узлов. Управляемый сервис обеспечивает надёжную работу контейнеризованных приложений. На базе Managed Service for Kubernetes® можно построить безопасную среду для эксплуатации ваших решений.
Как мы развиваем управляемый Kubernetes
Платформа Kubernetes® уже не просто запускает контейнеры. Этот инструмент, который позволяет управлять инфраструктурой, может выступать центральным ядром кластерной операционной системы. Наш управляемый сервис Managed Service for Kubernetes® развивается в том же направлении. Мы выпускаем продукты и делаем интеграции для управления всей вашей инфраструктурой.
Например, есть два декларативных инструмента для непрерывной доставки в Kubernetes® по модели GitOps: Argo CD и FluxCD
В этой статье мы расскажем:
Также стал доступен кабинет паблишера: теперь владельцы программных продуктов и энтузиасты свободного ПО могут публиковать свои приложения для Managed Services for Kubernetes® и тестировать их перед публикацией.
Что нового появилось
В 2022 году в Managed Service for Kubernetes® появилась поддержка инструмента Cillium. Cilium — это сетевой плагин, который обеспечивает сетевое взаимодействие, безопасность и доступность для Kubernetes®. С его помощью можно производить тонкую настройку сетевых политик, например, он предоставляет доступ к сервису только от подов с определённой меткой, даёт возможность получать данные о трафике внутри кластера, не создавая дополнительных задержек. Также можно ограничивать исходящую пропускную способность сети у конкретных подов.
У сервиса вышли версии Kubernetes 1.22, 1.23 и 1.24, которые доступны для установки. При этом перед публикацией каждая версия проходит цикл тестирования, что позволяет выпускать в продакшн продукт с минимальным количеством багов.
В сервисе для сбора и выгрузки аудитных логов ресурсов Audit Trails теперь можно видеть данные о создании кластеров, добавлении нод‑групп, увеличении их размера и другие изменения. Это удалось сделать за счёт получения логов Control Plane. Также был реализован сбор логов серверов API в сервис Cloud Logging, что позволит видеть и анализировать логи аудита серверов API в своих кластерах.
Ещё одна новинка от сервиса связана с продуктом Prometheus, который большинство пользователей Kubernetes® использует для мониторинга. Однако при хранении в нём больших объёмов метрик наблюдается увеличенное потребление ресурсов, что делает использование Prometheus нецелесообразным. Эту проблему теперь можно решить при помощи сервиса Monitoring, который подключается к Prometheus в качестве remote backend и позволяет собирать метрики для долгосрочного хранения.
Как решают проблемы Kubernetes: резервирование ресурсов на нодах и масштабирование контроллеров
Kubernetes® — это декларативная система, работающая с манифестами, которые применяются внутри кластера. Все компоненты Kubernetes® работают на то, чтобы привести часть инфраструктуры в состояние, которое описано в манифестах клиента и хранится внутри базы данных кластера. Базой данных кластера является etcd (распределённое хранилище ключей и значений, предназначенное для безопасного хранения данных в кластере), которая интересна тем, что поддерживает такой механизм, как change data capture. Благодаря ему etcd и была выбрана изначально для Kubernetes.
Что касается таких паттернов чтения, как Kubernetes® из базы данных, то это обычно запросы какого‑то класса объектов. Например, выбор всех подов, которые принадлежат определённому деплойменту или набору реплик. Однако у etcd нет индексов, и, соответственно, чтение по фильтрам выполняется фулл‑сканом. То есть сначала считывается весь диапазон объектов, а дальше формируется выборка по указанному фильтру. И механизм change data capture нужен для того, чтобы клиенты могли один раз вычитать из etcd все интересующие их объекты, а после этого подписаться на изменения и получать их списком. Хороший профиль нагрузки для etcd — это когда все клиенты хранят кеш, вычитывая только изменения из etcd по мере их поступления. Kubernetes® спроектирован точно так же. То есть сервер API получает кеш из etcd и подписывается только на изменения. А все клиенты, которые подключаются к серверу API, также на своей стороне хранят кеш интересующих их объектов и подписываются в сервере API только на изменения.
Как работает стабильный Kubernetes®? Во‑первых, все запросы на чтение проходят через клиентский серверный кеш. Во‑вторых, Rate of Change отрабатывается мастером и распространяется между клиентами. Это значит, что API сервер держит в памяти плюс‑минус все объекты, которые есть в кластере. Следовательно, для этого необходимо много памяти. И если посмотреть на метрики бэкендов, которые находятся в Kubernetes, то можно видеть, что иногда требуется почти в восемь раз больше памяти на хранение объектов в контроллерах, чем на тот объём, который реально хранится в etcd.
Теперь нужно понять, как «сломать» и разбалансировать систему. Нужно сделать так, чтобы не использовались кеши. Желательно, чтобы это происходило одновременно и много раз от разных контроллеров, а также необходимо создать значительную нагрузку за счёт вычитывания больших объектов. В теории для этого достаточно написать Bash‑скрипт, который бесконечно вычитывает из кластера весь список секретов.
Однако надо понимать, что секрет — это хитрый объект в Kubernetes®. Он предназначен для хранения пароля, но часто используется для хранения сертификатов, которые представляют собой достаточно большой объём информации. Туда могут попадать конфигурации из‑за того, что в них есть одна секретная строчка, но вытащить её оттуда нельзя. Соответственно, в секрет складывается вся конфигурация. А если в кластере используется Helm, то свои релизы он хранит внутри секретов и полностью отрендеренные манифесты всего Helm хранятся там же.
Проблема в том, что при таких запросах нет никакого кеша. Чтобы использовался серверный кеш, от клиента нужно передать специальный ключ, который как бы говорит: «Вот мой кеш, его состояние. Покажи, пожалуйста, свой кеш на сервере и давай сравним. И мне нужны только изменения относительно моего кеша». То есть такой запрос происходит вне кеша клиентского и вне кеша серверного, фактически отправляясь сразу же в etcd.
Например, то же самое делают все kubelet в кластере: они вычитывают набор нужных им объектов из кластера. А если в кластере с большим количеством нод запустить обновление, которое будет затрагивать ещё и не одну ноду? Это может привести к тому, что на время обновления kubelet будут активно вычитывать из сервера API информацию.
Примерно то же самое происходит при запуске DaemonSet, который также развёрнут на каждой ноде, следит за определёнными типами объектов в кластере и вычитывает из кластера какую‑то информацию. И в определённый момент объектов становится слишком много, кеш увеличивается и DaemonSet исчерпывает свои лимиты памяти. При одновременном рестарте кеши вынуждены постоянно обновляться. Возникает ситуация, описанная выше: всё работает без кешей.
Другой пример с секретами, когда Kubernetes Dashboard по той или иной причине начинал в бесконечном цикле вычитывать все секреты из кластера. Это приводило ровно к такой же проблеме: всё начинало работать без кешей.
Чтобы всё работало нормально, рекомендуется использовать кеширующие клиенты. Самый простой вариант — стандартная библиотека client‑go от Kubernetes, в которой это всё реализовано.
Также стоит не забывать выставлять лимиты resource requests/limits на контроллерах. При этом нужно быть готовым, что придётся динамически их изменять — чем больше объектов становится в кластере, тем больше объектов попадает в кеш каждого контроллера и тем больше контроллеру требуется памяти. И в данном случае может помочь Vertical Pod Autoscaler, позволяющий динамически менять requests/limits в приложениях внутри кластера Kubernetes.
Ещё один способ решения проблемы относится к Kubernetes® версии 1.20. В beta появились два типа объектов — Priority и Fairness API, которые позволяют ограничивать запросы, приходящие в сервер API, и управлять ими. То есть либо отрубать запросы, в которых превышен rate‑лимит, либо выстраивать их в очередь. Такие объекты позволяют спасти сервер API от всплеска запросов.
Priority and Fairness APIPriority and Fairness API a. k. a. Flow Control
apiVersion: flowcontrol.apiserver.k8s.io/v1beta1
kind: PriorityLevelConfiguration ——— Можно всё
spec:
type: Exempt
type: Limited
limited:
assuredConcurrencyShares: 5 —— Относительный вес (количество запросов)
limitResponse:
type: Reject —————————— При превышении квоты — ошибка
type: Limited
limited:
assuredConcurrencyShares: 30
limitResponse:
type: Queue —————————— При превышении квоты — сначала Throttle
queuing:
handSize: 6
queueLengthLimit: 50
queues: 64
Но здесь есть важный нюанс, который необходимо учитывать: при описании что и как лимитировать, стоит выкинуть health check. В противном случае можно легко столкнуться с ситуацией, когда сервер API начнёт лимитировать в том числе и health check к себе. Балансировщики, которые закрывают сервер API, не смогут «достучаться» до health check, и серверы API начнут валиться из‑за того, что их ограничили. Соответственно, flow‑схема позволяет выкинуть определённые ручки из этого механизма и спасти health check.
Be careful with health checks
apiVersion: flowcontrol.apiserver.k8s.io/v1beta2
kind: FlowSchema
metadata:
name: health-for-strangers
spec:
matchingPrecedence: 1000
priorityLevelConfiguration:
name: exempt
rules:
- nonResourceRules:
- nonResourceURLs:
- “/healthz”
- “/livez”
- “/readyz”
verbs:
- “*”
subjects:
- kind: Group
group:
name: system:unauthenticated
Почему бы не решить данную проблему масштабированием? Во‑первых, горизонтального масштабирования в Kubernetes® нет. Несмотря на то что одновременно может быть запущено несколько контроллеров, по факту в один момент выполнять работу будет только один экземпляр, блокирующий за собой право быть мастером сервер API фактически тоже не масштабируется горизонтально, потому что все серверы API работают одновременно. Можно попробовать расшардировать запросы на серверы API, но это спасёт только от нагрузки на CPU. Память останется всё равно той же самой, потому что они одновременно вычитывают в кеше все объекты Kubernetes®. Остаётся вертикальное масштабирование, но оно занимает очень много времени: придётся вывести мастера из managed‑кластера, выключить виртуальную машину, изменить на ней ресурсы, включить и дождаться, пока все компоненты запустятся.
Ещё одна проблема с масштабированием — не очевидно, на основании чего нужно это делать и каким образом. Также это может быть экономически невыгодно: проблемы случаются нечасто и нет смысла постоянно держать множество ресурсов на мастерах.
Для решения поставленной задачи нужно несколько действий. Во‑первых, необходимо резервировать ресурсы для Kubelet и других системных компонентов. Это легко осуществить при помощи ключей ‑ ‑kube‑reserved и ‑ ‑system‑reserved, которые из общей capacity нода позволяют какое‑то количество ресурсов удалить и использовать их только для системы. Во‑вторых, использовать масштабирование контроллеров с помощью, например, Vertical Pod Autoscaler. Тем не менее одна из сложностей заключается в формировании понятного сигнала, который легко будет давать представление, на основании чего производить масштабирование и в какой момент. Инженеры Managed Service for Kubernetes работают над тем, чтобы это происходило в автоматическом режиме.
Архитектура и масштабирование системы напрямую зависят от выбранного хранилища и того, как оно работает. Kubernetes® — это система, которая рассчитана на стационарность и плохо переживает переходные процессы. К тому же существуют определённые паттерны нагрузки, которые могут быстро положить сервер API. Стоит избегать и некешированных клиентов. Чтобы ограничить «тяжёлые» запросы, лучше использовать Flow Control. И будем ждать новых архитектурных решений от Kubernetes®.
Выводы
Мы постоянно работаем над расширением экосистемы Kubernetes® и совершенствованием управляемого сервиса. Такой подход уже привёл к тому, что всё больше и больше компаний используют не собственные инфраструктуры на виртуальных машинах, а выбирают именно Managed Service for Kubernetes®. Например, количество работающих в Yandex Cloud кластеров в 2022 году по сравнению с 2021 увеличилось больше чем в два раза. Мы продолжаем свою работу и активно взаимодействуем с комьюнити Kubernetes®, чтобы решить все задачи и проблемы наших клиентов.