Passa al contenuto principale

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-02 con 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-edge per 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" con modparam("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.sh invocato 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.