Проблемы в производственной среде — это всегда беда. Происходят именно тогда, когда уходишь домой, а причина всегда кажется глупой. Недавно у нас на узлах в кластере Kubernetes закончилась память, правда узел тут же восстановился, без видимых прерываний. Сегодня мы расскажем об этом случае, о том, какой урон мы понесли и как намерены избегать подобной проблемы в будущем.
Случай первый
Суббота, 15 июня 2019 г., 17:12
Blue Matador (да, мы мониторим сами себя!) создает оповещение: событие на одном из узлов в продакшен-кластере Kubernetes — SystemOOM.
17:16
Blue Matador создает предупреждение: низкий уровень EBS Burst Balance в корневом томе узла — того, где имело место событие SystemOOM. И хотя предупреждение о Burst Balance пришло после оповещения о SystemOOM, фактические данные CloudWatch показывают, что Burst Balance достиг минимального уровня в 17:02. Причина задержки в том, что метрики EBS постоянно отстают на 10-15 минут, и наша система не отлавливает все события в режиме реального времени.
17:18
Вот сейчас я и увидел оповещение и предупреждение. Быстро выполняю kubectl get pods, чтобы посмотреть, какой ущерб мы понесли, и с удивлением обнаруживаю, что pod'ов в приложении умерло ровно 0. Выполняю kubectl top nodes, но и эта проверка показывает, что у подозреваемого узла неполадки с памятью; правда, он уже восстановился и памяти использует примерно 60%. Время 5 вечера, и крафтовый пивасик уже греется. Убедившись, что узел работоспособен и ни один pod не пострадал, я решил, что произошла случайность. Если что, разберусь в понедельник.
Вот наша с СТО переписка в Slack за тот вечер:
Случай второй
Суббота, 16 июня 2019 г., 18:02
Blue Matador генерирует оповещение: событие уже на другом узле, тоже SystemOOM. Должно быть, СТО в этот момент как раз смотрел в экран смартфона, потому что написал мне и заставил немедленно заняться событием, сам-де не может включить компьютер (не пора ли снова переустановить Windows?). И снова с виду все нормально. Ни один pod не убит, а узел едва ли потребляет 70% памяти.
18:06
Blue Matador снова генерирует предупреждение: EBS Burst Balance. Второй раз за сутки, а значит, на тормозах я эту проблему спустить не могу. В CloudWatch без изменений, Burst Balance отклонился от нормы за 2 с лишним часа до того, как обозначилась проблема.
18:11
Захожу в Datalog и просматриваю данные по потреблению памяти. Вижу, что прямо перед самым событием SystemOOM, подозреваемый узел и правда забрал много памяти. След ведет к нашим fluentd-sumologic pod'ам.
Ясно видно резко отклонение в потреблении памяти, примерно в то же время, когда случились события SystemOOM. Мой вывод: именно эти pоd'ы и забирали всю память, а когда случилось SystemOOM, Kubernetes понял, что эти pod'ы можно убить и запустить заново, чтобы вернуть нужную память, не затронув другие мои pod'ы. Какой ты молодец, Kubernetes!
Так почему я не увидел этого еще в субботу, когда разбирался, какие pod'ы перезапустились? Дело в том, что fluentd-sumologic pod'ы я запускаю в отдельном неймспейсе и в спешке просто не додумался в него заглянуть.
Вывод 1: Когда ищете перезапустившиеся pod'ы, проверяйте все неймспейсы.
Получив эти данные, я подсчитал, что в ближайшие сутки память на других узлах не закончится, однако пошел дальше и перезапустил все sumologic pod'ы, чтобы они начали работать с низким потреблением памяти. Следующим утром на скорую руку планирую, как бы встроить работу над проблемой в план на неделю и при этом не слишком загрузить воскресный вечер.
23:00
Посмотрел очередную серию "Черного зеркала" (мне, кстати, понравилась Майли) и решил глянуть, как там дела у кластера. Потребление памяти в норме, так что смело оставляю все как есть на ночь.
Починка
В понедельник выкроил время на эту проблему. Уж больно не охота возиться с ней каждый вечер. Что мне известно на данный момент:
- Fluentd-sumologic контейнеры сожрали уйму памяти;
- Событию SystemOOM предшествует высокая дисковая активность, но какая именно — не знаю.
Сперва я думал, что fluentd-sumologic контейнеры принимаются кушать память при внезапном наплыве логов. Однако, проверив Sumologic, я увидел, что логи использовались стабильно, и в то же время, когда возникали неполадки, увеличения в данных логов не происходило.
Немного погуглив, я нашел вот эту задачу на github, в которой предлагется скорректировать некоторые настройки Ruby — чтобы снизить потребление памяти. Я решил попробовать, добавил переменную среды в спецификации pod'а и запустил его:
env:
- name: RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR
value: "0.9"
Проглядывая манифест fluentd-sumologic, я заметил, что не определил запросы на ресурсы и ограничения. Начинаю подозревать, что фикс RUBY_GCP_HEAP сотворит какое-нибудь чудо, так что сейчас есть смысл задать ограничения потребления памяти. Даже если не устраню неполадки с памятью, то получится хотя бы ограничить ее потребление этим набором pod'ов. Используя kubectl top pods | grep fluentd-sumologic, я уже знаю, сколько ресурсов затребовать:
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "1024Mi"
cpu: "250m"
Вывод 2: Задавайте ограничения по ресурсам, особенно для сторонних приложений.
Проверка исполнения
Спустя несколько дней подтверждаю, что вышеназванная метода работает. Потребление памяти было стабильным, и — никаких неполадок ни с одним компонентом Kubernetes, EC2 и EBS. Теперь ясно, как важно определять запросы на ресурсы и ограничения для всех запускаемых мною pod'ов, и вот что предстоит сделать: применить некое сочетание ограничений по ресурсам по умолчанию и квот на ресурсы.
Последняя неразгаданная тайна — EBS Burst Balance, который совпал с событием SystemOOM. Я знаю, что когда памяти мало, ОС использует область подкачки, чтобы не остаться совсем без памяти. Но я-то не вчера родился и в курсе, что Kubernetes даже не запустится на серверах, где активирован файл подкачки. Просто желая удостовериться, я залез на свои узлы через SSH — проверить, не активирован ли файл подкачки; я использовал как свободную память, так и ту, что в области подкачки. Файл не был активирован.
А раз подкачка не при делах, больше у меня зацепок относительно того, что вызвало рост потоков ввода-вывода, из-за чего на узле чуть не закончилась память, нет. Вообще, у меня есть догадка: сам fluentd-sumologic pod в это время писал уйму журнальных сообщений, возможно даже, журнальное сообщение, связанное с настройкой Ruby GC. Возможно также, есть другие источники Kubernetes или journald сообщений, которые становятся не в меру продуктивны, когда память заканчивается, а я их отсеял, пока настраивал fluentd. К несчастью, у меня больше нет доступа к файлам логов, записанных непосредственно перед тем, как случилась неисправность, и сейчас никак не копнуть глубже.
Вывод 3: Пока возможность есть, копайте глубже при анализе первопричин, какой бы проблемой ни занимались.
Заключение
И хотя я не докопался до первопричин, уверен, они не нужны, чтобы предотвратить те же неисправности в будущем. Время — деньги, а я и так слишком долго возился, а после еще писал для вас этот пост. И раз уж мы используем Blue Matador, подобные неисправности у нас разбираются очень подробно, поэтому я позволяю себе спускать кое-что на тормозах, не отвлекаясь от основного проекта.
Комментарии (17)
vitaly_il1
18.09.2019 12:23Вывод 2: Задавайте ограничения по ресурсам
ИМХО, это должно быть правилом для любого использования контейнеровdarthslider
18.09.2019 21:50Вообще очень не хватает возможность выставить квоту просто на неймспейс, мол вот вам 2 ядра 2 гига, разбирайтесь там.
ferocactus
19.09.2019 09:24Так они же вроде есть. Или на что те квоты, которые не "лимиты ресурсов" контейнеров?
darthslider
19.09.2019 11:48Смотрите, если выставить квоту на неймспейс, но не выставить на контейнеры внутри они просто не запустятся.
Таким образом что бы выставить квоту на неймспейс, надо сначала в выставить квоты на контейнеры внутри (не на поды, именно на контейнеры внутри подов!), сложить всё это и выставить на неймспейс.
Решили добавить еще пару экземпляра пода в неймспейсе — они не стартанут, т.к. не хватит ресурсов, нужно повышать квоту на неймспейс в целом.
Ну или overprovisioning дикий устраивать.
Я же мечтаю о «в неймспейсе что-то происходит, на все контейнеры даю 2 гб памяти, пущай сами делят».
Условно, как виртуальная машина позволяет выделить строго определённое число ресурсов под все процессы внутри себя, без строго разделения по процесса. Ресурсы просто общие, но ограниченные.ferocactus
19.09.2019 12:21Ха, динамическое распределение ресурсов квоты по контейнерам внутри namespace? Звучит настолько логично, что трудно поверить в отсутствие такой возможности.
darthslider
19.09.2019 12:35Я так понимаю, суть в том, что квоты задаются через cgroups при старте процесса (контейнера) и они не динамические в принципе. Плюс всё это размазано по нескольким нодам, что усложняет задачу.
Мне бы хватило просто лимитов вида «ни один из контейнеров в этом неймспейсе не может отожрать больше 2000 мб памяти», это уже уровень логики кубера, но он так не умеет.
vitaly_il1
19.09.2019 09:57Мне кажется, это слишком «грубо», т.е. малополезно. Может, я просто еще не научился использовать неймспейсы.
darthslider
19.09.2019 11:50Банальный пример: мы хотим что бы разработчик в своём dev неймспейсе шальными экспериментами не выжрал всю память ноды.
Сейчас, если мы ставим квоту на этот неймспейс, то разработчик обязан в каждом своём контейнере прописывать явно лимиты, причем сумма лимитов не может быть больше отведённого на неймспейс.
RouR
18.09.2019 19:48Вывод 2: Задавайте ограничения по ресурсам
Но ведь тогда эти поды будут убиваться в последнюю очередь (при нехватке ресурсов из-за падения ноды).
А поды без ограничений будут убиты первыми.
Или уже что-то поменялось в кубере?darthslider
18.09.2019 21:49Подам без ограничения можно выставить request равный limit например, тем самым можно регулировать порядок смерти подов по OOM.
ferocactus
19.09.2019 09:22Подов без ограничений не должно быть. Kubernetes не следовало допускать возможность запуска таких.
Kib0rg
18.09.2019 21:53Для тех, кому интересно, что могло быть причиной повышенной дисковой активности: сама по себе нехватка памяти. При увеличении memory pressure сначала начинают сбрасываться дисковые кэши, I/O растёт как из-за сброса грязных страниц, так и из-за уменьшения размеров кэшей, а когда и этой меры оказывается недостаточно, ядро начинает выгружать страницы, занятые отображенными в память файлами программ. Когда программе передаётся управление и поток исполнения доходит до такой страницы, он снова начинает читать её с диска, и т.д.
В общем, при острой нехватке памяти ядро (ну, по крайней мере Linux) пытается сделать всё, чтобы выжить, хотя на самом деле непонятно, действительно ли это так необходимо, поскольку, с одной стороны, процессу с утечкой в любом случае уже ничем не помочь (если есть утечка, память однажды всё равно закончится), а с другой стороны, подобные попытки сохранить жизнь утекающему процессу и отложить вызов OOM-Killer иногда приводят к такому замедлению системы, что проше сразу всю машину ребутнуть, потому что она десятки минут обрабатывает даже ввод с клавиатуры. Вспомним известный баг 12309, говорят, его аналог и в недавних версиях ядра ещё стреляет.gecube
19.09.2019 14:22О, да, хотел написать то же самое, но Вы меня опередили.
+1
На самом деле получается, что отключение свопа помогает, но не до конца — все равно тюнить vm.
Кстати, как относитесь к отключению overcommit памяти в линуксе?Kib0rg
20.09.2019 14:02В целом никак не отношусь, по-моему в каждом конкретном случае надо смотреть, насколько оно помогает или наоборот вредит. Если утечек памяти нет и её потребление в целом стабильно, не вижу в overcommit ничего плохого.
А вообще я не то чтобы большой специалист по настройке всех этих параметров, просто рассказал то, что знаю, так как кому-то может помочь и сэкономить часть времени на поиск несуществующих проблем в своем приложении. Что, кстати, усугубляется невозможностью простыми средствами наподобие iotop узнать топ процессов по IO на конкретное блочное устройство.
mapcuk
19.09.2019 23:37Если заменить fluentd на fluentbit можно было бы исключить побочные явления от Ruby.
А fluentd метрики отдаёт (fluentbit умеет)? Перед самым OOM интересно бы метрики самого fluentd посмотреть.
AlexBin
Интересно, когда пройдет мода в КДПВ класть фотку тонущего контейнеровоза в постах про Kubernetes.