Туториал по развертыванию Rails-приложений на Amazon с помощью Docker. Часть 3
Всем привет! Перед вами последняя часть туториала, в котором мы учимся самостоятельно разворачивать Rails-приложение с помощью Docker и инструментов AWS.
В предыдущих частях туториала мы уже успели проделать много работы.
В первой части мы рассмотрели теорию Docker: принцип работы, его компоненты, а также преимущества и недостатки работы Docker в сравнении с виртуальными машинами. Кроме того, мы прошли пошаговую сборку Rails-приложения в Dockerfile и запустили наше Spree-приложение и все зависимые сервисы на локальной машине.
Во второй части мы:
- Реализовали возможность хранения sensitive data приложения.
- Создали Docker образ для веб-сервера Nginx.
- Подготовили конфигурацию для развертывания staging инфраструктуры на AWS.
- Запустили staging приложение на AWS.
В этой части мы переходим к этапу развертыванию production-окружения. Поехали!
Какую проблему решаем
Итак, как уже было сказано ранее, в предыдущей части мы развернули наше staging-приложение, протестировали его и убедились, что все работает. Теперь необходимо развернуть готовое к масштабированию production-приложение.
На инфографике мы видим, что мы находимся на последнем этапе: Production. Также мы видим этапы нашей работы и стэк технологий, который будем использовать.
В этой части мы рассматриваем последний этап — Production. Также мы видим ПО, которое будем использовать на данном этапе
Решение
Стек сервисов production-приложения остается таким же, как и на предыдущем этапе Staging. Инфраструктура имеет следующие отличия:
- Cloud database. В качестве базы данных для нашего приложения мы будем использовать Amazon RDS, который упрощает развертывание и настройку реляционных баз данных в облаке.
- Cloud in-memory storage. В качестве in-memory store мы будем использовать Amazon ElastiCache, который облегчает развертывание и управление средами Redis и Memcached.
- Масштабирование и балансировщик нагрузки. В случае увеличения количества запросов, мощностей одного сервера может быть недостаточно. Нужен сервис, который будет распределять запросы между несколькими серверами в зависимости от их нагрузки. Мы будем использовать Amazon Elasticloadbalancing.
- Декомпозиция. Если на предыдущих этапах все зависимые сервисы приложения размещались на одном инстансе, то в условиях горизонтальной масштабируемости приложения нужно увеличивать только те количества сервисов, на которые приходится наибольшая нагрузка. В контексте нашей инфраструктуры, необходимо вынести в отдельные инстансы Server App и Worker App, так как не всегда, когда нужно масштабировать Server App, нужно масштабировать и Worker App, и наоборот.
Таким образом, архитектура production-приложения преображается в следующую структуру:
Постановка задачи
Для корректной и безопасной работы production-окружения нам понадобится:
- Сохранить все переменные production окружения в зашифрованном виде.
- Создать Postgresql инстанс с помощью RDS.
- Создать Redis инстанс с помощью ElasticCache.
- Создать кластер для production приложения на AWS ECS.
- Создать compose файл для запуска приложения и его зависимых сервисов на AWS ECS.
- Запустить сервис с production версией приложения на AWS.
Поскольку количество окружений возросло, необходимо автоматизировать процесс деплоя, а именно, интегрировать CircleCI для автоматизации деплоя для staging-окружения.
Хранение данных приложения
Начнем с добавления AWS credentials в файл с глобальными переменными приложения, используя команду EDITOR=nano rails credentials:edit.
production: # ... AWS_ACCESS_KEY_ID: 'YOUR_AWS_ACCESS_KEY_ID' AWS_SECRET_ACCESS_KEY: 'YOUR_AWS_SECRET_ACCESS_KEY' DEVISE_SECRET_KEY: 'YOUR_DEVISE_SECRET_KEY' SECRET_KEY_BASE: 'YOUR_SECRET_KEY_BASE'
Конфигурация сервисов AWS
Конфигурация доступа
Коммуникация клиента с инфраструктурой нашего приложения будет происходить через elasticloadbalancing. В целях безопасности, доступ ко всем остальным сервисам будет предоставлен только в рамках внутренней сети AWS и определенных security groups.
Исходя из диаграммы production-окружения, указанной в начале, создадим группы доступа:
# GroupId группы load balancer обозначим, как `$BALANCER_SG` aws ec2 create-security-group \ --group-name spreeproject-production-balancer \ --description "Spree project Production Balancer" # GroupId группы серверного приложения обозначим, как `$SERVER_APP_SG` aws ec2 create-security-group \ --group-name spreeproject-production-server-app \ --description "Spree project Production Server App" # GroupId группы баз данных приложения обозначим, как `$DB_SG` aws ec2 create-security-group \ --group-name spreeproject-production-db \ --description "Spree project Production DB" # GroupId группы для memory stores сервиса обозначим, как `$STORE_SG` aws ec2 create-security-group \ --group-name spreeproject-production-store \ --description "Spree project Production Memory Store"
И укажем правила коммуникации сервисов:
# Откроем доступ любому клиенту на 80-й порт балансера. aws ec2 authorize-security-group-ingress \ --group-id $BALANCER_SG \ --protocol tcp \ --port 80 \ --cidr 0.0.0.0/0 # Откроем доступ балансеру на 8080-й порт будущим инстансам, где будет запущен Nginx. aws ec2 authorize-security-group-ingress \ --group-id $SERVER_APP_SG \ --protocol tcp \ --port 8080 \ --source-group $BALANCER_SG # Откроем доступ будущим инстансам с приложением к RDS, где по 5432 порту будет доступен Postgres. aws ec2 authorize-security-group-ingress \ --group-id $DB_SG \ --protocol tcp \ --port 5432 \ --source-group $SERVER_APP_SG # Откроем доступ будущим инстансам с приложением к ElastiCache, где по 6379 порту будет доступен Redis. aws ec2 authorize-security-group-ingress \ --group-id $STORE_SG \ --protocol tcp \ --port 6379 \ --source-group $SERVER_APP_SG
Хранение изображений и быстрый доступ к ним
Для хранения изображений наше приложение будет использовать S3-bucket. Создадим его с помощью следующей команды:
aws s3api create-bucket --bucket spreeproject-production
После создания bucket на S3 обновим переменные credentials:
production: # ... S3_BUCKET_NAME: 'spreeproject-production' S3_REGION: 'us-east-1' S3_HOST_NAME: 's3.amazonaws.com'
RDS
Amazon предоставляет управляемый сервис RDS, который упрощает настройку, использование и масштабирование реляционной базы данных.
Чтобы начать работать, вам достаточно просто создать RDS-инстанс с выбранной вами базой данных и получить хост-сервиса для подключения вашего приложения к нему.
В качестве базы данных мы будем использовать инстанс RDS с готовой сборкой для Postgres. В $YOUR_DB_USERNAME и $YOUR_DB_PASSWORD укажите собственные значения.
aws rds create-db-instance \ --engine postgres \ --no-multi-az \ --vpc-security-group-ids $DB_SG \ --db-instance-class db.t2.micro \ --allocated-storage 20 \ --db-instance-identifier spreeproject-production \ --db-name spreeproject_production \ --master-username $YOUR_DB_USERNAME \ --master-user-password $YOUR_DB_PASSWORD
Выполним команду для проверки статуса RDS инстанса:
aws rds describe-db-instances
В отличие от EC2-инстансов, RDS будет доступен через некоторое время, и после успешной инициализации будет доступен его endpoint.
Обновим наш файл с encrypted credentials:
production: # ... DB_NAME: 'spreeproject_production' DB_USERNAME: 'YOUR_DB_USERNAME' DB_PASSWORD: 'YOUR_DB_PASSWORD' DB_HOST: 'YOUR_RDS_ENDPOINT' DB_PORT: '5432'
ElastiCache
Amazon предоставляет управляемый сервис ElastiCache, который облегчает развертывание и запуск в облаке серверных узлов, совместимых с протоколами Memcached или Redis. Сервис упрощает и берет на себя управление средами в памяти, их мониторинг и решение связанных операционных вопросов, позволяя клиентам сосредоточить усилия технических специалистов на разработке приложений.
В качестве in-memory store будем использовать сервис ElastiCache с готовой сборкой Redis:
aws elasticache create-cache-cluster \ --engine redis \ --security-group-ids $STORE_SG \ --cache-node-type cache.t2.micro \ --num-cache-nodes 1 \ --cache-cluster-id spree-production
Команда для проверки статуса инстанса:
aws elasticache describe-cache-clusters --show-cache-node-info
На инициализацию ElastiCache также потребуется некоторое время. После успешной инициализации ElastiCache сохраним полученный Address сервиса в encrypted credentials:
production: # ... REDIS_DB: "redis://YOUR_EC_ENDPOINT:6379"
ELB
В случае увеличения количества запросов, мощностей одного сервера может быть недостаточно. Это приводит к увеличению времени обслуживания запросов. Чтобы устранить эту проблему, подключим к нашей инфраструктуре балансировщик нагрузки (load balancer), который будет распределять запросы между несколькими нодами в зависимости от их нагрузки. У AWS есть готовое решение Elasticloadbalancing.
Использование ELB для распределения нагрузки
Для создания инстанса необходимо получить Subnets, VPС и Keypair для будущего инстанса:
aws ec2 describe-subnets
В результате команды выбираем SubnetId, у которых одинаковый VpcId и DefaultForAz параметр имеет значение true. И записываем их в переменную.
# Пример AWS_SUBNETS=subnet-e49c19b8,subnet-20ae1647,subnet-319d1a1f
Создаем непосредственно сам loadbalancer:
aws elb create-load-balancer \ --load-balancer-name spreeproject-balancer-production \ --listeners "Protocol=HTTP,LoadBalancerPort=80,InstanceProtocol=HTTP,InstancePort=8080" \ --subnets $YOUR_SUBNETS \ --security-groups $BALANCER_SG
Добавим к нему connection settings:
aws elb modify-load-balancer-attributes \ --load-balancer-name spreeproject-balancer-production \ --load-balancer-attributes "{\"ConnectionSettings\":{\"IdleTimeout\":5}}"
Состояние созданного балансировщика можно проверить с помощью команды:
aws elb describe-load-balancers
В дальнейшем, наше Spree-приложение будет доступно через его DNSName.
Настраиваем production окружение
ECS Cluster
Работа с ECS Cluster и создание в нем сервисов идентична главе ECS Cluster из предыдущей части, где мы разворачивали staging-приложение.
Создадим конфигурацию для будущего кластера spreeproject-production, введя следующую команду в консоли:
CLUSTER_NAME=spreeproject-production ecs-cli configure --region us-east-1 --cluster $CLUSTER_NAME --config-name $CLUSTER_NAME
Необходимо получить список доступных VPC:
aws ec2 describe-vpcs
Дальше VpcId этих subnets записываем в переменную $AWS_VPC. Например:
AWS_VPC=vpc-0e934a76
После, создадим кластер spreeproject-production, к которому будет подвязан один EC2 инстанс типа t2.micro. Для этого вводим в терминале следующую команду:
ecs-cli up \ --keypair spreeproject_keypair \ --capability-iam \ --size 3 \ --instance-type t2.micro \ --vpc $AWS_VPC \ --subnets $AWS_SUBNETS \ --image-id ami-0a6be20ed8ce1f055 \ --security-group $SERVER_APP_SG \ --cluster-config $CLUSTER_NAME \ --verbose
ECR
Создадим репозитории для хранения образов production-версии приложения.
Предварительно нужно аутентифицироваться с помощью следующей команды:
$(aws ecr get-login --region us-east-1 --no-include-email)
Актуализируем образы наших сервисов, вызвав команду:
docker-compose -p spreeproject -f docker-compose.development.yml build
После, загрузим локальный образ в репозиторий YOUR_ECR_ID.dkr.ecr.us-east-1.amazonaws.com. YOUR_ECR_ID — registryId созданного репозитория:
docker tag spreeproject_server_app:latest $YOUR_ECR_ID.dkr.ecr.us-east-1.amazonaws.com/spreeproject/server_app:production
docker push $YOUR_ECR_ID.dkr.ecr.us-east-1.amazonaws.com/spreeproject/server_app:production
Сделаем то же самое для web_server, в котором будет образ Nginx:
aws ecr create-repository --repository-name spreeproject/web_server
docker tag spreeproject_web_server:latest $YOUR_ECR_ID.dkr.ecr.us-east-1.amazonaws.com/spreeproject/web_server:production
docker push $YOUR_ECR_ID.dkr.ecr.us-east-1.amazonaws.com/spreeproject/web_server:production
ECS Tasks
После, создадим docker-compose.production.yml как compose production версии приложения. Замените YOUR_ECR_ID и YOUR_RAILS_MASTER_KEY на собственные значения:
cd ../docker && touch docker-compose.production.yml
version: '3' services: web_server: image: YOUR_ECR_ID.dkr.ecr.us-east-1.amazonaws.com/spreeproject/web_server:production ports: - 8080:8080 links: - server_app server_app: image: YOUR_ECR_ID.dkr.ecr.us-east-1.amazonaws.com/spreeproject/server_app:production command: bundle exec puma -C config/puma.rb entrypoint: "./docker-entrypoint.sh" expose: - 3000 environment: RAILS_ENV: production RAILS_MASTER_KEY: YOUR_RAILS_MASTER_KEY
Создадим файл схему ecs-params
touch ecs-params.production.yml
version: 1 task_definition: ecs_network_mode: bridge task_size: cpu_limit: 768 mem_limit: 0.5GB services: web_server: essential: true server_app: essential: true
Запускаем production-окружение
ECS Services
Создаем задачу для будущего сервиса ECS:
# создайте task definition для контейнера docker ecs-cli compose \ --file docker-compose.production.yml \ --project-name $CLUSTER_NAME \ --ecs-params ecs-params.production.yml \ --cluster-config $CLUSTER_NAME \ create
После создания задачи ей будет присвоенный определенный номер. Запишем этот номер в переменную TASK_NUMBER и создадим сервис ECS:
aws ecs create-service \ --service-name 'spreeproject' \ --cluster $CLUSTER_NAME \ --task-definition "$CLUSTER_NAME:$TASK_NUMBER" \ --load-balancers "loadBalancerName=$BALANCER_NAME,containerName=web_server,containerPort=8080" \ --desired-count 2 \ --deployment-configuration "maximumPercent=200,minimumHealthyPercent=50"
Теперь на оставшемся незанятом инстансе нашего кластера, запустим приложение для background processing.
Создадим docker-compose-worker.production.yml как compose production версии Sidekiq приложения. Замените YOUR_ECR_ID и YOUR_RAILS_MASTER_KEY на собственные значения:
touch docker-compose-worker.production.yml
version: '3' services: worker_app: image: YOUR_ECR_ID.dkr.ecr.us-east-1.amazonaws.com/spreeproject/server_app:production command: bundle exec sidekiq -C config/sidekiq.yml environment: RAILS_ENV: production RAILS_MASTER_KEY: YOUR_RAILS_MASTER_KEY
Создадим файл схему ecs-params touch ecs-params-worker.production.yml:
version: 1 task_definition: ecs_network_mode: bridge task_size: cpu_limit: 768 mem_limit: 0.5GB services: worker_app: essential: true
Создаем задачу для будущего сервиса ECS:
# create task definition for a docker container ecs-cli compose \ --file docker-compose-worker.production.yml \ --project-name "$CLUSTER_NAME-worker" \ --ecs-params ecs-params-worker.production.yml \ --cluster-config $CLUSTER_NAME \ create
И создадим сервис для этой задачи:
aws ecs create-service \ --service-name "spreeproject-worker" \ --cluster $CLUSTER_NAME \ --task-definition "$CLUSTER_NAME-worker:$TASK_NUMBER" \ --desired-count 1 \ --deployment-configuration "maximumPercent=200,minimumHealthyPercent=50"
Continuous Deployment
Какую проблему решаем
Поскольку поддерживаемых окружений стало больше, нужно упростить процесс обновления приложения.
Решение
В основе термина Continuous deployment лежит слово Continuous, которое, несмотря на широкое употребление, разработчики трактуют по-разному. Понимание этого слова заключается в определении трех аспектов:
- Continuous Delivery ‒ отвечает за поставку бизнесу каждой части функционала постепенно. Такой подход позволяет получить отклик от клиента своевременно и, при необходимости, сделать изменения.
- Continuous Deployment ‒ отвечает за то, чтобы весь новый функционал после тестирования сразу же интегрировался в работающее приложение без ручного вмешательства инженеров DevOps.
- Continuous Integration ‒ ключевой компонент практики Agile Development. Основывается на постоянном попадании кода в центральный репозиторий после успешного прохождения тестов. Основные цели Continuous Integration — поиск и устранение потенциальных проблем как можно быстрее, улучшение качества ПО и сокращение времени на выпуск обновлений.
В качестве сервиса, который позволяет реализовать Continuous методологию, мы будем использовать CircleCI. Для Continuous Deployment будем использовать ecs-deploy. Все связанные скрипты для развертывания приложения будем хранить в директории config/deploy. Там же определим список зависимых инструментов для развертывания.
Конфигурация процесса деплоя
Создадим файл dependencies.txt
cd ../spree-docker-demo mkdir config/deploy touch config/deploy/dependencies.txt
И укажем в нем пакеты, которые понадобятся для деплоя:
ecs-deploy==1.4.3 awscli==1.16.38
Далее в deploy.sh создадим собственный скрипт с удобным интерфейсом для обновления наших сервисов на ECS:
mkdir config/deploy/lib touch config/deploy/lib/deploy.sh
#!/bin/bash # Required variables: # - region # ECR Region # - aws-access-key # AWS Access Id # - aws-secret-key # AWS Secret Key # - service # Service's name # - repo # Service's repository # - cluster # Service's cluster # - name # Application's container name # - task # AWS ECS task name set -e deploy() { while [[ $# -gt 0 ]] do key="$1" case $key in --region) REGION="$2" shift shift ;; --aws-access-key) AWS_ACCESS_KEY_ID="$2" shift shift ;; --aws-secret-key) AWS_SECRET_ACCESS_KEY="$2" shift shift ;; --repo) REPO="$2" shift shift ;; --cluster) CLUSTER="$2" shift shift ;; --task) TASK="$2" shift shift ;; --service) SERVICE="$2" shift shift ;; --name) APP_NAME="$2" shift shift ;; --skip-build) SKIP_BUILD="$2" shift shift ;; *) echo "Unknown option $1\n" shift shift esac done VERSION=${CIRCLE_BRANCH:="$(git rev-parse --abbrev-ref HEAD)"} BUILD_APP=$APP_NAME:$VERSION BUILD_REPO=$REPO:$VERSION echo "Похожие статьи:IT-компанія з білоруським корінням EPAM опублікувала звіт про фінансові результати за перший квартал 2023 року та оновила прогноз на увесь...На вчерашней презентации, наряду с флагманскими смартфонами Lumia 950 и 950 XL, компания Microsoft также анонсировала и более простой и доступный...У новому випуску YouTube-рубрики «X питань», де ми розпитуємо представників різних спеціальностей про те, що турбує IT-спільноту, DOU...