Проблемы в производственной среде — это всегда беда. Происходят именно тогда, когда уходишь домой, а причина всегда кажется глупой. Недавно у нас на узлах в кластере 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)


  1. AlexBin
    18.09.2019 12:23

    Интересно, когда пройдет мода в КДПВ класть фотку тонущего контейнеровоза в постах про Kubernetes.


  1. vitaly_il1
    18.09.2019 12:23

    Вывод 2: Задавайте ограничения по ресурсам

    ИМХО, это должно быть правилом для любого использования контейнеров


    1. darthslider
      18.09.2019 21:50

      Вообще очень не хватает возможность выставить квоту просто на неймспейс, мол вот вам 2 ядра 2 гига, разбирайтесь там.


      1. ferocactus
        19.09.2019 09:24

        Так они же вроде есть. Или на что те квоты, которые не "лимиты ресурсов" контейнеров?


        1. darthslider
          19.09.2019 11:48

          Смотрите, если выставить квоту на неймспейс, но не выставить на контейнеры внутри они просто не запустятся.
          Таким образом что бы выставить квоту на неймспейс, надо сначала в выставить квоты на контейнеры внутри (не на поды, именно на контейнеры внутри подов!), сложить всё это и выставить на неймспейс.
          Решили добавить еще пару экземпляра пода в неймспейсе — они не стартанут, т.к. не хватит ресурсов, нужно повышать квоту на неймспейс в целом.
          Ну или overprovisioning дикий устраивать.
          Я же мечтаю о «в неймспейсе что-то происходит, на все контейнеры даю 2 гб памяти, пущай сами делят».
          Условно, как виртуальная машина позволяет выделить строго определённое число ресурсов под все процессы внутри себя, без строго разделения по процесса. Ресурсы просто общие, но ограниченные.


          1. ferocactus
            19.09.2019 12:21

            Ха, динамическое распределение ресурсов квоты по контейнерам внутри namespace? Звучит настолько логично, что трудно поверить в отсутствие такой возможности.


            1. darthslider
              19.09.2019 12:35

              Я так понимаю, суть в том, что квоты задаются через cgroups при старте процесса (контейнера) и они не динамические в принципе. Плюс всё это размазано по нескольким нодам, что усложняет задачу.

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


      1. vitaly_il1
        19.09.2019 09:57

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


        1. darthslider
          19.09.2019 11:50

          Банальный пример: мы хотим что бы разработчик в своём dev неймспейсе шальными экспериментами не выжрал всю память ноды.
          Сейчас, если мы ставим квоту на этот неймспейс, то разработчик обязан в каждом своём контейнере прописывать явно лимиты, причем сумма лимитов не может быть больше отведённого на неймспейс.


          1. vitaly_il1
            19.09.2019 12:16

            Да, логично, понял


  1. RouR
    18.09.2019 19:48

    Вывод 2: Задавайте ограничения по ресурсам

    Но ведь тогда эти поды будут убиваться в последнюю очередь (при нехватке ресурсов из-за падения ноды).
    А поды без ограничений будут убиты первыми.
    Или уже что-то поменялось в кубере?


    1. darthslider
      18.09.2019 21:49

      Подам без ограничения можно выставить request равный limit например, тем самым можно регулировать порядок смерти подов по OOM.


    1. ferocactus
      19.09.2019 09:22

      Подов без ограничений не должно быть. Kubernetes не следовало допускать возможность запуска таких.


  1. Kib0rg
    18.09.2019 21:53

    Для тех, кому интересно, что могло быть причиной повышенной дисковой активности: сама по себе нехватка памяти. При увеличении memory pressure сначала начинают сбрасываться дисковые кэши, I/O растёт как из-за сброса грязных страниц, так и из-за уменьшения размеров кэшей, а когда и этой меры оказывается недостаточно, ядро начинает выгружать страницы, занятые отображенными в память файлами программ. Когда программе передаётся управление и поток исполнения доходит до такой страницы, он снова начинает читать её с диска, и т.д.
    В общем, при острой нехватке памяти ядро (ну, по крайней мере Linux) пытается сделать всё, чтобы выжить, хотя на самом деле непонятно, действительно ли это так необходимо, поскольку, с одной стороны, процессу с утечкой в любом случае уже ничем не помочь (если есть утечка, память однажды всё равно закончится), а с другой стороны, подобные попытки сохранить жизнь утекающему процессу и отложить вызов OOM-Killer иногда приводят к такому замедлению системы, что проше сразу всю машину ребутнуть, потому что она десятки минут обрабатывает даже ввод с клавиатуры. Вспомним известный баг 12309, говорят, его аналог и в недавних версиях ядра ещё стреляет.


    1. gecube
      19.09.2019 14:22

      О, да, хотел написать то же самое, но Вы меня опередили.
      +1
      На самом деле получается, что отключение свопа помогает, но не до конца — все равно тюнить vm.
      Кстати, как относитесь к отключению overcommit памяти в линуксе?


      1. Kib0rg
        20.09.2019 14:02

        В целом никак не отношусь, по-моему в каждом конкретном случае надо смотреть, насколько оно помогает или наоборот вредит. Если утечек памяти нет и её потребление в целом стабильно, не вижу в overcommit ничего плохого.
        А вообще я не то чтобы большой специалист по настройке всех этих параметров, просто рассказал то, что знаю, так как кому-то может помочь и сэкономить часть времени на поиск несуществующих проблем в своем приложении. Что, кстати, усугубляется невозможностью простыми средствами наподобие iotop узнать топ процессов по IO на конкретное блочное устройство.


  1. mapcuk
    19.09.2019 23:37

    Если заменить fluentd на fluentbit можно было бы исключить побочные явления от Ruby.
    А fluentd метрики отдаёт (fluentbit умеет)? Перед самым OOM интересно бы метрики самого fluentd посмотреть.