Туториал по развертыванию Rails-приложений на Amazon с помощью Docker. Часть 2
Всем привет! В этой части мы продолжаем наш туториал по развертыванию Rails-приложения на AWS с помощью Docker. Напомню, что в предыдущей части туториала мы:
- рассмотрели преимущества Docker для развертывания приложений;
- запустили наше Spree-приложение и все зависимые сервисы на локальной машине.
Какую проблему решаем
После проверки корректности работы приложения в локальном окружении, необходимо развернуть идентичную инфраструктуру в облаке. Этой задаче и будет посвящена вторая часть нашего туториала. Итак, приступим к работе!
Решение: 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 c двумя EC2 инстансами. Image source
Cluster — это группа EC2 инстансов, на которых запущен один или несколько Docker контейнеров. Если ваш проект состоит из нескольких приложений, вы можете разместить их в одном кластере в виде отдельных сервисов. Такой подход позволяет более эффективно использовать доступные ресурсы и минимизировать время установки.
Task definition
Task definition
Эта инструкция описывает, как и какие Docker контейнеры необходимо запускать. Task Definition можно создать путем определения следующих параметров:
- какой образ Docker использовать для запуска определенного контейнера;
- сколько центральных процессоров (ЦП) и памяти использовать для каждой задачи;
- связи между контейнерами, если нужна коммуникации между ними;
- команда для запуска контейнера;
- команда, которую должен запустить контейнер при запуске;
- способ логирования контейнеров;
- команда для проверки состояния контейнера.
Из вышеперечисленных пунктов, Task Definition напоминает конфигурацию, которую мы создаем для Docker compose. Так и есть, и благодаря ранее установленному ECS CLI, мы сможем создавать task definition наших сервисов, работая с синтаксисом Docker compose. Но это не значит, что на вашем инстансе все сервисы будут запущены через Docker compose: ECS Agent конвертирует инструкцию из синтаксиса Docker compose в свой нативный синтаксис. Поэтому будьте внимательны, так как не вся конфигурация, которая доступна в Docker compose, будет работать в ECS. По этой причине в туториале мы будем создавать отдельный docker-compose файл, который будет использоваться для AWS.
Task
Создание Task на основе Task Definition. Image source
Если Task Definition — это инструкция по запуску одного или нескольких контейнеров, то Task представляет из себя один или несколько запущенных контейнеров. У задачи есть три состояния:
- Running — все контейнеры запущены и работают корректно;
- Pending — контейнеры в процессе запуска;
- Stopped — контейнеры остановлены.
Service
Группировка Task в один сервис. Image Source
В Service можно вынести одну или несколько задач, где вы определяете, какое минимальное и максимальное количество задач необходимо запустить. Это позволит вам настроить автомасштабирование и балансировку нагрузки для вашего приложения. Так, в случае превышения CPU из-за определенной задачи, которая выполняется, ECS Agent может выделить еще один инстанс и распределить трафик с помощью ELB на время загруженности.
Разумеется, мы можем ограничить максимальное количество задач, которые могут быть запущены, поскольку для запуска дополнительных задач используются дополнительные ресурсы в виде новых инстансов, а это требует финансовых затрат.
Подведем итоги. Если говорить кратко о компонентах ECS, это:
- Cluster ‒ группа связанных между собой EC2 инстансов;
- ECR ‒ приватный репозиторий, на котором хранятся Docker-образы нашего приложения;
- Task definition ‒ инструкция по запуску контейнеров на EC2 кластера;
- Task ‒ один или несколько запущенных контейнеров;
- Service ‒ совокупность запущенных Tasks.
Теперь, когда мы рассмотрели, из чего состоит ECS, приступим к практической реализации.
План действий
- Настроить инструменты для работы с AWS.
- Реализовать возможность безопасного хранения таких чувствительных данных нашего приложения, как пароли от внешних сервисов, ключей доступа и т. д.
- Создать образ Docker для веб-сервера Nginx.
- Подготовить staging-инфраструктуру на AWS.
- Запустить staging-приложение на AWS.
Туториал состоит из трех частей. На этой инфографике вы можете увидеть этапы, из которых будет состоять цикл статей туториал. Текущая часть затрагивает вторую часть Staging:
Пошаговое описание цикла туториалов и ПО, которые мы будем применять
Инфраструктура staging-приложения практически идентична той, что мы разворачивали локально. Впрочем, есть несколько отличий:
- Сборка образов. В отличие от локального окружения, образ основного приложения мы будем хранить не на машине, где запускается приложение (Host OS), а в приватном хранилище образов на AWS.
- Веб-сервер. В облачной инфраструктуре важно иметь в наличии веб-сервер, который бы контролировал все входящие запросы к основному серверному приложению.
Схема инфраструктуры, которую мы хотим развернуть:
Инфраструктура staging-окружения
Решение
Инструменты для настройки сервисов на 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-атак.
Прежде чем мы перейдем к созданию образа для Nginx, создадим отдельную директорию deploy, в которой будем хранить все конфигурации внешних сервисов, связанных с деплоем приложения и AWS.
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; # Устанавливаем максимальное количество времени кэширования. Мы можем сделать это, потому что конвейер ресурсов Ruby Rails md5 хеширует все имена файлов для нас. Когда файл изменяется, его md5 будет меняться, и кеш будет автоматически отключен. Другие структуры также могут сделать это. 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; }
Создадим файл 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 на
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"
Все обращения к нашему приложению будут происходить через веб-сервер, который запущен на
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-group
aws logs create-log-group —log-group-name $CLUSTER_NAME.
ECS Tasks
После, создадим docker-compose.staging.yml как compose staging версии приложения для Task Definition
mkdir 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.
В следующей части мы развернем готовое к масштабированию production-приложение. Stay tuned!