Для начала немного теории. Что такое The Twelve-Factor App?
Простыми словами, это документ призванный упростить разработку SaaS приложений, помогает тем что, осведомляет разработчиков и DevOps инженеров о проблемах \ практиках которые чаще всего встречались в разработке современных приложений.
Документ сформирован разработчиками платформы Heroku.
Методология двенадцати факторов(The Twelve-Factor App) может быть применена для приложений, написанных на любом языке программирования и использующих любые комбинации сторонних служб (backing services) (базы данных, очереди сообщений, кэш-памяти, и т.д.).
Коротко о самих факторах на которых строится эта методология:
- Кодовая база – Одна кодовая база, отслеживаемая в системе контроля версий, – множество развёртываний
- Зависимости – Явно объявляйте и изолируйте зависимости
- Конфигурация – Сохраняйте конфигурацию в среде выполнения
- Сторонние службы (Backing Services) – Считайте сторонние службы (backing services) подключаемыми ресурсами
- Сборка, релиз, выполнение – Строго разделяйте стадии сборки и выполнения
- Процессы – Запускайте приложение как один или несколько процессов не сохраняющих внутреннее состояние (stateless)
- Привязка портов (Port binding) – Экспортируйте сервисы через привязку портов
- Параллелизм – Масштабируйте приложение с помощью процессов
- Утилизируемость (Disposability) – Максимизируйте надёжность с помощью быстрого запуска и корректного завершения работы
- Паритет разработки/работы приложения – Держите окружения разработки, промежуточного развёртывания (staging) и рабочего развёртывания (production) максимально похожими
- Журналирование (Logs) – Рассматривайте журнал как поток событий
- Задачи администрирования – Выполняйте задачи администрирования/управления с помощью разовых процессов
Больше информации о 12 факторах вы можете получить из следующих ресурсов:
- Официальный первоисточник — обязательно к прочтению
- Статья на habr. Приложение двенадцати факторов — The Twelve-Factor App — официальный перевод
- Статья на habr. 7 недостающих факторов в подходе 12 Factor App — свежий взгляд на 12 факторов с целью усовершенствовать их.
Что такое Blue-Green deployment?
Blue-Green deployment – это способ доставки приложения на production таким образом, что конечный клиент не видит никаких изменений со своей стороны. Другими словами, разворачивание приложения с нулевым downtime.
Классическая схема BG Deploy выглядит так как указано на изображении ниже.
- На старте есть 2 физических сервера с абсолютно одинаковым кодом, приложением, проектом, и есть роутер (балансировщик).
- Роутер изначально направляет все запросы на один из серверов (зеленый).
- В момент когда нужно снова сделать релиз, весь проект обновляется на другом сервере (синий), который в данный момент не обрабатывает никаких запросов.
- После того как код на синем сервере полностью обновлен, роутеру даётся команда на то что нужно переключиться с зеленого на синий сервер.
- Теперь все клиенты видят результат работы кода с синего сервера.
- Какое-то время, зеленый сервер служит резервной копией на случай неудачного деплоя на синий сервер и в случае неудачи и багов, роутер переключает поток пользователей обратно на зеленый сервер со старой стабильной версией, а новый код отправляется на доработку и тестирование.
- И под конец процесса, точно так же обновляется зеленый сервер. И после его обновления, роутер переключает поток запросов обратно на зеленый сервер.
Выглядит это все очень хорошо и на первый взгляд с этим не должно быть никаких проблем.
Но так как мы живем в современном мире, то вариант с физическим переключением как указано в классической схеме нам не подходит. Информацию пока зафиксируйте, мы вернемся к ней позже.
Вредные и хорошие советы
Disclaimer: В примерах ниже указаны утилиты / методологии которые использую я, вы можете использовать абсолютно любые альтернативы со схожими функциями.
Большая часть примеров будет так или иначе пересекаться с веб-разработкой (вот это неожиданность), с PHP и Docker.
В пунктах ниже идет простое практическое описание использования факторов на определенных примерах, если вы хотите получить больше теории по данной теме, обратитесь по ссылкам выше к первоисточнику.
1. Кодовая база
В проекте всегда должна быть единая кодовая база, то есть, весь код идет из одного Git репозитория. Серверы (production, staging, test1, test2 ...) используют код из веток одного общего репозитория. Таким образом мы добиваемся консистентность кода.
2. Зависимости
Проект всегда должен иметь четко понятный список зависимостей (под зависимостями я так же понимаю и окружение). Все зависимости должны быть явно определены и изолированы.
В качестве примера возьмем Composer и Docker.
Composer — пакетный менеджер, позволяющий устанавливать в PHP библиотеки. Composer дает возможность строго или не строго указывать версии, и явно их определять. На сервере может быть 20 различных проектов и у каждого будет личный список пакетов и библиотек не зависящий от другого.
Docker — утилита, позволяющая определять и изолировать окружение в котором будет работать приложение. Соответственно, так же как и с composer, но уже более основательно, мы можем определить то с чем работает приложение. Выбрать определенную версию PHP, поставить только необходимые для работы проекта пакеты, не добавляя ничего лишнего. А самое главное не пересекаясь с пакетами и окружением хостовой машины и других проектов. То есть все проекты на сервере работающие через Docker, могут использовать абсолютно любой набор пакетов и абсолютно разное окружение.
3. Конфигурация
Конфигурации — это единственное чем должны различаться развертывания проекта (deployment). В идеале конфигурации должны передаваться через переменные окружения (env vars).
То есть даже если вы будете хранить несколько конфигурационных файлов .config.prod .config.local и переименовывать их в момент разворачивания в .config (основной конфиг из которого происходит чтение данных приложением) — это будет не верным подходом, так как в таком случае информация из конфигураций будет общедоступна всем разработчикам приложения и данные от продакшен сервера будут скомпрометированы. Все конфигурации должны хранится непосредственно в системе деплоймента (CI/CD) и генерироваться под разные окружения с разными значениями необходимыми для конкретного окружения именно в момент деплоймента.
4. Сторонние службы (Backing Services)
На самом деле этот пункт сильно пересекается с пунктом о конфигурациях, так как без наличия этого пункта не сделать нормальные конфигурационные данные и вообще возможность конфигурирования спадет на нет.
Все подключения к внешним службам, таким как серверы очередей, базы данных, службы кэширования должны быть едиными как для локального окружения, так и для стороннего / продакшен окружения. Иными словами, я в любой момент могу изменив строку подключения заменить обращения к базе #1 на базу #2 без изменения кода приложения. Или забегая вперед как пример подойдет то что при масштабировании сервиса, вам не придется для дополнительного сервера кэша указывать подключение каким-то особенным образом.
5. Сборка, релиз, выполнение
Все стадии деплоймента, должны быть разделены между собой.
Имейте шанс откатиться назад. Делайте релизы с сохранением в быстром доступе старых копий приложения (уже собранных и готовых к бою), что бы в случае ошибок восстановить старую версию. То есть условно есть папка releases и папка current, и после успешного деплоя и сборки папка current связывается символической ссылкой с новым релизом который лежит внутри releases с условным названием номера релиза.
Вот тут мы и вспоминаем о Blue-Green deployment, который позволяет не просто делать переключение между кодом, но так же и переключение между всеми ресурсами и даже окружениями с возможностью откатить все назад.
6. Процессы
По поводу сессий, храните данные только в кэше, контролируемом сторонними службами (memcached, redis), таким образом даже если у вас будет 20 процессов приложения запущено, то любой из них обратившись в кэш, сможет продолжить работать с клиентом в том же состоянии в котором пользователь был работая с приложением в другом процессе. При таком подходе получается так что, сколько бы вы копий сторонних служб не использовали бы, все будет работать штатно и без проблем с доступом к данным.
7. Привязка портов (Port binding)
Все ваши службы должны быть доступны друг для друга через обращение к какому-либо адресу и порту (localgost:5432, localhost:3000, nginx:80, php-fpm:9000), то есть из nginx я могу получить доступ как к php-fpm, так и к postgres, а из php-fpm к postgres и nginx и собственно из каждой службы я могу получить доступ к другой службе. Таким образом жизнеспособность службы не завязана на жизнеспособности другой службы.
8. Параллелизм
Оставляйте возможность масштабирования. Отлично для этого подойдет docker swarm.
Docker Swarm — это инструмент для создания и управления кластерами контейнеров как между различными машинами так и кучей контейнеров на одной машине.
Используя swarm, я могу определять то сколько ресурсов я буду выделять на каждый процесс и какое количество процессов одной и той же службы я буду запускать, а внутренний балансировщик принимая данные на заданный порт, будет автоматически проксировать его на процессы. Таким образом увидев, что нагрузка на сервер выросла, я могу добавить больше процессов, тем самым уменьшить нагрузку на определенные процессы.
9. Утилизируемость (Disposability)
Каждый процесс и служба могут быть выключены в любой момент и это не должно затронуть другие службы (речь конечно не о том что служба будет недоступна для другой службы, а о том что другая служба не отключится в след за этой). Все процессы должны завершаться мягко, таким образом что при их завершении не пострадают данные и при следующем включении система заработает корректно. То есть даже в случае аварийного завершения, данные не должны пострадать (тут подойдет механизм транзакций, запросы в бд работают только группами, и если хоть один запрос из группы не выполнился или выполнился с ошибкой, то не никакой другой запрос из группы в итоге не выполняется в действительности).
10. Паритет разработки/работы приложения
В действительности все разворачивания и работа с кодом должны быть чуть ли не в идентичном окружении (речь не идет о физическом железе). Так же развернуть код на продакшене при необходимости, должен суметь любой сотрудник разрабоки, а не какой-то специально обученный devops отдел, который только благодаря особой силе может поднимать приложение в продакшене.
В этом так же нам помогает Docker. При соблюдении всех предыдущих пунктов, использование docker, доведет процесс разворачивания окружения как на production, так и на локальной машине до ввода одной-двух команд.
11. Журналирование (Logs)
Все логи надо рассматривать как поток событий. Само приложение не должно заниматься обработкой логов. Логи должны выдаваться либо в stdout, либо отправляться по такому протоколу как udp, чтобы приложению работа с логами не создавала никаких проблем. Для этого хорошо подойдет graylog. Graylog принимая все логи по udp (по этому протоколу не требуется дожидаться ответа об успешном приеме пакета) не мешает приложению никаким образом и занимается только структурированием и обработкой логов. Логика приложения не меняется для работы с подобными подходами.
12. Задачи администрирования
Все задачи администрирования должны выполняться в той же среде что и весь код, на уровне релизов. То есть, если нам необходимо изменить структуру бд, то мы не будем делать это руками меняя названия колонок и добавляя новые через какие-то визуальные инструменты управления БД. Для таких вещей мы создаем отдельные скрипты — миграции, которые выполняются везде и на всех окружениях одинакового с общим и понятным результатом. Для всех остальных задач, типа наполнения проекта данными, должны применяться схожие методологии.
Пример реализации на PHP, Laravel, Laradock, Docker-Compose
P.S Все примеры делались на MacOS. Большая часть подойдет и для Linux. Пользователи Windows уж простите, но с виндой я давно не работал.
Представим ситуацию что у нас на ПК не установлена никакая версия PHP и вообще ничего нет.
Устанавливаем docker и docker-compose последних версий. (это можно найти в интернете)
docker -v && docker-compose -v
1. Ставим Laradock
git clone https://github.com/Laradock/laradock.git && ls
По поводу Laradock скажу что это очень классная штука, в которой собрано очень много контейнеров и вспомогательных штук. Но использовать как таковой Laradock без модификаций на production — я бы не рекомендовал из-за его избыточности. Лучше создать свои контейнеры опираясь на примеры в Laradock, так будет куда оптимизирование, ибо никому не нужно все что там есть одновременно.
2. Конфигурируем Laradock для работы нашего приложения.
cd laradock && cp env-example .env
2.1. Открываем каталог habr (родительская папка в которую склонирован laradock) в каком либо редакторе. (В моем кейсе PHPStorm)
На этом этапе ставим только название проекту.
2.2. Запускаем образ workspace. (В вашем случае образы будут какое-то время билдиться)
Workspace — это специально подготовленный образ для работы с фреймворком от имени разработчика.
Переходим внутрь контейнера с помощью
docker-compose up -d workspace && docker-compose exec workspace bash
2.3. Устанавливаем Laravel
composer create-project --prefer-dist laravel/laravel application
2.4. После установки проверяем создалась ли директория с проектом, и убиваем compose.
ls
exit
docker-compose down
2.5. Возвращаемся обратно в PHPStorm и ставим верный путь до нашего laravel приложения в файле .env.
3. Добавим весь код в Git.
Для этого создадим на Github (или где угодно ещё) репозиторий. Перейдем в терминале в директорию habr и выполним следующий код.
echo "# habr-12factor" >> README.md
git init
git add README.md
git commit -m "first commit"
git remote add origin git@github.com:nzulfigarov/habr-12factor.git # здесь будет ссылка на ваш репо
git push -u origin master
git status
Проверяем все ли в порядке.
Для удобства рекомендую использовать какой-либо визуальный интерфейс для Git, в моём случае это GitKraken. (тут реферальная ссылка)
4. Запускаем!
Перед запуском убедитесь, что у вас на 80 и 443 порту ничего не висит.
docker-compose up -d nginx php-fpm
Таким образом наш проект состоит из 3 отдельных сервисов:
- nginx — веб-сервер
- php-fpm — php для приема запросов с с веб-сервера
- workspace — php для разработчика
На данный момент мы добились того, что создали приложение соответствующее, уже 4 пунктам из 12, а именно:
1. Кодовая база — весь код лежит в одном репозитории (небольшая ремарка: возможно правильным будет внести docker внутрь проекта laravel, но это не принципиально).
2. Зависимости — Все наши зависимости явно прописаны в application/composer.json и в каждом Dockerfile каждого контейнера.
3. Сторонние службы (Backing Services) — Каждая из служб (php-fom, nignx, workspace) живет своей жизнью и подключена из вне и при работе с одной службой, другая не будет затронута.
4. Процессы — каждая служба это один процесс. Каждая из служб не сохраняет внутреннее состояние.
5. Привязка портов (Port binding)
docker ps
Как мы видим, каждая служба запущена на своем собственном порту и доступна для всех других служб.
6. Параллелизм
Docker позволяет нам поднимать несколько процессов одних и тех же служб с автоматической балансировкой нагрузки между ними.
Остановим контейнеры и запустим их через флаг --scale
docker-compose down && docker-compose up -d --scale php-fpm=3 nginx php-fpm
Как мы видим, у php-fpm контейнера создались копии. Нам в работе с данным контейнером ничего не нужно менять. Мы так же продолжаем обращаться к нему по 9000 порту, а Docker за нас регулирует нагрузку между контейнерами.
7. Утилизируемость (Disposability) — каждый контейнер можно убить без вреда для другого. Остановка или перезапуск контейнера никак не повлияют на работу приложения при последующих запусках. Каждый контейнер так же можно поднять в любое время.
8. Паритет разработки/работы приложения — все наши окружения одинаковые. Запустив систему на сервере в продакшен вам не придется ничего менять в ваших командах. Все будет точно так же базироваться на Docker.
9. Журналирование (Logs) — все логи в данных контейнерах выходят на поток и видны в консоли Docker. (в данном кейсе, в действительности, с другими самодельными контейнерами, может быть не так если вы об этом не позаботитесь)
docker-compose logs -f
Но, тут есть загвоздка в том, что Default значения в PHP и Nginx так же записывают логи в файл. Для соответствия 12 факторам, необходимо отключить запись логов в файл в конфигурациях каждого контейнера отдельно.
Так же докер предоставляет возможность направлять логи не просто в stdout, а ещё и в такие вещи как graylog о котором я говорил выше. А внутри graylog, мы можем оперировать логами как угодно и наше приложение никак не будет замечать этого.
10. Задачи администрирования — все задачи администрирования решаются laravel благодаря инструменту artisan именно так как этого хотели бы создатели 12 факторного приложения.
В качестве примера покажу как выполняются некоторые команды.
Заходим в контейнер.
docker-compose exec workspace bash
php artisan list
Теперь можем воспользоваться любой командой. (обратите внимание что мы не настраивали базу данных и кэш поэтому половина команд не выполнится корректно, ибо они предназначены для работы с кэшем и бд).
11. Конфигурации и 12. Сборка, релиз, выполнение
Эту часть я хотел посвятить Blue-Green Deployment, но это оказалось слишком развернутым для этой статьи. Об этом я напишу отдельную статью.
В двух словах, концепт строится на системах CI/CD типо Jenkins и Gitlab CI. И в той и в другой можно задавать переменные окружения связанные с конкретным окружением. Соответственно при таком раскладе будет выполняться пункт с Конфигурациями.
А пункт про Сборка, релиз, выполнение решается встроенными в обе утилиты функции с названием Pipeline.
Pipeline позволяет разделять на много этапов процесс деплоймента, выделяя стадии сборки, релиза и выполнения. Так же в Pipeline, вы сможете создать бекапы, да и вообще что угодно. Этот инструмент с безграничным потенциалом.
Код приложения лежит на Github.
Не забудьте инициализировать submodule при клонировании данного репозитория.
P.S.: Все эти подходы можно использовать с любыми другими утилитами и языками программирования. Главное, чтобы суть не отличалась.
EXayer
У нас проект на ларевеле, конфиги из него и .env хранится в отдельной репе. Сборка настроена через Jenkins, который собирает проект из 2 репозиториев (код и конфиг). Изменения в конфиг репозиторий надо вносить вручную. Насколько такой подход верный?
Hackerook Автор
Теоретически это более правильный подход чем просто хранить конфиги в этом же репозитории с основным проектом.
В какой-то степени это можно считать соблюдением этого пункта, если этот репозиторий не доступен всем, а доступен лишь ограниченному кругу людей которые занимаются деплойментом.