Практики Continuous Delivery з Docker (огляд та відео)
Свій блог ми розпочнемо з публікацій, створених за мотивами останніх виступів нашого технічного директора distol (Дмитро Столярова). Всі вони відбулися у 2016 році на різних професійних заходах та були присвячені темі DevOps та Docker. Одне відео з зустрічі Docker Moscow в офісі Badoo ми вже публікували на сайті. Нові супроводжуватимуться статтями, що передають суть доповідей. Отже…
31 травня на конференції RootConf 2016, що проходила в рамках фестивалю «Російські інтернет-технології» (РІТ++ 2016), секція «Безперервне розгортання та деплой» відкрилася доповіддю «Найкращі практики Continuous Delivery з Docker». У ньому було узагальнено та систематизовано найкращі практики побудови процесу Continuous Delivery (CD) з використанням Docker та інших Open Source-продуктів. З цими рішеннями ми працюємо у production, що дозволяє спиратися на практичний досвід.
Якщо у вас є можливість витратити годину на відео з доповіддюрекомендуємо подивитися його повністю. В іншому випадку нижче представлена основна вичавка в текстовому вигляді.
Continuous Delivery з Docker
Під Безперервна доставка ми розуміємо ланцюжок заходів, внаслідок яких код програми з Git-репозиторію спочатку приходить на production, а потім потрапляє до архіву. Виглядає вона так: Git → Build (складання) → Test (тестування) → Release (реліз) → Operate (наступне обслуговування).
Більшість доповіді присвячена стадії build (складання програми), а теми release і operate порушені оглядово. Йтиметься про проблеми та патерни, що дозволяють їх вирішити, а конкретні реалізації цих патернів можуть бути різними.
Чому тут взагалі потрібний Docker? Не просто так ми вирішили розповісти про практику Continuous Delivery у контексті цього Open Source-інструменту. Хоча його застосуванню присвячена вся доповідь, багато причин розкриваються вже при розгляді головного патерну викочування коду програми.
Головний патерн викочування
Отже, при викаті нових версій програми ми неодмінно стикаємося з проблемою простою, що утворюється під час перемикання production-сервера Трафік зі старої версії програми на нову не може перемикатися миттєво: попередньо ми повинні переконатися, що нова версія не тільки успішно викачена, а й «прогріта» (тобто повністю готова до обслуговування запитів).
Таким чином, деякий час обидві версії програми (стара та нова) працюватимуть одночасно. Що автоматично призводить до конфлікту загальних ресурсів: мережі, файлової системи, IPC і т.п. З Docker ця проблема легко вирішується запуском різних версій програми в окремих контейнерах, для яких гарантується ізоляція ресурсів у межах одного хоста (сервера/віртуальної машини). Звичайно, можна обійтися деякими хитрощами і без ізоляції зовсім, але якщо існує готовий і зручний інструмент, тобто зворотний резон — не ігнорувати їх.
Контейнеризація дає багато інших плюсів при депло. Будь-яка програма залежить від певної версії (або діапазону версій) інтерпретатора, наявності модулів/розширень тощо, а також їх версій. І відноситься це не тільки до безпосереднього виконуваного середовища, але і до всього оточення включаючи системний софт та його версії (аж до використовуваного Linux-дистрибутиву). Завдяки тому, що контейнери містять не лише код додатків, а й попередньо встановлений системний та прикладний софт потрібних версій, про проблеми із залежностями можна забути.
Узагальним головний патерн викочування нових версій з урахуванням перерахованих факторів:
Спочатку стара версія програми працює у першому контейнері.
Потім нова версія викочується і прогрівається в другому контейнері. Примітно, що ця нова версія може нести не тільки оновлений код програми, але й будь-яких його залежностей, а також системних компонентів (наприклад, нову версію OpenSSL або всього дистрибутива).
Коли нова версія повністю готова до обслуговування запитів, трафік перемикається з першого контейнера на другий.
Тепер стару версію може бути зупинено.
Такий підхід із розгортанням різних версій програми в окремих контейнерах дає ще одну зручність. швидкий відкат на стару версію (достатньо переключити трафік на потрібний контейнер).
Підсумкова перша рекомендація звучить так, що навіть Капітану не причепитися: «[при організації Continuous Delivery з Docker]Використовуйте Docker[і розумійте, що це дає]». Пам'ятайте, що це не срібна куля, що вирішує будь-які проблеми, але інструмент, який дає чудовий фундамент.
Відтворюваність
Під "відтворюваністю" ми розуміємо узагальнений набір проблем, з якими зустрічаються при експлуатації додатків. Йдеться про такі випадки:
Сценарії, перевірені відділом якості на staging повинні точно відтворюватися в production.
Програми публікуються на серверах, які можуть отримати пакети з різних дзеркал репозиторіїв (згодом вони оновлюються, а разом з ними — і версії додатків, що встановлюються).
"У мене локально все працює!" (… та розробників на production не пускають.)
Потрібно перевірити щось у старій (архівній) версії.
...
Загальна їх суть зводиться до того, що необхідна повна відповідність оточень, що використовуються (а також відсутність людського фактора). Як же гарантувати відтворюваність? Робити Docker-образи на базі коду з Git, а потім використовувати їх для будь-яких завдань: на тестових майданчиках, у production, на локальних машинах програмістів… При цьому важливо мінімізувати дії, які виконуються після складання образу: чим простіше — тим менша ймовірність помилок.
Інфраструктура – це код
Якщо вимоги до інфраструктури (наявність серверного ПЗ, його версії тощо) не формалізувати та не «програмувати», то викочування будь-якого оновлення програми може закінчитися сумними наслідками. Наприклад, на staging ви вже перейшли на PHP 7.0 і переписали код відповідним чином тоді його поява на production з яким-небудь старим PHP (5.5) неодмінно когось здивує. Нехай про велику зміну версії інтерпретатора ви не забудете, але "диявол криється в деталях": сюрприз може опинитися в мінорному оновленні будь-якої залежності.
Вирішальний цю проблему підхід відомий як IaC (Infrastructure as Code, «інфраструктура як код») і передбачає збереження вимог до інфраструктури разом із кодом програми. При його використанні розробники та DevOps-фахівці можуть працювати з одним Git-репозиторієм програми, але над різними його частинами. З цього коду в Git створюється образ Docker, в якому програма розгорнута з урахуванням всієї специфіки інфраструктури. Простіше кажучи, скрипти (правила) збирання образів повинні лежати в одному репозиторії з вихідними джерелами і разом мерзтися.
У разі багатошарової архітектури програми — наприклад, є nginx, який стоїть перед програмою, вже запущеною всередині Docker-контейнера, — образи Docker повинні створюватися з коду Git для кожного шару. Тоді в першому образі буде додаток з інтерпретатором та іншими найближчими залежностями, а в другому — nginx, що стоїть вище.
Docker-образи, зв'язок з Git
Усі Docker-образи, які збираються з Git, ми поділяємо на дві категорії: тимчасові та релізні. Тимчасові образи тегуються за назвою гілки Git, можуть перезаписуватися черговим коммітом і викочуються тільки для попереднього перегляду (не для production). У цьому їхня ключова відмінність від релізних: ви ніколи не знаєте, який конкретно коміт у них перебуває.
Має сенс збирати в часові образи: гілку master (можна автоматично викочувати на окремий майданчик, щоб бачити поточну версію master), гілки з релізами, гілки конкретних нововведень.
Після того, як попередній перегляд тимчасових образів приходить до необхідності переведення в production, розробники ставлять певний тег. За тегом автоматично збирається релізний образ (його тегу відповідає тег з Git) та викочується на staging. У разі його успішної перевірки відділом якості він потрапляє на виробництво.
dapp
Все описане (викочування, складання образів, подальше обслуговування) можна реалізувати самостійно за допомогою Bash-скриптів та інших «підручних» засобів. Але якщо так робити, то в якийсь момент реалізація призведе до великої складності та поганої керованості. Розуміючи це, ми прийшли до створення своєї спеціалізованої Workflow-утиліти для побудови CI/CD. dapp.
Її вихідний код написаний на Ruby, відкритий та опублікований на GitHub. На жаль, документація на даний момент найслабше місце інструменту, але ми працюємо над цим. І ще раз напишемо і розповімо про dapp, т.к. нам щиро не терпиться поділитися його можливостями з усім зацікавленим співтовариством, а поки що надсилайте свої issues і pull requests та/або стежте за розвитком проекту на GitHub.
Оновлено 13 серпня 2019 р.: в даний час проект dapp перейменований на werf, його код повністю переписаний на Go, а документацію значно покращено.
Кубернетес
Інший готовий Open Source-інструмент, що вже здобув значне визнання у професійному середовищі, — це Кубернетес, кластер для управління Docker. Тема його використання в експлуатації проектів, побудованих на Docker, виходить за межі доповіді, тому виступ обмежений оглядом деяких цікавих можливостей.
Для викату Kubernetes пропонує:
readiness probe – перевірку готовності нової версії програми (для перемикання трафіку на неї);
rolling update – послідовне оновлення образу в кластері з контейнерів (відключення, оновлення, підготовка до запуску, перемикання трафіку);
synchronous update — оновлення образу в кластері з іншим підходом: спочатку половині контейнерів, потім інших;
canary releases – запуск нового образу на обмеженій (невеликій) кількості контейнерів для моніторингу аномалій.
Оскільки Continuous Delivery — це не тільки реліз нової версії, у Kubernetes є низка можливостей для подальшого обслуговування інфраструктури: вбудований моніторинг та логування по всіх контейнерах, автоматичне масштабування та ін. Все це вже працює і тільки чекає на грамотне впровадження у ваші процеси.
Підсумкові рекомендації
Використовуйте Docker.
Робіть Docker-образи програми для всіх потреб.
Дотримуйтесь принципу "Інфраструктура - це код".
Зв'язати Git з Docker.
Регламентуйте порядок викочування.
Використовуйте готову платформу (Kubernetes чи іншу).
Відео та слайди
Відео з виступу (близько години) опубліковано на YouTube(безпосередньо доповідь починається з 5-ї хвилини - за посиланням відтворення з цього моменту).