Вы выполнили kubectl describe pod. Ваша панель мониторинга показывает 40 миллиядер, запрошенных для контейнера api-server. Вы, вероятно, решили, что ваша система мониторинга обладает полной картиной.
На самом деле планировщик Kubernetes зарезервировал для него 1000 миллиядер — целое ядро CPU.
Как оба значения могут быть верными?
Если вы занимаетесь подбором размеров кластеров, созданием моделей затрат или настройкой порогов HPA на основе значений для отдельных контейнеров, вы можете использовать неверные цифры — в данном случае в 25 раз меньше, чем то, что на самом деле выделил кластер.
Первым признаком часто является неожиданное поведение кластера: поды не могут быть запланированы, хотя на узлах, по-видимому, есть свободные ресурсы, или рабочие нагрузки вытесняются, несмотря на запросы ресурсов, которые на бумаге выглядят безопасными.
В этой статье объясняется, почему существует это расхождение, когда оно проявляется и как Dynatrace автоматически выявляет правильное число — включая новую функцию Kubernetes 1.34, которую, насколько нам известно, еще не реализовала ни одна другая платформа мониторинга.
Число, которое Kubernetes вам не сообщает
Когда вы выполняете kubectl describe pod, вы видите запросы и лимиты ресурсов для каждого контейнера. Чего вы не видите, так это эффективного значения на уровне пода — реального объема CPU и памяти, который Kubernetes вычисляет для пода в целом. Оно вычисляется дважды для двух разных целей: один раз для запросов (requests), которые планировщик использует для определения места размещения пода; и один раз для лимитов (limits), которые 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-контейнер доминирует, и простое суммирование всех контейнеров приложений может дать катастрофически неверный ответ.
Когда доминирует 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 (Out of Memory), хотя в представлении на уровне контейнеров ничто не предвещает такой проблемы.
Запросы (requests) на уровне пода меняются только в одном направлении: Kubernetes отклоняет запрос на уровне пода, если он ниже суммы запросов контейнеров, поэтому он может увеличить резервирование планировщика, но никогда не может его уменьшить.
Важный нюанс: ресурсы на уровне пода переопределяются для каждого имени ресурса и каждого направления — запросы и лимиты независимы. Под может устанавливать запросы памяти на уровне пода, в то время как лимит памяти по-прежнему определяется формулой на уровне контейнеров, поэтому в одном поде могут одновременно действовать до четырех комбинаций.
Dynatrace обрабатывает оба направления с помощью одной и той же компенсирующей дельты: отрицательной, когда ограничение на уровне пода снижает общий показатель, и положительной, когда запрос на уровне пода его повышает. Независимо от того, резервирует ли Kubernetes больше, чем предполагают определения ваших контейнеров, или применяет меньшие ограничения, то, что вы видите в Dynatrace, отражает реальное поведение кластера, предоставляя платформенным командам точную базу для принятия любых решений по емкости.
Смотрите фактическое значение, а не просто сумму контейнеров
В следующий раз, когда вы выполните kubectl describe pod и увидите 40 м, вы будете знать, о чем эта команда умалчивает. Kubernetes всегда вычислял фактическое значение на уровне пода как для запросов, так и для лимитов — фактические запросы использовались планировщиком для каждого решения о размещении с момента запуска первого пода миграции на узле; фактические лимиты — это то, что kubelet применяет во время выполнения. Ваш мониторинг просто не отслеживал ни то, ни другое.
Поскольку PodLevelResources теперь по умолчанию находится в бета-версии в Kubernetes 1.34, каждая команда, внедряющая ресурсы на уровне пода — для эффективности упаковки (bin-packing), изоляции мультиарендности или более жесткого контроля кластера — работает с моделью ресурсов, где представления на уровне контейнеров могут не учитывать фактическое значение на уровне пода, если платформа не вычисляет его явно.
Dynatrace вычисляет правильное число при приеме данных — для каждого пода — без необходимости настройки.
Хотите увидеть это в действии? В Dynatrace K8s playground есть работающий кластер, где вы можете прямо сейчас выполнить указанный выше запрос и самостоятельно найти строки с дельтой null-контейнера.






