Статьи

Сине-зеленое развертывание в Docker Swarm с плагином рабочего процесса Jenkins

Идея этой статьи состоит в том, чтобы изучить способы развертывания релизов с Jenkins в Docker Swarm без простоев. Мы будем использовать сине-зеленую процедуру. Более подробную информацию о процессе и одной из возможных реализаций можно найти в статье « Процедура развертывания, автоматизации и самовосстановления Blue-Green» . Одним из недостатков процесса, который мы использовали в этой статье, является сама Ansible. Хотя это, вероятно, лучший инструмент для обеспечения и оркестровки, у него были некоторые недостатки, когда мы пытались использовать его в качестве инструмента для развертывания контейнеров. Это особенно очевидно, когда процесс сложный. В Ansible отсутствуют некоторые конструкции, распространенные в большинстве языков программирования. На этот раз мы попытаемся реализовать тот же процесс, но с помощью плагина Jenkins Workflow, который был разработан и внесен в проект открытого исходного кода Jenkins CloudBees .

В двух словах, мы сделаем следующее.

  1. Предоставление кластера Рой
  2. Предоставление прокси сервиса
  3. Вытащите последнюю версию
  4. Разверните последний выпуск параллельно с текущим
  5. Запустите прединтеграционные тесты, которые подтвердят, что все работает правильно
  6. Обновите прокси сервис
  7. Запустите тесты после интеграции, которые подтвердят, что все работает правильно через прокси-сервис
  8. Остановить предыдущий выпуск

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

Помимо этих шагов, процесс должен быть способен откатиться в случае, если что-то пойдет не так. Дополнительной функцией, которую мы представим, является запрос на масштабирование службы и поддержание масштабированного количества экземпляров в последующих развертываниях.

Сначала мы увидим работу в действии, а затем обсудим, как она была выполнена.

Настройка Docker Swarm Cluster и Jenkins

Начнем с создания кластера, необходимого для запуска Docker Swarm. Мы будем использовать VirtualBox и Vagrant для создания виртуальных машин, а затем предоставим им Ansible . Я не буду вдаваться в подробности того, как работают пьесы Ansible, которые мы будем запускать, поскольку эту информацию можно найти в других публикациях в этом блоге. Настройка будет аналогична описанной в статье Развертывание контейнеров с помощью Docker Swarm и Docker Networking, опубликованной недавно. Основным отличием будет добавление Jenkins и плагина Workflow.

Я предполагаю, что у вас уже установлены VirtualBox , Vagrant и Git . Если вы пользователь Windows, пожалуйста, следуйте инструкциям, описанным в разделе Запуск виртуальных машин Linux в Windows, прежде чем углубляться в описанные ниже.

Давайте идти! Мы можем создать серверы, выполнив следующую команду.

1
2
3
4
5
6
7
git clone https://github.com/vfarcic/blue-green-docker-jenkins.git
 
cd blue-green-docker-jenkins
 
vagrant plugin install vagrant-cachier
 
vagrant up swarm-master swarm-node-1 swarm-node-2

Затем мы должны предоставить серверам Docker Swarm, Docker Compose, Consul, Consul Template и Registrator. Если вы новичок в Docker Swarm, вам может быть полезно прочесть статью « Сравнение инструментов кластеризации Docker: Kubernetes vs Docker Swarm» . Информацию о Консуле и других инструментах, которые мы будем использовать для обнаружения служб, можно найти в статье « Обнаружение служб: Zookeeper vs etcd vs Consul» .

Создание всех трех серверов может занять некоторое время, особенно если вы впервые используете Vagrant с Ubuntu. Как только они будут запущены, мы можем использовать Ansible для настройки кластера. Поскольку создание виртуальных машин включало установку Ansible в узле swarm-master , мы можем использовать его для запуска playbook swarm.yml .

1
2
3
4
vagrant ssh swarm-master
 
ansible-playbook /vagrant/ansible/swarm.yml \
    -i /vagrant/ansible/hosts/prod

Наконец, осталось только настроить Дженкинс. Мы продолжим использовать Ansible для этой задачи. Он создаст несколько каталогов, запустит контейнер Jenkins, возьмется за несколько конфигураций, установит необходимые нам плагины и, наконец, создаст работу, которая будет выполнять фактическое развертывание. Ключ заключается в том, чтобы максимально автоматизировать процесс, и установка Jenkins не является исключением. Поскольку целью этой статьи является эксперимент с Jenkins Workflow в контексте сине-зеленого развертывания в кластере Swarm, я пропущу объяснения того, как работает книга воспроизведения jenkins.yml . Настройка напоминает ту, которую я использовал в статье « Непрерывная интеграция, доставка или развертывание» в статье Jenkins, Docker и Ansible, с небольшими улучшениями. Не стесняйтесь взглянуть на код.

1
2
ansible-playbook /vagrant/ansible/jenkins.yml \
    -i /vagrant/ansible/hosts/prod

Изучение рабочего процесса Дженкинса

Давайте начнем строить. Поскольку первый запуск длится немного дольше, чем последовательные, мы будем использовать это время для обсуждения решения. Пожалуйста, откройте http://10.100.192.200:8080/job/books-ms/build?delay=0sec и нажмите кнопку Build . Вы можете следить за ходом работы, открыв страницу http://10.100.192.200:8080/job/books-ms/lastBuild/console .

строить

Пока работа выполняется, давайте кратко рассмотрим docker-compose.yml, который мы будем использовать для запуска тестов и развертывания сервиса.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
app:
image: vfarcic/books-ms
ports:
– 8080
environment:
– SERVICE_NAME=books-ms
– DB_HOST=books-ms-db
 
app-blue:
environment:
– SERVICE_NAME=books-ms-blue
extends:
service: app
 
app-green:
environment:
– SERVICE_NAME=books-ms-green
extends:
service: app
 
db:
container_name: books-ms-db
image: mongo
environment:
– SERVICE_NAME=books-ms-db
 
integ:
image: vfarcic/books-ms-tests
volumes:
– ./src:/source/src
environment:
– TEST_TYPE=integ
– DOMAIN=$DOMAIN

Цель приложения представляет наш основной сервис. Однако, поскольку нам нужно будет отличить синий цвет от зеленого , есть две дополнительные цели: app-blue и app-green . Действительно важной частью являются целевые имена. Они оба расширяют цель приложения , чтобы избежать повторения. Мы передаем переменную среды SERVICE_NAME , которая поможет Регистратору лучше определить службу перед отправкой информации в Консул . Помимо целей app *, у нас есть MongoDB, идентифицированный как db, и целевая цель, которую мы будем использовать для запуска тестов.

Используя Docker Compose, мы можем продолжить и исследовать скрипт Groovy service-flow.groovy, который будет выполнять фактическое развертывание. Обратите внимание, что для простоты я пропустил шаги по созданию контейнеров, запуску модульных и функциональных тестов и отправке контейнеров в реестр. Рекомендуется поместить их в рабочий процесс, который мы собираемся изучить. В этом случае скрипт Groovy предполагает, что эти шаги уже были выполнены в отдельном процессе и что контейнер, который мы собираемся развернуть, проверен.

Сначала мы должны объявить несколько переменных.

1
2
3
4
5
def swarmMaster = "10.100.192.200"
def proxy = "10.100.192.200"
def currentColor = getCurrentColor(swarmMaster, service)
def nextColor = getNextColor(currentColor)
def instances = getInstances(swarmMaster, service)

SwarmMaster и прокси представляют IP-адреса, которые мы будем использовать позже. Значения переменных currentColor и nextColor устанавливаются через функции, которые обращаются к Consul и извлекают значения развернутого в данный момент цвета, а также цвета, который мы собираемся развернуть. Наконец, мы выясняем, сколько экземпляров мы должны развернуть. Функция getInstances имеет два условия. Если для параметра экземпляров на экране сборки Jenkins задано значение 0 (значение по умолчанию), функция будет запрашивать эту информацию у Консула, которая будет возвращать количество запущенных экземпляров, или 1, если это первое развертывание. С другой стороны, если параметр сборки задания Jenkins не равен 0 , он будет использоваться как переменная экземпляров . Другими словами, мы можем решить, сколько экземпляров развернуть, задав некоторое значение на экране сборки Jenkins или разрешив сценарию развернуть то же количество контейнеров, что и в текущем выпуске. Пожалуйста, обратитесь к скрипту service-flow.groovy, если вам интересно, как работают эти функции (они находятся внизу скрипта).

Мы должны начать с предоставления кластера. Несмотря на то, что мы уже настроили Swarm и инструменты обнаружения сервисов, всегда полезно убедиться, что все работает так, как ожидалось. Помимо кластера нам также понадобится балансировщик нагрузки (в данном случае nginx ). Если все настроено правильно, подготовка займет всего несколько секунд. С другой стороны, если какой-то процесс остановился или, как в случае с nginx, он даже никогда не работал, наш этап подготовки исправит это.

шаги:

  • Provision Docker Swarm кластер
  • Обеспечение балансировки нагрузки (nginx)
01
02
03
04
05
06
07
08
09
10
11
node("cd") {
    env.PYTHONUNBUFFERED = 1
 
    stage "> Provisioning"
    if (provision.toBoolean()) {
        sh "ansible-playbook /vagrant/ansible/swarm.yml \
            -i /vagrant/ansible/hosts/prod"
        sh "ansible-playbook /vagrant/ansible/nginx.yml \
            -i /vagrant/ansible/hosts/prod --extra-vars \
            \"proxy_host=swarm-master\""
    }

Мы начали с объявления, что шаги будут выполняться внутри узла Jenkins, называемого или помеченного cd (сокращение от непрерывного развертывания). Для простоты в этом случае cd является одной из меток узла -роя . На производстве вы должны работать как можно больше на серверах, предназначенных для непрерывной доставки, а не на производстве. Следующим является этап объявления, который служит нескольким целям. Он помечает группу шагов, позволяет ограничить параллелизм и, если вы решите использовать CloudBees Jenkins Enterprise Edition , обеспечивает визуализацию, возможность перезапуска с выбранного этапа и некоторые другие функции. Ниже сцены вы заметите, что команды находятся внутри оператора if . Значение переменной обеспечения берется из флажка, который вы видели на экране сборки Jenkins. «Мясо» этого фрагмента — два утверждения. ‘sh’ — это один из типов шагов, предоставляемых плагином Workflow, и, как вы уже догадались, он запускает любую указанную нами команду оболочки. В этом случае мы используем его для запуска сборников пьес Ansible, которые позаботятся о предоставлении ресурсов. Обратите внимание, что мы не устанавливали nginx при настройке серверов. Плагин nginx.yml сделает это за нас.

С предоставлением дополнительных ресурсов мы должны начать развертывание. Поскольку мы внедряем докер-контейнеры, которые уже были собраны и отправлены в Docker Hub, все, что нам нужно, это файл docker-compose.yml, который лучше всего хранить в том же хранилище, что и код сервиса. Таким образом, команда, поддерживающая код, полностью контролирует, как следует развертывать и тестировать контейнеры, и не нужно знать, что такое Jenkins. Мы можем получить файл, клонировав репозиторий простым шагом git .

шаги:

  • Клонировать код из хранилища
1
2
stage "> Deployment"
    git url: "https://github.com/${repo}.git"

Обратите внимание на использование ${repo} . Это еще один случай использования параметров, которые можно указать на экране сборки Jenkins.

Теперь мы готовы к развертыванию новой версии.

шаги:

  • Вытащите новый релиз
  • Запустите новый выпуск
  • Сохраните количество экземпляров, которые мы развернули в Консуле
01
02
03
04
05
06
07
08
09
10
11
env.DOCKER_HOST = "tcp://${swarmMaster}:2375"
    sh "docker-compose -f docker-compose-jenkins.yml \
        pull app-${nextColor}"
    sh "docker-compose -f docker-compose-jenkins.yml \
        --x-networking up -d db"
    sh "docker-compose -f docker-compose-jenkins.yml \
        rm -f app-${nextColor}"
    sh "docker-compose -f docker-compose-jenkins.yml \
        --x-networking scale app-${nextColor}=$instances"
    sh "curl -X PUT -d $instances \
        http://${swarmMaster}:8500/v1/kv/${service}/instances"

Сначала мы устанавливаем переменную DOCKER_HOST, чтобы она указала на Swarm Master. С этого момента все команды Docker будут отправляться на этот IP / порт. Далее мы тянем приложение. Затем следует развертывание базы данных и удаление цели, которую мы собираемся развернуть. Чем мы сами запускаем сервис. Обратите внимание, что мы используем переменную instance для масштабирования сервиса. Наконец, мы отправляем количество экземпляров в Consul, чтобы тот же номер использовался при следующем запуске развертывания (если мы не изменим его явно на экране сборки Jenkins).

Теперь, когда служба развернута где-то внутри кластера Swarm, мы можем перейти к задачам после развертывания. Обратите внимание, что если это не будет первое развертывание, наша прокси-служба будет указывать на старый выпуск, а наши пользователи будут не замечать существования новой версии, которую мы только что развернули. Прежде чем перенастроить прокси-сервер, мы должны проверить, работает ли новый выпуск должным образом.

шаги:

  • Найдите недавно развернутую службу
  • Запустите предварительные интеграционные тесты
  • Остановите новый выпуск, если тесты не пройдены
01
02
03
04
05
06
07
08
09
10
11
12
stage "> Post-Deployment"
    def address = getAddress(swarmMaster, service, nextColor)
    try {
        env.DOCKER_HOST = ""
        sh "docker-compose -f docker-compose-jenkins.yml \
            run --rm -e DOMAIN=http://$address integ"
    } catch (e) {
        env.DOCKER_HOST = "tcp://${swarmMaster}:2375"
        sh "docker-compose -f docker-compose-jenkins.yml \
            stop app-${nextColor}"
        error("Pre-integration tests failed")
    }

Особенность Swarm в том, что мы больше не контролируем ситуацию. Мы не решаем, где будут размещены контейнеры. Docker Swarm сделал эту работу за нас, и мы должны были выяснить, где он развернул наш сервис. Поскольку регистратор хранит информацию, касающуюся недавно развернутого выпуска, в Consul, все, что нам нужно сделать, чтобы найти IP и порт, это запросить его. Это то, что делает функция getAddress . Пожалуйста, обратитесь к исходному коду service-flow.groovy, если вы заинтересованы в просмотре кода функции.

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

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

шаги:

  • Создать конфигурацию nginx
  • Скопируйте конфигурацию на прокси-сервер
  • Перезагрузить nginx
1
updateProxy(swarmMaster, service, nextColor);

С помощью шаблона Consul легко изменить конфигурацию nginx. Нам нужен шаблон, который в этом случае хранится в хранилище сервисов ( nginx-upstreams-blue.ctmpl и nginx-upstreams-green.ctmpl ) и выполните команду. В результате получается файл nginx-upstreams.conf, сгенерированный из шаблона и данных, сохраненных в Consul.

Обратите внимание, что все, что мы делали к настоящему времени, было выполнено в узле cd, так что это оказывает минимальное влияние на рабочие серверы. Настал момент переключиться на прокси-сервер ( фунт ). Для получения подробной информации о реализации, пожалуйста, взгляните на функцию updateProxy в исходном коде service-flow.groovy .

В первом раунде тестов (до интеграции) мы убедились, что все было развернуто и настроено правильно, кроме самого прокси. Цель состояла в том, чтобы как можно больше тестировать, не затрагивая наших пользователей (прокси до недавнего времени указывал на старый выпуск). Несмотря на то, что это не является частью этой статьи, до того, как контейнер был собран и помещен в концентратор, я запускаю всевозможные модульные, функциональные и любые другие типы тестов, которые можно выполнить без развертывания службы.

Сейчас самое время сделать финальный раунд тестов (после интеграции). Мы должны убедиться, что изменения в конфигурации прокси были выполнены правильно, и служба действительно доступна через прокси.

шаги:

  • Запустите тесты после интеграции
  • Восстановить настройки прокси, если тесты не пройдены
  • Остановите новый выпуск, если тесты не пройдены
01
02
03
04
05
06
07
08
09
10
11
12
13
14
try {
        env.DOCKER_HOST = ""
        sh "docker-compose -f docker-compose-jenkins.yml \
            run --rm -e DOMAIN=http://${proxy} integ"
    } catch (e) {
        if (currentColor != "") {
            updateProxy(swarmMaster, service, currentColor)
        }
        env.DOCKER_HOST = "tcp://${swarmMaster}:2375"
        sh "docker-compose -f docker-compose-jenkins.yml \
            stop app-${nextColor}"
        error("Post-integration tests failed")
    }
    sh "curl -X PUT -d ${nextColor} http://${swarmMaster}:8500/v1/kv/${service}/color"

Этот блок почти такой же, как и у нас перед тестами перед интеграцией. Основное отличие состоит в том, что на этот раз мы не тестируем сервис напрямую через его IP и порт, а проходим через прокси таким же образом, как потребители сервиса будут получать к нему доступ. Если тесты не пройдены, нам нужно не только остановить новый выпуск, но и отменить изменения в конфигурации nginx. Наконец, если все прошло успешно, мы обновляем Consul информацией о только что развернутом цвете.

Мы почти закончили. Служба развернута, протестирована (несколько раз), и прокси был изменен соответствующим образом. Единственное, что осталось, это немного привести в порядок.

шаги:

  • Остановите старый выпуск (если это не было развертывание первого выпуска)
1
2
3
4
5
if (currentColor != "") {
        env.DOCKER_HOST = "tcp://${swarmMaster}:2375"
        sh "docker-compose -f docker-compose-jenkins.yml \
            stop app-${currentColor}"
    }

Мы закончили исследование сценария рабочего процесса Jenkins, используемого для выполнения сине-зеленого развертывания в Docker Swarm. Надеюсь, к этому времени первый запуск сборки Jenkins завершен. Основной причиной медлительности является тестовый контейнер с его виртуальным размером более 2 ГБ. В то время как в этом случае мы использовали его только для выполнения внутренних тестов интеграции, в нескольких других случаях я использовал его для запуска внешних тестов, работающих на FireFox и Chrome, а также полного набора внутренних тестов. Он содержит JDK, Scala, NodeJS, множество библиотек Bower и Scala и так далее. Это контейнер-монстр, в котором содержится все, что мне нужно для тестирования этого сервиса. Тот же контейнер использовался для выполнения модульных и функциональных тестов, прежде чем я поместил контейнер в Docker Hub. Хотя такой большой размер подходит для тестирования, обратите внимание, что производственные контейнеры должны быть как можно меньше. Например, контейнер books-ms имеет размер чуть более 300 МБ виртуального размера.

Вернитесь к экрану консоли Jenkins и подтвердите, что сборка завершена. Последняя запись должна быть завершена: УСПЕХ . Примите мои извинения, если он все еще работает, и, пожалуйста, будьте терпеливы. У меня не было времени создать меньший образец только для этой статьи.

приставка

Если смотреть на этот огромный журнал слишком сложно, поэкспериментируйте с экраном « Шаги рабочего процесса» . Ссылка находится в левом меню и показывает отдельные журналы для каждого шага в скрипте.

Вы также можете взглянуть на контейнеры, которые были развернуты.

1
2
3
export DOCKER_HOST=tcp://10.100.192.200:2375
 
docker ps --filter name=books --format "table {{.Names}}"

Вывод команды docker ps в моем случае выглядит следующим образом.

1
2
3
NAMES
swarm-node-1/booksms_app-blue_1
swarm-node-2/books-ms-db

Swarm развернул контейнер приложения на узле 1, а контейнер db — на узле 2. Обратите внимание, что нет никаких гарантий, что ваши результаты будут одинаковыми. Возможно, Swarm решил развернуть контейнеры на разных узлах.

Продолжайте и повторите сборку несколько раз. Поэкспериментируйте с разными значениями параметра instances . Например, посмотрите, что произойдет, если вы решите развернуть пять экземпляров следующего выпуска. После завершения такого развертывания вы можете повторить команду docker ps и посмотреть, где Swarm развернул контейнеры. Каждый раз, когда вы запускаете сборку, цвет будет меняться, и во время процесса не будет простоев. Более того, релиз будет протестирован в производстве без ущерба для пользователей. Если вы любите приключения, попробуйте остановить контейнер nginx или один из узлов роя. Поскольку инициализация является частью развертывания, все вернется к нормальному состоянию после завершения новой сборки. Не расстраивайтесь из-за долгого времени, которое потребовалось для запуска работы в первый раз. Все серверы теперь подготовлены, и все контейнеры уже извлечены. Последовательные пробеги будут намного быстрее.

Прощальные мысли

Надеемся, что эта статья предоставила вам обзор возможных шагов, которые нам может понадобиться для успешного выполнения сине-зеленого развертывания в Docker Swarm. То, что мы сделали, — это сочетание подготовки и настройки кластера с сине-зеленым процессом развертывания. Что еще более важно, я надеюсь, что вы увидели преимущества плагина Jenkins Workflow по сравнению с более распространенными способами организации заданий Jenkins. Один и тот же процесс, вероятно, потребует нескольких заданий, которые будет гораздо сложнее поддерживать. Более того, читать и писать скрипты Groovy гораздо проще и быстрее, чем пытаться бороться со стандартным синтаксисом XML заданий Jenkins. Этот скрипт может и должен быть в хранилище исходного кода. Любое изменение в этой работе может быть сделано так же, как мы обычно меняем код. Все, что нам нужно сделать, это перезапустить Ansible playbook, и работа будет обновлена.

Обратите внимание, что хотя этот сценарий может показаться пугающим с самого начала, с небольшой адаптацией для вашей собственной организации, его можно легко использовать для развертывания множества различных служб. До тех пор, пока используются определенные соглашения об именах (в основном так, как мы называем цели docker-compose.yml), вы сможете использовать его в большинстве (если не во всех) развертываниях. Все, что вам нужно сделать, это добавить больше записей в переменную jobs внутри файла defaults / main.yml и снова запустить playbook.

настройки

Основным недостатком того, как мы работали, является создание рабочих мест Jenkins через Ansible. Хотя он и сделал то, что нам было нужно, он больше похож на обходной путь, чем на долгосрочное решение. Проблема, на мой взгляд, в том, что текущие решения Jenkins OSS для управления заданиями еще менее продуктивны. CloudBees Jenkins Platform Enterprise Edition решает эту проблему с помощью шаблонов плагинов и предлагает больше возможностей для Jenkins Workflow. Если вы заинтересованы в его испытании, служба поддержки CloudBees будет рада предоставить рекомендации.

Ссылка: Сине-зеленое развертывание в Docker Swarm с подключаемым модулем документооборота Jenkins от нашего партнера по JCG Виктора Фарчича в блоге технологических бесед .