В сегодняшнем сообщении я опишу работу с двумя Load Balancers — Traefik и HAProxy. Эти Load Balancers имеют возможность работать с внушительным списком средств оркестрации. В примерах будет описана работа с системой оркестрации Nomad.
В прошлом сообщении я уже приводил пример Load Balancers — Fabio. Его ограничения: работает только с http/https протоколами, и работает только с Consul. В отличие от Fabio, Load Balancers Troefik работает с впечатляющим количеством различных систем. Вот их неполный список, взятый с сайта разработчика: Docker, Swarm mode, Kubernetes, Marathon, Consul, Etcd, Rancher, Amazon ECS,…
Я продолжу пример из предыдущего сообщения, в котором создавалось несколько реплик сервиса django.
Traefik можно загрузить с сайта разработчика в виде исполняемого файла для наиболее распространенных операционных систем. Для интеграции с Nomad (собственно с Consul) необходимо создать файл конфигурации:
[entryPoints]
[entryPoints.http]
address = ":5001"
[web]
address = ":8080"
[consulCatalog]
endpoint = "127.0.0.1:8500"
domain = "consul.localhost"
exposedByDefault = false
prefix = "traefik"
И далее запустить на выполнение команду с приведенным файлом конфигурации:
traefik -c nomad/traefik.toml
После этого на порте 8080 будет доступен UI Traefik, в котором пока не опубликован ни один сервис. Для публикации сервисов существует несколько способов, которые в конечном счете делают одно и тоже — загружают в систему Traefik данные типа ключ/значение. Мы воспользуемся возможностью задавать пары ключ/значение через tags сервисов. Дополним конфигурационный файл сервиса django параметром tags:
job "django-job" {
datacenters = ["dc1"]
type = "service"
group "django-group" {
count = 3
restart {
attempts = 2
interval = "30m"
delay = "15s"
mode = "fail"
}
ephemeral_disk {
size = 300
}
task "django-job" {
driver = "docker"
config {
image = "apapacy/tut-django:1.0.1"
port_map {
lb = 8000
}
}
resources {
network {
mbits = 10
port "lb" {}
}
}
service {
name = "django"
tags = [
"traefik.enable=true",
"traefik.frontend.entryPoints=http",
"traefik.frontend.rule=Host:localhost;PathStrip:/test",
"traefik.tags=exposed"
]
port = "lb"
check {
name = "alive"
type = "http"
path = "/"
interval = "10s"
timeout = "2s"
}
}
}
}
}
В данном примере сервис будет опубликован на хосте localhost и примонтирован к роуту /test. В Troefik разработана гибкая и полная система правил для конфигурирования роутов, включая работу с регулярными выражениями. Перечень параметров для правил в документации разработчика.
После выполнения команды
nomad job run nomad/django.conf
правила применятся, и на сервис будет направлен трафик с Load Balancer. Соответственно, можно менять эти параметры, деплоить новый вариант сервиса командой nomad job run nomad/django.conf
, и все изменения применятся без досадной остановки трафика.Недостатком Troefik является то что он работает с протоколами семейства http/https (на всякий случай замечу что это семейство включает и веб-сокеты). Но все же есть такая вероятность, что возникнет необходимость работать и с другими протоколами. Поэтому перейдем к следующему более широкому решению на основе HAProxy. Какое-то время назад у HAProxy были проблемы с «мягкой» перегрузкой, что делало его использование с системами оркестрации сложным (на время рестарта приходилось на сетевом уровне приостанавливать движение пакетов). Сейчас это уже не проблема.
Для начала необходимо установить haproxy на компьютер. Здесь не подойдет вариант с установкой внутри контейнера. В haproxy только недавно появилась возможность рестартовать процесс в «мягком» режиме, но при этом все равно контейнер докер останавливается, так как фактически запускается второй процесс с haproxy, просто их смена проходит в режиме ожидания — что не работает с докером и его принципом «один-контейнер-один процесс».
Для работы haproxy необходимо иметь файл конфигурации, в котором прописаны необходимые правила. В Nomad (собственно в Consul) применяется система шаблонов, которая может генерировать конфигурации:
global
debug
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5000
timeout client 50000
timeout server 50000
frontend http_front
bind *:5001
stats uri /haproxy?stats
default_backend http_back
backend http_back
balance roundrobin{{range service "django"}}
server {{.Node}} {{.Address}}:{{.Port}} check{{end}}
Ключевое слово
range
в данном случае выполняет роль итератора. Для трех сервисов «django» будет сформирован такой файл конфигурации:global
debug
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5000
timeout client 50000
timeout server 50000
frontend http_front
bind *:5001
stats uri /haproxy?stats
default_backend http_back
backend http_back
balance roundrobin
server 228.195.86.224 127.0.0.1:21469 check
server 228.195.86.224 127.0.0.1:25872 check
server 228.195.86.224 127.0.0.1:25865 check
Для запуска процесса генерации по шаблону «на лету» применяется библиотека https://github.com/hashicorp/consul-template. С ресурса разработчика можно загрузить исполняемый файл для всех распространенных операционных систем, и запускать процесс от имени не привилегированного пользователя командой:
consul-template -template="haproxy/haproxy.cfg.tmpl:haproxy/haproxy.cfg:./haproxy/haproxy.reload.sh"
Параметр
-template
содержит разделенные двоеточием параметры 1) имя шаблона, 2) имя получаемого файла конфигурации 3) команда, которая выполняется после генерации файла. Файл будет автоматически генерироваться если будут изменены переменные входящие в шаблон (например изменено количество реплик сервиса django).После запуска шаблонизатора, который сгенерирует первую конфигурацию можно запускать haproxy:
haproxy -D -f haproxy/haproxy.cfg -p `pwd`/haproxy.pid
Мы явно указываем pid-файл, для того чтобы иметь возможность отправить сигнал на «мягкую» перегрузку haproxy:
haproxy -D -f ./haproxy/haproxy.cfg -p `pwd`/haproxy.pid -sf $(cat `pwd`/haproxy.pid)
В данном примере сервис опубликован на порту 5001. На этом же порту можно просмотреть статистику самого haproxy по адресу
/haproxy?stats
.UPDATED 24.02.2019 22:43
По комментарию @usego было проведено дополнительное уточнение работы haproxy в контейнере докер, в частности по фрагменту из документации github.com/neo4j/docker-library-docs/tree/master/haproxy#reloading-config
Reloading config
If you used a bind mount for the config and have edited your haproxy.cfg file, you can use HAProxy's graceful reload feature by sending a SIGHUP to the container:
$ docker kill -s HUP my-running-haproxy
The entrypoint script in the image checks for running the command haproxy and replaces it with haproxy-systemd-wrapper from HAProxy upstream which takes care of signal handling to do the graceful reload. Under the hood this uses the -sf option of haproxy so «there are two small windows of a few milliseconds each where it is possible that a few connection failures will be noticed during high loads» (see Stopping and restarting HAProxy).
При таком подходе действительно происходит перезагрузка конфигурации, но в результате прерывания текущего процесса. А это означает что сервисы будут иметь хотя и очень незначительный, но все же период недоступности и часть клиентов может наблюдать сообщение об ошибке. Но иногда это не является главным критерием выбора. Поэтому приведу в дополнение конфигурацию docker-compose.yml для запуска haproxy в докере:
version: '3'
services:
haproxy_lb:
image: haproxy
volumes:
- ./haproxy:/usr/local/etc/haproxy
network_mode: host
Также изменится команда, которая будет перегружать конфигурацию haproxy:
consul-template -template="haproxy/haproxy.cfg.tmpl:haproxy/haproxy.cfg:docker kill -s HUP $(docker-compose ps -q haproxy_lb)"
К преимуществам такой реализации следует отнести возможность работать без инсталляции haproxy.
Код примеров доступен в репозитарии
Полезные ссылки:
1. www.haproxy.com/blog/haproxy-and-consul-with-dns-for-service-discovery
2. m.mattmclaugh.com/traefik-and-consul-catalog-example-2c33fc1480c0
3. www.hashicorp.com/blog/load-balancing-strategies-for-consul
apapacy@gmail.com
24 февраля 2019 года
Комментарии (11)
powerman
25.02.2019 07:23Было бы интересно почитать общее сравнение разных способов реализовать балансировщик, начиная с вариантов "для бедных" вроде DNS плюс обычный nginx (через
resolver a.b.c.d;
иproxy_pass $backend;
), встроенный балансировщик docker swarm, AWS ELB, вышеупомянутые tr?fik и haproxy, платный NGINX Plus, …alexesDev
25.02.2019 09:15+1Оно есть на английском
www.hashicorp.com/blog/load-balancing-strategies-for-consul
Там не указан traefik, но это можно просто в его доке посмотреть (в разделе Consul Catalog).
Valsha
25.02.2019 18:28Добрый день. Очень интересная тема, как раз последние 2-3 дня разбираюсь с Load Balancers для систем оркестрации, в моем случае это Docker Swarm.
Подскажите пожалуйста, я использовал этот материал для развертывания и деплоя — habr.com/ru/post/344324 (запущен 1 manager и 3 воркера)
В данном примере используется nginx как Load Balancers, но там он работает в моем случае (возможно я что то упустил) очень плохо, часто вижу ошибку 502 Bad Gateway (тайминги/задержки в nginx выставил по 900ms)
При этом в логах
dz6prru com.docker.swarm.node.id=tyllayuan6hd5uhtkr9nlz55y,com.docker.swarm.service.id=aincbpoo6d4etcrcat4upmba7,com.docker.swarm.task.id=dz6prrugfy9lypymsvdxomxdp 2019/02/24 22:21:00 [emerg] 1#1: host not found in upstream «web» in /etc/nginx/conf.d/flask.conf:26
mvchude 10.255.0.2 — - [24/Feb/2019:22:23:38 +0000] «GET / HTTP/1.1» 499 0 "-" «Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0; Waterfox) Gecko/20100101 Firefox/56.2.6» "-"
54ukzuf 10.255.0.2 — - [24/Feb/2019:22:25:50 +0000] «GET / HTTP/1.1» 502 157 "-" «Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0; Waterfox) Gecko/20100101 Firefox/56.2.6» "-"
54ukzuf 10.255.0.2 — - [25/Feb/2019:08:41:05 +0000] «GET / HTTP/1.1» 499 0 "-" «Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0; Waterfox) Gecko/20100101 Firefox/56.2.6» "-"
mvchude 2019/02/24 22:39:53 [error] 6#6: *3 connect() failed (110: Connection timed out) while connecting to upstream, client: 10.255.0.2, server:, request: «GET / HTTP/1.1», upstream: «10.0.1.94:5000/», host: «192.168.21.66:88»
54ukzuf 2019/02/24 22:25:50 [error] 6#6: *1 connect() failed (110: Connection timed out) while connecting to upstream, client: 10.255.0.2, server:, request: «GET / HTTP/1.1», upstream: «10.0.1.94:5000/», host: «192.168.21.66:88»
mvchude 10.255.0.2 — - [24/Feb/2019:23:00:42 +0000] «GET / HTTP/1.1» 200 71 "-" «Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0; Waterfox) Gecko/20100101 Firefox/56.2.6» "-"
Я по ману пробовал настроить Traefik docs.traefik.io/user-guide/swarm-mode
1. docker network create --driver=overlay traefik-net
2. docker service create \
--name traefik \
--constraint=node.role==manager \
--publish 90:90 --publish 9090:9090 \
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
--network traefik-net \
traefik \
--docker \
--docker.swarmMode \
--docker.domain=traefik \
--docker.watch \
--api
Но на manager_swarm_ip:9090 ничего не открывает тоже.
Даже и не знаю куда еще «копать».
Может вы что то посоветуете,
Спасибо.apapacy Автор
25.02.2019 19:52Я со swarm не разбирался. Судя по строчке в логах host not found in upstream не настроена сеть.
powerman
25.02.2019 20:14В swarm встроенный балансировщик: подключение на открытый внешний порт любого узла swarm-а автоматически перенаправляет запрос на внутренний порт одного из тех узлов swarm, на котором сейчас запущен данный сервис (контейнер).
Valsha
26.02.2019 12:50Это понятно, что в «swarm встроенный балансировщик», просто не могу разобратся чего одна и та же нода, может так вот работать:
54ukzuf 10.255.0.2 - - [24/Feb/2019:22:25:50 +0000] "GET / HTTP/1.1" 502 157 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0; Waterfox) Gecko/20100101 Firefox/56.2.6" "-"
54ukzuf 10.255.0.2 - - [25/Feb/2019:08:41:05 +0000] "GET / HTTP/1.1" 499 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0; Waterfox) Gecko/20100101 Firefox/56.2.6" "-"
54ukzuf 10.255.0.2 - - [25/Feb/2019:20:44:59 +0000] "GET / HTTP/1.1" 200 71 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0; Waterfox) Gecko/20100101 Firefox/56.2.6" "-"
54ukzuf 10.255.0.2 - - [25/Feb/2019:20:45:00 +0000] "GET / HTTP/1.1" 200 71 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0; Waterfox) Gecko/20100101 Firefox/56.2.6" "-"
54ukzuf 10.255.0.2 - - [25/Feb/2019:20:45:06 +0000] "GET / HTTP/1.1" 499 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0; Waterfox) Gecko/20100101 Firefox/56.2.6" "-"
54ukzuf 10.255.0.2 - - [26/Feb/2019:04:40:13 +0000] "GET / HTTP/1.1" 499 0 "-" "NetSystemsResearch studies the availability of various services across the internet. Our website is netsystemsresearch.com" "-"
54ukzuf 10.255.0.2 - - [26/Feb/2019:08:56:37 +0000] "GET / HTTP/1.1" 200 71 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0; Waterfox) Gecko/20100101 Firefox/56.2.6" "-"
54ukzuf 10.255.0.2 - - [26/Feb/2019:08:56:39 +0000] "GET / HTTP/1.1" 200 72 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0; Waterfox) Gecko/20100101 Firefox/56.2.6"
Понтяно было бы если разные ноды и может проблема была с каким то из узлов, а так…powerman
26.02.2019 16:50Я отвечал не Вам. А по Вашей проблеме смотреть надо логи backend-сервиса, в который эти запросы передаются, плюс смотреть не перезапускаются ли постоянно какие-нибудь контейнеры. 499 обычно означает, что backend слишком долго обрабатывал запрос и клиент отвалился, 502 что nginx не достучался до backend — в общем, проблема не с nginx, и его логи тут не сильно помогут.
usego
> В haproxy только недавно появилась возможность рестартовать процесс в «мягком» режиме, но при этом все равно контейнер докер останавливается
Это точно? Сам не пробовал, но в официальном докере написано:
Reloading config
If you used a bind mount for the config and have edited your haproxy.cfg file, you can use HAProxy's graceful reload feature by sending a SIGHUP to the container:
$ docker kill -s HUP my-running-haproxy
apapacy Автор
Спасибо за дополнение. Я дополнил статью Вашим примером из документации.
В этом случае будет «жесткая» перегрузка haproxy. Т.к. механизм «мягкой загрузки» подразумевает одновременную работу двух процессов. В документации haproxy, на которую ссылается Ваш источник это процесс описан так: