Pinterest нашла причину сетевых сбоев ML-тренировок: зомби-cgroup от постороннего ECS-агента

В начале 2025 года ML-платформенная команда Pinterest сообщила команде PinCompute (платформа Kubernetes), что распределённые тренировки на Ray на GPU-машинах падают не всегда, но достаточно часто, чтобы это было заметно: логи показывали кратковременную потерю сетевого соединения, из-за которой задачи обучения обрывались, а для части сценариев успешность запуска падала более чем на 25%. Так началось расследование, растянувшееся больше чем на три месяца.

Первой зацепкой стало то, что сбои совпадали по времени со «сбросами» (reset) сетевого драйвера ENA (Elastic Network Adapter) на EC2-инстансах Kubernetes-кластеров. По документации AWS, такой сброс, механизм самовосстановления, который срабатывает, когда поток передачи данных не получает ответа дольше зашитого в код порога в 5 секунд; типичная причина такой паузы, голодание сетевых потоков по процессорному времени.

Команда заметила, что на проблемных машинах повышена системная загрузка CPU, что укладывалось в теорию голодания. Перепробовали стандартные средства: включили Transparent Huge Pages, перешли на аллокатор памяти jemalloc, закрепили ядра CPU за тренировочными задачами через taskset, перенастроили прерывания сетевой карты на другие ядра, ничего не убрало сбросы полностью. Перезагрузка проблемных машин помогала, но лишь примерно на неделю, после чего сбросы возвращались.

Третья зацепка: сбросы происходили только на машинах в одной зоне доступности AWS (us-east-1a), хотя конфигурации кластеров в разных зонах выглядели идентичными; поддержка AWS подтвердила, что проблема не на стороне облака.

Чтобы найти конкретного «пожирателя» CPU, инженеры использовали perf и mpstat. Поскольку GPU-машины имели по 96 виртуальных ядер, агрегированная картина CPU ничего не показывала, но подетальная статистика выявила, что одно конкретное ядро (например, ядро 39) иногда занимало 100% системного CPU в течение нескольких секунд, и это точно совпадало по времени со сбросом сети.

Поскольку сбросы происходили непредсказуемо, команда выстроила «временное профилирование»: выделила несколько машин, запустила на них одинаковые тренировочные задачи и всю ночь (около 12 часов) записывала perf короткими интервалами по 2 минуты. Когда сброс наконец попал в записи, инженеры разобрали нужный отрезок в инструменте Flamescope (разработан в Netflix) и увидели, что за несколько секунд до сброса kubelet, лёгкий агент Kubernetes на узле, кратковременно занимал около 6,5% CPU против обычных менее 1%, и почти всё это время уходило на системный вызов mem_cgroup_nr_lru_pages, то есть на перебор cgroup памяти хоста.

Проверка числа cgroup на проблемной машине подтвердила догадку (натолкнувшись на статью инженеров Oracle про «зомби»-cgroup памяти): ядро отслеживало 68 680 cgroup памяти через /proc/cgroups, тогда как реально использовалось лишь 240, то есть kubelet на каждой проверке перебирал десятки тысяч лишних записей.

Источником «зомби» оказался контейнер amazon-ecs-agent, который на каждой проблемной машине был виден в docker ps как постоянно перезапускающийся, никогда не старше нескольких секунд. Выяснилось, что для GPU-инстансов Pinterest использовала базовый образ AWS Deep Learning AMI (Ubuntu 20.04), который по умолчанию ставит агент ECS отдельным systemd-юнитом, хотя Pinterest платформу ECS вообще не использует. Без прав на подключение к ECS-кластеру агент не мог стартовать и закономерно падал, а каждое такое падение оставляло после себя «осиротевшую» cgroup памяти, которую ядро не освобождало; за несколько дней таких падений накапливались десятки тысяч зомби. Это же объяснило, почему перезагрузка помогала лишь временно, она сбрасывала счётчик cgroup.

Исправление оказалось простым: отключить systemd-юнит ECS-агента в базовом образе и перезагрузить все затронутые машины, чтобы вычистить накопленных зомби. После этого число cgroup памяти стабилизировалось, а тренировки Ray вернулись к ожидаемой успешности.

Загадка с «безопасной» зоной доступности разрешилась отдельным, не связанным багом: одинаковый бинарник Kubernetes в две зоны доставлялся по двум разным URL, и в «незатронутой» зоне это расхождение ломало один из шагов отправки метрики при бутстрапе узла, из-за чего скрипт бутстрапа помечался как неудачный. А поскольку systemd-юнит ECS-агента был настроен на запуск только после успешного завершения бутстрапа, в этой зоне агент вообще не запускался и «зомби» не накапливались. Команда Kubernetes уже независимо чинила именно эту проблему с URL, и её исправление вскоре привело бы к тем же сетевым сбоям и во «второй», ранее не тронутой зоне.

Среди выводов, которые Pinterest выносит из этой истории: заводить парковые метрики для редких переходных событий, строить воспроизводимые изолированные среды для отладки, вкладываться во временное профилирование (команда развивает у себя инструмент gProfiler), внимательно проверять, что по умолчанию запускается в базовых образах ОС, следить за частотой падений systemd-юнитов и не доверять «одинаковым на вид» окружениям, за расхождением в поведении обычно стоит расхождение в конфигурации.

Ключевые факты

  • Тренировки Ray на GPU-машинах Pinterest падали из-за кратковременной потери сети; расследование заняло больше трёх месяцев, для части сценариев успешность запуска падала более чем на 25%.
  • Сбои совпадали со сбросами сетевого драйвера ENA, которые AWS связывает с голоданием по CPU; huge pages, jemalloc, taskset и перенастройка прерываний не помогли, перезагрузка снимала проблему лишь примерно на неделю.
  • Временное профилирование через perf и Netflix Flamescope показало: перед каждым сбросом kubelet кратковременно занимал около 6,5% CPU (против обычного менее 1%), перебирая cgroup памяти хоста.
  • На проблемной машине ядро отслеживало 68 680 cgroup памяти, а реально использовалось только 240, источником зомби оказался постоянно падающий контейнер amazon-ecs-agent, включённый по умолчанию в образе AWS Deep Learning AMI, хотя Pinterest ECS не использует.
  • Отключение systemd-юнита ECS-агента и перезагрузка машин решили проблему; загадка с «безопасной» зоной доступности оказалась побочным эффектом отдельного, не связанного бага с доставкой бинарника Kubernetes.

Почему это важно

История показывает, как один незаметный компонент базового облачного образа может месяцами ломать критичную инфраструктуру: команде Pinterest потребовалось больше трёх месяцев и несколько раундов профилирования, чтобы дойти до причины, которая на бумаге звучит просто, забытый агент ECS, включённый по умолчанию. Это разбор методологии отладки трудноуловимых узких мест по CPU в масштабной инфраструктуре Kubernetes и GPU, а не новость про ИИ-продукт, но именно от таких скрытых дефектов зависит стабильность обучения моделей.

Кому это важно

Инженерам платформенных и SRE-команд, которые держат Kubernetes-кластеры на AWS, особенно на GPU-инстансах с образом AWS Deep Learning AMI; ML-платформенным командам, использующим Ray для распределённого обучения; всем, кто расследует редкие сетевые сбои и необъяснимые скачки CPU в проде.

Как это применить

Проверить, какие systemd-юниты и контейнеры запускаются по умолчанию в базовом образе (особенно облачные AMI), не работает ли на хосте ненужный агент вроде ecs-agent. Завести парковые метрики по редким событиям (сбросы сетевого драйвера, рестарты контейнеров), чтобы ловить закономерности по зонам и кластерам. При подозрении на утечку cgroup сравнить вывод cat /proc/cgroups | grep memory с реальным числом каталогов в /sys/fs/cgroup/memory/, расхождение в десятки тысяч и есть зомби. Для трудноуловимых пиков CPU использовать временное профилирование (perf короткими интервалами плюс инструмент вроде Flamescope) вместо разовых снимков.

Можно ли доверять

Да: это подробный разбор от первого лица в инженерном блоге Pinterest на Medium, подписанный десятью инженерами и руководителями PinCompute и ML-платформы, с конкретными командами, логами, числами и стек-трейсами. Автоматическая докачка страницы вернула ошибку HTTP 403 (блок платформы), но полный текст статьи был получен из сохранённого снимка и совпадает с проверенной фактурой источника.

Риски и подводные камни

Пост не называет ни общую сумму потерянных денег или GPU-часов, ни точное число затронутых машин или кластеров, ни точную календарную дату исправления, только качественную оценку «значительное замедление» и цифру «более 25%» для части сценариев. Неясно, актуальна ли проблема сейчас: не сказано, изменил ли AWS дефолт AWS Deep Learning AMI и сталкивались ли с тем же другие клиенты AWS. Исправление специфично для инфраструктуры Pinterest (отключение юнита ECS-агента в своём образе), а не патч от AWS.

«Чтобы вызвать голодание по процессору, может быть достаточно того, что лишь одно ядро CPU сильно загружено и блокирует «невезучий» сетевой поток, оказавшийся именно на этом ядре.»

— инженеры Pinterest (команда PinCompute)