Оптимизация использования ресурсов рабочих нагрузок с помощью приложения Kubernetes и Notebook

Материал из Документация Ключ-АСТРОМ

Оптимизация использования ресурсов рабочих нагрузок с помощью приложения Kubernetes и Notebooks

Максимизируйте ресурсы вашего кластера и сократите расходы, выявляя и оптимизируя недоиспользуемые рабочие нагрузки. Используйте Kubernetes (new) вместе с продвинутыми запросами в Notebooks для точных рекомендаций по распределению ресурсов.

Это руководство предназначено для специалистов по эксплуатации, DevOps и DevSecOps, управляющих кластерами Kubernetes. Предполагается базовое понимание концепций Kubernetes, таких как запросы/лимиты ресурсов, поды и ноды, однако экспертиза в Ключ-АСТРОМ или управлении кластерами не требуется.

Сценарий

  • Сложности с планированием рабочих нагрузок из-за ошибок доступности ресурсов.
  • Отчёты указывают на высокое использование ресурсов на основе запросов, однако фактическое использование CPU и памяти на нодах остаётся низким.

Предварительные требования

  • Доступ к кластеру Kubernetes.
  • Настроенный новый опыт работы с Kubernetes.

Выявление и оптимизация ресурсов

Стратегии, применимые для оптимизации конкретного типа ресурса, такого как CPU, можно аналогично адаптировать и для других, например памяти.

1. Выявление кластеров для оптимизации

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

Запас (slack) — это разница между запрошенными ресурсами (CPU или память) и фактически использованными:

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

В идеале запас должен быть минимизирован при сохранении буфера для колебаний. Такие подходы обсуждаются на шаге 3.

2. Анализ рабочих нагрузок кластера

Изучите конкретные рабочие нагрузки в выбранном кластере. Перейдите к обзору рабочих нагрузок через страницу кластера, чтобы визуально наблюдать запас CPU.

Выбор Просмотреть в списке рабочих нагрузок применяет фильтр для выбранного кластера, переключая отображение на агрегированное представление всех рабочих нагрузок.

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

Добавьте фильтр и установите Статус оповещения в значение Нет оповещений.

Перспектива и предпочтения сортировки, установленные на уровне кластера, сохраняются, позволяя быстро выявить рабочую нагрузку с наибольшим запасом CPU — а именно рабочую нагрузку astromkey-oneagent-csi-driver.

При выборе этой рабочей нагрузки становится очевидным значительный разрыв между фактическим использованием и запрошенными ресурсами. Поскольку Kubernetes (new) отображает только самые последние данные, мы должны проверить эти показатели использования, чтобы убедиться, что они точно отражают типичное поведение, а не являются аномалиями.

Чтобы проверить согласованность этих шаблонов использования, рассмотрим исторические данные об использовании CPU. Это легко сделать, перейдя с вкладки Обзор на вкладку Использование.

Оттуда мы получаем доступ к детальным данным, выбрав > Редактировать в Notebook, что открывает соответствующий запрос DQL в Notebooks.

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

При проверке мы видим, что использование CPU нашего сервиса составляет менее 0,5 CPU (показано синей линией внизу графика), что значительно ниже запрошенных 4 CPU (показано зелёной линией вверху графика).

3. Оптимизация ресурсов рабочей нагрузки

Теперь вы можете эффективно настроить запросы и лимиты ресурсов рабочей нагрузки. Однако в этом процессе есть важные соображения, которые могут быть не сразу очевидны:

  • Рабочие нагрузки, превышающие свои запросы ресурсов, более подвержены вытеснению в сценариях конкуренции за ресурсы.
  • Превышение лимита CPU приводит к троттлингу рабочей нагрузки, а превышение лимита памяти может привести к завершению рабочей нагрузки из-за условий нехватки памяти (OOM).
  • Запрошенные ресурсы будут зарезервированы на ноде. Даже если они не используются, они всё равно недоступны. Это означает, что вы можете быстро насытить ноду, фактически не потребляя много её ресурсов, что препятствует планированию дальнейших рабочих нагрузок на этой ноде.
  • Рабочая нагрузка может состоять из нескольких подов, которые, в свою очередь, могут состоять из нескольких контейнеров. В этом примере рабочая нагрузка состоит только из одного пода с одним контейнером, поэтому ресурсы рабочей нагрузки совпадают с ресурсами этого контейнера. Если есть несколько подов и/или контейнеров, вы должны рассчитать, как соответствующим образом скорректировать соответствующие запросы или лимиты на уровне контейнера.

Вы можете использовать Ключ-АСТРОМ для детализации до уровня пода/контейнера, перейдя со страницы обзора рабочей нагрузки и нажав Просмотреть в списке подов в разделе подов. На странице обзора пода есть соответствующая ссылка Просмотреть в списке контейнеров.

С учётом этих факторов очевидно, что наши текущие настройки запросов и лимитов, вероятно, завышены. Наблюдая незначительные всплески использования, мы решаем снизить запрос CPU до 100m — консервативное значение, которое всё же обеспечивает стабильную производительность при внезапных увеличениях спроса.

Для лимитов мы выбираем 200m — порог, который вряд ли будет превышен в нормальных условиях. Хотя можно установить ещё более низкие лимиты, учитывая минимальный риск прерывания сервиса при ограничениях CPU (в отличие от памяти), мы отдаём приоритет осторожному подходу, допускающему будущие корректировки на основе постоянного мониторинга производительности.

Чрезмерно строгие лимиты могут вызвать троттлинг CPU даже без превышения установленных порогов. Это связано с механизмом CFS (Completely Fair Scheduler), используемым для применения лимитов в Kubernetes, который выделяет время CPU квантами по 100 миллисекунд. Например, лимит 0,4 (или 400m) подразумевает, что процесс имеет 40 миллисекунд из каждых 100 миллисекунд для выполнения, что приводит к потенциальному времени ожидания, если он не завершится в течение этого окна.

Благодаря этой оптимизации мы высвободили более половины ядра CPU, скорректировав ресурсы одной рабочей нагрузки.

4. Работа с рабочими нагрузками без запросов или лимитов

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

Однако в реальности не у всех рабочих нагрузок могут быть настроены эти запросы ресурсов. Хорошая новость в том, что выявить такие рабочие нагрузки просто с помощью DQL.

Мы используем следующий запрос, основанный на таблице, которую мы видим в Kubernetes (new).

// Get all Kubernetes workload types
smartscapeNodes { K8S_DEPLOYMENT, K8S_DEPLOYMENTCONFIG, K8S_DAEMONSET, K8S_STATEFULSET, K8S_REPLICASET, K8S_REPLICATIONCONTROLLER, K8S_JOB, K8S_CRONJOB }
// extract and rename workload metadata for clarity
| fields id, workload.name = name, k8s.workload.kind, k8s.cluster.name, k8s.namespace.name
// filter to specific cluster
| filter k8s.cluster.name == "cluster-keaton"
// exclude system namespaces
| filterOut k8s.namespace.name == "kube-system"
// lookup CPU request for the last few minutes
| lookup [
  // sum CPU requests across containers, and group them by workload name
  timeseries requests_CPU = sum(dt.kubernetes.container.requests_cpu, scalar:true, rate:1m, rollup:sum),
  by:{dt.smartscape.k8s_deployment, dt.smartscape.k8s_deploymentconfig, dt.smartscape.k8s_daemonset, dt.smartscape.k8s_statefulset, dt.smartscape.k8s_replicaset, dt.smartscape.k8s_replicationcontroller, dt.smartscape.k8s_job, dt.smartscape.k8s_cronjob},
  // do NOT use data newer than 2 minutes to ensure stable and complete data
  timeframe: timeframe(from: -10m, to: -2m)
  // use coalesce to find first non-null top-level workload id
  | fieldsAdd id = coalesce(dt.smartscape.k8s_deployment, dt.smartscape.k8s_deploymentconfig, dt.smartscape.k8s_daemonset, dt.smartscape.k8s_statefulset, dt.smartscape.k8s_replicaset, dt.smartscape.k8s_replicationcontroller, dt.smartscape.k8s_job, dt.smartscape.k8s_cronjob)
], sourceField:id, lookupField:id, fields:{requests_CPU}, executionOrder:leftFirst
// same logic for memory requests
| lookup [
  timeseries requests_memory = sum(dt.kubernetes.container.requests_memory, scalar:true, rate:1m, rollup:sum),
  by:{dt.smartscape.k8s_deployment, dt.smartscape.k8s_deploymentconfig, dt.smartscape.k8s_daemonset, dt.smartscape.k8s_statefulset, dt.smartscape.k8s_replicaset, dt.smartscape.k8s_replicationcontroller, dt.smartscape.k8s_job, dt.smartscape.k8s_cronjob},
  timeframe: timeframe(from: -10m, to: -2m)
  | fieldsAdd id = coalesce(dt.smartscape.k8s_deployment, dt.smartscape.k8s_deploymentconfig, dt.smartscape.k8s_daemonset, dt.smartscape.k8s_statefulset, dt.smartscape.k8s_replicaset, dt.smartscape.k8s_replicationcontroller, dt.smartscape.k8s_job, dt.smartscape.k8s_cronjob)
], sourceField:id, lookupField:id, fields:{requests_memory}, executionOrder:leftFirst
// find workloads missing CPU or memory requests.
| filter isNull(requests_CPU) or isNull(requests_memory)

Этот запрос формирует список всех рабочих нагрузок, у которых отсутствуют запросы CPU или памяти. Он специально нацелен на наш кластер и исключает любые сущности из пространства имён kube-system, признавая, что определённые рабочие нагрузки должны быть запланированы независимо от доступности ресурсов ноды.