Як я вже зазначав у статті Jack and the Elastic Beanstalk, Elastic Beanstalk є чудовим PaaS-рішенням від AWS, яке дозволяє розробникам розгортати та запускати свої додатки на інстансах EC2. Я трохи експериментував з різними способами пришвидшити команду eb deploy
з моєї локальної машини і зміг трохи її пришвидшити. Хоча, чесно кажучи, я сподівався на кращі результати. Я опишу результати, щоб показати, чого я навчився.
Зверніть увагу, що проєкт і всі його файли доступні на GitHub за посиланням tongueroo/hi на гілці docker-cache
.
Розуміння того, як EB обробляє розгортання
Для EB я використовую Docker, оскільки це стандартизує одиницю розгортання. Існує деякі способи розгорнути свій код на EB, коли використовуєш Docker. Можна мати або файл Dockerfile
, або файл Dockerrun.aws.json
у проєкті. EB шукає обидва ці файли, щоб побудувати Docker-образ під час розгортання. Скрипт, який насправді обробляє команду docker build
, знаходиться за шляхом /opt/elasticbeanstalk/hooks/appdeploy/pre/03build.sh
.
Отже, чим менше робити у файлі Dockerfile
, тим швидше буде розгортання на EB. Насправді, якщо подивитись на скрипт 03build.sh, можна побачити, що коли існує лише файл Dockerrun.aws.json
, EB створює простий Dockerfile
з інструкцією FROM та EXPOSE, як визначено у файлі Dockerrun.aws.json
. Це має сенс, тому що тоді немає залежностей, які потрібно завантажувати, і розгортання буде менш ймовірно зламатися. Все, що вам фактично потрібно, це щоб Docker-образ існував і його можна було успішно завантажити. Отже, ідеальне розгортання — це коли Docker-образ будується, файл Dockerrun.aws.json
оновлюється з ім’ям образу, і він відправляється на EB. Я зазвичай використовую тільки файл Dockerrun.aws.json
для розгортання.
Хоча використання лише Dockerrun.aws.json
є ідеальним для продакшн-розгортань, з точки зору розробки це неприємно. Кожного разу будувати Docker-образ після кожної незначної зміни і повторно розгортати на середовищі — це повільно. Це особливо болісно, якщо використовувати великий Ruby-образ з повільним інтернет-з’єднанням. Ідеальний підхід — це не розгортати контейнер на моєму Mac OSX, а на сервері для побудови. Але коли я розробляю, я вважаю за краще працювати на своїй власній машині, коли це можливо.
Розробники переважно обирають eb deploy
Я пам’ятаю, як один розробник неодноразово казав мені, що йому більше подобається команда eb deploy
, тому що вона зазвичай швидша. По суті, після початкового eb deploy
, поки сервери не є новими інстансами, розгортання зазвичай відбувається дуже швидко, оскільки шари кешу Docker вже існують. Коли ви намагаєтесь швидко розвивати та постійно деплоїти зміни на середовище staging, використання потоку eb deploy
має повний сенс. Однак це не має сенсу в продакшн-середовищі, оскільки ви не хочете, щоб інстанси завантажували багато залежностей при масштабуванні AutoScaling.
Будування Docker-образа безпосередньо на інстансі виглядало для мене дуже неприємно. Я уявляв собі, як всі залежності завантажуються, встановлюються та будуються щоразу, коли код розгортається на середовищі EB. А що буде, якщо apt-get або RubyGems не працюватимуть? Це виглядає так, наче може піти багато чого не так, особливо якщо Dockerfile
повинен встановлювати довгий список речей.
Зазвичай я будую Docker-образ заздалегідь, використовуючи файл Dockerrun.aws.json
і розгортаю його як одиницю, замість того, щоб використовувати eb deploy
.
Компроміс концепції кешування Docker-образу
Потім я натрапив на пост на StackOverflow How to speed up CI build times when using docker? і це нагадало мені про концепцію, яку я вже пробував раніше. Ідея проста: створюєте базовий Docker-образ кешу, який вже містить залежності та пакети додатка, і відправляєте цей образ у Docker-реєстр. Потім ви використовуєте цей Docker-образ як початкову базову точку, і eb deploy
будує з цієї точки. Таким чином, коли ви використовуєте eb deploy
, він буде лише деплоїти інкрементальні зміни в коді. Якщо ці інкрементальні зміни не вимагають завантаження великої кількості нових залежностей, що часто трапляється, розгортання буде швидким.
Це компроміс, який дозволяє отримати найкраще з обох світів. Ви отримуєте швидкість eb deploy
, коли шари кешу Docker існують. Ви також отримуєте надійність завдяки тому, що пакети вже закешовані в базовому Docker-образі. Залежності вже вмонтовані і завантажуються разом з початковим Docker-образом. Тепер EB більше не встановлює пакети під час масштабування AutoScaling. Не важливо, якщо apt-get або RubyGems не працюють у цей момент.
Великий недолік цього підходу полягає в тому, що вам потрібно не забувати оновлювати кеш-образ Docker час від часу, і ця ручна задача є досить неприємною для людей. Якщо ви лінуєтесь і забуваєте оновити базовий образ, то повільність і всі ненадійні проблеми, про які я згадував, знову з’являються, коли код відхиляється від базової точки.
Легке створення кеш-образу Docker
Тому я вирішив спростити ручну задачу створення цього базового образу і оновлення початкової точки для інструкції FROM. Структура, яку я в результаті налаштував, полягала в створенні двох Docker-файлів: стандартного Dockerfile
та файлу Dockerfile.base
.
Ось як ці два файли виглядають спочатку. Спочатку Dockerfile.base
:
FROM ruby:2.3.3RUN apt-get update && \
apt-get install -y build-essential nodejs && \
rm -rf /var/lib/apt/lists/* && apt-get clean && apt-get purgeWORKDIR /app
ADD Gemfile /app/Gemfile
ADD Gemfile.lock /app/Gemfile.lock
RUN bundle install --system
Важливою частиною тут є те, що я встановлюю RubyGems як частину базового Dockerfile.base
. Для файлу Dockerfile
:
FROM tongueroo/hiWORKDIR /app
ADD Gemfile /app/Gemfile
ADD Gemfile.lock /app/Gemfile.lock
RUN bundle install --systemADD . /app
RUN bundle install --systemRUN chmod a+x bin/*EXPOSE 3000CMD ["bin/web"]
Інструкція FROM для Dockerfile
спочатку має tongueroo/hi
, але вона автоматично змінюється за допомогою інструменту ufo. Інструмент ufo добре описано в статті: Ufo — Easily Build Docker Images and Ship Containers to AWS ECS. Інструмент ufo має зручну команду ufo docker base
, яка будує базовий образ і автоматично оновлює інструкцію FROM в поточному Dockerfile. Ви можете побачити, як оновлюється інструкція FROM у прикладі нижче.
$ ufo docker base
Pushed tongueroo/hi:base-2016-12-01T14-49-53-ca5cbd3 docker image. Took 2s.
The Dockerfile FROM statement has been updated with the latest base image:
tongueroo/hi:base-2016-12-01T14-49-53-ca5cbd3
$ grep FROM Dockerfile
FROM tongueroo/hi:base-2016-12-01T14-49-53-ca5cbd3
$
Все, що вам потрібно зробити, це спочатку встановити "tongueroo/hi" в інструкції FROM, а потім команда ufo docker base
додасть до кінця "tongueroo/hi" тег з міткою часу як частину цього процесу:
- Створіть і завантажте Docker-образ за допомогою файлу
Dockerfile.base
і назвіть його з міткою часу кешованого імені: tongueroo/hi:timestamp.
2.
Оновіть інструкціюFROM
у Dockerfile на ім’я кешованого Docker-образу з міткою часу, який щойно був завантажений.
Використовуючи команду ufo docker base
, ви можете легко оновити кеш-образ за допомогою однієї команди і зафіксувати новий Dockerfile. Було б добре поставити це в заплановану задачу десь, щоб вона завжди автоматично оновлювала кешований образ.
Проєкт та всі його файли доступні на GitHub за посиланням tongueroo/hi на гілці docker-cache
.
Тести швидкості
Давайте проведемо кілька елементарних тестів швидкості, щоб побачити, яку різницю дає ця стратегія кешування. Для кожного тесту я почну з нового інстанса без жодних Docker-кешованих шарів. Потім я двічі запущу команду eb deploy
для кожного випадку.
Перший випадок — це коли я використовую лише один Dockerfile
і будую все з образу ruby 2.3.3.
$ time eb deploy hi-web-stag # перший деплой: без Docker-шаp
real 4m58.785s
$ time eb deploy hi-web-stag # другий деплой: існують Docker-кешовані шари
real 0m30.050s
$
Другий випадок — коли я використовую Dockerfile
і Dockerfile.base
.
$ time eb deploy hi-web-stag # перший деплой: без Docker-шаp
real 3m34.994s
$ time eb deploy hi-web-stag # другий деплой: існують Docker-кешовані шари
real 0m30.287s
$
Ви можете побачити, що для eb deploy
, коли немає Docker-кешованих шарів, є покращення швидкості розгортання на 1 хвилину 26 секунд. Коли ж існують Docker-кешовані шари, на другому деплої цей показник не змінюється, що логічно.
Для простих проєктів, таких як приклад tongueroo/hi, виграно близько півтори хвилини. Для реальних проєктів різниця значно більша. Я скопіював Gemfile з одного з реальних застосунків, над якими я працюю, і провів той самий тест. Без кешованого Docker-образу:
$ time eb deploy hi-web-stag
real 7m5.983s
$ time eb deploy hi-web-stag
real 0m36.247s
$
З кешованим Docker-образом:
$ time eb deploy hi-web-stag
real 4m47.825s
$ time eb deploy hi-web-stag
real 0m30.378s
$
Отже, різниця становить 2 хвилини 18 секунд у випадку з реальним проєктом.
Підсумок
Для будь-якого eb deploy
після початкового розгортання, незалежно від стратегії, яку ми вибираємо, процес буде дуже швидким. Це тому, що після першого деплою Docker-шари вже існують на інстансі EC2, і тому при другому розгортанні EB не потрібно буде знову будувати ці Docker-шари. Цей швидкий eb deploy
тривалістю близько 30 секунд — приємна перевага.
Використання цього додаткового базового Docker-образу як кешу — це одна з стратегій для пришвидшення команди eb deploy
. Однак результати не були настільки хорошими, як я сподівався для нових інстансів, але це дозволяє заощадити кілька хвилин.
Одне зауваження: ця стратегія також допоможе пришвидшити час побудови CI, якщо ви працюєте на недедикованому обладнанні і використовуєте один з хмарних інструментів CI, таких як CircleCI. Вона пришвидшується, оскільки залежності більше не потрібно встановлювати щоразу. Якщо ви працюєте на спеціалізованому обладнанні, ця стратегія також дасть перевагу, тому що сервер буде мати кешовані Docker-шари. Запуск спеціалізованого інстансу для використання Docker-шарів завжди буде найшвидшим підходом, і чи варто керувати цим сервером — це вже ваш вибір. Інша техніка, яку я охоплю в іншому пості, — це налаштування віддаленого Docker-демона, щоб збірка і пуш відбувалися на інстансах EC2, де мережа буде дуже швидкою.
Дякую за те, що дочитали до кінця. Якщо цей пост виявився корисним, я буду дуже вдячний, якщо ви порекомендуєте його (натиснувши кнопку аплодисментів), щоб інші могли знайти його також! Також підключайтеся до мене на LinkedIn.
P.S. Обов'язково підпишіться на розсилку BoltOps, щоб отримувати безкоштовні поради та оновлення DevOps.
Перекладено з: Speeding Up AWS Elastic Beanstalk’s eb deploy