Статьи

Масштабирование до бесконечности с помощью Docker Swarm, Docker Compose и Consul (Часть 3/4) — Процедура развертывания, автоматизации и самовосстановления сине-зеленого цвета

Эта серия разделена на следующие статьи.

В предыдущей статье мы вручную развернули первую версию нашего сервиса вместе с отдельным экземпляром контейнера Mongo DB. Оба (вероятно) работают на разных серверах. Docker Swarm решил, где запустить наши контейнеры, и Консул хранил информацию об IP-адресах и портах службы, а также другую полезную информацию. Эти данные использовались для связи одного сервиса с другим, а также для предоставления информации, необходимой nginx для создания прокси.

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

Настроить

Для тех из вас, кто остановил виртуальные машины, которые мы создали в предыдущей статье ( vagrant halt ) или выключили свои ноутбуки, вот как быстро добраться до того же состояния, в котором мы были раньше. Остальные могут пропустить эту главу.

1
2
3
4
5
6
7
vagrant up
vagrant ssh swarm-master
ansible-playbook /vagrant/ansible/infra.yml -i /vagrant/ansible/hosts/prod
export DOCKER_HOST=tcp://0.0.0.0:2375
docker start booksservice_db_1
docker start booksservice_blue_1
sudo consul-template -consul localhost:8500 -template "/data/nginx/templates/books-service-blue-upstream.conf.ctmpl:/data/nginx/upstreams/books-service.conf:docker kill -s HUP nginx" -once

Мы можем проверить, все ли в порядке, выполнив следующее.

1
2
docker ps
curl http://10.100.199.200/api/v1/books | jq .

Первая команда должна перечислить, среди прочего, контейнеры booksservice_blue_1 и booksservice_db_1. Второй должен получить ответ JSON с тремя книгами, которые мы вставили ранее.

С этим в стороне, мы можем продолжать, где мы оставили.

Запустите второй выпуск служебного контейнера

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

1
2
3
4
5
cd /data/compose/config/books-service
docker-compose pull green
docker-compose rm -f green
docker-compose up -d green
docker ps | grep booksservice

На данный момент у нас есть две версии нашего сервиса (старая и новая; синяя и зеленая). Это видно из вывода последней команды ( docker ps ). Он должен отображать обе службы, работающие на разных серверах (или одинаковые, если это оказалось местом с наименьшим количеством контейнеров).

Это момент, когда мы должны запустить наши автоматизированные тесты. Мы не подготовили их для этой статьи, поэтому мы вручную выполним команду curl чтобы проверить, все ли в порядке.

1
2
curl http://localhost:8500/v1/catalog/service/books-service-green | jq .
curl http://[IP]:[PORT]/api/v1/books | jq .

Первая команда запрашивает Consul и возвращает данные, связанные с сервисом books-service-green. Обязательно измените [IP] и [PORT] во второй команде ( curl ) на значения, которые вы получили от Consul.

Имейте в виду, что мы протестировали сервис, отправив запрос непосредственно на его IP-адрес и порт, а не на общедоступный адрес, обслуживаемый nginx . На данный момент обе службы работают, причем старая (синяя) доступна для общественности по адресу http://10.100.199.200, а новая (зеленая) на данный момент доступна только нам. Вскоре мы изменим nginx и скажем, чтобы он перенаправлял все запросы на новый, таким образом архивируя время простоя. Теперь, когда все работает, как и ожидалось, пришло время сделать новый выпуск (зеленый) доступным для общественности.

1
2
3
4
5
curl -X PUT -d 'green' http://localhost:8500/v1/kv/services/books-service/color
sudo consul-template -consul localhost:8500 -template "/data/nginx/templates/books-service-green-upstream.conf.ctmpl:/data/nginx/upstreams/books-service.conf:docker kill -s HUP nginx" -once
docker stop booksservice_blue_1
curl http://10.100.199.200/api/v1/books | jq .
docker ps -a | grep booksservice

Сначала мы добавляем новое значение (зеленое) к ключу Консул книжного сервиса / цвета . Это для дальнейшего использования в случае, если мы хотим знать, какой цвет работает в данный момент. Эта информация пригодится, когда мы достигнем момента, когда все это будет полностью автоматизировано. Затем мы обновили конфигурацию nginx , запустив consul-template . Затем, команда curl была последним этапом тестирования, который проверяет, что общедоступный сервис (на этот раз зеленый) продолжает работать, как ожидалось, и без простоя. Это тестирование должно быть автоматизировано, но для краткости автоматизация тестирования была пропущена из этой статьи. Наконец, мы проверяем вывод команды docker ps -a . Он должен отображать booksservice_green_1 как Up и booksservice_blue_1 и Exited.

Автоматизация с Ansible и Дженкинс

Все, что мы делали к настоящему времени, было хорошо как учебное упражнение, но в «реальном мире» все это должно быть автоматизировано. Мы будем использовать Ansible как инструмент оркестровки для запуска всех команд, которые мы сделали к настоящему моменту, и еще несколько. Мы не будем вдаваться в подробности Ansible playbook books-service.yml. Его можно найти вместе с остальным исходным кодом в репозитории docker-swarm GitHub. Поскольку Ansible playbook следует той же логике, что и ручные команды, которые мы запускаем, и, как правило, его playbooks очень легко читать, надеюсь, у вас не возникнет проблем с его пониманием без дальнейшего объяснения. Если у вас возникли проблемы, обратитесь к разделу «Непрерывная интеграция, доставка и развертывание» . В нем довольно много статей, посвященных Ansible. Не стесняйтесь присылать комментарии (ниже) или свяжитесь со мной напрямую с вопросами, если таковые имеются.

Важно отметить, что для разных служб нет отдельных ролей. Когда Ansible используется с архитектурой микро-сервисов, управление отдельной ролью для каждого сервиса может быстро стать слишком сложным. Поскольку с помощью Docker легко стандартизировать развертывания, существует только одна роль, называемая службой . Эта роль использует переменные для настройки нескольких различий между различными микро сервисами. Например, один может использовать базу данных (например, книжный сервис ), а другой — нет (например, books-fe ). Другими словами, у каждого сервиса есть отдельная книга воспроизведения с переменными, специфичными для сервиса, и все они полагаются на одну и ту же роль.

В качестве примера ниже приводится определение playbook books-service.yml .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
- hosts: service
  remote_user: vagrant
  sudo: yes
  vars:
    - container_image: books-service
    - container_name: books-service
    - http_address: /api/v1/books
    - has_db: true
  roles:
    - docker
    - consul
    - swarm
    - nginx
    - service

У нас есть специфичные для сервиса переменные, определенные в разделе vars, за которыми следуют все роли, необходимые для этого сервиса. Переменные определяют образ и имя контейнера Docker, на котором должна работать служба HTTP-адресов (используется для предоставления прокси-сервера nginx), и независимо от того, есть ли у нее база данных или нет. Все услуги зависят от одинаковых ролей. Им нужны Докер , Консул , Рой , nginx и сервис . Все, кроме последнего, являются зависимостями. Роль службы определяет эту и все другие службы, которые мы запускаем. Если вы посмотрите на playbook books-fe.yml , вы заметите, что единственное различие между ними — переменные. Пожалуйста, взгляните на исходный код в репозитории docker-swarm GitHub для получения дополнительной информации.

Теперь давайте запустим playbook books-service.yml, чтобы развернуть еще одну версию нашего сервиса.

1
ansible-playbook /vagrant/ansible/books-service.yml -i /vagrant/ansible/hosts/prod

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

Работало ли это правильно? Мы можем проверить, что так же было, как и раньше.

1
2
3
curl http://localhost:8500/v1/kv/services/books-service/color?raw
curl http://10.100.199.200/api/v1/books | jq .
docker ps -a | grep booksservice

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

Можем ли мы сделать это, даже не выполняя эту единственную команду? Это то, что Дженкинс (и аналогичные инструменты) для. Если вы уже использовали Jenkins, вы можете подумать, что вам следует пропустить эту часть. Пожалуйста, потерпите меня, потому что, как только мы это настроим, мы пойдем по пути восстановления после неудачи.

Давайте сначала убедимся, что Дженкинс запущен и работает.

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

Самое замечательное в инструментах оркестровки, таких как Ansible, заключается в том, что они всегда проверяют состояние и действуют только при необходимости. Если Дженкинс уже работал, Ансибл ничего не делал. Если, с другой стороны, он не был развернут или был закрыт, Ansible действовал соответствующим образом и заставил его работать снова.

Дженкинс можно посмотреть, открыв http://10.100.199.200:8080/ в вашем любимом браузере. Среди других заданий вы должны увидеть задание по обслуживанию книг, которое мы будем использовать для развертывания новой версии нашего сервиса.

Скриншот-от-2015-07-02-211442

Затем создайте новый узел, выполнив следующие действия.

  • Нажмите Управление Дженкинс > Управление узлами > Новый узел
  • Назовите его cd , выберите Dumb Slave и нажмите OK
  • Введите / data / jenkins / slaves / cd в качестве удаленного корневого каталога
  • Введите 10.100.199.200 в качестве хоста
  • Нажмите Добавить * рядом с ** Учетные данные
  • Используйте vagrant в качестве имени пользователя и пароля и нажмите кнопку Добавить
  • Нажмите Сохранить

Скриншот-от-2015-07-02-211727

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

Скриншот-от-2015-07-02-212007

Запустите docker ps чтобы подтвердить, что служба работает.

1
docker ps -a | grep booksservice

Чего не хватает в этой конфигурации задания Jenkins, так это в настройке управления исходным кодом, которая будет извлекать код из хранилища при любых изменениях и инициировать развертывание. В конце концов, мы не хотим тратить время на нажатие кнопки Build каждый раз, когда кто-то меняет код. Поскольку это выполняется локально, настройка SCM не может быть продемонстрирована. Однако у вас не должно возникнуть проблем с поиском информации в Интернете.

Теперь давайте перейдем к более интересным вещам и посмотрим, как оправиться от неудач.

Самовосстанавливающаяся система

Что происходит, когда что-то идет не так? Что происходит, когда, например, один сервис или весь узел выходит из строя? Наша система должна быть в состоянии оправиться от таких проблем. Самоисцеление — это большая тема, охватывающая архитектуру кода, настройку серверов, уведомления и т. Д. Этот предмет заслуживает по крайней мере целой статьи (и, вероятно, целой книги), поэтому мы не будем углубляться в концепции самовосстановления, а лишь рассмотрим Самый простой сценарий. Мы настроим систему таким образом, чтобы при выходе из строя одного сервиса он был повторно развернут. Если, с другой стороны, весь узел перестает работать, все контейнеры с этого узла будут переведены в исправный. Для этого нам понадобятся Консул, Дженкинс и Ансибл.

Вот поток, который мы хотим достичь. Нам нужно что-то контролировать здоровье наших услуг. Мы будем использовать Консул для этой цели. Если один из них не отвечает на запрос, отправляемый каждые 10 секунд, будет вызвано задание Jenkins. Дженкинс, в свою очередь, запустит Ansible, который будет следить за тем, чтобы все, что связано с этим сервисом, работало. Поскольку мы используем Docker Swarm , если причиной неработающей службы является отключение сервера, будет выбран здоровый узел. Мы могли бы сделать это без Jenkins, но, поскольку мы уже настроили его, и он предоставляет много приятных и простых в настройке функций (даже если мы не используем их в этом примере), мы будем придерживаться этого.

Прежде чем мы углубимся в детали, давайте посмотрим на это в действии. Мы начнем с остановки нашего сервиса.

1
2
3
4
docker ps | grep booksservice
docker stop booksservice_green_1
docker stop booksservice_blue_1
docker ps -a

В этот момент я не мог быть уверен, используете ли вы синюю или зеленую версию, поэтому приведенные выше команды попытаются остановить обе. Вторая команда docker ps -a используется для проверки того, что служба действительно остановлена ​​(статус должен быть Выход ).

Теперь, когда мы остановили наш сервис, Консул обнаружит это, так как он запускает проверку каждые 10 секунд. Мы можем увидеть его журнал со следующим.

1
cat /data/consul/logs/watchers.log

Вывод должен быть похож на приведенный ниже.

1
2
3
4
5
6
Consul watch request:
[{"Node":"swarm-master","CheckID":"service:books-service","Name":"Service 'books-service' check","Status":"critical","Notes":"","Output":"HTTP GET http://10.100.199.200/api/v1/books: 502 Bad Gateway Output: u003chtmlu003ernu003cheadu003eu003ctitleu003e502 Bad Gatewayu003c/titleu003eu003c/headu003ernu003cbody bgcolor="white"u003ernu003ccenteru003eu003ch1u003e502 Bad Gatewayu003c/h1u003eu003c/centeru003ernu003chru003eu003ccenteru003enginx/1.9.2u003c/centeru003ernu003c/bodyu003ernu003c/htmlu003ern","ServiceID":"books-service","ServiceName":"books-service"}]
 
>>> Service books-service is critical
 
Triggering Jenkins job http://10.100.199.200:8080/job/books-service/build

Если вы не видите тот же вывод, убедитесь, что между остановкой службы и проверкой журналов Консула прошло 10 секунд.

Консул обнаружил, что что-то не так, и сделал запрос на http://10.100.199.200:8080/job/books-service/build . Это, в свою очередь, вызвало ту же задачу Jenkins, которую мы запустили вручную, и выполнило другое развертывание.

Вы можете увидеть прогресс развертывания от Jenkins. Как только он закончится, мы можем еще раз взглянуть на состояние контейнеров.

1
docker ps | grep booksservice

Предполагая, что вы остановили зеленую версию, на этот раз вы увидите, что работает booksservice_blue_1 .

Такая же процедура произойдет, если мы остановим MongoDB или даже любой из узлов (например, swarm-node-01). Если какая-то служба не отвечает, Consul запускает соответствующее задание Jenkins и повторяет цикл развертывания. Даже если весь узел не работает, службы, находящиеся на этом узле, не будут отвечать, и процедура будет повторена.

Как это работает?

Каждый раз, когда мы разворачиваем сервис с Ansible (на данный момент только один, но который может распространяться на любое их количество), мы следим за тем, чтобы конфигурация Consul обновлялась информацией об услуге. Давайте посмотрим на конфигурацию для книжного сервиса .

1
cat /etc/consul.d/service-books-service.json

Выход должен быть следующим.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
{
    "service": {
        "name": "books-service",
        "tags": ["service"],
        "port": 80,
        "address": "10.100.199.200",
        "checks": [{
            "id": "api",
            "name": "HTTP on port 80",
            "http": "http://10.100.199.200/api/v1/books",
            "interval": "10s"
        }]
    }
}

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

Наконец, есть раздел проверок . он говорит консулу выполнять http- запрос на http://10.100.199.200/api/v1/books каждые 10 секунд. Если сервер возвращает что-либо кроме кода 200 , Консул посчитает, что эта служба не работает должным образом, и инициирует процедуру «спасения».

Каждый сервис должен иметь свой собственный файл конфигурации сервиса с разделом проверок, адаптированным к его особенностям. Существуют разные типы проверок, но в нашем случае http делает именно то, что нам нужно.

Следующий на очереди файл конфигурации watchers.json .

1
cat /etc/consul.d/watchers.json

Выход следующий.

1
2
3
4
5
6
7
8
9
{
  "watches": [
    {
      "type": "checks",
      "state": "critical",
      "handler": "/data/consul/scripts/redeploy_service.sh >>/data/consul/logs/watchers.log"
    }
  ]
}

Это говорит Консулу, чтобы наблюдать все службы проверок типа. Этот фильтр необходим, поскольку у нас есть услуги, зарегистрированные в Консуле, и мы хотим, чтобы проверялись только те, которые мы указали. Второй фильтр — это состояние . Мы хотим, чтобы был восстановлен только сервис в критическом состоянии. Наконец, если Консул найдет службу, у которой совпадают тип и состояние , он запустит набор команд redeploy_service.sh в обработчике . Давайте посмотрим на это.

1
cat /data/consul/scripts/redeploy_service.sh

Выход следующий.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
#!/usr/bin/env bash
 
RED='33[0;31m'
NC='33[0;0m'
 
read -r JSON
echo "Consul watch request:"
echo "$JSON"
echo "$JSON" | jq -r '.[] | select(.CheckID | contains("service:")) | .ServiceName' | while read SERVICE_NAME
do
  echo ""
  echo -e ">>> ${RED}Service $SERVICE_NAME is critical${NC}"
  echo ""
  echo "Triggering Jenkins job http://10.100.199.200:8080/job/$SERVICE_NAME/build"
  curl -X POST http://10.100.199.200:8080/job/$SERVICE_NAME/build
done

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

Продолжение следует

Мы развернули несколько выпусков нашего сервиса, следуя сине-зеленой технологии развертывания . Никогда сервис не прерывался во время процесса развертывания. У нас весь процесс автоматизирован с помощью Ansible и управляется Jenkins.

Мы представили некоторые новые функции Consul, которые позволяют нам отслеживать состояние наших сервисов и выполнять меры, которые помогут восстановить их после сбоев. В текущей настройке мы по-прежнему не можем гарантировать нулевое время простоя, поскольку Consul может обнаружить до 10 секунд, чтобы обнаружить сбой, и чуть больше времени Jenkins / Ansible, чтобы выполнить развертывание.

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

История продолжается в статье « Масштабирование отдельных сервисов» .