Статья не претендует на идеальную техническую точность и полноту, она предназначена для быстрого решения конкретной практической задачи с минимально достаточным пониманием сути происходящего.
Задача
Последние несколько месяцев команда разработчиков в компании WebKateработала над довольно большим стартапом для наших норвежских заказчиков. Проект представляет собой мультиплатформенную спортивную соцсеть. Мобильная часть реализована с помощью Xamarin. На бекенде — Symfony2 с API, админкой и веб-интерфейсами для всех типов пользователей.
Возникла необходимость развернуть проект на платформе Amazon Web Services. Казалось бы — возьми мануал да и сделай, но оказалось, не всё так просто.
Штатные мануалы Амазона не предусматривают такого внезапного развития событий, да и вообще, к сожалению, не отличаются удобством и ясностью (Амазон предлагаетсоздавать проект и AWS-приложение с нуля, и как по мне, довольно неудобным способом).
Методом гугления были раздобыты кое-какие части требуемого пазла. Но так как готовой картины нигде не оказалось, пришлось подключать метод научного тыка.
В результате получился довольно простой и удобный способ деплоя.
Итак, что у нас есть:
- Вполне обычный проект на Symfony 2.5. Читает обычную MySQL-базу. На вопросы отвечает обычными HTML-страничками. Обитает в обычном git-репозитории. На локальном LAMP-е работает очень даже неплохо.
- Аккаунт на AWS.
Что требуется:
- Развернуть проект на большом и надежном Амазоне.
- Минимизировать время и усилия при выгрузке новых версий проекта.
- Предусмотреть возможность быстрого и легкого масштабирования в дальнейшем.
Для начала немного об AWS
Общей информации на просторах веба вполне достаточно, потому попробую объяснить на пальцах. Итак, среди множества нужных и удобных сервисов к нашему случаю имеют непосредственное отношение следующие:
EC2 — (Elastic Compute Cloud) — виртуальные сервера приложений
RDS — (Relational Database Service) — реляционные базы
EB — (Elastic Beanstalk) — сервис, использующий предыдущие два, предоставляет комплексную среду для приложения с механизмами репликации, балансировки и т. д.
Собственно именно с Beanstalk-ом мы будем договариваться чтобы приютил наш проект, предоставил ему все необходимое, помог обжиться и приглянул в дальнейшем.
Теперь разберемся как это выглядит.
В Beanstalk вы можете создавать Приложения (Application). Приложение содержит Среды (Environments). Каждая Среда содержит, собственно, экземпляры самих серверов (EC2-Instances). Особняком сидят базы (RDS-инстансы). Каждая прикреплена к своей Среде. Также в каждой Среде есть свой балансировщик, распределяющий нагрузку между серверами (EC2-инстансами) этой Среды. Когда проект уже развернут, пользователи видят его по URL Среды (по сути это URL балансировщика, и пользователь не может знать на каком именно EC2-инстансе выполняется код).
В процессе деплоя вы предоставляете Приложению новую версию вашего кода. Как правило, вы сразу же говорите ему на инстансах какой Среды развернуть эту версию. База, при необходимости, заливается отдельно, стандартным для MySQL способом, напрямую в RDS-инстанс, который прилинкован к данной Среде.
Приложение помнит выгруженные версии проекта, и вы можете в любой момент переключить любую Среду на любую версию. В нашем случае этих знаний достаточно, а более подробно можно почитать в документации Beanstalk.
Создание Приложения
Подразумевается, что у вас есть аккаунт на AWS, вы прописали пользователей, предоставили для них доступ к сервисам (EC2, RDS, EB). Из веб интерфейса создать Приложение достаточно просто. Заходим на страницу сервиса Elastic Beanstalk, нажимаем Create New Application, следуем интуитивно понятному интерфейсу и логике. Можно подстраховываться инструкцией.
Название для Приложения лучше выбрать короткое — скажем, MyApp. Для Сред неплох вариант типа MyApp-dev (это я к тому, что вам потом из консоли набирать). Не забудьте прописать Security Groups и подключить к ним пользователей. Также укажите в настройках Среды относительный путь в проекте к корневой папке хоста (например /web).
Несмотря на то, что инстансы были созданы через Beanstalk, вы можете их видеть и через интерфейс сервиса EC2. Так же и с базами. Вполне возможно, что вам понадобится ssh-доступ к инстансам. В таком случае вам необходимо свой ключ добавить в интерфейсе EC2 (вкладка Key Pairs) и подключить его в настройках Среды (вкладка Instances). IP инстанса URL можно взять в его свойствах в интерфейсе EC2. Коннектитесь как ec2-user без пароля. Аналогично необходимые параметры RDS инстанса можно взять в его свойствах в интерфейсе RDS.
Установка консоли
AWS предоставляет консольный инструмент для работы со своими сервисами. Его нужно установить:
1. Скачать AWS Elastic Beanstalk Command Line Tool.
2. Распаковать его (например как /opt/AWS-ElasticBeanstalk-CLI).
3. Выполнить в любой директории:export PATH=$PATH:/opt/AWS-ElasticBeanstalk-CLI/api/bin/:/opt/AWS-ElasticBeanstalk-CLI/eb/linux/python2.7/
Инструмент установлен, теперь расскажем о нем нашему проекту:
4. В корневой директории нашего репозитория выполнить:/opt/AWS-ElasticBeanstalk-CLI/AWSDevTools/Linux/<a href="http://AWSDevTools-RepositorySetup.sh">AWSDevTools-RepositorySetup.sh</a>
5. Там же выполнить: git aws.config
и ввести
свои AWS-креденшалы (пользователя которого вы создали в аккаунте),
зону где прописана Среда в которую мы собираемся деплоить свой проект (например eu-west-1)
название Приложения (application) (например MyApp)
название Среды (environment) (например MyApp-dev)
После этого там же, в корневой директории репозитория, можно выполнять команду git aws.push при этом текущая ветка репозитория будет автоматически задеплоена в заданную Среду.
Вполне возможно вы захотите создать другую Среду (например MyApp-live) и деплоить в нее другие ветви этого же проекта. В таком случае вы всегда можете переключиться на другую Среду командой git aws.config, при этом креденшалы переспрашиваться уже не будут, так как они уже сохранены в домашней директории текущего пользователя.
В процессе деплоя внутри EC2-инстанса полученный из репозитория код размещается в директорию /var/app/ondeck. Если есть файл composer.json то выполняется подгрузка зависимостей и выполняются скрипты, прописанные для композера. В случае Symfony это генерация ассетов, чистка кеша и т.д.
Если обнаруживается, что мы разворачиваем проект на Symfony, то инстанс силами ACL предоставляет необходимые доступы к папкам кеша и логов. После чего, если все прошло гладко, удаляется директория с развернутым проектом предыдущей версии, и новый, готовый к работе проект переносится на его место, в директорию /var/app/current.
Казалось бы все, но есть проблемка — нам придется каждый раз лезть руками в каждый инстанс, чтобы прописать данные в parameters.ini нашей любимой Symfony. Но не все так плохо.
Создание скрипта
1. Дело в том, что у нас внутри инстанса уже есть необходимые для parameters.ini данные. Они инжектятся в инстанс Средой и видны в PHP как переменные окружения. А если чего нет, мы можем внести в конфигурацию Среды, и оно будет инжектиться в каждый создаваемый там инстанс.
2. Мы можем написать скрипт сборки предварительного файла конфигурации и прописать его выполнение в композер.
// /src/MyApp/AppBundle/Util/AwsDeploy.php<?php namespace MyApp\AppBundle\Util; use Symfony\Component\Yaml\Yaml; use Composer\Script\Event; class AwsDeploy { public static function buildParameters(Event $event) { if (array_key_exists('RDS_HOSTNAME', $_SERVER)) { // Если проект разворачивается в Beanstalk, собираем данные, переданные нам Средой о подключенном RDS-инстансе (базе). $parameters = array( 'database_host' => $_SERVER['RDS_HOSTNAME'], 'database_port' => $_SERVER['RDS_PORT'], 'database_name' => $_SERVER['RDS_DB_NAME'], 'database_user' => $_SERVER['RDS_USERNAME'], 'database_password' => $_SERVER['RDS_PASSWORD'], ); // Также собираем произвольные параметры, которые мы перед этим поместили в конфигурацию Среды foreach ($_SERVER as $key => $val) { if ('PARAM_' == substr($key, 0, 6)) { $parameters[strtolower(substr($key, 6))] = $val; } } // Сохраняем эти данные в файл параметров file_put_contents('/var/app/ondeck/app/config/parameters.yml', Yaml::dump(array('parameters' => $parameters), 99)); } } }
И прописываем запуск. Причем обязательно ДО окончательной сборки файла параметров (Incenteev\\ParameterHandler\\ScriptHandler::buildParameters):
// /composer.json .... "scripts": { "post-root-package-install": [ "SymfonyStandard\\Composer::hookRootPackageInstall" ], "post-install-cmd": [ "MyApp\\AppBundle\\Util\\AwsDeploy::buildParameters", "Incenteev\\ParameterHandler\\ScriptHandler::buildParameters", "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap", "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::clearCache", "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installAssets", "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installRequirementsFile", "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::removeSymfonyStandardFiles" ], "post-update-cmd": [ "MyApp\\AppBundle\\Util\\AwsDeploy::buildParameters", "Incenteev\\ParameterHandler\\ScriptHandler::buildParameters", "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap", "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::clearCache", "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installAssets", "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installRequirementsFile", "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::removeSymfonyStandardFiles" ] }, ....
Если есть необходимость передать дополнительные данные в файл параметров — можно добавить переменные в конфигурации Среды (Configuration -> Software configuration -> Environment properties) с названиями, что начинаются с «PARAM_». Например, переменная с названием «PARAM_NEW_VAL» будет передана в parameters.yml как «new_val».
Итоги
Вот теперь деплоймент проекта действительно автоматический. В случае каких-либо сбоев вы можете просмотреть логи через соответствующий раздел в интерфейсе Beanstalk. При этом недодеплоенный проект останется в инстансе в /var/app/ondeck доступный для исследования по ssh. Ну и, естественно, старая версия проекта при этом будет как ни в чем не бывало обслуживать запросы, и пользователи вашего проекта так и не узнают о мелких технических проблемках.
Надеюсь, статья окажется полезной для тех, кто начинает работать с AWS. Желаю вам сэкономленное на деплойменте время потратить на новые полезные фичи для вашего проекта. :)