Чек-лист: что нужно было делать до того, как запускать микросервисы в prod

    Эта статья содержит краткую выжимку из моего собственного опыта и опыта моих коллег, с которыми мне днями и ночами доводилось разгребать инциденты. И многих инцидентов не возникло бы никогда, если бы всеми любимые микросервисы были написаны хотя бы немного аккуратнее.


    К сожалению, некоторые невысокие программисты всерьёз полагают, что Dockerfile с какой-нибудь вообще любой командой внутри — это уже сам по себе микросервис и его можно деплоить хоть сейчас. Докеры крутятся, лавешка мутится. Такой подход оборачивается проблемами начиная с падения производительности, невозможностью отладки и отказами обслуживания и заканчивая кошмарным сном под названием Data Inconsistency.


    Если вы ощущаете, что пришло время запустить ещё одну аппку в Kubernetes/ECS/whatever, то мне есть чем вам возразить.


    English version is also available.


    Я сформировал для себя некий набор критериев оценки готовности приложений к запуску в продакшн. Некоторые пункты этого чек-листа не могут быть применены ко всем приложениям, а только к особенным. Иные в целом применимы ко всему. Уверен, вы сможете добавить свои варианты в комментариях или оспорить какие-то из этих пунктов.


    Если ваш микро-сервис не соответствует хотя бы одному из критериев, я не допущу его в свой идеальный кластер, построенный в бункере в 2000 метрах под землёй с подогревом полов и замкнутой самодостаточной системой подачи интернета.


    Поехали....


    Примечание: порядок пунктов не имеет никакого значения. Во всяком случае, для меня.


    Короткое описание в Readme


    Содержит короткое описание себя в самом начале Readme.md в своём репозитории.

    Боже, это кажется так просто. Но как же часто я сталкивался, что репозиторий не содержит ни малейшего объяснения зачем он нужен, какие задачи решает и так далее. Про что-то более сложное вроде вариантов конфигурации и говорить не приходится.


    Интеграция с системой мониторинга


    Шлёт метрики в DataDog, NewRelic, Prometheus и так далее.

    Анализ потребления ресурсов, утечек памяти, stacktraces, взаимозависимости сервисов, частота ошибок — без понимания всего этого (и не только) крайне сложно контролировать что происходит в большом распределённом приложении.


    Оповещения настроены


    Для сервиса включены оповещения (alerts) которые покрывают все стандартные ситуации плюс известные уникальные ситуации.

    Метрики это хорошо, но следить за ними никто не будет. Поэтому автоматически получаем звонки/пуши/смс если:


    • Потребление CPU/памяти резко возросло.
    • Трафик резко возрос / упал.
    • Количество обрабатываемых транзакций в секунду резко изменилось в любую сторону.
    • Размер артефакта после сборки резко изменился (exe, app, jar, ...).
    • Процент ошибок или их частота превысила допустимый порог.
    • Сервис перестал слать метрики (часто пропускаемая ситуация).
    • Регулярность определённых ожидаемых событий нарушена (cron job-а не срабатывает, не все ивенты обрабатываются etc.)
    • ...

    Runbooks созданы


    Для сервиса создан документ, описывающий известные или ожидаемые нештатные ситуации.

    • как убедиться, что ошибка внутренняя и не зависит от third-party;
    • если зависит, куда, кому и что писать;
    • как его безопасно перезапустить;
    • как восстановить из бекапа и где лежат бекапы;
    • какие специальные dashboards/queries созданы для мониторинга этого сервиса;
    • есть ли у сервиса своя админ-панель и как туда попасть;
    • есть ли API / CLI и как пользоваться для исправления известных проблем;
    • и так далее.

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


    Все логи пишутся в STDOUT/STDERR


    Сервис не создаёт никаких файлов с логами в режиме работы в production, не отправляет их в какие-либо внешние сервисы, не содержит никаких избыточных абстракций для log rotation и т.п.

    Когда приложение создаёт файлы с логами — эти логи бесполезны. Вы не будете заходить в 5 запущенных параллельно контейнеров, в надежде поймать нужную ошибку (а вот и будете, плачет...). Перезапуск контейнера приведёт к полной потере этих журналов.


    Если приложение пишет логи самостоятельно в стороннюю систему, например в Logstash — это создаёт бесполезную избыточность. Соседний сервис не умеет этого делать, т.к. у него другой фреймворк? Вы получаете зоопарк.


    Приложение пишет часть логов в файлы, а часть в stdout потому, что разработчику удобно видеть INFO в консоли, а DEBUG в файлах? Это вообще худший вариант. Никому не нужные сложности и совершенно лишний код и конфигурации, которые нужно знать и поддерживать.


    Логи — это Json


    Каждая строчка лога написана в формате Json и содержит согласованный набор полей

    До сих пор практически все пишут логи в plain text. Это настоящая катастрофа. Я был бы счастлив никогда не знать про Grok Patterns. Они мне снятся иногда и я замерзаю, стараясь не шевелиться, чтобы не привлечь их внимание. Просто попробуйте однажды распарсить исключения Java в логах.


    Json — это благо, это огонь, подаренный с небес. Просто добавьте туда:


    • timestamp с миллисекундами согласно RFC 3339;
    • level: info, warning, error, debug
    • user_id;
    • app_name,
    • и другие поля.

    Загрузите в любую подходящую систему (правильно настроенный ElasticSearch, например) и наслаждайтесь. Соедините логи множества микросервисов и снова почувствуйте чем были хороши монолитные приложения.


    (А ещё можно добавить Request-Id и получить tracing...)


    Логи с уровнями verbosity


    Приложение должно поддерживать переменную окружения, например LOG_LEVEL, с как минимум двумя режимами работы: ERRORS и DEBUG.

    Желательно, чтобы все сервисы в одной экосистеме поддерживали одну и ту же переменную окружения. Не опция конфига, не опция в командной строке (хотя это оборачиваемо, конечно), а сразу by default из окружения. Вы должны иметь возможность получить как можно больше логов, если что-то идёт не так и как можно меньше логов, если всё хорошо.


    Фиксированные версии зависимостей


    Зависимости для пакетных менеджеров указаны фиксированно, включая минорные версии (Например, cool_framework=2.5.3).

    Об этом много где уже говорилось, конечно. Некоторые фиксируют зависимости на мажорных версиях, надеясь, что в минорных будут только bug fixes и security fixes. Это неправильно.
    Каждое изменение каждой зависимости должно быть отражено отдельным коммитом. Чтобы его можно было отменить в случае проблем. Тяжело контролировать руками? Есть полезные роботы, вроде этого, которые проследят за обновлениями и создадут вам Pull Requests на каждое из них.


    Dockerized


    Репозиторий содержит production-ready Dockerfile и docker-compose.yml

    Докер давно стал стандартом для многих компаний. Бывают исключения, но даже если в production у вас не Docker, то любой инженер должен иметь возможность просто выполнить docker-compose up и ни о чём больше не думать, чтобы получить дев-сборку для локальной проверки. А системный администратор должен иметь уже выверенную разработчиками сборку с нужными версиями библиотек, утилит и так далее, в которой приложение хотя бы как-то работает, чтобы адаптировать её под production.


    Конфигурация через окружение


    Все важные опции конфигурации читаются из окружения и окружение имеет приоритет выше чем у конфигурационных файлов (но ниже чем у аргументов командной строки при запуске).

    Никто и никогда не захочет читать ваши файлы конфигурации и изучать их формат. Просто примите это.


    Детальнее тут: https://12factor.net/config


    Readiness and Liveness probes


    Содержит соответствующие endpoints или команды cli для проверки готовности обслуживать запросы при старте и работоспособности в течение жизни.

    Если приложение обслуживает HTTP запросы, оно должно по-умолчанию иметь два интерфейса:


    1. Для проверки, что приложение живое и не зависло, используется Liveness-проба. Если приложение не отвечает, оно может быть автоматически остановлено оркестраторами вроде Kubernetes, "но это не точно". На самом деле убийство зависшего приложения может вызвать эффект домино и насовсем положить ваш сервис. Но это не проблема разработчика, просто сделайте этот endpoint.


    2. Для проверки, что приложение не просто запустилось, но уже готово принимать запросы, выполняется Readiness-проба. Если приложение установило соединение с базой данных, системой очередей и так далее, оно должно ответить статусом от 200 до 400 (для Kubernetes).



    Ограничения ресурсов


    Содержит лимиты потребления памяти, CPU, дискового пространства и любых других доступных ресурсов в согласованном формате.

    Конкретная реализация этого пункта будет очень разной в разных организациях и для разных оркестраторов. Однако эти лимиты обязаны быть заданы в едином формате для всех сервисов, быть разными для разных окружений (prod, dev, test, ...) и находиться вне репозитория с кодом приложения.


    Сборка и доставка автоматизирована


    CI/CD-система, используемая в вашей организации или проекте, сконфигурирована и может доставить приложение на нужное окружение согласно принятому рабочему процессу (workflow).

    Никогда и ничего не доставляется в production вручную.


    Как бы ни было сложно автоматизировать сборку и доставку вашего проекта, это должно быть сделано до того, как этот проект попадёт в production. Этот пункт включает сборку и запуск Ansible/Chef cookbooks/Salt/..., сборку приложений для мобильных устройств, сборку форка операционной системы, сборку образов виртуальных машин, что угодно.
    Не можете автоматизировать? Значит вам нельзя это запускать в мир. После вас уже никто это не соберёт.


    Graceful shutdown – корректное выключение


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

    Это крайне важный пункт. Docker-процессы становятся осиротевшими и месяцами работают в фоне, где их никто не видит. Нетранзакционные операции обрываются в середине выполнения, создавая несогласованность данных между сервисами и в базах данных. Это приводит к ошибкам, которые невозможно предусмотреть и может стоить очень, очень дорого.


    Если вы не контролируете какие-то зависимости и не можете гарантировать, что ваш код корректно обработает SIGTERM, воспользуйтесь чем-то вроде dumb-init.


    Больше информации здесь:



    Соединение с базой данных регулярно проверяется


    Приложение постоянно пингует базу данных и автоматически реагирует на исключение "потеря соединения" при любых запросах, пытаясь восстановить его самостоятельно или корректно завершает свою работу

    Я видел множество случаев (это не просто оборот речи) когда сервисы, созданные для обработки очередей или событий теряли соединение по таймауту и начинали бесконечно сыпать ошибки в логи, возвращая сообщения в очереди, отправляя их в Dead Letter Queue или попросту не выполняя свою работу.


    Масштабируется горизонтально


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

    Далеко не все приложения могут масштабироваться горизонтально. Ярким примером могут служить Kafka Consumers. Это не обязательно плохо, но если конкретное приложение не может быть запущено дважды, об этом нужно знать заранее всем заинтересованным лицам. Эта информация должна быть бельмом в глазу, висеть в Readme и везде, где только можно. Некоторые приложения вообще нельзя запускать параллельно ни при каком обстоятельстве, что создаёт серьёзные трудности в его поддержке.


    Гораздо лучше если приложение само контролирует эти ситуации или для него написана обёртка, которая эффективно следит за "конкурентами" и просто не даёт процессу запуститься или начать работу пока другой процесс не завершит свою или пока какая-то внешняя конфигурация не позволит работать N процессам одновременно.


    Dead letter queues и устойчивость к "плохим" сообщениям


    Если сервис слушает очереди или реагирует на события, изменение формата или содержимого сообщений не приводит к его падению. Неудачные попытки обработать задачу повторяются N раз, после чего сообщение отправляется в Dead Letter Queue.

    Множество раз я видел бесконечно перезапускаемые consumers и очереди, раздувшиеся до таких размеров, что их последующая обработка занимала много дней. Любой слушатель очереди должен быть готовым к изменению формата, к случайным ошибкам в самом сообщении (типизация данных в json, например) или же при его обработке дочерним кодом. Я даже сталкивался с ситуацией, когда стандартная библиотека по работе с RabbitMQ для одного крайне популярного фреймворка вообще не поддерживала retries, счётчики попыток и т.п.


    Ещё хуже, когда сообщение просто уничтожается в случае неудачи.


    Ограничение на количество обрабатываемых сообщений и задач одним процессом


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

    Всё течёт, всё меняется, особенно память. Непрерывно растущий график потребления памяти и OOM Killed в итоге — это норма жизни современных кубернетических разумов. Имплементация примитивной проверки, которая бы просто избавила вас даже от самой необходимости исследовать все эти утечки памяти сделала бы жизнь легче. Не раз видел, как люди тратят уйму сил и времени (и денег), чтобы приостановить эту текучесть, но нет никаких гарантий, что следующий коммит вашего коллеги не сделает всё ещё хуже. Если приложение может выживать неделю — это прекрасный показатель. Пусть потом оно просто само завершится и будет перезапущено. Это лучше чем SIGKILL (про SIGTERM см. выше) или исключение "out of memory". На пару десятков лет вам этой затычки хватит.


    Не использует third-party интеграции с фильтрацией по IP адресам


    Если приложение делает запросы к стороннему сервису, который допускает обращения с ограниченных IP-адресов, сервис выполняет эти обращения опосредованно через обратный прокси.

    Это редкий случай, но крайне неприятный. Очень неудобно когда один крошечный сервис блокирует возможность смены кластера или переезда в другой регион всей инфраструктуре. Если необходимо общаться с кем-то, кто не умеет в oAuth или VPN, заранее настройте reverse proxy. Не реализуйте в своей программе динамическое добавление/удаление подобных внешних интеграций, так как делая это вы гвоздями прибиваете себя к единственной доступной среде выполнения. Лучше сразу автоматизируйте эти процессы для управления конфигами Nginx, а в своём приложении обращайтесь на него.


    Очевидный HTTP User-agent


    Сервис подменяет заголовок User-agent на кастомизированный для всех запросов к любым API и этот заголовок содержит достаточно информации о самом сервисе и его версии.

    Когда у вас 100 разных приложений общаются друг с другом, можно сойти с ума, видя в логах что-то вроде "Go-http-client/1.1" и динамический IP-адрес контейнера Kubernetes. Всегда идентифицируйте своё приложение и его версию явным образом.


    Не нарушает лицензии


    Не содержит зависимости, чрезмерно ограничивающие применение, не является копией чужого кода и так далее.

    Это самоочевидный кейс, но доводилось такое видеть, что даже юрист, писавший NDA, сейчас икает.


    Не использует неподдерживаемые зависимости


    При первом запуске сервиса в него не включены зависимости, которые уже устарели.

    Если библиотека, которую вы взяли в проект, больше никем не поддерживается — ищите другой способ достичь цели или занимайтесь развитием самой библиотеки.


    Заключение


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

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 71

      +6
      Отличная статья! Можно ещё накидать пунктов.
      Можно уточнить — это точно авторский материал? Меня не покидало ощущение, что эта статья перевод, но ссылка на оригинал потерялась.
      Если я ошибся, то поздравляю — это ещё раз говорит о качестве фактуры материала (с хорошей стороны)
        +4
        Спасибо. Это точно авторский материал. :)
          +10
          Они мне снятся иногда и я замерзаю, стараясь не шевелиться, чтобы не привлечь их внимание.

          Всё же это явно перевод, хотя допускаю что вы автор английского оригинала. «Я замерзаю» — это явно «I freeze» («я замираю»).

          Но чеклист понравился, спасибо.
            0

            Я уточнил, точно я. :D Вообще сам чек-лист у меня по-английски хранится, возможно это ощущается. Остальной текст написан сразу по-русски.

              +2

              А можете на английском хабре опубликовать. Прям вот очень нужно!

                +2
                Я займусь переводом в ближайшие дни.
                  +2
                  Перевод здесь: habr.com/en/post/438186

                  Если найдёте ошибки, сообщите мне, пожалуйста.
                    0

                    Спасибо!

                  0

                  Так себе объяснение, почему явно английская фраза присутствует в статье.


                  Если, на самом деле, это компиляция из разных источников, то так и стоит указать.

                    +1
                    Но поскольку это не компиляция из разных источников, то я и не указываю. Фраза не английская, я имел ввиду именно то, что в ней написано.
                0
                Картинка — огонь. Текст — бесподобный. У вас — талант!
                  0
                  Спасибо.
              +3
              Отлично!
              все так и есть на самом деле.
              Еще одна рекомендация:
              не пихать в контейнеры все подряд «потому что это модно»! Только тогда, когда это нужно.
                0
                Тут уже не в моде дело. Из своего опыта могу сказать, что если контейнеры «пошли» в жизнь и вокруг выросла зрелая экосистема их обслуживания, то остановиться уже невозможно. Начинаешь тихо ненавидеть всё прочее, включая всевозможные lambda functions, потому что там нет и половины того, что уже настроил для Kubernetes. А это огромный список мелочей, начиная с весьма развитого CI/CD и заканчивая красивыми визуализациями происходящего внутри каждого контейнера.
                А ещё есть service mesh networks и так далее. Стоит только начать…
                +2
                Что характерно — у меня в проектах нет микросервисов в их типичном понимании (они есть, но это OSGI бандлы, например), однако же список примерно такой же. Плюс-минус докер, скажем.

                Я думаю это вполне можно расширить и на другие типы проектов, без микросервисов — почти ничего не изменится.
                  0

                  В крупноблочных или монолитных приложениях легче отслеживаются проблемы, если всё выполняется в одном потоке. Конечно всё это выжимка опыта работы, включающая и до-докерную эру, однако с множеством мелких программ в одном проекте приходят новые сложности. Зоопарк технологий, фреймворков, всё это очень усложняет понимание сути происходящего.

                    +1
                    Легче — не значит не нужно. В итоге я прикинул, процентов 90 из описанного так или иначе применялось и проверялось.
                      +1
                      В монолитах все еще хуже и с деплоем и логированием.
                      Так что ваша статья прекрасно подходит для монолитов и сервисной архитектуры.

                        0
                        Вот подумал я и, пожалуй, соглашусь. Монолиты иногда кладут в контейнеры и множат инстансы. Вот тогда начинается тихий ужас, т.к. они вообще не созданы для этого. Появляется supervisord внутри или, например, фреймворк сам начинает контролировать свои форки и плодит 20 процессов внутри одного контейнера и каждый из них ведёт себя как отдельное ПО со своими логами.

                        На заре развития Докера, когда я его не любил, я тоже так делал. Чистый докер тяжело управляется, стыковать контейнеры друг с другом сложно и долго, поэтому Supervisor был спасением и получался эдакий «быстрый Vagrant».
                    +1
                    Отличный набор.
                    Большинство пунктов применимо к приложениям других архитектур.
                      +1
                      Кстати, про логи. Есть альтернатива JSON-подходу с упомянутыми плюшками для организации логов, но в табулированном виде (разделенные ,) которые я могу парсить awkом? как, например, делает это apache сервер и другие *nix тулы…
                        +1
                        Табулированные логи — это зло. Я не говорю просто про то, что в выхлопе может нечаянно табуляция затесаться, так еще и переменное кол-во параметров. Чуть-чуть фантазии — и начинаются проблемы.
                          0

                          Все будет хорошо до первого исключения на 100 строк с другим логом внутри

                            +2
                            json вполне удобно парсить при помощи jq
                            +3
                            Оповещения настроены

                            И эскалации, чтобы никто не проспал оповещения. Для этого нужен Incident Management, например:
                            www.pagerduty.com
                            victorops.com
                            www.opsgenie.com
                            amixr.io (Да, я советую свой сервис, но он правда хороший)
                              0

                              Вы совершенно правы, но это уже сильно индивидуальный выбор, да и стоит денег.

                            +2
                            <зануда>
                            в бункере в 2000 метрах под землёй с подогревом полов
                            я бы лучше подумал над охлождением на такой глубине
                            </зануда>
                              0

                              Я слаботеплокровный, ноги стынут, особенно когда всякую чушь сочиняю :)

                              0
                              Все логи пишутся в STDOUT/STDERR

                              А дальше куда? Где их искать, чтобы почитать?
                                +1
                                В докере. Либо в описании сервиса — перенаправление в лог-коллектор
                                journald, кстати, тоже прекрасно хватает с legacy приложений, обернутых в systemd юниты, stdout & stderr
                                  +1
                                  0
                                  Если приложение пишет логи самостоятельно в стороннюю систему, например в Logstash — это создаёт бесполезную избыточность.
                                  и
                                  Загрузите в любую подходящую систему (правильно настроенный ElasticSearch, например)
                                  не противоречат друг другу или я не правильно понял?
                                    +1
                                    Нет, не противоречат. Просто приложение не должно самостоятельно этим заниматься. Вся сборка логов должна быть автоматизирована одним инструментом сразу для всех контейнеров вне их самих. Rsyslog, fluentd, logstash, что-то из этой серии.
                                      +2
                                      Не противоречат
                                      На целевой системе — шлем логи в stdout/stderr
                                      Далее лог-коллектор их собирает, аггрегирует, обогащает и шлёт…
                                      В централизованный ELK, например, где пользователь уже может с логами делать поиск, фильтрацию, статистику собирать и пр
                                      0
                                      ---deleted
                                        0
                                        Классная статья! Подскажите, а вообще есть «промышленный стандарт» для логгирования и разбора аварийных ситуаций для мультисервисной аппы?
                                          0
                                          Каждый пишет свой стандарт ((((
                                            +1
                                            Стандарта нет, есть некие общие принципы.
                                            — Логи «снимаются» с докера (rsyslog, fluentd, ...). Способы реализации разные. Можно изнутри Kubernetes это делать, а можно снаружи, собирая просто логи докера на каждой ноде прямо на хосте, настраивая это всё через оркестратор типа Chef/Salt/whatever.
                                            — собранные логи «умно» пересылаются в какой-либо аггрегатор, с учётом его производительности. Здесь тоже сетап может сильно отличаться. Если логов мало и они качественные (согласованный json), то можно прямо в ElasticSearch слать в нужный индекс. Если их много, можно сначала в какую-то промежуточную точку сбора вроде Kafka, а затем уже в конечную (это может быть какой-то SAAS, не обязательно ElasticSearch).
                                            — потом выводятся в каких-нибудь вьюхах. Kibana/Graphana, тут кому что ближе к телу. Сильно зависит от знания технологии и наличия свободного времени. Можно наворотить нереально красивые и полезные графики. Но большинство просто использует поиск и фильтрацию.
                                            — а поверх настраиваются оповещения о событиях.
                                            0
                                            Норм статья!
                                              0
                                              Спасибо.
                                              +1
                                              Если приложение пишет логи самостоятельно в стороннюю систему, например в Logstash — это создаёт бесполезную избыточность. Соседний сервис не умеет этого делать, т.к. у него другой фреймворк? Вы получаете зоопарк.

                                              Сбивчивый абзац. Можно поподробнее что не так с Logstash?
                                                +4
                                                Автор хочет сказать, что транспорт для доставки логов должен быть частью инфраструктуры системы запуска контейнеров, а не находиться ВНУТРИ контейнера.
                                                  +3
                                                  Да, именно так. Приложения не должны заниматься доставкой логов.
                                                0
                                                Выглядит как отрерайченная и дополненная версия этого поста: https://news.ycombinator.com/item?id=12509533.
                                                  0
                                                  Это базовые принципы — где бы о них не писали они будут пересекаться)
                                                  12factor.net
                                                  0
                                                  Отличная статься!
                                                  Автор — вы молодец!
                                                    0
                                                    Спасибо.
                                                    0

                                                    Ещё можно добавить использование сервисов типа Crazy Monkey для раннего обнаружения проблем с архитектурой и реализацией

                                                      +2
                                                      Спасибо за статью, жизненно.
                                                      Вашим бы списком да по нашим ребятам из dev'а…
                                                      Я просто тут как раз занимался процессом принятия кучи микросервисов из dev-окружения в production Kubernetes, и прямо вот из ваших 22 кажется пунктов были на месте от силы 4, и это боль, да. Кину им английскую версию почитать :)

                                                      Вот про «Все логи пишутся в STDOUT/STDERR» еще добавлю: оно еще важно не только с точки зрения однообразия и удобства, но и использования места на диске — у нас так один микросервис со «случайно» включенным DEBUG режимом за короткое время заполнил всю файловую систему /var/lib/docker, просто потому что писал логи в файл внутри контейнера. И главное в такой ситуации log rotation самого докера не поможет никак.
                                                        0
                                                        у нас так один микросервис со «случайно» включенным DEBUG режимом за короткое время заполнил всю файловую систему /var/lib/docker, просто потому что писал логи в файл внутри контейнера.

                                                        он заполнил эфемерную файловую систему самого себя (там лимит кажется по умолчанию 10 ГиБ)? Или заполнил файловую систему, на которой был каталог /var/lib/docker? Ну, вообще-то можно заставить по умолчанию docker писать в journald, а не в json-file и там сразу автоматически куча плюшек (включая лимит по размеру логов и ротация). И как человек, который тащил это в прод, Вы должны были это знать…
                                                          +1

                                                          Ну во-первых это был один из staging'ов. Во-вторых как я сказал, проблема не в ротации логов, а в том что контейнер заполнил файловую систему которую ему выделили. 10G лимит распространяется только на devicemapper, в случае overlay2 лимитов нет.

                                                            +1

                                                            Еще точнее так, из документации:


                                                            For the overlay2 storage driver, the size option is only available if the backing fs is xfs and mounted with the pquota mount option. Under these conditions, user can pass any size less than the backing fs size.

                                                            Вообще я ожидаю от разработчика, что его приложение вообще не пишет ничего в свою контейнерную файловую систему. Если уж нужно что-то писать, то для этого подключаются волюмы.

                                                              0
                                                              да, не увидел коммента. Именно centos, установленная на xfs. Насчет pquota — не знаю.
                                                              Если уж нужно что-то писать, то для этого подключаются волюмы.

                                                              есть еще временные файлы, которые на вольюм не нужно класть. От слова совсем. И может быть ошибка в приложении, которая не позволяет очищать эти временные файлы. Почему я знаю об этом? Да с gitlab'ом в докере в определенный момент времени такая история произошла и пришлось заняться расследованием инцидента. Решилось изменением параметров gitlab pages.

                                                              И еще — как временный костыль могло помочь увеличение базового размера образа. Примерно как в ответе stackoverflow.com/questions/30994760/how-to-increase-docker-container-default-size
                                                              но учитывая, что была ошибка в прикладе, то это все равно не помогло надолго.

                                                              p.s. судя по всему действительно там изначально был devicemapper…
                                                              +1
                                                              у меня overlay2 на centos 7 и эфемерная файловая система закончилась. Допускаю, что возможно есть разница — писать логи в "/" или в подмонтированный tmpfs.

                                                              P.S. и, да, большое спасибо за Ваш опыт. Это навело меня на мысли, где еще может сломаться кубернетес и как правильно его приготовить… И так уже целая коллекция, а тут еще один ценный экспонат (кейс).
                                                          0
                                                          Да, это тоже знакомая ситуация. Но даже при централизированном сборе логов иногда один сервис может наплодить больше логов, чем вся инфраструктура вместе взятая. Но это хотя бы становится видно, если есть мониторинг.
                                                          0
                                                          Все логи пишутся в STDOUT/STDERR

                                                          А ещё лучше — в старый добрый syslog или новый модный systemd-journal. Их API использует локальный DGRAM сокет, а значит в приложении не потребуется синхронизация между тредами перед записью в общий FIFO буфер пайпа.

                                                            0
                                                            Вот тут осторожно. Если syslog не на текущем сервере, то докер может знатно зависать.
                                                            Прочитал комментарий ниже и до меня дошло, что вы наверное имеете ввиду писать куда-то вовне из самого контейнера. С этим я не могу согласиться.
                                                              0
                                                              нет. Приложение должно писать в STDOUT/STDERR. journald/systemd тащить ВНУТРЬ контейнера никто в здравом уме не будет. А вот снаружи — да, хоть syslog'ом, хоть journald собирай и передавай дальше
                                                                0

                                                                Ничего в контейнер тащить и не нужно — я говорю об интерфейсе, а не имплементации. Достаточно лишь пробросить UDP сокет общехостового сборщика внутрь контейнера.

                                                                  0
                                                                  Достаточно лишь пробросить UDP сокет общехостового сборщика внутрь контейнера.

                                                                  понял Вашу идею, спасибо, подумаю на досуге насколько это ок.
                                                                    0
                                                                    Опять же, здесь есть риск, что зависнет всё и сразу. Но если у вас это надёжно работает, концепция конечно же имеет право на существование.
                                                                +1
                                                                Мое мнение — писать логи в JSON так себе затея. Логи по определению для человека. Если писать для машины, то это уже метрики и что-то другое. Система мониторинга не должна никаким образом вмешиваться в формат логов. Существуют прекрасные тулы, которые спокойно вытягивают любую информацию из неструктурированы сообщений. Тот-же Splunk может (в простом случае) с помощью regexp-ов вытягивать поля или (в случае посложнее) анализировать и строить метрики на основе групп сообщений (транзакции, кореляции и т.д.). Другое дело, что можно помочь им и несколько структурировать сообщения, но не до «нечитаемости».

                                                                Вот еще пример: LogLevel. Когда я пишу библиотеку аутентификации и пароль не совпадает — это ошибка. Тот-же самый вызов со стороны клиентского приложения может быть как нормальной ситуацией (ну ошибся при вводе и что?), фатальной ситуацией (сервис не может аутентифицироваться), предупреждением (много ошибок — может подбор?). Что тут делать? А ничего. Ставить тот LogLevel, какой считаешь нужным на данном уровне. После систему логирования можно сконфигурировать понижать/повышать уровень сообщений от конкретного компонента.

                                                                Пример с уровнем можно расширить на весь лог — компонент логирует как считает нужным (он может быть вообще 3rd party), а потом его лог обрабатывается как надо.
                                                                  0
                                                                  зачем напрягать проц regexp'ами? Если можно писать структурированные логи!
                                                                  Понятно, что если Вам нужно смотреть логи прямо на продакшене, то да — json становится человеком не читабельным… Но нужно ли это?
                                                                  Аналогия — никто же не обламывается от бинарных протоколов типа grpc, protobuf!?
                                                                    0
                                                                    Вот есть компонент и он пишет неструктурированные сообщения в, допустим, log4net. Сделать с этим ничего нельзя — компонент не наш. Есть и наши, которые пишут в Json. Как быть? Перенаправлять его лог в что-то типа { Message: "..."}? Но в том неструктурированном с точки зрения Json сообщении структура может быть! Примерно так: «12:30:21 Service is up. ReasonCode=1000» — тут три части. Более того компонент может писать структурированый лог, да только не в Json, а например в XML (извращенцы, да).

                                                                    Вот принято у вас в Json писать — пишите, но не надо говорить, что это вселенское добро и всем так надо. Не надо. Безопаснее (и в конечном итоге проще) сразу предполагать, что постпроцессинг логов потребуется и не накладывать жестких ограничений. А точнее надо давать рекомендации. Но скоуп у рекомендаций все равно ограничен вашей системой. А что если в другой системе жестко запрещен Json и все должно идти через logd?

                                                                    Пример из жизни: поскольку у саппорта Geneos настроен на LogLevel=ERROR в программе нельзя вообще использовать этот уровень — иначе есть вероятность, что тебя поднимут ночью из-за «Temporary file already exists. Using another name.». В данном (воображаемом) примере программа не ожидала, что временный файл с таким именем будет существовать (т.е. с ее точки зрения это ошибка), но все равно попыталась исправить ситуацию и успешно это сделала. Однако звонок в 3 ночи обеспечен. Разумеется можно (и нужно) поменять сообщение, его уровень, настройки Geneos, runbook и т.д. Но звонок уже был и кто-то прийдет на работу сонный.
                                                                      0
                                                                      Выход — обогащать сообщения в момент сбора на локальной машине, например, именем процесса/сервиса. То что не подходит под общий стандарт (для самописных сервисов это решает административно + постоянное причесывание Легаси и лучше — выделение логгера в отдельную библиотеку, которую все подключают) — действительно скидывать в что-нибудь типа общей очереди Кафки, а потом уже на стороне общего лог коллектора отдельно распарсивать (разбирать). Я здесь никоим образом Вам не противоречу, просто интересно почему Вы так топите за препроцессинг логов.
                                                                        0
                                                                        Ну так мы об одном и том-же. Я просто говорю что препроцессинг нужен, но и без пост процессинга не обойтись. Я в основном про постпроцессинг говорил. Он не накладывает жестких ограничений на исходные логи, но при этом результат такой-же даже лучше. Лучше потому, что можно строить данные по группе сообщений и по сообщениям разных сервисов. Я согласен, что постпроцессинг требует больше ресурсов.
                                                                    0
                                                                    Во-первых, я не соглашусь, что логи — для человека. Это конечное представление логов, оно для человека. Где уже есть фильтры, колонки, группировки, графики и всё прочее. И чтобы получить это представление и видеть что происходит во всей системе, а не в каком-то отдельном процессе, логи и должны писаться в структурированном машинно-читаемом формате.

                                                                    > Вот принято у вас в Json писать — пишите, но не надо говорить, что это вселенское добро и всем так надо.

                                                                    Если вы нашли иной способ, используя другой формат, получать гарантированный парсинг всех требуемых полей, начиная (но не ограничиваясь) с точной метки времени — то конечно. Но из всего, что я знаю, только json поддерживается в любом языке и парсится чем угодно. Следовательно, я буду продолжать утверждать, что это и есть единственный оптимальный формат для подавляющего большинства проектов. А редкие исключения требуют редких специалистов.

                                                                    По-поводу LogLevel, конечно же разработчик сам определяет что критично, а что нет. Но у администратора системы должна быть возможность выключить всё некритичное и включить когда требуется единым способом.

                                                                    0

                                                                    Я согласен, что приложение должно выводить логи на stdout/stderr, но меня сильно напрягает список исправленных достаточно критичных ошибок (включающих потерю строк, дублирование строк, и блокирование при записи в лог) в области обработки логов докером в почти каждом релизе докера. Поэтому я предпочитаю запускать внутри контейнера не только приложение, но и лог-процессор, который будет забирать логи с stdout/stderr приложения и надёжно куда-то их писать/отправлять.


                                                                    Что до вывода логов в json, то здесь всё зависит от масштабов: пока проект небольшой и серьёзной инфраструктуры для обработки логов вроде ELK нет — удобнее выводить логи текстом. Но если/когда проект вырастет, то логи нужно будет перевести в json. Поэтому крайне желательно, чтобы используемая библиотека для логирования изначально принимала от приложения структурированные данные для вывода в лог и позволяла переключать вывод между текстом и json.

                                                                      0
                                                                      Я согласен, что приложение должно выводить логи на stdout/stderr, но меня сильно напрягает список исправленных достаточно критичных ошибок (включающих потерю строк, дублирование строк, и блокирование при записи в лог) в области обработки логов докером в почти каждом релизе докера. Поэтому я предпочитаю запускать внутри контейнера не только приложение, но и лог-процессор, который будет забирать логи с stdout/stderr приложения и надёжно куда-то их писать/отправлять.

                                                                      Тогда может лучше кубернетес и писать логи в файл? А лог процессор — сайдкартом?

                                                                    Only users with full accounts can post comments. Log in, please.