Какую проблему решаем
После проверки корректности работы приложения в локальном окружении, необходимо развернуть идентичную инфраструктуру в облаке. Этой задаче и будет посвящена вторая часть нашего туториала. Итак, приступим к работе!Решение: AWS ECS
ECS запускает ваши контейнеры в кластере экземпляров Amazon EC2 с предварительно установленным Docker-ом. ECS управляет установкой контейнеров, масштабированием, мониторингом и управлением ими через API и Консоль управления AWS.Вы можете разместить и запустить Docker контейнер приложения на EC2 вручную. Но вы лишите себя следующих вещей:
- Безопасность. Amazon ECS запускает контейнеры в Amazon VPC, что позволяет использовать собственные группы безопасности VPC и списки контроля доступа к сети.
- Масштабируемость. С помощью ECS вы можете упростить и автоматизировать процесс клонирования ваших сервисов и распределения нагрузки между ними с помощью ELB.
- Интеграция с сервисами. ECS предоставляет возможность интеграции приложения с такими сервисами AWS, как Amazon ECR, Amazon CloudWatch, AWS CloudFormation, Amazon ELB и так далее.
- Удобный деплой. ECS помогает запускать приложения в виде микросервисов и обеспечивает непрерывную интеграцию и непрерывное развертывание при помощи API. Также ECS позволяет совершать деплой без времени простоя.
- Мониторинг состояния. ECS упрощает просмотр использования ресурсов экземпляров EC2, таких как процессор и память.
ECS Cluster

Cluster — это группа EC2 инстансов, на которых запущен один или несколько Docker контейнеров. Если ваш проект состоит из нескольких приложений, вы можете разместить их в одном кластере в виде отдельных сервисов. Такой подход позволяет более эффективно использовать доступные ресурсы и минимизировать время установки.
Task definition

Эта инструкция описывает, как и какие Docker контейнеры необходимо запускать. Task Definition можно создать путем определения следующих параметров:
- какой образ Docker использовать для запуска определенного контейнера;
- сколько центральных процессоров (ЦП) и памяти использовать для каждой задачи;
- связи между контейнерами, если нужна коммуникации между ними;
- команда для запуска контейнера;
- команда, которую должен запустить контейнер при запуске;
- способ логирования контейнеров;
- команда для проверки состояния контейнера.
Task

Если Task Definition — это инструкция по запуску одного или нескольких контейнеров, то Task представляет из себя один или несколько запущенных контейнеров. У задачи есть три состояния:
- Running — все контейнеры запущены и работают корректно;
- Pending — контейнеры в процессе запуска;
- Stopped — контейнеры остановлены.
Service

В Service можно вынести одну или несколько задач, где вы определяете, какое минимальное и максимальное количество задач необходимо запустить. Это позволит вам настроить автомасштабирование и балансировку нагрузки для вашего приложения. Так, в случае превышения CPU из-за определенной задачи, которая выполняется, ECS Agent может выделить еще один инстанс и распределить трафик с помощью ELB на время загруженности.
Разумеется, мы можем ограничить максимальное количество задач, которые могут быть запущены, поскольку для запуска дополнительных задач используются дополнительные ресурсы в виде новых инстансов, а это требует финансовых затрат.
Подведем итоги. Если говорить кратко о компонентах ECS, это:
- Cluster ‒ группа связанных между собой EC2 инстансов;
- ECR ‒ приватный репозиторий, на котором хранятся Docker-образы нашего приложения;
- Task definition ‒ инструкция по запуску контейнеров на EC2 кластера;
- Task ‒ один или несколько запущенных контейнеров;
- Service ‒ совокупность запущенных Tasks.
План действий
- Настроить инструменты для работы с AWS.
- Реализовать возможность безопасного хранения таких чувствительных данных нашего приложения, как пароли от внешних сервисов, ключей доступа и т. д.
- Создать образ Docker для веб-сервера Nginx.
- Подготовить staging-инфраструктуру на AWS.
- Запустить staging-приложение на AWS.

Инфраструктура staging-приложения практически идентична той, что мы разворачивали локально. Впрочем, есть несколько отличий:
- Сборка образов. В отличие от локального окружения, образ основного приложения мы будем хранить не на машине, где запускается приложение (Host OS), а в приватном хранилище образов на AWS.
- Веб-сервер. В облачной инфраструктуре важно иметь в наличии веб-сервер, который бы контролировал все входящие запросы к основному серверному приложению.

Решение
Инструменты для настройки сервисов на AWS
Мы будем использовать AWS CLI для установки и настройки веб-сервисов Amazon.Есть много удобных инструментов развертывания инфраструктуры, например Terraform и CloudFormation, которые позволяют автоматизировать деплой приложения. Работу с такими инструментами лучше рассматривать отдельно. Наша основная задача в этой главе ‒ разобраться, какие Amazon сервисы используются для развертывания приложения на AWS с помощью Docker.
Конфигурация инструментов
Устанавливаем AWS CLI
Устанавливаем AWS CLI при помощи следующей команды:pip install -Iv awscli==1.16.28
Для конфигурации AWS вводим следующие значения:
aws configure
# AWS Access Key ID [None]: YOUR_AWS_ACCESS_KEY
# AWS Secret Access Key [None]: YOUR_AWS_SECRET_KEY
# Default region name [None]: us-east-1
# Default output format [None]: json
Чтобы получить YOUR_AWS_ACCESS_KEY и YOUR_AWS_SECRET_KEY, нужно создать аккаунт пользователя AWS.
При создании аккаунта AWS, вы по умолчанию являетесь root-пользователем. Настоятельно не рекомендую настраивать инфраструктуру от имени root-пользователя. Поэтому, в целях безопасности, все команды AWS CLI в этом разделе будут совершаться от имени отдельно созданного AWS-пользователя с AdministratorAccess правами. Подробнее о создании Administrator-пользователя в веб-версии AWS можно прочитать в разделе Creating an Administrator IAM User and Group (Console).
Устанавливаем ECS CLI
После, необходимо установить ECS CLI, следуя инструкции.Реализация возможности хранения sensitive data приложения
После успешной конфигурации вы получите AWS credentials, которые понадобятся нам в дальнейшем. Поскольку эти данные должны быть скрыты от посторонних лиц, мы будем хранить их в зашифрованном виде в репозитории приложения. Эта возможность появилась совсем недавно, в версии Rails 5.2.Дополнение: если у вас версия Rails меньше 5.2, то можно использовать гем sekrets.
Чтобы начать работать с зашифрованными данными, нужно проинициализировать YAML-файл, в который в дальнейшем мы добавим AWS-ключи и другие sensitive данные. Переменные в нем будут сгруппированы по имени текущего окружения. Поскольку директория config примонтирована к контейнеру, как volume, мы можем ее изменить через bash самого контейнера.
docker-compose -f docker-compose.development.yml -p spreeproject exec server_app bash
В bash-контейнере вызовем следующую команду:
EDITOR=nano rails credentials:edit # or EDITOR=vim
И добавим в него необходимые нам ключи:
staging:
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 credentials можно с помощью следующей команды:
cat ~/.aws/credentials
После того, как мы добавили ключи, сохраняем файл. В результате, вы можете увидеть созданный файл config/credentials.yml.enc в директории приложения. Теперь версионирование доступно и для секретных ключей приложения.
Также был добавлен файл config/master.key, который содержит ключ RAILS_MASTER_KEY. Он необходим для расшифровывания данных. Этот ключ обязательно должен быть скрыт от посторонних лиц. В дальнейшем он будет задействован для запуска приложения на AWS.
По умолчанию в Rails 5.2, credentials не позволяет определять переменные текущего окружения (environment variables) через ENV-константу. Для этого в нашем приложении написан инициалайзер SecretsEnvLoader(config/secrets_env_loader.rb). Переменные development окружения хранятся в config/credentials.local.yml.
Создание Docker образа для веб-сервера Nginx
Зачем мы добавили веб-сервер?
Конечно, можно использовать только сервер приложения (Puma или Unicorn), но тогда вы лишитесь следующих преимуществ, которые предоставляют веб-серверы, такие как Nginx:- Статический редирект. Вы можете настроить Nginx на редирект всего HTTP-траффика на тот же URL с HTTPS. Таким образом, можно настроить более безопасную коммуникацию между сервером и клиентом.
- Multipart upload. Nginx лучше подходит для обработки multipart uploads. Nginx объединит все запросы и отправит один файл в Puma.
- Работа со статическими файлами. Используя Nginx, вы можете отдавать статические файлы (которые лежат в public директории Rails-приложения) без обращения к Puma. Этот способ в разы быстрее.
- Защита от DDoS. В Nginx встроены некоторые базовые средства защиты от DDoS-атак.
mkdir ./deploy && mkdir ./deploy/configs && cd $_
Проинициализируем Dockerfile для Nginx:
mkdir nginx && touch nginx/Dockerfile
И опишем в нем следующую инструкцию:
# В качестве родительского образа будем использовать готовый образ:
FROM nginx:1.16.0
# Все последующие команды будут выполняться от имени root-пользователя:
USER root
# Устанавливаем программное обеспечение, необходимое для корректной работы приложения:
ENV BUILD_PACKAGES curl
RUN apt-get update -qq && apt-get install -y $BUILD_PACKAGES
# Удалим дефолтную welcome-страницу nginx
RUN rm /usr/share/nginx/html/*
# Скопируем custom и default nginx конфигурации
COPY configs/nginx.conf /etc/nginx/nginx.conf
COPY configs/default.conf /etc/nginx/conf.d/default.conf
# Даем права www-data пользователю на системные директории для корректной работы nginx
RUN touch /var/run/nginx.pid && \
chown -R www-data:www-data /var/run/nginx.pid && \
chown -R www-data:www-data /var/cache/nginx && \
chown -R www-data:www-data /etc/nginx && \
chown -R www-data:www-data /var/log
# Все последующие команды будут выполняться от имени www-data пользователя:
USER www-data
# Команды, которые будут выполнены только перед запуском контейнера:
COPY ./docker-entrypoint.sh /
ENTRYPOINT ["./docker-entrypoint.sh"]
# Стандартная команда по запуску образа:
CMD ["nginx", "-g", "daemon off;"]
Конфигурация nginx будет храниться в директории nginx/configs:
mkdir nginx/configs && touch nginx/configs/nginx.conf
Определим следующие надстройки для nginx.conf:
# Настройки безопасности взяты из https://gist.github.com/plentz/6737338
# Указываем количество workers для запуска (обычно равно количеству CPU ядер)
worker_processes auto;
# Указываем максимальное количество открытых файлов за процесс
worker_rlimit_nofile 4096;
events {
# Указываем максимальное количество одновременных соединений, которые могут быть открыты worker процессом
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# ---------------------------------------------------------------------------
# Отключаем отображение версии Nginx в случае ошибок:
server_tokens off;
# https://developer.mozilla.org/en-US/docs/HTTP/X-Frame-Options
add_header X-Frame-Options SAMEORIGIN;
# При обслуживании пользовательского контента включайте заголовок X-Content-Type-Options: nosniff вместе с заголовком Content-Type:
add_header X-Content-Type-Options nosniff;
# Этот заголовок включает фильтр Cross-site scripting (XSS), который встроен в самые последние веб-браузеры.
add_header X-XSS-Protection "1; mode=block";
# ---------------------------------------------------------------------------
# Избегайте ситуаций, когда имя хоста слишком длинное при работе с vhosts
server_names_hash_bucket_size 64;
server_names_hash_max_size 512;
# Оптимизация производительности.
sendfile on;
tcp_nopush on;
# http://nginx.org/en/docs/hash.html
types_hash_max_size 2048;
# Включаем gzip для всего, кроме IE6.
gzip on;
gzip_disable "msie6";
# Конфигурация по умолчанию для бэкэнда приложения.
include /etc/nginx/conf.d/default.conf;
}
Также создадим файл default.conf
touch nginx/configs/default.conf
В файл default.conf добавим следующие конфигурации:
# Объявляем хост и порт upstream сервер. В данном случае APP_NAME заменится на наше server_app, а APP_PORT на 3000, на котором будем запущен сервер приложения Puma.
upstream app {
server APP_NAME:APP_PORT;
}
# Перенаправить адреса www на версию без www, а также позаботиться о перенаправлениях на HTTPS одновременно
server {
# Указываем что nginx будет слушать порт 8080 на текущем хосту. APP_VHOST заменится на хост EC2 инстанса на котором будет запущен nginx.
listen 8080;
server_name www.APP_VHOST;
return 301 http://APP_VHOST$request_uri;
}
server {
# Указываем что nginx будет слушать порт 8080. 'deferred' уменьшает количество формальностей между сервером и клиентом.
listen 8080 default deferred;
server_name APP_VHOST;
# Указываем директории для записи логов
access_log /var/log/nginx.access.log;
error_log /var/log/nginx.error.log info;
# Указываем редирект в случае ошибок 405 и 503
error_page 405 /405.html;
error_page 503 /503.html;
# Устанавливает максимально допустимый размер тела запроса клиента, указанного в поле заголовка запроса «Content-Length»
client_max_body_size 64M;
# Указываем время ожидания в сек, в течение которого клиентское соединение keep-alive будет оставаться открытым на стороне сервера.
keepalive_timeout 10;
# Путь к статическим ресурсам, который считывается из VOLUME текущего контейнера по маршруту STATIC_PATH.
root STATIC_PATH;
# Указываем маршрут для обслуживания статических ресурсов
location ^~ /assets/ {
gzip_static on;
expires max;
add_header Cache-Control public;
}
# Указываем доступные методы запросов
if ($request_method !~ ^(GET|HEAD|PUT|PATCH|POST|DELETE|OPTIONS)$ ){
return 405;
}
# Указываем локации для обсуживания статических файлов ошибки. Internal означет, что данное местоположение может использоваться только для внутренних запросов
location = /503.html {
internal;
}
location = /405.html {
internal;
}
# Все запросы буду обрабатываться блоком app_proxy объявленным ниже
location / {
try_files $uri @app_proxy;
}
# Объявляем блок который будет проксировать запросы на созданный вначале документа upstream server с нужныеми заголовками
location @app_proxy {
proxy_redirect off;
proxy_set_header Client-Ip $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
gzip_static on;
proxy_pass http://app;
}
}
Создадим файл docker-entrypoint.sh, который будет выполняться перед запуском контейнера:
touch nginx/docker-entrypoint.sh && chmod +x nginx/docker-entrypoint.sh
И опишем в нем команды для замены APP_NAME, APP_PORT и APP_VHOST в конфигах nginx в docker-entrypoint.sh:
#!/usr/bin/env bash
# Завершаем выполнение скрипта, в случае ошибки:
set -e
APP_NAME=${CUSTOM_APP_NAME:="server_app"} # имя контейнера с запущенным приложением Spree
APP_PORT=${CUSTOM_APP_PORT:="3000"} # порт, по которому доступно приложение Spree
APP_VHOST=${CUSTOM_APP_VHOST:="$(curl http://169.254.169.254/latest/meta-data/public-hostname)"} # Хост виртуального сервера на AWS по умолчанию ссылается на общедоступный DNS-адрес AWS EC2, "подтянув" эту информацию из метаданных EC2. Это позволяет нам динамически настраивать Nginx во время запуска контейнера
DEFAULT_CONFIG_PATH="/etc/nginx/conf.d/default.conf"
# Заменяем все инстансы плейсхолдеров на значения, указанные выше:
sed -i "s+APP_NAME+${APP_NAME}+g" "${DEFAULT_CONFIG_PATH}"
sed -i "s+APP_PORT+${APP_PORT}+g" "${DEFAULT_CONFIG_PATH}"
sed -i "s+APP_VHOST+${APP_VHOST}+g" "${DEFAULT_CONFIG_PATH}"
sed -i "s+STATIC_PATH+${STATIC_PATH}+g" "${DEFAULT_CONFIG_PATH}"
# Выполнение CMD из Dockerfile с передачей всех аргументов
exec "$@"
Вернемся в root-директорию приложения и добавим docker-compose.staging.yml, в котором будет присутствовать сервис для веб-сервера Nginx:
version: '3.1'
volumes:
redis:
postgres:
assets:
services:
db:
image: postgres:10
expose:
- 5432
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: spreedemo_staging
volumes:
- postgres:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready", "-U", "postgres"]
in_memory_store:
image: redis:4-alpine
expose:
- 6379
volumes:
- redis:/var/lib/redis/data
healthcheck:
test: ["CMD", "redis-cli", "-h", "localhost", "ping"]
server_app: &server_app
build: .
command: bundle exec puma -C config/puma.rb
entrypoint: "./docker-entrypoint.sh"
volumes:
- assets:/home/www/spreedemo/public/assets
- ./config/master.key:/home/www/spreedemo/config/master.key
environment:
RAILS_ENV: staging
DB_HOST: db
DB_PORT: 5432
DB_NAME: spreedemo_staging
DB_USERNAME: postgres
DB_PASSWORD: postgres
REDIS_DB: "redis://in_memory_store:6379"
SECRET_KEY_BASE: STUB
DEVISE_SECRET_KEY: STUB
depends_on:
- db
- in_memory_store
expose:
- 3000
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000"]
server_worker_app:
<<: *server_app
command: bundle exec sidekiq -C config/sidekiq.yml
entrypoint: ''
ports: []
depends_on:
- db
- server_app
- in_memory_store
healthcheck:
test: ["CMD-SHELL", "ps ax | grep -v grep | grep sidekiq || exit 1"]
web_server:
build: ./deploy/configs/nginx
volumes:
- assets:/home/www/spreedemo/public/assets
environment:
CUSTOM_APP_VHOST: server_app
STATIC_PATH: /home/www/spreedemo/public
ports:
- 80:8080
depends_on:
- server_app
healthcheck:
test: ["CMD-SHELL", "service nginx status || exit 1"]
Убедимся, что все development контейнеры, созданные и запущенные ранее, отключены:
docker-compose -p spreeproject -f docker-compose.development.yml down
И запустим staging-сервисы с помощью команды:
docker-compose -p spreeproject -f docker-compose.staging.yml up --build
После этого, Rails-приложение будет доступно через Nginx на 80-м порте машины, то есть localhost.
Cервер на AWS
В качестве платформы облачных сервисов мы будем использовать AWS, который позволяет запросто создать сервер EC2 для своих задач.AWS предоставляет вычислительные мощности в облаке. С помощью веб-сервиса EC2 вы можете создать для своих задач виртуальную машину с нужными характеристиками и разместить там ваше программное обеспечение. Именно на EC2 мы разместим наше приложение.
Firewall
Интернет-серверы важно обезопасить от несанкционированного доступа со стороны злоумышленников с помощью firewall policy. AWS предоставляет виртуальный firewall Security groups, позволяющий ограничить доступ к определенному сервису или группе сервисов. Проще говоря, с помощью Security Groups вы можете явно указать, какие порты сервера и каким клиентам будут открыты.Таким образом, к инстансу, на котором мы разместим наше приложение, нужно подвязать ряд ограничений доступа. Для этого создадим security-группу.
# GroupId группы серверного приложения обозначим, как `$STAGING_SERVER_APP_SG`
aws ec2 create-security-group \
--group-name staging-spreeproject-server-app \
--description "Staging Spree project Server App"
Все обращения к нашему приложению будут происходить через веб-сервер, который запущен на 80-м порту. Следовательно, мы должны открыть доступ любому клиенту (0.0.0.0/0) только на 80-й порт EC2 инстанса, на котором будет запущен Nginx.
aws ec2 authorize-security-group-ingress \
--group-id $STAGING_SERVER_APP_SG \
--protocol tcp \
--port 80 \
--cidr 0.0.0.0/0
Хранение изображений и быстрый доступ к ним
AWS предоставляет сервис хранения объектов S3. Этот сервис мы будем использовать для хранения изображений нашего приложения.Создадим его с помощью следующей команды:
aws s3api create-bucket --bucket spreeproject-staging
После, обновим переменные credentials, добавив туда имя созданного нами bucket:
RAILS_MASTER_KEY=YOUR_RAILS_MASTER_KEY EDITOR=nano rails credentials:edit
staging:
# ...
S3_BUCKET_NAME: 'spreeproject-staging'
S3_REGION: 'us-east-1'
S3_HOST_NAME: 's3.amazonaws.com'
Настраиваем Staging окружение
План действий
- Создаем Cluster с одним EC2 инстансом.
- Импортируем актуальные образы Rails-приложения и веб-сервера Nginx на ECR.
- Описываем и регистрируем Task Definition для запуска Rails-приложения и веб-сервера Nginx с помощью compose-файла.
- Создаем и запускаем Service с двумя Tasks, Rails-приложения и веб-сервера Nginx.
Решение
Создадим конфигурацию для будущего кластера spreeproject-staging, введя следующую команду в консоли:CLUSTER_NAME=spreeproject-staging # сохраним в глобальную переменную имя будущего кластера для удобного использования в дальнейшем.
ecs-cli configure --region us-east-1 --cluster $CLUSTER_NAME --config-name $CLUSTER_NAME
Для создания инстанса необходимо получить Subnets, VPС и Keypair для будущего инстанса:
aws ec2 describe-subnets
В результате команды выбираем SubnetId, у которых одинаковый VpcId и DefaultForAz параметр имеет значение true. И записываем их в переменную.
# Пример
AWS_SUBNETS=subnet-e49c19b8,subnet-20ae1647,subnet-319d1a1f
Также необходимо получить список доступных VPC:
aws ec2 describe-vpcs
Дальше VpcId этих subnets записываем в переменную $AWS_VPC. Например:
AWS_VPC=vpc-0e934a76
Теперь создадим keypair. Это ключ, по которому будет происходить вход на инстанс по SSH соединению. Это необходимо из-за соображений безопасности.
Создадим его с помощью следующей команды:
aws ec2 create-key-pair \
--key-name spreeproject_keypair \
--query 'KeyMaterial' \
--output text > ~/.ssh/spreeproject_keypair.pem
Разрешаем чтение этого файла:
chmod 400 ~/.ssh/spreeproject_keypair.pem
Если в дальнейшем нужно будет осуществить вход по SSH-ключу, просто обновите security group:
aws ec2 authorize-security-group-ingress \
--group-id $STAGING_SERVER_APP_SG \
--protocol tcp \
--port 22 \
--cidr 0.0.0.0/0
Команда для подключения к определенному инстансу по ssh-ключу:
ssh -i ~/.ssh/spreeproject_keypair.pem ec2-user@$EC2_PUBLIC_DOMAIN
AWS предоставляет ряд готовых images для инстансов для каждого региона. Мы воспользуемся образом ami-0a6be20ed8ce1f055 для региона us-east-1.
После создадим кластер spreeproject-staging, к которому будет привязан один инстанс EC2 типа t2.micro. Для этого вводим в терминале следующую команду:
ecs-cli up \
--keypair spreeproject_keypair \
--capability-iam \
--size 1 \
--instance-type t2.micro \
--vpc $AWS_VPC \
--subnets $AWS_SUBNETS \
--image-id ami-0a6be20ed8ce1f055 \
--security-group $STAGING_SERVER_APP_SG \
--cluster-config $CLUSTER_NAME \
--verbose
ECR
Актуализируем образ нашего Rails-приложения, вызвав команду:docker-compose -f docker-compose.development.yml -p spreeproject build
Теперь необходимо импортировать эти образы на AWS, для этого AWS предоставляет ECR. Проходим аутентификацию с помощью следующей команды:
$(aws ecr get-login --region us-east-1 --no-include-email)
После, создаем ECR репозиторий server_app для нашего Spree-приложения:
aws ecr create-repository --repository-name spreeproject/server_app
Далее, загрузим локальный образ в репозиторий 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:staging
docker push $YOUR_ECR_ID.dkr.ecr.us-east-1.amazonaws.com/spreeproject/server_app:staging
Сделаем тоже самое для 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:staging
docker push $YOUR_ECR_ID.dkr.ecr.us-east-1.amazonaws.com/spreeproject/web_server:staging
AWS Logs
Все логи контейнеров будут храниться в AWS Logs. Для это создадим группу log-groupaws logs create-log-group --log-group-name $CLUSTER_NAME.
ECS Tasks
После, создадим docker-compose.staging.yml как compose staging версии приложения для Task Definitionmkdir deploy/configs/ecs && touch deploy/configs/ecs/docker-compose.staging.yml
С помощью docker-compose.staging.yml мы указываем, какие сервисы и как необходимо будет запустить на EC2 инстансе.
Замените YOUR_ECR_ID, CLUSTER_NAME и YOUR_RAILS_MASTER_KEY на собственные значения:
version: '3'
volumes:
assets:
services:
web_server:
image: YOUR_ECR_ID.dkr.ecr.us-east-1.amazonaws.com/spreeproject/web_server:staging
volumes:
- assets:/home/www/spreedemo/public/assets
environment:
STATIC_PATH: /home/www/spreedemo/public
ports:
- 80:8080
links:
- server_app
logging:
driver: awslogs
options:
awslogs-group: CLUSTER_NAME
awslogs-region: us-east-1
awslogs-stream-prefix: web_server
healthcheck:
test: ["CMD-SHELL", "service nginx status || exit 1"]
server_app: &server_app
image: YOUR_ECR_ID.dkr.ecr.us-east-1.amazonaws.com/spreeproject/server_app:staging
command: bundle exec puma -C config/puma.rb
entrypoint: "./docker-entrypoint.sh"
ports:
- 3000
environment:
RAILS_ENV: staging
RAILS_MASTER_KEY: YOUR_RAILS_MASTER_KEY
DB_HOST: db
DB_PORT: 5432
DB_NAME: spreeproject_staging
DB_USERNAME: postgres
DB_PASSWORD: postgres
REDIS_DB: "redis://in_memory_store:6379"
volumes:
- assets:/home/www/spreedemo/public/assets
links:
- db
- in_memory_store
logging:
driver: awslogs
options:
awslogs-group: CLUSTER_NAME
awslogs-region: us-east-1
awslogs-stream-prefix: server_app
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000"]
worker_app:
<<: *server_app
command: bundle exec sidekiq -C config/sidekiq.yml
entrypoint: ''
logging:
driver: awslogs
options:
awslogs-group: CLUSTER_NAME
awslogs-region: us-east-1
awslogs-stream-prefix: worker_app
healthcheck:
test: ["CMD-SHELL", "ps ax | grep -v grep | grep sidekiq || exit 1"]
db:
image: postgres:10
environment:
POSTGRES_DB: spreeproject_staging
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- 5432
volumes:
- /postgres:/var/lib/postgresql/data
logging:
driver: awslogs
options:
awslogs-group: CLUSTER_NAME
awslogs-region: us-east-1
awslogs-stream-prefix: db
healthcheck:
test: ["CMD", "pg_isready", "-U", "postgres"]
in_memory_store:
image: redis:4-alpine
ports:
- 6379
volumes:
- /redis:/var/lib/redis/data
logging:
driver: awslogs
options:
awslogs-group: CLUSTER_NAME
awslogs-region: us-east-1
awslogs-stream-prefix: in_memory_store
healthcheck:
test: ["CMD", "redis-cli", "-h", "localhost", "ping"]
После заменяем переменные на ваши собственные значения:
Untitled
sed -i -e "s/YOUR_ECR_ID/$YOUR_ECR_ID/g" deploy/configs/ecs/docker-compose.staging.yml
sed -i -e "s/CLUSTER_NAME/$CLUSTER_NAME/g" deploy/configs/ecs/docker-compose.staging.yml
sed -i -e "s/YOUR_RAILS_MASTER_KEY/$YOUR_RAILS_MASTER_KEY/g" deploy/configs/ecs/docker-compose.staging.yml
ECS Task — это конфигурационный файл, в котором мы определяем, какие контейнеры необходимо запускать и каким образом.
Хорошая практика безопасности в Docker ‒ ограничивать потребление ресурсов, которые может задействовать контейнер. Нашему контейнеру мы укажем такой лимит, который необходим для его корректной работы, но не больше. В ECS есть возможность определить task size, то есть сколько CPU и памяти необходимо использовать задаче или контейнеру, запущенному этой задачей. Именно с помощью ecs-params.staging.yml мы указываем эти параметры.
Подробнее о структуре конфигурации задачи с помощью ecs-params.
touch deploy/configs/ecs/ecs-params.staging.yml
version: 1
task_definition:
ecs_network_mode: bridge
task_size:
cpu_limit: 768 # 896
mem_limit: 0.5GB # 900
services:
web_server:
essential: true
server_app:
essential: true
worker_app:
essential: true
db:
essential: true
in_memory_store:
essential: true
Регистрируем задачу для будущего сервиса ECS:
# create task definition for a docker container
ecs-cli compose \
--file deploy/configs/ecs/docker-compose.staging.yml \
--project-name $CLUSTER_NAME \
--ecs-params deploy/configs/ecs/ecs-params.staging.yml \
--cluster-config $CLUSTER_NAME \
create
После создания задачи, ей будет присвоенный определенный номер. Запишем этот номер в переменную TASK_NUMBER.
Запускаем Staging-приложение
ECS Services
Теперь создадим и запустим сервис по этой задаче.aws ecs create-service \
--service-name "spreeproject" \
--cluster $CLUSTER_NAME \
--task-definition "spreeproject-staging:$TASK_NUMBER" \
--desired-count 1 \
--deployment-configuration "maximumPercent=200,minimumHealthyPercent=50"
После того, как у всех задач Health Status станет HEALTHY, мы сможем получить доступ к нашему staging-приложению по публичному DNS инстанса.
Важно! После завершения работы с ECS, удалите RAILS_MASTER_KEY с файлов конфигураций. Повторюсь, что этот ключ не должен храниться в репозитории приложения.
Подведем итог
В этой части туториала мы развернули инфраструктуру staging-приложения:- реализовали возможность хранения чувствительных данных приложения;
- создали Docker образ для веб-сервера Nginx;
- подготовили конфигурацию для развертывания staging инфраструктуры на AWS;
- запустили staging-приложение на AWS.

DOU
DOU – Найбільша спільнота розробників України. Все про IT: цікаві статті, інтервʼю, розслідування, дослідження ринку, свіжі новини та події. Спілкування на форумі з айтівцями на найгарячіші теми та технічні матеріали від експертів. Вакансії, рейтинг IT-компаній, відгуки співробітників, аналітика...
