О чем речь
• как упорядочить структуру приложения• как свести воедино веб-часть и фоновые сценарии• как сделать приложение более надежным• как сделать приложение масштабируемым• какие рекомендации могут помочь вне зависимости отсредства реализации
http://12factor.net
2 / 30
12 факторов1 Единая кодовая база2 Явные локализованные зависимости3 Конфигурация в переменных окружения4 Сервисы как подключаемые ресурсы5 Отдельные стадии сборки и запуска6 Приложение = набор процессов7 Экспорт сервисов через привязку портов8 Масштабирование через процессы9 Одноразовость процесса10 Идентичность сред разработки и выполнения11 Журнал как поток сообщений12 Административные процессы
3 / 30
1. Единая кодовая база
• код приложения хранится в системе контроля версий• одно приложение – один репозиторий• общий код для нескольких приложений – зависимость, всвою очередь соответствующая 12 факторам
• репозиторий и код едины для различных вариантовразвертывания приложения
• различные варианты развертывания могут соответствоватьразличным веткам репозитория
На практике: ничего нового, используем контроль версий.
4 / 30
2. Явные локализованныезависимости
• зависимости должны быть так или иначе явнопродекларированы
• зависимости от системных пакетов должны бытьминимизированы, все что может быть установленолокально для приложения, должно быть установленолокально
• в идеале все внешние программы, вызываемыеприложением, должны поставляться с приложением(например, используя контейнер)
5 / 30
Зависимости: практика
• мы практически не используем внешние PHP библиотеки• полная контейнеризация приложений когда-нибудь будетвозможна, но не сейчас
• используем стандартный формат модуля нашейплатформы, явно декларируем зависимости в Makefile
• используем внешние программы установленные в рамкахсистемы/контейнера
• используем симлинки как основной инструментсвязывания с зависимостями
6 / 30
3. Конфигурация в переменныхокружения
• информация, специфичная для развертывания, никогда нехранится в коде (иначе противоречие с первым фактором)
• различные варианты конфигурации в отдельных файлахусложняет либо развертывание, либо код
• все параметры конфигурации, относящиеся к средезапуска – из переменных окружения
• все это не относится к конфигурации, описывающейструктуру приложения и не меняющуюся в зависимости отсреды запуска
7 / 30
Конфигурация: практикаРабота с переменными окружения встроена в DI-контейнер:
1 $container = new Container();2 $container->with(App::DATABASE, function ($c) {3 return new Connection(4 $c[’DATABASE_DSN’],5 $c[’DATABASE_USER’],6 $c[’DATABASE_PASS’]7 );8 });
Объект соединения создается с параметрами подключения,определенными в окружении.
1 export DATABASE_DSN=’mysql:host=localhost;dbname=test’2 export DATABASE_USER=test3 export DATABASE_PASSW=test
8 / 30
Конфигурация: практика
• не надо искать файл конфигурации, разбирать его и т.д.• все уже встроено в DI-контейнер• единые настройки окружения для всех процессовприложения, независимо от реализации
• конфигурация гарантированно не попадает в репозиторий,не нужно хранить примеры файлов конфигурации
• определение переменных: уровень системы, уровеньпрограммы-супервизора, конфигурация веб-сервера и т.д.
9 / 30
4. Сервисы как подключаемыересурсы
• используемые внешние сервисы (база данных, поисковыйиндекс, кеш и т.д.) определяются идентификатором ресурса
• работа cо сторонним сервисом не зависит от конкретногорасположения сервиса
• код не должен содержать специфики, связанной срасположением (например локальным) того или иногосервиса
• в разных средах выполнения различные ресурсы,определяемые конфигурацией в соответствии с пунктом 3
10 / 30
Ресурсы: практика
MySQL удовлетворяет принципуSMTP есть проблемы (зависимость от локального
sendmail)Sphinx файлы индексов нужно выносить за пределы
файловой системы приложения
Главное – при использовании любого стороннего сервисапомнить, что он может быть развернут как локально, так иудаленно, не рассчитывать на локальную установку
11 / 30
5. Отдельные стадии сборки изапуска
• сборка формирует из репозитория запускаемые артефакты• публикация устанавливает результат сборки в окружение сопределенной конфигураций в соответствии с пунктом 3
• код запускается в новом окружении• запускаемый код не содержит элементов, необходимыхтолько на этапе сборки и проходит все необходимые фазыоптимизации
12 / 30
Сборка: практика
• разделяем фазы сборки и запуска на уровне файловойструктуры проекта
• автоматически формируем структуру запускаемого образана этапе разработки при помощи симлинков
• на этапе публикации формируем полный образ,содержащий все необходимое путем разрешения всехсимлинков
• образы различных версий могут быть опубликованынезависимо с последующим атомарным переключениемрелиза
13 / 30
6. Приложение = наборпроцессов
• приложение выполняется как набор процессов• чем проще процессы, тем лучше• все состояние системы – в стороннем сервисе хранения,процессы ничего не сохраняют
• веб-приложение – процесс(ы) выполняемыевеб-сервером, но не только
• процессы, выполняющие фоновую обработку• процессы, запускаемые периодически и т.д.
Самый важный принцип, должен быть номер один.
14 / 30
Процессы: практика
Supervisor: A Process Control System
http://supervisord.org
15 / 30
supervisord• на первоначальном этапе используем для управленияфоновыми процессами
• в дальнейшем и для процессов веб-части тоже• supervisord.conf – описание всех процессов
1 [supervisord]2 logfile=/dev/null
3 [program:api-worker]4 command=api-worker5 autostart=true6 autorestart=true7 stdout_logfile=/dev/df/18 stdout_logfile_maxbytes=09 process_name=%(program_name)s-%(process_num)01d
16 / 30
supervisord: полезные свойства
• перезапускает процесс в случае аварийного завершения• позволяет указать необходимое количество запускаемыхэкземпляров программы
• поддерживает [include] – итоговую конфигурациюпроцесса можно строить из описаний процессовотдельных функциональных модулей
• можно доопределить свойства процесса, определяемогово включаемом файле
17 / 30
supervisord: fcgiВстроенная поддержка fcgi:
1 [fcgi-program:php-cgi]2 socket=tcp://127.0.0.1:500003 command=/usr/bin/php-cgi -b 127.0.0.1:500004 numprocs=15 priority=9996 process_name=%(program_name)s_%(process_num)02d7 autorestart=true8 autostart=true9 startsecs=1
10 startretries=311 stopsignal=QUIT12 stopwaitsecs=10
Порождение процессов php-cgi – возможны варианты.
18 / 30
7. экспорт сервисов черезпривязку портов
• приложение самодостаточно и предоставляет сервис черезопределенный порт
• веб-приложение предоставляет HTTP-сервис черезнепривилегированный порт
• порт может быть использован при разработке безнеобходимости использовать веб-сервер спривилегированным портом
• mod_http is so last century• в production роутинг запросов через HTTP-frontend• HTTP - только один из вариантов
19 / 30
Порты: PHP• встроенный сервер годится для отладки, противоречит п.10• supervisord + php-cgi + внешний nginx
1 map $http_host $id {2 hostnames;3 domain1 50000; domain2 50001;4 }5 server {6 listen 80 default_server;7 root /srv/www/$id;8 location / { try_files $uri $uri/ /index.php; }9 location ~ \.php$ {
10 fastcgi_pass 127.0.0.1:$id;11 fastcgi_index index.php;12 fastcgi_param SCRIPT_FILENAME \13 $document_root$fastcgi_script_name;14 include fastcgi_params;15 }
20 / 30
9. Масштабирование черезпроцессы
• масштабирование приложения с помощью моделипроцессов
• в случае PHP - вообще единственно возможный вариант• приложение = набор процессов различных типов• количество однотипных процессов можно менять взависимости от нагрузки
• процессы не пытаются реализовать полноценный демон,делегирую все необходимое системе запуска (supervisor)
• процессы должны допускать параллельную работу,перезапуск в случае сбоя и т.д.
21 / 30
Масштабирование: практикаВ пределах одного сервера:
• supervisord:
1 [program:api-worker]2 command=api-worker3 numprocs=34 process_name=%(program_name)s_%(process_num)02d
• GNU parallel (http://gnu.org/software/parallel/)
1 inotifywait -q -m -r -e MOVED_TO -e CLOSE_WRITE \2 --format %w%f my_dir \3 | parallel -u worker-script
Если скрипт простой и использует стандартные средства(окружение, обработчики сигналов и т.д.), его легчераспараллелить.
22 / 30
10. Одноразовость процесса
• процессы быстро запускаются и быстро завершаются• процессы корректно обрабатывают сигналы, в частностиSIGTERM
• процессы должны корректно отрабатывать ситуациювнезапной остановки
• процесс может быть быстро перезапущен супервизором вслучае возникновения проблемы
23 / 30
Одноразовость: практика• обрабатываем сигналы
1 declare(ticks=1); # важно!2 include(__DIR__ . ’/../lib/autoload.php’);3 use imp\std\os\Process;4 Process::main(5 $app = new Application(),6 [7 SIGINT => [ $app, ’stop’ ],8 SIGTERM => [ $app, ’stop’ ]9 ]
10 );
• стараемся свести функциональность к обработкеминимально возможной единицы данных
• допускаем возможность перезапуска процесса междуотдельными задачами обработки
• допускаем параллельный запуск однотипных процессов24 / 30
11. Идентичность средразработки и выполнения
• среды выполнения и разработки максимально идентичны• разработку и развертывание выполняют одни и те же люди• частая публикация изменений
На практике: важно для больших проектов, в нашей ситуациичисто формальный пункт.
25 / 30
11. Журнал как поток сообщений
• все процессы пишут лог в stdout• журналирование в едином легко обрабатываемомформате, например JSON
• каждое сообщение позволяет идентифицировать процесс,который его сформировал
• stdout всей группы процессов формирует единый поток• поток может быть при необходимости перенаправлен,обработан внешними инструментами и т.д.
26 / 30
Журналирование: практикаЖурналирование в JSON поддерживается фреймворком,добавляем в сообщение идентификаторы процесса и типапроцесса:
1 use imp\cli\Application as CLIApplication;2 use imp\log\JSONLogger;3 use imp\std\IO;4 use imp\std\os\Process;
5 $application = new CLIApplication();6 $application->onLog(new JSONLogger(7 IO::stdout(),8 [ ’sid’ => $this->sid, ’pid’ => Process::PID() ]9 ));
10 $application->log(Log::INFO, ’application started’);
27 / 30
12. Административные процессы
• типовые административные задачи реализуются какотдельные процессы
• административные процессы запускаются в той же средевыполнения, что и длительно выполняющиеся процессыприложения
• административные задачи входят в состав приложения ииспользуют общий код с основными процессами
28 / 30
Итого• приложение = веб-часть + фоновые процессы• максимально простая логика отдельных процессов• делегирование управления процессами супервизору• процессы не должны быть привязаны к состоянию,состояние – во внешнем хранилище
• конфигурация – свойство окружения, а не состоянияпроцесса
• веб-приложение – сервис, экспортирующий http-порт• журнал = поток сообщений от всех процессов приложения
http://12factor.net
29 / 30
Что читать
• http://12factor.net: методология• http://habrahabr.ru/post/258739/: русский перевод• http://supervisord.org: документация по supervisor• http://www.gnu.opg/software/parallel/man.html:документация по GNU parallel с примерами
30 / 30