Запускаем HAProxy Kubernetes Ingress Controller вне кластера для уменьшения сетевой задержки и числа хопов

Содержание:

В чем проблем запуска внутри кластера

Обычно вы можете запустить HAProxy Kubernetes Ingress Controller как под внутри Kubernetes-кластера. Как под, он имеет доступ к другим подам, потому что они используют внутреннюю сеть Kubernetes-кластера. Это дает возможность управлять маршрутизацией и балансировать трафик к приложениям, запущенным в кластере. Но возникает проблема, как передать внешний трафик во внутренний Ingress Controller.

Как и другие поды, Ingress Controller существует внутри изолированной среды Kubernetes, поэтому клиенты не получат к нему доступ, пока его не пробросят с помощью Service. Когда вы создаете сервис типа NodePort или LoadBalancer, Kubernetes открывает доступ к поду из внешнего мира.

В облаках более распространен вариант LoadBalancer, потому что он говорит облачному провайдеру развернуть один из его облачных балансировщиков нагрузки (например, AWS Network Load Balancer) и поместить его перед Ingress Controller. В случае self hosted Kubernetes обычно используют NodePort и затем вручную устанавливают балансировщик нагрузки. Так что практически в каждом случае балансировщик нагрузки размещается перед вашим Ingress Controller, что означает наличие двойного проксирования, через которые должен пройти трафик для достижения приложений.

В обычной схеме внешний балансировщик нагрузки (Load Balancer) посылает трафик на один из воркеров, а затем Kubernetes передает его на узел, на котором запущен под с Ingress Controller:

Начиная с версии 1.5 HAProxy Ingress Controller, у вас есть возможность запустить его снаружи вашего Kubernetes-кластера, что избавляет от необходимости дополнительного балансировщика перед Ingress Controller. Это дает доступ к физической сети и позволяет достучаться до кластера внешним клиентам. Проблема в том, что теперь Ingress Controller не запущен как под и находится вне кластера. Поэтому ему нужен доступ к внутренней сети кластера каким-то другим способом.

Для решения этой проблемы мы установим Calico в качестве сетевого плагина в Kubernetes и настроим маршрутизацию с помощью протокола BGP. В продакшне BGP будет работать на третьем уровне сети, но для демонстрации этого мы используем в качестве роутера демона BIRD, установленного на той же VM, что Ingress Controller.

Диаграмма поясняет схему, где Ingress Controller находится снаружи Kubernetes-кластера и использует BIRD для взаимодействия с сетью кластера:

Для самостоятельного повторения примеров загрузите демо-проект из GitHub. В нем используются VirtualBox и Vagrant для создания тестового окружения с 4 виртуальными машинами. Одна VM запускает Ingress Controller и BIRD, а 3 остальные формируют Kubernetes-кластер. Вызовите vagrant up из директории проекта для создания виртуальных машин.

Мы пошагово разберем, как запустить внешний HAProxy Kubernetes Ingress Controller и как установить Kubernetes-кластер с Calico.

Kubernetes-кластер

В демо-проекте используются Vagrant и VirtualBox для создания виртуальных машин. 3 из них составляют Kubernetes-кластер:

  • один мастер-узел, который управляет кластером 

  • два рабочих узла для запуска подов

Vagrant автоматизирует большую часть установки через запуск Bash-скрипта, который запускает необходимые команды для установки Kubernetes и Calico. Если вы задаетесь вопросом, зачем нам понадобилось устанавливать Calico, напомню, что Kubneretes — модульный фреймворк. Некоторые его компоненты можно заменить другими при условии, что они будут реализовывать нужный интерфейс. Сетевые плагины должны реализовывать CNI. Среди популярных вариантов Calico, Flannel, Cilium и Weave Net. BGP-пиринг в Calico делает его хорошим вариантом конкретно в нашем случае.

Чтобы посмотреть, как настраивается мастер-узел, посмотрите Bash-скрипт проекта setup_kubernetes_control_plane.sh. Поверхностно этот процесс делится на такие этапы:

  1. Установка Docker

  2. Установка утилит kubeadm и kubectl

  3. Вызов kubeadm init для для начальной установки Kubernetes-кластера 

  4. Копирование файл kubeconfig в домашнюю директорию пользователя, чтобы при подключении по SSH к этой виртуальной машине этот пользователь мог запускать kubectl-команды

  5. Установка Calico в качестве сетевого плагина с включенным BGP

  6. Установка утилиты calicoctl

  7. Создание ConfigMap-объекта в Kubernetes haproxy-kubernetes-ingress, который требуется для Ingress Controller

Два рабочих узла инициализируются с помощью Bash-скрипта setup_kubernetes_worker.sh. Он выполняет следующие шаги:

  1. Установка Docker

  2. Установка kubeadm и kubectl

  3. Вызов kubeadm join для присоединения к кластеру в качестве воркера

Особенно интересна та часть, в которой мы установим Calico на мастер. Скрипт сначала устанавливает оператор Calico, а затем создает Kubernetes-объект Installation, который включает BGP и назначает диапазон IP-адресов для сети:

apiVersion: operator.tigera.io/v1
kind: Installation
metadata:
 name: default
spec:
 # Configures Calico networking.
 calicoNetwork:
   bgp: Enabled
    
   # Note: The ipPools section cannot be modified post-install.
   ipPools:
   - blockSize: 26
     cidr: 172.16.0.0/16
     encapsulation: IPIP
     natOutgoing: Enabled
     nodeSelector: all()

После этого скрипт устанавливает calicoctl и использует его для создания BGPConfiguration и объекта BGPPeer в Kubernetes.

apiVersion: projectcalico.org/v3
kind: BGPConfiguration
metadata:
 name: default
spec:
 logSeverityScreen: Info
 nodeToNodeMeshEnabled: true
 asNumber: 65000

---
apiVersion: projectcalico.org/v3
kind: BGPPeer
metadata:
 name: my-global-peer
spec:
 peerIP: 192.168.50.21
 asNumber: 65000

Объект BGPConfiguration устанавливает уровень логирования для BGP-коннекторов, включает режим full mesh network и назначает номер Autonomous System (AS) для Calico. Объект BGPPeer получает IP-адрес виртуальной машины, где находится Ingress Controller, где будет запущен BIRD, который установит номер AS для BIRD-маршрутизатора. Я выбрал номер AS 65000. Так Calico расшар��т сетевые маршруты BIRD, чтобы их мог использовать Ingress Controller.

Ingress Controller и BIRD

В демо-проекте мы устанавливаем HAProxy Kubernetes Ingress Controller и BIRD на одну и ту же виртуальную машину. Эта VM существует вне Kubernetes-кластера, где BIRD получает IP-маршруты от Calico, и Ingress Controller использует их для передачи клиентского трафика на поды.

Vagrant вызывает Bash-скрипт setup_ingress_controller.sh для выполнения шагов:

  1. Устанавливает HAProxy, при этом дизейблит сервис 

  2. Вызывает команду setcap для разрешения HAProxy слушать привилегированные порты 80 и 443

  3. Загружает HAProxy Kubernetes Ingress Controller и копирует его в /usr/local/bin

  4. Настраивает Systemd для запуска Ingress Controller

  5. Копирует в корень домашней директории пользователя файл kubeconfig, который необходим кластеру для отслеживания Ingress-объектов и смены сервисов

  6. Устанавливает BIRD

Ingress Controller должен быть уже запущен, так как он настроен как сервис Systemd. Он выполняет команду:

/usr/local/bin/haproxy-ingress-controller \
  --external \
  --configmap=default/haproxy-kubernetes-ingress \
  --program=/usr/sbin/haproxy \
  --disable-ipv6 \
  --ipv4-bind-address=0.0.0.0 \
  --http-bind-port=80

Аргумент --external позволяет Ingress Controller запуститься вне Kubernetes. Также он может общаться с кластером и конфигурировать HAProxy, потому что у него есть файл kubeconfig в /root/.kube/config. Однако запросы будут падать, пока мы не сделаем сеть 172.16.0.0/16 доступной.

Calico может взаимодействовать с BIRD, так что она может посылать информацию о внутренней сети. BIRD заполняет таблицу маршрутизации на сервере, где он запущен, что делает IP-адреса подов доступными для Ingress Controller. Перед тем как показать, как настроить BIRD для получения маршрутов от Calico, будет полезно увидеть, как назначаются маршруты со стороны Kubernetes.

Calico и BGP-обмен

Чтобы дать вам представление о том, как это работает, давайте познакомимся с демонстрационной средой. Виртуальные машины получили следующие IP-адреса в сети 192.168.50.0/24:

  • узел Ingress Controller = 192.168.50.21

  • мастер-узел = 192.168.50.22

  • worker 1 = 192.168.50.23

  • worker 2 = 192.168.50.24

Сначала подключимся по SSH к мастер-узлу Kubernetes, используя команду vagrant ssh:

$ vagrant ssh controlplane

Вызовем kubectl get nodes, чтобы увидеть, что все узлы подняты и готовы:

$ kubectl get nodes

NAME           STATUS   ROLES                  AGE     VERSION
controlplane   Ready    control-plane,master   4h7m    v1.21.1
worker1        Ready    <none>                 4h2m    v1.21.1
worker2        Ready    <none>                 3h58m   v1.21.1

Вызовем calicoctl node status для проверки, какая из VM делится маршрутом через BGP:

$ sudo calicoctl node status

Calico process is running.

IPv4 BGP status
+---------------+-------------------+-------+----------+--------------------------------+
| PEER ADDRESS  |     PEER TYPE     | STATE |  SINCE   |              INFO              |
+---------------+-------------------+-------+----------+--------------------------------+
| 192.168.50.21 | global            | start | 00:10:03 | Active Socket: Connection      |
|               |                   |       |          | refused                        |
| 192.168.50.23 | node-to-node mesh | up    | 00:16:11 | Established                    |
| 192.168.50.24 | node-to-node mesh | up    | 00:20:08 | Established                    |
+---------------+-------------------+-------+----------+--------------------------------+

Последние две строки с адресами 192.168.50.23 и 192.168.50.24 показывают, что рабочие узлы установили BGP-подключение и совместно используют маршруты. Однако на первой строке с адресом 192.168.50.21 мы видим, что подключение виртуальной машины Ingress Controller не выполнено, потому что мы пока не настроили BIRD.

Calico назначила каждому из воркеров Kubernetes-кластера IP адреса из подмножества более крупной сети 172.16.0.0/16, которая работает поверх сети 192.168.50.0/24. Вы можете вызвать kubectl describe blockaffinities, чтобы увидеть назначенные сетевые диапазоны.

$ kubectl describe blockaffinities | grep -E "Name:|Cidr:"

Name: controlplane-172-16-49-64-26
 Cidr: 172.16.49.64/26
Name: worker1-172-16-171-64-26
 Cidr: 172.16.171.64/26
Name: worker2-172-16-189-64-26
 Cidr: 172.16.189.64/26

Здесь мы видим, что worker 1 получил диапазон 172.16.171.64/26, а worker 2 — 172.16.171.64/26. Нам нужно отправить эту информацию в BIRD, чтобы: 

  • запрос клиента ушел в worker 1, если включает IP в диапазоне 172.16.171.64/26

  • запрос клиента ушел в worker 2, если включает IP в диапазоне 172.16.189.64/26

Мастер Kubernetes запускает поды на один из этих узлов.

Чтобы настроить конфигурацию BIRD, подключитесь по SSH к той виртуальной машине, на которой находится Ingress Controller.

$ vagrant ssh ingress

Откройте файл /etc/bird/bird.conf и добавьте protocol bgp для каждого воркера. Обратите внимание, что секция import filter говорит BIRD, что ему следует принимать только те IP-диапазоны, которые назначены этому узлу, а все остальные блокировать:

router id 192.168.50.21;

log syslog all;

# мастер-узел
protocol bgp {
   local 192.168.50.21 as 65000;
   neighbor 192.168.50.22 as 65000;
   direct;
   import filter {
     if ( net ~ [ 172.16.0.0/16{26,26} ] ) then accept;
   };
   export none;
}

# worker1
protocol bgp {
   local 192.168.50.21 as 65000;
   neighbor 192.168.50.23 as 65000;
   direct;
   import filter {
     if ( net ~ [ 172.16.0.0/16{26,26} ] ) then accept;
   };
   export none;
}

# worker2
protocol bgp {
   local 192.168.50.21 as 65000;
   neighbor 192.168.50.24 as 65000;
   direct;
   import filter {
     if ( net ~ [ 172.16.0.0/16{26,26} ] ) then accept;
   };
   export none;
}

protocol kernel {
   scan time 60;
   #import none;
   export all;   # insert routes into the kernel routing table
}

protocol device {
   scan time 60;
}

Перезапустите BIRD, чтобы принять изменения:

$ sudo systemctl restart bird

Давайте убедимся, что все работает. Вызовите birdc show protocols чтобы увидеть со стороны BIRD, что BGP-обмен установлен:

$ sudo birdc show protocols

BIRD 1.6.8 ready.
name     proto    table    state  since       info
bgp1     BGP      master   up     23:18:17    Established   
bgp2     BGP      master   up     23:18:17    Established   
bgp3     BGP      master   up     23:18:59    Established   
kernel1  Kernel   master   up     23:18:15    
device1  Device   master   up     23:18:15 

Вы также можете вызвать birdc show route protocol для проверки соответствия сетей IP-адресам виртуальных машин и сети подов:

$ sudo birdc show route protocol bgp2

BIRD 1.6.8 ready.
172.16.171.64/26   via 192.168.50.23 on enp0s8 [bgp2 23:18:18] * (100) [i]

$ sudo birdc show route protocol bgp3

BIRD 1.6.8 ready.
172.16.189.64/26   via 192.168.50.24 on enp0s8 [bgp3 23:19:00] * (100) [i]

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

$ route

Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         _gateway        0.0.0.0         UG    100    0        0 enp0s3
10.0.2.0        0.0.0.0         255.255.255.0   U     0      0        0 enp0s3
_gateway        0.0.0.0         255.255.255.255 UH    100    0        0 enp0s3
172.16.49.64    192.168.50.22   255.255.255.192 UG    0      0        0 enp0s8
172.16.171.64   192.168.50.23   255.255.255.192 UG    0      0        0 enp0s8
172.16.189.64   192.168.50.24   255.255.255.192 UG    0      0        0 enp0s8
192.168.50.0    0.0.0.0         255.255.255.0   U     0      0        0 enp0s8

Если вы вернетесь к виртуальной машине мастера и запустите calicoctl node status, вы увидите, что Calico уже зафиксировала установку BGP-обмена с виртуальной машиной Ingress Controller, 192.168.50.21:

$ sudo calicoctl node status

Calico process is running.

IPv4 BGP status
+---------------+-------------------+-------+----------+-------------+
| PEER ADDRESS  |     PEER TYPE     | STATE |  SINCE   |    INFO     |
+---------------+-------------------+-------+----------+-------------+
| 192.168.50.21 | global            | up    | 00:32:13 | Established |
| 192.168.50.23 | node-to-node mesh | up    | 00:16:12 | Established |
| 192.168.50.24 | node-to-node mesh | up    | 00:20:09 | Established |
+---------------+-------------------+-------+----------+-------------+

Добавляем Ingress

Благодаря обмену BGP-маршрутами между Kubernetes-кластером и сервером Ingress Controller мы готовы приступить к работе. Давайте добавим объект Ingress, чтобы убедиться, что все работает. Следующий YAML раскладывает 5 экземпляров приложения и создает Ingress-объект:

apiVersion: apps/v1
kind: Deployment
metadata:
 labels:
   run: app
 name: app
spec:
 replicas: 5
 selector:
   matchLabels:
     run: app
 template:
   metadata:
     labels:
       run: app
   spec:
     containers:
     - name: app
       image: jmalloc/echo-server
       ports:
       - containerPort: 8080

---       
apiVersion: v1
kind: Service
metadata:
 labels:
   run: app
 name: app
spec:
 selector:
   run: app
 ports:
 - name: port-1
   port: 80
   protocol: TCP
   targetPort: 8080

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
 name: test-ingress
 namespace: default
spec:
 rules:
 - host: test.local
   http:
     paths:
     - path: /
       pathType: Prefix
       backend:
         service:
           name: app
           port:
             number: 80

Ingress-объект настраивает Ingress Controller так, чтобы отправлять любой запрос от test.local к приложению, которое вы только что развернули. Вам понадобится обновить файл /etc/hosts на вашем хосте для сопоставления test.local с IP-адресом виртуальной машины Ingress Controller, 192.168.50.21.

Разложите объекты через kubectl:

$ kubectl apply -f app.yaml

Откройте test.local в вашем браузере, и вас встретит приложение, которое просто напечатает подробности вашего HTTP-запроса. Поздравляю! Вы запустили HAProxy Kubernetes Ingress Controller снаружи Kubernetes-кластера! Вам больше не нужно использовать двойное проксирование.

Чтобы увидеть созданный файл harpoxy.cfg, откройте /tmp/haproxy-ingress/etc/haproxy.cfg. Он сгенерировал backend с именем default-app-port-1, который содержит строку server для каждого из запущенных в приложении подов. Конечно, IP-адреса в каждой строке server теперь доступны. Вы можете масштабировать приложение в большую или меньшую сторону, и Ingress Controller автоматически настроит соответствующую конфигурацию.

backend default-app-port-1
  mode http
  balance roundrobin
  option forwardfor
  server SRV_1 172.16.171.67:8080 check weight 128
  server SRV_2 172.16.171.68:8080 check weight 128
  server SRV_3 172.16.189.68:8080 check weight 128
  server SRV_4 172.16.189.69:8080 check weight 128
  server SRV_5 172.16.189.70:8080 check weight 128

Заключение

В этой статье мы разобрали, как запустить HAProxy Kubernetes Ingress Controller снаружи Kubernetes-кластера. Это избавляет от необходимости запуска дополнительного  балансировщика нагрузки. Такой подход позволит снизить время ожидания к��иента, поскольку требует меньшего количества сетевых переходов. Для обеспечения высокой доступности вы дополнительно можете настроить Keepalive.


Другие статьи про DevOps для начинающих:

Другие статьи про DevOps для продолжающих:


За последние годы де-факто стандартом оркестрации и запуска приложений стал Kubernetes. Поэтому умение управлять Kubernetes-кластерами является особенно важным в работе любого современного DevOps-инженера. 

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

Почитать про курс подробнее можно здесь: https://vk.cc/cn0ty6