Вы выполнили kubectl describe pod. Ваша панель мониторинга показывает 40 миллиядер, запрошенных для контейнера api-server. Вероятно, вы решили, что система мониторинга обладает полной картиной.
На самом деле планировщик Kubernetes зарезервировал для него 1000 миллиядер — целое ядро CPU.
Как оба этих значения могут быть верными?
Если вы занимаетесь подбором размера кластеров, созданием моделей затрат или настройкой порогов HPA на основе значений для каждого контейнера, вы можете использовать неверные данные — в данном случае в 25 раз меньше того, что кластер фактически выделил.
Первым признаком часто является неожиданное поведение кластера: поды не могут быть запланированы, даже если на узлах есть свободные ресурсы, или рабочие нагрузки вытесняются, несмотря на запросы ресурсов, которые на бумаге выглядят безопасными.
В этой статье объясняется, почему возникает такое расхождение, когда оно проявляется и как Dynatrace автоматически выявляет правильное число, включая новую функцию Kubernetes 1.34, которую, насколько нам известно, еще не поддерживает ни одна другая платформа мониторинга.
Число, которое Kubernetes вам не сообщает
Когда вы выполняете kubectl describe pod, вы видите запросы и лимиты ресурсов для каждого контейнера. Чего вы не видите, так это эффективного значения на уровне пода — реального объема CPU и памяти, который Kubernetes вычисляет для пода в целом. Оно вычисляется дважды для двух разных целей: один раз для запросов, которые планировщик использует для определения места размещения пода; и один раз для лимитов, которые kubelet принудительно применяет через cgroups после запуска пода. Это значение можно считать из cgroup пода на самом узле, что требует привилегированного доступа к хосту, но даже в этом случае вы не получите данных в разрезе контейнеров.
Команда kubectl describe pod показывает только значения для каждого контейнера — общего итога на уровне пода нет. Команда kubectl describe node ближе к истине, правильно суммируя эффективное значение каждого пода, хотя в kubectl 1.35 и более ранних версиях это перестает работать, как только заданы ресурсы на уровне пода. В любом случае, это не поле в API Kubernetes; вам все равно придется вычислять его самостоятельно.
Kubernetes никогда не упрощал поиск этого значения. Вам пришлось бы читать спецификацию, реализовывать формулу и применять ее к каждому поду. Исходные спецификации доступны: лимиты init-контейнеров, лимиты контейнеров приложений, ресурсы на уровне пода. Но Kubernetes не предоставляет эффективное значение на уровне пода, полученное путем применения формулы планирования к этим спецификациям.
Инструменты мониторинга должны вычислять его самостоятельно на основе данных по каждому контейнеру. Dynatrace делает это при приеме данных для каждого пода по умолчанию, включая случай с ресурсами на уровне пода в Kubernetes 1.34, который, насколько нам известно, на момент написания статьи не обрабатывает ни одна другая платформа наблюдаемости (см. раздел «Еще один нюанс: ресурсы на уровне пода» ниже).
Под Kubernetes может содержать три типа контейнеров (четыре, если считать эфемерные отладочные контейнеры, но они не участвуют в учете ресурсов):
Поскольку обычные init-контейнеры должны завершить работу до запуска любого контейнера приложения, они никогда не работают одновременно с вашим приложением. Под с init-контейнером на 1000m и контейнером приложения на 40m никогда не будет потреблять 1040m. Но в разные моменты своего жизненного цикла ему потребуется 1000m (во время инициализации) и 40m (во время работы).
Для большинства подов init-контейнеры — это легковесные утилиты: проверка готовности, получение секретов, рендеринг конфигурации. Формула становится важной, когда init-контейнер выполняет более тяжелую работу, чем приложение, которое он предваряет. Например, миграция базы данных.
Формула одинакова как для запросов, так и для лимитов. Эффективное значение — это большее из этих двух фаз:
Для пода, где самый тяжелый init-контейнер превышает общую сумму фазы выполнения, init-контейнер доминирует, и простое суммирование всех контейнеров приложения может дать катастрофически неверный результат.
Когда доминирует инициализация: паттерн миграции базы данных
Возьмем пример миграции базы данных. В Kubernetes хорошо зарекомендовал себя паттерн запуска миграции схемы в качестве init-контейнера, чтобы гарантировать актуальность базы данных перед запуском приложения. Задача миграции может быть значительно более ресурсоемкой, чем контейнер приложения, который она предваряет.
Если вы когда-нибудь задавались вопросом, почему узел выглядит более загруженным, чем показывают метрики вашего приложения, проверьте, не запущены ли на нем поды миграции. Вот типичный пример:
Эффективные запросы составляют:
- CPU: MAX(1000m, 40m) = 1000m
- Память: MAX(512Mi, 64Mi) = 512Mi
Планировщик зарезервировал целое ядро CPU и 512 МБ памяти для этого пода, даже если контейнеру приложения требуется лишь малая часть этого объема в течение всего времени работы. Любой инструмент мониторинга, сообщающий о запросах на уровне контейнера, покажет 40m CPU. Эффективное резервирование в 25 раз больше.
Планировщик должен учитывать пик фазы инициализации, а не только общую сумму фазы выполнения — init-контейнер должен где-то выполниться, прежде чем приложение сможет запуститься.
Как Dynatrace вычисляет и отображает эффективное значение
Dynatrace хранит метрики ресурсов на уровне контейнера (dt.kubernetes.container.requests_cpu, dt.kubernetes.container.requests_memory и их эквиваленты для лимитов). Контейнеры приложений и встроенные sidecar-контейнеры сохраняют свои собственные строки. Пик фазы инициализации отображается как дельта на уровне пода, относящаяся к поду, а не к отдельному init-контейнеру.
Для большинства подов агрегирование по контейнерам дает правильный итог. Но когда простое суммирование приводит к неверному эффективному значению, Dynatrace при приеме данных добавляет компенсирующую дельту в ту же метрику.
Дельта корректирует сумму, не изменяя точку данных ни одного отдельного контейнера. Для пода миграции выше:
Для SRE или архитектора платформы это означает, что решения о емкости кластера принимаются на основе числа, которое действительно имеет значение: резервирования планировщика, а не подмножества фазы выполнения. Независимо от того, подбираете ли вы размер пулов узлов, планируете запас ресурсов или решаете, когда масштабировать кластер, вы работаете с тем, что действительно нужно Kubernetes, а не с заниженным числом, которое делает кластер более свободным, чем он есть на самом деле.
Просмотр в DQL
Хотите проверить, не влияет ли это скрыто на какие-либо ваши рабочие нагрузки прямо сейчас? Вот запрос:
Запрашивайте по одному ресурсу за раз. Запрос двух метрик в одной команде timeseries выравнивает ряды по обоим параметрам и удаляет строку сверки для любого пода, чье расхождение затрагивает только один ресурс.
С учетом компенсирующей дельты вы увидите одну строку на контейнер плюс дополнительную строку с пустым именем и типом контейнера. Эта пустая строка и есть дельта. Просуммируйте все строки для пода, чтобы получить правильные эффективные значения.
Еще один нюанс: ресурсы на уровне пода
В Kubernetes 1.34 функция PodLevelResources перешла в стадию бета-тестирования и включена по умолчанию. Она позволяет командам платформы устанавливать запросы и лимиты ресурсов непосредственно на уровне пода, а не распределять их по отдельным контейнерам. Это полезный инструмент для подов с несколькими контейнерами, где вы хотите ограничить общее потребление, не предписывая точное распределение по каждому контейнеру.
Сумма ресурсов контейнеров здесь составляет 100 м CPU / 60 МиБ памяти, но ограничение на уровне пода — 80 м / 50 МиБ. Это и есть эффективный лимит пода, который kubelet применяет к cgroup пода. Инструмент, суммирующий лимиты отдельных контейнеров, показывает потолок на 25% выше фактически действующего: эти два контейнера никогда не смогут суммарно потребить 100 м. С памятью ситуация еще острее: каждый контейнер выглядит безопасным при 30 МиБ, но cgroup пода ограничивает их суммарно 50 МиБ, поэтому, если оба приблизятся к своим индивидуальным лимитам, под будет завершен по OOM, при этом в представлении на уровне контейнеров не будет никаких признаков, предвещающих это.
Запросы на уровне пода меняются только в одном направлении: Kubernetes отклоняет запрос на уровне пода, если он ниже суммы запросов контейнеров, поэтому он может увеличить резервирование планировщика, но никогда не может его уменьшить.
Стоит учитывать один нюанс: ресурсы на уровне пода переопределяются для каждого имени ресурса и каждого направления — запросы и лимиты независимы. Под может устанавливать запросы памяти на уровне пода, в то время как лимит памяти по-прежнему определяется формулой на уровне контейнеров, поэтому в одном поде могут одновременно действовать до четырех комбинаций.
Dynatrace обрабатывает оба направления с помощью одной и той же компенсирующей дельты: отрицательной, когда ограничение на уровне пода снижает общую сумму, и положительной, когда запрос на уровне пода ее увеличивает. Независимо от того, резервирует ли Kubernetes больше, чем предполагают определения ваших контейнеров, или применяет меньшие ограничения, то, что вы видите в Dynatrace, отражает реальное поведение кластера, предоставляя платформенным командам точную базу для принятия любых решений по емкости.
Смотрите эффективное значение, а не просто сумму контейнеров
В следующий раз, когда вы выполните kubectl describe pod и увидите 40 м, вы будете знать, о чем эта команда умалчивает. Kubernetes всегда вычислял эффективное значение на уровне пода как для запросов, так и для лимитов — эффективные запросы — это то, что ваш планировщик использовал для каждого решения о размещении с момента появления первого пода миграции на узле; эффективные лимиты — это то, что kubelet применяет во время выполнения. Ваш мониторинг просто не отслеживал ни то, ни другое.
Поскольку PodLevelResources теперь по умолчанию находится в бета-версии в Kubernetes 1.34, каждая команда, внедряющая ресурсы на уровне пода — для эффективности упаковки (bin-packing), изоляции мультиарендности или более строгого контроля кластера — работает с моделью ресурсов, где представления на уровне контейнеров могут упускать эффективное значение на уровне пода, если платформа не вычисляет его явно.
Dynatrace вычисляет правильное число при приеме данных — для каждого пода — без необходимости настройки.
Хотите увидеть это в действии? В Dynatrace K8s playground есть работающий кластер, где вы можете прямо сейчас выполнить приведенный выше запрос и самостоятельно найти строки дельты для null-контейнеров.







