8 lines
17 KiB
JSON
8 lines
17 KiB
JSON
{
|
||
"index": 129,
|
||
"slug": "editorial-2024-06-practice-container-orchestration",
|
||
"title": "Контейнерная нагрузка: как связать ресурсы, готовность и автомасштабирование",
|
||
"excerpt": "Почему Pod может успешно разместиться, но не выдержать трафик: разбираем requests и limits, readiness и HPA на одном учебном примере.",
|
||
"contentHtml": "<p>После выкладки Pod получает статус Running, но запросы к сервису ждут дольше обычного. Иногда HPA увеличивает число реплик, а доступных backend не становится больше: новые Pod остаются неготовыми. В другой версии проблемы readiness отвечает успешно ещё до прогрева, и Service отправляет трафик в приложение, которое не держит рабочую нагрузку. Цена ошибки — задержки для клиентов, лишние реплики и трудный откат. Команда видит зелёный rollout и ищет причину уже под нагрузкой.</p>\n<p>Тезис простой: requests, limits, readiness и HPA описывают разные границы. Их нельзя настраивать как четыре независимые строки в манифесте. Сначала нужно назвать профиль приложения и единицу работы. Затем связать каждую границу с проверяемым сигналом. Тогда Kubernetes размещает Pod по одному правилу, допускает его к трафику по другому, а autoscaler меняет replicas по третьему. Это не даёт готовых чисел для любого сервиса. Зато не позволяет принять один сигнал за другой.</p>\n<h2>Симптом → причина → проверка → действие</h2>\n<div class=\"table-scroll\"><table><caption>Как отделить похожие симптомы</caption><thead><tr><th scope=\"col\">Симптом</th><th scope=\"col\">Причина</th><th scope=\"col\">Проверка</th><th scope=\"col\">Действие</th></tr></thead><tbody><tr><td>Pod долго остаётся Pending</td><td>Request не помещается на доступный Node или не учтены requests всех containers.</td><td>Сверить request каждого container с событиями scheduler и allocatable Node.</td><td>Исправить размер или размещение только после проверки профиля.</td></tr><tr><td>Pod Running, но сервис медленный</td><td>Running означает процесс, а не готовность и не capacity.</td><td>Сравнить Ready, readiness probe, latency и очередь за один период.</td><td>Разделить контракт готовности и гипотезу о ресурсе.</td></tr><tr><td>HPA меняет replicas без эффекта</td><td>Новые Pod не готовы, либо CPU target считается от неверного request.</td><td>Проверить effective request, metric source, Ready и время прогрева.</td><td>Не повышать maxReplicas, пока не исправлен сигнал.</td></tr><tr><td>Container получает OOMKilled</td><td>Memory limit ограничивает container, но не описывает жизненный цикл cache или объектов.</td><td>Сопоставить предел, рост working set и действие приложения при нехватке памяти.</td><td>Изменять limit вместе с политикой роста и восстановления.</td></tr></tbody></table></div>\n<h2>Как устроена связка</h2>\n<p><strong>Request</strong> — заявка на ресурс для размещения. Scheduler использует её, когда выбирает Node. Для Pod ресурсная заявка складывается из заявок его containers. Request не равен фактическому потреблению. Container может использовать больше request, если на Node есть свободный ресурс и limit это позволяет. Поэтому фраза «у Pod есть 500m CPU» неполна: нужно сказать, это request, limit или наблюдаемое usage.</p>\n<p><strong>Limit</strong> — верхняя граница исполнения для container. Он не обещает пропускную способность и не является знаменателем CPU utilization HPA. Для CPU превышение limit ограничивает доступ к CPU. Для memory превышение может закончиться убийством container. Применение зависит от ресурса и среды, поэтому нельзя переносить правило для CPU на memory. Если limit задан без request, конкретная admission-политика может использовать limit как request. Effective значения нужно увидеть в разрешённой проверке, а не угадывать по шаблону.</p>\n<p><strong>Readiness</strong> отвечает на узкий вопрос: можно ли сейчас отправлять трафик в этот container. При failed readiness Kubernetes убирает Pod из EndpointSlice соответствующего Service. Probe не измеряет запас capacity, throughput и качество каждого ответа. Не стоит включать в неё все внешние зависимости без явной политики отказа: краткий сбой одной зависимости способен вывести из трафика все реплики. Обратная ошибка не менее опасна: слишком ранний success пускает запросы до окончания прогрева.</p>\n<p><strong>HPA</strong> периодически меняет desired replicas по наблюдаемой метрике. Для CPU utilization в процентах важен request, к которому относится usage. Target 70 процентов — это не 70 процентов Node и не 70 процентов limit. Если request отсутствует там, где он нужен для расчёта, вывод по такой метрике нельзя считать осмысленным. При этом новый Pod ещё должен пройти startup и readiness. Автомасштабирование не может исправить неверный healthcheck и не сокращает время загрузки большого cache.</p>\n<h2>Учебный манифест</h2>\n<p>Ниже — ограниченный пример для HTTP-сервиса с устойчивой CPU-нагрузкой после прогрева. Значения не описывают реальный production workload. Они нужны, чтобы увидеть отношения между полями. Здесь request равен 500m, limit — 1000m, а target HPA относится к request. Startup probe отделяет запуск от liveness и readiness. Readiness проверяет локальный признак готовности приложения, а не весь внешний мир.</p>\n<pre><code>apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: app\nspec:\n replicas: 2\n selector:\n matchLabels:\n app: app\n template:\n metadata:\n labels:\n app: app\n spec:\n containers:\n - name: app\n image: registry.example/app:1.4.0\n resources:\n requests:\n cpu: \"500m\"\n memory: \"384Mi\"\n limits:\n cpu: \"1000m\"\n memory: \"768Mi\"\n startupProbe:\n httpGet: { path: /startup, port: 8080 }\n failureThreshold: 30\n periodSeconds: 2\n readinessProbe:\n httpGet: { path: /ready, port: 8080 }\n periodSeconds: 5\n livenessProbe:\n httpGet: { path: /live, port: 8080 }\n periodSeconds: 10\n---\napiVersion: autoscaling/v2\nkind: HorizontalPodAutoscaler\nmetadata:\n name: app\nspec:\n scaleTargetRef:\n apiVersion: apps/v1\n kind: Deployment\n name: app\n minReplicas: 2\n maxReplicas: 6\n metrics:\n - type: Resource\n resource:\n name: cpu\n target:\n type: Utilization\n averageUtilization: 70</code></pre>\n<p>Этот манифест не доказывает, что 500m достаточно. Он задаёт гипотезу: после прогрева одна реплика выдерживает согласованную steady-нагрузку, а CPU — её главный ограничитель. Если в реальности первым растёт memory или очередь, HPA по CPU не решает проблему. Если /ready отвечает до открытия рабочих пулов, реплики формально Ready, но фактически бесполезны. Отрицательный путь важен: когда гипотеза не подтверждается, нужно остановить изменение чисел и пересмотреть профиль, а не автоматически добавлять replicas.</p>\n<h2>Иллюстрация границ Pod</h2>\n<figure><img src=\"/assets/editorial/2024/container-orchestration-2024-pod-lifecycle.svg\" alt=\"Связь профиля приложения, ресурсов и состояний Pod\" loading=\"lazy\" /><figcaption>Схема отделяет состояния Pending, Running, Ready и Unready от границ ресурсов. Она иллюстрирует порядок рассуждения и не показывает данные живого кластера.</figcaption></figure>\n<p>Схему полезно читать слева направо. Профиль задаёт вопрос к manifest: что является единицей работы и какой ресурс ограничивает её первым. Request влияет на размещение. Limit ограничивает исполнение. Только затем readiness отвечает на вопрос о допуске к Service traffic. HPA использует свою метрику и свой знаменатель. Перепрыгнуть через профиль нельзя: иначе одинаковый target будет означать разные вещи для CPU-bound HTTP, memory-retaining cache и batch-задачи.</p>\n<h2>Проверка на конкретном workload</h2>\n<ol><li><strong>Назовите workload.</strong> Запишите owner, режим serving или batch, обычный startup, рабочую единицу и dominant resource. Фраза «сервис нагружен» для этого слишком расплывчата.</li><li><strong>Разберите каждый container.</strong> Выпишите CPU и memory request и limit отдельно. Укажите, складываются ли значения нескольких containers. Разрешённым способом проверьте admission defaults и effective manifest.</li><li><strong>Зафиксируйте readiness contract.</strong> Одним предложением опишите, что значит «можно принять запрос». Отдельно назовите случаи, когда Pod должен стать Unready, и случаи, которые не должны выводить его из трафика.</li><li><strong>Проверьте startup и liveness.</strong> Убедитесь, что долгая инициализация не выглядит как зависший процесс. Liveness должна обнаруживать неисправимое состояние, а не временную очередь или медленную внешнюю зависимость.</li><li><strong>Опишите метрику HPA.</strong> Запишите тип метрики, источник, request-знаменатель для utilization, minReplicas, maxReplicas и поведение при missing или not-yet-ready Pod. Не подменяйте эту проверку значением из учебного примера.</li><li><strong>Соберите ограниченное evidence.</strong> Выберите среду, владельца, временное окно и разрешённые источники. Сопоставьте Ready, usage, restart, latency и queue с одной гипотезой. Не объединяйте их в безымянное «состояние Pod».</li><li><strong>Измените одну границу.</strong> Задайте stop condition и rollback для request, limit, probe или HPA. После изменения повторите ту же проверку. Если сигнал не изменился, вернитесь к причине, а не к следующему коэффициенту.</li></ol>\n<h2>Ограничения и отрицательный путь</h2>\n<p>Эта схема не назначает ресурсы настоящему сервису и не подтверждает состояние кластера. Она не читает Metrics API, события Node, EndpointSlice, controller flags, feature gates, логи или trace. Официальные документы описывают механизм Kubernetes, но не знают версию, admission-политику и настройки конкретной среды. Поэтому учебные 500m, 384Mi, 70 процентов и два Pod нельзя выдавать за результат измерения.</p>\n<p>Есть и граница применимости. HPA по CPU подходит не каждому workload. Для memory-heavy приложения нужно отдельно описать рост рабочего набора и способ его ограничить. Для batch часто важнее размер очереди и время обработки. Для сервиса с дорогим warmup нужно учитывать startup и скорость появления Ready backend. Если исходный сигнал не объясняет цену ошибки, корректное действие — не менять manifest. Сначала нужно получить недостающее разрешённое наблюдение или признать задачу неготовой к настройке.</p>\n<h2>Проверяемый критерий готовности</h2>\n<p>Конфигурация готова к обсуждению, когда для одного workload существует короткая карточка: request и limit каждого container, смысл readiness, условие startup, назначение liveness, metric HPA и её знаменатель, источник evidence, stop condition и rollback. Проверка должна связать каждое поле с одним наблюдаемым вопросом. После учебного прогона готовность не означает «Pod зелёный». Она означает, что команда может объяснить, какой сигнал изменился, почему это подтверждает или опровергает гипотезу и что произойдёт при отрицательном результате.</p>\n<h2>Проверяемые источники</h2><ul><li><a href=\"https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/\" target=\"_blank\" rel=\"noopener noreferrer\">Kubernetes: Resource Management for Pods and Containers</a> — requests используются scheduler, limits применяются к работающему container.</li><li><a href=\"https://kubernetes.io/docs/concepts/workloads/pods/probes/\" target=\"_blank\" rel=\"noopener noreferrer\">Kubernetes: Liveness, Readiness, and Startup Probes</a> — readiness управляет допуском к трафику, startup отделяет инициализацию от других probe.</li><li><a href=\"https://kubernetes.io/docs/concepts/workloads/autoscaling/\" target=\"_blank\" rel=\"noopener noreferrer\">Kubernetes: Autoscaling Workloads</a> — HPA меняет число реплик по наблюдаемой у workload метрике.</li></ul>"
|
||
}
|