Туториал по развертыванию 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

Какую проблему решаем

Поскольку поддерживаемых окружений стало больше, нужно упростить процесс обновления приложения.

Решение

Image Source

В основе термина 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 "


					  
Похожие статьи:
20 червня видання TechCrunch повідомило, що маркетплейс OLX Group скорочує близько 800 фахівців по всьому світу через проблеми з підрозділом Olx...
У понеділок, 28 лютого, у Twitter зʼявився допис із закликом поширити інформацію про те, що «ІТ-компанія NIX погрожує звільнити...
Привіт! Мене звати Євгеній Коваль, я PHP-розробник в компанії Wikr Group. Ми займаємось створенням та розвитком...
Український стартап Datrics у 2021 році залучив понад 1 млн доларів інвестицій та пройшов в YCombinator. Це no-code...
Российский оператор МТС на основании обезличенных данных о пользовании услугами связи в июне-августе...
Яндекс.Метрика