Multi-layer DDoS protection for Minecraft servers. - rampart-core: Edge node with XDP/eBPF + Rust L7 filtering - rampart-manager: REST API with JWT auth, Redis sync - rampart-cli: CLI tool for operators - velocity-plugin: Domain check, HMAC verify, server registry, load balancer - paper-plugin: Auto-registration, heartbeat, HMAC verify - dashboard: React + Vite web UI for management
183 lines
5.4 KiB
Markdown
183 lines
5.4 KiB
Markdown
# HAProxy и свой Rust Load Balancer
|
||
|
||
> HAProxy - хорошее начало для v0.1-v0.2.
|
||
> Свой Rust LB - цель для v0.4 (убирает SPOF, добавляет MC-aware health check).
|
||
|
||
---
|
||
|
||
## Проблема с HAProxy
|
||
|
||
```
|
||
HAProxy как единственная точка входа = Single Point of Failure
|
||
|
||
Если HAProxy упал:
|
||
Все 20 Velocity нод недоступны
|
||
Все игроки дисконнектятся
|
||
Нет автоматического failover
|
||
|
||
Решение:
|
||
v0.1-v0.2: HAProxy + keepalived (VRRP failover)
|
||
v0.4+: Собственный Rust LB (несколько инстансов + SO_REUSEPORT)
|
||
```
|
||
|
||
---
|
||
|
||
## HAProxy конфиг (v0.1)
|
||
|
||
```
|
||
# /etc/haproxy/haproxy.cfg
|
||
|
||
global
|
||
maxconn 100000
|
||
log /dev/log local0
|
||
stats socket /run/haproxy/admin.sock mode 660 level admin
|
||
|
||
defaults
|
||
mode tcp
|
||
timeout connect 3s
|
||
timeout client 30s
|
||
timeout server 30s
|
||
option tcplog
|
||
|
||
# ── Входящие игроки (от edge нод) ──
|
||
frontend minecraft_in
|
||
bind *:25565
|
||
mode tcp
|
||
|
||
# Принимаем только от наших edge нод
|
||
acl is_edge_ip src 10.0.100.0/24
|
||
tcp-request connection reject if !is_edge_ip
|
||
|
||
default_backend velocity_pool
|
||
|
||
# ── Velocity кластер ──
|
||
backend velocity_pool
|
||
mode tcp
|
||
balance leastconn # наименьшее число активных соединений
|
||
option tcp-check # проверяем что порт открыт
|
||
|
||
# check inter 3s - проверяем каждые 3 сек
|
||
# rise 2 - нужно 2 успеха чтобы считать живым
|
||
# fall 3 - 3 неудачи → выводим из ротации
|
||
server vel1 10.0.0.2:25565 check inter 3s rise 2 fall 3
|
||
server vel2 10.0.0.3:25565 check inter 3s rise 2 fall 3
|
||
server vel3 10.0.0.4:25565 check inter 3s rise 2 fall 3
|
||
# ... до vel20
|
||
|
||
# ── Stats страница (для Prometheus) ──
|
||
frontend stats
|
||
bind 10.0.0.1:8404
|
||
stats enable
|
||
stats uri /stats
|
||
stats refresh 10s
|
||
stats auth admin:${HAPROXY_STATS_PASS}
|
||
```
|
||
|
||
## HAProxy + keepalived (устраняет SPOF)
|
||
|
||
```
|
||
# Два HAProxy сервера, один активный (MASTER), второй резервный (BACKUP)
|
||
# Виртуальный IP переключается автоматически при падении MASTER
|
||
|
||
# /etc/keepalived/keepalived.conf (на MASTER)
|
||
vrrp_instance VI_1 {
|
||
state MASTER
|
||
interface eth0
|
||
virtual_router_id 51
|
||
priority 100 # MASTER имеет высший приоритет
|
||
|
||
authentication {
|
||
auth_type PASS
|
||
auth_pass rampart
|
||
}
|
||
|
||
virtual_ipaddress {
|
||
10.0.0.1/24 # виртуальный IP, на него смотрят edge ноды
|
||
}
|
||
|
||
notify_master "/etc/keepalived/notify.sh MASTER"
|
||
notify_backup "/etc/keepalived/notify.sh BACKUP"
|
||
}
|
||
|
||
# На BACKUP: state BACKUP, priority 90
|
||
```
|
||
|
||
---
|
||
|
||
## Свой Rust Load Balancer (v0.4)
|
||
|
||
### Преимущества
|
||
|
||
```
|
||
✓ Нет SPOF - несколько инстансов на разных машинах
|
||
✓ SO_REUSEPORT - линейный scale по CPU
|
||
✓ MC-aware health check (не просто TCP, а настоящий MC ping)
|
||
✓ Hot reload без рестарта (добавить/убрать Velocity)
|
||
✓ Нативная интеграция с Redis/NATS
|
||
✓ Метрики в формате Prometheus из коробки
|
||
```
|
||
|
||
### MC-aware Health Check
|
||
|
||
```rust
|
||
// Не просто TCP connect, а настоящий MC Status ping
|
||
async fn check_velocity_health(addr: &SocketAddr) -> bool {
|
||
let mut stream = match tokio::time::timeout(
|
||
Duration::from_secs(2),
|
||
TcpStream::connect(addr)
|
||
).await {
|
||
Ok(Ok(s)) => s,
|
||
_ => return false,
|
||
};
|
||
|
||
// Шлём MC Handshake (next_state=1, status ping)
|
||
let handshake = build_mc_handshake("health.check", addr.port(), 1);
|
||
if stream.write_all(&handshake).await.is_err() { return false; }
|
||
|
||
// Шлём Status Request (0x00)
|
||
let status_req = vec![0x01, 0x00];
|
||
if stream.write_all(&status_req).await.is_err() { return false; }
|
||
|
||
// Ждём Status Response
|
||
let mut buf = vec![0u8; 1024];
|
||
match tokio::time::timeout(Duration::from_secs(1), stream.read(&mut buf)).await {
|
||
Ok(Ok(n)) if n > 5 => true,
|
||
_ => false,
|
||
}
|
||
}
|
||
```
|
||
|
||
### Hot Reload
|
||
|
||
```rust
|
||
pub struct RustLoadBalancer {
|
||
backends: Arc<ArcSwap<Vec<Backend>>>, // ArcSwap - lock-free swap
|
||
}
|
||
|
||
impl RustLoadBalancer {
|
||
// Атомарная замена списка бэкендов - без блокировки
|
||
pub async fn reload(&self, new_backends: Vec<Backend>) {
|
||
self.backends.store(Arc::new(new_backends));
|
||
// Текущие соединения не прерываются
|
||
// Новые соединения идут по новому списку
|
||
}
|
||
}
|
||
|
||
// ArcSwap из crates.io: arc-swap = "1"
|
||
```
|
||
|
||
### Несколько инстансов без SPOF
|
||
|
||
```
|
||
# На трёх разных машинах запускаем Rust LB
|
||
# Edge ноды видят все три через DNS round-robin или BGP anycast
|
||
|
||
DNS:
|
||
lb.internal A → 10.0.0.10 (LB1)
|
||
lb.internal A → 10.0.0.11 (LB2)
|
||
lb.internal A → 10.0.0.12 (LB3)
|
||
|
||
Если LB1 упал:
|
||
DNS TTL = 10 сек → edge ноды переключаются на LB2/LB3
|
||
Без keepalived, без VRRP, без единой точки отказа
|
||
```
|