Запускаем 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. Поверхностно этот процесс делится на такие этапы:
Установка Docker
Установка утилит
kubeadm
иkubectl
Вызов
kubeadm init
для для начальной установки Kubernetes-кластераКопирование файл kubeconfig в домашнюю директорию пользователя, чтобы при подключении по SSH к этой виртуальной машине этот пользователь мог запускать
kubectl
-командыУстановка Calico в качестве сетевого плагина с включенным BGP
Установка утилиты
calicoctl
Создание ConfigMap-объекта в Kubernetes haproxy-kubernetes-ingress, который требуется для Ingress Controller
Два рабочих узла инициализируются с помощью Bash-скрипта setup_kubernetes_worker.sh. Он выполняет следующие шаги:
Установка Docker
Установка
kubeadm
иkubectl
Вызов
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
для выполнения шагов:
Устанавливает HAProxy, при этом дизейблит сервис
Вызывает команду
setcap
для разрешения HAProxy слушать привилегированные порты 80 и 443Загружает HAProxy Kubernetes Ingress Controller и копирует его в /usr/local/bin
Настраивает Systemd для запуска Ingress Controller
Копирует в корень домашней директории пользователя файл kubeconfig, который необходим кластеру для отслеживания Ingress-объектов и смены сервисов
Устанавливает 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.
От переводчиков
В эту среду, 1-го декабря в 19:00, мы в KTS проведем открытый вебинар на тему:
«От кода до продакшена: деплоим приложение в Kubernetes с нуля»
Приходите посмотреть и послушать:
Комментарии (9)
DarkHost
29.11.2021 10:47Очень странное решение. Во-первых, как написали выше, hostNetwork прекрасно работает. Во-вторых, лично мое мнение, лучше всего для ingress в self-hosted кластере смотрелся бы hostNetwork+DaemonSet, если нужен внешний доступ. Таким образом мы еще и избавляемся от единой точки отказа в кластере.
igorcoding
29.11.2021 12:50+2Как написал выше, что зачастую кластера находятся во внутренней сети и просто так трафик не пропустить в кластер. Хочется еще отметить, что в вашем предложении с DaemonSet+hostnetwork есть проблема - а как в итоге клиенту выбрать на какой узел попасть? Помещать в dns все узлы кластера плохой путь - при добавлении/удалении узла из кластера нужно вносить изменения в dns, а они распространяются не моментально. Поэтому желательно иметь какую-то одну точку входа, желательно отказоустойчивую.
С подходом внешнего ингресс контроллера можно как избежать лишних проксирований, так и запустить несколько балансировщиков в HA режиме, например с помощью keepalived.
emashev
29.11.2021 11:08MetalLB разве не тоже самое делает?
igorcoding
29.11.2021 12:55+2Не совсем, metallb лишь выдаст доступный ip-адрес из пула, узел, которого должен привлекать трафик в кластер. Уже логика на этом узле должна как-то запроксировать трафик к нужным подам.
vainkop
30.11.2021 00:56Автор, наверное, отлично поупражнялся, но ставить такое кому-то точно не стоит, да и себе зачем, если существует как минимум несколько описанных выше в комментариях более "нативных" для куба решений, которыми гораздо удобнее управлять и они более гибкие.
identw
Вы же можете публиковать pod'ы ingress controller'а с помощью опции hostNetwork: true, получать трафик напрямую без лишних проксирований. Нет никакой необходимости иметь внешний балансировщик или вытаскивать ingress controller из кластера.
igorcoding
Все это в целом может работать, но начинаются проблемы, если узлы кластера все в приватной сети и нужно как-то извне пробросить доступ к нему. Необходим какой-либо edge узел, способный принять трафик, но дальше он должен все же прийти на узел кластера (желательно на любой) и попасть на ingress-контроллер. Получаем двойное проксирование, чего можно избежать например с подобным решением как в статье
identw
Делаете такие узлы воркерами кластера. Шедулите туда pod'ы ingress-controller'а с hostNentwork: true, и никаких двойных проксирований не будет. pod'у будет досупна как сеть внутри кластера, так и сеть хоста (то есть сеть edge узла).
Эта схема ничем принципиально не отличается от того что вы сделали. Только ingress-controller будет у вас в кластере, и деплоить вы его будете с помощью абстракций k8s. Любой pod k8s может иметь доступ в сеть хоста. На мой взгляд нет никаких причин выносить ingress-controller из куба. Вы сделали тоже самое решение что и с hostNetwork: true, только деплой реализовали сами (вместо того чтобы деплоить с помощью k8s).