Статьи

Масштабирование до бесконечности с помощью Docker Swarm, Docker Compose и Consul (Часть 2/4) — Развертывание сервисов вручную

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

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

Мы не будем вдаваться в подробности того, как настроить Consul, Docker Compose, Docker Swarm, nginx и т. Д. Их можно увидеть, посмотрев книги Plays Ansible в репозитории vfarcic / docker-swarm GitHub.

Создание новых серверов

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

1
vagrant destroy

Давайте создадим наши виртуальные машины и настроим инфраструктуру, используя несколько игровых книг Ansible. Если вас спросят, хотите ли вы продолжить подключение, ответьте « да».

1
2
3
4
5
6
vagrant up
vagrant ssh swarm-master
ansible-playbook /vagrant/ansible/swarm.yml -i /vagrant/ansible/hosts/prod
ansible-playbook /vagrant/ansible/compose.yml -i /vagrant/ansible/hosts/prod
ansible-playbook /vagrant/ansible/nginx.yml -i /vagrant/ansible/hosts/prod
ansible-playbook /vagrant/ansible/consul.yml -i /vagrant/ansible/hosts/prod

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

1
2
3
export DOCKER_HOST=tcp://0.0.0.0:2375
docker info
docker ps -a

Первая команда должна показать, что в кластере есть три узла. Второй должен перечислить 12 контейнеров; swarm-node, registrator и registrator-kv на каждом из трех узлов, которые у нас есть.

Теперь пришло время начать работу над развертыванием.

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

Развертывание сервиса

Мы будем использовать Docker Compose для запуска наших контейнеров. У него очень простой синтаксис, основанный на YML. Те, кто знаком с Ansible, будут чувствовать себя знакомыми с ним.

Во всех предыдущих статьях мы использовали Ansible для этой задачи. Мое мнение было таково, что Ansible предлагает все, что делает Docker Compose, и многое другое. Docker compose занимается только сборкой, выполнением и другими операциями Docker. Ansible предназначен для организации всего, от настройки сервера, развертывания, сборки и т. Д. Это один инструмент, который может позаботиться обо всех этапах оркестровки и развертывания.

Однако модуль Ansible Docker плохо работает с Swarm. Как только мы закончим с ручными командами, мы продолжим использовать Ansible для всех задач, кроме запуска контейнеров Docker через Swarm.

Мы будем развертывать книжный сервис . Это приложение, которое предоставляет REST API для просмотра, обновления или удаления книг. Данные хранятся в базе данных Mongo.

Настройка Docker Compose Files на сервере

Первый шаг — настроить шаблоны Docker Compose. Нам понадобится каталог, в котором будут находиться эти шаблоны, и сам шаблон.

Создать каталог легко.

1
sudo mkdir -p /data/compose/config/books-service

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

Мы хотим выполнить что-то похожее на следующую конфигурацию Docker Compose.

1
2
3
4
5
6
ports:
  - 8080
environment:
  - SERVICE_NAME=books-service
  - DB_PORT_27017_TCP=[IP_AND_PORT_OF_THE_MONGO_DB_SERVICE]
image: vfarcic/books-service

Мы хотим выставить внутренний порт 8080 (это тот сервис, который используется). Для внешнего мира Docker сопоставит этот порт любому доступному порту. Назовем сервисную книжку-сервис .

Теперь самое сложное, мы должны выяснить IP-адрес и порт БД, прежде чем создавать этот шаблон.

Чтобы решить эту проблему, мы создадим шаблон Consul. Запустите следующую команду.

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
cd /data/compose/config/books-service
echo 'db:
  ports:
    - 27017
  environment:
    - SERVICE_ID=books-service-db
  image: mongo
blue:
  ports:
    - 8080
  environment:
    - SERVICE_NAME=books-service-blue
    - DB_PORT_27017_TCP={{ key "services/mongo/books-service-db" }}
 
  image: vfarcic/books-service
green:
  ports:
    - 8080
  environment:
    - SERVICE_NAME=books-service-green
    - DB_PORT_27017_TCP={{ key "services/mongo/books-service-db" }}
 
  image: vfarcic/books-service
' | sudo tee docker-compose.yml.ctmpl
sudo cp docker-compose.yml.ctmpl docker-compose.yml

Мы создали новый шаблон /data/compose/config/books-service/docker-compose.yml.ctmpl. «Странные» вещи внутри {% и %} будут объяснены в ближайшее время. Пока достаточно сказать, что значение DB_PORT_27017_TCP будет заменено на IP адрес и порт books-service-db .

Давайте быстро пройдемся по шаблону. Сначала мы определяем контейнер БД, который предоставляет порт 27017 (стандартный порт Mongo), устанавливает переменную окружения SERVICE_ID (мы будем использовать ее позже) и указывает, что изображение является mongo . Аналогичное делается для книжного сервиса, за исключением того, что мы указываем его дважды. Один раз синий, а другой зеленый . Мы будем практиковать сине-зеленое развертывание, чтобы не достигать цели простоя (дополнительную информацию можно найти в статье о стратегиях непрерывного развертывания ).

Мы могли бы сделать так, чтобы Mongo DB всегда работала на одном сервере с книжным сервисом, но это могло бы вызвать потенциальные проблемы. Во-первых, это будет означать, что все три контейнера (дБ, синий и зеленый) должны быть на одном сервере. Хотя это может быть хорошо в этом сравнительно небольшом примере, в больших системах это может стать узким местом. Больше свободы нам нужно для распределения контейнеров, больше ресурсов ЦП, памяти и HD, которые мы будем выдавливать из наших серверов.

Запустите контейнер БД

Запустить контейнер БД просто, поскольку он не зависит от других сервисов. Мы можем просто запустить цель db, которую мы указали ранее.

1
2
docker-compose pull db
docker-compose up -d --no-recreate db

Первая команда вывела db на все узлы (мы рассмотрим причины этой команды чуть позже).

Второй аргумент команды up указывает compose, что мы бы хотели, чтобы он удостоверился, что он запущен и работает, -d означает, что он должен работать в отдельном режиме, --no-recreate говорит, что compose ничего не делает, если контейнер уже запущен, и наконец, последний аргумент — это имя, которое мы указали в docker-compose.yml .

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

1
docker ps | grep booksservice_db

Вы увидите IP и порт службы БД .

Запустите сервисный контейнер в первый раз

Запуск сервисного контейнера будет немного сложнее. Есть несколько препятствий, с которыми мы не столкнулись с базой данных. Основным из них является то, что нам нужно знать IP и порт базы данных, которую мы только что развернули, и передавать эту информацию. Позже, когда мы запустим сервис во второй раз (новый выпуск), все станет еще сложнее.

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

Прежде чем мы запустим команду, давайте посмотрим, как выглядит раздел окружений шаблона Consul docker-compose.yml.ctmpl .

1
cat docker-compose.yml.ctmpl | grep DB_PORT_27017_TCP

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

1
2
- DB_PORT_27017_TCP={{ key "services/mongo/books-service-db" }}
- DB_PORT_27017_TCP={{ key "services/mongo/books-service-db" }}

Теперь давайте запустим шаблон Consul.

1
sudo consul-template -consul localhost:8500 -template "docker-compose.yml.ctmpl:docker-compose.yml" -once

Давайте посмотрим на только что созданный docker-compose.yml .

1
cat docker-compose.yml | grep DB_PORT_27017_TCP

Результат может отличаться в зависимости от местоположения (IP и порт), который Docker Swarm выбрал для нашей службы БД. В моем случае вывод следующий.

1
2
- DB_PORT_27017_TCP=10.100.199.202:32768
- DB_PORT_27017_TCP=10.100.199.202:32768

Шаблон Консул поставил правильный IP-адрес базы данных и порт. Как это произошло? Давайте сначала рассмотрим аргументы команды.

  • -consul позволяет нам указать адрес нашего экземпляра Консула (localhost: 5000).
  • — шаблон состоит из двух частей; источник и пункт назначения. В этом случае мы говорим использовать docker-compose.yml.ctmpl в качестве шаблона и продукт docker-compose.yml в качестве вывода.
  • -Конце не требует объяснений. Это должно выполняться только один раз.

Настоящая «магия» находится внутри шаблона. У нас есть следующая строка в docker-compose.yml.ctmpl.

1
{{ key "services/mongo/books-service-db" }}

Это заставляет шаблон Consul искать ключ services / mongo / books-service-db и заменять его значением.

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

1
curl http://localhost:8500/v1/kv/services/mongo/books-service-db?raw

Единственная загадка, которая осталась неразгаданной, это то, как эта информация попала в Консул. Ответ в удобном инструменте под названием регистратор . Это позволяет нам отслеживать контейнеры и обновлять хранилище ключей / значений Consul при каждом запуске или остановке. Мы уже настроили его с помощью Ansible, поэтому при запуске службы базы данных он обнаружил новый контейнер и соответственно обновил Consul.

Теперь, когда наш docker-compose.yml правильно обновлен информацией о базе данных, пришло время загрузить последнюю версию нашего сервиса.

1
docker-compose pull blue

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

Теперь мы можем запустить контейнер.

1
2
docker-compose up -d blue
docker ps | grep booksservice_blue

Вторая команда перечисляет недавно запущенный сервис (синий). Помимо прочего, вы можете увидеть IP-адрес и порт, на котором он работает.

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

1
curl -X PUT -d 'blue' http://localhost:8500/v1/kv/services/books-service/color

Мы еще не закончили. Даже если приложение работает и правильно указывает на базу данных, запущенную на другом сервере, мы все еще не решили проблему с портом. Наш сервис должен быть доступен по адресу http://10.100.199.200/api/v1/books, а не на одном из серверов, на которых Swarm его развернул. Кроме того, мы должны иметь возможность использовать его через порт 80 (стандартный http), а не случайный порт, который был назначен нам. Это можно решить с помощью обратного прокси-сервера nginx и шаблона Consul. Мы можем обновить конфигурацию nginx таким же образом, как мы обновили docker-compose.yml.

Сначала мы создадим несколько файлов конфигурации nginx.

01
02
03
04
05
06
07
08
09
10
11
echo '
server {
    listen 80;
    server_name 10.100.199.200;
    include includes/*.conf;
}' | sudo tee /data/nginx/servers/common.conf
 
echo '
location /api/v1/books {
  proxy_pass http://books-service/api/v1/books;
}' | sudo tee /data/nginx/includes/books-service.conf

Нам также понадобятся еще два шаблона Consul.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
echo '
upstream books-service {
    {{range service "books-service-blue" "any" }}
    server {{.Address}}:{{.Port}};
    {{end}}
}
' | sudo tee /data/nginx/templates/books-service-blue-upstream.conf.ctmpl
echo '
upstream books-service {
    {{range service "books-service-green" "any" }}
    server {{.Address}}:{{.Port}};
    {{end}}
}
' | sudo tee /data/nginx/templates/books-service-green-upstream.conf.ctmpl

Этот шаблон немного сложнее. Он сообщает Консулу, чтобы он извлекал все экземпляры службы (диапазона) с именем books-service-blue, игнорируя их статус (любой). Для каждого из этих экземпляров следует указать IP (.Address) и порт (.Port). Мы создали шаблон для синих и зеленых версий.

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

Давайте применим синий шаблон.

1
2
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
cat /data/nginx/upstreams/books-service.conf

Единственная новая вещь здесь — это третий аргумент в -template . После указания источника и места назначения, мы говорим ему перезапустить nginx, выполнив команду docker kill -s HUP nginx .

Вывод вновь созданного файла будет аналогичен следующему.

1
2
3
upstream books-service {
    server 10.100.199.203:32769;
}

Наконец, давайте проверим, все ли работает как положено.

1
2
3
4
curl -H 'Content-Type: application/json' -X PUT -d '{"_id": 1, "title": "My First Book", "author": "Joh Doe", "description": "Not a very good book"}' http://10.100.199.200/api/v1/books | jq .
curl -H 'Content-Type: application/json' -X PUT -d '{"_id": 2, "title": "My Second Book", "author": "John Doe", "description": "Not a bad as the first book"}' http://10.100.199.200/api/v1/books | jq .
curl -H 'Content-Type: application/json' -X PUT -d '{"_id": 3, "title": "My Third Book", "author": "John Doe", "description": "Failed writers club"}' http://10.100.199.200/api/v1/books | jq .
curl http://10.100.199.200/api/v1/books | jq .

Последняя команда curl должна вывести три книги, которые мы вставили ранее.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
[
  {
    "_id": 1,
    "title": "My First Book",
    "author": "Joh Doe"
  },
  {
    "_id": 2,
    "title": "My Second Book",
    "author": "John Doe"
  },
  {
    "_id": 3,
    "title": "My Third Book",
    "author": "John Doe"
  }
]

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

Нам удалось вручную развернуть одну базу данных и одну службу REST API. Оба они были развернуты не на сервере, который мы указали заранее, а на том, на котором было запущено наименьшее количество контейнеров.

У нас еще есть много возможностей для покрытия. Следующая версия нашего сервиса должна сделать еще несколько шагов, которые мы еще не сделали. Без этих дополнительных шагов у нас не было бы сине-зеленого развертывания, и каждый раз, когда мы выпускаем новую версию, было бы некоторое время простоя.

У Consul есть дополнительные преимущества, такие как проверка состояния здоровья, которая вместе с Jenkins будет перераспределять наши услуги, когда что-то пойдет не так.

Более того, мы можем захотеть иметь возможность не только масштабировать разные сервисы, но и масштабировать один и тот же сервис на нескольких серверах.

Наконец, все, что мы делали к настоящему времени, было ручным, и мы должны использовать Ansible playbooks, которые сделают все это за нас.

История продолжается в статье « Процедура развертывания, автоматизации и самовосстановления сине-зеленых» .