Привіт усім, я повернувся після довгої перерви! У мене було багато справ, але я досліджував цікаві концепції щодо того, як взяти невеликий додаток з швидким циклом розробки та знайти спосіб доставити його в продакшн за мінімальні кошти, тож давайте розглянемо це!
Основна мета цієї статті та серії публікацій, які я буду публікувати, — це в основному бізнес-напрямок стартапів. Акцент робиться на запуску додатку з нуля і масштабуванні його найгнучкішим, швидким і найбільш економічним способом!
У цій серії я покрию, що потрібно зробити з вашими існуючими Docker-образами, щоб вони працювали, як створити CI/CD Jenkins конвеєр для швидкої розробки для dev та prod середовищ, а також як розгорнути на AWS Elastic Beanstalk та додаткові налаштування, які ви можете зробити, і багато іншого!
Що ця стаття не буде охоплювати, так це створення React-додатку (або будь-якого іншого додатку) з нуля. Існує мільйон статей і туторіалів, які вже це роблять, тому я б не додав нічого нового, якщо б повторював це знову. Я припускаю, що у вас вже є якийсь додаток, якщо ні — ви можете просто використати docker-compose з набором офіційних зображень з DockerHub, таких як база даних, веб-сервер, бекенд тощо, адже суть цієї серії — не в розробці самого додатку, а в розробці інфраструктури та основи, яка підтримує ваш додаток.
Що ми будемо використовувати
Що стосується інфраструктури, ми будемо використовувати Docker-образи, docker-compose та Elastic Beanstalk від Amazon Web Services (AWS EBS).
Це може не звучати так гламурно, якщо ви очікували щось на кшталт Kubernetes, який, здається, всі намагаються використовувати для своїх додатків, навіть якщо це не найкращий варіант для вашого випадку, але послухайте мене. EBS має багато переваг. Це дуже швидко для налаштування інфраструктури, насправді багато з того, що він робить — це просто запуск попередньо збудованих (але настроюваних) CloudFormation шаблонів за лаштунками. Це дуже дешево, тому що воно просто запускає необхідне для того, щоб ваш хмарний середовище запустилося. Деплої відбуваються швидко, зазвичай це займає лише кілька хвилин. І, до того ж, деплої зазвичай настільки прості, що достатньо виконати одну команду в терміналі.
Тож чому не всі використовують EBS для своєї інфраструктури? Ну, багато хто і використовує, але… багато хто й не використовує, тому що або це насправді не підходить для їхнього випадку, або могло б підійти, але вони мають певні хибні уявлення. Справа в тому, що спочатку EBS підтримував лише обмежену кількість платформ, потім додав підтримку Docker, що значно розширило підтримку платформ, але навіть з Docker працювати з багаторівневою інфраструктурою було важко. Вам доводилося працювати з AWS-специфічними форматами файлів і конвертувати вашу існуючу Docker-інфраструктуру, з якою ваша команда була добре знайома, у синтаксис, специфічний для AWS. Тому, коли доводилося проходити через усі ці труднощі, було логічніше використовувати більш специфічні інструменти для Docker, які мали менший поріг для навчання, такі як Elastic Container Service (ECS), AWS Fargate або Elastic Kubernetes Service (EKS). Але тепер EBS підтримує просто завантаження файлу docker-compose.yml, і це працює з коробки, значно спрощуючи запуск Docker-інфраструктури!
Все ж, навіть з усіма спрощеннями запуску Docker-інфраструктури через docker-compose в EBS, EBS стає менш доцільним, чим більша ваша інфраструктура. Тому, якщо у вас вже є величезний трафік до вашого додатку, EBS є менш доцільним, ніж ECS, Fargate або EKS, саме тому я на початку сказав, що це в основному для стартапів, які постійно розвиваються та розгортають додатки, одночасно збільшуючи свою базу користувачів! Для компаній на стадії стартапу EBS важко перевершити.
Це має легку інтеграцію з платформами CI/CD, ви можете підтримувати тисячі клієнтів/користувачів за невеликі гроші в порівнянні з деякими більш потужними пропозиціями AWS, а також має підтримку багатоточкової інфраструктури з коробки! Також не забувайте, оскільки ми використовуємо Docker на всіх етапах, ми отримуємо додаткову перевагу, оскільки можемо в майбутньому перейти на більш потужну інфраструктуру, наприклад Kubernetes, без необхідності значної переробки коду!
Початок роботи з EBS
Хоча ви можете багато чого зробити з EBS, використовуючи лише графічну консоль на вебсайті AWS, я буду використовувати командний інтерфейс. Причина цього — ми будемо будувати наш CI/CD конвеєр навколо нього!
Спочатку вам потрібно буде встановити EBS CLI.
Я не буду розглядати інструкції, оскільки це просто повторення того, що вже написано ось тут.
https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/eb-cli3-install-advanced.html
Після того, як ви встановите CLI, використання цього інструменту складається з двох етапів. Спочатку потрібно активувати virtualenv, а потім можна його використовувати. Зазвичай активація virtualenv для Python-модуля здійснюється за допомогою команди "source" в будь-якому каталозі, який містить скрипт "activate", ось так:
source ~/.ebcli-virtual-env/bin/activate
Особисто я додаю команду alias в файл bashalias, що автоматично завантажується в мою сесію bash на Linux робочій станції, щоб зробити цю команду зручнішою. Тож ви можете додати наступне в свій файл bashaliases, якщо хочете зробити це так само:
echo "alias ebact='source ~/.ebcli-virtual-env/bin/activate'" >> ~/.bash_aliases
Зверніть увагу на подвійне ">>", яке додає до кінця файлу, на відміну від одиночного ">", що перезаписує файл. Додавши це до вашого файлу bash_alias, ви зможете просто ввести команду ebact (або будь-яку іншу, яку ви хочете) для активації EB CLI.
Тепер, перед тим як ми насправді використаємо EB CLI, потрібно внести деякі зміни в наші Docker-образи, щоб коректно їх розгорнути!
Налаштування вашого Docker-образу
Нижче показано, як виглядатимуть ваші стандартні команди docker run на вашій розробницькій машині. Зверніть увагу, що я сказав "docker run" замість файлу docker-compose для вашого dev середовища. Це тому, що зазвичай на практиці ви будете мати окремий CI/CD конвеєр для вашого фронтенду та бекенду, а потім будете будувати код для них, тестуючи їх окремо. Потім на продакшн-середовищі ви з'єднаєте ваші протестовані Docker-образи разом за допомогою docker-compose, docker swarm або Kubernetes, і розгорнете їх.
docker run -p 5002:5002 -d --name backend imageName docker run -p 3000:3000 -d --name frontend imageName
На жаль, якщо ви перетворите ці команди docker run у налаштування docker-compose на віддаленому сервері та спробуєте їх розгорнути, це не працюватиме.
Чому?
По-перше, хоча це не показано, ваші dev середовища, ймовірно, використовують "localhost" як свою кінцеву точку, тобто URL, за яким ви звертаєтесь до них, а також URL, за яким фронтенд і бекенд надсилають запити один одному. Тепер, щоб вирішити це, є кілька способів виправити вашу кінцеву точку, і це залежить від того, як ви налаштували свій конкретний додаток.
Як ви це зробите, залежить від вас, але я просто розповім, що я зазвичай роблю залежно від розміру або специфікацій проєкту. Для React-додатку я часто читаю кінцеву точку з файлу .env у кореневому каталозі і переписую її на етапі CI/CD-будування, щоб вказати правильний URL. Тому всі мої запити пишуться ось так, щоб я міг редагувати один файл і мати всі запити, які вказують на одне місце:
const responseData = await fetch(https://${process.env.BASE_API_URL}/)
Об'єкт process.env є спеціальним, він є "вбудованим" об'єктом у node.js. Пам'ятайте, що React.js і Next.js побудовані на основі Node.js, тому це працює для них так само. Це означає, що мені не потрібно імпортувати його в кожен файл, він вже визначений для всіх моїх файлів.
Отже, ми визначаємо наші запити, як показано вище, з файлом .env, який виглядає ось так:
const BASE_API_URL="whatevermyendpointis.com" module.exports = BASE_API_URL
Причина, чому я роблю це так, полягає в тому, що іноді я працюю з next.js, а іноді з звичайним React, тому можуть бути невеликі відмінності у тому, як налаштовуються кінцеві точки між двома. Наприклад, у додатку на next.js ви можете налаштувати кінцеву точку у файлі next.config.js, ось так:
const BASE_API_URL = require("./env"); /** * @type {import('next').NextConfig} */ const nextConfig = { /* config options here */ 'staticPageGenerationTimeout': '600', reactStrictMode: false, compiler: { removeConsole: true, }, eslint: { // Warning: This allows production builds to successfully complete even if // your project has ESLint errors. ignoreDuringBuilds: true, }, env: { BASE_API_URL: BASE_API_URL }, async rewrites() { return [{ source: '/example*', destination: 'example/', },] }, } module.exports = nextConfig
Але оскільки я визначив це у стандартному файлі .env, я можу імпортувати його в конфігурацію next.js як звичайну змінну, або я можу імпортувати її в конфігурацію React як стандартну змінну, якщо я не використовую next.js, і таким чином я зможу переписати лише один файл .env під час етапу побудови CI/CD, який буде імпортований у залежності від проєкту.
Тепер, після того як ми поговорили про налаштування кінцевої точки, давайте обговоримо, якою має бути ваша кінцева точка! Ви можете припустити, що кінцева точка має бути доменним ім'ям віддаленого хоста, на який ви розгортаєте ваш додаток, і ви частково праві.
У чому ж краса docker-compose? Він налаштовує власну мережу та DNS-резолюцію для контейнерів, тому контейнери можуть посилатися один на одного за їхніми іменами, як це визначено в файлі docker-compose.yml. Тепер ви, мабуть, думаєте, що ми можемо просто використовувати DNS-імена контейнерів docker-compose як кінцеві точки наших серверів… і знову ж таки, це частково вірно. Ми робимо обидва варіанти!
Отже, я покажу вам робочий шаблон docker-compose і поясню.
version: '3.8' services: frontend: image: frontend:latest ports: - 3300:3000 what-to-play-backend: image:backend:latest ports: - 5002:5002 mongo: image: mongo restart: always environment: # MONGO_INITDB_ROOT_USERNAME: root # MONGO_INITDB_ROOT_PASSWORD: example MONGO_INITDB_DATABASE: test nginx: image: nginx:latest ports: - 80:80 volumes: mongodb_data:
Окрім додавання бази даних з опційними іменем користувача / паролем, ви помітите ще одну важливу деталь — я додав nginx! Чому нам потрібен nginx і що він робить?
Ну, що часто ставить в скрутне становище людей при використанні docker-compose або інших контейнерних сервісів з різними механізмами мережі, так це те, що вам зазвичай потрібно поставити проксі перед ними для коректного управління та маршрутизації всіх запитів.
Насправді, найпростіше це продемонструвати за допомогою веб-браузера.
У вкладці мережі Chrome ви можете побачити мережевий запит, який відправляється, коли я натискаю кнопку "login" на одному зі своїх веб-додатків.
Тепер URL:
what-to-play.codingbyisaac.com/users/login
не є бекенд API, яке обробляє аутентифікацію. У нашому docker-compose ім'я DNS бекенду, який займається аутентифікацією, це “backend”. Тож чому запити не відправляються туди? Ну, тому що браузер не має доступу до цього домену, бо, якщо пам'ятаєте, він визначений тільки для контейнерів у нашому файлі docker-compose, а наш браузер не є контейнером.
Отже, якщо браузер надішле запити на автентифікацію до “backend.com”, вони не будуть розв’язані.
Щоб обійти це, я відправляю всі запити від фронтенду та бекенду на what-to-play.codingbyisaac.com, де знаходиться проксі-сервер nginx! Тому в принципі всі запити, незалежно від того, чи це фронтенд, чи бекенд, надсилаються на проксі nginx, і відтак, в залежності від шляху запиту, якщо він містить /api/whatever, запит перенаправляється на бекенд. Оскільки наша інсталяція nginx є контейнером Docker в тій самій мережі, контейнер nginx може передавати трафік на нього, просто використовуючи імена контейнерів як кінцеві точки. Отже, ваш nginx.conf виглядатиме приблизно так:
worker_processes 4; events { worker_connections 1024; } http { upstream backend { server docker-compose-name-of-backend:5002; } upstream frontend { server docker-compose-name-of-frontend:3000; } server { listen 80; include /etc/nginx/mime.types; proxy_set_header X-Forwarded-Host $host:$server_port; proxy_set_header X-Forwarded-Server $host; add_header 'Content-Security-Policy' 'upgrade-insecure-requests'; limit_except GET POST PATCH DELETE{ deny all; } proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; #trailing / on locations are required to match subpaths. location ~ /\. { deny all; #this is regex to block any path beginning with a . } location / { proxy_pass http://frontend; } location /api/ { proxy_pass http://backend; } } }
Тепер у цьому файлі є деякі додаткові налаштування заголовків nginx, які я розгляну детальніше в наступній частині цієї серії, оскільки це в основному додаткова безпека, тому не переживайте, ми до цього дійдемо!
Але в основному всі запити потрапляють у контейнер nginx, і потім nginx перенаправляє їх у Docker мережу звідти. Отже, тепер ваш додаток має всі кінцеві точки, правильно налаштовані для розгортання на віддаленому хості!
Робота з інструментом EB CLI
Тепер, коли ми налаштували наш додаток, можемо створити середовище EB та розгорнути наш додаток через CLI, який ми встановили раніше.
Без особливих налаштувань, створення та розгортання — це прості однорядкові команди.
Спочатку створіть директорію, в якій знаходяться ваші файли для розгортання (яка на даний момент, по суті, є тільки docker-compose.yml), переконайтеся, що ви активували віртуальне середовище ebcli, і потім перейдіть до цієї директорії та виконайте:
eb create your-env-name
Він проведе вас через кілька загальних питань інсталятора, на які можна відповісти відповідно до ваших потреб, наприклад, якщо у вас є існуючий cname для вашого середовища тощо. Налаштуйте їх як вам зручно, або виберіть за замовчуванням, оскільки я повернуся до конфігурації більш детально в наступній частині цієї серії.
Після цього, переконавшись, що ви все ще в тій самій директорії з вашими файлами для розгортання:
eb use your-environment-name eb deploy
Команда “eb use” просто встановлює ваше середовище за замовчуванням, а команда eb deploy тепер зіб'є ваші файли, завантажить їх у бакет S3 і використає docker-compose.yml як джерело для вашого додатку.
Що далі?
Це все! З налаштованими кінцевими точками та створеним середовищем EB, всього лише одна команда “eb deploy” і ви виконали своє перше розгортання на EB!
Тепер, звісно, є багато чого, що можна налаштувати з цього моменту, але частина 1 вже досить довга, тому поверніться до наступної статті, де я покажу вам, як додати SSL-шифрування до ваших розгортань EB, налаштувати пайплайн Jenkins для ваших розгортань, доопрацювати середовище EB і більше!
Перекладено з: Launch your production 3-tier application with Docker Compose and Elastic Beanstalk