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

Вокруг концепции Cloud Native сложилась огромная, динамичная экосистема решений и продуктов, помогающих решить неизбежные проблемы распределенных асинхронных систем. Прежде всего это “сервисные сетки” (service mesh), такие как Istio и Linkerd. Смысл термина “service mesh” состоит в упорядочении и управлении сетевыми переговорами микросервисов, но слово “сеть” уже давно и прочно занято, и мы используем “сетку”, чтобы уменьшить путаницу. Сервисные сетки решают многие проблемы, дают возможность наблюдать за сетевыми вызовами, строить графы, получать задержки, и многое другое. Системы сбора метрик, такие как Prometheus, позволяют интегрировать метрики вашей системы в единые центры наблюдения (к примеру, на основе Grafana). Управление журналами, например Fluentd, справляется со сбором и упорядочением десятков разнообразных журналов, полученных от микросервисов.

Разработка на практике - 12 факторов облачного приложения

Теория и концепция Cloud Native, то есть приложений, созданных для облака, пока выглядит стройно и логично, и остальную часть книги мы посвятим практическому применению ее основных частей. Однако всегда интересно узнать “выжимку” опыта компаний, команд и программистов, которые уже попробовали разработку таких приложений, и увидели всю подноготную проблем, с которыми придется столкнуться - как мы знаем, в программировании многие неприятности скрываются именно в мелких деталях.

Команда облачного сервиса Heroku, популярного выбора для небольших команд и микросервисных систем, собрала свои наблюдения в каталог из 12 факторов (12 factor app), наличие которых в дизайне и реализации системы резко повышает его шансы на успешную работу в облаке и простую поддержку готовой системы. Отсутствие этих факторов, или, что хуже, выбор противоположных решений, может впоследствии усложнить разработку и развертывание облачного приложения. Давайте посмотрим на эти факторы, и увидим, как они соотносятся с положениями концепции Cloud Native.

1 - Единая база кода

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

2 - Явное описание и изоляция зависимостей

Облачное приложение ни в коем случае не должно рассчитывать, что на серверах кластера что-то будет доступно или предустановлено. Этот фактор отлично накладывается на рекомендуемый способ работы с контейнерами - вы всегда способны заново построить образ контейнера своего приложения (обычно с помощью Dockerfile), и он включает в себя все необходимое для работы - любые библиотеки JAR, пакеты Node.js, и так далее.

3 - Управление конфигурацией

Гибкое приложение избегает включения любых элементов конфигурации в свой исходный код - это пароли, адреса баз данных, даже порты микросервисов-партнеров. Большую помощь в реализации этого фактора оказывает Kubernetes. Он, хоть и не может вас заставить вынести всю конфигурацию в переменные окружения (environment variables), предоставляет удобные инструменты, такие как “секреты” (secrets) и карты конфигурации (config maps). Они описываются декларативно, в файлах YAML. Меняя карты конфигурации, вы с легкостью можете развернуть свою систему в совершенно другом окружении, например, для отладки, или у нового клиента на его собственных серверах.

4 - Вспомогательные ресурсы через конфигурацию

Система, использующая дополнительные внешние системы и ресурсы, такие как базы данных, хранилища неструктурированных данных, почтовые и СМС-сервисы, в идеале максимально абстрагирует свои связи с ними. Выделение точного интерфейса для работы с ними, и вынесение в переменные окружения всех параметров для доступа и соединения с такими ресурсами поможет системе уменьшить количество зависимостей и легкость работы в разнообразных облаках и окружениях. Вновь, карты конфигурации Kubernetes отлично справятся. Для более сложных случаев можно описать ресурс в виде нестандартного объекта Kubernetes (CRD, custom resource definition).

5 - Строгое разделение построения и запуска системы

Система не должна запускаться из непроверенных изменений в коде или конфигурации. Собранная с помощью постоянной сборки (CI, continuous integration) система помечается версией или меткой (tag), все собранные бинарные и конфигурационные файлы доступны для перезапуска в случае проблемы. Этот фактор прекрасно обеспечивают образы (image) контейнеров - они неизменны после сборки, вы знаете историю версий в репозитории образов (к примеру Docker Hub), и можете строго воспроизвести любое состояние системы, не откатывая для этого никаких изменений в коде.

6 - Сервисы без состояния

Микросервисы облачного приложения в идеале не обладают вообще никаким состоянием и стараются не хранить никаких промежуточных результатов для выдачи другим серверам (stateless, share-nothing). Это позволяет добиться легкой масштабируемости и восстановления системы. Необходимо рассчитывать на динамичность облака и то, что любой сервер или диск может быть перезапущен в любую минуту. Данные должны храниться в специализированных сервисах для данных, обычно управляемых облаком - облачных базах данных (Cloud SQL, Amazon RDS), кэшах Memcached, и других. Как мы увидим, именно микросервисы без состояния намного проще создавать с помощью Docker и управлять Kubernetes.

7 - Доступ через сетевые порты

Микросервисы общаются через сетевые порты, обычно с помощью HTTP в стиле REST, отсылая данные в формате JSON или XML, или используют бинарный протокол gRPC. Если микросервис вызывает другие микросервисы-партнеры, адреса доступа к ним и их порты хранятся отдельно, в конфигурации, или с помощью системы обнаружения сервисов (service discovery). Данное требование идеально исполняется контейнерами, которые объявляют, по какому порту они будут ожидать соединений, и сервисами (service) Kubernetes, описывающими, как эти порты будут доступны в кластере. Kubernetes обеспечивает обнаружение сервисов с помощью простых имен DNS. Все необходимое для работы HTTP сервера находится внутри контейнера (встроенные сервера, например Netty для Java).

8 - Масштабирование через запуск дополнительных экземпляров

Концепция микросервисов позволяет снизить количество ресурсов для четко выделенного компонента системы, и провести его точечное горизонтальное масштабирование - это возможно только в случае микросервисов без состояния (фактор 6). Kubernetes делает масштабирование, в том числе автоматическое в зависимости от загрузки системы, тривиальной задачей, и поддерживает желаемое количество экземпляров микросервиса.

9 - Быстрый запуск и надежная остановка

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

10 - Одинаковая среда разработки и эксплуатации

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

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

Хотя контейнеры и Kubernetes не смогут автоматически предоставить вам идентичные среды, они сделают это намного проще, благодаря неизменности образов контейнеров, работающих в системе, и легкой переносимости конфигураций YAML. Следование факторам 2, 3, 4 и 6 также сделает создание идентичной среды разработки проще. Более того, если среды абсолютно идентичны, то любой член команды сможет выполнить развертывание, приближая команду к DevOps.

11 - Журналы logs в виде потока событий

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

В облачном приложении журналы не сохраняются и не обрабатываются. Все записи делаются в стандартный вывод (standard output), тот самый, что выводится в терминал при ручном запуске. Именно стандартный вывод используется в контейнерах и Kubernetes. Дополнительные решения (ELK - ElasticSearch + Logstash + Kibana, или Fluentd), работающие под управлением Kubernetes, собирают журналы с различных микросервисов, форматируют, хранят их, и предоставляют инструменты для полного анализа.

12 - Администрирование как часть приложения

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

Резюме

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

comments
comments powered by Disqus