ADR-0008: High Availability Kamailio su Hetzner Cloud
Status
Accepted — applicabile da Fase 2 (Opzione A, 12 VM) in poi. Fase 1 (single-node MVP) non implementa HA.
Context
Hetzner Cloud è il provider scelto per Akira (cost-effective, datacenter EU, API matura). Tuttavia presenta vincoli infrastrutturali che impediscono l'approccio HA "classico" per Kamailio:
- No multicast L2 sulla private network → keepalived/VRRP non funziona (VRRP usa multicast 224.0.0.18).
- Floating IP disponibile, ma il failover via Hetzner Cloud API ha latenza 10-30s (DNS API + ARP propagation).
- No load balancer L4 UDP managed (HCLoadBalancer è L4 TCP / L7 HTTP, non UDP — fondamentale per SIP).
Allo stesso tempo Kamailio ha esigenze HA forti:
- Stateful: location table, dispatcher state, rate limiter buckets — non si possono perdere ad ogni failover.
- SIP signaling: i peer carrier devono poter raggiungere il servizio anche durante manutenzione di un nodo.
- Symmetric routing: alcuni peer NAT-aware vincolano l'IP sorgente e destinazione.
Approcci comunemente usati on-premise (VRRP + carp + Pacemaker) non sono direttamente trasponibili su Hetzner Cloud.
Decision
Adottare un approccio a due vie, scelto per peer al momento dell'onboarding:
Path A — Peer SRV-aware (preferito)
Per peer carrier/customer che supportano DNS SRV multi-record con priority/weight:
- Pubblicare 2+ record SRV per il servizio SIP Akira, con priority uguale e weight bilanciato.
_sip._udp.akira.example. IN SRV 10 50 5060 sip-01.akira.example._sip._udp.akira.example. IN SRV 10 50 5060 sip-02.akira.example.
- Il peer fa round-robin / load balance lato suo.
- Failover gestito dal peer (TTL DNS basso = 60s).
Path B — Peer SRV-blind (fallback)
Per peer che inviano traffico a un singolo IP/hostname e non parlano SRV multi-record:
- Kamailio dispatcher module davanti a sip-01 e sip-02, su un nodo
sip-edge-01/sip-edge-02con Floating IP Hetzner. - Shared state via Redis: dispatcher state (active/probing/inactive) sincronizzato su Redis chiave
dispatcher:state:*. - Floating IP + Hetzner API trigger come last-resort failover (latenza 10-30s accettabile per disaster, NON per load balance).
Stato condiviso (entrambi i path)
Stato Kamailio condiviso su Redis cluster:
location:*(user location, registrations).dispatcher:state:*(peer health).htable:rate_limit:*(rate limiter buckets).htable:auth_nonce:*(digest nonce cache).
Redis in produzione deploya con Redis Sentinel (3 sentinel + 1 master + 2 replica) per evitare SPOF.
Consequences
Positive
- Zero-downtime per peer SRV-aware (la maggioranza dei tier-1 carrier).
- Failover automatico ≤ 5s per Path A (TTL DNS + retry SIP).
- Stato preservato durante manutenzione singolo nodo (Redis condiviso).
- Compatibilità peer legacy garantita via Path B.
Negative
- 2 paths di setup da documentare e mantenere. Onboarding peer richiede check "supporta SRV multi-record?".
- Redis diventa critico: se Redis cluster down → Kamailio perde state. Mitigato da Sentinel, ma è un componente in più da monitorare.
- Floating IP Hetzner: failover 10-30s in scenario disaster Path B — accettabile per recovery, non per blue/green.
- DNS provider requirement: deve supportare SRV multi-record con weight (Cloudflare, Route53, Hetzner DNS OK; alcuni provider economici no).
Neutral
- Configurazione Kamailio più complessa (dispatcher + redis_cluster module + htable persistence).
- Costo: +1 VM
sip-edgeper Path B (se peer SRV-blind sono maggioranza). Stimato +€10-20/mese per nodo.
Implementation hints
Ansible role layout
roles/kamailio/
templates/
kamailio.cfg.j2
dispatcher.list.j2 # populate da inventory
tls.cfg.j2
files/
modules/ # custom params
tasks/
main.yml
redis_cluster.yml # config htable + location backend
dispatcher_sync.yml # subscribe Redis pub/sub kamailio:dispatcher:*
Kamailio config snippet
loadmodule "dispatcher.so"+modparam("dispatcher","db_url","sqlite or file").loadmodule "htable.so"conmodparam("htable","htable","ratelimit=>size=10;dbtable=...").loadmodule "ndb_redis.so"per integrazione Redis cluster custom (dispatcher state replication).
DNS setup
- Provider raccomandato: Cloudflare o Route53 (SRV record supportati nativamente, TTL custom).
- TTL SRV record: 60s (failover rapido).
- Health check esterno (uptimerobot, Prometheus blackbox-exporter) che rimuove SRV down via API in caso di outage prolungato.
Hetzner Cloud Floating IP (Path B)
- Script
infra/scripts/hetzner-floating-ip-failover.shinvocato da systemd watcher. - Token API in
/etc/akira/hetzner.token, mode 0600. - Failover trigger: 3 consecutive SIP OPTIONS failed (Prometheus alertmanager → webhook).
Redis Sentinel
- 3 sentinel su
mgmt-01,sip-01,sip-02(quorum 2). min-replicas-to-write 1,min-replicas-max-lag 10.
Alternatives considered
- Pure Floating IP + Hetzner API trigger: scartato come strategia primaria — 10-30s failover inaccettabile per load balance. Tenuto come last-resort.
- HCLoadBalancer L4: scartato — non supporta UDP, SIP unusable.
- DNS round-robin senza SRV (multi A record): scartato — i client SIP non hanno failover standardizzato su A record multipli, comportamento imprevedibile.
- OpenSIPS al posto di Kamailio: scartato — cambio di tecnologia non giustificato dal solo problema HA; Kamailio + dispatcher copre il caso.
- Migrare a provider con multicast (OVH, Equinix Metal): scartato — costo significativamente maggiore, lock-in non desiderato per MVP.
- Kubernetes su Hetzner (k3s + MetalLB BGP): scartato per Fase 2 — operational complexity troppo alta; rivalutabile post-MVP.