9 lines
22 KiB
JSON
9 lines
22 KiB
JSON
{
|
||
"index": 129,
|
||
"slug": "editorial-2024-06-practice-container-orchestration",
|
||
"title": "Контейнерная нагрузка: как связать ресурсы, готовность и автомасштабирование",
|
||
"excerpt": "Почему Pod может успешно разместиться, но не выдержать трафик: разбираем requests и limits, readiness и HPA на одном учебном примере.",
|
||
"contentHtml": "<p>После выкладки Pod получает статус <code>Running</code>, но запросы к сервису ждут дольше обычного. Иногда HorizontalPodAutoscaler (HPA) увеличивает число реплик, а доступных backend не становится больше: новые Pod не проходят readiness. В другой версии проблемы endpoint отвечает успешно ещё до прогрева, и Service отправляет трафик в приложение, которое не готово к рабочей нагрузке. Команда видит зелёный rollout и ищет причину уже под трафиком.</p>\n<p>У этих симптомов общий источник: в манифесте смешивают четыре разные границы. <code>requests</code> нужны планировщику для размещения, <code>limits</code> ограничивают выполнение container, readiness управляет допуском к трафику, а HPA меняет число реплик по метрике. Ни одно поле не обещает пропускную способность сервиса само по себе. Поэтому разберём не «правильные числа», а последовательность, в которой каждое число связывается с наблюдаемым результатом.</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></tr></thead><tbody><tr><td>Pod остаётся <code>Pending</code></td><td>Pod не назначен на Node; request может не помещаться в доступный ресурс.</td><td>События Pod, requests всех containers, allocatable Node, taints и affinity.</td></tr><tr><td>Pod <code>Running</code>, но не <code>Ready</code></td><td>Процесс запущен, но probe не разрешает отправлять ему трафик.</td><td>Смысл endpoint, результаты probe, время прогрева и состояние Service.</td></tr><tr><td>Pod <code>Ready</code>, latency растёт</td><td>Текущая probe пропускает трафик; она не доказывает запас capacity.</td><td>CPU, memory, очередь, внешние вызовы и лимиты приложения.</td></tr><tr><td>HPA поднял replicas без эффекта</td><td>Решение о масштабировании было принято, но новые реплики могут не стать Ready или метрика не описывает bottleneck.</td><td>Метрику HPA, request-знаменатель, события, readiness и время появления backend.</td></tr><tr><td>Container перезапускается с <code>OOMKilled</code></td><td>Память пересекла ограничение или приложение получило другую фатальную ошибку.</td><td>Причину завершения, working set, limit, рост cache и политику восстановления.</td></tr></tbody></table></div>\n<p>Начинайте с колонки «наблюдение», а не с изменения манифеста. <code>Running</code> описывает состояние процесса, не готовность к запросам. Значение CPU в HPA не означает процент от Node или от <code>limit</code>: для ресурсной метрики utilization это отношение usage к request. Эти различия и есть рабочая карта диагностики.</p>\n<h2>Четыре границы одного Pod</h2>\n<p><strong>Request</strong> — заявка на ресурс. Scheduler учитывает requests контейнеров, когда выбирает Node; для обычного Pod суммарная заявка контейнеров определяет, сколько ресурса нужно зарезервировать при размещении. Request не равен фактическому usage: container может потреблять больше заявки, если это разрешают limit и свободный ресурс. Без request нельзя осмысленно интерпретировать CPU utilization HPA для такого контейнера.</p>\n<p><strong>Limit</strong> — ограничение выполнения контейнера. Для CPU превышение limit может привести к throttling, а для memory превышение может завершить процесс с <code>OOMKilled</code>. Limit не является обещанием throughput и не заменяет нагрузочное измерение. Если request или limit добавляет admission-политика namespace, смотрите итоговый объект в кластере, а не только исходный файл.</p>\n<p><strong>Readiness probe</strong> отвечает на узкий вопрос: можно ли сейчас отправить запрос этому контейнеру. При неуспешной readiness Kubernetes перестаёт считать Pod готовым endpoint для Service. Probe не измеряет запас capacity и не должна бездумно превращаться в проверку всех внешних зависимостей. Иначе краткий сбой партнёра способен убрать из трафика все реплики. Слишком ранний успешный ответ создаёт обратную проблему: приложение принимает запросы до открытия пулов, миграций или cache.</p>\n<p><strong>HPA</strong> периодически вычисляет желаемое число реплик по метрике. Для CPU с <code>averageUtilization: 70</code> контроллер сравнивает среднее потребление с CPU request, а не с limit. Pod без нужного request не даёт корректного CPU utilization; Pod, который ещё не готов или не имеет метрики, может учитываться консервативно в расчёте. Поэтому HPA нельзя читать без <code>describe</code>, состояния реплик и понимания того, откуда пришла метрика.</p>\n<h2>Учебный манифест с явной гипотезой</h2>\n<p>Ниже — минимальный serving-workload для HTTP-приложения. Предположим, что после прогрева CPU — главный ограничитель, endpoint <code>/startup</code> появляется один раз, <code>/ready</code> проверяет локальную готовность пулов, а <code>/live</code> отвечает только при неисправимом состоянии процесса. Значения <code>500m</code>, <code>384Mi</code>, две реплики и target 70% — не рекомендация и не результат измерения. Это числа для воспроизводимого учебного прогона.</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 ports:\n - name: http\n containerPort: 8080\n resources:\n requests:\n cpu: \"500m\"\n memory: \"384Mi\"\n limits:\n cpu: \"1000m\"\n memory: \"768Mi\"\n startupProbe:\n httpGet:\n path: /startup\n port: http\n periodSeconds: 2\n failureThreshold: 30\n readinessProbe:\n httpGet:\n path: /ready\n port: http\n periodSeconds: 5\n timeoutSeconds: 2\n livenessProbe:\n httpGet:\n path: /live\n port: http\n periodSeconds: 10\n timeoutSeconds: 2\n---\napiVersion: v1\nkind: Service\nmetadata:\n name: app\nspec:\n selector:\n app: app\n ports:\n - name: http\n port: 80\n targetPort: http\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><code>startupProbe</code> отделяет медленный запуск от последующих проверок: пока она не завершилась успешно, liveness и readiness не начинают обычную работу. Это защищает процесс от преждевременного перезапуска, но не ускоряет прогрев. После прогрева readiness должна означать «этот Pod может принять обычный запрос», а не «процесс слушает порт». У HPA есть верхняя граница шесть реплик, но она не гарантирует, что шесть Pod поместятся в кластер или выдержат запросы.</p>\n<h2>Иллюстрация пути запроса</h2>\n<figure><img src=\"/assets/editorial/2024/container-orchestration-2024-pod-lifecycle.svg\" alt=\"Путь от request и запуска контейнера к readiness, Service и решению HPA\" loading=\"lazy\" /><figcaption>Учебная схема разделяет размещение Pod, допуск к Service и масштабирование по метрике. Она показывает порядок вопросов, а не состояние конкретного кластера.</figcaption></figure>\n<p>Читать схему нужно слева направо. Сначала scheduler решает, где Pod может быть размещён по requests. Затем kubelet запускает контейнер и выполняет startup, readiness и liveness в своих ролях. Service направляет запросы только к готовым backend. Параллельно HPA получает свою метрику и меняет desired replicas. При таком порядке увеличение replicas не исправляет неверный readiness, а поднятие memory limit не исправляет очередь запросов.</p>\n<h2>Воспроизводимый прогон</h2>\n<p>Сохраните манифест в файл <code>app.yaml</code> в тестовом namespace и сначала проверьте его сервером. Эта команда обращается к API и может требовать прав; локальный кластер, версия Kubernetes, policy admission и наличие Metrics API должны быть известны заранее.</p>\n<pre><code>kubectl config current-context\nkubectl auth can-i create deployment -n demo\nkubectl describe pod -n demo -l app=app --show-events\nkubectl apply --dry-run=server -n demo -f app.yaml\nkubectl diff -n demo -f app.yaml\nkubectl apply -n demo -f app.yaml\nkubectl rollout status deployment/app -n demo --timeout=120s\nkubectl get pods -n demo -l app=app -o wide\nkubectl get endpointslice -n demo -l kubernetes.io/service-name=app\nkubectl describe hpa app -n demo\nkubectl top pods -n demo -l app=app</code></pre>\n<p>Последняя команда требует работающего Metrics API, часто его предоставляет Metrics Server. Если она возвращает ошибку, это не доказательство нулевой нагрузки и не повод подставить число вручную. Зафиксируйте ошибку как ограничение прогона. <code>kubectl diff</code> и <code>--dry-run=server</code> также не заменяют rollout: первая сравнивает объект, вторая проверяет запрос к API, а третья показывает, что Deployment действительно продвигается. Команда <code>describe pod -l</code> удобна как быстрый запрос, но при нескольких Pod для детального анализа выберите конкретное имя из <code>get pods</code>.</p>\n<h2>Как читать результат и отрицательный путь</h2>\n<ol><li><strong>Проверьте контекст.</strong> Убедитесь, что команды обращаются к ожидаемому кластеру и namespace. Не применяйте учебный файл в production только потому, что текущий context называется коротко.</li><li><strong>Отделите размещение от запуска.</strong> Для <code>Pending</code> прочитайте <code>kubectl describe pod</code> и события. Сопоставьте requests с allocatable, а не с общей памятью Node. Если Pod размещён, переходите к probes.</li><li><strong>Проверьте время.</strong> Сравните длительность startup с <code>failureThreshold × periodSeconds</code>. Для этого примера окно startup — до 60 секунд при последовательных неуспехах, но реальное время зависит от результата probe и приложения.</li><li><strong>Сверьте Ready и endpoint.</strong> Сопоставьте поле Ready у Pod с EndpointSlice и фактическим ответом <code>/ready</code>. Если Pod Ready, а latency растёт, probe выполняет свой узкий контракт; ищите bottleneck в CPU, memory, очереди или внешнем вызове.</li><li><strong>Разберите HPA.</strong> В <code>describe hpa</code> найдите текущую и целевую метрики, desired replicas, события и ошибки. Сопоставьте их с request. Отсутствие данных Metrics API нельзя трактовать как отсутствие нагрузки.</li><li><strong>Проверьте отрицательный путь.</strong> Если новые Pod не становятся Ready, не увеличивайте <code>maxReplicas</code>. Сначала исправьте контракт готовности или startup, затем повторите тот же прогон. Если HPA масштабируется, но latency не улучшается, проверьте, является ли CPU главным ограничителем.</li><li><strong>Изменяйте одну границу.</strong> Зафиксируйте гипотезу, одно изменение, окно наблюдения, критерий остановки и rollback. Иначе после одновременного изменения limit, probe и HPA нельзя понять, что именно изменило результат.</li></ol>\n<h2>Какие числа можно считать доказанными</h2>\n<p>В учебном файле доказан только синтаксический и объектный контракт, если его принял API. Значение <code>500m</code> становится обоснованным request лишь после повторяемого измерения representative-нагрузки с согласованным latency budget и запасом на пики. Target 70% становится рабочей гипотезой только вместе с наблюдаемым временем масштабирования, размером очереди и готовностью новых реплик. Значение <code>768Mi</code> нельзя оправдать одной строкой <code>OOMKilled</code>: нужно понять, растёт ли cache, есть ли утечка и сколько памяти нужно процессу при штатном пике.</p>\n<p>После каждого изменения сохраняйте минимум: версию образа, итоговый Deployment, временное окно, входную нагрузку, latency/error rate, состояние Pod и HPA, а также решение об откате. Это превращает настройку из перебора коэффициентов в проверяемый эксперимент. Если не хватает разрешённого наблюдения, корректное действие — остановить эксперимент, а не додумывать результат.</p>\n<h2>Ограничения применимости</h2>\n<p>Схема рассчитана на stateless HTTP-сервис, который можно безопасно масштабировать горизонтально. Она не решает координацию StatefulSet, порядок миграций базы, работу с локальным диском, очередями, GPU или внешним rate limit. HPA по CPU может быть плохим сигналом для memory-heavy приложения, batch-задачи или сервиса, где bottleneck — очередь и время ответа партнёра. Для таких случаев нужна другая метрика и отдельная проверка её источника.</p>\n<p>Точный результат зависит от версии Kubernetes, API и controller flags, Metrics API, admission defaults, политики namespace, сетевого маршрута и реализации приложения. Схема также не проверяет PDB, topology spread, node autoscaling и безопасность образа. Наличие статуса <code>Available</code> или зелёного rollout не является доказательством производительности. Граница вывода простая: эта статья даёт порядок проверки, а не готовый production-манифест.</p>\n<h2>Проверяемые источники</h2>\n<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 и limits, их связь с размещением и поведением контейнеров.</li><li><a href=\"https://kubernetes.io/docs/concepts/workloads/pods/probes/\" target=\"_blank\" rel=\"noopener noreferrer\">Kubernetes: Liveness, Readiness, and Startup Probes</a> — роли probe, порядок startup и влияние readiness на доступность Pod.</li><li><a href=\"https://kubernetes.io/docs/concepts/workloads/autoscaling/horizontal-pod-autoscale/\" target=\"_blank\" rel=\"noopener noreferrer\">Kubernetes: Horizontal Pod Autoscaling</a> — вычисление desired replicas, CPU utilization относительно request и обработка неполных метрик.</li><li><a href=\"https://kubernetes.io/docs/concepts/workloads/controllers/deployment/\" target=\"_blank\" rel=\"noopener noreferrer\">Kubernetes: Deployments</a> — rollout Deployment и связь желаемого состояния с ReplicaSet.</li><li><a href=\"https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/\" target=\"_blank\" rel=\"noopener noreferrer\">Kubernetes: EndpointSlices</a> — представление backend Service и условий готовности endpoint.</li><li><a href=\"https://kubernetes.io/docs/reference/kubectl/generated/kubectl_top/\" target=\"_blank\" rel=\"noopener noreferrer\">Kubernetes: kubectl top</a> — назначение команды и требование Metrics API.</li></ul>",
|
||
"readingMinutes": 9
|
||
}
|