Мониторинг производительности Kubernetes: инструменты и методы оптимизации

Отслеживайте производительность Kubernetes с помощью полезных метрик, Prometheus, Grafana, kubectl и практических привычек настройки ресурсов.

Мониторинг производительности Kubernetes: инструменты и методы оптимизации

Мониторинг производительности Kubernetes — это не просто наблюдение за графиками CPU. Кластер может показывать низкую среднюю загрузку CPU, в то время как пользователи видят медленные запросы. Pod может иметь достаточно памяти большую часть дня и всё равно быть убитым во время пакетной задачи. Узел может выглядеть здоровым, пока давление на диск не начнёт вытеснять pod'ы. Хороший мониторинг связывает сигналы кластера с тем опытом, который действительно важен для людей: быстр ли сервис, доступен ли он и предсказуем ли?

Первая ошибка — начинать с инструментов, а не с вопросов. Prometheus, Grafana, metrics-server, kube-state-metrics и облачные платформы мониторинга — все они полезны, но они не решают, что важно. Вы решаете это, понимая рабочую нагрузку. Публичный API заботится о задержках и ошибках. Очередной воркер заботится о backlog'е и скорости обработки. Ночная задача заботится о времени завершения и упавших pod'ах. Нагрузка, похожая на базу данных, заботится о задержках диска и давлении памяти.

Для быстрого взгляда kubectl top всё ещё полезен:

kubectl top nodes
kubectl top pods -A
kubectl top pod -n production api-7d9c8f7b9d-2x4mq --containers

Эти команды зависят от metrics-server. Они дают недавнее использование CPU и памяти, а не полную историю. Используйте их во время триажа, а не как единственную систему мониторинга. Если pod перезапустился десять минут назад из-за нехватки памяти, kubectl top может не показать скачок, который это вызвал.

Prometheus — это общая основа для метрик Kubernetes, потому что он собирает данные временных рядов и хорошо работает с обнаружением сервисов Kubernetes. В типичной настройке метрики поступают из нескольких мест. Kubelet предоставляет метрики ресурсов контейнеров и pod'ов. cAdvisor, интегрированный с kubelet, предоставляет данные CPU, памяти, файловой системы и сети контейнеров. node-exporter сообщает метрики на уровне хоста. kube-state-metrics преобразует состояние объектов Kubernetes в метрики: желаемые реплики, доступные реплики, фазы pod'ов, состояния узлов и многое другое.

Затем Grafana превращает эти метрики в дашборды. Хороший дашборд — это не стена датчиков. Он должен быстро отвечать на конкретные вопросы: какой сервис медленный, какие pod'ы троттлятся, какие узлы находятся под давлением, какой Deployment не может выполнить развёртывание и успевает ли автоскейлинг.

Начните с уровня приложения. Для пользовательских сервисов наиболее важными сигналами являются скорость запросов, частота ошибок и задержка. Если у вас есть SLO, отобразите их на графиках. График CPU pod'а не скажет вам, падает ли оформление заказа. Метрики приложения — скажут. Инструментируйте сервисы с помощью клиентских библиотек Prometheus, OpenTelemetry или системы мониторинга, которую уже использует ваша платформа. Метрики Kubernetes объясняют, почему сервис нездоров; метрики приложения говорят вам, что он нездоров.

Затем свяжите симптомы приложения с ресурсами pod'ов. Использование CPU легко неправильно истолковать в Kubernetes. Контейнер с лимитом CPU может быть троттлирован, даже если средний CPU не выглядит драматично. Троттлинг происходит, когда контейнер пытается использовать больше времени CPU, чем позволяет его лимит в течение периода планирования. Для приложений, чувствительных к задержкам, это может вызывать медленные запросы, которые кажутся случайными.

Полезный запрос PromQL для троттлинга:

rate(container_cpu_cfs_throttled_periods_total{namespace="production", container!=""}[5m])

Растущее значение означает, что контейнер троттлится. Сопоставьте его с использованием CPU и задержкой запросов. Если всплески задержки совпадают с троттлингом, рассмотрите возможность увеличения или удаления лимита CPU, увеличения реплик или оптимизации кода. Некоторые команды устанавливают запросы CPU, но избегают лимитов CPU для сервисов, чувствительных к задержкам, полагаясь вместо этого на запросы, автоскейлинг и контроль ёмкости узлов. Это может быть разумным, но требует дисциплины на уровне кластера, чтобы шумные рабочие нагрузки не истощали другие.

Память ведёт себя иначе. CPU можно троттлить; память нельзя замедлить таким же образом. Если контейнер превышает лимит памяти, он может быть убит OOM. Ищите причины перезапуска:

kubectl describe pod -n production api-7d9c8f7b9d-2x4mq
kubectl get pod -n production api-7d9c8f7b9d-2x4mq -o jsonpath='{.status.containerStatuses[*].lastState}'

В Prometheus следите за рабочей памятью и сравнивайте её с лимитами:

container_memory_working_set_bytes{namespace="production", container!=""}

Не настраивайте память на основе одного тихого часа. Смотрите на пиковый трафик, окна пакетных задач, развёртывания и поведение сборщика мусора. Сервисы на Java, Go, Node.js и Python имеют разные профили памяти. Лимит, который выглядит щедрым при нормальном трафике, может быть слишком жёстким во время запуска, прогрева кэша или большого запроса.

Запросы ресурсов важны, потому что планировщик использует их для размещения pod'ов. Если запросы слишком низкие, Kubernetes может упаковать слишком много занятых pod'ов на один узел. Всё выглядит эффективно, пока эти pod'ы не станут заняты одновременно. Если запросы слишком высоки, кластер тратит ёмкость впустую, и автоскейлинг может добавлять узлы раньше, чем нужно. Лучший запрос обычно основан на наблюдаемом использовании плюс запас, а не на скопированном значении из другого сервиса.

Vertical Pod Autoscaler может помочь, рекомендуя запросы на основе исторического использования. Многие команды сначала запускают VPA в режиме рекомендаций, потому что автоматические обновления могут перезапускать pod'ы в зависимости от конфигурации и типа рабочей нагрузки. Относитесь к рекомендациям как к вводу, а не закону. Сервис с редкими, но важными всплесками может нуждаться в большем запасе, чем предполагает его средняя история.

Horizontal Pod Autoscaler полезен, когда больше реплик действительно улучшает пропускную способность. Он хорошо работает для stateless веб-сервисов и воркеров, которые могут разделять нагрузку. Он не исправляет однопоточное узкое место, блокировку базы данных или зависимость downstream, которая уже насыщена.

Базовый HPA может выглядеть так:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api
  minReplicas: 3
  maxReplicas: 20
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70

Отслеживайте поведение HPA, а не только количество реплик. Если он постоянно масштабируется вверх и вниз, настройте окна стабилизации, цели или метрику. Если он достигает maxReplicas, а задержка всё ещё плохая, проблема может быть в ёмкости, коде или зависимости. Если он никогда не масштабируется, хотя pod'ы явно перегружены, проверьте доступность метрик и установлены ли запросы. Цели использования CPU зависят от запросов CPU; отсутствующие или нереалистичные запросы могут сделать автоскейлинг вводящим в заблуждение.

Здоровье узлов — следующий уровень. Проблема pod'а, которая проявляется во многих сервисах на одном узле, обычно является проблемой узла. Следите за насыщением CPU, средней нагрузкой, доступной памятью, давлением на диск, использованием inode, задержками файловой системы, сетевыми ошибками и здоровьем kubelet. Состояния узлов, такие как MemoryPressure, DiskPressure и PIDPressure, должны быть видны на дашбордах и в оповещениях.

Используйте kubectl describe node, когда узел выглядит подозрительно:

kubectl describe node worker-12

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

Мониторинг control plane важен, даже если ваши pod'ы приложений выглядят нормально. Задержка API-сервера может замедлить развёртывания, автоскейлинг и контроллеры. Задержка etcd или проблемы с диском могут сделать весь кластер вялым. Проблемы с controller manager и scheduler могут задерживать размещение pod'ов или согласование. В управляемом Kubernetes вы можете не видеть каждый компонент control plane, но облачные провайдеры обычно предоставляют некоторые метрики здоровья и задержки API.

События полезны во время инцидентов, но они не являются долгосрочным хранилищем метрик. Тем не менее, они часто объясняют, что только что произошло:

kubectl get events -A --sort-by=.lastTimestamp

Ищите неудачное планирование, ошибки вытягивания образов, сбои проб, вытеснения и сообщения о back-off. События могут быть шумными, поэтому при необходимости фильтруйте по namespace или задействованному объекту.

Пробы заслуживают тщательного мониторинга. Слишком агрессивные liveness-пробы могут перезапустить медленное, но восстанавливающееся приложение и усугубить инцидент. Readiness-пробы, которые правильно срабатывают, могут защитить пользователей, удаляя плохие pod'ы из обслуживания. Отслеживайте сбои проб и коррелируйте их с троттлингом CPU, паузами GC, таймаутами downstream и развёртываниями.

Для рабочих нагрузок с интенсивным хранением данных CPU и памяти контейнера недостаточно. Следите за задержкой постоянного тома, пропускной способностью диска, глубиной очереди и заполненностью файловой системы. Pod, ожидающий медленного хранилища, может показывать низкий CPU, потому что он заблокирован. Если база данных или очередь работают на Kubernetes, метрики хранения являются частью производительности приложения, а не инфраструктурной мелочью.

Практический путь устранения неполадок начинается широко и сужается. Сначала подтвердите симптом, видимый пользователю: задержка, ошибки, неудачные задачи или backlog. Во-вторых, определите область: один pod, один Deployment, один узел, один namespace или весь кластер. В-третьих, проверьте недавние изменения: развёртывания, обновления конфигурации, активность автоскейлера, ротации узлов или всплески трафика. В-четвёртых, проверьте поведение ресурсов pod'а: троттлинг CPU, давление памяти, перезапуски и сбои проб. В-пятых, проверьте здоровье узлов и зависимостей.

Оповещения должны избегать пробуждения людей из-за безвредного шума. Сначала оповещайте о влиянии на пользователя: высокая частота ошибок, высокая задержка, пропущенный срок задачи, растущий возраст очереди. Затем оповещайте о сильных опережающих индикаторах: частые OOMKill, устойчивый троттлинг CPU на сервисах, чувствительных к задержкам, pod'ы недоступны ниже желаемых реплик, давление на узел, постоянные ожидающие pod'ы и HPA, застрявший на максимальных репликах, в то время как метрики сервиса плохие.

Цель — не идеальная утилизация. Кластер, работающий на 95% использования ресурсов весь день, может выглядеть эффективным, пока один узел не выйдет из строя и не останется места для перепланировки pod'ов. Оставьте ёмкость для развёртываний, повторных попыток, всплесков трафика и сбоев. Оптимизация должна сокращать отходы, не удаляя буфер, который удерживает инциденты небольшими.

Хороший мониторинг производительности Kubernetes ощущается практичным. Вы можете открыть дашборд и увидеть здоровье сервиса, здоровье pod'ов, здоровье узлов и поведение масштабирования, не охотясь по двадцати вкладкам. Вы можете ответить, является ли замедление кодом, лимитами ресурсов, давлением на узел, хранилищем, сетью или control plane. И когда вы меняете запросы, лимиты или автоскейлинг, вы можете увидеть, помогло ли изменение, вместо того чтобы гадать.

Представления на уровне namespace полезны, когда много команд делят один кластер. Одна команда может не видеть надвигающегося насыщения узла, если они смотрят только на свои собственные Deployment'ы. Платформенные команды должны предоставлять дашборды, показывающие запросы CPU и памяти namespace, фактическое использование, количество pod'ов, перезапуски и троттлинг. Это делает разговоры о ёмкости менее эмоциональными. Вместо того чтобы говорить, что команда использует "слишком много", вы можете показать тренды запросов, пиковое использование и отходы.

Оптимизация затрат должна следовать за сигналами надёжности, а не предшествовать им. Если у сервиса никогда не настраивались запросы, вы можете найти лёгкую экономию. Но агрессивное сокращение запросов может создать давление на планирование и проблемы с шумными соседями. Хороший процесс меняет один класс рабочей нагрузки за раз, следит за задержкой и перезапусками и оставляет заметки для отката. Относитесь к настройке ресурсов как к производственному коду: небольшие изменения, измеренные результаты.

Сами развёртывания могут создавать инциденты производительности. Развёртывание, которое заменяет слишком много pod'ов одновременно, может перегрузить холодные кэши, пулы соединений или downstream-сервисы. Следите за продолжительностью развёртывания, недоступными репликами и задержкой приложения во время развёртываний. Настройте maxSurge и maxUnavailable в зависимости от того, как сервис ведёт себя во время запуска. Сервис с медленным прогревом может нуждаться в консервативном развёртывании, даже если производительность в стабильном состоянии хорошая.

strategy:
  type: RollingUpdate
  rollingUpdate:
    maxSurge: 1
    maxUnavailable: 0

Эта настройка не является универсально лучшей, но она показывает компромисс: более медленное развёртывание, большая защита от падений ёмкости. Для stateless сервиса, который запускается мгновенно, вы можете выбрать более быстрое развёртывание. Для JVM-сервиса, прогревающего кэши и открывающего множество downstream-соединений, более медленный может быть безопаснее.

Следите за кардинальностью в метриках. Метки Kubernetes заманчивы, но метки с высокой кардинальностью, такие как UID pod'а, ID запроса или ID пользователя, могут сделать Prometheus дорогим и медленным. Используйте метки, которые помогают агрегировать: namespace, рабочая нагрузка, pod, контейнер, узел, код статуса, шаблон маршрута. Избегайте меток, которые создают новый временной ряд для каждого пользователя или каждого запроса. Мониторинг не должен становиться тем, что вредит производительности кластера.

Логи и трейсы завершают картину. Метрики говорят вам, что задержка увеличилась; трейсы могут показать, какой downstream-вызов замедлился; логи могут показать точную ошибку или таймаут. OpenTelemetry обычно используется для соединения этих сигналов, но инструмент менее важен, чем корреляция. Используйте согласованные имена сервисов, namespace, версии и ID трейсов, чтобы вы могли перейти от оповещения к соответствующим логам без гаданий.

Для пакетных и воркерных систем следите за возрастом backlog'а, а не только за CPU pod'а. Воркер очереди может быть здоров на уровне pod'а, но отставать, потому что входящая работа превышает мощность обработки. Метрики, такие как возраст самого старого сообщения, количество выполненных задач в минуту, повторные попытки и количество dead-letter, часто важнее, чем использование контейнера. HPA может масштабироваться на основе пользовательских или внешних метрик, когда CPU является неправильным сигналом.

Пересматривайте дашборды после инцидентов. Если реагирующим пришлось выполнить пять ручных команд, чтобы ответить на один и тот же вопрос, этот вопрос должен быть на дашборде или в runbook. Мониторинг становится лучше через использование. Цель — не предсказать каждый сбой; это сделать следующее расследование короче и менее зависимым от памяти одного человека.