Skip to content

Building Microservices: Designing Fine-Grained Systems - Sam Newman

Refs:

Chapter 1. Microservices

What Are Microservices?

Микросервисы — это небольшие, автономные, совместно работающие сервисы.

Small, and Focused on Doing One Thing Well (Небольшие и нацеленные на то, чтобы хорошо справляться только с одной работой)

Autonomous (Автономные)

Key Benefits

Микросервисы обладают множеством разнообразных преимуществ. Многие из них могут быть присущи любой распределенной системе. Но микросервисы нацелены на достижение вершин этих преимуществ, что обусловливается в первую очередь тем, насколько глубоко ими принимаются концепции, положенные в основу distributed systems (распределенных систем) и service-oriented architecture (сервис-ориентированной архитектуры).

Technology Heterogeneity (Технологическая разнородность)

Resilience (Устойчивость)

Scaling (Масштабирование)

Ease of Deployment (Простота развертывания)

Organizational Alignment (Решение организационных вопросов)

Composability (Компонуемость)

Optimizing for Replaceability (Оптимизация с целью последующей замены)

What About Service-Oriented Architecture?

Service-oriented architecture (SOA, Сервис-ориентированная архитектура) представляет собой подход в проектировании, при котором несколько сервисов работают совместно для предоставления некоего конечного набора возможностей. Под сервисом здесь обычно понимается полностью отдельный процесс операционной системы. Связь между такими сервисами осуществляется через сетевые вызовы, а не через вызовы методов в границах процесса.

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

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

Other Decompositional Techniques

Shared Libraries (Совместно используемые библиотеки)

Modules (Модули)

No Silver Bullet

Chapter 2. The Evolutionary Architect

Inaccurate Comparisons (Неверные сравнения)

У архитекторов весьма важная задача. Они отвечают за координацию технического представления, помогающего нам дать клиентам именно ту систему, в которой они нуждаются. В одних организациях архитекторам приходится работать с одной командой, в таком случае роли архитектора и технического лидера зачастую сливаются в одну. В других организациях они могут определять представление о всей программе работы в целом, координировать взгляды команд, разбросанных по всему миру или, может быть, действующих под крышей всего одной организации. Но, на каком бы уровне они ни работали, точно определить их роль весьма трудно, и даже несмотря на то, что в компаниях стать архитектором считается карьерным ростом для разработчика, эта роль попадает под огонь критики намного чаще любой другой. Архитектор гораздо чаще других может напрямую повлиять на качество создаваемых систем, условия работы своих коллег и способность организации реагировать на изменения, и все же зачастую нам представляется, что в получении этой роли нет ничего хорошего. Почему же так происходит?

An Evolutionary Vision for the Architect (Эволюционное видение для архитектора)

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

Zoning

  • Программирующий архитектор
    Если нужно гарантировать, что создаваемые системы не вызывают дискомфорта у разработчиков, архитекторам нужно разбираться в том влиянии, которое имеют их решения. Как минимум это означает, что следует вникать в дела команды, а в идеале это должно означать, что нужно с этой командой еще и заниматься программированием. Тем из вас, кто занимался программированием с компаньоном, не составит труда в качестве архитектора влиться на непродолжительный срок в команду и заняться программированием в паре с кем-нибудь из ее членов. Если хотите быть в курсе хода работ, это должно стать обычной практикой. Нужно подчеркнуть, насколько важно для архитектора вникать в работу команды! Это куда эффективнее, чем перезваниваться с этой командой или просто просматривать созданный ею код. Насколько часто нужно погружаться в работу подведомственной вам команды (или команд), зависит главным образом от ее количественного состава. Но главное, чтобы это стало постоянной практикой. Если, к примеру, вы работаете с четырьмя командами, проведите по несколько часов с каждой из них в течение каждых четырех недель, чтобы гарантировать, что у вас сложились взаимопонимание и хорошие взаимоотношения с их членами.

A Principled Approach

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

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

Strategic Goals (Стратегические цели)

Principles (Принципы)

Practices (Инструкции)

Combining Principles and Practices (Объединение принципов и инструкций)

The Required Standard

При проработке инструкций и размышлении насчет компромиссов, на которые необходимо пойти, нужно определить и такой очень важный момент, как допустимая степень изменчивости вашей системы. Одним из основных способов определения неизменных качеств для всех сервисов является установка критериев отбора тех из них, поведение и качество которых не выходят за рамки допустимого. Какой же из сервисов станет добропорядочным жителем вашей системы? Какими возможностями он должен обладать, чтобы гарантировать управляемость системы, и то, что один негодный сервис не приведет к сбою всей системы? Ведь здесь как с людьми: добропорядочность жителя в одних условиях не определяет то, на что он может стать похож в каких-либо других условиях. И все же есть некие общие характеристики подходящих сервисов, которых, на мой взгляд, очень важно придерживаться. Существует ряд ключевых областей, в которых допустимость слишком больших отклонений может привести к весьма жарким временам. Согласно определению Бена Кристенсена (Ben Christensen) из компании Netflix, когда мы мысленно рисуем себе крупную картину, «должна быть стройная система из множества мелких деталей с автономными жизненными циклами, способная представлять собой единое целое». Следовательно, нужно найти разумный баланс оптимизации автономии отдельно взятого микросервиса, не теряя при этом из виду общую картину.

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

Monitoring (Мониторинг)

Interfaces (Интерфейсы)

Architectural Safety (Архитектурная безопасность)

Governance Through Code

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

Exemplars (Экземпляры)

Tailored Service Template (Подгоняемый шаблон сервиса)

Technical Debt

Зачастую мы попадаем в такие ситуации, когда не можем следовать нашему техническому видению буквально, и, чтобы получить крайне необходимые функции, нам приходится срезать ряд углов. Это просто один из компромиссов, на которые надо идти. У нашего технического видения есть вполне веские основания. При отклонении от этих оснований могут быть получены краткосрочные преимущества, но в долгосрочной перспективе это может обойтись весьма дорого. Понятие, помогающее нам разобраться с этим компромиссом, называется техническим обязательством. Когда мы получаем техническое обязательство, похожее на обязательство в обычном мире, у него есть текущие затраты и нечто, что нам нужно выплатить по обязательству.

Exception Handling

Таковы наши принципы и инструкции, определяющие порядок создания систем. А что происходит, когда система от них отклоняется? Иногда принимается решение о том, что это всего лишь исключение из правила. В таком случае стоит, наверное, зарегистрировать такое решение где-нибудь в рабочем журнале как напоминание на будущее. При возникновении довольно большого количества исключений может появиться смысл изменить принцип или инструкцию, чтобы в них отразилось новое понимание действительности.

Governance and Leading from the Center

Чтобы справиться со своими задачами, архитекторам нужна руководящая роль. Что подразумевается под руководством? Весьма точное определение этому понятию дается в бизнес-модели по руководству и управлению ИТ на предприятии (Control Objectives for Information and Related Technology (COBIT)): «Руководство обеспечивает уверенность в достижении целей предприятия путем сбалансированной оценки потребностей заинтересованных сторон, существующих условий и возможных вариантов, установления направления развития через приоритизацию и принятие решений, постоянного мониторинга соответствия фактической продуктивности и степени выполнения требований установленным направлению и целям предприятия».

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

Building a Team

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

Summary

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

  • Vision (Концепция)
    Следует обеспечить наличие четко воспринимаемой технической концепции системы, что поможет последней отвечать требованиям потребителей и вашей организации.
  • Empathy (Чуткость)
    Нужно чутко воспринимать влияние ваших решений на потребителей и коллег.
  • Collaboration (Сотрудничество)
    Нужно взаимодействовать с как можно большим числом коллег и сотрудников, чтобы стало легче определять, уточнять и реализовывать положения концепции.
  • Adaptability (Приспособляемость)
    Следует обеспечить внесение в техническую концепцию изменений в соответствии с требованиями, которые выдвигают потребители или организация.
  • Autonomy (Автономность)
    Нужно найти разумный баланс между стандартизацией и возможностью автономности в работе ваших команд.
  • Governance (Руководство)
    Следует обеспечить соответствие реализуемой системы технической концепции.

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

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

Chapter 3. How to Model Services

What Makes a Good Service?

Как создать хороший сервис? Если вы уже испытали на себе горечь поражения при создании сервис-ориентированной архитектуры, то вполне можете понять, к чему я клоню. Но на случай, если сия участь вас миновала, хочу выделить две основные концепции: loose coupling (слабую связанность) и high cohesion (сильное зацепление).

Loose Coupling

Когда между сервисами наблюдается слабая связанность, изменения, вносимые в один сервис, не требуют изменений в другом. Для микросервиса самое главное — возможность внесения изменений в один сервис и его развертывания без необходимости вносить изменения в любую другую часть системы. И это действительно очень важно.

Что вызывает необходимость тесной связанности? Классической ошибкой является выбор такого стиля интеграции, который тесно привязывает один сервис к другому, что при изменении внутри сервиса требует изменений в его потребителях. Более подробно способы, позволяющие избегать подобных ситуаций, будут рассматриваться в главе 4.

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

High Cohesion

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

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

The Bounded Context

В книге Эрика Эванса (Eric Evans) Domain-Driven Design (Addison-Wesley) основное внимание уделялось способам создания систем, моделирующих реально существующие области. В книге множество великолепных идей вроде использования единого языка, хранилища абстракций и т. п., а еще там представлено одно очень важное понятие, которое я поначалу упустил из виду: bounded context (ограниченный контекст). Суть его в том, что каждая отдельно взятая область состоит из нескольких ограниченных контекстов и то, что в каждом из них находится, — это things (предметы) (Эрик широко использует слово model (модель), что, наверное, лучше things (предмета)), которые не должны общаться с внешним миром, а также предметами, которые во внешнем мире используются совместно с другими ограниченными контекстами. У каждого ограниченного контекста имеется четко определенный интерфейс, где он решает, какие модели использовать совместно с другими контекстами.

Мне нравится еще одно определение ограниченного контекста: конкретная ответственность, обеспечиваемая четко обозначенными границами. Если нужна информация из ограниченного контекста или нужно сделать запросы на какие-либо действия внутри ограниченного контекста, происходит обмен данными с его четко обозначенной границей с помощью моделей. В своей книге Эванс использовал аналогию с клетками: «Клетки могут существовать благодаря своим мембранам, определяющим, что попадает внутрь, что выходит наружу и что именно может через них проходить».

Business Capabilities

Приступая к обдумыванию bounded contexts, имеющихся в вашей организации, нужно размышлять не в понятиях shared data (совместно используемых данных), а в понятиях capabilities (возможностей), предоставляемых такими контекстами всей остальной области. Товарный склад, к примеру, может предоставить возможность получения текущего списка запасов, а финансовый контекст — выдать состояние счетов на конец месяца или позволить внести новичка в платежную ведомость. Для этих возможностей может понадобиться взаимный обмен информацией, то есть shared models (совместно используемые модели), но мне довольно часто приходилось наблюдать, что обдумывание data (данных) приводило к созданию anemic (безжизненных), основанных на CRUD (create — «создание», read — «чтение», update — «обновление», delete — «удаление») сервисов. Поэтому сначала нужно задать себе вопрос «Чем этот контекст занимается?», а затем уже вопрос «А какие данные ему для этого нужны?».

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

Chapter 4. Integration

Looking for the Ideal Integration Technology

Avoid Breaking Changes

Keep Your APIs Technology-Agnostic

Make Your Service Simple for Consumers

Hide Internal Implementation Detail

Interfacing with Customers

The Shared Database

Synchronous Versus Asynchronous

Remote Procedure Calls

Remote Procedure Call (Удаленный вызов процедуры) является технологией локального вызова, который выполняется где-то на удаленном сервисе. У технологии RPC имеется множество разновидностей. Некоторые из них основаны на применении определенного интерфейса (SOAP, Thrift, Protocol Buffers).

Technology Coupling

Local Calls Are Not Like Remote Calls

Brittleness

REST

REpresentational State Transfer (REST, Передача репрезентативного состояния) представляет собой архитектурный стиль, инспирированный Всемирной сетью. У REST-стиля имеется множество принципов и ограничений, но мы собираемся сконцентрировать внимание на тех из них, которые действительно помогут нам при встрече с интеграционными трудностями в мире микросервисов и поиске стилей интерфейсов для наших сервисов, выступающих в качестве альтернативы RPC.

Richardson Maturity Model

HATEOAS

Hypermedia As the Engine of Application State (HATEOAS, гиперсреда, используемая в качестве механизма определения состояния приложения)

Гиперсреда является понятием, в соответствии с которым часть содержимого содержит ссылки на другие части содержимого, представленного разнообразными форматами (например, в виде текста, изображений, звуков). Вам это должно быть знакомо, поскольку тем же самым занимается типовая веб-страница: для просмотра родственного содержимого вы следуете по ссылкам, являющимся формой элементов управления гиперсредой. Идея, положенная в основу HATEOAS, заключается в том, что клиенты должны взаимодействовать с сервером (что потенциально приводит к передаче состояния) посредством этих ссылок на другие ресурсы. При этом, зная, на какой идентификатор ресурса (URI) нужно попасть, не требуется знать, где именно на сервере располагаются данные о клиентах. Вместо этого клиент, чтобы найти нужную информацию, ищет ссылки и переходит по ним.

JSON, XML, or Something Else?

Применение стандартных текстовых форматов дает клиентам гибкость в потреблении ресурсов, а REST с использованием HTTP позволяет применять различные форматы. В ранее показанных примерах применялся XML, но на данном этапе намного более популярным форматом содержимого для сервисов, работающих с использованием HTTP, является JSON.

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

Но у JSON есть и недостатки. В XML определяется элемент управления link, который ранее использовался нами в качестве элемента управления гиперсредой. В стандарте JSON ничего подобного не определяется, поэтому для содействия этой концепции часто используются внутренние стили. В Hypertext Application Language (прикладном гипертекстовом языке, HAL) предпринимается попытка исправить ситуацию путем определения общих стандартов для создания гиперссылок в JSON (а также в XML, хотя XML, возможно, меньше нуждается в такой помощи). Если следовать стандарту HAL, то для выявления элементов управления гиперсредой можно воспользоваться такими инструментами, как HAL-браузер на веб-основе, который может существенно упростить задачу создания клиента.

Implementing Asynchronous Event-Based Collaboration

DRY and the Perils of Code Reuse in a Microservice World

Одним из часто употребляемых акронимов является DRY: don’t repeat yourself — «не повторяйтесь». Хотя это определение зачастую упрощенно рассматривается как попытка избежать использования продублированного кода, более точное значение DRY заключается в стремлении избежать продублированности поведения и осведомленности нашей системы. В целом это весьма разумный совет. Наличие большого количества строк кода, выполняющих одно и то же, делает объем исходного кода больше необходимого, затрудняя тем самым его осмысление. При желании изменить поведение, продублированное во многих частях системы, совсем не трудно забыть о каких-либо местах, в которые нужно вносить изменения, что может привести к появлению ошибок. Поэтому в целом придерживаться DRY-принципа все же стоит.

Versioning

Defer It for as Long as Possible

Catch Breaking Changes Early

Use Semantic Versioning

Coexist Different Endpoints

Use Multiple Concurrent Service Versions

User Interfaces

API Composition

UI Fragment Composition

Backends for Frontends

A Hybrid Approach

Integrating with Third-Party Software

  • Commercial off-the-shelf software (COTS, готовые коммерческие программы)
  • software as a service (SaaS, программы в виде сервисов)
  • Content Management System (CMS, систем управления контентом)
  • Customer Relationship Management (CRM, система управления взаимосвязями с клиентами)

The Strangler Pattern

Когда дело доходит до устаревших или даже COTS-платформ, которые находятся полностью под вашим контролем, приходится справляться с ситуациями, при которых нужно их удалить или по крайней мере от них отойти. В таком случае пригодится шаблон под названием [Strangler Application Pattern]https://martinfowler.com/bliki/StranglerFigApplication.html). Во многом подобно примеру выстраивания лицевой части CMS с помощью собственного кода, с использованием шаблона Strangler добывают и перехватывают вызовы к старой системе. Это дает возможность принять решение, нужно ли направлять эти вызовы существующему устаревшему коду или же направлять их к новому коду, который вы могли написать. Это позволяет со временем заменить функциональные свойства, не требуя для этого больших переделок кода.

Chapter 5. Splitting the Monolith

It’s All About Seams

Майкл Физерс (Michael Feathers) в своей книге Working Effectively with Legacy Code (Prentice-Hall) дал определение понятия seam (стыка) как порции кода, которая не может рассматриваться изолированно и работать, не влияя на весь остальной исходный код. Нам также нужно дать определение стыкам. Но вместо поиска определения для более четкого понимания исходного кода нужно определить стыки, которые могут превратиться в service boundaries (границы сервисов).

Итак, по каким же критериям можно определить хороший стык? Как уже говорилось, превосходными стыками могут послужить bounded contexts (ограниченные контексты), поскольку по определению они представляют собой сильно зацепленные, но все же слабо связанные границы внутри организации. Следовательно, первым шагом должно стать определение этих границ в нашем коде.

The Reasons to Split the Monolith

Pace of Change

Team Structure

Security

Technology

Refactoring Databases

То, что было рассмотрено в предыдущих примерах, относится к перестройкам баз данных, способствующих разделению ваших схем. Для более подробного изучения предмета можно обратиться к книге Скотта Дж. Амблера (Scott J. Ambler) и Прамода Садаладжа (Pramod J. Sadalage) Refactoring Databases (Addison-Wesley).

Staging the Break

Transactional Boundaries

Try Again Later

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

Во многих смыслах это еще одна форма того, что называется eventual consistency (возможной согласованностью). Вместо использования транзакционной границы как гарантии согласованного состояния по окончании транзакции мы допускаем, что система сама приведет себя в согласованное состояние в какой-то будущий момент времени. Такой подход особенно хорош для продолжительных бизнес-операций. Более подробно он будет рассмотрен в главе 11 при изучении особенностей масштабируемых шаблонов.

Abort the Entire Operation

Еще один вариант заключается в отмене всей операции. В этом случае систему нужно вернуть в прежнее согласованное состояние. С таблицей комплектации все просто, поскольку вставка дала сбой, но в таблице заказов мы имеем уже зафиксированную транзакцию. Поэтому нужно сделать откат. Необходимое действие выполняется в рамках compensating transaction (компенсационной транзакции), то есть запуска новой транзакции для отката всего, что только что случилось. В нашем случае все может свестись к простой выдаче инструкции удаления DELETE, предназначенной для удаления заказа из базы данных. Затем нужно будет отчитаться в пользовательском интерфейсе о сбое операции. В монолитной системе наше приложение может справиться с обоими аспектами, а вот когда код приложения уже разбит на части, нужно призадуматься о том, что делать. Где именно должна находиться логика управления компенсационной транзакцией, в клиентском сервисе или где-то еще?

А как быть, если произойдет сбой компенсационной транзакции? Вероятность этого не исключена. Тогда у нас в таблице заказов будет заказ, не имеющий соответствующей ему инструкции по комплектации. В такой ситуации нужно либо провести компенсационную транзакцию повторно, либо позволить какому-нибудь внутреннему процессу убрать несогласованность чуть позже. Можно было бы просто воспользоваться экраном обслуживания с доступом только со стороны административного персонала или же использовать автоматизированный процесс.

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

Distributed Transactions

Альтернативой ручной организации компенсационных транзакций является использование distributed transaction ( распределенной транзакции). Распределенные транзакции пытаются объединить в себе сразу несколько транзакций, используя для управления различными транзакциями, проводимыми в базовых системах, общий управляющий процесс, называемый transaction manager (диспетчером транзакций). Точно так же, как и обычная транзакция, распределенная транзакция старается гарантировать пребывание всего в согласованном состоянии, только она пытается сделать это в рамках нескольких систем, запущенных в различных процессах, связь между которыми зачастую осуществляется через сетевые границы.

Наиболее распространенный алгоритм управления распределенными транзакциями — особенно теми, которые носят кратковременный характер, как в случае с нашим клиентским заказом, — заключается в использовании two-phase commit ( двухфазной фиксации). При этом сначала следует фаза голосования, при которой каждый участник (также называемый в данном контексте cohort (партнером)) распределенной транзакции сообщает диспетчеру транзакций о том, считает ли он, что его локальная транзакция может начинаться. Если диспетчер транзакций получит положительный ответ от всех участников, он дает им команду на начало транзакций и выполняет их фиксацию. Для того чтобы совершить откат всех транзакций, диспетчеру транзакций хватает единственного отрицательного ответа.

Такой подход предполагает, что все участники останавливаются, пока центральный координационный процесс не даст команду на продолжение работы. Это означает, что мы не застрахованы от остановки работы. Если диспетчер транзакций зависнет, отложенные транзакции никогда не завершатся. Если партнер не ответит в процессе голосования, все будет заблокировано. И неизвестно, что произойдет, если фиксация даст сбой после голосования. В этом алгоритме есть безусловное предположение о том, что такого никогда не случится: если партнер сказал «да» при голосовании, значит, мы должны предполагать, что его транзакция будет зафиксирована. Партнерам нужен способ, позволяющий заставить фиксацию происходить в нужный момент. Это означает, что данный алгоритм не защищен от сторонних сбоев, вернее, он предусматривает попытку обнаружения большинства случаев сбоев.

Этот координационный процесс предусматривает также установку блокировок, то есть отложенная транзакция должна удерживать блокировку ресурсов. Блокировка ресурсов может привести к конкуренции, существенно усложняя масштабируемые системы, особенно в контексте распределенных систем.

Распределенные транзакции были реализованы для конкретных технологических стеков, таких как Transaction API в Java, что позволяет таким разрозненным ресурсам, как база данных и очередь сообщений, участвовать в одной и той же всеобъемлющей транзакции. Разобраться в различных алгоритмах довольно трудно, поэтому я советую отказаться от попытки создания собственных алгоритмов. Если вы считаете, что нужно пойти именно этим путем, лучше досконально исследуйте данную тему и посмотрите, можно ли воспользоваться какой-либо из уже имеющихся реализаций.

Reporting

The Reporting Database

Data Retrieval via Service Calls

Data Pumps

Event Data Pump

Backup Data Pump

Summary

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

Chapter 6. Deployment

A Brief Introduction to Continuous Integration

При использовании Continuous integration (CI) основной целью является поддержка всеобщего синхронизированного состояния, которая достигается путем проверки того, что только что введенный в эксплуатацию код интегрируется с существующим кодом должным образом. Для достижения этой цели CI-сервер определяет переданный код и проводит ряд проверок, убеждаясь в том, что код скомпилирован и тесты с ним проходят без сбоев.

В качестве части данного процесса часто создается некий artifact (артефакт) или несколько, используемый для дальнейшей проверки, например развертывания работающего сервиса с целью запуска для него ряда тестов. В идеале нам потребуется создавать эти артефакты только один раз и использовать их для всех развертываний той или иной версии кода. Это делается для того, чтобы избежать многократного повторения одних и тех же действий и чтобы можно было подтвердить, что развернутый нами артефакт уже прошел тестирование. Чтобы допустить повторное использование этих артефактов, мы помещаем их в особое хранилище, либо предоставляемое самим CI-инструментарием, либо находящееся в отдельной системе.

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

Мне нравятся три вопроса, которые Джез Хамбл (Jez Humble) задает людям, чтобы проверить, правильно ли они понимают, что такое CI.

  • Вы проходите ежедневную проверку в основной программе?
    Вам нужно убедиться в том, что ваш код интегрируется в систему. Если не проводить частые проверки своего кода вместе с чьими-либо изменениями, можно существенно усложнить будущую интеграцию. Даже при использовании для управления изменениями недолговечных ответвлений интеграцию с единой основной ветвью нужно проводить как можно чаще.
  • Имеется ли у вас набор тестов для проверки правильности вносимых вами изменений?
    Без тестов мы просто знаем, что синтаксически интеграция работает, но не знаем, не нарушим ли поведение системы. CI без ряда проверок, подтверждающих ожидаемое поведение кода, — это не CI.
  • Служит ли исправление неисправной сборки главным приоритетом команды?
    Проход зеленой сборки означает, что изменения были безопасно интегрированы. Красная сборка означает, что последнее изменение, возможно, не прошло интеграцию. Нужно остановить все последующие проверки, не применяемые для исправления сборок, чтобы пройти тесты повторно. Если нагромоздить большое количество изменений, время исправления сборки существенно возрастет. Мне приходилось работать с командами, у которых сборка оставалась неисправной по нескольку дней, что приводило к необходимости прикладывать существенные усилия для получения в итоге проходящей тесты сборки.

Build Pipelines and Continuous Delivery

В самом начале использования непрерывной интеграции мы поняли важность наличия временами внутри сборки нескольких стадий. В качестве весьма распространенного проявления этой особенности можно назвать тесты. Может использоваться множество быстрых, весьма ограниченных по области применения тестов и небольшое количество широкомасштабных медленных тестов. Если запустить сразу все тесты, то в режиме ожидания завершения широкомасштабных медленных тестов можно не дождаться быстрого получения ответного результата на сбой при проведении быстрых тестов. А если быстрые тесты не будут пройдены, то запускать медленные тесты не будет никакого смысла! Решением этой проблемы является использование различных стадий сборки путем создания того, что называется build pipeline (сборочным конвейером). Одна стадия будет для быстрых тестов, а другая — для медленных.

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

Continuous delivery (Непрерывная доставка, CD) построена на этой и некоторых других концепциях. Как подчеркивается в книге с таким же названием, авторами которой являются Джез Хамбл и Дэйв Фарли (Dave Farley):

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

Operating System Artifacts

Что касается Linux, FPM package manager tool предоставляет весьма неплохую абстракцию для создания пакетов этой операционной системы, а преобразование из развертывания на основе tarball в развертывание на основе средств операционной системы может проводиться без особого труда.

Custom Images

Packer

Используя настроечные сценарии по вашему выбору (поддерживаются Chef, Ansible, Puppet и многие другие средства), Packer позволяет создавать образы для различных платформ из одной и той же конфигурации. На момент написания книги это средство поддерживало VMWare, AWS, Rackspace Cloud, Digital Ocean и Vagrant, и мне встречались команды, успешно использующие его для создания образов под Linux и Windows. Это означает, что из одной и той же конфигурации можно создавать образ для развертывания в вашей производственной среде AWS и соответствующего Vagrant-образа для локального развертывания и тестирования.

Images as Artifacts

Immutable Servers

При хранении всех наших настроек в системе управления версиями мы стремимся обеспечить возможность автоматизированного воспроизводства сервисов и, надеюсь, при желании — всей среды. Но что случится, если мы запустили процесс развертывания, а кто-то войдет в систему и внесет изменения независимо от того, что находится в системе управления версиями? Эту проблему часто называют configuration drift (дрейфом конфигурации) — код в системе управления версиями больше не является отражением конфигурации на работающем хосте.

Service-to-Host Mapping

Когда речь заходит о микросервисах, в самом начале возникает вопрос: «А сколько сервисов может быть на каждой машине?» Перед тем как дать ответ, нужно подобрать более подходящее понятие, чем machine (машина() или даже использованное мною ранее более универсальное понятие box (стойка). В нашу эру виртуализации отображение между отдельно взятым хостом, на котором работает операционная система, и используемой физической инфраструктурой может варьироваться в самых широких пределах. Я, например, склонен говорить о hosts (хостах), используя их как универсальную единицу изолированности, а именно как операционную систему, под управлением которой могу установить и запустить свои сервисы. Если развертывание ведется вами непосредственно на физические машины, тогда один физический сервер отображается на один хост (что, возможно, будет не вполне корректной терминологией в данном контексте, но за неимением лучшей может считаться подходящей). Если используется виртуализация, то отдельная физическая машина может отображаться на несколько независимых хостов, каждый из которых может содержать один сервис и более.

Multiple Services Per Host

Application Containers

Single Service Per Host

Platform as a Service (PaaS)

From Physical to Virtual

Traditional Virtualization

Vagrant

Linux Containers

Docker

Summary

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

Затем, если есть такая возможность, перейдите к схеме, предполагающей размещение одного сервиса на каждом хосте или в каждом контейнере. Чтобы удешевить и облегчить управление перемещаемыми частями, обратите внимание на альтернативные технологии, такие как LXC или Docker, но при этом отнеситесь с пониманием к тому, что, какую бы технологию вы ни взяли на вооружение, ключом к управлению всеми аспектами служит культура автоматизации. Автоматизируйте все, что можно, и если используемая вами технология не позволяет этого сделать, перейдите на новую технологию! Когда дело дойдет до автоматизации, огромные преимущества вам даст возможность использования платформы, подобной AWS.

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

И наконец, если вы хотите изучить эту тему глубже, я настоятельно рекомендую прочитать книгу Джеза Хамбла (Jez Humble) и Дэвида Фарли (David Farley) Continuous Delivery (Addison-Wesley), в которой намного больше подробностей, касающихся конструирования конвейеров и управления артефактами.

Chapter 7. Testing

Types of Tests

Luckily, Brian Marick came up with a fantastic categorization system for tests that fits right in. Figure 7-1 shows a variation of Marick’s quadrant from Lisa Crispin and Janet Gregory’s book Agile Testing (AddisonWesley) that helps categorize the different types of tests.

Test Scope

In his book Succeeding with Agile (Addison-Wesley), Mike Cohn outlines a model called the Test Pyramid to help explain what types of automated tests you need.

Unit Tests

These are tests that typically test a single function or method call. The tests generated as a side effect of test-driven design (TDD) will fall into this category, as do the sorts of tests generated by techniques such as property-based testing. We’re not launching services here, and are limiting the use of external files or network connections. In general, you want a large number of these sorts of tests. Done right, they are very, very fast, and on modern hardware you could expect to run many thousands of these in less than a minute.

These are tests that help us developers and so would be technology-facing, not business-facing, in Marick’s terminology. They are also where we hope to catch most of our bugs. So, in our example, when we think about the customer service, unit tests would cover small parts of the code in isolation.

Service Tests

Service tests are designed to bypass the user interface and test services directly. In a monolithic application, we might just be testing a collection of classes that provide a service to the UI. For a system comprising a number of services, a service test would test an individual service’s capabilities.

The reason we want to test a single service by itself is to improve the isolation of the test to make finding and fixing problems faster. To achieve this isolation, we need to stub out all external collaborators so only the service itself is in scope.

End-to-End Tests

End-to-end tests are tests run against your entire system. Often they will be driving a GUI through a browser, but could easily be mimicking other sorts of user interaction, like uploading a file.

Consumer-Driven Tests to the Rescue

С какой из ключевых проблем при использовании упомянутых ранее интеграционных тестов мы пытаемся справиться? Мы стараемся гарантировать, что при развертывании нового сервиса для работы в производственном режиме внесенные нами изменения не внесут разлад в работу потребителей. Одним из способов добиться обозначенной цели без необходимости проведения тестов с участием реальных потребителей является использование consumer-driven contract (CDC, контрактов, составленных на основе запросов потребителей).

При использовании CDC определяются ожидания потребителя от сервиса (или поставщика). Ожидания потребителей оформляются в виде кода тестов, который затем запускается в отношении поставщика. При правильной постановке вопроса эти CDC должны запускаться как часть CI-сборки поставщика, гарантируя, что он никогда не будет развернут при нарушении хотя бы одного из этих контрактов. Что очень важно с точки зрения получения обратного ответа при тестировании, эти тесты должны запускаться только в отношении отдельно взятого поставщика, находящегося в изоляции, поэтому они могут выполняться быстрее и надежнее тех сквозных тестов, которые могут быть ими подменены.

Testing After Production

Separating Deployment from Release

Одним из способов отлавливания большего количества дефектов еще до того, как они смогут проявиться в производственном режиме, является выведение области запуска тестов за пределы традиционных этапов, проводимых до развертывания. Если можно развернуть программное средство и протестировать его по месту до направления на него производственной нагрузки, можно выявить дефекты, характерные для заданной среды. Довольно распространенным примером этого может послужить smoke test suite (набор для проведения дымового теста), то есть коллекция тестов, разработанная для запуска в отношении только что развернутого программного средства с целью подтверждения работоспособности его кода. Эти тесты помогают выявить любые проблемы локальной среды. Если для развертывания любого отдельно взятого микросервиса используется инструкция командной строки (как, собственно, это и следует делать), эта инструкция должна запускать дымовые тесты автоматически.

Еще одним примером может послужить так называемое smoke test suite (сине-зеленое развертывание), при котором имеются две одновременно развернутые копии программного средства, но при этом реальные запросы получает только одна из версий.

Canary Releasing

Mean Time to Repair Over Mean Time Between Failures?

Иногда затрата таких же усилий на совершенствование средств корректировки выпуска может быть намного выгоднее добавления дополнительных автоматизированных тестов. В мире веб-операций это часто рассматривается как компромисс между оптимизацией под mean time between failures (MTBF, среднее время безотказной работы) и mean time to repair ( MTTR, среднее время восстановления работоспособности).

Cross-Functional Testing

Основная часть этой главы посвящена тестированию конкретных функциональных возможностей и особенностям его проведения в отношении систем на основе использования микросервисов. Но есть и другой вид тестирования, который важно рассмотреть. Для описания характеристик системы, не поддающихся простой реализации в виде обычной функции, существует обобщающий термин Nonfunctional requirements (нефункциональные требования). Эти требования включают в себя такие аспекты, как приемлемая задержка при выводе веб-страницы, количество пользователей, которое должно поддерживаться системой, показатель приспособленности пользовательского интерфейса к работе с ним людей с ограниченными возможностями, степень безопасности клиентских данных.

Термин nonfunctional никогда мне не нравился. Некоторые подпадающие под него понятия представляются весьма функциональными по своей природе! Одна из моих коллег, Сара Тарапоревалла (Sarah Taraporewalla), придумала вместо него словосочетание cross-functional requirements (межфункциональное тестирование, CFR), которому я всецело отдаю предпочтение. Оно говорит скорее о том, что эти элементы поведения системы действительно проявляются только в результате большого объема комплексной работы.

Summary

  • Проводите оптимизацию в целях получения быстрых ответных результатов и соответствующего разделения тестов на типы.
  • Избегайте использования сквозных тестов там, где можно воспользоваться контрактами на основе запросов потребителей.
  • Используйте контракты на основе запросов потребителей для фокусировки вокруг них переговоров между командами разработчиков.
  • Постарайтесь добиться разумного компромисса и решить, к чему все же следует прикладывать больше усилий — к тестированию или к ускоренному выявлению проблемных моментов при работе в производственном режиме (проводя оптимизацию либо под MTBF, либо под MTTR).

Если вы заинтересованы в получении дополнительных сведений о тестировании, я могу порекомендовать книгу Agile Testing ( Addison-Wesley), написанную Лизой Криспин (Lisa Crispin) и Джанет Грегори (Janet Gregory), в которой, кроме всего прочего, более подробно рассмотрены вопросы использования секторов тестирования.

Chapter 8. Monitoring

Single Service, Single Server

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

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

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

Single Service, Multiple Servers

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

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

Теперь о журналах. Поскольку сервис запущен на более чем одном сервере, нам, чтобы заглянуть вовнутрь, придется, наверное, регистрироваться на каждом из них. Если количество хостов невелико, можно воспользоваться таким средством, как SSH-мультиплексор, которое позволяет запускать одни и те же команды сразу на нескольких хостах. Затем понадобятся большой монитор и поиск виновника с помощью команды grep "Error" app.log.

Для задач типа отслеживания времени отклика мы можем, не проводя специального объединения, довольствоваться отслеживанием балансировщика нагрузки. Но нужно, разумеется, отслеживать работу и самого балансировщика: если он начнет барахлить, у нас возникнут проблемы. Тут, наверное, нужно побеспокоиться и о том, как должен выглядеть нормально работающий сервис, поскольку мы будем настраивать балансировщик нагрузки на удаление из приложения неисправных узлов.

Multiple Services, Multiple Servers

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

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

Logs, Logs, and Yet More Logs...

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

Synthetic Monitoring

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

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

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

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

Correlation IDs

Одним из подходов, который может помочь в данной ситуации, является использование идентификаторов взаимосвязанности. Когда делается первый вызов, для него можно сгенерировать глобальный уникальный идентификатор (GUID). Затем он передается по очереди всем последующим вызовам и может быть помещен в ваши журналы в структурированном виде. Это очень похоже на то, что вы уже делали с такими компонентами, как уровень журнала или дата. При правильном применении инструментов объединения журналов после этого появится возможность выполнить трассировку этого события по всей системе.

Consider the Audience

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

  • о чем они хотят знать в текущий момент;
  • что им может понадобиться чуть позже;
  • как они хотят воспользоваться данными.

Оповещайте о том, что они должны знать прямо сейчас. Создайте большое окно с данной информацией, располагающееся в углу экрана. Предоставьте легкий доступ к данным, о которых они хотят узнать чуть позже. И уделите время общению с ними, чтобы узнать, как они хотят воспользоваться данными. Обсуждение всех нюансов графического отображения количественной информации, конечно же, выходит за рамки данной книги, но для начала отличным подспорьем может послужить прекрасная книга Стивена Фью (Stephen Few) Information Dashboard Design: Displaying Data for Ata-Glance Monitoring (Analytics Press).

The Future

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

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

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

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

Summary

Для каждого сервиса:

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

Для системы:

  • объединяйте показатели на уровне хоста, например показатели загруженности центрального процессора с показателями на уровне приложения;
  • выбирайте такие инструменты хранения показателей, которые позволяют выполнять их объединение на системном или сервисном уровне и извлекать их для отдельно взятых хостов;
  • выбирайте такие инструменты хранения показателей, которые позволяют сохранять данные достаточно долго для того, чтобы можно было отследить тенденции в работе вашей системы;
  • используйте для объединения и хранения регистрационных записей единое средство, поддерживающее обработку запросов;
  • строго придерживайтесь стандартизации при использовании идентификаторов взаимосвязанности;
  • разберитесь, чему для перехода к действию требуется вызов, и выстройте соответствующим образом структуру оповещения и вывода на панель отслеживания;
  • исследуйте возможность унификации способов объединения всевозможных показателей, выяснив, пригодятся ли вам для этого такие средства, как Suro или Riemann.

Я также попытался обозначить направление, в котором развивается мониторинг: уход от систем, специализирующихся на выполнении какой-нибудь одной задачи, и переход к разработке типовых систем обработки событий, позволяющих составить более целостное представление о вашей системе. Это очень интересная и постоянно развивающаяся область, и хотя ее полноценное исследование не входит в задачи данной книги, я надеюсь, что для начала полученной вами информации вполне достаточно. При желании расширить познания можете обратиться к моей более ранней публикации Lightweight Systems for Realtime Monitoring (O’Reilly), где глубже рассматриваются как некоторые из этих, так и многие другие идеи.

Chapter 9. Security

Authentication and Authorization

Когда речь заходит о людях и о том, что вступает во взаимодействие с нашей системой, основными понятиями являются authentication (аутентификация) и authorization (авторизация). В контексте безопасности под authentication понимается процесс подтверждения того, что сторона, заявившая о себе, таковой на самом деле и является. Для человека authentication обычно сводится к набору имени и пароля. Предполагается, что только этот человек имеет доступ к такой информации и потому лицо, набравшее ее, и должно быть данным человеком. Разумеется, существуют и другие, более сложные системы. Мой телефон теперь позволяет мне использовать отпечаток пальца, чтобы я мог подтвердить свою личность. В общем смысле, когда речь заходит о том, кто или что проходит authentication, мы называем данную сторону principal ( принципалом).

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

Common Single Sign-On Implementations

Общепринятым подходом к аутентификации и авторизации является использование какого-либо из решений single sign-on ( единого входа, SSO). Соответствующие возможности в данной области предоставляются SAML — реализации, доминирующей в области промышленных предприятий, и OpenID Connect. В них применяются более или менее одинаковые основные понятия, хотя терминология немного различается. В данной главе будут использоваться термины, применяемые в SAML.

Когда принципал пытается получить доступ к ресурсу (например, интерфейсу на веб-основе), он перенаправляется на аутентификацию с участием identity provider *провайдера идентификации*). При этом у него могут быть запрошены имя пользователя и пароль или же может быть использовано что-то более совершенное — вроде двухфакторной аутентификации. После того как поставщик убедится в том, что принципал был аутентифицирован, он выдает информацию service provider ( сервис-провайдеру**), позволяя ему решать, нужно ли предоставлять принципалу доступ к ресурсу.

Провайдером идентификации может быть система на внешнем оборудовании или что-нибудь, что находится внутри вашей организации. Например, компанией Google предоставляется провайдер идентификации OpenID Connect. Но промышленным предприятиям свойственно иметь собственного провайдера идентификации, который может быть связан с directory service ( сервисом каталогов) вашей компании. Сервисом каталогов может быть какое-либо средство вроде Lightweight Directory Access Protocol (LDAP) или Active Directory. Эти системы позволяют хранить информацию о принципалах, свидетельствующую о тех ролях, которые они играют в организации. Зачастую сервис каталогов и провайдер идентификации представляют собой единое целое, а иногда они могут быть разделены, но тесно связаны друг с другом. Например, Okta содержит SAML-провайдер идентификации, выполняющий задачи двухфакторной идентификации, но как источник достоверных данных он может быть связан с сервисами каталогов вашей компании.

SAML представляет собой стандарт на основе использования SOAP-протокола, он известен тем, что с ним весьма непросто работать, несмотря на доступность поддерживающих его библиотек и инструментальных средств. OpenID Connect является стандартом, возникшим в качестве конкретной реализации OAuth 2.0 и основанным на способах управления технологией единого входа, принятых Google и рядом других компаний. В нем используются простые REST-вызовы, и, на мой взгляд, это сделано, скорее всего, для проникновения на рынок промышленных предприятий за счет простоты использования. Сейчас главным камнем преткновения является отсутствие поддерживающих его провайдеров идентификации. Для открытых для публичного просмотра сайтов может вполне подойти использование в качестве вашего провайдера средств компании Google, но для внутренних систем или систем, в которых требуются повышенный контроль и отображение того, как и где устанавливаются ваши данные, понадобится собственный домашний провайдер идентификации. На момент написания книги доступными в данном качестве были два из весьма немногих вариантов, OpenAM и Gluu, что не может сравниться с богатством выбора вариантов для SAML (включая средство Active Directory, которое, похоже, получило всеобщее распространение). До тех пор пока имеющиеся провайдеры идентификации не начали поддерживать OpenID Connect, распространение этого средства будет ограничено ситуациями, при которых люди будут вполне удовлетворены использованием публичных провайдеров идентификации.

Single Sign-On Gateway

Service-to-Service Authentication and Authorization

Allow Everything Inside the Perimeter

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

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

HTTP(S) Basic Authentication

Стандарт HTTP Basic Authentication позволяет клиенту отправлять имя пользователя и пароль в стандартном HTTP-заголовке. После этого сервер может проверить эти элементы и подтвердить, что клиенту разрешен доступ к сервису. Преимущество заключается в том, что это предельно понятный и повсеместно поддерживаемый протокол. Проблема в том, что отправка по протоколу HTTP весьма проблематична, потому что имя пользователя и пароль не отправляются в безопасном режиме. Любая промежуточная инстанция может просмотреть информацию, находящуюся в заголовке, и увидеть данные. Поэтому HTTP Basic Authentication следует использовать с привлечением протокола HTTPS.

При использовании HTTPS клиент получает твердые гарантии, что сервер, с которым он ведет обмен данными, является тем самым сервером, с которым клиент задумал связаться. Кроме этого, дается гарантия защиты от подглядываний за трафиком между клиентом и сервером или от манипуляций с полезной нагрузкой.

Use SAML or OpenID Connect

Если в качестве схемы аутентификации и авторизации вами уже используется SAML или OpenID Connect, этим можно воспользоваться и для взаимодействия между сервисами. Если используется шлюз, весь внутрисетевой трафик нужно будет также направлять через шлюз, но если каждый сервис справляется с интеграцией своими силами, этот подход должен работать просто замечательно. Преимущество заключается в использовании существующей инфраструктуры и получении возможности централизации всех ваших элементов управления доступом к сервисам в центральном сервере каталогов. Если же нужно предотвратить возможность атаки в пути следования данных, передачу все равно следует направлять через HTTPS.

Client Certificates

Еще один подход к идентификации клиента заключается в использовании возможностей протокола безопасности транспортного уровня (Transport Layer Security (TLS)), последователя SSL, для формирования клиентских сертификатов. Здесь у каждого клиента имеется сертификат X.509, используемый при установке связи между клиентом и сервером. Сервер может проверить аутентичность клиентского сертификата, предоставляя твердые гарантии надежности клиента.

HMAC Over HTTP

Альтернативный способ подписи запроса, который активно используется API-интерфейсами S3 компании Amazon и является частью OAuth-спецификации, — применение кода проверки подлинности сообщений на основе хеш-функции (HMAC).

При использовании HMAC тело запроса хешируется закрытым ключом и получившийся хеш отправляется вместе с запросом. Затем сервер использует собственную копию закрытого ключа и тело запроса для воссоздания хеша. При совпадении запрос принимается. Приятным обстоятельством является то, что, если где-нибудь на дистанции кто-то проведет какие-либо манипуляции с запросом, хеш не совпадет и сервер будет знать, что запрос был подделан. А закрытый ключ в запросе никогда не пересылается, поэтому он не может быть скомпрометирован в пути! Дополнительным преимуществом является то, что затем трафик может намного легче кэшироваться и издержки на генерирование хешей могут быть намного ниже, чем на обработку HTTPS-трафика (хотя у вас может сложиться и другое мнение).

API Keys

API-ключи используются всеми открытыми API-интерфейсами таких сервисов, как Twitter, Google, Flickr и AWS. API-ключи позволяют сервису идентифицировать того, кто осуществляет вызов, и наложить ограничения на то, что он может сделать. Зачастую ограничения выходят за рамки простого предоставления доступа к ресурсам и могут распространяться на такие действия, как ограничение скорости конкретных абонентов для обеспечения качества предоставления сервиса другим людям.

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

Chapter 10. Conway’s Law and System Design

В статье Мелвина Конвея (Melvin Conway) How Do Committees Invent, опубликованной в журнале Datamation в апреле 1968 года, было замечено: «Организации, проектирующие системы (здесь имеется в виду более широкое толкование, включающее не только информационные системы), неизбежно производят конструкцию, чья структура является копией структуры взаимодействия внутри самой организации».

People

[quote,Gerry Weinberg, The Second Law of Consulting] No matter how it looks at first, it’s always a people problem.

Chapter 11. Microservices at Scale

Failure Is Everywhere

How Much Is Too Much?

  • Response time/latency (Время отклика/задержка)
    Сколько времени должно тратиться на ту или иную операцию? Здесь может пригодиться измерение данного показателя в ходе работы различного количества пользователей с целью определения степени влияния возрастающей нагрузки на время отклика. Из-за характерных особенностей сетей неизбежны выпадения, поэтому может пригодиться задание целей для заданной процентили отслеживаемых ответов. Цель должна также включать количество параллельных подключений (пользователей), с которым, как ожидается, сможет справиться ваша программа. Следовательно, вы можете сказать: «Мы ожидаем, что у сайта значение 90-й процентили времени отклика будет порядка 2 секунд при обслуживании 200 параллельных подключений в секунду».
  • Availability (Доступность)
    Ожидается ли падение сервиса? Считается ли, что сервис работает в режиме 24/7? Некоторым при оценке доступности нравится смотреть на периоды допустимого вынужденного простоя, но насколько это практично для тех, кто вызывает ваш сервис? Я должен либо полагаться, либо не полагаться на доступность сервиса. Фактически оценка периодов вынужденного простоя более полезна с точки зрения исторической отчетности.
  • Durability of data (Живучесть данных)
    Каков приемлемый объем потери данных? Каков обязательный срок хранения данных? Скорее всего, для разных случаев можно будет дать разные ответы на эти вопросы. Например, можно выбрать вариант годичного хранения журналов регистрации пользовательских сеансов или с целью экономии дискового пространства — менее продолжительный срок, но записи о финансовых транзакциях может потребоваться хранить в течение многих лет.

Degrading Functionality

The Antifragile Organization

Timeouts

Circuit Breakers

Bulkheads

Isolation

Scaling

Go Bigger

Splitting Workloads

Spreading Your Risk

Load Balancing

Worker-Based Systems

Starting Again

Scaling Databases

Availability of Service Versus Durability of Data

Scaling for Reads

Scaling for Writes

Shared Database Infrastructure

CQRS

Caching

Client-Side, Proxy, and Server-Side Caching

Caching in HTTP

Caching for Writes

Caching for Resilience

Hiding the Origin

Keep It Simple

Cache Poisoning: A Cautionary Tale

CAP Theorem

At its heart it tells us that in a distributed system, we have three things we can trade off against each other: consistency, availability, and partition tolerance

Sacrificing Consistency

Sacrificing Availability

Sacrificing Partition Tolerance?

Service Discovery

DNS

Dynamic Service Registries

Zookeeper

Consul

Eureka

Chapter 12. Bringing It All Together