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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

Сбивчивый абзац. Можно поподробнее что не так с Logstash?
Автор хочет сказать, что транспорт для доставки логов должен быть частью инфраструктуры системы запуска контейнеров, а не находиться ВНУТРИ контейнера.
Да, именно так. Приложения не должны заниматься доставкой логов.

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

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

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

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

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

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


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.

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

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

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

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

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

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

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

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

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

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

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

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

Пример с уровнем можно расширить на весь лог — компонент логирует как считает нужным (он может быть вообще 3rd party), а потом его лог обрабатывается как надо.
зачем напрягать проц regexp'ами? Если можно писать структурированные логи!
Понятно, что если Вам нужно смотреть логи прямо на продакшене, то да — json становится человеком не читабельным… Но нужно ли это?
Аналогия — никто же не обламывается от бинарных протоколов типа grpc, protobuf!?
Вот есть компонент и он пишет неструктурированные сообщения в, допустим, 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 и т.д. Но звонок уже был и кто-то прийдет на работу сонный.
Выход — обогащать сообщения в момент сбора на локальной машине, например, именем процесса/сервиса. То что не подходит под общий стандарт (для самописных сервисов это решает административно + постоянное причесывание Легаси и лучше — выделение логгера в отдельную библиотеку, которую все подключают) — действительно скидывать в что-нибудь типа общей очереди Кафки, а потом уже на стороне общего лог коллектора отдельно распарсивать (разбирать). Я здесь никоим образом Вам не противоречу, просто интересно почему Вы так топите за препроцессинг логов.
Ну так мы об одном и том-же. Я просто говорю что препроцессинг нужен, но и без пост процессинга не обойтись. Я в основном про постпроцессинг говорил. Он не накладывает жестких ограничений на исходные логи, но при этом результат такой-же даже лучше. Лучше потому, что можно строить данные по группе сообщений и по сообщениям разных сервисов. Я согласен, что постпроцессинг требует больше ресурсов.
Во-первых, я не соглашусь, что логи — для человека. Это конечное представление логов, оно для человека. Где уже есть фильтры, колонки, группировки, графики и всё прочее. И чтобы получить это представление и видеть что происходит во всей системе, а не в каком-то отдельном процессе, логи и должны писаться в структурированном машинно-читаемом формате.

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

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

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

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


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

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

Тогда может лучше кубернетес и писать логи в файл? А лог процессор — сайдкартом?
Only those users with full accounts are able to leave comments. Log in, please.