🚀 Публичный API для голоса. Дополнительные ноды и настройка NGINX Upstream load balancer (Балансировка нагрузки)
Задача
В одной из своих статей я описывал способ создания и настройки публичной API ноды на примере своей собственной ноды -
wss://api.golos.cf
Сейчас, спустя пару месяцев, ее стали использовать многие разработчики и нагрузки стали пиковые, что часто служит причиной вывода ноды из строя.
Пришло время масштабировать Public API путем объединения нескольких нод в одной точке доступа к API
Напомню, что в моих нодах включены все API плагины, что делает ноду требовательной к моим ресурсам, но зато позволяет пользователям использовать весь набор вызовов.
Решение
Если у вас уже есть публичная нода и доступ к Public API настроен через NGINX как описано в моей статье для масштабирования вам понадобится добавить всего несколько строк и установить дополнительные ноды.
(Если вы просто добавили внешний IP сервера в config.ini - вам нужно будет сменить его на localhost и создать проксирование на NGINX для настройки load balancer)
Load balancer будет состоять из 4 серверов, на каждый будет установлена полная нода голоса (со всеми плагинами) и nginx.
На сервере 1 будет открыт доступ к ноде для всех желающих по WSS на порте 443 (ssl/tls) - как это сделать описано по ссылкам выше.
В качестве живого примера wss://api.golos.cf
На серверах 2,3,4 доступ к нодам будет заблокирован для всех, кроме ip адреса сервера 1. Ноды будут иметь условный порт :777
Когда кто-то будет подключаться к wss://api.golos.cf
nginx на сервере 1 будет распределять нагрузку между серверами 2,3,4 перенаправляя запрос на наименее нагруженный сервер. В случае недоступности серверов 2,3,4 - запрос будет обрабатываться нодой на сервере 1.
Настройка - подготовка сервера
Если у вас сервер на обычных SSD вводим команду
df -h
Ищем shm и смотрим на его размер. Если менее 32Gb - увеличим его или добавим shm если такого не оказалось. Размер shm не должен превышать объем ОЗУ, хотя и может.
nano /etc/fstab
- открываем конфиг в редакторе и добавляем строку
none /dev/shm tmpfs defaults,size=32G 0 0
Проверяем
mount -o remount /dev/shm
df -h
Далее в config.ini
golosd добавляем строки
shared-file-dir = /dev/shm
shared-file-size = 32G
Если ваш сервер на быстрых NVMe SSD путь в shared-file-dir
можно указать на произвольную директорию.
Устанавливаем ноды на произвольное количество серверов по моей инструкции способ создания и настройки публичной API ноды. Или по другим инструкциям, например через docker, в этом случае убедитесь, что установлен nginx.
Nginx.conf
сервер 1
Подключим файл с настройками в nginx
Открываем в редакторе конфиг
nano /etc/nginx/nginx.conf
Ищем часть с include
и добавляем перед ними
limit_req_zone $binary_remote_addr zone=ws:10m rate=1r/s
include /путь/к/удобной/папке/Nginx.conf;
Открываем файл конфига Nginx.conf
и настраиваем поведение load balancer
Используем директиву nginx - upstream
docs
upstream websockets {
least_conn;
server ws1.chain.cf:777 weight=5 max_fails=4 fail_timeout=30;
server ws2.chain.cf:777 weight=2 max_fails=3 fail_timeout=10s;
server ws3.chain.cf:777;
server 127.0.0.1:9090 backup;
}
Строки server ws*.chain.cf:777
- это адреса дополнительных нод для балансера.
least_conn
Задаёт для группы метод балансировки нагрузки, при котором запрос передаётся серверу с наименьшим числом активных соединений, с учётом весов серверов. Если подходит сразу несколько серверов, они выбираются циклически (в режиме round-robin) с учётом их весов.
backup
Этот параметр указывает, что к указанному серверу можно обращаться только в случае, если все остальные откажут. Я указал в качестве backup сервер 1 на котором стоит нода по той причине, что именно он распределяет нагрузку и если он будет чрезмерно перегружен, это отразится на всех остальных.
weight=*
Параметр вес - это приоритет сервера, может быть полезно если в вашем кластере разные по мощности сервера.
Например в конфиге выше есть сервер с weight=5, weight=2 , без weight и backup
Это значит, что если мы получим 8 запросов, то 5 уйдут на ноду с весом 5, 2 на ноду с весом 2 и один на ноду без веса. При этом, если ноды выше не доступны, запрос будет направлен на backup ноду.
max_fails=4 fail_timeout=30
Это параметр создает условие, при котором сервер становится недоступным в течении 30 секунд если случилось 4 неудачных подключения подряд.
После описания upstream кластера с балансировкой, нужно перенаправить все запросы отправляемые к api.golos.cf
на него. Это достигается в продолжении конфига:
server {
listen 443 ssl;
ssl_certificate /tls/fullchain.pem;
ssl_certificate_key /tls/privkey.pem;
include /tls/options-ssl-nginx.conf;
server_name api.golos.cf;
root /home/html/;
keepalive_timeout 65;
keepalive_requests 100000;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
location ~ / {
limit_req zone=ws burst=5;
access_log off;
proxy_pass http://websockets;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_next_upstream error timeout invalid_header http_500;
proxy_connect_timeout 2;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Первые несколько строк указывают используемый порт, 443 в данном случае позволяет подключаться к домену напрямую по зашифрованному wss. wss://api.golos.cf
Если я укажу 80 порт, то доступ к нодам будет ws://api.golos.cf
(ws - без шифрования)
Если я укажу произвольный порт 12345, то доступ к нодам будет таким ws://api.golos.cf:12345
(Если используем шифрование, нужно указать пути к сертификатам.)
server_name
- домен или ip сервера с нодой
location ~ / { ... }
- указываем, что ноды будут доступны в корне домена, а не на определенной "странице".
limit_req zone=ws burst=5
- важный параметр:
Задаёт зону разделяемой памяти (zone) и максимальный размер всплеска запросов (burst). Если скорость поступления запросов превышает описанную в зоне, то их обработка задерживается так, чтобы запросы обрабатывались с заданной скоростью. Избыточные запросы задерживаются до тех пор, пока их число не превысит максимальный размер всплеска.
proxy_pass http://websockets;
указываем куда перенаправлять запросы к api.golos.cf. websockets в данном случае указывает на upstream websockets {...}
а начале конфига, а значит все запросы к api.golos.cf балансируются между серверами указанными в upstream websockets {...}
Nginx.conf
сервера 2,3,4 ...
С остальными серверами настройка намного проще, в upstream мы укажем только локальный адрес ноды, который указан в config.ini у golosd
upstream websockets {
server 127.0.0.1:9090;
}
В качестве порта укажем произвольный 777
server {
listen 777;
server_name ws1(ИЛИ: 2,3,4).chain.cf;
keepalive_timeout 65;
keepalive_requests 100000;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
А в директории location заблокируем все подключения к ноде кроме подключений с ip адреса сервера 1
location ~ / {
allow IP.АДРЕС.СЕРВЕРА.1;
deny all;
access_log off;
proxy_pass http://websockets;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_next_upstream error timeout invalid_header http_500;
proxy_connect_timeout 2;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
После этих настроек можно перезапустить nginx командой
systemctl reload nginx
Описанная настройка работает на моей публичной ноде.
Дополнения
Если в конфиге указан dev/shm - в случае аварийной перезагрузки сервера вам придется делать replay блокчейна (это несколько часов для full ноды)
Если указана физическая директория - восстановить ноду можно будет не делая replay, однако если индекс будет поврежден, replay займет гораздо больше времени, так как диски медленнее RAM.
На серверах 2,3,4 - вместо nginx можно использовать встроенные iptables чтобы заблокировать доступ к нодам с чужих ip, а в конфиге ноды указать rpc-endpoint адрес вашего сервера. Но это гораздо менее гибкий способ по сравнениею с nginx проксированием.
Для поднятия кластера можно использовать docker, а в качестве серверной инфраструктуры использовать AWS, Google Cloud, Azure и т.п. - это может снизить стоимость если ваши ноды не используются 24\7. В случае постоянное нагрузки - такие облачные решения могут оказаться гораздо дороже обычных VPS/VDS. (О своем опыте выбора сервера я напишу в будущем)
Для использования нод единолично - кластер использовать не обязательно, поскольку можно оптимизировать свои скрипты.
Можно значительно сэкономить ресурсы отключив ненужные плагины в нодах, однако в силу особенностей дизайна API унаследованного со steemit - некоторые плагины имеют неочевидные кросс-зависимости, например для получения virtual ops из блоков, необходимо активировать тяжелый плагин истории аккаунта, чтобы получать корректную репутацию - необходимо включать follow плагин.
Чтобы не ограничивать функционал я включаю все плагины на паблик нодах.
Помните, что если вы включаете дополнительные плагины на работающей ноде - скорее всего вам приедтся запускать replay/resync для того, чтобы плагин правильно работал с данными.
Описанное выше вчера было применено к ноде wss://api.golos.cf
в связи с переконфигруацией нода была недоступна некоторое время. Сейчас работа восстановлена.