Eric Evans — Domain-Driven Design: Tackling Complexity in the Heart of Software

После короткой книги Vladik Khononov я решил, что надо углубиться в тему DDD и прочитать книгу Eric Evans, раскрывающую тему более подробно. Совсем не пожалел и в очередной раз убедился, что некое краткое изложение — это хорошо, но более подробная и полная книга позволяет действительно проникнуться идеями.

Книга состоит из 4х больших частей.

В первой автор рассуждает о важности общей модели предметной области — общей и для программистов и для представителей бизнеса. Сама мысль изложена и в What is domain driven design, но тут она подается подробнее, с примерами из жизни. Интересная мысль, что целостность модели и ее общий язык — это не только то о чем мы договорились в начале. Если в процессе работы мы нашли более удачные слова, точнее описывающие нашу область и лучше понятные бизнесу, то необходимо отразить это в том числе в коде. Необходим рефакторинг кода, для того чтобы используемый в нем язык продолжал совпадать с языком модели, чтобы не происходило постепенного расхождения.

Во второй — автор переходит от общих рассуждений о моделецентричности при разработке к конкретным предложениями как проектировать ПО так, чтобы в итоге оно отражало модель предметной области. Для этого сначала вводятся понятия entity, value object, service описывающие различные составные части системы. Затем — aggregate, группирующий их в тесно связанные с точки зрения бизнеса блоки, factory — для создания сущностей и агрегатов, repository — для внешнего хранения сущностей и восстановления их из внешнего хранилища в память. В конце главы приводится пример проектирования системы управления перевозкой товаров на основе предлагаемых принципов из этих кирпичиков.
С одной стороны здесь не описывается ничего революционного, но с другой методично и с примерами показывается как может быть упрощена или наоборот усложнена система, если глубже вникнуть в бизнес требования и отразить их при проектировании. Как влияет на дизайн правильное понимание, что в бизнес области является entity, а что просто value, т.е. определяется только своими свойствами и не требует отслеживания уникальности. Как упрощает систему замена двунаправленных связей однонаправленным, где это возможно. Как грамотное использование репозиториев может решать проблемы с конкретными изменениями (опять таки если бизнес требования позволяют это)

В третьей части автор обсуждает рефакторинг, как необходимый компонент поддержания соответствия кода модели, по мере ее изменения и усовершенствования. Начинает он с примера из жизни, когда вникнув глубже в доменную область, удалось создать более удачную её модель, что с одной стороны позволило упростить дальнейшую разработку, устранив недопонимания с бизнес-заказчиками, но с другой потребовало существенного переписывания системы под новую модель. После этого, ещё порассуждав о том, что модель постоянно улучшается и меняется — переходит к рассуждениям о гибком и в то же время понятном дизайне приложений. Сначала обсуждается, как лучше отражать бизнес-правила и ограничения в коде. Альтернативой вынесения сложной бизнес логики в уровень приложения предлагается создание классов-спецификаций, инкапсулирующих внутри себя проверку неких бизнес-правил, а возможно во взаимодействии с репозиториями и выборку объектов, удовлетворяющих спецификации.
Другими инструментами гибкого дизайна автор называет:
— интерфейсы, с понятными названиям как самих классов, так и методов, чтобы из самих их названий было понятно, что они делаютчистые функции
— операции над иммутабельными объектами
— операции над value objects, которые бы возвращали новые value objects

В четвертой части автор переходит к вопросам организации работы большой компании со сложным продуктом, над которым работает несколько команд. На примерах он показывает, какие проблемы могут возникнуть, когда команды работают в рамках своих моделей, но их границы четко не очерчены и одна команда может вносить изменения, нарушающие работу кода, поддерживаемого другой. Чтобы не допускать таких проблем предполагается концепция bounded contexts — четко очерченных границ моделей, с понятными местами их пересечения. Если это сделано — то, как минимум проблема обозначена и обозначены пересечения, по которым работа должна быть в какой-то степени совместной и надо принять решение как будет построена эта работа.
Автор рассматривает следующие способы взаимодействия команд, по степени уменьшения степени интеграции: continuous integration, shared kernel, upstream/downstream, anticorruption level, open host service, separate ways. Суть ясна из названий, разве что стоит пояснить что под anticorruption автор понимает набор фасадов/адаптеров, которые защищают нашу модель от «повреждения» неподходящей нам чужой моделью. Важная мысль при этом, что выбор модели взаимодействия — это чаще всего не техническое, а организационное решение и зависит прежде всего от размера команд, их взаимоотношений, их организационной подчинённости, общности их целей.

Но возвращаясь чуть назад, как разделить единую и ставшую слишком сложной и запутанной модель на отдельные bounded contexts, чтобы с одной стороны над ними могли работать независимые команды, а с другой — чтобы усилия наиболее сильных программистов (команд) были направлены на действительно важные вещи, а какие-то менее критичные могли бы быть сделаны силами джунов или вовсе отданы на аутсорс?
Для этого необходимо провести model distillation — анализ модели и выделение, что в ней является нашим core domain — тем, что мы на самом деле делаем, что составляет цель нашего продукта, чем он отличается от конкурентов. А что является generic / supporting domains — частями продукта, которые необходимы, но при этом могут быть такими же как у всех вокруг, решают задачи поддержки, не являются основными. Часто такими частями являются биллинг, поддержка, общие технические модели работы с сетью / датами и т.п.
Для упрощения основной модели полезно выделить generic subdomains в отдельные модули / команды, а возможно и отдать на аутсорс для ускорения разработки. Другим подходом может быть наоборот явное выделение в отдельный модуль / сервис в первую очередь core domain (автор называет этот подход segregated core), а уже потом распиливание остатка.

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

Team divisions that assume some developers are not smart enough to design are likely to fail because they underestimate the difficulty of application development. If those people are not smart enough to design, they shouldn’t be assigned to develop software. If they are smart enough, then the attempts to coddle them will only put up barriers between them and the tools they need

Vladik Khononov — What Is Domain-Driven Design?

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

  • Очень кратко описывается понятие домена (core / generic / supporting), субдоменов, ubiquitous language, bounded contexts.
  • Рассматриваются различные способы взаимодействия между командами, работающими в bounded contexts (как оно общепринято переводится на русский искать пока было лень).
  • По верхам пробегается по паттернам организации кода — transaction script, active record, domain model, event sourcing domain model.
  • Также по верхам по архитектурным паттернам — слоеная архитектура, порты и адаптеры, CQRS.
  • Дальше автор перескакивает от кода опять к организационным практикам — обсуждает event storming и эволюцию доменов (переход между core / generic / supporting при изменениях ситуации на рынке)

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

Gregor Hohpe, Bobby Woolf — Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions

Книга посвящена паттернам построения приложений на базе систем передачи сообщений. Выпущена в 2003 году и это чувствуется (кругом XML и SOAP, древние версии .net и java) — но при этом основные идеи не устарели и замечательно перекладываются в голове на Redis, Kafka и т.п.

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

Во второй авторы рассматривают 4 основных способа интеграции приложений — передача файлов, общая БД, RPC и передача сообщений (messaging). Описываются их плюсы и минусы, причем не только технические, но и организационные (особенно касаясь общей БД). Наиболее гибкой, но при этом и более сложной авторы видят интеграцию посредством передачи сообщений.

В следующих главах последовательно рассматриваются:

  • Основы построения систем, используя передачу сообщений. Ничего особенного, но полезно посмотреть и на свою систему, используя предлагаемые функциональные элементы.
  • Каналы передачи сообщений — на какие типы можно поделить, и какие когда и как использовать. Важная мысль про спец. каналы для ошибочных сообщений, причем ошибочных именно с точки зрения транспорта и потому явного отделения уровня транспорта от логики приложения
  • Сами сообщения. Рассуждения, что сообщения бывают трёх типов — команды, данные и события и какие есть особенности у разных типов ( такие как, например, канал, куда помещать ответ у команд и время жизни у событий )
  • Message router. Автор рассматривает сначала относительно простые темы, как content based router и альтернативы ему в виде фильтров сообщений, а затем переходит к более сложным (и интересным) темам — как splitter / aggregator сообщений, resequencer (по русски видимо восстановитель очередности — звучит так себе :). Особенно понравилось, что темы не просто поверхностно обозначены, но также обсуждаются сложности при реализации ( например, какие есть варианты аггрегатору понимать, что сообщения закончились? какого размера должен быть буфер у resequencer и как избежать его переполнения?) и трейдофы различных решений.
  • В этой главе интересная мысль, что проблема нарушения очередности сообщений в системе построенной на их обмене — это та же проблема, которая решается в tcp — который по своей сути протокол обмена сообщениями по нестабильной сети, в котором порядок сообщений важен. И потому полезно представлять, как tcp работает и какие решения применяет, и с какими трейдофами (окно сообщений и способы определения его размера / подтверждение получения сообщений и т.п.)
  • Далее рассматриваются ещё более сложные паттерны, реализующие многоэтапный бизнес-процесс — route slip, process manager и message broker. Самое ценное при этом — опять таки обсуждение применимости.
  • Трансформация сообщений. Больше общие рассуждения о том как лучше организовать связь между собой приложений с разными форматами сообщений. Рассматривает такие паттерны как envelope message, content enricher, content filter, claim check и normalizer. Суть большинства ясна из названия, claim check — удаление из сообщения части информации, но с тем, чтобы вернуть ее в него позже, на последующих этапах обработки бизнес-транзакции.
  • Вопросы взаимодействия приложения с системой передачи сообщений. Отделение кода работающего с ней от основного кода приложения, poll / push модели чтения сообщений, способы организации отработки одной очереди несколькими экземплярами приложения или несколькими приложениями.
  • Особенности эксплуатации системы на базе передачи сообщений. Рассматриваются паттерны, описывающие как организовать конфигурирование системы и переконфигурирование ее в реальном времени, логирование запросов, сбор метрик.

Главы сопровождаются примерами на java используя JMS и на .Net, а также бизнес-ситуациями, при которых могут использоваться одни или другие описанные паттерны. Примеры кода часто выглядят сильно учебными и слегка устаревшими (особенно когда там XML и рассуждения про SOAP и чего нет в .Net 1.1), но все равно полезны ещё и для понимания терминологии и того как одни и те же вещи по разному могут называться в разных имплементациях. Особенно полезны несколько глав с примерами ближе к концу книги, где одну и ту же задачу сначала решают разными путями, а потом наворачивают получившееся решения для использования в продакшене — добавляют мониторинг, фейловер одной из систем и т.п.

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

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

Brendan Burns — Designing distributed systems

Книга представляет собой обзор паттернов разработки распределенных приложений используя контейнеризацию.

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

Sidecar — второй контейнер рядом с основным, расширяющий его функциональность. Как теория ок. Примеры автор приводит сначала про добавление поддержки https к легаси системе — наверно имеет право на жизнь, если легаси настолько старое, что и правда засунуть его в контейнер будет проще. Следующий пример понравился уже сильно меньше — контейнер который добавляет мониторинг соседнего и некую веб морду с данными о использовании ресурсов. Имхо для таких целей нужен все таки какой-то централизованный мониторинг причем с графиками по времени и т.п. типа как даёт заббикс. Поэтому пример выглядит нежизненным. Третий пример про реализацию доставки изменений через гит репозиторий посредством контейнера который в цикле делает пулл из него. Ну такое. Имхо правильным будет всё-таки когда доставку изменений производит сторонняя система по событию пуша в гит репозиторий, а не трата ресурсов на пулл в цикле.

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

Ambassador — через второй контейнер происходит взаимодействие с внешним миром. От первого примера про шардинг у меня знатно подгорело. Пример собственно про то, что сделаем шардинг редиса так, чтобы для приложения он был как будто один и нам его менять не пришлось. Поднимают 3 независимых редиса, и контейнер-прокси запросы к себе распределяет на них. Ааааа! Кластер редиса делает это сам по себе из коробки, давая при этом ещё и отказоустойчивость. А если заменить редис на какую-то не key-value базу — то там так просто на прокси запросы от приложения не поделишь, совсем приложение не трогая. Другие примеры — сервис-брокер для сервис-дискавери и канареечный релиз / дублирование продакшн трафика на тестовое окружение. Первый да, могу понять, если сервис-дискавери делается не посредством днс. Со вторым так и не смог для себя придумать, когда это было бы полезно делать со стороны клиента, а не сервера принимающего запросы — тестировать то мы будем изменение поведения сервера.

Адаптер. Похоже на амбассадора по сути, но смысл использования — предоставление интерфейса, который основной контейнер не предоставляет. Тут у меня наконец-то вопросов не возникло. И кейсы с мониторингом редиса и изменением формата логов понятные.

Дальше в книге начинаются паттерны для распределенных систем и там уже все выглядит вполне понятно, но в то же время и весьма лайтово (сильно лайтовее книги с кабанчиком). Рассматриваются шардирование, реплики стейтлесс сервисов, scatter/gather паттерн (отправка запросов сразу на несколько серверов и объединение ответа от них в ответ пользователю). Примеры по прежнему не впечатляют, но уже скорее из-за своей простоты. Например пример где делают консистент хешинг на nginx, но при этом на фиксированном в конфиге числе апстримов, учитывая что до этого шла речь про изменение числа шардов, ожидал бы тут какой-то пример, где это самое число шардов можно было бы менять на лету.

Следующая глава посвящена FAAS — function as a service. Общие рассуждения о применимости, удобстве и экономической обоснованности при разной нагрузке полезны. А вот первый предлагаемый пример использования странный — предлагается декоратор по такой схеме: пользователь -> декоратор -> основное приложение. Если декоратор условная лямбда на Амазоне, то если ответ идёт по тому же пути, то декоратор висит все время запроса и мы платим за все это время. Чтобы эта схема была экономически не провальна, мне кажется, нужно усложнять получение ответа от основного приложения в таком сценарии, что никак не согласуется со сценарием — просто добавим декоратор на FAAS, чтобы не править основное приложение.

Выбор мастера и владение (ownership) ресурсом. Первая глава в книге, к которой у меня нет претензий. Более того — она дала мне очень важный инсайт, т.к. на самом деле речь в ней идёт о распределенном локе. Распределенные локи активно используются у нас в коде ( реализованы через редис ). И почему-то раньше я не задумывался о том, чтобы использовать их же для того, чтобы только один наш демон читал определенную очередь (по пулл модели), а думал как-то дополнительно прикручивать zookeper и какой-то более сложный механизм выбора мастера. Глава дала хорошую пищу для размышлений. Также полезной была мысль, что хотя современный оркестратор способен обеспечивать некоторые гарантии fault tolerance, перезапуская упавший контейнер и перенося его с упавшей машины — этого недостаточно при частом деплое.

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

Общее ощущение от книги — она простовата и наверно скорее полезна как обзорная для новичков. Примеры скорее учебные и не очень близки к реальным. Но в целом прочитать было полезно.

Martin Kleppmann — Designing Data-Intensive Applications

Долгие годы источником новых знаний в IT для меня был хабрахабр, но или я его перерос или он в последние годы скатился, но что-то стоящее там попадается всё реже и реже. Настолько, что становится жалко времени, которое тратится на перелопачивание новых статей и невольное залипание во всяких статьях, обсуждающих текущие новости, ценность которых, если смотреть даже с небольшого отдаления, стремится к нулю. Достойной замены я ему пока не нашёл, некоторое количество людей в твиттере и медиуме — это всё ещё не то, да и соотношение польза/шум в твиттере не сильно лучше хабра, даже если стараться подписаться на кого-то, от кого прилетает время от времени интересный материал.

В итоге пока решил переключиться на книги, тем более, что книг по специальности я прочёл не так много — в основном потому, что читаю я в основном с читалки или телефона, и специальную литературу в pdf и там и там читать неудобно.

Но технологии не стоят на месте 🙂 Оказалось, что весьма удобную оболочку для чтения книг даёт в своём приложении O’Reilly. К тому же в полном соответствии с мотивационными теориями, если ты за что-то заплатил, то твоя мотивация это использовать заметно повышается 🙂

Это была присказка, а первой прочтённой мной на платформе O’Reilly книгой стала классическая книга с кабанчиком, о которой дальше и пойдёт речь.

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

Дальше крупными мазками по содержанию.

Книга состоит из трёх частей. Первая — представляет собой обзор способов хранения и обработки данных в пределах одной машины:

  • Какие бывают БД, как они развивались и для каких задач лучше подходят. Тут очень интересным было читать про графовые БД, так как я сам плотно с ними не работал. Особенно порадовало сходство Datalog и пролога.
  • Как БД хранят данные внутри себя, append-only logs и структуры для индексов — очень подробное и понятное описание, мне конечно тяжело судить, но показалось, что настолько понятное, что его можно было бы читать даже без какого-то бэкграунда
  • Форматы хранения данных — текстовые, бинарные, рассуждения о forward и backward compatibility. Тут интересным было читать про Avro (опять-таки, потому что я с ним не сталкивался ранее 🙂

Вторая — распределенные данные и проблемы возникающие при этом

  • Репликация. Очень подробный обзор — синхронная / асинхронная, с одним лидером и без лидера, проблемы с лагом репликации и способы их решения
  • Партиционирование. Способы шардинга, распределённые индексы, ребалансировка, роутинг запросов.
  • Транзакции. Не просто что такое и зачем, но и трейдоффы, уровни изоляции и как они релизовываются на низком уровне.
  • Проблемы распределённых систем. Одна из самых полезных глав. Что и как может пойти не так — нестабильные сети, процессы на паузе, ненадежное время, разница между монотонным и календарным временем, Byzantine faults (не могу придумать как это корректно перевести на русский таким же коротким термином — возможно, в русском нет подобного термина для неполадок, вызванных поведением частей системы, которые нарочно врут и им нельзя доверять)
  • Распределенные транзакции — двухфазный коммит, алгоритмы консенсуса (без глубокого погружения, но общие принципы, и какие существующие инструменты их реализовывают)

Третья — пакетная и потоковая обработка данных

  • Пакетная обработка (batch processing) — MapReduce и описание какие есть способы осуществления джойнов данных при этом + рассуждения о том насколько оно похоже на Unix утилиты
  • Потоковая обработка (stream processing) — переход от пакетной обработки, message-based и log-based источники данных, change data capture, event sourcing и как и везде в книге — рассуждения о проблемах и трейдофах при потоковой обработке данных, а также как сделать её максимально ошибкоустойчивой (микробатчи, идемпотентность)
  • И последняя глава состоит из рассуждений автора о будущем обработки данных и этических вопросах. Второе неинтересно (т.к. ничего нового, всё те же опасения, и гос. регулирование нас спасёт), разве что стоит отметить сравнение текущего положения со сбором и обработкой данных с ранним капитализмом — детским трудом, 12-часовым рабочим днём и т.п.
    А вот техническая часть главы выглядит интереснее — кругом сплошное телевидение сплошные стримы и данные порождаемые из данных. Интересна идея замены двухфазного коммита на единое сообщение с бизнес-транзакцией пользователя, на основании которой уже все системы (базы данных, поисковые индексы, очереди сообщений и т.п.) делают нужные им изменения состояния, но автор сам отмечает, что тут есть большая проблема с read your own writes, которая по его мнению как-нибудь будет решена (мы же о будущем мечтаем).

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

Улучшение оптимизатора запросов в MySQL 8.0.17 (по сравнению с 8.0.16)

Минорные релизы MySQL 8 совсем не минорные и у этого есть как минусы (в случае проблем откатиться обратно на предыдущий минорный релиз не выйдет), так и плюсы, т.к. в них появляются новые фишки и улучшения.

В 8.0.17 к примеру завезли оптимизацию запросов с NOT EXISTS:

The optimizer now transforms a WHERE condition having NOT IN (subquery)NOT EXISTS (subquery)IN (subquery) IS NOT TRUE, or EXISTS (subquery) IS NOT TRUE internally into an antijoin, thus removing the subquery.

Пример, имеем запрос:

SELECT task.TaskID
FROM contact
INNER JOIN login ON login.LoginID = contact.LoginID
INNER JOIN task ON task.TaskID = contact.TaskID
LEFT OUTER JOIN taskstar ON taskstar.TaskID = task.TaskID AND taskstar.LoginID = 858636
INNER JOIN taskaccess accesslogined ON task.TaskID = accesslogined.TaskID AND accesslogined.LoginID = 858636
WHERE 1
AND contact.ContactBool_1 = 0
AND contact.ContactSpam = 0
AND task.TaskType = 4
AND contact.ContactIsDeleted = 0
AND task.TaskIsDeleted = 0
AND (NOT EXISTS(SELECT 1
FROM task
WHERE ClientID = contact.ContactID
AND task.TaskIsDeleted = 0
AND (task.TaskType = 0)
AND task.TaskStatusSetID = 57450)
)
GROUP BY task.TaskID
ORDER BY task.TaskID
LIMIT 0,5

Выполняем его в MySQL 8.0.16 и получаем:

5 rows in set (1 min 9.92 sec)

Больше минуты, совсем некомфортно.
Обновляем MySQL и выполняем его же в 8.0.17:

5 rows in set (1.02 sec)

Профит 🙂

Запуск ansible c конфигом из папки со слишком широкими правами на запись

Дано: ansible запускается с виртуалки VirtualBox, файлы при этом лежат на windows хосте и примонтированы в виртуалку. При попытке запуска ansible-playbook получаем:

[WARNING]: Ansible is in a world writable directory (/media/D_DRIVE/work/ansible), ignoring it as an ansible.cfg source.

Обсуждение проблемы и workaround есть тут: https://github.com/ansible/ansible/issues/42388

Надо добавить путь к конфигу в переменные окружения. Я в виртуалке работаю один, ansible у меня там тоже один — поэтому добавил ее сразу в /etc/environment:

Идем в /etc/environment и дописываем туда путь к конфигу:

ANSIBLE_CONFIG=» /media/D_DRIVE/work/ansible/ansible.cfg»

Применяем оттуда переменные до перезагрузки:

for env in $( cat /etc/environment ); do export $(echo $env | sed -e ‘s/»//g’); done

Готово, ansible продолжает ругаться, но при этом работает

Opendkim, query timed out и _внезапно_ DNSSEC

Столкнулся с тем, что встряла входящая почта. Приходить приходит, но вся застревает в maildrop. В postqueue -p — тысячи писем. Беглый анализ логов показал, что предположительный виновник opendkim:

Oct 15 09:55:01 postnew opendkim[31282]: 221D962914: key retrieval failed (s=20161025, d=youtube.com): ‘20161025._domainkey.youtube.com’ query timed out

А вот дальше начались долгие и тяжелые поиски причины, собственно завязка такая — opendkim выдает query timed out

~# opendkim-testkey -d youtube.com -s 20161025 -v -v -v
opendkim-testkey: using default configfile /etc/opendkim.conf
opendkim-testkey: checking key ‘20161025._domainkey.youtube.com’
opendkim-testkey: ‘20161025._domainkey.youtube.com’ query timed out

а dig — нет )

# dig 20161025._domainkey.youtube.com TXT

; <<>> DiG 9.10.3-P4-Ubuntu <<>> 20161025._domainkey.youtube.com TXT
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 44900
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;20161025._domainkey.youtube.com. IN TXT

;; ANSWER SECTION:
20161025._domainkey.youtube.com. 3599 IN TXT «k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0UfgFQF/Ms63E9cKpj+WdM5RepAYbfAT+h4iAzOb93Q7eVjNd0WabrALPs3qcUEKkrhpI1nlZtOofutG8l4VgslGn7+9ggc489LWyU+u674c3eRoErGOFRq0xV8xnG+Rf» «WKF+im0t4n/QGA2ZcdOIcIfevxyPHudcJipW0G8C6vMtBmQulAfN/SgE1/cugl6VBedZIYuynEF2ttqO9vDLy90/BctMBRJGyyj/CJR7gdLl655Y2+73BFvjhCIWroDrGa2io005Hh1nkReAY9Q2BPQ2K6O7TlIq3SGFVRuPIp0cszwwIjPTy3BP92X2NfiM4CZSPr9R02UfNyPYLQv4wIDAQAB»

;; Query time: 15 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Mon Oct 15 11:01:48 GMT 2018
;; MSG SIZE rcvd: 475

Гугл говорит — ну так это дело ясное, opendkim ломится не на тот сервер — надо явно прописать Nameservers, прописываем:

Nameservers 8.8.8.8

Ноль эффекта. Пробуем 1.1.1.1 — так же ноль эффекта.

Подключаем тяжелую артиллерию:

tcpdump -i eth1 udp port 53 -n

Вау — запросы есть, и ответы приходят

11:05:45.772081 IP 144.76.68.186.30286 > 216.239.34.10.53: 37169% [1au] TXT? 20161025._domainkey.youtube.com. (60)
11:05:45.795999 IP 216.239.34.10.53 > 144.76.68.186.30286: 37169*- 1/0/0 TXT «k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0UfgFQF/Ms63E9cKpj+WdM5RepAYbfAT+h4iAzOb93Q7eVjNd0WabrALPs3qcUEKkrhpI1nlZtOofutG8l4VgslGn7+9ggc489LWyU+u674c3eRoErGOFRq0xV8xnG+Rf» «WKF+im0t4n/QGA2ZcdOIcIfevxyPHudcJipW0G8C6vMtBmQulAfN/SgE1/cugl6VBedZIYuynEF2ttqO9vDLy90/BctMBRJGyyj/CJR7gdLl655Y2+73BFvjhCIWroDrGa2io005Hh1nkReAY9Q2BPQ2K6O7TlIq3SGFVRuPIp0cszwwIjPTy3BP92X2NfiM4CZSPr9R02UfNyPYLQv4wIDAQAB» (464)
11:05:45.796262 IP 144.76.68.186.23689 > 192.112.36.4.53: 62833% [1au] DNSKEY? . (28)
11:05:45.821559 IP 192.112.36.4.53 > 144.76.68.186.23689: 62833*- 4/0/1 DNSKEY, DNSKEY, DNSKEY, RRSIG (1139)
11:05:45.821755 IP 144.76.68.186.63995 > 199.7.91.13.53: 33914% [1au] DNSKEY? . (28)
11:05:45.833611 IP 199.7.91.13.53 > 144.76.68.186.63995: 33914*- 4/0/1 DNSKEY, DNSKEY, DNSKEY, RRSIG (1139)
11:05:45.833814 IP 144.76.68.186.42019 > 202.12.27.33.53: 53753% [1au] DNSKEY? . (28)
11:05:45.848947 IP 202.12.27.33.53 > 144.76.68.186.42019: 53753*- 4/0/1 DNSKEY, DNSKEY, DNSKEY, RRSIG (1139)
11:05:45.849195 IP 144.76.68.186.9289 > 198.41.0.4.53: 19115% [1au] DNSKEY? . (28)
11:05:45.855702 IP 198.41.0.4.53 > 144.76.68.186.9289: 19115*- 4/0/1 DNSKEY, DNSKEY, DNSKEY, RRSIG (1139)
11:05:45.856003 IP 144.76.68.186.33889 > 193.0.14.129.53: 31594% [1au] DNSKEY? . (28)
11:05:45.861259 IP 193.0.14.129.53 > 144.76.68.186.33889: 31594*- 4/0/1 DNSKEY, DNSKEY, DNSKEY, RRSIG (1139)
11:05:45.861547 IP 144.76.68.186.36263 > 199.9.14.201.53: 50292% [1au] DNSKEY? . (28)
11:05:46.017319 IP 199.9.14.201.53 > 144.76.68.186.36263: 50292*- 4/0/1 DNSKEY, DNSKEY, DNSKEY, RRSIG (1139)

но opendkim упорно отвечает query timed out.
Единственное, что что за пачка DNSKEY в запросе?? Вспоминаем, что пару дней назад Эшер в телеграме писал про смену ключей DNSSEC https://t.me/usher2/444

Начинаем гуглить в эту сторону — ценнейшая статья от редхэта:
https://www.redhat.com/en/blog/what-you-need-know-about-first-ever-dnssec-root-key-rollover-october-11-2018

Ок, проверяем dnsmasq — нового ключа нет (но если виноват он — почему не работало при указании явно внешнего сервера в Nameservers). Обновляем — ключ приходит, эффекта нет.

Ищем, какой ключ таки использует opendkim, после некоторых поисков обнаруживаем, что этот:

# cat /usr/share/dns/root.key
. 172800 IN DNSKEY 257 3 8 AwEAAagAIKlVZrpC6Ia7gEzahOR+9W29euxhJhVVLOyQbSEW0O8gcCjFFVQUTf6v58fLjwBd0YI0EzrAcQqBGCzh/RStIoO8g0NfnfL2MTJRkxoXbfDaUeVPQuYEhg37NZWAJQ9VnMVDxP/VHL496M/QZxkjf5/Efucp2gaDX6RS6CXpoY68LsvPVjR0ZSwzz1apAzvN9dlzEheX7ICJBBtuA6G3LQpzW5hOA2hzCTMjJPJ8LbqF6dsV6DoBQzgul0sGIcGOYl7OyQdXfZ57relSQageu+ipAdTTJ25AsRTAoub8ONGcLmqrAmRLKBP1dfwhYB4N7knNnulqQxA+Uk1ihz0= ;{id = 19036 (ksk), size = 2048b} ;;state=2 [ VALID ] ;;count=0 ;;lastchange=1404118431 ;;Mon Jun 30 10:53:51 2014

Система обновлялась давно, нового ключа в списке нет.

Выясняем кто поставляет файл, и обновляем:

# dpkg -S /usr/share/dns/root.key
dns-root-data: /usr/share/dns/root.key

# apt-get install dns-root-data

И все тут же начинает работать:

# opendkim-testkey -d youtube.com -s 20161025 -v -v -v
opendkim-testkey: using default configfile /etc/opendkim.conf
opendkim-testkey: checking key ‘20161025._domainkey.youtube.com’
opendkim-testkey: key not secure
opendkim-testkey: key OK

Несколько фактов про Redis #painisinstructional

  1. Пустая строка — валидный ключ в редисе.
    redis.rpoplpush(list, processingList) когда processlingList — пустая строка успешно складывает данные в него.
  2. Имеем 3 сервера с автопереключением через Redis Sentinel. Из-за ошибки из пункта 1 на мастере переполняется память, но не слишком быстро — в итоге до падения по OOM не доходит, но памяти перестает хватать на bgsave. Мастер перестает обрабатывать запросы, т.к. отвалились слейвы и он не может сделать bgsave. Слейвы отваливаются, т.к. мастер не может сделать bgsave, но при этом считают его живым и не осуществляют переключение на себя.
    Единственный выход — вручную убить мастер, предварительно добавив памяти слейвам и исправив ошибку из п.1, чтобы все не началось заново.

Performance monitoring metrics из XenServer в Zabbix

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

До этого эти метрики смотрели в XenCenter — но это неудобно, т.к. сложно соотносить с бизнес-метриками из заббикса (с средним временем обработки запросов к примеру).

Метрики доступны начиная с XenServer 6.1:
https://support.citrix.com/article/CTX135033

Для их использования существует тулза rrd2csv — но для целей передачи значения из неё в заббикс она обладает рядом недостатков:

  1. работает пока не прервут, выдавая метрики раз в заданное число секунд (по-умолчанию 5)
  2. выплевывает результат в формате csv
  3. работает только под root

Проблема 1 решается с помощью timeout.

Проблема 2 решается благодаря тому, что утилите можно передать в особом формате какие именно метрики хочется получить.
В итоге получается нечто такое:

# (timeout 1s rrd2csv AVERAGE:host::cpu_avg || true) | cut -f2 -d» » | tail -n 1

возвращает текущую среднюю загрузку процессора.

Для решения проблемы 3 — поставил получение этой метрики в крон с сохранением значения в файл:

 * * * * * (timeout 1s /opt/xensource/bin/rrd2csv AVERAGE:host::cpu_avg || true) | cut -f2 -d» » | tail -n 1 > /tmp/xencpu

а UserParameter в zabbix читает уж из него:

 UserParameter=xen.cpu,cat /tmp/xencpu