Туторіал по розгортанню Rails-додатків на Amazon за допомогою Docker. Частина 2

Всім привіт! У цій частині ми продовжуємо наш туторіал по розгортанню Rails-додатки на AWS з допомогою Docker. Нагадаю, що в попередній частині туториала ми:

Яку проблему вирішуємо

Після перевірки коректності роботи програми в локальному оточенні, необхідно розгорнути ідентичну інфраструктуру в хмарі. Цьому завданню й присвячена друга частина нашого туториала. Отже, приступимо до роботи!

Рішення: AWS ECS

ECS запускає ваші контейнери в кластері примірників Amazon EC2 з попередньо встановленим Docker-му. ECS керує установкою контейнерів, масштабуванням, моніторингом та управлінням ними через API і Консоль управління AWS.

Ви можете розмістити і запустити Docker контейнер програми на EC2 вручну. Але ви позбавите себе наступних речей:

ECS Cluster

Cluster c двома EC2 инстансами. Image source

Cluster — це група EC2 инстансов, на яких запущено один або кілька Docker контейнерів. Якщо ваш проект складається з кількох додатків, ви можете розмістити їх в одному кластері у вигляді окремих сервісів. Такий підхід дозволяє більш ефективно використовувати наявні ресурси і мінімізувати час установки.

Task definition

Task definition

Ця інструкція описує, як і які Docker контейнери необхідно запускати. Task Definition можна створити шляхом визначення наступних параметрів:

З перерахованих вище пунктів, 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 представляє з себе один або кілька запущених контейнерів. У завдання є три стани:

Service

Групування Task в один сервіс. Image Source

У Service можна винести одну або кілька завдань, де ви визначаєте, яку мінімальну і максимальну кількість завдань необхідно запустити. Це дозволить вам налаштувати автомасштабування і балансування навантаження для вашої програми. Так, у разі перевищення CPU з-за певної задачі, що виконується, ECS Agent може виділити ще один інстанси і розподілити трафік з допомогою ELB на час завантаженості.
Зрозуміло, ми можемо обмежити максимальну кількість завдань, які можуть бути запущені, оскільки для запуску додаткових завдань використовуються додаткові ресурси у вигляді нових инстансов, а це вимагає фінансових витрат.

Підведемо підсумки. Якщо говорити коротко про компонентах ECS, це:

Тепер, коли ми розглянули, з чого складається ECS, приступимо до практичної реалізації.

План дій

Туторіал складається з трьох частин. На цій інфографіці ви можете побачити етапи, з яких буде складатися цикл статей туторіал. Поточна частина торкається другу частину Staging:

Покрокове опис циклу туториалов і, які ми будемо застосовувати

Інфраструктура staging-програми практично ідентична тій, що ми розгортали локально. Втім, є кілька відмінностей:

Схема інфраструктури, яку ми хочемо розгорнути:

Інфраструктура 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, створимо директорію 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:

версія: '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.

Сервер на 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 оточення

План дій

  1. Створюємо Cluster з одним EC2 инстансом.
  2. Імпортуємо актуальні образи Rails-додатки і веб-сервера Nginx на ECR.
  3. Описуємо і реєструємо Task Definition для запуску Rails-додатки і веб-сервера Nginx з допомогою compose-файлу.
  4. Створюємо і запускаємо 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 на власні значення:

версія: '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

версія: 1
task_definition:
 ecs_network_mode: bridge

task_size:
 cpu_limit: 768 # 896
 mem_limit: 0.5 GB # 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 container docker
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-додатки:

У наступній частині ми розгорнемо готове до масштабування production-додаток. Stay tuned!


Перша частина туториала

Опубліковано: 13/06/19 @ 11:44
Розділ Безпека

Рекомендуємо:

Крос-культурна комунікація за Хофстеде: дані замість здогадок
Портрет ІТ-спеціаліста — 2019. Інфографіка
Як QA пройшов шлях до зарплати в $5000 за 8 років
Виведення сайту по продажі торгового обладнання в топ 3
Java дайджест #43: Jakarta EE і реактивний Spring