The Twelve-Factor App — чек-лист, из 12 правил (принципов), позволяющих создать и поддерживать современное SaaS приложение. Обязательно к прочтению для *Ops-инженеров, разработчиков и архитекторов.
Эти принципы сформулировали разработчики платформы Heroku, обобщив свой опыт работы с тысячами приложений. Они не привязаны к конкретному языку программирования или технологии, т.е. являются универсальными.
Где это может пригодиться?
Приходилось ли вам работать в проекте, где развёртывание каждой новой версии занимает часы, внесение изменений в конфигурацию невозможно без привлечения разработчиков, а большинство возникающих в проде проблем не воспроизводятся в тестовом окружении?
Часто это связано с тем, что участники проекта начинают изобретать велосипед вместо того, чтобы использовать проверенные практики. Следование методам «The Twelve-Factor App» позволит обойти множество подводных камней, которые могут быть неочевидными на первых этапах и создать современное SaaS приложение, отвечающее таким важным требованиям, как масштабируемость, отказоустойчивость, простота в управлении, максимальная переносимость между средами выполнения, поддержка современных облачных платформ и т.п.
Процесс установки и настройки таких приложений позволяет использовать непрерывное развёртывание (CD), а также минимизировать затраты времени и ресурсов на онбординг новых сотрудников.
Независимо от того, являетесь ли вы архитектором, разработчиком или DevOps-инженером, у вас появятся знания и инструменты, необходимые для создания современных облачных приложений, которые можно масштабировать и адаптировать к постоянно меняющимся требованиям.
Двенадцать факторов
I. Кодовая база
Кодовая база — это единый источник исходного кода приложения, отслеживаемый в системе контроля версий (Git, Mercurial, Subversion и т. п.). Она определяет границу приложения: всё, что входит в эту базу, — часть приложения; всё, что вне её, — отдельные компоненты (библиотеки, сервисы).
Не путайте кодовую базу и репозиторий!
Кодовая база — логическое понятие. Это всё, что нужно для сборки и работы одного приложения. Она должна быть едина и уникальна у каждого проекта.
Репозиторий — это техническая единица хранения (каталог + история изменений в системе контроля версий). Их может быть несколько в одной кодовой базе, главное, чтобы у них был общий корневой коммит.
Если компания разрабатывает 2 приложения, у которых есть общие функции, эти функции должны быть вынесены в отдельные библиотеки. Иначе рано или поздно это приведёт к расхождению кода: фикс багов в общей функции может доехать до одного репозитория и не доехать до другого.
Обратный пример: в одном репозитории лежат три независимых сервиса. В этом случае пытаясь изменить один сервис можно случайно сломать другой, а настройка CI/CD становится тем ещё мучением, т.к. приходится собирать и тестировать всё сразу, даже если изменился только один сервис.
II. Зависимости
Если одна и та же версия приложения успешно выполняется в тестовой среде, а в продакшене возникают ошибки, в первую очередь проверьте зависимости.
Зависимости — это внешние компоненты, библиотеки, службы или условия, без которых приложение не может корректно работать.
Этот принцип требует явного объявления и изоляции всех внешних библиотек и пакетов, необходимых для работы приложения.
Что означает явное объявление? Все зависимости должны быть перечислены в специальных файлах (манифестах), например, package.json или requirements.txt. К слову, явное объявление зависимостей сильно упрощает процесс развёртывания.
Теперь поговорим об изоляции: приложение не должно полагаться на наличие необходимых для работы библиотек в системе, а системные библиотеки никак не должны влиять на работу приложения. Это обеспечивает воспроизводимость окружения, т.е. тест и прод по сути одинаковы, что исключает случаи, когда в тестовой среде всё работает штатно, а в продакшене возникают проблемы.
III. Конфигурация
Конфигурация — это настройки приложения, которые могут меняться от окружения к окружению (локальная разработка, тестирование, продакшен) и не должны храниться в коде. Проще говоря: это «переменные», которые задаются снаружи (через ENV), а не внутри кода.
Критерием того, насколько корректно в приложении реализована вся конфигурация вне кода, является возможность в любой момент сделать кодовую базу открытым исходным кодом без ущерба для учетных данных.
IV. Сторонние службы
Сторонняя служба — это любой внешний сервис, который нужен для работы приложения и с которым построено взаимодействие по сети как с "чужой" системой, т.е. она используется как готовый инструмент.
С точки зрения кода приложению должно быть всё равно где и как развёрнут внешний сервис, например, БД. Если для переноса БД с локального сервера в "облако" нужно переписывать код — это плохо. Если достаточно указать новые параметры подключения в ENV — код соответствует четвёртому принципу методологии.
V. Сборка, релиз, выполнение
Любое приложение проходит три фазы: превращение исходного кода в исполняемый пакет, объединение этого пакета с конфигурацией окружения и запуск готового приложения.
На этапе сборки создаётся "образ" приложения, содержащий всё необходимое для работы, это может быть Docker‑образ, JAR‑файл или даже ZIP‑архив. Важно, что образ, созданный на этом этапе можно развернуть в любом окружении.
Обратите внимание, что в контексте 12‑Factor App если Docker-образ публикуется в реестре с присвоенной ему версией, это ещё не является релизом!
На втором этапе "образ" (а точнее артефакт) объединяется с конфигурацией окружения (переменными ENV, настройками развёртывания) и становится релизом. Ему присваивается уникальный идентификатор, например, `bestapp-v2.0-prod-eu-202512181900`.
Другими словами, всё, что происходит с момента "взятия" образа и до непосредственного запуска приложения - является релизом. Причём любое дальнейшее изменение (например, адрес подключения к БД) порождает новый релиз. Это позволяет легко откатывать изменения и диагностировать проблемы.
Этап выполнения включает в себя запуск готового приложения. Он должен быть самым простым, чтобы в случае нештатной ситуации посреди ночи любой дежурный инженер (а лучше автоматика) смогли выполнить перезапуск без привлечения сил разработчиков или DevOps.
Этапы сборки, релиза и выполнения должны выполняться в строгой последовательности.
VI. Процессы
Шестой принцип методологии подразумевает, что приложение должно запускаться как набор временных, независимых процессов, которые можно легко создавать, останавливать и масштабировать.
В этом контексте процессом является независимая, временная единица выполнения приложения, которая не хранит состояние внутри себя может быть мгновенно запущена или остановлена, работает изолированно от других процессов и выполняет строго определённую задачу.
Например, у нас есть простой веб-ресурс, который выводит статистику по его посещению (кол-во юзеров онлайн, с каких ip и в какое время подключались и т.д.). Для соблюдения принципа, нам нужно "разбить" приложение на следующие независимые процессы:
- Web-сервер. Он лишь обрабатывает HTTP-запросы и не хранит в себе никакой информации. В случае резкого наплыва (или оттока) посетителей ресурса кол-во таких процессов, может быть увеличено (уменьшено) в любой момент времени без вреда для хранимых данных.
- Сборщик. Принимает "сырые" данные (IP, время, User‑Agent и т.д.) и складывает их в хранилище.
- Экспортёр отчётов. Формирует отчёты на основе собранных данных, хранящихся в БД.
Процессы не должны общаться напрямую через память или файлы. Для этого должны использоваться внешние сервисы (Kafka, Redis, PostgreSQL и т.д.).
VII. Привязка портов
Седьмой принцип требует отказаться от жёсткой привязки приложения к конкретному сетевому порту на уровне кода. Порт, который будет слушать приложение в момент запуска должен быть указан в конфигурации (например, в ENV). Это позволяет избавиться от многих проблем при масштабировании.
Другими словами, приложение должно уметь работать на любом порту, который ему укажут в момент запуска.
VIII. Параллелизм
В приложении двенадцати факторов процессы являются сущностями первого класса. Для работы над конкретным видом задач назначаются определённые типы процессов (веб процесс обрабатывает http запросы, а воркер в фоне работает над длительными задачами). При необходимости количество процессов того или иного типа должно легко увеличено или уменьшено.
В каждом типе процессов не должно быть "главного" процесса, все должны быть равноправны для лёгкого масштабирования. Чтобы прерывание одного процесса не влияло на остальные, все данные должны храниться во внешних сервисах (Redis, Kafka, PostgreSQL и т.п.).
Это позволяет сделать приложение устойчивым к сбоям и совместимым с современными оркестраторами, например, Kubernetes.
IX. Утилизируемость
Утилизируемость — это способность процессов запускаться и останавливаться в любой момент времени.
Чтобы соответствовать девятому принципу, каждый процесс должен:
- запускаться за считанные секунды;
- корректно обрабатывать сигнал SIGTERM ("просьбу" завершиться самостоятельно); при получении этого сигнала процесс "доделывает" текущую задачу и останавливается;
- в случае внезапного завершения не теряет данные, т.е. не обработанная задача возвращается в очередь, чтобы её взял другой процесс.
Это позволяет добиться от приложения гибкого масштабирования и устойчивости к сбоям.
X. Паритет сред разработки и эксплуатации
Среды разработки (dev), тестирования (test) и эксплуатации (prod) должны иметь минимальные отличия. Т.е. должны использоваться те же внешние сервисы с минимальными различиями в настройках, те же технологии (например, контейнеризации) и т.д.
Придерживаться этого принципа, пожалуй, сложнее всего потому, что:
- разработчик работает с кодом, который попадёт в продакшен через дни или недели;
- развёртыванием приложений занимаются *Ops инженеры, что приводит к разделению ролей: разработчики могут не знать тонкостей продового окружения, *Ops инженеры не вникают в логику кода и зависимости; в конечном итоге это может приводить к отсутствию чувства общей ответственности;
- тестовое окружение не требует больших ресурсов и может возникнуть соблазн использовать леговесные "аналоги", например, Docker в тестовой среде и Kubernetes в продакшене.
Разработчик, написавший код, должен принимать активное участие в развёртывании и наблюдении за работой приложения.
XI. Журналирование
Чтобы понимать как работает приложение, разработчики учат его "отчитываться" о своём поведении. Для этого приложение генерирует поток событий, называемых логом.
Одиннадцатый принцип методологии 12-Factor App предписывает выводить лог в stdout/stderr (стандартные потоки вывода и ошибок). Приложение не должно заботиться о том, в какую систему будут отправлены его логи и сколько места они займут, это забота сборщика логов.
Вывод в stdout/stderr обеспечивает совместимость с современными системами контейнеризации и оркестрации (Docker, Docker Swarm, Kubernetes и т.п.)
XII. Задачи администрирования (Административные процессы)
Административные процессы — это разовые, отдельно запускаемые задачи, которые выполняются в том же окружении, но не входят в регулярный рабочий цикл приложения и запускаются по требованию.
Они должны использовать ту же кодовую базу, зависимости и конфигурацию, что и само приложение. Административный код должен поставляться вместе с кодом приложения, чтобы избежать проблем с синхронизацией.
Примеры административных процессов:
- миграция БД;
- работа с оболочкой (консолью), например, Perl или Elixir;
- разовые скрипты;
- импорт данных;
- генерация отчётов и т.д.
Методология двенадцати факторов отдаёт предпочтение языкам, которые
предоставляют оболочки "из коробки", и которые позволяют легко
выполнять разовые скрипты.
Литература
Если желаете больше углубиться в тему, рекомендую обратиться к первоисточнику: