Статьи

Обнаружение службы с помощью Docker и Consul: часть 1

За последний год я стал большим поклонником использования Consul для всего, что связано с обнаружением сервисов. Если вы пользуетесь микросервисами, вы, вероятно, столкнулись с проблемой, заключающейся в том, что с увеличением количества создаваемых вами услуг становится все сложнее управлять связью между всеми этими службами. Консул обеспечивает идеальное решение этой проблемы. Он предоставляет простой в использовании, открытый (основанный на стандартах) подход к обнаружению услуг (и, кроме того, предоставляет большой набор других функций). Недавно я выступил с докладом о том, как выполнить обнаружение служб в архитектуре микросервисов с помощью Consul , и получил несколько запросов, чтобы объяснить немного больше об этом. Итак, в этой статье и нескольких последующих действиях я расскажу немного больше о том, как вы можете использовать Консул. Я не просто остановлюсь только на части обнаружения услуг, предоставляемой консулом, но также покажу вам пару других функций, предоставляемых консулом или одним из инструментов, его окружающих. Обратите внимание, что все примеры, файлы Docker и т. Д. Можно найти в следующем репозитории: https://github.com/josdirksen/next-build-consul . Поэтому вместо «копирования и вставки» из этой статьи просто клонируйте репозиторий.

Начиная

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

demo1

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

Примечание: я использую docker-machine на моем Mac, чтобы сделать все это. Если вы используете Windows или Linux, команды могут немного отличаться. Будем надеяться, что Docker для Mac (и Windows) быстро выйдет из бета-версии ( https://blog.docker.com/2016/03/docker-for-mac-windows-beta/) , поэтому нам это больше не нужно …

  1. Создайте четыре док-машины: одну, на которой мы запустим сервер Consul, и три, на которых мы запустим наши отдельные сервисы и агента Consul.
  2. Запустите главный консул-сервер: мы будем использовать один сервер Consul (и несколько агентов Consul, подробнее об этом позже), чтобы отслеживать работающие службы и некоторые связанные с докером вещи.
  3. Настройка Docker Swarm : чтобы избежать необходимости развертывания наших служб по отдельности, мы будем использовать Docker Swarm для управления тремя узлами, на которых мы будем запускать наши службы. В оставшейся части этой статьи мы будем использовать docker-compose для запуска и остановки отдельных сервисов.
  4. Настройка оверлейной сети Docker: если мы хотим, чтобы наши сервисы общались друг с другом простым способом, мы создадим оверлейную сеть. Это позволит компонентам, которые мы развертываем в Docker, легко общаться друг с другом (так как они будут использовать одну подсеть)
  5. Запустите агентов Консула: у каждого узла будет свой собственный агент консула, который будет следить за состоянием служб на этом узле и связываться с сервером консула.

Создание докер-машин

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

1
docker-machine create nb-consul --driver virtualbox

Прежде чем мы запустим сервер Consul, давайте кратко рассмотрим архитектуру, лежащую в основе Consul.

Консул-арка

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

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

1
2
3
4
5
6
7
# quickly switch environments e.g: . dm-env nb-consul
$ cat ~/bin/dm-env
eval `docker-machine env $2 $1`
  
# avoid typing too much
$ alias dm
dm=docker-machine

Таким образом, с этими псевдонимами, сначала мы делаем «dm-env nb-consul», чтобы выбрать правильный докер.

Запустить главный консул машины

Затем мы получаем IP-адрес этого сервера, а затем мы можем запустить наш сервер Consul следующим образом.

1
2
3
4
5
6
7
8
# get the ip address
$
192.168.99.106
  
# use this ip address in the advertise
docker run -d --restart always -p 8300:8300 -p 8301:8301 -p 8301:8301/udp -p 8302:8302/udp \
           -p 8302:8302 -p 8400:8400 -p 8500:8500 -p 53:53/udp -h server1 progrium/consul \
           -server -bootstrap -ui-dir /ui -advertise $(dm ip nb-consul)

На данный момент у нас работает консольный сервер Docker. Теперь давайте создадим три других сервера, на которых мы будем запускать наши сервисы.

Настройка Docker Swarm

Как вы можете видеть в следующих командах, мы также одновременно создаем рой-кластер Docker, и узел «nb1» является мастером роя.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
docker-machine create -d virtualbox --swarm --swarm-master \
           --swarm-discovery="consul://$(docker-machine ip nb-consul):8500" \
           --engine-opt="cluster-store=consul://$(docker-machine ip nb-consul):8500" \
           --engine-opt="cluster-advertise=eth1:2376" nb1
  
docker-machine create -d virtualbox --swarm  \
            --swarm-discovery="consul://$(docker-machine ip nb-consul):8500" \
            --engine-opt="cluster-store=consul://$(docker-machine ip nb-consul):8500" \
           --engine-opt="cluster-advertise=eth1:2376" nb2
  
docker-machine create -d virtualbox --swarm \
              --swarm-discovery="consul://$(docker-machine ip nb-consul):8500"  \
              --engine-opt="cluster-store=consul://$(docker-machine ip nb-consul):8500" \
              --engine-opt="cluster-advertise=eth1:2376" nb3

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

1
2
3
4
5
6
$ dm ls
NAME        ACTIVE   DRIVER       STATE     URL                         SWARM
nb1         -        virtualbox   Running   tcp://192.168.99.110:2376   nb1 (master)
nb2         -        virtualbox   Running   tcp://192.168.99.111:2376   nb1
nb3         -        virtualbox   Running   tcp://192.168.99.112:2376   nb1
nb-consul   *        virtualbox   Running   tcp://192.168.99.106:2376

Прежде чем мы продолжим настройку ведомых устройств, есть еще один полезный скрипт, который может пригодиться:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
$ cat addToHost
#!/usr/bin/env bash
  
$ cat addToHost
#!/usr/bin/env bash
  
update-docker-host(){
    # clear existing docker.local entry from /etc/hosts
    sudo sed -i "/"${1}"\.local$/d" /etc/hosts
  
    # get ip of running machine
    export DOCKER_IP="$(docker-machine ip $1)"
  
    # update /etc/hosts with docker machine ip
     && sudo /bin/bash -c "echo \"${DOCKER_IP} $1.local\" >> /etc/hosts"
}
  
update-docker-host nb1
update-docker-host nb2
update-docker-host nb3
update-docker-host nb-consul

Этот скрипт добавляет IP-адреса докер-машин в ваш локальный файл «hosts». Это означает, что мы можем просто получить доступ к узлам докера, просто перейдя, например, по адресу « http: //nb-consul.local: 8500 ».

Настройте сеть докеров

В нашем сценарии мы хотим, чтобы все наши сервисы могли общаться друг с другом. У нас есть несколько хостов докеров, поэтому нам нужно найти простой способ, чтобы службы работали в узле «nb1», чтобы иметь возможность общаться с «nb2». Самый простой способ сделать это — создать единую сеть, которая будет использоваться всеми службами, работающими в док-контейнерах. Для этого мы создаем простую «оверлейную» сеть, например:

1
2
3
# select the swarm master
$dm-env nb1 --swarm
# create an overlay network the the name my-net

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

Начать консул агентов

Чтобы запустить консулов, мы собираемся использовать docker-compose. Файл docker-compose очень прост и является простым способом избежать ввода всех команд запуска (особенно когда вы делаете живые демонстрации)

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
version: '2'
  
services:
  agent-1:
    image: progrium/consul
    container_name: consul_agent_1
    ports:
      - 8300:8300
      - 8301:8301
      - 8301:8301/udp
      - 8302:8302
      - 8302:8302/udp
      - 8400:8400
      - 8500:8500
      - 53:53/udp
    environment:
      - "constraint:node==nb1"
    command: -ui-dir /ui -join 192.168.99.106 -advertise 192.168.99.110
    networks:
      default:
        aliases:
          - agent-1
  
  agent-2:
    image: progrium/consul
    container_name: consul_agent_2
    ports:
      - 8300:8300
      - 8301:8301
      - 8301:8301/udp
      - 8302:8302
      - 8302:8302/udp
      - 8400:8400
      - 8500:8500
      - 53:53/udp
    environment:
      - "constraint:node==nb2"
    command: -ui-dir /ui -join 192.168.99.106 -advertise 192.168.99.111
    networks:
      default:
        aliases:
          - agent-2
  
  agent-3:
    image: progrium/consul
    container_name: consul_agent_3
    ports:
      - 8300:8300
      - 8301:8301
      - 8301:8301/udp
      - 8302:8302
      - 8302:8302/udp
      - 8400:8400
      - 8500:8500
      - 53:53/udp
    environment:
      - "constraint:node==nb3"
    command: -ui-dir /ui -join 192.168.99.106 -advertise 192.168.99.112
    networks:
      default:
        aliases:
          - agent-3
  
networks:
  default:
    external:
      name: my-net

Ничего особенного в этом файле. Единственное, что вы можете заметить, это то, что мы используем явные IP-адреса в командах для запуска агентов Консула. Мы могли бы просто использовать для этого переменную окружения, которая устанавливается простым скриптом bash. Но для этой статьи мы просто указываем IP-адреса соответствующих докер-машин. Убедитесь, что ваш «DOCKER_HOST» указывает на мастера роя докеров и запускайте агентов следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
# start the agents
$ docker-compose -f docker-compose-agents.yml up -d
Creating consul_agent_3
Creating consul_agent_2
Creating consul_agent_1
  
# check what is running
$ docker ps  --format '{{ .ID }}\t{{ .Image }}\t{{ .Command }}\t{{ .Names}}'
  
bf2000882dcc    progrium/consul "/bin/start -ui-dir /"  nb1/consul_agent_1
a1bc26eef516    progrium/consul "/bin/start -ui-dir /"  nb2/consul_agent_2
eb0d1c0cc075    progrium/consul "/bin/start -ui-dir /"  nb3/consul_agent_3

На данный момент у нас есть сервер Consul, работающий в док-машине «nb-consul», и на наших узлах работают три агента. Чтобы проверить нашу настройку, давайте откроем интерфейс сервера Consul: http: //nb-consul.local: 8500

consul_1

И, как вы видите, у нас работает 1 сервер (наш сервер Consul) и три агента. Итак, с этого момента мы можем начать добавлять наши сервисы, чтобы перейти к этой архитектуре:

demo1 (1)

Добавление услуг

Сервисы в этом случае — просто простые приложения golang. Я создал простое приложение, которое может работать в веб-интерфейсе или в бэкэнд-режиме. В режиме внешнего интерфейса он предоставляет минимальный пользовательский интерфейс с кнопкой для вызова бэкэнд-службы, а в режиме внутреннего интерфейса он предоставляет простой API, который возвращает некоторую информацию вызывающей стороне, и предоставляет простой интерфейс, показывающий некоторую статистику. Для удобства я перенес это изображение в концентратор докеров ( https://hub.docker.com/r/josdirksen/demo-service/ ), чтобы вы могли легко использовать его без необходимости сборки из исходного репозитория github.

Как вы можете видеть из предыдущего обзора архитектуры, мы хотим запустить интерфейс и бэкэнд-сервис на каждом из узлов. Мы могли бы сделать это вручную, но, поскольку у нас есть docker-swarm, мы можем легко сделать это через один файл docker-compose. Если вы хотите посмотреть, как этот файл выглядит, вы можете проверить источники здесь ( https://github.com/josdirksen/next-build-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
26
27
# make sure you select the swarm master
$ . dm-env nb1 --swarm
  
# now use docker-compose to run the backend services
$ docker-compose -f docker-compose-backend.yml up -d
Creating Backend2
Creating Backend3
Creating Backend1
  
# and use docker-compose to run the frontend services
$ docker-compose -f docker-compose-frontend.yml up -d
Creating Frontend1
Creating Frontend3
Creating Frontend2
  
# check in docker if everything is running
$ docker ps --format '{{ .ID }}\t{{ .Image }}\t{{ .Command }}\t{{ .Names}}'
  
65846be2e367    josdirksen/demo-service "/entrypoint.sh --typ"  nb2/Frontend2
aedd80ab0889    josdirksen/demo-service "/entrypoint.sh --typ"  nb3/Frontend3
d9c3b1d83b5e    josdirksen/demo-service "/entrypoint.sh --typ"  nb1/Frontend1
7c860403b257    josdirksen/demo-service "/entrypoint.sh --typ"  nb1/Backend1
80632e910d33    josdirksen/demo-service "/entrypoint.sh --typ"  nb3/Backend3
534da0670e13    josdirksen/demo-service "/entrypoint.sh --typ"  nb2/Backend2
bf2000882dcc    progrium/consul "/bin/start -ui-dir /"  nb1/consul_agent_1
a1bc26eef516    progrium/consul "/bin/start -ui-dir /"  nb2/consul_agent_2
eb0d1c0cc075    progrium/consul "/bin/start -ui-dir /"  nb3/consul_agent_3

Как вы можете видеть в последнем выводе «docker ps», у нас работают три интерфейса, три сервера и три агента консула. Это в значительной степени та архитектура, к которой мы стремимся. Мы также можем увидеть это, когда откроем Консул:

consul_2

Как видите, у нас есть три внешних сервиса и три внутренних сервиса, зарегистрированных в Консуле. Если мы откроем один из бэкэндов, мы увидим некоторую общую информацию:

backend_1

И мы можем использовать интерфейс внешнего интерфейса, чтобы вызвать один из наших бэкэндов:

frontend_1

Однако есть пара вопросов, на которые мы должны ответить:

  1. Регистрация сервиса : когда мы запускаем бэкэнд или фронтенд сервис, мы видим его в Консуле. как нам это сделать?
  2. Обнаружение службы : и когда мы нажимаем кнопку на веб-службе, вызывается одна из внутренних служб. Как веб-интерфейс узнает, какую службу позвонить?

В следующих разделах мы рассмотрим эти вопросы поближе.

Служба регистрации

Во-первых, регистрация сервиса. Чтобы зарегистрировать услугу в Консуле, мы должны сделать очень простой вызов REST нашему местному консулу-агенту, который выглядит примерно так:

1
2
3
4
5
6
7
8
9
{
  "Name": "service1",
  "address": "10.0.0.12",
  "port": 8080,
  "Check": {
     "http": "http://10.0.0.12:8080/health",
     "interval": "5s"
  }
}

Как видите, мы указываем имя, адрес и порт, где находится служба, и добавляем дополнительную проверку работоспособности. Когда healtcheck возвращает что-то в диапазоне 200, служба помечается как исправная и может быть обнаружена другими службами. Так, как мы делаем это для наших услуг. Если вы посмотрите на источники для этого примера, вы можете найти файл «script / entrypoint.sh», который выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
#!/usr/bin/env bash
  
IP=`ip addr | grep -E 'eth0.*state UP' -A2 | tail -n 1 | awk '{print $2}' | cut -f1 -d '/'`
NAME="$2-service"
  
read -r -d '' MSG << EOM
{
  "Name": "$NAME",
  "address": "$IP",
  "port": $PORT,
  "Check": {
     "http": "http://$IP:$PORT",
     "interval": "5s"
  }
}
EOM
  
curl -v -XPUT -d "$MSG" http://consul_agent_$SERVER_ID:8500/v1/agent/service/register && /app/main "$@"

Что делает этот скрипт, так это то, что он создает JSON для отправки на консул-агент, и перед запуском основного приложения он использует «curl» для его отправки. Поэтому, когда служба запускается, она автоматически регистрируется в локальном агенте консула (обратите внимание, что вы также можете сделать это более автоматически, например, с помощью Consul Registrator . Это работает, поскольку мы можем просто ссылаться на локального агента по его имени, поскольку он находится в тот же самый контроллер. Если вы посмотрите внимательно, вы можете увидеть, что здесь мы используем пару переменных окружения. Они передаются через файл docker-compose, который мы используем:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
...
frontend-1:
    image: josdirksen/demo-service
    container_name: Frontend1
    ports:
      - 8090:8090
    environment:
      - "constraint:node==nb1"
      - SERVER_ID=1
      - SERVERNAME=Server1
      - PORT=8090
    command: /entrypoint.sh --type frontend
    dns: 192.168.99.106
    dns_search: service.consul
...

Интересная часть здесь — записи DNS. Как вы помните, 192.168.99.106 — это адрес нашего консул-сервера. Это означает, что мы проводим поиск DNS против Консула (мы могли бы также указать на агента консула).

Обнаружение услуг

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

01
02
03
04
05
06
07
08
09
10
11
12
# check which IPs are registered for the backend-service
# called from outside the container
$ dig @nb-consul.local backend-service.service.consul +short
10.0.9.7
10.0.9.8
10.0.9.6
  
# If we do this from a container, we can do just this
docker exec -ti nb2/Frontend2 ping backend-service
PING backend-service.service.consul (10.0.9.8): 56 data bytes
64 bytes from 10.0.9.8: icmp_seq=0 ttl=64 time=0.809 ms
64 bytes from 10.0.9.8: icmp_seq=1 ttl=64 time=0.636 ms

Круто верно? Мы можем обнаружить сервис, просто используя DNS. Это также означает, что интегрировать это в наши существующие приложения очень просто, поскольку мы можем просто полагаться на базовое разрешение DNS. Например, в веб-сервисе мы вызываем бэкэнд, используя этот код:

01
02
03
04
05
06
07
08
09
10
resp, err := http.Get("http://backend-service:8081")
if err != nil {
    // handle error
    fmt.Println(err)
} else {
    defer resp.Body.Close()
    body, _ := ioutil.ReadAll(resp.Body)
    w.Header().Set("Content-Type",resp.Header.Get("Content-Type"))
    w.Write(body)
}

Это вызывает одну из внутренних служб, использующих DNS. Теперь у нас также есть простое переключение при сбое, так как временное время DNS Consul установлено в 0. Приложения могут все еще выполнять некоторое кэширование, но это означает, что у нас уже есть некоторое базовое переключение:

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
$ curl -s backend-service:8081
  
        {"result" : {
          "servername" : "Server1",
          "querycount" : 778
          }
        }
  
# shutdown server 1 and do again, curl has a DNS cache of
# 1 minute, so you might need to wait a bit
$ curl -s backend-service:8081
  
        {"result" : {
          "servername" : "Server2",
          "querycount" : 770
          }
        }
  
$ curl -s backend-service:8081
  
        {"result" : {
          "servername" : "Server2",
          "querycount" : 771
          }
        }

Что, конечно, также работает для нашего приложения frontend / golang:

consul_3

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

Выводы

Это в значительной степени завершает эту первую статью. Итак, в заключение, что мы сделали:

  1. Мы настроили простую архитектуру, используя 4 док-узла. 1 для сервера консула и три для наших услуг.
  2. Службы регистрируются в Консуле при запуске службы.
  3. Нам не нужно явно что-то делать, чтобы включить обнаружение службы. Мы можем использовать стандартный DNS для поиска службы.
  4. Consul использует TTL 0 для DNS и возвращает доступные сервисы, используя roundrobin. Как вы уже видели, вы уже можете использовать это для базового переключения при сбое, когда поиск DNS не удается.

Следите за новостями в ближайшие недели.

Ссылка: Обнаружение услуг с Docker и Consul: часть 1 от нашего партнера JCG Йоса Дирксена из блога Smart Java .