Kubernetes в проде: уроки эксплуатации
Поднять кластер легко. Сложности начинаются на втором году, когда нагрузка выросла, а кто-то забыл выставить лимиты. Собрал то, что чаще всего болит.
Kubernetes на демо и Kubernetes под реальной банковской нагрузкой — это два разных мира. В первом всё работает само. Во втором ты узнаёшь о проблемах в 3 часа ночи. За несколько лет эксплуатации production-кластеров я собрал список вещей, которые ломаются чаще всего — и почти все они про дисциплину, а не про экзотику.
1. Probes, которые врут
Самая частая боль — неправильно настроенные liveness и readiness probes.
Классическая ошибка: liveness-проба ходит в эндпоинт, который зависит от БД. База тормозит → проба
падает → Kubernetes перезапускает под → нагрузка на оставшиеся реплики растёт → каскадный отказ.
Liveness отвечает на вопрос «жив ли процесс». Readiness — «готов ли он принимать трафик». Путать их — значит превращать временную деградацию в полный отказ.
livenessProbe: # только сам процесс, без внешних зависимостей
httpGet: { path: /actuator/health/liveness, port: 8443 }
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe: # учитывает БД, очереди и т.п.
httpGet: { path: /actuator/health/readiness, port: 8443 }
periodSeconds: 5
failureThreshold: 3
2. Ресурсы: requests и limits — не формальность
Под без requests планировщик ставит куда угодно; под без limits может съесть
память соседей и спровоцировать OOM на ноде. Мы взяли за правило: у каждого пода заданы и requests, и
limits, а значения берём не из головы, а из метрик реального потребления.
- CPU requests — по реальному среднему, чтобы планировщик не переуплотнял ноды;
- Memory limit — с запасом над пиком, иначе словишь
OOMKilledпод нагрузкой; - для JVM не забываем про
-XX:MaxRAMPercentage, иначе heap не знает про лимит контейнера.
3. Stateful-приложения требуют уважения
Запускать базу данных в Kubernetes можно — но только понимая, во что ввязываешься. StatefulSet,
правильные PersistentVolume с подходящим storage class, аккуратные стратегии обновления и,
снова, проверенные бэкапы. Если данные критичны и команда небольшая — иногда честнее держать БД вне
кластера. Это не поражение, а трезвая оценка рисков.
4. Сеть: deny by default
По умолчанию в Kubernetes любой под может ходить в любой другой — для банковской среды это
неприемлемо. Мы внедрили NetworkPolicy с принципом deny-by-default: явно разрешён только
нужный трафик, остальное закрыто.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: default-deny, namespace: prod }
spec:
podSelector: {}
policyTypes: [ Ingress, Egress ] # дальше точечно открываем нужное
Поверх — Ingress с TLS-терминацией, rate limiting и нормальными таймаутами. Открытый
наружу сервис без ограничений рано или поздно найдут.
5. Логи и метрики: пока не видишь — не управляешь
Дебажить kubectl logs по отдельным подам не масштабируется. Мы свели всё в централизованное
логирование (ELK / OpenSearch) и метрики в Prometheus + Grafana. Алерты завязаны на симптомы,
которые чувствует пользователь (рост latency, ошибки 5xx), а не на каждый чих инфраструктуры — иначе
дежурный перестаёт реагировать на алерты вообще.
# симптом, а не причина: пользователь видит ошибки
- alert: HighErrorRate
expr: sum(rate(http_requests_total{status=~"5.."}[5m]))
/ sum(rate(http_requests_total[5m])) > 0.02
for: 5m
labels: { severity: page }
6. Обновления кластера — это проект, а не команда
Апгрейд версии Kubernetes мы планируем как отдельную задачу: читаем changelog на предмет deprecated
API, прогоняем на стейдже, обновляем ноды по одной с drain и контролем
PodDisruptionBudget. Спешка здесь стоит дороже всего.
Итог: скучная эксплуатация — это успех
Kubernetes даёт огромную силу, но вместе с ней — много способов выстрелить себе в ногу. Почти все инциденты, что я разбирал, сводились к пропущенным базовым вещам: probes, ресурсы, сеть, наблюдаемость. Хорошая эксплуатация выглядит скучно — и именно в этом её ценность.