Что такое распределенное приложение
Мир распределенных систем
Просто спросите любой «замечательный ребенок»: современные, распределенные системы в, а монолитные приложения истекает!
Но это не просто «замечательные дети». Прогрессивные ИТ-руководители, корпоративные архитекторы и разработчики внимательныйи выводят на экран те же идеи, что и исследование и оценка современных распределенных приложений. Многие были приобретены в. Они разрабатывает новые и Реконструирование существующих корпоративных приложений, следуя принципам, шаблонам и методикам распределенных приложений микрослужб.
Но эта эволюция создает множество вопросов.
Для начала давайте перейдем назад и взглянем за последние 15 лет. В течение этого периода мы обычно создавали приложения в виде единой монолитной единицы. На рис. 1-1 показана архитектура.
Рис. 1-1. Монолитная архитектура.
Обратите внимание, как модули для упорядочивания, идентификации и маркетинга выполняются в односерверном процессе. Данные приложения хранятся в общей базе данных. Бизнес-функции предоставляются через интерфейсы HTML и RESTFUL.
Однако монолитные архитектуры могут представлять значительные трудности.
Со временем вы можете достичь точки, в которой начинается потеря контроля.
Рис. 1-2. Архитектура SOA.
С помощью SOA централизованные поставщики услуг, зарегистрированные в ESB. Бизнес-логика будет встроена в ESB для интеграции поставщиков и потребителей. Потребители служб могут затем находить эти поставщики и взаимодействовать с ними с помощью ESB.
Несмотря на обещание SOA, реализация такого подхода часто повышает сложность и предлагает узкие места. Затраты на обслуживание стали высокими, и по промежуточному плану ESB дорого. Службы, как правило, имеют большой размер. Они часто являются общими зависимостями и хранилищем данных. В итоге SOA часто приводит к созданию распределенной структуры с централизованными службами, которые были защищены от изменения.
Настоящее время, многие организации применяют скорость и гибкость, применяя архитектурный подход к созданию систем на основе архитектуры распределенных микрослужб. На рис. 1-3 показана система, созданная с использованием распределенных методик и методик.
Рис. 1-3. Распределенная архитектура.
Обратите внимание, что одно и то же приложение разбивается по набору распределенных служб. Каждый из них самодостаточен и инкапсулирует собственный код, данные и зависимости. Каждый из них развертывается в контейнере программного обеспечения и управляется контейнером Orchestrator. Вместо единственной базы данных, совместно используемой несколькими службами, каждая служба владеет частной базой данных. Другие службы не имеют прямой доступ к этой базе данных и могут получить только данные, предоставляемые через общедоступный API службы, которой она принадлежит. Обратите внимание, что некоторым службам требуется полная реляционная база данных, а другие — хранилище данных NoSQL. Служба корзины хранит свое состояние в распределенном кэше ключей и значений. Обратите внимание, как входящий трафик перенаправляется через службу шлюза API. Он отвечает за направление вызовов к службам и применение перекрестной задачи. Что важнее всего, приложение использует все преимущества функций масштабируемости, доступности и устойчивости, имеющихся в современных облачных платформах.
Однако, в то время как распределенные службы обеспечивают гибкость и скорость, они представляют собой разные наборы проблем. Рассмотрим следующее.
Для каждой из этих задач часто доступны несколько продуктов. Однако экранирование приложения от различий к продуктам и обеспечение поддержки и переносимости кода становится проблемой.
В этой книге представлены ДАПР. ДАПР — это распределенная среда выполнения приложения. Он напрямую решает многие из обнаруженных проблем вместе с распределенными приложениями. Глядя на то, что ДАПР может иметь более глубокое воздействие на разработку распределенных приложений.
Сводка
В этой главе мы обсуждали внедрение распределенных приложений. Мы отменяем монолитный подход к работе с распределенными службами. Мы указали многие распространенные проблемы при рассмотрении распределенного подхода.
Теперь вы можете переключить свою работу и подать нам новый мир ДАПР.
1. Распределенные приложения. Введение
Зачастую для предоставления телекоммуникационных услуг требуется создание распределённых приложений (распределённых информационных систем).
Распределённое приложение – это программа, состоящая из нескольких взаимодействующих частей, каждая из которых, как правило, выполняется на отдельном компьютере (или другом устройстве) сети:
Например, одна часть приложения, выполняющаяся на компьютере пользователя, может поддерживать специализированный графический интерфейс вторая работать на мощном выделенном компьютере и заниматься статистической обработкой введенных пользователем данных, а третья — заносить полученные результаты в базу данных на компьютере с установленной стандартной СУБД. Распределенные приложения в полной мере используют потенциальные возможности распределенной обработки, предоставляемые вычислительной сетью, и поэтому часто называются сетевыми приложениями
Распределённая система – программно-аппаратное решение, состоящее из компонентов, функционирующих на физически удаленных и независимых друг от друга гетерогенных узлах, представляющееся пользователям единой объединенной системой.
Распределенные вычислительные системы обладают такими общими свойствами, как:
Недостатки распределённых систем:
Требования к разрабатываемым распределённым системам:
1 Типовые архитектуры распределённых приложений
К таковым относятся:
1.1 Архитектура клиент-сервер
Данный вид архитектуры распределённых приложений является в настоящее время наиболее распространённым для информационных систем. Существует мнение, что все остальные архитектуры могут быть представлены вариациями данной базовой архитектуры.
Данная информационная система представляет собой совокупность взаимодействующих компонент двух типов: клиентов и серверов. Как правило, данные компоненты разнесены по узлам двух типов: узлам-клиентам и узлам-серверам.
Клиенты обращаются к серверам с запросами, которые те обрабатывают и возвращают результат. Один клиент может обращаться с запросами к нескольким серверам. Серверы также могут обращаться с запросами друг к другу. Таким образом, типичный протокол взаимодействия может быть представлен в виде обмена сообщениями: запрос клиента – ответ сервера.
Наиболее часто встречающийся класс приложений, выполненных в архитектуре клиент-сервер, – это приложения, работающие с базами данных. В данном случае в качестве сервера выступает СУБД, обеспечивающая выполнение запросов клиента, который реализует интерфейс пользователя.
Далее представлены несколько моделей, реализующих архитектуру клиент-сервер.
1.1.1 Модель сервиса
Модель сервиса реализует ситуацию, когда услугу выполняет не один, а несколько серверов, представляемых клиенту как единое целое. Т.е. сервер имеет сложную структуру.
Данный вариант хорош для критичных сервисов, когда недопустима приостановка обслуживания. Для прекращения обслуживания необходима остановка всех серверов системы. Кроме того, такая система позволяет сбалансированно распределять нагрузку между серверами. Таким, образом, может быть повышена как производительность, так и устойчивость к сбоям.
С другой стороны такая модель требует более сложной реализации по сравнению с базовой, и могут возникнуть проблемы с репликацией обрабатываемых на нескольких серверах данных или с поддержанием целостности распределённых данных.
1.1.2. Технология Proxy
Примером данной модели является привычная для нас структура: браузер – proxy-сервер — web-сервер.
Отличие от предыдущих моделей заключается в том, что клиент соединяется не с сервером, а с некоторым промежуточным компонентом (посредником).
Посредник может сам решать, какому из серверов передать запрос клиента (можно с его помощью распределять нагрузку). Кроме того, посредник может сохранять последние запросы клиента, чтобы при следующем обращении вернуть ему ответ, не обращаясь к серверу.
Недостаток: при неправильном построении посредник может стать узким местом системы в целом.
При работе в интернет чаще всего используется именно такой подход.
1.2 Мобильные клиенты
Идея рассматриваемой модели состоит в том, что зачастую, как это ни парадоксально, клиент сам в состоянии выполнить ту задачу, решение которой он запросил у сервера, более того, данные, необходимые для решения этой задачи, располагаются на клиенте. В таком случае для разгрузки сервера (и очень часто — для снижения сетевого трафика) целесообразно решать эту задачу на клиенте. Но как это сделать, если у клиента нет соответствующего программного модуля, содержащего необходимую функциональность? Ответ таков — этот модуль нужно клиенту отправить. Клиент, получив модуль (этот модуль называется мобильным агентом), может выполнить его локально, решив таким образом задачу.
В качестве примера можно рассмотреть взаимодействие браузера и веб-сервера, возвращающего страницу, которая содержит апплет. Апплет также передается клиенту и выполняется в браузере (т.е. на клиенте), выполняя какие-то значимые для пользователя действия. Основная проблема для такого подхода состоит в сложности реализации механизма передачи и выполнения мобильных агентов, а также контроля безопасности. Однако, современные средства middleware1 (Java, например) такими возможностями обладают.
1.3 Тонкий клиент
В течение нескольких последних лет наблюдается постоянное увеличение количества применяемых портативных устройств — сотовых телефонов, PDA, планшетов и т.д. Возникает естественное желание использовать такие устройства как средства для работы с информационными системами (например, зайти на wap-сайт туристического агентства и заказать путевку прямо с сотового телефона). Однако, интерфейс, предоставляемый браузерами, весьма ограничен. С другой стороны, в силу ограниченной мощности мобильных устройств в них пока не удается размещать приложения со сложной бизнес-логикой.
Решить эту проблему можно, используя технологию «тонкого клиента». Суть этой технологии состоит в том, что клиент выполняет очень ограниченную по функционалу задачу (часто — только прием ввода с клавиатуры и других устройств и обработка команд рисования). Схема работы подобных систем в простейшем случае следующая. Клиентская программа передает весь ввод пользователя (нажатия клавиш, движение мыши и т.д.) по сети серверу.
Таким образом, сервер фактически берет на себя не только задачу управления данными, но и вообще все задачи по логике клиентского интерфейса.
1.2 Архитектура P2P (Peer to Peer)
Другой архитектурой, встречающейся, в основном, в специальных областях, является архитектура P2P. Приложение, выполненное в такой архитектуре, не имеет четкого разделения на серверные и клиентские модули — все его части равноправны и могут выполняться на любых узлах.
Таким образом, на одном и том же узле в один момент выполняются части системы, обрабатывающие запросы других частей (и узел выполняет «серверную» часть), а в другой момент времени — части системы, посылающие запросы (и узел выполняет «клиентскую» часть). Причем приложение может быть устроено так, что вызывающая часть не знает, локально или удаленно расположена вызываемая.
Построенные таким образом приложения обладают уникальными свойствами — их части никак не привязаны друг к другу и к узлам, на которых они исполняются. Таким образом, от запуска к запуску может меняться состав модулей, расположенных на узле. Это позволяет организовывать очень изощренные политики распределения нагрузки, а также обеспечивать очень хорошие показатели масштабируемости и отказоустойчивости.
В отличие от архитектуры клиент-сервер, такая организация позволяет сохранять работоспособность сети при любом количестве и любом сочетании доступных узлов.
Такая архитектура активно используется для разработки параллельных вычислительных систем для решения сложных вычислительных задач.
Помимо чистых P2P-сетей, существуют так называемые гибридные сети, в которых существуют серверы, используемые для координации работы, поиска или предоставления информации о существующих машинах сети и их статусе (on-line, off-line и т. д.). Гибридные сети сочетают скорость централизованных сетей и надёжность децентрализованных благодаря гибридным схемам с независимыми индексационными серверами, синхронизирующими информацию между собой. При выходе из строя одного или нескольких серверов, сеть продолжает функционировать. К частично децентрализованным файлообменным сетям относятся, например EDonkey, BitTorrent.
Распределенные приложения (ĐApps)
Что такое распределенные приложения (ĐApps)
Распределенные приложения (ĐApps) – это программные приложения, которые хранятся и выполняются в основном на платформах облачных вычислений и запускаются одновременно в нескольких системах. Эти распределенные системы работают в одной сети и обмениваются данными друг с другом, чтобы выполнить определенную задачу или команду – в отличие от традиционного приложения, которое использует одну выделенную систему для выполнения поставленной задачи.
Ключевые выводы
Общие сведения о распределенных приложениях (ĐApps)
ĐПриложение предназначено для того, чтобы пользователи сети могли сотрудничать и обмениваться идеями, координировать выполнение задач, получать доступ к информации и обмениваться приложениями через сервер. В основном они используются в сетях клиент-сервер, где компьютер пользователя получает доступ к информации с сервера или сервера облачных вычислений.
Различные компьютерные системы, распределенные по сети, обычно имеют одинаковые или разные цели. Например, в платформе электронной коммерции каждый компьютер может отвечать за определенные задачи, такие как:
Каждая из этих задач будет выполняться одной или несколькими системами в сети, но все системы взаимодействуют друг с другом, чтобы гарантировать, что клиент покупает и получает продукт, который им выгоден.
Краткий обзор
Популярная платформа потоковой передачи музыки Spotify – это децентрализованное приложение, которое использует хранилище и вычислительную мощность пользователя для эффективной работы с малой задержкой.
Пример распределенных приложений (ĐApps)
Компании финансового сектора постоянно ищут новые способы включения incorpoприложений в свои рабочие процессы через прозрачности операций фирмы, чтобы соответствовать строгим требованиям финансовых регуляторов.
Другие причины, почему фирма в финансовом секторе может потребоваться интегрировать ĐApps включают сокращение числа посредников, участвующих в финансовых операциях, предоставляя клиентам доступ к cryptocurrencies, и обеспечение доступа к таким группам, как равный-равному (P2P) кредитные группы. Đ Приложения также могут использоваться для улучшения проверки исторических транзакций, упрощения процессов AML и KYC, улучшения торгового финансирования и финансирования цепочки поставок, а также обеспечения открытого банковского обслуживания.
Блокчейны и распределенные приложения (ĐApps)
В криптоэкономике блокчейн, используемый большинством криптовалют, использует «приложения» для поддержания эффективного цифрового рынка. В отличие от традиционной сети клиент-сервер, принятой в большинстве централизованных организаций, блокчейны работают в одноранговой сети, где транзакционная информация, передаваемая между двумя сторонами, записывается и совместно используется несколькими компьютерами в сети. Эти компьютеры, называемые узлами, действуют как администраторы на рынках биткойнов и добровольно присоединяются к сети, чтобы получить возможность получать биткойны в качестве вознаграждения.
У каждого узла есть дублирующая копия исходной транзакции, которая постоянно согласовывается сетью. Таким образом, какая бы запись у узла A ни была для биткойн-транзакции между Джейн и Джоном, не может отличаться от того, что есть у узлов B, C, D, E и F. Это средство проверки каждой транзакции несколькими узлами называется распределенными реестрами.
Поскольку версию событий можно проверить на разных компьютерах, хакеру потребуется проникнуть во все системы, разбросанные по разным географическим точкам, чтобы настроить транзакцию и повредить записанные данные. Это невозможно, что делает блокчейн биткойнов прозрачным и неподкупным.
Кроме того, сохраняя блоки информации на различных узлах сети блокчейнов, блокчейн не может быть разрушен из-за отказа одной системы. Когда компьютер или система выходит из строя, другие системы действуют как резервные копии и продолжают работать независимо от того, что система вышла из строя. После того, как все активные узлы получили и подтвердили, что транзакция действительна, блок (то есть транзакция) добавляется в цепочку – главную книгу – для общего доступа. Способность всех узлов продолжать функционировать, даже когда один или два узла выпадают из сети, гарантирует, что пользователи постоянно записывают и подтверждают свои транзакции непрерывно и своевременно.
Эволюция распределённых систем в Kubernetes
Фото Cenk Batuhan Özaltun, Unsplash.com
Обзор
На конференции QCon в марте я рассказывал об эволюции распределённых систем в Kubernetes. Главный вопрос: что будет после микросервисов? У вас наверняка есть мнение на этот счет. У меня оно тоже есть. В конце вы узнаете, что я думаю. Но для начала давайте обсудим потребности распределённых систем. Как эти потребности развиваются с годами, начиная с монолитных приложений до Kubernetes и таких новых проектов, как Dapr, Istio, Knative, и как они меняют наши методы работы с распределёнными системами. Попробуем сделать несколько прогнозов на будущее.
Современные распределённые приложения
Что я называю распределёнными системами? Это системы, состоящие из сотен компонентов. Эти компоненты могут быть stateful, stateless или бессерверными. Более того, эти компоненты можно создавать на разных языках в гибридных средах, с помощью опенсорс-технологий, открытых стандартов и интероперабельности. Вы можете создавать такие системы с помощью коммерческого ПО, в AWS и в других местах. Здесь мы поговорим об экосистеме Kubernetes и о том, как создать такую систему на платформе Kubernetes.
Начнем с потребностей распределённых систем. Допустим, мы хотим создать приложение или сервис и написать бизнес-логику. Что еще нам нужно от платформы и от рантайма, чтобы создать распределённую систему? Для начала нам нужны способы управлять жизненным циклом. Вот мы написали приложение на каком-то языке, а теперь хотим упаковать и задеплоить его, делать откаты и проверки работоспособности. Еще мы хотим разместить приложение на разных нодах, изолировать ресурсы, масштабировать их, управлять конфигурацией и все в таком духе. Это первое, что нам нужно для создания распределённого приложения.
Второе — это взаимодействие. Наше приложение должно надежно подключаться к другим сервисам в кластере или во внешнем мире. Нам нужны такие возможности, как обнаружение сервисов (service discovery) или балансировка нагрузки. Мы хотим перераспределять трафик для разных стратегий релизов или по другим причинам. Еще нам нужно устойчивое взаимодействие с другими системами с помощью повторных попыток, таймаутов и circuit breaker. Не забудем и про безопасность, а еще адекватный мониторинг, трейсинг, наблюдаемость и тому подобное.
Наладив сетевые соединения, мы переходим к привязке ресурсов и связываемся с разными API и эндпоинтами, чтобы использовать другие протоколы и разные форматы данных. Может быть, мы даже хотим преобразовывать один формат данных в другой. Я бы еще включил сюда фильтрацию, чтобы можно было подписаться только на определённые события в теме.
Последняя категория — состояние. Здесь я не имею в виду управление состоянием, как в базе данных или файловой системе. Речь об абстракциях, которые используют состояние. Например, мы хотим управлять рабочим процессом, какими-то долговременными процессами, хотим что-то запланировать или выполнять задания по расписанию для периодического запуска сервисов. Еще нам может понадобиться распределённое кэширование, идемпотентность или возможность откатов. Все это примитивы уровня разработки, которые зависят от состояния. Для создания надёжных распределённых систем нам обязательно понадобятся эти абстракции.
На примере этих примитивов посмотрим, что меняется в Kubernetes и других проектах.
Монолитная архитектура — традиционные возможности промежуточного ПО
Начнём с монолитной архитектуры. В контексте распредёленных приложений первое, что приходит на ум, — ESB (Enterprise Service Bus, сервисная шина данных). Это довольно эффективный инструмент, который прекрасно поддерживает все stateful-абстракции из нашего списка требований.
С ESB можно оркестрировать долгосрочные процессы, выполнять распределённые транзакции и откаты и гарантировать идемпотентность. Более того, ESB предлагает широкие возможности привязки ресурсов и сотни коннекторов, а еще поддерживает преобразование, оркестрацию и даже сетевые функции. И, наконец, ESB может взять на себя service discovery и балансировку нагрузки.
Что касается изоляции, то в монолитной архитектуре невозможно изолировать ни ресурс, ни сбой. Выходит, нашему списку требований монолитная архитектура ESB не отвечает.
Облачная архитектура — микросервисы и Kubernetes
Давайте теперь обратимся к облачной архитектуре и связанным с ней изменениями. Если смотреть в общих чертах, изменения в нативной облачной архитектуре, пожалуй, начались с микросервисов. Микросервисы позволяют разделить монолитное приложение на бизнес-домены. Оказалось, что контейнеры и Kubernetes отлично подходят для управления микросервисами. Какие фичи и возможности делают Kubernetes привлекательной платформой для микросервисов?
Изначально Kubernetes стал популярен благодаря пробам работоспособности (health probe). На практике это означает, что вы деплоите контейнер в под, а Kubernetes следит за работоспособностью процесса. Обычно такая модель недостаточно хороша. Процесс может выполняться, но при этом не быть работоспособным. Здесь пригодятся проверки readiness и liveness. Kubernetes выполняет readiness-проверку, чтобы решить, когда приложение будет готово принимать трафик во время запуска. Liveness-проба непрерывно проверяет работоспособность сервиса. До Kubernetes это было не очень популярно, зато сегодня почти все языки, фреймворки и рантаймы проводят проверки работоспособности для быстрого запуска конечного устройства.
Также Kubernetes предложил управление жизненным циклом приложения, то есть нам больше не нужно самим контролировать запуск и остановку сервиса. Kubernetes запускает приложение, останавливает его, переносит по разным нодам. Чтобы вся эта система работала, нужно правильно настроить события при запуске и остановке.
Еще одна причина популярности Kubernetes — декларативные развёртывания. Нам больше не нужно запускать сервисы и проверять логи, чтобы узнать, запущены ли они. Нам не нужно апгрейдить инстансы вручную. За все отвечает декларативное развёртывание в Kubernetes. В зависимости от выбранной стратегии оно останавливает старые инстансы и запускает новые. А если что-то пошло не так, само делает откат.
Еще одно преимущество — объявление требований для ресурсов. При создании сервиса мы упаковываем его в контейнер и говорим платформе, сколько ЦП и памяти ему понадобится. На основе этой информации Kubernetes подбирает самую подходящую ноду для ваших рабочих нагрузок. Раньше приходилось вручную размещать инстанс на ноде, опираясь на наши критерии. Теперь мы сообщаем Kubernetes наши предпочтения, и он все решает за нас.
В Kubernetes можно управлять конфигурациями на разных языках. Нам не нужно проверять конфигурацию в рантайме приложения — Kubernetes проследит, чтобы настройки оказались на той же ноде, что и рабочая нагрузка. Настройки для приложения подключаются как файлы в томе или переменные окружения.
Оказывается, что эти возможности, о которых я говорил, тоже связаны. Например, нам нужно выполнить автоматическое размещение, и мы сообщаем Kubernetes требования к ресурсам для сервиса и желаемую стратегию развёртывания. Чтобы стратегия работала правильно, приложение должно уметь обрабатывать события, поступающие из окружения, и проверки работоспособности. Если следовать всем рекомендациям и использовать все доступные возможности, мы получим приложение, которое идеально вписывается в облако и готово для автоматизации в Kubernetes. Это — базовые паттерны выполнения рабочих нагрузок в Kubernetes. Кроме того, существуют паттерны, связанные со структурированием контейнеров в поде, управлением конфигурацией и поведением.
Перейдем к рабочим нагрузкам. В рамках жизненного цикла мы хотим выполнять разные рабочие нагрузки. В Kubernetes это возможно. Запускать 12-факторные приложения (Twelve-Factor App) и stateless-микросервисы довольно просто, Kubernetes справится с этим без проблем. Но у нас будут и другие рабочие нагрузки, в том числе stateful, — для них в Kubernetes используется stateful set.
Или это может быть singleton. Допустим, мы хотим, чтобы инстанс приложения был единственным на всем кластере, и это должен быть надежный singleton. При сбое он должен перезапускаться. В этом случае мы выбираем между stateful set и replica set в зависимости от потребностей и семантики — at least one (не менее одного раза) или at most one (не более одного раза). Kubernetes также поддерживает job и cron job.
В целом, Kubernetes покрывает все наши потребности в отношении жизненного цикла. Обычно мой список требований основан на том, что сейчас предоставляет Kubernetes. Это возможности, которые можно ожидать от любой платформы, — управление конфигурацией, изоляция ресурсов и изоляция сбоев. Кроме того, Kubernetes поддерживает разные рабочие нагрузки, кроме бессерверных (без дополнительных инструментов).
Если это все, что дает Kubernetes, как его расширить? Как получить больше функций? Есть два распространенных способа.
Механизм расширения out-of-process
Под — это абстракция, которую мы используем, чтобы деплоить контейнеры на нодах. Под дает две гарантии:
Для init-контейнеров и контейнеров приложения предоставляются разные гарантии. Например, init-контейнеры выполняются в начале, один за другим, строго после выполнения предыдущего контейнера. Они помогают реализовать логику рабочего процесса.
Контейнеры приложений выполняются параллельно на протяжении жизненного цикла пода. На этом основаны sidecar-контейнеры, которые могут запускать несколько контейнеров, совместно выполняющих ту или иную задачу для пользователя. Это один из основных механизмов добавления функционала в Kubernetes.
Чтобы понять, как работает следующая возможность, нужно вспомнить о внутренних механизмах Kubernetes. Все основано на цикле согласования (reconciliation loop), который воплощает желаемое состояние в фактическое. В Kubernetes от этого многое зависит. Допустим, мы объявляем, что нам нужно два инстанса пода. Это желаемое состояние системы. Цикл контроля (control loop) постоянно проверяет, есть ли у пода два инстанса. Если их не два, а больше или меньше, он вычисляет разницу. Его задача — гарантировать, что их будет ровно два.
Таких примеров много. Допустим, replica set или stateful set. Определение ресурса сопоставляется с контроллером, а контроллеры есть почти на все случаи жизни. Контроллер следит за тем, чтобы реальность соответствовала нашим желанием, причем контроллер можно даже написать самому.
Например, при выполнении приложения в поде нельзя загрузить файл конфигурации с изменениями в реальном времени. Зато можно написать кастомный контроллер, который будет обнаруживать эти изменения и в соответствии с ними перезапускать под и приложение.
Хотя в Kubernetes достаточно ресурсов, они удовлетворяют не все наши потребности. Поэтому в Kubernetes появилось такое понятие, как кастомные определения ресурсов (custom resource definitions, CRD). То есть мы можем изложить свои требования и определить API, который будет сосуществовать с нативными ресурсами Kubernetes. Контроллер можно написать на любом языке, который сможет описать нашу модель. Можно, например, создать ConfigWatcher, реализованный на Java, который всё это описывает. Это и будет оператор — контроллер, который работает с кастомными определениями ресурсов. Сегодня появляется много операторов, и это второй способ расширения возможностей Kubernetes.
Теперь давайте поговорим о платформах поверх Kubernetes, которые активно используют sidecar-контейнеры и операторы, чтобы предоставлять дополнительные возможности.
Что такое service mesh?
Начнем с service mesh.
Допустим, у нас есть два сервиса. Сервис A (на любом языке) вызывает сервис B. Допустим, это рабочая нагрузка нашего приложения. Service mesh использует sidecar-контроллеры и внедряет прокси рядом с сервисом. В итоге мы получаем в поде два контейнера. Прокси прозрачный, и приложение не догадывается о его существовании, при этом он перехватывает весь входящий и исходящий трафик. Прокси также работает как файрвол.
Эти маленькие stateless-прокси составляют плоскость данных (data plane). Состояние и конфигурацию они получают от плоскости управления (control plane). Плоскость управления отслеживает состояния. Она хранит все конфигурации, собирает метрики, принимает решения и взаимодействует с плоскостью данных. Нам нужен еще один компонент — шлюз API (API gateway), через который данные попадают в кластер. У некоторых service mesh есть свой шлюз API, другие используют сторонний. Все эти компоненты предоставляют нам нужные возможности.
Шлюз API абстрагирует реализацию наших сервисов, скрывая детали и устанавливая границы. У service mesh противоположная задача. В каком-то смысле она повышает видимость и надежность сервисов. Вместе шлюз API и service mesh удовлетворяют все наши сетевые потребности. Одних сервисов будет недостаточно, нужна service mesh.
Что такое Knative?
Давайте теперь поговорим о проекте Knative, запущенном Google несколько лет назад. Это слой поверх Kubernetes, который предоставляет бессерверные вычисления и состоит из двух модулей:
Как работает Knative Serving? С Knative Serving мы определяем сервис, но не как в Kubernetes. Тут свои особенности. Когда мы определяем рабочую нагрузку с сервисом Knative, мы используем бессерверные вычисления. Нам не нужно запускать инстанс. Его можно запустить с нуля при поступлении запроса. Масштаб бессерверных вычислений можно быстро увеличивать или уменьшать до нуля.
Knative Eventing предоставляет полностью декларативную систему управления событиями. Допустим, мы хотим интегрировать внешние системы, которые создают события. Внизу на схеме находится наше приложение в контейнере с HTTP endpoint. С Knative Eventing мы можем запустить брокер. Это может быть Kafka, процесс в памяти или облачный сервис. Еще можно запустить импортеры, которые подключатся к внешней системе и будут импортировать события в наш брокер. Эти импортеры могут быть основаны, например, на Apache Camel с сотнями коннекторов.
Когда события будут поступать в брокер, мы можем подписать на них контейнер декларативно при помощи yaml-файла. В самом контейнере нам не нужны клиенты обмена сообщениями, вроде Kafka. Он будет получать события через HTTP POST с помощью облачных событий. Эта инфраструктура обмена сообщениями полностью управляется платформой. Разработчик просто пишет в контейнере бизнес-код и не занимается логикой сообщений.
Knative удовлетворяет несколько потребностей из нашего списка. С точки зрения жизненного цикла он предоставляет бессерверные вычисления нашим рабочим нагрузкам, чтобы они масштабировались до нуля или на увеличение. С точки зрения сетей, если будет какое-то перекрытие с service mesh, Knative может перераспределять трафик. Что касается привязок, они поддерживаются импортерами Knative. Мы можем использовать шаблон Pub/Sub («издатель-подписчик»), взаимодействие «точка-точка» или даже последовательности. Это удовлетворяет наши требования в нескольких категориях.
Что такое Dapr?
Еще один проект, который использует sidecar и операторы, — Dapr. Microsoft запустила его всего несколько месяцев назад, но он быстро набирает популярность. Более того, версия 1.0 считается готовой для продакшена (прим. переводчика: в конце мая 2021 вышла версия 1.2.0). В Dapr все фичи предоставляются как sidecar. Он предлагает так называемые «стандартные блоки», или наборы возможностей.
Что это за возможности? Первый набор связан с сетями. Dapr может обнаруживать сервисы и устанавливать прямую интеграцию между ними. Он выполняет трейсинг, устанавливает надежное взаимодействие, обеспечивает повторные попытки и восстановление, как service mesh. Второй набор связан с привязкой ресурсов:
Что интересно, в Dapr есть управление состояниями, которое дополняет Knative и service mesh. С Dapr можно взаимодействовать с помощью пар «ключ-значение» на основе механизма хранения.
В общих чертах архитектура выглядит так. Сверху у нас приложение на любом языке. Можно использовать клиентские библиотеки от Dapr, но это не обязательно. Можно использовать языковые функции, чтобы делать HTTP- и gRPC-вызовы к sidecar. Отличие от service mesh в том, что Dapr sidecar — это не прозрачный прокси. Его нужно вызывать из приложения и с ним нужно взаимодействовать по HTTP или gRPC. В зависимости от наших потребностей Dapr может общаться с другими системами, например, облачными сервисами.
В Kubernetes Dapr деплоится как sidecar и может работать не только с Kubernetes. Более того, у него есть оператор (один из двух основных механизмов расширения). Другие компоненты управляют сертификатами, работают с моделированием на основе акторов и внедряют sidecar. Наша рабочая нагрузка взаимодействует с sidecar, который обеспечивает взаимодействие с другими сервисами, а мы получаем интероперабельность с разными облачными провайдерами.
Кроме того, в нашем распоряжении дополнительные возможности распределенной системы.
Если подытожить преимущества этих проектов, можно назвать ESB ранним воплощением распределенных систем, где у нас есть централизованная плоскость управления и плоскость данных, но нет удобного масштабирования. В облачных проектах централизованная плоскость управления еще присутствует, а вот плоскость данных децентрализована, легко масштабируется и предлагает приемлемую изоляцию.
Нам все равно нужен Kubernetes для управления жизненным циклом, но поверх него можно реализовать что-то еще. Например, Istio для расширенного взаимодействия. Knative может отвечать за бессерверные рабочие нагрузки, а Dapr — за интеграцию. Они прекрасно сочетаются с Istio и Envoy. Из Dapr и Knative можно выбрать что-то одно, но вместе они дают то, что было у нас в ESB, но в облаке.
Будущие облачные тренды — жизненный цикл
Я составил субъективный список из нескольких проектов, от которых я многого жду.
Начнем с жизненного цикла. В Kubernetes мы можем контролировать жизненный цикл приложения, но этого не хватит для более сложных сценариев, например, где примитивов развертывания Kubernetes недостаточно для сложного stateful-приложения.
В этих случаях можно использовать операторы, например, оператор, который отвечает за развертывание и апгрейд и, допустим, хранение сервиса в S3. Механизм проверки работоспособности в Kubernetes может оказаться для нас недостаточно эффективным. Допустим, нам мало проверок liveness и readiness. В этом случае можно использовать оператор для усовершенствованных проверок приложения, и на их основе выполнять восстановление.
Третий пример — автоскейлинг и тюнинг. Мы можем использовать оператор, который будет лучше понимать наше приложение и выполнять автотюнинг на платформе. На сегодня есть два основных фреймворка для написания операторов — Kubebuilder, от группы SIG Kubernetes, и Operator SDK, который входит в Operator Framework от Red Hat.
С помощью Operator SDK можно написать оператор, Operator Lifecycle Manager позволяет управлять его жизненным циклом, а в OperatorHub его можно опубликовать. Сейчас там больше 100 операторов для управления базами данных, очередями сообщений, инструментами мониторинга и т. д. В плане жизненного цикла именно в области операторов сейчас происходит самое активное развитие в экосистеме Kubernetes.
Тренды в сфере взаимодействия — Envoy
Еще один проект в моем списке — Envoy. С помощью спецификации интерфейсов service mesh нам будет проще переключаться между разными реализациями service mesh. Благодаря консолидации деплоев в Istio нам не придется деплоить семь подов на плоскости управления, достаточно одного развертывания. Гораздо интереснее, что происходит в проекте Envoy на плоскости данных. Как видим, туда добавляется все больше и больше протоколов L7.
Service mesh теперь поддерживает MongoDB, ZooKeeper, MySQL, Redis, а с недавнего времени еще и Kafka. Сообщество Kafka совершенствует свой протокол, чтобы он лучше работал с service mesh. Можно ожидать еще более тесную интеграцию и еще больше возможностей. Скорее всего, появится какая-то возможность бриджинга. Допустим, в сервисе мы делаем HTTP-вызов локально, из приложения, а прокси будет использовать Kafka. Мы можем выполнять преобразование и шифрование за пределами приложения, в sidecar для протокола Kafka.
Еще одно интересное нововведение — HTTP-кэширование, на которое теперь способен Envoy. Нам не нужно использовать клиенты кэширования в приложении, все это делается прозрачно в sidecar. Там есть фильтры перехвата, так что мы можем перехватывать и копировать трафик. Благодаря недавно появившемуся WebAssembly мы можем написать кастомный фильтр для Envoy, и при этом нам не придется использовать C++, а потом компилировать весь рантайм Envoy. Мы можем написать фильтр в WebAssembly, а потом задеплоить его в рантайме. Большинство из этих возможностей пока в разработке, но уже понятно, что плоскость данных и service mesh не остановятся на поддержке только HTTP и gRPC. Они будут поддерживать и другие протоколы на уровне приложения, чтобы у нас было больше возможностей и доступных сценариев использования. Например, с появлением WebAssembly мы можем писать кастомную логику в sidecar. Только бизнес-логику туда вставлять не надо.
Тренды в сфере привязок — Apache Camel
Apache Camel — это проект для интеграций, который предлагает множество коннекторов к разным системам с помощью шаблонов интеграции корпоративных приложений. Camel версии 3, например, глубоко интегрирован в Kubernetes и использует примитивы, о которых мы уже говорили, например операторы.
Мы можем написать логику интеграции в Camel на Java, JavaScript или YAML. В последней версии представлен оператор Camel, который выполняется в Kubernetes и понимает нашу интеграцию. Допустим, мы пишем приложение Camel и деплоим его в кастомном ресурсе, а оператор сам знает, как собрать контейнер или найти зависимости. В зависимости от возможностей нашей платформы (просто Kubernetes или Kubernetes с Knative) он решает, какие сервисы использовать и как реализовать нужную интеграцию. За пределами рантайма, в операторе, происходит много всего интересного, причем очень быстро. Почему я называю это трендом в сфере привязок? В основном, благодаря возможностям Apache Camel со всеми его коннекторами, причем все это тесно интегрировано в Kubernetes.
Тренды в сфере состояний — Cloudstate
Рассмотрим еще один проект — Cloudstate, который отвечает за состояния. Cloudstate от Lightbend ориентирован на бессерверную разработку на основе функций. Последние релизы тесно интегрированы с Kubernetes с помощью sidecar и операторов.
Идея в том, что когда мы пишем функцию, нам достаточно использовать gRPC, чтобы получить состояние и взаимодействовать с ним. Всё управление состояниями происходит в sidecar, который объединен с другими sidecar. Это позволяет нам создавать события, использовать CQRS, искать пары «ключ-значение» и передавать сообщения.
Приложение не вникает во все эти сложности — оно просто делает вызов к локальному sidecar, который обо всём позаботится. Sidecar может использовать два источника данных. У него есть все stateful-абстракции, которые понадобятся разработчику.
Итак, мы рассмотрели текущее состояние облачной экосистемы и последние тренды. Что всё это значит?
Микросервисы в нескольких рантаймах уже близко
Если мы используем микросервисы в Kubernetes, нам нужен разный функционал. Например, Kubernetes должен управлять жизненным циклом. Скорее всего, нам понадобится прозрачная service mesh, что-то вроде Envoy, для взаимодействия, допустим, для маршрутизации трафика, обеспечения устойчивости, дополнительной безопасности или хотя бы для мониторинга. Поверх, в зависимости от сценария и рабочих нагрузок, можно использовать Dapr или Knative. Всё это доступные нам дополнительные возможности, которые предоставляются вне процесса. Нам остаётся только написать бизнес-логику в отдельном рантайме. Скорее всего, в будущем микросервисы будут представлять собой несколько рантаймов, состоящих из нескольких контейнеров. Некоторые из них будут прозрачными, другие мы будем использовать явно.
Умные sidecar, глупые каналы
Я представляю это так. Мы пишем бизнес-логику на любом высокоуровневом языке — это уже не обязательно должен быть Java, берите любой.
Любые взаимодействия между бизнес-логикой и внешним миром происходят через sidecar, который интегрирован в платформы, управляет жизненным циклом, абстрагирует подключения для внешней системы и предоставляет расширенные возможности привязки и абстракций состояния. Мы не пишем sidecar сами — просто берем готовый, немного корректируем YAML или JSON и используем. Это значит, что sidecar можно легко обновлять и исправлять, ведь он больше не входит в наш рантайм. Получаем рантайм для бизнес-логики на любом языке.
Что будет после микросервисов?
Возвращаемся к вопросу, который я задал в начале, — что будет после микросервисов?
Если вспомнить, как развивалась архитектура приложений, все началось с монолитов. Микросервисы показали принципы разделения монолитных приложений на бизнес-домены. Затем появились бессерверные архитектуры и функция как услуга (FaaS), когда мы поняли, что приложения можно делить на операции. Так мы получили широкие возможности масштабирования, ведь масштабировать можно каждую операцию по отдельности.
FaaS, пожалуй, не лучшая модель. С помощью функций непросто реализовать сложные сервисы, в которых несколько операций должны вместе взаимодействовать с одним набором данных. Скорее всего, это будет несколько рантаймов, я называю это меха-архитектурой, где бизнес-логика находится в одном контейнере, а все, что связано с инфраструктурой, — в другом. Вместе они представляют микросервис в нескольких рантаймах. Пожалуй, это более подходящая модель, потому что мы получаем все преимущества микросервисов, при этом вся бизнес-логика хранится в одном месте, а все требования к инфраструктуре и распределённой системе находятся в отдельном контейнере, и их можно объединять в рантайме. Наверное, ближе всего к такой архитектуре сейчас Dapr. Если вас больше интересует, как реализовать взаимодействие, Envoy тоже приближается к этой модели.