Kubernetes для самых маленьких. Часть 32: Как работают метрики и автомасштабирование?
Все части серии «Kubernetes для самых маленьких»В прошлой части мы разбирали сбои по событиям и логам — по тому, что осталось в кластере на момент вопроса. Сегодня — о числах, собранных заранее: сколько CPU и памяти потребляют поды на самом деле, как Kubernetes по этим числам сам меняет число реплик и где хранить историю метрик, чтобы через неделю ответить, что было ночью. Начнём с встроенного контура — kubelet, metrics-server, kubectl top и HorizontalPodAutoscaler, — затем поставим Prometheus с Grafana и Alertmanager одним Helm-чартом (часть 27), научим его снимать метрики с hello и напишем первые правила алертов. Requests и limits из части 10 здесь понадобятся постоянно: автомасштабирование считает проценты именно от них.
Неймспейс demo остаётся таким, каким его оставила часть 30: под уровнем restricted, hello работает от UID 10001 без capabilities. Это повлияет на то, откуда мы будем давать нагрузку.
Два контура метрик
В кластере есть два независимых пути, по которым идут метрики, и путать их — частая ошибка:
Контур ресурсов (встроенный) Полный контур (мониторинг)
──────────────────────────── ──────────────────────────
kubelet на каждом узле экспортёры: kubelet, node-exporter,
/metrics/resource kube-state-metrics, приложения /metrics
│ каждые 15 с │ каждые 30 с (pull)
▼ ▼
metrics-server Prometheus
две последние точки в памяти TSDB на диске, недели истории
│ │
▼ ├──► Grafana: графики
kube-apiserver ├──► правила → Alertmanager
/apis/metrics.k8s.io (aggregation) └──► адаптер → custom/external
│ metrics API (по желанию)
├──► kubectl top
├──► HorizontalPodAutoscaler
└──► VerticalPodAutoscaler
Контур ресурсов — минимальный: только CPU и память подов и узлов, только «сейчас», без истории. Он нужен самому Kubernetes для автомасштабирования и человеку для kubectl top. Полный контур — система мониторинга: тысячи метрик, хранение на диске, запросы, графики и алерты. Один не заменяет другой: Prometheus не нужен HPA для масштабирования по CPU, а metrics-server бесполезен для вопроса «что было вчера».
Откуда kubelet берёт цифры
Источник обоих контуров — kubelet. Он получает статистику контейнеров от containerd по CRI и из cgroups, которые сам создал для подов (часть 10): cpu.stat (usage_usec — накопленное время CPU) и memory.current, memory.stat (из них считается working set — используемая память минус неактивный файловый кэш, который ядро может вытеснить). Внутри kubelet встроен cAdvisor — библиотека Google, которая обходит cgroups и собирает по ним подробную статистику.
Наружу kubelet отдаёт это на своём порту 10250 по нескольким адресам:
| Адрес | Что там | Кто читает |
|---|---|---|
/metrics/resource |
минимальный набор в формате Prometheus: CPU и working set по узлу, подам и контейнерам | metrics-server |
/stats/summary |
Summary API — то же и больше (сеть, файловые системы, тома) в JSON | старые версии metrics-server, вспомогательные утилиты |
/metrics/cadvisor |
всё, что собрал cAdvisor: сотни метрик по контейнерам | Prometheus |
/metrics/probes |
результаты проб liveness, readiness и startup | Prometheus |
/metrics |
метрики самого kubelet: время операций, PLEG, тома (kubelet_volume_stats_*) |
Prometheus |
Заглянуть можно через прокси API-сервера, не заходя на узел:
$ kubectl get --raw /api/v1/nodes/k8s-node-2/proxy/metrics/resource | grep -E 'hello-74d65597f7-5lfft|^node_'
container_cpu_usage_seconds_total{container="hello",namespace="demo",pod="hello-74d65597f7-5lfft"} 0.396541 1793735197827
container_memory_working_set_bytes{container="hello",namespace="demo",pod="hello-74d65597f7-5lfft"} 6.623232e+06 1793735197827
container_start_time_seconds{container="hello",namespace="demo",pod="hello-74d65597f7-5lfft"} 1.7937347742225347e+09
container_swap_limit_bytes{container="hello",namespace="demo",pod="hello-74d65597f7-5lfft"} 0 1793735197827
container_swap_usage_bytes{container="hello",namespace="demo",pod="hello-74d65597f7-5lfft"} 0 1793735197827
node_cpu_usage_seconds_total 13472.803 1793735195256
node_memory_working_set_bytes 1.55904e+09 1793735195256
node_swap_usage_bytes 0 1793735195256
pod_cpu_usage_seconds_total{namespace="demo",pod="hello-74d65597f7-5lfft"} 0.394523 1793735193027
pod_memory_working_set_bytes{namespace="demo",pod="hello-74d65597f7-5lfft"} 6.606848e+06 1793735193027
pod_swap_usage_bytes{namespace="demo",pod="hello-74d65597f7-5lfft"} 0 1793735193027
Последнее число в строке — метка времени измерения в миллисекундах. CPU здесь — счётчик: секунды процессорного времени с момента старта контейнера. Потребление «в ядрах» получается только как разность двух измерений, поделённая на интервал между ними. Следующий запрос показал 0.444832 с меткой 1793735209621, то есть за 11,8 секунды: (0,444832 − 0,396541) / 11,8 ≈ 0,004 ядра, или 4m. Память — мгновенное значение в байтах: здесь 6,6 МБ.
metrics-server
metrics-server — небольшой компонент из kubernetes-sigs. Каждые 15 секунд он опрашивает /metrics/resource у всех kubelet'ов, держит в памяти две последние точки на каждый объект, вычисляет из них CPU и отдаёт результат как API metrics.k8s.io/v1beta1. Отдельный объект APIService регистрирует этот API в слое агрегации kube-apiserver: запросы к /apis/metrics.k8s.io/... API-сервер не обрабатывает сам, а проксирует в сервис metrics-server.
Чего metrics-server не делает, по словам его же документации:
- не хранит историю — только последнее значение; перезапуск пода metrics-server ничего не теряет, потому что терять нечего;
- не подходит для мониторинга — не надо подключать к нему Prometheus или графики, для этого есть
/metrics/resourceи/metrics/cadvisorу kubelet; - не точен в деталях — значение CPU усреднено за окно, короткие пики в нём сглажены;
- не знает других метрик, кроме CPU и памяти.
Версии 0.9.x поддерживают Kubernetes 1.34 и новее. Ставится официальным манифестом с явной версией:
$ kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.9.1/components.yaml
serviceaccount/metrics-server created
clusterrole.rbac.authorization.k8s.io/system:aggregated-metrics-reader created
clusterrole.rbac.authorization.k8s.io/system:metrics-server created
rolebinding.rbac.authorization.k8s.io/metrics-server-auth-reader created
clusterrolebinding.rbac.authorization.k8s.io/metrics-server:system:auth-delegator created
clusterrolebinding.rbac.authorization.k8s.io/system:metrics-server created
service/metrics-server created
deployment.apps/metrics-server created
apiservice.apiregistration.k8s.io/v1beta1.metrics.k8s.io created
В манифесте у Deployment аргументы --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname (ходить к kubelet по IP узла), --kubelet-use-node-status-port (порт брать из статуса узла) и --metric-resolution=15s. Флага --kubelet-insecure-tls там нет: metrics-server проверяет сертификат, которым kubelet отвечает на порту 10250, по CA кластера. На нашем кластере проверка проходит сразу — ещё в части 4 мы включили serverTLSBootstrap: true и вручную одобрили CSR kubelet-serving, так что каждый kubelet отвечает сертификатом, подписанным CA кластера. Через полминуты metrics-server готов:
$ kubectl get apiservice v1beta1.metrics.k8s.io
NAME SERVICE AVAILABLE AGE
v1beta1.metrics.k8s.io kube-system/metrics-server True 32s
Почему не --kubelet-insecure-tls
На кластере kubeadm, поставленном без serverTLSBootstrap, kubelet сам генерирует для порта 10250 самоподписанный сертификат: издатель — он сам (CN=k8s-node-2-ca@1793733646), в SAN только имя узла, IP-адреса нет. Проверку по CA кластера такой сертификат не проходит. Увидеть это можно на одном узле, убрав на время поле из /var/lib/kubelet/config.yaml: в журнале metrics-server появляется ошибка, а узел в kubectl top теряет цифры.
$ kubectl logs -n kube-system deploy/metrics-server | grep -m1 'Failed to scrape'
E1103 19:20:50.298744 1 scraper.go:149] "Failed to scrape node" err="Get \"https://192.168.10.21:10250/metrics/resource\": tls: failed to verify certificate: x509: cannot validate certificate for 192.168.10.21 because it doesn't contain any IP SANs" node="k8s-node-2"
$ kubectl top nodes
NAME CPU(cores) CPU(%) MEMORY(bytes) MEMORY(%)
k8s-master-1 104m 5% 1927Mi 58%
k8s-node-1 40m 2% 735Mi 22%
k8s-node-2 <unknown> <unknown> <unknown> <unknown>
APIService при этом остаётся True: другие узлы отвечают. Первый ответ в поиске — добавить metrics-server флаг --kubelet-insecure-tls. Это выключает проверку сертификата kubelet совсем. Чем это плохо: при каждом опросе metrics-server предъявляет kubelet'у токен своего ServiceAccount, и без проверки сертификата он предъявит его кому угодно, кто сумеет перехватить трафик в сети узлов. Документация metrics-server прямо называет этот флаг вариантом для тестов. Правильно — дать kubelet'ам сертификаты, подписанные CA кластера, как это сделано на нашем стенде.
Подписанные сертификаты kubelet
С serverTLSBootstrap: true kubelet запрашивает серверный сертификат через API certificates.k8s.io — так же, как при присоединении к кластеру он запросил клиентский: создаёт объект CertificateSigningRequest (CSR) с подписантом kubernetes.io/kubelet-serving и перечисляет в нём свои имена и адреса. Клиентские CSR узлов kube-controller-manager одобряет автоматически, а серверные — нет: Kubernetes не может сам убедиться, что узел вправе получить сертификат на запрошенные имена и адреса. Поэтому в части 4 мы перед одобрением смотрели, что именно просит каждый узел. После одобрения kube-controller-manager подписывает сертификат CA кластера, kubelet забирает его в /var/lib/kubelet/pki/kubelet-server-current.pem и отвечает им.
Если кластер уже работает без этого поля, его включают в двух местах. ConfigMap kubelet-config — источник, из которого kubeadm берёт конфигурацию для новых узлов и при обновлениях:
$ kubectl edit cm kubelet-config -n kube-system
data:
kubelet: |
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
serverTLSBootstrap: true
...
На каждом существующем узле то же поле дописывают в /var/lib/kubelet/config.yaml, перезапускают kubelet и одобряют появившиеся CSR так же, как при установке кластера:
user@k8s-node-1:~$ echo 'serverTLSBootstrap: true' | sudo tee -a /var/lib/kubelet/config.yaml
user@k8s-node-1:~$ sudo systemctl restart kubelet
Сертификат живёт год и продлевается по тому же пути: kubelet заранее создаст новый CSR, и его снова нужно одобрить — иначе, когда старый сертификат истечёт, metrics-server перестанет снимать метрики с этого узла. Чтобы не забыть, одобрение автоматизируют контроллером вроде kubelet-csr-approver — он одобряет только запросы, где имена и IP соответствуют узлу. Контроллер, который одобряет всё подряд, хуже ручного одобрения: любой, у кого есть клиентский сертификат узла, получит серверный сертификат на любое имя.
Те же подписанные сертификаты позволяют закрыть и вторую дыру того же рода: по умолчанию kube-apiserver в kubeadm не проверяет сертификат kubelet, когда проксирует kubectl logs и exec. Это видно и на опыте выше: kubectl get --raw /api/v1/nodes/k8s-node-2/proxy/... с самоподписанным сертификатом продолжает работать. Раз kubelet'ы отвечают сертификатами от CA кластера, API-серверу можно задать --kubelet-certificate-authority с этим CA. Обратное направление — API-сервер к metrics-server — в манифесте помечено insecureSkipTLSVerify: true у APIService; аккуратная замена — выпустить metrics-server сертификат через cert-manager из части 19 и прописать caBundle.
kubectl top
$ kubectl top nodes
NAME CPU(cores) CPU(%) MEMORY(bytes) MEMORY(%)
k8s-master-1 145m 7% 1853Mi 56%
k8s-node-1 146m 7% 728Mi 22%
k8s-node-2 65m 3% 1492Mi 45%
$ kubectl top pods -n demo --containers
POD NAME CPU(cores) MEMORY(bytes)
hello-74d65597f7-5lfft hello 5m 6Mi
hello-74d65597f7-8vkl6 hello 4m 6Mi
hello-74d65597f7-qqdxp hello 4m 6Mi
Проценты у узлов считаются от allocatable — того, что kubelet готов отдать подам, а не от всей памяти машины. Память — working set, поэтому цифры не совпадут с free на узле: free считает по /proc/meminfo, а working set — по cgroup, и в него входит активная часть файлового кэша. Именно working set сравнивается с лимитом при OOM и с порогами выселения kubelet, поэтому для решений о requests и limits смотреть нужно на него.
HorizontalPodAutoscaler
HorizontalPodAutoscaler (HPA) — объект autoscaling/v2, который меняет replicas у Deployment, StatefulSet или любого ресурса с подресурсом scale. Его контроллер живёт в kube-controller-manager и раз в 15 секунд (--horizontal-pod-autoscaler-sync-period) для каждого HPA делает одно и то же: получает метрики, считает желаемое число реплик и записывает его в spec.replicas цели. Дальше работает обычный контроллер Deployment из части 7.
Алгоритм
desiredReplicas = ceil( currentReplicas × currentMetricValue / desiredMetricValue )
Три пода при цели 70 % CPU и фактических 140 % дают ceil(3 × 140 / 70) = 6. При 75 % — ceil(3 × 75 / 70) = ceil(3,21) = 4, но масштабирования не будет: если отношение текущего к целевому отличается от 1 меньше чем на допуск (10 % по умолчанию, --horizontal-pod-autoscaler-tolerance), HPA ничего не делает. Допуск можно задать и в самом HPA — полем tolerance в behavior.scaleUp и behavior.scaleDown: возможность появилась в 1.33, с 1.35 она в статусе бета и включена по умолчанию, в 1.37 стала стабильной.
Детали, которые объясняют «странное» поведение:
- Проценты — от requests. Утилизация 70 % означает 70 % от
resources.requests.cpu, а не от ядра и не от лимита. Если хотя бы у одного контейнера пода нет request по этому ресурсу, HPA не может посчитать утилизацию и бездействует, сообщая об этом в conditions. Это ещё одна причина всегда задавать requests. - Неготовые поды при масштабировании вверх по CPU считаются потребляющими 0 %, а поды без метрик — 0 % при росте и 100 % при снижении. В первые 5 минут после старта пода (
--horizontal-pod-autoscaler-cpu-initialization-period) его CPU учитывается, только если замер сделан, когда под уже был готов: всплеск CPU при старте Go- или Java-приложения не должен вызывать новый рост. - Несколько метрик — HPA считает желаемое число по каждой и берёт максимум.
replicasв манифесте. Если HPA управляет Deployment, полеreplicasиз манифеста Deployment убирают: иначе каждыйkubectl applyбудет возвращать число реплик к записанному в файле, споря с HPA.
Типы метрик
| Тип | Откуда | Пример |
|---|---|---|
Resource |
metrics.k8s.io (metrics-server) |
CPU или память в среднем по подам: Utilization в процентах от requests или AverageValue в абсолютных единицах |
ContainerResource |
metrics.k8s.io |
то же, но по одному контейнеру пода — чтобы sidecar не размывал среднее |
Pods |
custom.metrics.k8s.io |
метрика, у которой есть значение на каждый под: запросов в секунду на под |
Object |
custom.metrics.k8s.io |
метрика одного объекта кластера: запросов в секунду на Ingress |
External |
external.metrics.k8s.io |
метрика вне кластера: длина очереди в брокере сообщений |
API custom.metrics.k8s.io и external.metrics.k8s.io в кластере сами по себе не существуют: их, как metrics-server, регистрирует в слое агрегации отдельный адаптер — компонент, который переводит запросы HPA в запросы к системе мониторинга. Для Prometheus это prometheus-adapter (развивается медленно) или KEDA, о которой ниже.
behavior
Поле behavior задаёт, как быстро HPA может менять число реплик, отдельно вверх и вниз. Значения по умолчанию:
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 100
periodSeconds: 15
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 15
- type: Pods
value: 4
periodSeconds: 15
selectPolicy: Max
Окно стабилизации — HPA помнит рекомендации за последние N секунд и при снижении берёт из них наибольшую. Нагрузка упала на минуту и вернулась — реплики не тронуты. По умолчанию вниз окно 5 минут, вверх — ноль: расти можно сразу. Политики ограничивают шаг: вверх — не больше чем удвоение или +4 пода за 15 секунд (из двух берётся больший, selectPolicy: Max; с трёх реплик — сразу до семи), вниз — до всего лишнего сразу. Для сервиса, который долго прогревается или держит долгие соединения, резкое снижение вредно, и политику вниз делают мягче — например, не больше одного пода в минуту.
Пример на hello
В манифесте hello из части 30 ресурсов нет, а без requests.cpu HPA считать не из чего. Зададим их и начнём с трёх реплик:
$ kubectl set resources deployment hello -n demo --requests=cpu=100m,memory=32Mi --limits=memory=64Mi
deployment.apps/hello resource requirements updated
$ kubectl scale deployment hello -n demo --replicas=3
deployment.apps/hello scaled
HPA держит среднюю загрузку CPU на 70 % от request, от трёх до десяти реплик, и убирает поды не быстрее одного в минуту:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: hello
namespace: demo
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: hello
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Pods
value: 1
periodSeconds: 60
Нагрузку дадут поды с бесконечным циклом запросов к сервису. В demo их не запустить: busybox без securityContext уровень restricted отклонит (pods "..." is forbidden: violates PodSecurity "restricted:latest"). Поэтому генератор живёт в неймспейсе default и обращается к сервису по имени hello.demo (часть 16). Сначала два пода:
$ kubectl apply -f hello-hpa.yaml
horizontalpodautoscaler.autoscaling/hello created
$ kubectl create deployment load -n default --image=busybox:1.37 --replicas=2 -- \
/bin/sh -c 'while true; do wget -q -O /dev/null http://hello.demo; done'
deployment.apps/load created
$ kubectl get hpa hello -n demo --watch
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
hello Deployment/hello cpu: 0%/70% 3 10 3 75s
hello Deployment/hello cpu: 15%/70% 3 10 3 105s
hello Deployment/hello cpu: 59%/70% 3 10 3 2m1s
hello Deployment/hello cpu: 74%/70% 3 10 3 2m16s
hello Deployment/hello cpu: 75%/70% 3 10 3 2m31s
hello Deployment/hello cpu: 74%/70% 3 10 3 3m1s
hello Deployment/hello cpu: 76%/70% 3 10 3 6m16s
Целых семь минут цель превышена, а реплик по-прежнему три. Это и есть допуск в деле: 75 / 70 = 1,07, отличие от единицы меньше 10 %, поэтому HPA ничего не делает, хотя формула дала бы четыре. Дадим нагрузку посильнее — шесть подов:
$ kubectl delete deployment load -n default
$ kubectl create deployment load -n default --image=busybox:1.37 --replicas=6 -- \
/bin/sh -c 'while true; do wget -q -O /dev/null http://hello.demo; done'
$ kubectl get hpa hello -n demo --watch
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
hello Deployment/hello cpu: 0%/70% 3 10 3 22m
hello Deployment/hello cpu: 22%/70% 3 10 3 22m
hello Deployment/hello cpu: 134%/70% 3 10 3 22m
hello Deployment/hello cpu: 136%/70% 3 10 6 23m
hello Deployment/hello cpu: 126%/70% 3 10 6 23m
hello Deployment/hello cpu: 75%/70% 3 10 6 23m
hello Deployment/hello cpu: 48%/70% 3 10 6 23m
hello Deployment/hello cpu: 28%/70% 3 10 6 24m
ceil(3 × 136 / 70) = ceil(5,83) = 6: в рамках политик по умолчанию, поэтому одним шагом. Строка 126 % при шести репликах — запаздывание: новые поды ещё не вошли в замер. Дальше средняя загрузка ходит между 20 и 60 % и ниже цели, но реплики не снижаются: окно стабилизации держит наибольшую рекомендацию за пять минут, а нагрузка всё ещё идёт. Убираем её:
$ kubectl delete deployment load -n default
$ kubectl events -n demo --for hpa/hello
LAST SEEN TYPE REASON OBJECT MESSAGE
16m Normal SuccessfulRescale HorizontalPodAutoscaler/hello New size: 6; reason: cpu resource utilization (percentage of request) above target
6m13s Normal SuccessfulRescale HorizontalPodAutoscaler/hello New size: 5; reason: All metrics below target
5m13s Normal SuccessfulRescale HorizontalPodAutoscaler/hello New size: 4; reason: All metrics below target
4m12s Normal SuccessfulRescale HorizontalPodAutoscaler/hello New size: 3; reason: All metrics below target
Первое снижение — через 5 минут 47 секунд после снятия нагрузки (окно стабилизации плюс последний высокий замер), затем по одному поду в минуту, как задано в политике. Пока окно не прошло, в conditions видно AbleToScale с причиной ScaleDownStabilized и сообщением recent recommendations were higher than current one, applying the highest recent recommendation. Полное состояние HPA — в status:
$ kubectl get hpa hello -n demo -o yaml | sed -n '/^status:/,$p'
status:
conditions:
- lastTransitionTime: "2026-11-03T18:14:13Z"
message: recommended size matches current size
reason: ReadyForNewScale
status: "True"
type: AbleToScale
- lastTransitionTime: "2026-11-03T18:14:13Z"
message: the HPA was able to successfully calculate a replica count from cpu resource
utilization (percentage of request)
reason: ValidMetricFound
status: "True"
type: ScalingActive
- lastTransitionTime: "2026-11-03T18:46:50Z"
message: the desired replica count is less than the minimum replica count
reason: TooFewReplicas
status: "True"
type: ScalingLimited
currentMetrics:
- resource:
current:
averageUtilization: 1
averageValue: 1m
name: cpu
type: Resource
currentReplicas: 3
desiredReplicas: 3
lastScaleTime: "2026-11-03T18:48:51Z"
ScalingActive=False — HPA не может получить метрики: нет metrics-server, у контейнера нет request, адаптер не отвечает. ScalingLimited=True — упёрлись в minReplicas или maxReplicas.
Другие автомасштабировщики
VerticalPodAutoscaler (VPA) — отдельный проект из репозитория kubernetes/autoscaler: смотрит на фактическое потребление подов и рекомендует или сам выставляет им requests и limits. С тех пор как изменение ресурсов пода на месте (in-place resize) стало стабильным в Kubernetes 1.35, VPA умеет в режиме InPlaceOrRecreate менять ресурсы без пересоздания пода там, где это возможно. По CPU одновременно с HPA его не используют: они начнут спорить. Полезный безопасный режим — updateMode: "Off", когда VPA только пишет рекомендации, а человек решает.
KEDA (Kubernetes Event-Driven Autoscaling) — оператор, который масштабирует по внешним событиям: длине очереди RabbitMQ или Kafka, результату запроса к Prometheus, расписанию. Объект ScaledObject описывает источник, KEDA реализует для него external.metrics.k8s.io и создаёт обычный HPA; сверх HPA он умеет масштабировать до нуля реплик и обратно.
Cluster Autoscaler — меняет не число подов, а число узлов: добавляет машину, когда поды висят в Pending из-за нехватки ресурсов, и убирает недогруженную. Он работает через API облачного провайдера или платформы виртуализации, которая умеет создавать машины по запросу. В кластере на собственных серверах, как наш, ему нечего вызывать, поэтому здесь его нет; ёмкость планируют по метрикам заранее.
Prometheus
Prometheus — система мониторинга и база временных рядов, проект CNCF. Устройство в четырёх пунктах.
Модель данных. Временной ряд — это имя метрики плюс набор меток (пар ключ — значение), и последовательность точек «время — число» для этого сочетания. container_cpu_usage_seconds_total{namespace="demo", pod="hello-…-5lfft", container="hello"} — один ряд; другой под — другой ряд. Каждое уникальное сочетание меток — новый ряд в базе, поэтому в метки не кладут то, у чего неограниченное число значений (идентификаторы пользователей, URL с параметрами): это называется кардинальностью, и она главный враг памяти Prometheus.
Pull. Prometheus сам ходит к целям по HTTP за /metrics с заданным интервалом и забирает текущие значения в текстовом формате. Список целей он строит service discovery: для Kubernetes — по API кластера, следя за подами, сервисами, эндпоинтами и узлами. Если цель не ответила, ряд up{...} для неё становится 0 — по нему отличают «приложение молчит» от «приложение говорит, что всё плохо».
Экспортёры. Не каждая программа умеет отдавать /metrics. Экспортёр — отдельный процесс, который снимает состояние с чего-то другого и переводит в формат Prometheus: node-exporter читает /proc и /sys узла (CPU, память, диски, сеть, файловые системы), kube-state-metrics смотрит на объекты API и превращает их состояние в метрики (сколько реплик желаемо и готово, причина последнего завершения контейнера, фаза пода, ёмкость PVC). Приложения, как hello, отдают свои метрики сами.
Хранилище. Локальная база TSDB: свежие точки — в памяти и в журнале предзаписи (WAL) на диске, каждые два часа они сбрасываются в неизменяемый блок (каталог с чанками, индексом и файлом удалений), блоки потом укрупняются. Точка занимает на диске 1–2 байта благодаря сжатию.
PromQL за пять минут
Типов метрик четыре: counter (только растёт, сбрасывается при рестарте: запросы, секунды CPU), gauge (любое текущее значение: память, длина очереди), histogram (распределение по корзинам _bucket{le="..."} плюс _sum и _count: время ответа) и summary (квантили, посчитанные в самом приложении).
container_memory_working_set_bytes{namespace="demo", container="hello"}
Это мгновенный вектор — по одному последнему значению для каждого подходящего ряда. Счётчики в таком виде бесполезны, их смотрят через скорость изменения за окно — вектор диапазона [5m]:
rate(container_cpu_usage_seconds_total{namespace="demo", container="hello"}[5m])
rate — средняя скорость роста в секунду за 5 минут, с поправкой на сбросы счётчика; для секунд CPU это и есть ядра. increase(...[1h]) — прирост за час. Агрегация убирает лишние метки:
sum by (pod) (rate(container_cpu_usage_seconds_total{namespace="demo"}[5m]))
histogram_quantile(0.95, sum by (le) (rate(apiserver_request_duration_seconds_bucket{verb="GET"}[5m])))
Первое — CPU по подам. Второе — 95-й перцентиль времени ответа kube-apiserver на запросы GET по его гистограмме; на нашем стенде это около 0,07 секунды. Окно в rate должно вмещать хотя бы четыре интервала опроса: при 30 секундах — от двух минут.
kube-prometheus-stack
Собрать всё это руками можно, но долго. kube-prometheus-stack — Helm-чарт сообщества Prometheus, который ставит готовый набор:
- Prometheus Operator — оператор (часть 28) с набором CRD:
PrometheusиAlertmanagerописывают сами серверы (оператор превращает их в StatefulSet),ServiceMonitorиPodMonitor— что опрашивать,PrometheusRule— правила записи и алертов,AlertmanagerConfig— маршруты уведомлений. Оператор следит за этими объектами и генерирует из них конфигурацию Prometheus, так что цели и правила добавляются манифестами рядом с приложением; - Prometheus с готовыми ServiceMonitor'ами для kubelet и cAdvisor, API-сервера, CoreDNS, kube-controller-manager, kube-scheduler, etcd и kube-proxy;
- kube-state-metrics и node-exporter (DaemonSet на каждом узле);
- Alertmanager — сервис, который получает сработавшие алерты от Prometheus, группирует их, подавляет зависимые (узел недоступен — не нужно отдельно о каждом его поде), выдерживает паузы повторов и рассылает по каналам: почта, мессенджеры, webhook;
- Grafana — веб-интерфейс для графиков и дашбордов поверх Prometheus и других источников, с готовыми дашбордами по кластеру (в этой версии чарта их 29);
- 155 правил алертов в 35 объектах
PrometheusRuleиз проекта kubernetes-mixin и других:KubePodCrashLooping,KubePersistentVolumeFillingUp,CPUThrottlingHigh,KubeNodeNotReadyи так далее.
Хранилище: локальный том, не NFS
Prometheus пишет в TSDB постоянно, держит файлы открытыми через mmap и рассчитывает на POSIX-семантику файловой системы. Его документация прямо говорит: файловые системы, не соответствующие POSIX, не поддерживаются, возможна неисправимая порча данных, и NFS не поддерживается. Поэтому тома из csi-driver-nfs (часть 23) для Prometheus не годятся — ровно по той же причине, по которой на NFS не кладут базы данных. Нужен локальный диск узла: StorageClass local-path из части 25 (local-path-provisioner — контроллер, который создаёт том как каталог на узле, где запустился под) или статический локальный PV на отдельном диске. Если вы убрали его после той части, он ставится снова одним манифестом:
$ kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.37/deploy/local-path-storage.yaml
Локальный том привязывает под к узлу: если k8s-node-2 выключится, Prometheus будет ждать его возвращения. Для мониторинга небольшого кластера это приемлемо; где нет — ставят две реплики (replicas: 2 в ресурсе Prometheus), каждая со своим локальным томом на своём узле и независимым опросом тех же целей.
Сколько места. Формула из документации: объём = срок хранения в секундах × точек в секунду × байт на точку. На нашем стенде из трёх узлов этот чарт через час работы даёт 76 тысяч рядов и 2 100 точек в секунду. За 15 дней: 1 296 000 × 2 100 × 2 ≈ 5,4 ГБ, плюс WAL и запас на укрупнение блоков и рост кластера — тома на 20 ГБ хватит. Реальные цифры ваш кластер покажет сам: prometheus_tsdb_head_series и sum(rate(prometheus_tsdb_head_samples_appended_total[5m])). Срок хранения задают двумя ограничениями сразу: retention (время) и retentionSize (объём, процентов на 10–15 меньше тома), — что наступит раньше. Памяти Prometheus на стенде занял около 370 МиБ; это и есть ориентир для requests.
Установка
node-exporter монтирует каталоги узла и работает в его сетевом пространстве имён, поэтому неймспейс мониторинга сразу помечаем уровнем privileged (часть 30). Пароль администратора Grafana — в Secret, созданном заранее, а не в values (секрет в values окажется в истории релиза Helm и в Git):
$ kubectl create namespace monitoring
$ kubectl label namespace monitoring pod-security.kubernetes.io/enforce=privileged
$ kubectl create secret generic grafana-admin -n monitoring \
--from-literal=admin-user=admin --from-literal=admin-password="$(openssl rand -base64 24)"
Минимальные values:
# kps-values.yaml
fullnameOverride: kps
prometheus:
prometheusSpec:
retention: 15d
retentionSize: 17GB
# брать ServiceMonitor, PodMonitor и PrometheusRule из всех неймспейсов,
# а не только с меткой release: kps
serviceMonitorSelectorNilUsesHelmValues: false
podMonitorSelectorNilUsesHelmValues: false
ruleSelectorNilUsesHelmValues: false
# искать цели через EndpointSlice, а не через устаревший API Endpoints
serviceDiscoveryRole: EndpointSlice
resources:
requests: { cpu: 200m, memory: 1Gi }
limits: { memory: 2Gi }
storageSpec:
volumeClaimTemplate:
spec:
storageClassName: local-path
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 20Gi
alertmanager:
alertmanagerSpec:
storage:
volumeClaimTemplate:
spec:
storageClassName: local-path
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi
grafana:
admin:
existingSecret: grafana-admin
# наши kubelet'ы отвечают сертификатами от CA кластера, проверку можно включить
kubelet:
serviceMonitor:
tlsConfig:
insecureSkipVerify: false
Последний блок — та же тема, что и с metrics-server. По умолчанию чарт опрашивает kubelet'ы с insecureSkipVerify: true, потому что на обычном кластере kubeadm их сертификаты самоподписанные. У нас они подписаны CA кластера, и с проверкой все девять целей kubelet (по три адреса на узел) остаются up: CA чарт берёт из ConfigMap kube-root-ca.crt, который есть в каждом неймспейсе.
Чарт ставится из OCI-реестра одной командой с явной версией (подробно Helm — в части 27):
$ helm install kps oci://ghcr.io/prometheus-community/charts/kube-prometheus-stack \
--version 92.17.1 -n monitoring -f kps-values.yaml
...
STATUS: deployed
REVISION: 1
DESCRIPTION: Install complete
$ kubectl get pods -n monitoring
NAME READY STATUS RESTARTS AGE
alertmanager-kps-alertmanager-0 2/2 Running 0 8m34s
kps-grafana-6d697c9dd6-x947c 3/3 Running 2 (2m14s ago) 10m
kps-kube-state-metrics-67c8fc75bb-bn5cl 1/1 Running 0 10m
kps-operator-5f77976978-76cft 1/1 Running 0 10m
kps-prometheus-node-exporter-6b98q 1/1 Running 0 10m
kps-prometheus-node-exporter-7wzp5 1/1 Running 0 10m
kps-prometheus-node-exporter-t5rkg 1/1 Running 0 10m
prometheus-kps-prometheus-0 2/2 Running 0 8m32s
Это Prometheus 3.15, Alertmanager 0.34, оператор 0.94 и Grafana 13.3. Два перезапуска Grafana на стенде — не авария: на узлах с двумя vCPU первый старт занимает несколько минут, и контейнер дважды не укладывается в проверку живучести (Container grafana failed liveness probe, will be restarted). В поде Prometheus два контейнера: сам Prometheus и config-reloader, который следит за сгенерированной оператором конфигурацией и просит Prometheus перечитать её без перезапуска.
Особенность kubeadm. kube-controller-manager, kube-scheduler и метрики etcd в kubeadm по умолчанию слушают только 127.0.0.1, а kube-proxy отдаёт метрики на 127.0.0.1:10249. Prometheus в поде до них не дотянется: сразу после установки цели этих четырёх компонентов — down, а среди алертов etcdInsufficientMembers (critical, горит сразу), TargetDown, KubeControllerManagerInstanceUnreachable, KubeSchedulerInstanceUnreachable и KubeProxyInstanceUnreachable. Варианта два.
Первый — перевести их на адрес узла: bind-address: 0.0.0.0 в extraArgs kubeadm для controller-manager и scheduler, адрес узла в listen-metrics-urls у etcd и metricsBindAddress: 0.0.0.0:10249 в ConfigMap kube-proxy с последующим kubectl rollout restart ds/kube-proxy -n kube-system. На стенде после этого все четыре становятся up. Разница в защите: порты 10257 и 10259 controller-manager и scheduler требуют аутентификации (без токена — 403), а etcd и kube-proxy отдают метрики кому угодно (200), поэтому эти порты закрывают файрволом от всех, кроме подсети подов.
Второй — честно выключить эти цели в values, если они не нужны: kubeControllerManager.enabled: false, kubeScheduler.enabled: false, kubeEtcd.enabled: false, kubeProxy.enabled: false. Вместе с целями чарт убирает и связанные с ними алерты.
Доступ к интерфейсам. Для начала — kubectl port-forward -n monitoring svc/kps-grafana 3000:80 и svc/kps-prometheus 9090. Постоянный доступ к Grafana — через Ingress с сертификатом из части 18 и части 19; к Prometheus и Alertmanager собственной аутентификации нет, их наружу без прокси с аутентификацией не публикуют.
ServiceMonitor для hello
hello из части 5 уже отдаёт /metrics: счётчик запросов и версию сборки. Посмотреть можно через прокси API-сервера, не запуская отдельный под:
$ kubectl get --raw /api/v1/namespaces/demo/services/hello:http/proxy/metrics
# HELP hello_requests_total Requests served by the / handler.
# TYPE hello_requests_total counter
hello_requests_total 2122
# HELP hello_build_info Build information.
# TYPE hello_build_info gauge
hello_build_info{version="1.0"} 1
Чтобы Prometheus начал их собирать, нужен ServiceMonitor — объект, который говорит оператору: «опрашивай эндпоинты сервисов с такими метками, на таком порту, по такому пути». Метку на Service мы поставили ещё в части 15, а порт указывается по имени из Service (у hello он называется http):
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: hello
namespace: demo
spec:
selector:
matchLabels:
app.kubernetes.io/name: hello
endpoints:
- port: http
path: /metrics
interval: 30s
Оператор дописывает в конфигурацию Prometheus задание с Kubernetes service discovery: Prometheus сам следит за EndpointSlice сервисов с такими метками (так задано serviceDiscoveryRole: EndpointSlice в values; по умолчанию оператор всё ещё использует API Endpoints, объявленный устаревшим в Kubernetes 1.33) и опрашивает адреса всех подов за ними; к каждому ряду Prometheus добавит метки namespace, pod, service, container, endpoint, job и instance. PodMonitor делает то же без сервиса — по меткам подов; удобен для DaemonSet'ов и подов, у которых сервиса нет.
Если в неймспейсе demo действует политика «запретить всё входящее» из части 20, всё зависит от момента. Prometheus держит с целями постоянные соединения, а Calico уже открытые соединения не рвёт: на стенде почти четыре минуты после default-deny цели оставались up, пока мы сами не убрали политику. Но стоит подам смениться, новое соединение установить не удаётся:
$ kubectl rollout restart deploy/hello -n demo
$ curl -s 'localhost:9090/api/v1/targets?state=active' | jq -r '.data.activeTargets[] | select(.labels.job=="hello") | "\(.labels.pod) \(.health) \(.lastError)"'
hello-74d65597f7-5lfft down Get "http://172.20.172.25:8080/metrics": context deadline exceeded
hello-74d65597f7-qqdxp down Get "http://172.20.211.63:8080/metrics": context deadline exceeded
hello-74d65597f7-8vkl6 down Get "http://172.20.211.61:8080/metrics": context deadline exceeded
То есть мониторинг может сломаться не в момент применения политики, а при следующей выкатке. Нужна разрешающая политика — только для неймспейса мониторинга и только на порт метрик; с ней цели возвращаются в up при следующем опросе:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-prometheus
namespace: demo
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: hello
policyTypes: ["Ingress"]
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring
podSelector:
matchLabels:
app.kubernetes.io/name: prometheus
ports:
- port: 8080
protocol: TCP
Полезные запросы
Троттлинг CPU — доля периодов планировщика CFS, в которых контейнер упирался в CPU-лимит (часть 10). Выше 25 % — приложение регулярно тормозят, даже если средняя загрузка низкая. Ряды есть только у контейнеров с CPU-лимитом: hello в этом запросе не появится, а пробный под с limits.cpu: 50m и вечным циклом на стенде показывает ровно 1, то есть 100 %:
sum by (namespace, pod, container) (rate(container_cpu_cfs_throttled_periods_total{container!=""}[5m]))
/
sum by (namespace, pod, container) (rate(container_cpu_cfs_periods_total{container!=""}[5m]))
Рестарты за последний час — по kube-state-metrics:
increase(kube_pod_container_status_restarts_total[1h]) > 0
OOM — контейнеры, последний раз завершённые по OOM, и сами события OOM от cAdvisor:
kube_pod_container_status_last_terminated_reason{reason="OOMKilled"} == 1
increase(container_oom_events_total{container!=""}[1h]) > 0
Память относительно лимита — кто близок к OOM:
max by (namespace, pod, container) (container_memory_working_set_bytes{container!=""})
/
max by (namespace, pod, container) (kube_pod_container_resource_limits{resource="memory"})
> 0.9
Requests против потребления — для подбора requests: отношение реального CPU к запрошенному за сутки. Значения около 0,1 означают, что под заказывает в десять раз больше, чем тратит, и занимает место на узле зря:
sum by (namespace, pod) (rate(container_cpu_usage_seconds_total{container!=""}[1d]))
/
sum by (namespace, pod) (kube_pod_container_resource_requests{resource="cpu"})
Свободное место на PVC — метрики kubelet по смонтированным томам, и прогноз, что том заполнится в ближайшие четыре часа:
kubelet_volume_stats_available_bytes / kubelet_volume_stats_capacity_bytes < 0.1
predict_linear(kubelet_volume_stats_available_bytes[6h], 4 * 3600) < 0
predict_linear строит линейную регрессию по точкам за 6 часов и экстраполирует на 4 часа вперёд. Эти метрики есть только у томов, драйвер которых реализует получение статистики. У csi-driver-nfs и csi-driver-smb это так, но для общего NFS-экспорта со subDir «свободно» показывает место на всём экспорте, а не квоту тома: на стенде PVC на 1 ГиБ отчитывается о ёмкости 17 ГБ. А вот тома local-path — обычные каталоги узла, и рядов kubelet_volume_stats_* у них нет вовсе, значит, том самого Prometheus этими запросами не увидеть, и алерт KubePersistentVolumeFillingUp за ним не следит. Для него смотрят на файловую систему узла:
node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"} < 0.1
Правило алерта
Правила записываются объектом PrometheusRule. Каждое правило алерта — выражение PromQL: пока оно возвращает хотя бы один ряд, алерт активен. for — сколько времени выражение должно быть истинным подряд, прежде чем алерт перейдёт из pending в firing и уйдёт в Alertmanager. У hello два показателя: доступность цели и счётчик запросов, — на них и напишем:
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: hello
namespace: demo
spec:
groups:
- name: hello
rules:
- alert: HelloTargetDown
expr: up{namespace="demo", service="hello"} == 0
for: 2m
labels:
severity: warning
annotations:
summary: Prometheus не может снять метрики с пода hello
description: 'Под {{ $labels.pod }} не отвечает на /metrics уже 2 минуты.'
- alert: HelloHighRequestRate
expr: sum(rate(hello_requests_total{namespace="demo"}[5m])) > 50
for: 5m
labels:
severity: warning
annotations:
summary: hello получает больше 50 запросов в секунду
description: 'Сейчас {{ $value | humanize }} запросов в секунду, HPA может не успевать.'
Метка severity нужна Alertmanager'у для маршрутизации: critical — будить дежурного, warning — в рабочий канал. Кроме алертов, в PrometheusRule пишут recording rules — заранее вычисляемые выражения, которые сохраняются как новые ряды: тяжёлый запрос для дашборда считается раз в интервал, а не при каждом открытии страницы.
В наборе правил чарта есть алерт Watchdog, который горит всегда. Это не ошибка, а проверка всей цепочки: его настраивают на внешний сервис «мёртвой руки», который поднимает тревогу, если Watchdog перестал приходить, — то есть сломался сам мониторинг.
Смотрим руками
API метрик напрямую. kubectl top — всего лишь клиент к агрегированному API:
$ kubectl get --raw /apis/metrics.k8s.io/v1beta1/namespaces/demo/pods/hello-74d65597f7-5lfft | jq '{window, containers}'
{
"window": "27.174s",
"containers": [
{
"name": "hello",
"usage": {
"cpu": "4203724n",
"memory": "6228Ki"
}
}
]
}
window — интервал между двумя точками, по которым посчитан CPU: обычно около 15 секунд, но метки времени ставит kubelet, и окно бывает длиннее; 4203724n — наноядра, около 4,2m.
Сертификат kubelet — после serverTLSBootstrap подписан CA кластера, а не самим kubelet:
user@k8s-master-1:~$ sudo openssl x509 -in /var/lib/kubelet/pki/kubelet-server-current.pem -noout -issuer -subject -ext subjectAltName -enddate
issuer=CN=kubernetes
subject=O=system:nodes, CN=system:node:k8s-master-1
X509v3 Subject Alternative Name:
DNS:k8s-master-1, IP Address:192.168.10.17
notAfter=Oct 24 10:32:48 2027 GMT
Что оператор сделал из ServiceMonitor. Сгенерированная конфигурация лежит в Secret, сжатая gzip:
$ kubectl get secret prometheus-kps-prometheus -n monitoring -o jsonpath='{.data.prometheus\.yaml\.gz}' \
| base64 -d | gunzip | grep -A16 'job_name: serviceMonitor/demo/hello'
- job_name: serviceMonitor/demo/hello/0
honor_labels: false
kubernetes_sd_configs:
- role: endpointslice
namespaces:
names:
- demo
scrape_interval: 30s
metrics_path: /metrics
relabel_configs:
- source_labels:
- job
target_label: __tmp_prometheus_job_name
- action: keep
source_labels:
- __meta_kubernetes_service_label_app_kubernetes_io_name
- __meta_kubernetes_service_labelpresent_app_kubernetes_io_name
Цели и их состояние — через HTTP API Prometheus:
$ kubectl port-forward -n monitoring svc/kps-prometheus 9090 &
$ curl -s 'localhost:9090/api/v1/targets?state=active' | jq -r '.data.activeTargets[] | select(.labels.namespace=="demo") | "\(.labels.pod) \(.health) \(.lastScrapeDuration)"'
hello-74d65597f7-5lfft up 0.000396057
hello-74d65597f7-qqdxp up 0.000679508
hello-74d65597f7-8vkl6 up 0.000627355
$ curl -s localhost:9090/api/v1/query --data-urlencode 'query=prometheus_tsdb_head_series' | jq -r '.data.result[0].value[1]'
76398
TSDB на диске — в локальном томе, на узле, где запущен Prometheus. Изнутри контейнера не посмотреть: образ distroless, ls в нём нет (exec: "ls": executable file not found in $PATH), а путь к каталогу подскажет PV:
$ kubectl get pv $(kubectl get pvc -n monitoring prometheus-kps-prometheus-db-prometheus-kps-prometheus-0 -o jsonpath='{.spec.volumeName}') -o jsonpath='{.spec.hostPath.path}'
/opt/local-path-provisioner/pvc-3aa8da09-dea5-4d57-baaa-abeb390d8832_monitoring_prometheus-kps-prometheus-db-prometheus-kps-prometheus-0
user@k8s-node-2:~$ sudo ls -1 /opt/local-path-provisioner/pvc-3aa8da09-…/prometheus-db
01M6HPRQAS1NC6XZHWA1Y7GYB4
chunks_head
lock
queries.active
wal
Каталог с именем вида ULID — готовый блок: по умолчанию на два часа данных, а со временем блоки укрупняются. wal — журнал предзаписи, chunks_head — отображённые в память чанки свежих данных, lock защищает каталог от второго процесса, queries.active хранит выполняющиеся запросы. Именно с этими файлами Prometheus работает через mmap и блокировки, и именно поэтому им нужен локальный диск.
Алерт в работе. Под нагрузкой из шести подов hello получает больше тысячи запросов в секунду. Через 25 секунд после запуска выражение истинно, и алерт в pending; через пять минут — в firing, и тогда же он появляется в Alertmanager:
$ curl -s localhost:9090/api/v1/alerts | jq -r '.data.alerts[] | select(.labels.alertname | startswith("Hello")) | "\(.labels.alertname) \(.state) \(.activeAt)"'
HelloHighRequestRate firing 2026-11-03T19:07:55.540015347Z
$ kubectl port-forward -n monitoring svc/kps-alertmanager 9093 &
$ curl -s 'localhost:9093/api/v2/alerts?filter=alertname=~"Hello.*"' | jq -r '.[] | "\(.labels.alertname) \(.status.state) \(.startsAt) \(.annotations.description)"'
HelloHighRequestRate active 2026-11-03T19:12:55.540Z Сейчас 1.258k запросов в секунду, HPA может не успевать.
activeAt — когда выражение стало истинным, startsAt в Alertmanager — когда прошло for. Опыт с default-deny из раздела о ServiceMonitor заодно проверяет и второе правило: через две минуты после того, как цели стали down, у HelloTargetDown на каждый под по своему алерту в firing, в описании у каждого — его имя (Под hello-74d65597f7-8vkl6 не отвечает на /metrics уже 2 минуты.). Получателей в Alertmanager мы не настраивали, поэтому дальше API алерт пока никуда не уходит.
Уборка: kubectl delete -f для файлов политик default-deny и allow-prometheus и kubectl delete deployment load -n default, если генератор ещё работает. metrics-server, HPA и Prometheus оставляем: следующая часть продолжит на них.
Метрики отвечают на вопрос «сколько» и «когда»: сколько запросов, когда начался рост. На вопрос «какой именно запрос и почему» они не отвечают — для этого нужны логи и трассы, связанные с метриками общими метками и идентификаторами.
Источники
- Metrics Server — назначение и ограничения, совместимость версий, флаг
--kubelet-insecure-tls. - Resource metrics pipeline и Node metrics data — откуда берутся цифры
kubectl top, адреса kubelet. - Certificate Management with kubeadm —
serverTLSBootstrapи CSRkubelet-serving; kubelet-csr-approver — автоматическое одобрение. - Horizontal Pod Autoscaling и HorizontalPodAutoscaler Walkthrough — алгоритм, допуск,
behavior, типы метрик. - Vertical Pod Autoscaler, KEDA, Cluster Autoscaler, prometheus-adapter — другие автомасштабировщики и адаптер метрик.
- Prometheus: Overview, Data model, Metric types — устройство и модель данных.
- Prometheus: Storage — TSDB, формула объёма, запрет NFS.
- Querying basics и Query functions — PromQL,
rate,histogram_quantile,predict_linear. - Alerting rules и Alertmanager — правила алертов, группировка и подавление.
- kube-prometheus-stack — чарт и его values; kubernetes-mixin — правила алертов и дашборды.
- Prometheus Operator: Getting Started и API reference —
ServiceMonitor,PrometheusRule,serviceDiscoveryRole. - kube-state-metrics, node_exporter, Grafana — компоненты набора.
Дальше — часть 33: логи и трассы. Как устроены логи контейнеров на узле, сбор их агентом в Loki, LogQL, аудит API-сервера, трассировка на OpenTelemetry и Tempo и переход от графика к логу и от лога к трассе в Grafana.
Оставить комментарий могут только зарегистрированные пользователи.
Войдите на сайт или зарегистрируйтесь, чтобы оставить комментарий.