Разбор двухчасового инцидента на продуктивном Kubernetes-кластере, причиной которого стала неочевидная каскадная цепочка из настроек Nginx, cgroups systemd и специфики работы ядра Linux при динамическом изменении RAM виртуалки.
👈 Читать разбор
Если коротко, симптомы и цепочка отказа:
⚪️Симптом: клиенты начали получать
503 Service Unavailable, в логах внешнего proxy (Envoy Gateway) — UF и upstream_reset_before_response_started. В error.log самого ingress-nginx при этом была тишина, но Readiness-проба падала (:10246 connection refused), выбивая под из ротации.⚪️Лимит процессов (
pids.max): в логах ноды (journalctl) обнаружилась ошибка cgroup: fork rejected by pids controller. Контейнер упёрся в лимит pids.max = 975 и не мог создать новые воркеры.⚪️Накопление потоков в Nginx: из-за долгоживущих WebSocket-соединений старые воркеры не успевали завершиться за время
worker-shutdown-timeout (240s). Частые reload'ы конфига привели к сосуществованию ~30 воркеров (3 поколения). Включенный aio thread pool (threads=32) давал по 33 задачи на воркер. Итого: ~990 задач, что превысило лимит 975.⚪️Первопричина (KVM Hotplug &
threads-max): лимит pids.max выставлялся через systemd DefaultTasksMax (15% от kernel.threads-max). Лимит threads-max составлял всего 6501 (в десятки раз ниже нормы), потому что KVM-виртуалка стартовала с минимальной RAM, а затем была расширена до 16 ГБ через ballooning/hotplug. Ядро считало threads-max один раз при загрузке OS и не пересчитало его при увеличении памяти.При использовании динамического расширения памяти (RAM hotplug/ballooning) на нодах Kubernetes ядро Linux не пересчитывает kernel.threads-max на лету. Это оставляет контейнеры с заниженными лимитами pids.max, что при штатных перезагрузках Nginx и наличии WebSocket/thread-пулов неизбежно приводит к fork rejected и падению ingress-контроллера.
Лимиты нужно проверять и корректировать на нодах вручную.
@DevOpsKaz 😛
