Клиент Spring Boot для Kubernetes

Клиент Spring Boot для Kubernetes

Введение

При эксплуатации Kubernetes иногда возникают мысли: «Хотелось бы иметь возможность одним взглядом собрать информацию о том, нормально ли работают Pod'ы в кластере и каково состояние каждого Pod'а». Этот вопрос изначально возникает как простое любопытство, но по мере увеличения числа серверов, которыми вы управляете, он превращается в ежедневную заботу оператора.

Решением таких требований и является библиотека клиента Kubernetes. Это библиотека, которая позволяет напрямую получать информацию о состоянии кластера Kubernetes в приложении Spring Boot и реагировать на изменения в реальном времени. Она скорее выполняет роль моста, позволяя самому приложению стать частью кластера и взаимодействовать с ним, чем просто быть инструментом для «внешнего наблюдения за кластером».

В этой статье я постараюсь последовательно рассказать о мотивации внедрения клиента Kubernetes в Spring Boot, принципах работы и важных аспектах безопасности, которые необходимо учитывать в рабочей среде, на основе моего опыта в реальных проектах.

Мотивация для внедрения

Если кратко подвести итог, причина, по которой я начал изучать эту технологию, заключается в необходимости автоматизации операций и сбора состояния серверов. В текущем проекте работает более 100 серверов, и в будущем их количество, скорее всего, увеличится. Это означает не просто рост числа серверов, но и то, что одному оператору невозможно удерживать в голове состояние всех серверов.

С существующим методом для проверки состояния кластера необходимо было напрямую подключаться к Shell и выполнять команду kubectl, или же отдельно заходить в ArgoCD. Процесс, начиная с kubectl get pods — это, как мне кажется, фактор, который постепенно увеличивает время реакции на сбои.

Пределы существующей среды GitOps

Я управлял настройками подов, такими как ConfigMap и Deployment на сервере, изменяя среду GitOps через JGit в Spring Boot и вызывая синхронизацию ArgoCD.

Однако, поскольку не все серверы находятся в одинаковой среде, и в процессе развертывания иногда возникают человеческие ошибки из-за проблем с сетью или брандмауэром. Например, если ключ в ConfigMap отличается, или в определенной среде не удается успешно получить изображение. Каждый раз, когда я сталкиваюсь с тем, что даже если изменения корректно отражены в Git, фактическое состояние кластера может отличаться.

В результате этого под может не запускаться должным образом, и в случаях, когда возникают проблемы с изображением Pod или ConfigMap, иногда бывает трудно быстро выявить их. В конечном итоге накапливается состояние «в Git применено, но в эксплуатации не отражено», что приводит к порочному кругу, в котором операторам приходится снова вручную проверять.

Ожидаемое поведение

Применение клиентской библиотеки Kubernetes в Spring Boot позволяет приложению активно наблюдать за кластером и реагировать на аномальные ситуации. Я ожидаю четкого поведения.

  • Если изменение ConfigMap применяется не так, как было задумано, соответствующий сервер немедленно получает уведомление.

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

В конечном итоге суть заключается в автоматизации процесса, который ранее требовал ручной проверки, чтобы заранее предотвратить сбои, вызванные человеческой ошибкой. Это не просто вопрос 'стало удобнее', но и имеет большое операционное значение в сокращении времени, необходимого для обнаружения сбоя (MTTD).

Как это работает — принципы

На самом деле то, что делает клиентская библиотека Kubernetes, по своей сути очень просто. Это обертка, которая удобным образом упаковывает REST API kube-apiserver для разработчиков. Когда вы пишете одну строку кода для получения списка Pod, библиотека внутри отправляет HTTP GET-запрос к API Server и преобразует ответ в объект Java.

То есть, тот же процесс, который мы обычно вводим в терминале с помощью kubectl get pods, просто меняется на одну строку вызова библиотеки. Разница лишь в том, что результат мы получаем не в виде читаемого человеком текста, а в виде объектов Java, таких как PodList, и что мы можем передать этот объект прямо в бизнес-логику.

Откуда берется информация для доступа к API Server?

Возникает вопрос: "Должен ли разработчик самостоятельно настраивать адрес API-сервера или информацию для аутентификации?" Ответ – «нет». Когда приложение развёрнуто в Pod на кластере Kubernetes, то есть выполняется в одном и том же кластере, Kubernetes автоматически вводит всю необходимую информацию для подключения.

Конкретно, автоматически подготавливаются следующие три вещи. IP-адрес API-сервера вводится через переменные окружения KUBERNETES_SERVICE_HOST и KUBERNETES_SERVICE_PORT. Клиентская библиотека самостоятельно читает эту информацию, чтобы настроить соединение, поэтому с точки зрения разработчика фактически достаточно добавить одну строку для завершения настройки.

ApiClient client = new KubernetesClientBuilder().build();

Причиной, по которой эта строка может работать, является то, что библиотека автоматически определяет, нахожусь ли я сейчас в кластере, и, если я внутри кластера, использует настройки in-cluster, а если снаружи — контекст из ~/.kube/config для настройки подключения. Здесь лежит причина, по которой один и тот же код может работать как в локальной среде разработки, так и в производственной среде.

Обнаружение изменений в реальном времени — Watch и Informer

Также поддерживается обнаружение изменений в реальном времени. Помимо простого запроса, с помощью функции Watch вы можете в реальном времени получать события кластера по HTTP-стриму. Это позволяет реализовать логику, которая немедленно реагирует на добавление или удаление Pod, а также на обновление ConfigMap.

Watch является по сути длительным HTTP-соединением. После отправки запроса соединение остается открытым, и данные поступают в формате JSON построчно каждый раз, когда возникает событие. Поэтому в Spring Boot обычно обрабатывается в отдельном потоке или в асинхронном контексте.

В рабочей среде обычно используется более продвинутый шаблон Informer. Informer сначала получает полное состояние через List API, а затем получает только изменения через Watch, чтобы поддерживать локальный кэш. То есть, всегда держит в памяти «последнее состояние кластера», и запросы на получение информации обрабатываются непосредственно из кэша, не обращаясь к API Server.

Преимущества этой структуры заключаются в двух вещах. Во-первых, можно значительно снизить нагрузку на API-сервер. Во-вторых, вы всегда можете быстро проверить актуальное состояние. С точки зрения бизнес-логики, если вы просто спросите: «Дайте мне список подов», Информер немедленно ответит из кэша.

Подводя итоги, Однократная синхронизация → REST вызов, Простое изменение обнаружения → Watch, Отслеживание состояния операционного уровня → ИнформаторВы можете выбрать один из трех模式 использования в зависимости от ситуации.

Управление жизненным циклом клиента

Объект KubernetesClient внутренне использует пул HTTP-соединений, поэтому создание и использование нового экземпляра для каждого запроса неэффективно. Я зарегистрировал его как синглтон-бин в классе @Configuration и реализовал @PreDestroy или DisposableBean, чтобы close() вызывался при завершении приложения. Эта небольшая разница может привести к утечке соединений и зомби-потокам, поэтому лучше уделить этому внимание на этапе проектирования.

Вопросы, требующие внимания с точки зрения безопасности

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

RBAC

Наиболее распространенной ошибкой является предоставление чрезмерных прав приложениям. При настройке RBAC (управление доступом на основе ролей) возможно, что в процессе быстрого разработки все права будут разрешены, или роль cluster-admin будет просто привязана. На этапе разработки легко перейти к следующему этапу с мыслями о том, чтобы «сначала заставить это работать», но эта настройка часто попадает в эксплуатацию.

Таким образом, если данный Pod будет захвачен, это может привести к серьезной ситуации, когда злоумышленник получит доступ ко всему кластеру. Уязвимость контейнерного образа, RCE библиотек зависимостей или простая SSRF-атака могут сразу же привести к компрометации всего кластера.

На практике целесообразно явно разрешать только необходимые ресурсы и действия (get, list, watch), и, если возможно, предпочтительно использовать роль, применимую только к конкретному пространству имен, а не ClusterRole. Привычка задавать вопрос «Действительно ли нужны дополнительные права для этой функции?» при добавлении новых функций станет самой сильной защитной линией.

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

Также следует обращать внимание на управление токенами ServiceAccount. По умолчанию Kubernetes автоматически монтирует токены ServiceAccount во все Pod. Это означает, что токены монтируются даже в Pod, которые не нуждаются в доступе к API Kubernetes. То есть это может означать, что все Pod в кластере могут стать потенциальными точками входа.

Чтобы этого избежать, необходимо явно добавить настройку automountServiceAccountToken: false для Pod, которому не нужно API-доступ, чтобы предотвратить ненужное раскрытие токена. Поскольку это можно настроить как на уровне ServiceAccount, так и на уровне спецификации Pod, рекомендуется использовать оба варианта для создания двойной защиты.

Журналы аудита и уведомления

Наконец, так же важно оставлять отметки о том, «кто что сделал», как и контролировать собственные права. Kubernetes предоставляет функцию журнала аудита по умолчанию, поэтому рекомендуется включить в операционные процедуры регулярную проверку того, не пытается ли используемый нашим приложением ServiceAccount выполнять вызовы за пределами предполагаемых прав.

Заключение

Использование клиента Kubernetes в Spring Boot означает, что приложение выходит за рамки простого выполнения на кластере и начинает активно распознавать и использовать кластер. Ранее часть операций, которая полагалась на внешние решения с мыслью «платформа сама справится», теперь будет переноситься внутрь кода приложения, и это станет совместной ответственностью.

Хотя это является мощным выбором при создании автоматизированных инструментов управления или платформ, при неправильном использовании существует высокий риск безопасности, поэтому стоит внимательно подходить к настройке прав. Если обеспечивается удобство, то важным считается тщательно проверять значения по умолчанию и настройки.

Лично я почувствовал, что самое важное, что мы получили от этого внедрения, это то, что «границы между приложениями и инфраструктурой становятся все более размытыми». Ранее мониторинг состояния кластеров считался областью команды инфраструктуры, а теперь это стало частью повседневной жизни, которую мы должны естественно учитывать в нашем служебном коде. И это изменение приводит к более быстрой реакции для операторов и к более коротким периодам простоя для пользователей.

Справочные материалы

Банг

Site footer