Deploy Symfony + React приложения на AWS посредством CI

в 7:24, , рубрики: Amazon Web Services, AWS, ci, circleci, docker, elasticbeanstalk, symfony

Добрый день, в данной статье я покажу как развернуть Symfony 4 приложение на AWS. В официальной документации есть пример подобного процесса, однако мой вариант не столь тривиален, как загрузка zip архива с приложением. На дворе 2019, в моде docker, микросервисная архитектура и CI/CD практики наконец-то начинают входит в инструментарий не только DevOps-инженеров, но и простых смертных разработчиков. Чтобы статья была более интересна, я добавил фронт на React.JS, для охвата потребностей большей массы людей, если ваше приложение не использует Encore — не беда, я укажу как изменить Docker-файл для вас, поддержка React.JS тут влияет только на него. Кому будет интересен данный туториал? В первую очередь он направлен на PHP-разработчиков, желающих изменить свою практику деплоя — отойти от привычных канонов и воспользоваться docker для паковки своего приложения и выкладки образа. Но можно пойти чуть глубже, и дальнейшее повествование будет направлено на автоматический деплой приложения из Git'а посредством CI/CD платформы (будет использован CircleCI, но если интересует конфиг Gitlab'а, пишите в комментариях, я приложу). По сути, тут абсолютно не важно React/PHP ли у вас приложение или, скажем, на .NET Core, данная часть будет интересна разработчикам для получения навыков автоматизации деплоя в целом. Исходный код доступен в github-репозитории, ссылка в конце статьи. Ну что же, поехали!

Я предполагаю, что у вас имеется собственное Symfony приложение, я же в демонстрационных целях набросал «hello, world!», содержащее следующие пакеты:

`symfony/webpack-encore-bundle symfony/form symfony/orm-pack symfony/profiler-pack symfony/security-bundle symfony/twig-bundle symfony/validator symfony/phpunit-bridge` — минимальный джентельменский набор. На данный момент структура папок у вас должна быть такая:

image

Теперь необходимо настроить облачную инфраструктуру. Не буду заострять внимание на регистрации и активации пробного периода AWS, на данном этапе нам нужно создать 2 инстанса БД — я буду использовать 2 типа окружения: STG (staging) для тестирования внедрения новых «фич» и PROD (production) как непосредственно «боевой» сервер. О преимуществах, managed service БД написано немало статей, к тому же мы преследуем в данном руководстве главным образом удобство для разработчика, потому пользуемся именно RDS, а не поднимаем свой отдельный сервер БД. В качестве СУБД для данного примера я использовал PostgreSQL, вы вольны выбирать любую подходящую вам, перейдите в сервис RDS и создайте 2 инстанса необходимой вам мощности и объема. Так как «из коробки» в Symfony для нас доступен файл .env, будем использовать его, например, для PROD, а для STG создадим его копию .env.stg и изменим APP_ENV=dev на APP_ENV=stg в .env.stg и APP_ENV=dev на APP_ENV=prod в .env, а также впишем параметры подключения к Базе Данных для каждого из созданных экземпляров.

Прекрасно, начало положено! Как известно, Symfony-зависимости устанавливаются через composer, для его установки воспользуемся файлом composer.sh, который положим в корень проекта:

composer.sh

#!/bin/sh

EXPECTED_SIGNATURE="$(wget -q -O - https://composer.github.io/installer.sig)"
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
ACTUAL_SIGNATURE="$(php -r "echo hash_file('sha384', 'composer-setup.php');")"

if [ "$EXPECTED_SIGNATURE" != "$ACTUAL_SIGNATURE" ]
then
    >&2 echo 'ERROR: Invalid installer signature'
    rm composer-setup.php
    exit 1
fi

php composer-setup.php --quiet
RESULT=$?
rm composer-setup.php
exit $RESULT

Это инструкция по программной установке с сайта composer.

Теперь, для каждого из окружений создадим свой Dockerfile в корне проекта:

Dockerfile.stg (staging)
FROM php:7.2.19-apache

EXPOSE 80

RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" && a2enmod rewrite
RUN sed -ri -e 's!memory_limit = 128M!memory_limit = 256M!g' "$PHP_INI_DIR/php.ini"

RUN apt-get update && apt-get install -y 
    wget 
    curl 
    libfreetype6-dev 
    libjpeg62-turbo-dev 
    libpng-dev 
    libzip-dev 
    zip 
    libpq-dev 
    && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ 
    && docker-php-ext-configure zip --with-libzip 
    && docker-php-ext-configure pgsql -with-pgsql=/usr/local/pgsql 
    && docker-php-ext-install -j$(nproc) gd 
    && docker-php-ext-install zip 
    && docker-php-ext-install pdo pdo_pgsql pgsql

WORKDIR /var/www

ENV APACHE_DOCUMENT_ROOT /var/www/public
RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf
RUN sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf
RUN echo "ServerName localhost" >> /etc/apache2/apache2.conf

COPY ./composer.sh ./
RUN chmod +x ./composer.sh && ./composer.sh && mv composer.phar /usr/local/bin/composer

RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - 
    && apt-get install -y nodejs

RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - 
    && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list 
    && apt-get update -qq 
    && apt-get install -y yarn

COPY ./ ./
COPY ./.env.stg ./.env

RUN composer install && yarn && yarn run build

и

Dockerfile (production)

FROM php:7.2.19-apache

EXPOSE 80

RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" && a2enmod rewrite
RUN sed -ri -e 's!memory_limit = 128M!memory_limit = 256M!g' "$PHP_INI_DIR/php.ini"

RUN apt-get update && apt-get install -y 
    wget 
    curl 
    libfreetype6-dev 
    libjpeg62-turbo-dev 
    libpng-dev 
    libzip-dev 
    zip 
    libpq-dev 
    && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ 
    && docker-php-ext-configure zip --with-libzip 
    && docker-php-ext-configure pgsql -with-pgsql=/usr/local/pgsql 
    && docker-php-ext-install -j$(nproc) gd 
    && docker-php-ext-install zip 
    && docker-php-ext-install pdo pdo_pgsql pgsql

WORKDIR /var/www

ENV APACHE_DOCUMENT_ROOT /var/www/public
RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf
RUN sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf
RUN echo "ServerName localhost" >> /etc/apache2/apache2.conf

COPY ./composer.sh ./
RUN chmod +x ./composer.sh && ./composer.sh && mv composer.phar /usr/local/bin/composer

RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - 
    && apt-get install -y nodejs

RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - 
    && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list 
    && apt-get update -qq 
    && apt-get install -y yarn

COPY ./ ./

RUN composer install && yarn && yarn run build

Файлы вполне можно использовать «как есть», никаких макросов для изменения не используется. Давайте пройдемся по содержимому Dockerfile'а, чтобы развеять налет «магии». В качестве «основы» мы используем официальный образ PHP 7.2.19 с интегрированным веб-сервером Apache (вы вольны использовать любой по своему усмотрению, настроить связку с Nginx и так далее, в данном примере я использую означенную выше как самую, на мой взгляд, удобную). Строка expose в данный момент нам не важна, сама по себе она ничего не делает, однако в дальнейшем ее будет использовать ElasticBeanstalk, которому она необходима для корректного развертывания. Следующие конструкции используют оптимизированные для production настройки PHP, рекомендованные производителем, активируют mod_rewrite для Apache и увеличивают максимальный объем памяти для PHP скрипта со 128 до 256 мб, что необходимо для корректной работы composer. Далее мы устанавливаем необходимые приложения, PHP-зависимости и расширения, и сразу их настраиваем. Рабочей директорией нашего приложения назначаем папку /var/www – туда будет скопирован исходный код нашего приложения. Так как apache по-умолчанию использует /var/www как энтрипоинт для своего хоста, а индексный файл symfony находится в /var/www/public, следующей конструкцией меняем apache document root. Затем мы последовательно устанавливаем composer, nodejs и yarn (если в своем приложении вы не используете encore/react.js, то последние из указанных два пункта вам не нужны). Наконец мы копируем наш исходный код и запускаем установку зависимостей через composer для symfony и yarn для react.js. Смысл отдельного Dockerfile’а для STG кроется в предпоследней инструкции для docker’а – копировании .env.stg в .env, таким образом в образе для STG .env-файл будет содержать актуальные для данного окружения параметры. Можете локально (разумеется при установленном docker) собрать образ, запустить и убедиться, что приложение работает и ничего больше для этой работы ему не требуется:

docker build -t tmp:stg -f Dockerfile.stg .
docker run -p 80:80 tmp:stg

для STG и

docker build -t tmp:prod .
docker run -p 80:80 tmp:prod

для PROD.
Мы можем использовать EC2, настраивать ELB/ASG и тд или же воспользоваться ElasticBeanstalk, что для нас в плане удобства просто подарок. Зайдите в раздел ElasticBeanstalk и создайте новое приложение, указав его название и описание. Затем создадите 2 окружения, о которых говорилось ранее: STG и PROD, оба окружения создайте как окружение веб-сервера (Web server environment), в качестве платформы укажите “Docker”, а в качестве кода приложения оставьте Sample application. Деплой на ElasticBeanstalk осуществляется посредством «заливки» файлов проекта или инструкций, обычно в zip-архиве. В нашем случае флоу будет таким: собираем docker-образ нашего приложения, загружаем его в хранилище и загружаем вместо архива с исходниками или docker-образа инструкцию, которая укажет ElasticBeanstalk взять образ с удаленного сервера и развернуть. И все это – автоматически.

Начнем с создания репозитория для хранения docker-образов. Тут есть 2 варианта:

1 – у вас проект приватный, код его закрыт и репозиторий, соответственно, тоже должен быть закрыт. В данном случае вы или держите где-то свой собственный регистр образов или пользуетесь приватным облаком. Для данных целей в AWS есть ECR, вы можете создать репозиторий там, но никто вас к этому не принуждает.

2 – у вас проект с открытым кодом и вы можете использовать dockerhub.

В нашем примере код хоть и открыт, но я покажу как использовать именно закрытые репозитории, после понимания данного процесса подключение образа из dockerhub не составит труда. Первое, что нам понадобится это создание самого репозитория, после этого вы получите его уникальный URI. Дальнейшее повествование пойдет для сторонних (не AWS ECR репозиториях и их интеграции), для ECR напишу после этого.

После создания репозитория, нам необходимо авторизоваться на в данном сервисе и тут есть небольшая хитрость… Зайдите в настройки своего локально установленного docker’а и проверьте, чтобы у вас была убрана возможность сохранения паролей во внешнем хранилище (для пользователей macOS: “Securely store docker logins in macOS Keychain”), иначе необходимый нам конфигурационный файл будет пуст. И так, авторизуемся в выбранном сервисе хранения регистров ваших образов:

docker login -u LOGIN -p PASSWORD REGISTRY

после удачной аутентификации в конфигурационном файле ~/.docker/config.json появится конструкция следующего вида:

"REGISTRY" : {
      "auth" : "BASE64_ENCODED_TOKEN"
}

Если не появилась, еще раз проверьте настройку docker, описанную выше.

Теперь все готово, чтобы приготовить файл-инструкцию для ElasticBeanstalk – Dockerrun.aws.json, его код будет таким:

Dockerrun.aws.json

{
  "AWSEBDockerrunVersion": "1",
  "Authentication": {
    "Bucket": "BUCKET_ID",
    "Key": "KEY_PATH"
  },
  "Image": {
    "Name": "IMAGE_URL",
    "Update": "true"
  },
  "Ports": [
    {
      "ContainerPort": "80"
    }
  ]
}

В общем виде инструкция выглядит так: авторизовавшись с помощью ключа, расположенного по KEY_PATH в S3-хранилище BUCKET_ID, загрузить образ по IMAGE_URL перезаписав сохраненный, запустить его пробросив 80 порт на такой же порт контейнера. Теперь про использованные константы:

BUCKET_ID – это автоматически созданный для вас «рюкзак» в сервисе S3, имеющий вид elasticbeanstalk-REGION-HASH, именно там система располагает служебные файлы для вашего ElasticBeanstalk, в том числе и файлы приложения, которые вы загружаете по кнопке «Upload and deploy».

KEY_PATH – путь до авторизационного файла к хранилищу образов, я использую формат APP_NAME/cr.json, то есть в папку внутри BUCKET_ID под именем моего приложения (создаю, если пока нет) кладу файл cr.json, содержащий код, полученный после авторизации в регистре образов локально:

BUCKET_ID/APP_NAME/cr.json

{
    "REGISTRY" : {
        "auth" : "BASE64_ENCODED_TOKEN"
    }
}

IMAGE_URL – это уникальный URI вашего регистра образов + тег самого образа, тут все должно быть понятно.

Все, теперь можем загрузить данный файл как версию нашего приложения в ElasticBeanstalk, и он сам подтянет указанный образ и развернет его.

Осталось автоматизировать данный процесс. А чтобы было совсем интересно, я реализую последовательность шагов для следующего флоу: для всех коммитов НЕ в мастер-ветку образ будет собираться и разворачиваться в STG окружении, а если мы пушим в мастер, или что лучше, его вообще закрыть и наливать только merge request’ами, то код будет деплоиться на PROD. Таким образом мы получим в PROD актуальный мастер, в котором все должно быть хорошо, а ветки для разработки и тестирования нового кода в STG. Для данной реализации нам понадобится инструкция для заливки не latest-образов, скопируйте Dockerrun.aws.json в Dockerrun.aws.stg.json, а сам Dockerrun.aws.json переименуйте в Dockerrun.aws.prod.json (просто для удобства).

Единственное, что отличает Dockerrun.aws.stg.json от Dockerrun.aws.prod.json – это IMAGE_URL:

Dockerrun.aws.stg.json

{
  "AWSEBDockerrunVersion": "1",
  "Authentication": {
    "Bucket": "BUCKET_ID",
    "Key": "KEY_PATH"
  },
  "Image": {
    "Name": "IMAGE_URL:dev",
    "Update": "true"
  },
  "Ports": [
    {
      "ContainerPort": "80"
    }
  ]
}

Как я уже говорил в начале статьи, в качестве CI/CD я буду использовать CircleCI, который по личным ощущениям быстрее GitlabCI, если использовать бесплатную SaaS версию. Бесплатный Travis бы подошел, но так как с приватными git-репозиториями он работать не умеет, я не стал специально проводить демонстрацию на нем, дабы не было разочарования, когда такая возможность понадобится. Настройку проекта в CircleCI я оставлю читателям для самостоятельного изучения, сам же приведу необходимые для деплоя инструкции — в корне нашего проекта создадим папку .circleci, в ней config.yml следующего содержания:

.circleci/config.yml

version: 2
jobs:
  build:
    machine: true
    steps:
      - checkout
      - run: echo "$CI_REGISTRY_PASSWORD" | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
      - run: docker build -t $CI_REGISTRY/$CI_REGISTRY_ID:dev -f Dockerfile.stg .
      - run: docker push $CI_REGISTRY/$CI_REGISTRY_ID:dev

  build-master:
    machine: true
    steps:
      - checkout
      - run: echo "$CI_REGISTRY_PASSWORD" | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
      - run: docker build -t $CI_REGISTRY/$CI_REGISTRY_ID:latest .
      - run: docker push $CI_REGISTRY/$CI_REGISTRY_ID:latest

  deploy-stg:
    docker:
      - image: circleci/python:latest
    steps:
      - checkout
      - run: sudo pip install awsebcli --upgrade
      - run: |
          mkdir ~/.aws
          touch ~/.aws/config
          chmod 600 ~/.aws/config
          echo "[profile eb-cli]" > ~/.aws/config
          echo "aws_access_key_id=$AWS_ACCESS_KEY_ID" >> ~/.aws/config
          echo "aws_secret_access_key=$AWS_SECRET_ACCESS_KEY" >> ~/.aws/config
      - run: eb init --region EB_REGION --platform Docker EB_APP
      - run: cp Dockerrun.aws.stg.json Dockerrun.aws.json
      - run: eb use EB_ENV_STG --region EB_REGION
      - run: eb deploy -v --staged --profile eb-cli

  deploy-prod:
    docker:
      - image: circleci/python:latest
    steps:
      - checkout
      - run: sudo pip install awsebcli --upgrade
      - run: |
          mkdir ~/.aws
          touch ~/.aws/config
          chmod 600 ~/.aws/config
          echo "[profile eb-cli]" > ~/.aws/config
          echo "aws_access_key_id=$AWS_ACCESS_KEY_ID" >> ~/.aws/config
          echo "aws_secret_access_key=$AWS_SECRET_ACCESS_KEY" >> ~/.aws/config
      - run: eb init --region EB_REGION --platform Docker EB_APP
      - run: cp Dockerrun.aws.prod.json Dockerrun.aws.json
      - run: eb use EB_ENV_STG --region EB_REGION
      - run: eb deploy -v --staged --profile eb-cli

workflows:
  version: 2
  build:
    jobs:
      - build:
          filters:
            branches:
              ignore:
                - master
      - deploy-stg:
          requires:
            - build
          filters:
            branches:
              ignore:
                - master

  build-deploy:
    jobs:
      - build-master:
          filters:
            branches:
              only:
                - master
      - deploy-prod:
          requires:
            - build-master
          filters:
            branches:
              only:
                - master

Сам флоу я расписал чуть раньше, тут он переведен в yaml-инструкции для CircleCI, пройдемся по реализации конкретных шагов. Важно отметить наличие определенных для CI переменных окружения, которые будут использованы им в процессе работы:

CI_REGISTRY, CI_REGISTRY_USER, CI_REGISTRY_PASSWORD необходимы для доступа в хранилище docker-образов — тоже самое, что мы клали в cr.json, только без base64

CI_REGISTRY / CI_REGISTRY_ID составляют уникальный URL образа, без тега

AWS_ACCESS_KEY_ID и AWS_SECRET_ACCESS_KEY — названия говорят за себя сами, это креды AWS пользователя, от имени которого CircleCI будет производить деплой. Зайдите в AWS IAM и создайте пользователя, добавьте его в группу администраторов и предоставьте только программный доступ. Помните, что AWS_SECRET_ACCESS_KEY доступен для просмотра/копирования лишь однажды, после того как нажмете ссылку «show», больше вы ее не увидите.

Вернемся к шагам конфигурации CircleCI. В чем «магия»? Checkout загружает исходный код из git-ветки в текущую рабочую директорию, этот процесс повторяется в каждом джобике. В процессе build мы последовательно авторизуемся в хранилище, собираем код на основе Dockerfile.stg под тегом XXX:dev и отправляем его в хранилище. build-master делает тоже самое, только для сборки использует «обычный» Dockerfile под тегом XXX:latest.

deploy-stg устанавливает AWS EB CLI и создает авторизационный профиль в файле ~/.aws/config, который необходим для корректной работы CLI, далее инициализирует переменные для CLI – потребуется указать выбранный вами регион, платформу – всегда Docker и имя вашего приложения. Следом мы копируем содержимое Dockerrun.aws.stg.json в новый файл Dockerrun.aws.json и, используя конкретный environment и регион, даем команду на деплой нашего приложения, применив созданный авторизационный профиль. По умолчанию в результате работы данной команды весь код отслеживаемой ветки попадет в zip-архив, который будет загружен в ElasticBeanstalk и там распакован, но эта операция сравнительно затратная, потому мы и создали новый файл Dockerrun.aws.json, которого достаточно для развертывания созданного нами удаленного образа, и загрузить нам, по сути, нужно только его. Создадим для этого в корне проекта файл .ebignore:

.ebignore

*
!Dockerrun.aws.json
 

Данный файл использует синтаксис .gitignore да и является он .gitignore, но не для Git CLI, а для AWS EB CLI. В данном файле я говорю CLI пропустить все файлы, кроме Dockerrun.aws.json. Все, теперь при выполнении джобика deploy-stg в ElasticBeanstalk отправится только созданный нами файл. deploy-prod делает все тоже самое, только копирует в Dockerrun.aws.json содержимое файла Dockerrun.aws.prod.json, и последнее, это указание в «формате» CircleCI последовательности джобов (deploy-stg после build и deploy-prod после build-master), и на какие ветки смотрят данные джобики (ignore: — master и only: — master).

Немного иначе дело состоит с AWS ECR, как я и обещал, вернемся к нему. Вам не нужно логиниться удаленно на ECR и создавать файл cr.json, так как ElasticBeanstalk “знает собрата в лицо”. Соответственно и Dockerrun.aws.json будет выглядеть иначе – там попросту будет отсутствовать блок аутентификации:

Dockerrun.aws.json (AWS ECR)

{
  "AWSEBDockerrunVersion": "1",
  "Image": {
    "Name": "IMAGE_URL",
    "Update": "true"
  },
  "Ports": [
    {
      "ContainerPort": "80"
    }
  ]
}

Но как же тогда произойдет аутентификация? Дело в том, что сервис, который обращается к ECR имеет некий набор прав, которые в свою очередь базируются на определенных политиках безопасности. В нашем случае, когда деплой запускается посредством AWS CLI со стороннего сервера (из CI), применяется роль “aws-elasticbeanstalk-ec2-role”, найдите ее в AWS IAM в разделе ролей и прикрепите к ней дополнительную политику “AmazonEC2ContainerRegistryReadOnly”. Теперь загрузка из приватного репозитория его «соседу» удастся без ошибок.

Но это именно загрузка из той же VPC, посредством CLI же команда docker login то же не «без примудростей»: креденшалы для docker login вы должны получить (именно получить) посредством AWS CLI, для этого существует команда

aws ecr get-login --region REGION --no-include-email

Данная команда вернет вам строку вида docker login …, проще говоря, в консоли необходимо выполнить

eval $(aws ecr get-login --region EB_REGION --no-include-email)

Команда сначала получит строку для аутентификации, а затем запустит соответствующий процесс. В виду данных правил для AWS ECR файл-инструкций для CircleCI будет выглядеть следующим образом:

.circleci/config.yml (для AWS ECR)

version: 2
jobs:
  build:
    docker:
      - image: circleci/python:latest
    steps: 
      - checkout
      - run: sudo pip install awscli --upgrade
      - run: |
          mkdir ~/.aws
          touch ~/.aws/config
          chmod 600 ~/.aws/config
          echo "[profile eb-cli]" > ~/.aws/config
          echo "aws_access_key_id=$AWS_ACCESS_KEY_ID" >> ~/.aws/config
          echo "aws_secret_access_key=$AWS_SECRET_ACCESS_KEY" >> ~/.aws/config
      - setup_remote_docker
      - run: eval $(aws ecr get-login --region EB_REGION --no-include-email)      
      - run: docker build -t $CI_REGISTRY/$CI_REGISTRY_ID:dev -f Dockerfile.stg .
      - run: docker push $CI_REGISTRY/$CI_REGISTRY_ID:dev

  build-master:
    docker:
      - image: circleci/python:latest
    steps: 
      - checkout
      - run: sudo pip install awscli --upgrade
      - run: |
          mkdir ~/.aws
          touch ~/.aws/config
          chmod 600 ~/.aws/config
          echo "[profile eb-cli]" > ~/.aws/config
          echo "aws_access_key_id=$AWS_ACCESS_KEY_ID" >> ~/.aws/config
          echo "aws_secret_access_key=$AWS_SECRET_ACCESS_KEY" >> ~/.aws/config
      - setup_remote_docker
      - run: eval $(aws ecr get-login --region EB_REGION --no-include-email)      
      - run: docker build -t $CI_REGISTRY/$CI_REGISTRY_ID:latest .
      - run: docker push $CI_REGISTRY/$CI_REGISTRY_ID:latest

  deploy-stg:
    docker:
      - image: circleci/python:latest
    steps:
      - checkout
      - run: sudo pip install awsebcli --upgrade
      - run: |
          mkdir ~/.aws
          touch ~/.aws/config
          chmod 600 ~/.aws/config
          echo "[profile eb-cli]" > ~/.aws/config
          echo "aws_access_key_id=$AWS_ACCESS_KEY_ID" >> ~/.aws/config
          echo "aws_secret_access_key=$AWS_SECRET_ACCESS_KEY" >> ~/.aws/config
      - run: eb init --region EB_REGION --platform Docker EB_APP
      - run: cp Dockerrun.aws.stg.json Dockerrun.aws.json
      - run: eb use EB_ENV_STG --region EB_REGION
      - run: eb deploy -v --staged --profile eb-cli

  deploy-prod:
    docker:
      - image: circleci/python:latest
    steps:
      - checkout
      - run: sudo pip install awsebcli --upgrade
      - run: |
          mkdir ~/.aws
          touch ~/.aws/config
          chmod 600 ~/.aws/config
          echo "[profile eb-cli]" > ~/.aws/config
          echo "aws_access_key_id=$AWS_ACCESS_KEY_ID" >> ~/.aws/config
          echo "aws_secret_access_key=$AWS_SECRET_ACCESS_KEY" >> ~/.aws/config
      - run: eb init --region EB_REGION --platform Docker EB_APP
      - run: cp Dockerrun.aws.prod.json Dockerrun.aws.json
      - run: eb use EB_ENV_STG --region EB_REGION
      - run: eb deploy -v --staged --profile eb-cli

workflows:
  version: 2
  build:
    jobs:
      - build:
          filters:
            branches:
              ignore:
                - master
      - deploy-stg:
          requires:
            - build
          filters:
            branches:
              ignore:
                - master

  build-deploy:
    jobs:
      - build-master:
          filters:
            branches:
              only:
                - master
      - deploy-prod:
          requires:
            - build-master
          filters:
            branches:
              only:
                - master

Для поддержки docker-in-docker мы добавили setup_remote_docker на этапах сборок, остальное вам уже должно быть известно из содержания данной статьи. Вот и все, теперь структура нашего проекта выглядит следующем образом:

image

Попробуйте изменить код, запушить его в задачную ветку, а потом сделать мердж (пулл) реквест на мастер и принять его. Никаких больше «движений» для выкладки обновлений. Можно (и кому-то нужно) пойти далее, написать свои джобики для раскатки миграций, сделать промежуточный обязательный шаг прохождения автотестов и так далее, надеюсь данная статья положит начало для подобных экспериментов и последующих внедрений качественной и правильной доставки контента.

Исходный код на GitHub: tutorial-aws-symfony-ci

Автор: Дмитрий Вишин

Источник


* - обязательные к заполнению поля