Pull to refresh

Load Balancers для систем оркестрации

Reading time6 min
Views28K
К Load Balancers в системах оркестрации (Kubernetes, Nomad и других) предъявляется больше требований, чем просто балансировка нагрузки. Во-первых, Load Balancer должен уметь читать каталог со списком сервисов, на которые необходимо перенаправлять трафик (или, как вариант, давать возможность сервисам регистрироваться на включение их в трафик). Во-вторых, делать это динамически, т.к. системы оркестрации в любой момент могут увеличить или уменьшить количество реплик сервисов, или переместить их на другие адреса в сети. И, в-третьих, делать это без остановки трафика.

В сегодняшнем сообщении я опишу работу с двумя Load Balancers — Traefik и HAProxy. Эти Load Balancers имеют возможность работать с внушительным списком средств оркестрации. В примерах будет описана работа с системой оркестрации Nomad.

В прошлом сообщении я уже приводил пример Load Balancers — Fabio. Его ограничения: работает только с http/https протоколами, и работает только с Consul. В отличие от Fabio, Load Balancers Traefik работает с впечатляющим количеством различных систем. Вот их неполный список, взятый с сайта разработчика: 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. В Traefik разработана гибкая и полная система правил для конфигурирования роутов, включая работу с регулярными выражениями. Перечень параметров для правил в документации разработчика.

После выполнения команды nomad job run nomad/django.conf правила применятся, и на сервис будет направлен трафик с Load Balancer. Соответственно, можно менять эти параметры, деплоить новый вариант сервиса командой nomad job run nomad/django.conf, и все изменения применятся без досадной остановки трафика.

Недостатком Traefik является то что он работает с протоколами семейства 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 года
Tags:
Hubs:
+16
Comments12

Articles