Контейнеризация приложений сегодня является не просто модным трендом. Объективно такой подход позволяет во многом оптимизировать процесс серверной разработки путем унификации поддерживаемых инфраструктур (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 --list
Available 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, которые при запуске никаких действий не совершают, но подготавливают окружение для работы с выбранной инфраструктурой. Без них большинство других тасков выполниться не сможет. Это «стандартный» workaround (обход) того факта, что 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 --list
Available 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 для платформ MacOS и 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.

Буду рад ответить в комментариях на любые вопросы, а также выслушать конструктивную критику.
Поделиться с друзьями
-->

Комментарии (28)


  1. Fafnir
    27.06.2016 18:05

    Если гоняете микро-сервисы, попробуйте Kubernetes. Используем его уже где-то полгода, не без проблем, но полет нормальный.


    1. rumkin
      27.06.2016 18:59
      +1

      Кубернет требует работы через google cloud, что выглядит очень-очень странным решением. Ну, и ко всему требует коммиты на каждый чих, что лично мне совсем не нравится и является ненужным порогом при освоении технологии.


      1. kop
        27.06.2016 19:06

        > Кубернет требует работы через google cloud

        Его можно запросто поднять хоть на AWS, хоть на своем железе.

        > Ну, и ко всему требует коммиты на каждый чих

        Вовсе не обязательно хранить ваши конфигурации в VCS. С ними можно работать как душе угодно (хоть через UI, хоть редактировать напрямую с сервера). А в идеале вообще использовать Helm.


  1. kop
    27.06.2016 18:56

    Аналогично, первая мысль после прочтения: «Зачем писать велосипеды, если есть K8S?».


    1. renskiy
      27.06.2016 21:09
      +4

      Насколько я понял, вы имеет в виду Kubernetes? Или по крайней мере практики его использования в качестве средства для оркестрации Docker.

      Fabricio не является заменой Kubernetes/Swarm. Главная цель Fabricio — кастомизация логики деплоя через создание типовых классов с описанием логики запуска контейнера (которая в случае Docker Compose и Kubernetes прописывается в файлах YAML) на полноценном языке программирования с всеми вытекающими преимуществами ООП.

      При этом на целевой инфраструктуре, в теории, может быть развернута любая система запуска Docker контейнеров (мы ориентируемся в этом случае на Swarm, который начиная с версии Docker 1.12 является частью самого Docker).


  1. Andrey_Zentavr
    28.06.2016 02:34

    не знаю чем вам Ансибл не сгодился? мы вполне себе неплохо дженкинс+ансибл используем.
    Нужно вам найти ДевОПСа хорошего.

    Кстати, до этого мы лет пять строили велосипеды через Фабрику. Теперь вот всем этим занимается Ансибл.


    1. renskiy
      28.06.2016 08:58
      +3

      Ansible нам напомнил о некогда довольно популярном Apache Ant, только последний использует XML, а Ansible более модный YAML. Оба ведут себя неплохо до тех пор, пока идешь по дорожке, протоптанной авторами (или пока им хватает энтузиазма развивать продукт).

      Да и накладно содержать специалиста по Ansible только ради DevOps обязанностей.


      1. SamVimes
        28.06.2016 10:07

        А в чем проблема запускать шелл-, питон-скрипты из Ansible?


        1. renskiy
          28.06.2016 10:24
          +2

          Так и проблем наверно нет, но их можно и без Ansible запускать. А структура проекта Ansible и без внешних скриптов выглядит довольно запутанно.

          С Fabric/Fabricio я могу выбрать такую структуру, которую захочу. И прописать всю логику в одном файле (fabfile.py), либо создать целый подпроект (внутри модуля fabfile) с разделением на роли, инфраструктуры и пр.


          1. foxmuldercp
            28.06.2016 10:30

            На первый взгляд запутано, но в принципе достаточно неплохо структурировано


      1. foxmuldercp
        28.06.2016 10:30

        Соглашусь, к сожалению, при попытке уйти вправо/влево от того, что предоставляет модуль Ansible, приходится городить raw команды, например, к тем же iptables у меня треть плейбука именно raw, потому как штатно нужного мне функционала, пока не придвидится, и учитывая ньюансы, не проще ли отказаться от обертки и дергать ssh -c somecommmand напрямую…


      1. VolCh
        28.06.2016 10:56

        Вопрос надо ставить «можем ли мы себе позволить отдельного человека по DevOps» и если можем, то при выборе продуктов — его слово решающим должно быть, а не разработчиков или админов.


  1. VolCh
    28.06.2016 10:52

    Для того, чтобы понять эту причину, я задам простой вопрос: кто-нибудь из вас умеет программировать на YAML?

    Как по мне, то если процесс деплоя нельзя выразить в декларативной форме, то что-то то ли с архитектурой, то ли с процессом не так. А так тот же Ansible имеет и переменные, и условия, и циклы.
    Среди недостатков можно также упомянуть отсутствие autocomplete и возможности прочитать исходный код используемой функции, отсутствие подсказок о типе и количестве аргументов и прочих радостей использования IDE

    Вот это более весомый аргумент, но в принципе решаемый плагинами к IDE. Например, в PhpStorm YAML конфиги инфраструктуры Symfony довольно хорошо интегрированы. И для Ansible плагин есть https://plugins.jetbrains.com/plugin/7792


  1. welcomerooot
    28.06.2016 11:13

    Спасибо за статью!
    А можете немого поподробнее про rollback и откат миграций? Как это делает fabric? Очень интересен этот момент.


    1. renskiy
      28.06.2016 12:04
      +2

      Команда rollback последовательно запускает два стандартных метода у базовой сущности Fabricio (Container): migrate_back и revert. По-умолчанию, у первого метода отсутствует реализация, но потомки класса могут это поведение переопределить. DjangoContainer при откате миграций на предыдущую версию парсит список примененных миграций у текущей версии контейнера и у той, на которую нужно откатиться, составляет разницу и откатывает только те миграции, которых нет в старом контейнере.


  1. varnav
    28.06.2016 17:24
    +1

    Кто-нибудь из вас умеет программировать на YAML?


    Я умею! А на Ruby и Python — наоборот, не умею.


  1. gudron
    28.06.2016 18:39

    А как дела с сетью? Имеется ввиду, сеть на уровне связки докер-контейнеров. А не на уровне конкртеного контейнера.
    docker-compose позволяет настраивать некоторые вещи через:

    networks:
    app_net:
    driver: bridge
    ipam:
    driver: default
    config:
    - subnet: 172.16.100.0/28
    ip_range: 172.16.100.0/28
    gateway: 172.16.100.7


    1. renskiy
      28.06.2016 19:33

      Управление сетью не предусмотрено, но у класса Container есть свойство network, в котором можно задать имя сети.


  1. ushliy
    28.06.2016 19:24
    +1

    ИМХО, Ansible обеспечивает огромный простор для деплоя разнообразных приложений. На данный момент у меня им управляется зоопарк из Docker, LXC и физических серверов. Прекрасно дружит с Jenkins. Присоединюсь ко мнению, что с архитектурой что-то не так, если не хватает декларативного YAML для деплоя. Да и с теми же кастомными скриптами, проще один плейбук запустить, чем ломиться по ssh и выполнять поштучно. А если серверов больше одного, так и тем более.
    З.Ы.: Зачем отдельный специалист по Ansible, если там настолько хорошая документация, что рядовой *nix админ со средним английским осилит за неделю, максимум


    1. gudron
      29.06.2016 16:55

      Не могли бы вы уточнить как именно ansible у вас работает? Посмотрел на http://docs.ansible.com/ansible-container/. Не увидел большой разницы с тем же docker-compose. Увидел, что умеет билдить, пушить и пулить контейнеры, но так и не понял каким образом работает, то что называется деплоем.

      Не увидел, опять же, как делать rollback.

      или вы просто через ansible, вызывается на host-машине pull && run контейнеров?


      1. ushliy
        29.06.2016 17:04

        Ansible разворачивает проект на новых серверах, после сборки в Jenkins пулит на существующие. Согласен, Compose это все умеет. Но как-то не прижился, потом написали свои костыли и не вспоминали про него)


        1. renskiy
          29.06.2016 17:19

          Если проект один, то костыли да, решают проблему. Но копировать костыли из проекта в проект очень накладно. А нам приходится часто начинать новые проекты. Поэтому все костыли мы унифицировали и вынесли в отдельный проект.


          1. ushliy
            29.06.2016 17:25

            Костыли не настолько страшные и весьма унифицируемые. В рамках работы Ansible. Но по сравнению с развивающимся весьма динамично Swarm, это костыли. Но опять же, учитывая, что Docker — лишь часть структуры, Swarm не решит всех проблем, а Ansible — вполне


      1. VolCh
        30.06.2016 06:37

        1. Ansible готовит новый хост (грубо, чистая ось только с sshd) к исполнению докер-контейнеров.
        2. Ansible не просто запускает контейнеры, а обеспечивает им всю среду, в частности обеспечивает наличие на хосте файлов, которые монтируются в контейнер (или файлов, которые копируются в контейнер при билде).


  1. slainte
    29.06.2016 19:56

    Ещё есть готовый open source докер PaaS — deis.io


  1. rudenkovk
    30.06.2016 11:06

    Ринат, во многом ты прав. Но в сути, это обычная проблема, когда программисты пишут деплой, вы делаете так, как привыкли и вам удобнее. Это будет продолжаться ровно до момента, когда вы передадите весь этот набор деплоя в руки эксплуатации/девопсов. После этого будет резко удобно все писать на ansible (или прочих salt/puppet). Ровно потому, что при всей простоте того же fabric, поддержке все же надо будет освоить прилично писать на питоне, и поддерживать ваше понимание об организации кода. Я уже имел опыт перевода с хорошо написанного fabric на ansible. И на ansible, в результате, код получался короче, проще, красивее и быстрее. Особенно если нужно разливать на очень заковыристые inventory.


    PS по мне, inventory, это вообще беда fabric.


    1. renskiy
      01.07.2016 10:09

      Это будет продолжаться ровно до момента, когда вы передадите весь этот набор деплоя в руки эксплуатации/девопсов.

      На этом месте, как правило, и работа наша заканчивается — это специфика разработки аутсорс. А так-как эксплуатация полностью продолжается на стороне заказчика, то нам нет смысла развивать у себя экспертизу в этой области.

      Есть правда у нас в разработке пара типовых решений PaaS, где скорее всего процесс эксплуатации будет на нашей стороне. Поживем увидим.


      1. rudenkovk
        01.07.2016 10:17

        Опыт сын ошибок трудных :)
        Особенно для mass deploy.