Методология 12 факторов: как успешно разрабатывать облачные приложения
Методология 12 факторов: как успешно разрабатывать облачные приложения
Один из разделов большого курса про архитектуру ПО, который я веду, посвящен так называемой методологии 12 факторов. Она была придумана разработчиками американской компании Heroku и, если верить Википедии, впервые описана примерно в 2011 году. На Западе методология быстро завоевала поддержку ИТ-сообщества и стала отраслевым стандартом при разработке веб-приложений по модели SaaS (Software as a Service — программное обеспечение как услуга). В русскоязычной среде об этих фундаментальных принципах говорят редко, так что я решил исправить эту вселенскую несправедливость.
Привет, Хабр! Меня зовут Андрей Бирюков, я преподаватель-практик Учебного центра IBS и архитектор по разработке программного обеспечения. За 15 с лишним лет в ИТ я спроектировал и внедрил десятки инфраструктурных решений на базе ОС Linux, Windows, работал с системами виртуализации и контейнеризации, а также занимался комплексными проектами по защите корпоративных и промышленных сетей. В этой статье я расскажу о методологии, которая помогает разрабатывать более качественные, устойчивые и эффективные веб-приложения.
Облачные решения
Прежде чем переходить к описанию самих факторов, уточню, что данная методология предназначена для разработчиков и архитекторов облачных приложений по принципу «программное обеспечение как услуга», а также для инженеров, разворачивающих и обслуживающих такие приложения. При этом язык программирования, на котором пишется приложение, особого значения не имеет.ПО как услуга, или SaaS, — это модель обслуживания, при которой провайдер самостоятельно управляет всей инфраструктурой и программным обеспечением и предоставляет подписчикам доступ к функциям с клиентских устройств — как правило, через мобильное приложение или веб-браузер.
Главный плюс такой модели обслуживания заключается в том, что нам не нужно брать на себя ответственность за серверы и иметь соответствующих специалистов в штате — все это головная боль вендора.
Однако у облачных решений есть свои проблемы, а именно:
-
высокая сложность современных приложений: множество различных элементов, баз данных, менеджеров очередей и прочего;
-
проблема переносимости кода и конфигураций в различных средах: разработчики, тестировщики, безопасники — все работают в своих изолированных средах, и при переносах кода между ними могут всплывать непредвиденные ошибки;
-
взаимодействие со сторонними службами, например веб-сервисами или базами данных, к которым подключается разрабатываемое приложение;
-
болезненный процесс взращивания культуры DevOps в разработке и обслуживании приложения;
-
настройка мониторинга работы приложения;
-
сложность администрирования микросервисной архитектуры и человеческий фактор;
-
мультиарендность: множество процессов используют одно приложение.
12 факторов
А теперь перейдем непосредственно к двенадцати факторам успешной разработки облачных приложений.
Ниже я разберу каждый пункт подробно, но сначала просто дам список:
-
Кодовая база.
-
Зависимости.
-
Конфигурация.
-
Сторонние службы.
-
Сборка, релиз, выполнение.
-
Процессы.
-
Привязка портов.
-
Параллелизм.
-
Утилизируемость.
-
Паритет сред разработки и работы приложения.
-
Журналирование.
-
Задачи администрирования.
Фактор № 1. Кодовая база
Основной принцип: у нас должна быть одна кодовая база, отслеживаемая в системе контроля версий, и именно из этой базы мы должны выполнять все развертывания приложения по всем средам. В централизованных системах контроля версий типа Subversion кодовая база — это один репозиторий; в децентрализованных типа Git или Mercurial — множество репозиториев, имеющих общие начальные коммиты. Такой подход ускоряет процесс разработки и снимает проблему несоответствия коммитов, когда в одной среде раскатана одна версия приложения, а в другой — другая.Соответственно, для реализации подхода нам нужна одна из систем контроля версий, которая позволяет создавать различные ветки под те или иные фичи или патчи и впоследствии объединять их с основной веткой.
Фактор № 2. Зависимости
Реализация принципа:
-
В Python для объявления зависимостей используется Pip, а для их изоляции — Virtualenv.
-
Ruby использует Gemfile как формат манифеста для объявления зависимостей и bundle exec — для изоляции зависимостей.
-
В Docker — полная локализация контейнера, благодаря которой внутри контейнера можно использовать одни зависимости и утилиты, а снаружи — другие.
Фактор № 3. Конфигурация
Фактор № 4. Сторонние службы
Любое современное приложение использует сторонние службы — базы данных, веб-сервисы, менеджеры очередей, средства мониторинга, сервисы электронной почты для отправки уведомлений и прочие компоненты. Главный принцип при работе со сторонними службами, используемыми приложением, — осуществлять их подключение прозрачно, без внесения изменений в код. Для изменения настроек подключения нужно использовать такие технологии, как Secrets в Kubernetes, которые позволяют не хранить учетные данные в исходном коде, а подключать их либо в файл, либо в переменное окружение. Таким образом, конфиденциальная информация не будет храниться в открытом виде, а код останется неизменным. По сути, принцип здесь тот же, что и с конфигурацией: если мы хотим подключиться, например, к другой базе данных, то просто указываем соответствующие параметры в конфигурационных файлах.
Фактор № 5. Сборка, релиз, выполнение
Следующий важный принцип — строгое разделение этапов сборки, выпуска и выполнения релиза в продуктивной среде. Разработчики отдельно пишут код, к нему подтягиваются библиотеки, затем идет этап сборки, код проходит множество различных тестов; по результатам тестирования принимается решение, пускать ли сборку дальше или возвращать ее на доработку; успешная же сборка после финального ревью отправляется в релиз. При этом для работы в различных средах используются свои конфигурации, и все они хранятся отдельно.По сути, речь идет о конвейере CI/CD — Continuous Integration and Continuous Deployment — непрерывной автоматизированной интеграции и непрерывном развертывании. В Jenkins конвейер реализуется через отдельные задачи, которые выполняются в изолированных друг от друга средах.
Фактор № 6. Процессы
Основной принцип при работе с процессами — приложение не должно сохранять внутреннее состояние. Любые данные, которые требуется сохранить, должны быть сохранены в хранящей состояние сторонней службе, обычно — в базе данных, которая крутится где-то в облаке или вообще на «железе». Наш код при этом хранится в ряде идентичных контейнеров. В случае «падения» одного из них моментально будет запущен точно такой же. Таким образом, потеря конкретного контейнера никак не повлияет на работу приложения и не выльется в потерю ценных данных.
Фактор № 7. Привязка портов
Следующий важный момент — это взаимодействие компонентов приложения между собой. Старый подход, когда микросервисы общаются между собой с помощью развернутых на специальных томах файлов, неэффективен при больших нагрузках. Здесь гораздо лучше использовать сетевое взаимодействие, при котором приложение привязывается к определенному порту.При таком подходе приложение является полностью самодостаточным и экспортирует сервис посредством привязки портов. Нам не нужно волноваться, какой порт будет снаружи контейнера, — мы просто поменяем его внешними средствами с помощью соответствующих настроек Kubernetes, ничего не трогая в самом приложении.
Фактор № 8. Параллелизм
Приложение должно быть спроектировано таким образом, чтобы для обработки различной рабочей нагрузки можно было назначить каждому типу работы свой тип процесса. Например, в Docker мы можем указать, сколько экземпляров кода нам требуется в рамках конкретного развертывания, а при необходимости добавить больше экземпляров. На логическом уровне это просто реализовать средствами того же Kubernetes. У нас автоматически масштабируются экземпляры, и приложение продолжает работать. А далее мы уже можем перераспределить запросы между всеми этими экземплярами с помощью средств балансировки.
Фактор № 9. Утилизируемость
Мы должны быть готовы к тому, что процессы нашего приложения могут быть запущены и остановлены в любой момент. С запуском все просто: процессы должны минимизировать время запуска — для этого у нас есть микросервисная архитектура. В свою очередь, короткое время запуска предоставляет большую гибкость для релиза и масштабирования.
С остановкой чуть сложнее: приложение должно проектироваться так, чтобы обрабатывать неожиданные падения и некорректные выключения — то, что называется crash-only design. Процессы должны завершаться корректно, когда они получают SIGTERM-сигнал от диспетчера процессов. Внезапная остановка не должна приводить к тяжелым последствиям, когда мы вынуждены вручную перезапускать приложение и чистить временные файлы.
Фактор № 10. Паритет сред разработки и работы приложения
Среды окружения разработки, промежуточного развертывания и рабочего развертывания должны быть максимально похожими. В идеале между ними вообще не должно быть никаких различий. Идентичность сред разработки, тестирования и продуктива позволяет избежать массы проблем при развертывании и обновлении компонентов приложения, в частности появления ошибок при перемещении кода между средами.
Для реализации паритета сред используются:
-
Kubernetes,
-
Terraform,
-
Vagrant,
-
Ansible,
-
Puppet.
Фактор № 11. Журналирование
Приложение должно «рассказывать» о своей жизни. В первую очередь нас, конечно, интересуют события, связанные с информационной безопасностью, — сбои, входы в систему, создание новых пользователей, изменение прав и т. п. Наглядное представление о поведении работающего приложения обеспечивает подробное журналирование.
Журнал — это поток агрегированных, упорядоченных по времени событий, собранных из потоков вывода всех запущенных процессов и вспомогательных сервисов. Журнал не имеет фиксированного начала и конца, поток сообщений непрерывен, пока работает приложение.
Приложение не должно заботиться о том, в какой файл или на какой сервер ему необходимо переслать событие. Вместо этого каждый выполняющийся процесс должен записывать свой поток событий без буферизации в стандартный вывод stdout. Дальше это уже задача операционной системы — положить созданный syslog в общий лог, с которым мы потом и будем работать.
Важно, чтобы велся централизованный сбор событий на стороннем хранилище. Тогда мы не потеряем логи даже в случае поломки локальной машины или хакерского взлома и сможем найти источник проблемы или вовремя обнаружить активность злоумышленника.
Фактор № 12. Задачи администрирования
Задачи администрирования и управления нужно выполнять с помощью разовых процессов. При этом разовые процессы администрирования следует запускать в среде, идентичной регулярным длительным процессам приложения. Код администрирования должен поставляться вместе с кодом приложения, чтобы избежать проблем синхронизации.
Кроме того, задачи администрирования должны выполняться с помощью скриптовых языков, позволяющих максимально автоматизировать процесс. Если мы пишем скрипт, то для административных задач обычно используется Shell — в отличие от Python, с Shell не придется волноваться насчет зависимостей. Shell-скрипт можно прописать в системе автоматизированного управления конфигурациями Ansible, которая будет самостоятельно подключаться и выполнять наш скрипт на каждой машине.
Заключение
Но здесь важно понимать, что все 12 факторов тесно связаны между собой и приносят наибольшую пользу, когда применяются в разработке комплексно. Если следовать им полностью, если взаимосвязанно с ними работать, то ваше облачное приложение будет более качественным и безопасным, а его работа — более эффективной.
Оригинал статьи на Хабре!
Расскажи друзьям: