Туторіал по розгортанню 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 ? сукупність запущених Завдань.
Тепер, коли ми розглянули, з чого складається 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:
версія: '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 оточення
План дій
- Створюємо 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 на власні значення:
версія: '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-додатки:
- реалізували можливість зберігання чутливих даних програми;
- створили Docker спосіб для веб-сервера Nginx;
- підготували конфігурацію для розгортання staging інфраструктури на AWS;
- запустили staging-додаток на AWS.
У наступній частині ми розгорнемо готове до масштабування production-додаток. Stay tuned!
Опубліковано: 13/06/19 @ 11:44
Розділ Безпека
Рекомендуємо:
Крос-культурна комунікація за Хофстеде: дані замість здогадок
Портрет ІТ-спеціаліста — 2019. Інфографіка
Як QA пройшов шлях до зарплати в $5000 за 8 років
Виведення сайту по продажі торгового обладнання в топ 3
Java дайджест #43: Jakarta EE і реактивний Spring