Kubernetes-native reliability: SRE-практики внутри кластера¶
В главе «Инфраструктура как код» мы говорили про декларативное управление инфраструктурой. Kubernetes — это, по сути, фреймворк для декларативного управления подами, и в нём заложено много встроенных механизмов для надёжности. Грех ими не пользоваться. Эта глава — про то, какие именно.
Почему это отдельная тема¶
Kubernetes часто упоминается в книге: «ну, в K8s это», «в кластере K8s», «при деплое в K8s». Но свода SRE-практик именно для Kubernetes нет. А они свои, специфические: readiness probes, PodDisruptionBudget, topology spread, HPA, resource requests — это инструменты, которых нет в обычных VM-деплоях.
Если вы запускаете продакшн в Kubernetes, вы должны знать эти механизмы. Они дают бесплатную надёжность, которую не надо программировать.
Probes: liveness, readiness, startup¶
Probes — это проверки, по которым Kubernetes решает, готов ли под принимать трафик и жив ли он вообще.
Liveness probe¶
Что делает: проверяет, не завис ли процесс. Если проба падает N раз подряд — Kubernetes убивает контейнер и перезапускает.
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
Типичные ошибки: - Слишком жёсткая проверка (например, проверка внешних зависимостей) → под рестартится из-за проблем с базой - Слишком мягкая проверка (всегда возвращает 200) → реально зависший процесс не перезапускается - Нет livenessProbe вовсе → зависший под жрёт ресурсы до ручного вмешательства
Что должно проверять: что процесс вообще жив и отвечает. Не бизнес-логику. Не внешние зависимости.
Readiness probe¶
Что делает: проверяет, готов ли под принимать трафик. Если проба падает — под убирается из Service (балансировки), но не убивается.
readinessProbe:
httpGet:
path: /readyz
port: 8080
periodSeconds: 5
failureThreshold: 2
Что должно проверять: что под может обслуживать запросы. Можно проверять подключение к базе, кэшу, брокеру. Если что-то из этого недоступно — лучше убрать под из балансировки, чем возвращать 5xx.
Startup probe¶
Что делает: даёт медленно стартующим приложениям время на инициализацию, прежде чем начнут работать liveness/readiness проверки.
startupProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 0
periodSeconds: 10
failureThreshold: 30 # 5 минут на старт
Полезно для JVM-приложений с долгим warm-up или для приложений, загружающих модели в память при старте.
Resource requests и limits¶
Без requests/limits Kubernetes не знает, сколько ресурсов нужно поду, и планировщик принимает плохие решения.
Requests — гарантированный минимум¶
resources:
requests:
cpu: 100m # 0.1 CPU
memory: 256Mi
Что это даёт: планировщик ставит под только на ноду, где есть свободные 100m CPU и 256Mi памяти. Под получит минимум столько.
Limits — максимум¶
resources:
limits:
cpu: 500m # 0.5 CPU
memory: 512Mi
Что это даёт: контейнер не съест больше. По памяти — OOMKill при превышении. По CPU — throttling.
Best practices¶
- Requests и limits должны быть установлены для всех контейнеров. Без них — под может сожрать всю ноду.
- Requests < Limits. Если requests == limits, под получит класс QoS
Guaranteed. Если requests не установлен —Burstable. Если requests = 0 —BestEffort(первый кандидат на вытеснение). - Не завышайте requests. Лучше начать с реальных значений (по VPA-рекомендациям), чем просить 4 CPU «на всякий случай».
- Не занижайте memory limit. Память не сжимается, OOM приведёт к рестарту пода.
Quality of Service¶
| Класс | Условие | Поведение при нехватке ресурсов |
|---|---|---|
| Guaranteed | requests == limits для всех контейнеров | Убивается последним |
| Burstable | requests < limits или установлен только requests | Убивается при сильной нехватке |
| BestEffort | Нет requests и нет limits | Убивается первым |
Для критичных сервисов — Guaranteed. Для второстепенных — Burstable.
PodDisruptionBudget: защита от вытеснения¶
PodDisruptionBudget (PDB) — это обещание Kubernetes: даже во время добровольных disruption-ов (нод drain, деплой, autoscale) останется минимум N подов.
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: payment-api
spec:
minAvailable: 2 # минимум 2 пода всегда доступны
selector:
matchLabels:
app: payment-api
Или в процентах:
spec:
minAvailable: 75% # минимум 75% подов
Когда срабатывает: - kubectl drain (обслуживание ноды) - Обновление кластера - HPA scale down - kubectl delete pod (если через deployment)
Когда НЕ защищает: - Случайный OOM kill - Авария ноды (Node failure) - Принудительное удаление (force delete)
Типичные ошибки: - minAvailable: 100% — Kubernetes не сможет сделать drain, застрянет - Нет PDB вообще — Kubernetes может убить все поды одновременно
HPA, VPA, Cluster Autoscaler¶
HorizontalPodAutoscaler (HPA)¶
Масштабирует количество подов по метрикам.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: payment-api
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: payment-api
minReplicas: 3
maxReplicas: 50
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "1k"
Что подавать на вход: CPU, memory, custom metrics (RPS, queue depth, latency).
VerticalPodAutoscaler (VPA)¶
Масштабирует ресурсы контейнеров (requests/limits), а не количество подов.
Когда использовать: - Stateful приложения, которые сложно горизонтально масштабировать - Поиск оптимальных resource requests
Что НЕ надо: использовать VPA одновременно с HPA на одних и тех же метриках. Они будут конфликтовать.
Cluster Autoscaler¶
Масштабирует ноды кластера (добавляет/убирает EC2-инстансы в AWS, ВМ в Yandex Cloud и т.п.).
Триггеры: поды в Pending из-за нехватки ресурсов (scale up), ноды подолгу пустые (scale down).
Topology spread constraints¶
Ограничивает, как поды распределяются по нодам/зонам. Защита от того, чтобы все поды оказались на одной ноде или в одной зоне.
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: payment-api
Что даёт: если у вас 3 зоны и 6 подов, будет по 2 пода в каждой зоне. Падение одной зоны оставит 4 пода доступными.
maxSkew: максимальная разница между зонами. 1 = строго равномерно. 2 = допускается разница в один под.
whenUnsatisfiable: DoNotSchedule: если правило нельзя выполнить, под не будет создан. Альтернатива — ScheduleAnyway (попытаться, но не гарантировать).
PodDisruptionConditions и graceful shutdown¶
Когда под получает SIGTERM (например, при drain), у него есть terminationGracePeriodSeconds (по умолчанию 30 секунд), чтобы завершить текущие запросы.
Что должно приложение: 1. Получить SIGTERM 2. Перестать принимать новые запросы (readiness probe начнёт падать → Service уберёт под) 3. Дождаться завершения текущих запросов (или прервать их, если есть deadline) 4. Закрыть соединения с базой 5. Завершиться
Best practices: - Время graceful shutdown должно быть меньше terminationGracePeriodSeconds - Readiness probe должен быть настроен так, чтобы быстро убирать под из балансировки (periodSeconds: 1-2) - PreStop hook может помочь: sleep 5 — даём балансировщику время среагировать на изменение readiness
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 5"]
terminationGracePeriodSeconds: 60
Anti-affinity¶
Запрещает ставить поды с одинаковыми labels на одну ноду или в одну зону.
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: payment-api
topologyKey: kubernetes.io/hostname
Разница с topology spread: - Anti-affinity — запрещает соседство (hard или soft) - Topology spread — контролирует балансировку (численное распределение)
Network Policies¶
По умолчанию все поды в кластере могут общаться друг с другом. Это удобно для разработки, опасно для прода.
NetworkPolicy ограничивает сетевой трафик между подами:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: payment-api
spec:
podSelector:
matchLabels:
app: payment-api
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- port: 8080
egress:
- to:
- podSelector:
matchLabels:
app: postgres
ports:
- port: 5432
- to:
- namespaceSelector:
matchLabels:
name: kube-system
ports:
- port: 53
- protocol: UDP
Default deny: хорошая практика — сначала запретить всё, потом разрешать только нужное.
Замечание: NetworkPolicy работает только если CNI это поддерживает. Calico, Cilium — да. Flannel — нет.
Persistent Volumes и state¶
Если у вас stateful приложения (базы данных, очереди с persistent storage), нужно правильно настроить StorageClass, PersistentVolume, PersistentVolumeClaim.
Ключевые практики: - Использовать ReadWriteOnce для одного пода, ReadWriteMany для shared storage - Не хранить данные на ноде напрямую — только через PV - Регулярные снапшоты (VolumeSnapshot API) - Тестировать восстановление из бэкапа (отдельная глава про DR)
Security context¶
Минимальные права для контейнера:
securityContext:
runAsNonRoot: true
runAsUser: 1000
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
seccompProfile:
type: RuntimeDefault
Что даёт: - Не запускаем от root (компрометация контейнера ≠ компрометация ноды) - Read-only root filesystem (нельзя записать бинарник, который будет выполнен) - Без capabilities (нет CAP_NET_RAW, CAP_SYS_ADMIN и т.п.) - seccomp профиль (ограниченный набор syscall-ов)
Распространённые ошибки¶
1. Нет PDB. Любой kubectl drain убивает все поды одновременно. Забыли — узнали на первом maintenance.
2. Readiness = liveness. Разные проверки для разных целей. Liveness = процесс жив. Readiness = может обслуживать.
3. Лимиты по памяти слишком жёсткие. OOMKill при первом скачке нагрузки.
4. Anti-affinity required при 3 репликах на 2 нодах. Не сможем зашедулить третий под. Используйте preferredDuringScheduling.
5. HPA scale-up слишком медленный. По умолчанию HPA увеличивает реплики на 100% каждые 30 секунд. Для нагруженных систем — настройте behavior для быстрого scale-up.
6. Нет timeouts на входящем трафике через Ingress. Под может зависнуть на неопределённое время, если downstream сервис медленный.
7. Secrets в environment variables. Environment variables видны в kubectl describe pod. Лучше mount as files с subPath или использовать external secret operator.
Резюме¶
Kubernetes даёт десятки механизмов надёжности бесплатно — нужно только правильно их настроить:
- Probes для автоматического рестарта и балансировки
- Requests/limits для планирования и предотвращения OOM
- PDB для защиты от вытеснения
- HPA/VPA/Cluster Autoscaler для масштабирования
- Topology spread и anti-affinity для распределения по зонам/нодам
- Network Policies для сетевой изоляции
- Security context для минимальных прав
- Graceful shutdown для корректного завершения
Каждый из этих инструментов не заменяет хорошую архитектуру, но вместе они дают надёжность, которую в эпоху VM приходилось настраивать вручную. Не используйте K8s как «просто scheduler» — используйте его как платформу надёжности.
См. также: «Инфраструктура как код», «Иммутабельная инфраструктура», «Canary и Progressive Delivery», «Chaos Engineering», «Service Mesh», «eBPF».