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


					  
Похожие статьи:
У першій частині інтерв’ю Олександр Головатий розповів DOU про свій старт кар’єри в ІT: навчання та роботу в українських...
Згідно з даними НБУ, у жовтні обсяг ІТ-експорту з України становив $532 мільйони. Це на $13 млн (або на 2,5%) більше, ніж...
A light bar is great supplement stock headlamps already installed on your vehicle. These lights can give you broader, brighter, and penetrating ray of light in any direction on your vehicle from the front to...
Вопрос знатокам: какая одна из высоких позиций в IT-компаниях все еще непосредственно связана с решением...
Всем доброго дня! Недавно для одного из заказчиков проводил нагрузочное тестирование веб портала....
Яндекс.Метрика