Автоматизація розгортання Docker-контейнерів на довільній інфраструктурі



Контейнеризація додатків сьогодні є не просто модним трендом. Об'єктивно такий підхід дозволяє багато в чому оптимізувати процес серверної розробки шляхом уніфікації підтримуваних інфраструктур (dev, test, staging, production). Що в підсумку призводить до значного скорочення витрат упродовж усього циклу життя серверного додатка.

Хоча велика частина з перерахованих достоїнств Docker є правдою, тих, хто на практиці зіткнеться з контейнерами, може спіткати легке розчарування. І так як Docker не є панацеєю, а лише входить в список «лікарських засобів» від рецепту автоматичного деплоя, розробникам доводиться освоювати додаткові технології, писати додатковий код і т. д.

Розробкою свого власного рецепту автоматизації конфігурування і розгортання контейнерів на різні інфраструктури ми займалися в останні кілька місяців, паралельно з комерційними проектами. А отриманий результат практично повністю задовольнив наші поточні потреби в автодеплое.

Вибір інструменту


Коли вперше виникає потреба в автоматичному деплое Docker-додатків, то перше, що підказує досвід (або пошуковик) — це постаратися пристосувати для цієї задачі Docker Compose. Спочатку задуманий як інструмент для швидкого запуску контейнерів на тестовій інфраструктурі, Docker Compose, однак, можна використовувати і на бою. Ще один варіант, який розглядався нами в якості відповідного інструменту — Ansible, що має у своєму складі модулі для роботи з контейнерами і образами Docker.

Але ні те, ні інше рішення нас як розробників не влаштувало. І головна причина цього криється в способі описи конфігурацій — за допомогою файлів YAML. Для того, щоб зрозуміти цю причину, я поставлю просте запитання: хто-небудь з вас вміє програмувати на YAML? Здивуюся, якщо хтось відповість ствердно. Звідси головний недолік всіх інструментів, що використовують для конфігурування всілякі варіанти розміток (від INI/XML/JSON/YAML до більш екзотичних, на кшталт HCL — неможливість розширення логіки стандартними способами. Серед недоліків можна також згадати відсутність autocomplete і можливості прочитати вихідний код використовуваної функції, відсутність підказок про тип і кількість аргументів та інших радощів використання IDE.

Далі, ми подивилися в бік Fabric і Capistrano. Для конфігурування вони використовують звичайну мову програмування (Python і Ruby, відповідно), тобто дозволяють писати кастомний логіку всередині конфигурационых файлів з можливістю використання зовнішніх модулів, чого ми, власне, і добивалися. Ми не стали довго вибирати між Fabric і Capistrano і майже відразу зупинилися на першому. Наш вибір, в першу чергу, був обумовлений наявністю експертизи в Python і майже повною її відсутністю в Ruby. Плюс, збентежила досить складна структура проекту Capistrano.

Загалом, вибір припав на Fabric. Завдяки його простоті, зручності, компактності і модульності він оселився в кожному нашому проекті, дозволяючи зберігати сам додаток і логіку його розгортання в одному сховищі.

Наш перший досвід написання конфига для автоматичного деплоя за допомогою Fabric дав можливість виконувати основні дії по оновленню програми на бойовий і тестовій інфраструктурах і дозволив економити значний обсяг часу розробника (окремого реліз-менеджера у нас немає). Але при цьому файл з налаштуваннями вийшов досить громіздким і важким для перенесення на інші проекти. Ми задумалися над тим, як завдання адаптації конфіги під інший проект вирішувати легше і швидше. В ідеалі хотілося отримати універсальний і компактний шаблон конфігурації розгортання на стандартний набір наявних інфраструктур (test, staging, production). Приміром, зараз наші конфіги для автодеплоя виглядають приблизно так:
fabfile.py
from fabric import colors, api as fab
from fabricio import tasks, docker

##############################################################################
# infrastructures
##############################################################################


@tasks.infrastructure
def STAGING():
fab.env.update(
roledefs={
'nginx': ['devops@staging.example.com'],
},
)


@tasks.infrastructure(color=colors.red)
def PRODUCTION():
fab.env.update(
roledefs={
'nginx': ['devops@example.com'],
},
)

##############################################################################
# containers
##############################################################################


class NginxContainer(docker.Container):

image = docker.Image('nginx')

ports = '80:80'

##############################################################################
# tasks
##############################################################################


nginx = tasks.DockerTasks(
container=NginxContainer('nginx'),
roles=['nginx'],
)


Наведений приклад коду містить в собі опис декількох стандартних дій для управління контейнером, в якому запускається відомий веб сервер. Ось що ми побачимо, попросивши Fabric вивести список команд з директорії з цим файлом:
fab --listAvailable commands:

PRODUCTION
STAGING
nginx deploy[:force=no,tag=None,migrate=yes,backup=yes] — backup -> pull -> migrate -> update
nginx.deploy deploy[:force=no,tag=None,migrate=yes,backup=yes] — backup -> pull -> migrate -> update
nginx.pull pull[:tag=None] — pull Docker image from registry
nginx.revert revert — revert Docker container to previous version
nginx.rollback rollback[:migrate_back=yes] — migrate_back -> revert
nginx.update update[:force=no,tag=None] — recreate Docker container


Тут варто трохи пояснити, що крім типових deploy, pull, update і пр. в списку присутні також таски PRODUCTION і STAGING, які при запуску ніяких дій не роблять, але готують оточення для роботи з обраної інфраструктурою. Без них більшість інших тасков пройти не зможе. Це «стандартний» обхідний шлях (обхід) того факту, що Fabric не підтримує явного вибору інфраструктури для роботи. Отже, для того, щоб запустити процес розгортання/оновлення контейнера з Nginx, наприклад на STAGING, потрібно виконати наступну команду:
fab STAGING nginx

Як вже неважко було здогадатися, практично вся «магія» приховано за цими рядками:
nginx = tasks.DockerTasks(
container=NginxContainer('nginx'),
roles=['nginx'],
)


Ciao, Fabricio!

Загалом, дозвольте представити Fabricio — модуль, який розширює стандартні можливості Fabric, додаючи в нього функціонал для роботи з контейнерами Docker. Розробка Fabricio дозволила нам перестати думати про складність реалізації автоматичного деплоя і цілком зосередитися на вирішенні поставлених бізнес-завдань.

Дуже часто ми стикаємося з ситуацією, коли на бойовий інфраструктури замовника є обмеження на доступ в інтернет. У цьому випадку ми вирішуємо завдання деплоя за допомогою приватного Docker Registry, запущеного в локальній мережі адміністратора (або просто на його робочому комп'ютері). Для цього в прикладі вище потрібно всього лише замінити тип тасков DockerTasks PullDockerTasks. Список доступних команд в цьому випадку набуде вигляду:
fab --listAvailable commands:

PRODUCTION
STAGING
nginx deploy[:force=no,tag=None,migrate=yes,backup=yes] — prepare -> push -> backup -> pull -> migrate -> update
nginx.deploy deploy[:force=no,tag=None,migrate=yes,backup=yes] — prepare -> push -> backup -> pull -> migrate -> update
nginx.prepare prepare[:tag=None] — prepare Docker image
nginx.pull pull[:tag=None] — pull Docker image from registry
nginx.push push[:tag=None] — push Docker image to registry
nginx.revert revert — revert Docker container to previous version
nginx.rollback rollback[:migrate_back=yes] — migrate_back -> revert
nginx.update update[:force=no,tag=None] — recreate Docker container


Нові команди prepare і push готують скачують образ з основного Registry і закачують його в локальний, звідки вже через туннель образ потрапляє на бойову інфраструктуру (команда pull). Запустити приватний Registry локально можна виконавши в терміналі наступний рядок коду:
docker run --name registry --publish 5000:5000 --detach --restart always registry:2

Приклад зі складанням образу відрізняється від перших двох, аналогічно, тільки типом тасков — в даному випадку це BuildDockerTasks. Список команд для завдань зі складанням такий же, як і в попередньому прикладі, за винятком того, що команда prepare замість скачування образу з основного Registry будує його з локальних джерел.

Для використання PullDockerTasks і BuildDockerTasks необхідний встановлений на комп'ютері адміністратора клієнт Docker. Після анонсування публічних бета-версій Docker для платформ mac os і Windows це вже не така головний біль для користувачів.

Fabricio є повністю відкритим проектом, будь-які доопрацювання вітаються. При цьому ми самі активно продовжуємо доповнювати його новими можливостями, виправляти баги і усувати неточності, постійно вдосконалюючи необхідний інструмент. На поточний момент основними можливостями Fabricio є:
  • складання образів Docker
  • запуск контейнерів з образів з довільними тегами
  • сумісність з режимом паралельного виконання тасков на різних хостах
  • можливість відкоту до попереднього стану (rollback)
  • робота з публічними та приватними Docker Registry
  • групування типових завдань в окремі класи
  • автоматичне застосування і відкат міграцій Django-додатків
Встановити і спробувати Fabricio можна через стандартний пакетний менеджер Python:
pip install --upgrade fabricio

Підтримка поки обмежена Python 2.5-2.7. Дане обмеження є прямим наслідком підтримки відповідних версій модулем Fabric. Сподіваємося, що в найближчому майбутньому Fabric обзаведеться можливістю запуску на Python 3. Хоча необхідності в цьому немає особливої — в більшості дистрибутивів Linux, а також MacOS, версія 2 є дефолтної версією Python.

Буду радий відповісти в коментарях на будь-які питання, а також вислухати конструктивну критику.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.