суббота, 31 июля 2021 г.

Балансировка исходящего трафика

Балансировка NAT-серверов.

Расскажу, как мы раньше разделяли нагрузку между несколькими NAT-серверами.
Проблемы с исчерпанием ipv4-адресов у нас тогда не стояло, но мы решили заблаговременно настроить NAT для абонентов. Недолго думая, запустили NAT-сервер на x86 сервере, Centos 6.
Железо обычное, Xeon E3-12xx, v2 или что там было в то время. Памяти гигабайт 8. Двухпортовая десятигигабитная сетевая карта.


Сервер воткнули в бордер двумя концами и принудительной маршрутизацией (PBR) зарулили трафик тестовых серых абонентов.

Сам nat описывать нет особого смысла, там было всё штатно, NAT силами conntrack/netfilter, настроенный через iptables. Какой-то обычный тюнинг sysctl. Да драйвер для сетевой карты взяли с сайта производителя, благо он собирался в RPM пакет одной командой (производитель позаботился).

Самое интересное настало, когда мы решили запускать туда настоящих абонентов, а значит нужен, как минимум, еще один сервер (для отказоустойчивости).

Воткнули второй сервер во второй бордер на другом узле, PBR заменили на динамическую маршрутизацию: сверху бордер "вещает" дефолтный маршрут в NAT, а тот его передаёт абонентам (на самом деле в тот же бордер, но уже в другой VRF, в котором обитают "серые" абоненты).
Снизу VRF с абонентами вещает в NAT серые сети абонентов, NAT'ы же анонсируют наверх в бордер свои публичные сети.

Отказоустойчивость какую-никакую, но обеспечили. А вот с балансировкой нагрузки тяжелее. Маршрутизацией её добиться довольно сложно, это ручные игры с метриками и "нарезкой" абонентов на микро VRF (чтобы анонсировать разным порциям разный дефолт -- через первый нат или через второй).

А тут уже и третий сервер пора ставить. Воткнули в первый бордер. Теперь бордер "видит" два маршрута по умолчанию. Но тут нам не удалось настроить корректную работу балансировки. В чем была беда: многие сайты держат web-интерфейс на одном сервере, а контент на другом. И эти сайты считали, что контент нужно получать с тем же SRC IP, с которым авторизовался на web-интерфейсе.

И роутер был слишком умным, чтобы выбирать маршрут основываясь только лишь на адресе абонента. Этот роутер использовал в вычислении хэша для балансировки и адрес назначения, и отключить это нам не удалось. В результате абоненты начали страдать от сайтов, потому что авторизовались на сайте с использованием одного SRC IP (через первый нат), а за контентом пришли со второго IP (через другой нат).

В тот момент у нас не было возможности поставить другой (промежуточный) роутер, который бы выбирал один из восьми (да, мы довели количество натов до восьми штук на каждом бордере) маршрут, основываясь лишь на SRC IP абонента.

Поэтому мы сделали всё сами, с помощью Linux. Мы, как и любой провайдер, знали профиль нашего трафика и решили, что на узле достаточно одного 10G балансировщика исходящего трафика. Т.е. разница между входящим и исходящим у нас была более, чем в восемь раз.

Значит маршрутизацией заманиваем исходящий от абонента трафик на такой же сервер, как и остальные NAT'ы (всё воткнуто в бордер, через промежуточный 10G-коммутатор, конечно же), на нём происходит магия, трафик от одного абонента всегда попадает на один и тот же NAT, а с NAT'а возвращается к абонентам минуя балансировщик (ведь никто же не заставляет маршрутизацию быть симметричной).

Что за магия на балансировщике? Тот же Centos и те же аппаратные характеристики.

Восемь таблиц маршрутизации (ip route table), каждая со своим статическим маршрутом по умолчанию, смотрящим в нужный нат.

Восемь правил маршрутизации (ip rule), заворачивающих трафик с определенными метками в соответствующие таблицы.

Восемь пустых сетов ipset (NAT1, NAT2, .. NAT8).

Если ip-адрес абонента уже есть в каком-то сете, то такой трафик отправляется в соответствующую сету цепочку NATx таблицы MANGLE:
-A PREROUTING -i eth3 -m set --match-set NAT1 src -j NAT1
-A PREROUTING -i eth3 -m set --match-set NAT2 src -j NAT2
-A PREROUTING -i eth3 -m set --match-set NAT3 src -j NAT3
-A PREROUTING -i eth3 -m set --match-set NAT4 src -j NAT4
-A PREROUTING -i eth3 -m set --match-set NAT5 src -j NAT5
-A PREROUTING -i eth3 -m set --match-set NAT6 src -j NAT6
-A PREROUTING -i eth3 -m set --match-set NAT7 src -j NAT7
-A PREROUTING -i eth3 -m set --match-set NAT8 src -j NAT8
Если же ip-адреса еще нет, то пакет дойдёт до этих правил и с равной вероятностью (1/8=0.125; 1/7=0.143; 1/6=0.167 и т.д.) окажется в одной из цепочек:
-A PREROUTING -i eth3 -m statistic --mode random --probability 0.125 -j NAT1
-A PREROUTING -i eth3 -m statistic --mode random --probability 0.143 -j NAT2
-A PREROUTING -i eth3 -m statistic --mode random --probability 0.167 -j NAT3
-A PREROUTING -i eth3 -m statistic --mode random --probability 0.200 -j NAT4
-A PREROUTING -i eth3 -m statistic --mode random --probability 0.250 -j NAT5
-A PREROUTING -i eth3 -m statistic --mode random --probability 0.333 -j NAT6
-A PREROUTING -i eth3 -m statistic --mode random --probability 0.500 -j NAT7
-A PREROUTING -i eth3 -j NAT8
А уже в этих цепочках пакет маркируется нужной меткой,..
-A NAT1 -j SET --add-set NAT1 src --exist
-A NAT1 -j MARK --set-xmark 0xa/0xffffffff
-A NAT1 -j ACCEPT
-A NAT2 -j SET --add-set NAT2 src --exist
-A NAT2 -j MARK --set-xmark 0x14/0xffffffff
-A NAT2 -j ACCEPT
-A NAT3 -j SET --add-set NAT3 src --exist
-A NAT3 -j MARK --set-xmark 0x1e/0xffffffff
-A NAT3 -j ACCEPT
-A NAT4 -j SET --add-set NAT4 src --exist
-A NAT4 -j MARK --set-xmark 0x28/0xffffffff
-A NAT4 -j ACCEPT
...
...и попадает, благодаря ip rule в нужную таблицу маршрутизации, а далее летит по нужному маршруту на нужный NAT-сервер.
Суть понятна? Если балансировщик еще не встречал этот абонентский IP, то засунет его в одну из восьми цепочек ipset благодаря mode random. Если же адрес уже нам известен (есть в каком-либо ipset), то такой трафик всегда будет лететь в нужный NAT.

Еще был написан небольшой демон, который простым пингом проверял наличие NAT-серверов за портом и выводил его из работы, динамически удаляя/добавляя правила в iptables с корректным пересчетом вероятности.

понедельник, 28 июня 2021 г.

Grafana с аутентификацией через Radius

Встала задача поднять Grafana с аутентификацией через Radius.

Сразу же была найдена статья.

Принцип работы ясен. В grafana есть специальный механизм auth.proxy, который может получать данные в виде переменных окружения. В том примере используется веб-сервер Apache, мы же воспользуемся более привычным Nginx.

Ставим grafana, nginx и libpam-radius-auth штатным для дистрибутива способом.

Настраиваем pam:

/etc/pam_radius_auth.conf
radius-server-ip:port   secret
Делаем этот файл доступным для чтения пользователю, под которым работает nginx (у меня это www-data):
chown www-data:www-data /etc/pam_radius_auth.conf
/etc/pam.d/nginx
auth    required        pam_radius_auth.so debug client_id=grafana conf=/etc/pam_radius_auth.conf
account required        pam_permit.so
session required        pam_permit.so

Настраиваем nginx:
server {
       listen 443 ssl;

       ssl_certificate /etc/ssl/cert.pem;
       ssl_certificate_key /etc/ssl/cert.key;
       ssl_protocols TLSv1.2;
       ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256;
       ssl_prefer_server_ciphers on;
       add_header Strict-Transport-Security 'max-age=604800';
       server_name grafana.example.com;

       access_log /var/log/nginx/gr.access.log;
       error_log /var/log/nginx/gr.error.log;
       location / {
           auth_pam "Auth";
           auth_pam_service_name "nginx";
           proxy_pass http://localhost:3000/;
           proxy_set_header X-WEBAUTH-USER $remote_user;
           proxy_set_header Authorization "";
       }
}

Настраиваем grafana
/etc/grafana/grafana.ini:
....
[auth.proxy]
enabled = true
....

Перезапускаем grafana-server и nginx и проверяем.