Files
MatchLiveTv/docs/infrastructure/STREAMING_AUTOSCALE.md
T
eminuxandCursor c3878cdc6d Aggiunge registry nodi stream e provisioning Hetzner/lab per lo scale-out.
Prepara l'architettura multi-nodo (MediaMTX+ffmpeg) con assignment URL per sessione, admin di provision/drain e provider Cloud/DNS astratti verso mltv-stream.net.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-09 19:52:47 +02:00

14 KiB
Raw Blame History

Autoscale streaming — Proxmox attuale + Hetzner Cloud

Stato: design approvato (decisioni chiuse) — pronto per implementazione
Branch: feature/streaming-autoscale-hetzner
Ultimo aggiornamento: 2026-08-09 (dominio ingest: mltv-stream.net)

Sostituisce / estende: il piano overflow-only HETZNER_CLOUD_RELAY_OVERFLOW.md. Questo documento copre MediaMTX + ffmpeg, warm spare, DNS, storage, disco e lab sul Proxmox di produzione.

Documenti correlati: ARCHITECTURE.md, LIVE_STREAMING.md, REPLAY_MODULE.md, OPS_MONITORING.md.


1. Obiettivo

Scalare molte dirette contemporanee (sito e/o YouTube) senza saturare un unico box:

  • Control plane (ora): Proxmox già in produzione (192.168.1.146 / /opt/matchlivetv) — sito, API, DB, Redis, Garage, MediaMTX/ffmpeg “home”.
  • Data plane elastico: Hetzner Cloud — nodi MediaMTX + ffmpeg con warm spare (+1).
  • DNS ingest: secondo dominio su Hetzner DNS (API).
  • Lab: stesso Proxmox, con ProxmoxLabProvider, prima di affidarsi al Cloud in produzione.
  • Futuro (non bloccante): migrazione control plane su Hetzner Auction + Proxmox (vSwitch nativo, hardware dedicato).

2. Decisioni chiuse

# Decisione Scelta
D1 Host primario ora Proxmox già usato in produzione (non attendere Auction)
D1b Host primario futuro Hetzner Server Auction + Proxmox (quando si migra)
D2 Overflow / warm spare Hetzner Cloud (attivato per questo progetto)
D3 Unità di scala Nodo stream = MediaMTX + worker youtube_relay (ffmpeg remux copy)
D4 Warm pool Almeno 1 spare ready quando il carico si avvicina al cap
D5 DNS ingest Dominio mltv-stream.net (registrar Aruba, NS → Hetzner DNS). matchlivetv.it resta su Aruba intatto
D6 Aruba Nessuna delega NS; niente Floating IP prenotate
D7a Rete ora (casa/Proxmox ↔ Cloud) WireGuard (o Tailscale) privato: Redis, DB, API MediaMTX home. Non esporre Redis/Postgres su WAN
D7b Rete futuro (Auction ↔ Cloud) vSwitch Robot + Cloud Network (stessa location)
D8 Object storage Garage resta. B2 = migrazione pronta quando misurato
D9 Staging Segmenti su disco VM stream-* in live; upload Garage a fine sessione
D10 Prodotto Consigliare YouTube per alleggerire storage/egress replay
D11 Disco Separazione volumi + monitoraggio attivo obbligatorio (§6)
D12 Lab prima del Cloud prod ProxmoxLabProvider + DNS lab sullo stesso Proxmox; poi HetznerCloudProvider

3. Topologia — fase attuale (Proxmox prod + Cloud)

Internet
   │  HTTPS :443 · RTMP :1935
   ▼
┌──────────────────────────────────────────────────────────┐
│  Proxmox produzione (attuale)                            │
│  eminux@192.168.1.146 · /opt/matchlivetv                 │
│                                                          │
│  edge · rails · sidekiq-core · postgres · redis          │
│  garage · stream-home (MediaMTX + youtube_relay)         │
│  autoscaler (Rails/Sidekiq)                              │
│                                                          │
│  Lab (opz.): clone VM stream-* via ProxmoxLabProvider    │
└────────────────────────┬─────────────────────────────────┘
                         │  WireGuard / tunnel privato
                         ▼
┌──────────────────────────────────────────────────────────┐
│  Hetzner Cloud — pool stream nodes                       │
│  stream-01 … stream-N   MediaMTX + ffmpeg                │
│  + 1 spare ready (warm)                                  │
└────────────────────────┬─────────────────────────────────┘
                         │
              Hetzner DNS (secondo dominio)
              ingest-XX.<dominio> → IP pubblico nodo

Futuro Auction: stesso schema, tunnel sostituito da vSwitch; control plane migrato sul bare metal Hetzner.

Principio: Rails solo controllo. Live sui MediaMTX del nodo assegnato. Replay su Garage (poi eventualmente B2).


4. DNS (secondo dominio)

RTMP non tollera round-robin su un unico hostname.

  1. Dominio mltv-stream.net registrato su Aruba; zona DNS su Hetzner Console (progetto matchlivetv-stream).
  2. NS Aruba del dominio → hydrogen / oxygen / helium Hetzner (delegazione OK).
  3. Autoscaler a VM ready → upsert A ingest-03.mltv-stream.net → health-check → nodo ready.
  4. API create/start sessione → URL del nodo assegnato:
rtmp://ingest-03.mltv-stream.net:1935/live/match_<uuid>
https://ingest-03.mltv-stream.net/hls/...   # o via edge matchlivetv.it che proxya il nodo
  1. Scale-in: drain → delete DNS → destroy VM.

Hostname pubblici dei nodi vivono sotto *.mltv-stream.net. In lab: LabDnsProvider (/etc/hosts, dnsmasq).


5. Autoscaler e warm spare

Ogni nodo: max_publishers / max_relays (es. 4).

Regola Comportamento
Scale-out free_slots sotto soglia soft → crea 1 nodo
Warm spare Se spare_ready < 1 vicino al carico → crea spare
Scale-in Idle da X min e non unica spare → destroy
Floor stream-home sul Proxmox sempre on

CloudProvider: HetznerCloudProvider (prod overflow) + ProxmoxLabProvider (test).
Code Sidekiq: coda youtube_relay isolata; sticky owner Redis.


6. Disco Proxmox — requisito critico

Non mettere OS, DB e video sullo stesso volume che può riempirsi al 100%.

Vale già sul Proxmox di produzione (rinforzare ora), e di nuovo alla migrazione Auction.

6.1 Layout volumi (target)

Volume / disco Contenuto Note
os Proxmox / sistema host Quasi statico
vm-system / root stack rails, edge, redis, … Snapshot
vm-data-db Postgres Separato; backup prioritari
vm-data-video Garage + staging MediaMTX Disco che può riempirsi
backup Backup / PBS Mai sullo stesso disco dei video

Regole: cap Garage/staging; se staging pieno → rifiutare nuove sessioni, non far cadere DB/API.

6.2 Monitoraggio attivo (obbligatorio)

Metrica Warn Critical
Uso % volume video ≥ 70% ≥ 85%
Spazio libero GB sotto soglia < X GB
Inode liberi bassi esaurimento
Crescita GB/giorno Garage anomalia
Purge/retention falliti immediato
Staging per nodo stream-* ≥ 70% ≥ 85% + blocco live su quel nodo

Checklist:

  • Alert ntfy disco testati
  • Vista ops “disco video”
  • Test periodico “80% pieno”
  • Restore di prova Postgres

7. Storage: Garage ora, B2 dopo

| Ora | Garage | | Dopo | Backblaze B2 quando metriche/€ lo giustificano |

Client S3-ready; staging locale invariato. Spinta prodotto YouTube riduce pressione replay.


8. Prodotto: YouTube consigliato

Suggerire YouTube in app/dashboard quando ha senso → meno storage/egress MatchLiveTV; middle-tier (slate + relay) resta il valore. Live solo-sito restano supportate.


9. Astrazioni codice

CloudProvider
  create_node / destroy_node / list_nodes / wait_until_running / public_ip
  → HetznerCloudProvider | ProxmoxLabProvider

DnsProvider
  upsert_a / delete_a
  → HetznerDnsProvider | LabDnsProvider

StreamNodeRegistry
  register / mark_ready / allocate_for_session / drain / release

Autoscaler
  reconcile!(metrics) → ensure spare / scale-in idle

Tag VM Cloud: matchlivetv, role=stream-node, env=prod|lab.


10. Lab sul Proxmox attuale

Prima (o in parallelo) al Cloud “vero”:

  1. Template VM stream-node su Proxmox.
  2. CLOUD_PROVIDER=proxmox_lab → clone/start/stop via API Proxmox.
  3. DNS lab (hosts/dnsmasq).
  4. Soft cap basso; N sessioni di test; verifica assignment, spare, scale-in, alert disco.
  5. Poi smoke su Hetzner Cloud (1 CX piccolo) + DNS reale.

Cosa il lab non replica al 100%: tempi boot Hetzner, vSwitch, RTMP 4G multi-nodo (smoke WAN dopo).


11. Fasi di implementazione

Fase Cosa Esito
L — Lab Proxmox LocalLab / ProxmoxLab, DNS lab, admin nodi implementata sul branch
0 — Multi-nodo ready Registry + URL RTMP/HLS in API (StreamNode, Streams::NodeRegistry) implementata sul branch
1 — Hetzner Cloud + DNS HetznerCloudProvider + HetznerDnsProvider, mltv-stream.net, cloud-init, WireGuard (§16) implementata sul branch (WG ops manuale)
2 — Routing relay Coda youtube_relay, sticky owner, cap Niente doppi ffmpeg
3 — Autoscaler Soglie + warm spare (+1) Automatico
4 — Hardening Drain, budget, runbook, kill-switch Picchi weekend
A — Auction (futuro) Migrazione control plane + vSwitch al posto di WireGuard Hardware dedicato Hetzner
B2 (opz.) Switch object storage Quando misurato

Fase 0 — dettagli implementati

  • Tabella stream_nodes + stream_sessions.stream_node_id
  • Streams::NodeRegistry.ensure_home_from_env! / allocate! (least-loaded)
  • Sessions::Create assegna il nodo e crea il path MediaMTX sul client del nodo
  • URL RTMP/HLS/API/internal per sessione derivati dal nodo (fallback ENV se nodo assente)
  • API JSON espone stream_node (slug)
  • Dominio ingest pubblico futuro: *.mltv-stream.net

Fase L — dettagli implementati

  • Streams::CloudProviders (local_lab, proxmox_lab, stub hetzner)
  • Streams::DnsProviders (lab su Redis, stub hetzner)
  • Streams::NodeProvisioner (provision / drain / decommission)
  • Admin Nodi stream (/admin/stream_nodes): lista, provision lab, drain, delete
  • Rake: streams:nodes:ensure_home, streams:nodes:provision_lab, streams:nodes:dns_lab_dump
  • Default sicuro: STREAM_CLOUD_PROVIDER=local_lab (nessuna chiamata Proxmox finché non configuri token)

Fase 1 — dettagli implementati

  • Streams::CloudProviders::Hetzner (create/destroy server, wait running, labels)
  • Streams::DnsProviders::Hetzner (upsert/delete A su zona mltv-stream.net)
  • NodeProvisioner#provision_cloud! + bottone admin Provisiona nodo Hetzner
  • Cloud-init: infra/stream-node/cloud-init.yaml (HCLOUD_USER_DATA_FILE)
  • Decommission usa il provider del nodo (non solo ENV globale)
  • WireGuard: vedi §16

Ordine di lavoro consigliato sul branch: 0 → L → 1 → 2 → 3 → 4; A quando si decide di lasciare il Proxmox casa.


12. Ordine operativo “ora” (senza Auction)

  1. Rinforzare dischi/alert sul Proxmox prod (§6).
  2. Secondo dominio + zona Hetzner DNS.
  3. Progetto Hetzner Cloud + immagine/snapshot stream-node.
  4. Tunnel WireGuard Proxmox ↔ Cloud (Redis/DB/API private).
  5. Implementare fasi 0 → L → 1… sul branch.
  6. Cutover overflow in produzione solo dopo lab + smoke Cloud verdi.

13. Criteri di successo

Criterio Misura
Lab Scale-out/in e assignment verificati su Proxmox senza Hetzner
Picco N live oltre soft cap home con spare Cloud ready
Continuità Drop telefono → slate YT attiva
Disco Nessun outage DB/API per disco video; alert prima del critical
DNS Aruba intatto; ingest sul secondo dominio
Futuro Path chiaro verso Auction senza riscrivere autoscaler

14. Aperto / da calibrare

Voce Note
Nome secondo dominio mltv-stream.net (chiuso)
Soft cap stream-home es. 4 vs 6
Tipo VM Cloud CPX vs CCX
OVERFLOW_MAX / budget
Spare di notte 0 vs 1
Dettaglio WireGuard subnet, peer, MTU
Soglie GB disco da size reale volume video

15. Riepilogo esecutivo

Ora: restiamo sul Proxmox di produzione; attiviamo Hetzner Cloud (spare MediaMTX+ffmpeg) + Hetzner DNS (secondo dominio) + WireGuard. Testiamo prima in lab sullo stesso Proxmox. Garage resta; B2 dopo. YouTube consigliato in prodotto. Disco e alert sono vincolo non negoziabile.

Dopo: eventuale Auction Hetzner sostituisce il control plane casa e WireGuard → vSwitch, senza cambiare il modello nodi/autoscaler.


16. WireGuard (Proxmox casa ↔ Hetzner Cloud)

Obiettivo: Rails/Sidekiq sullhost di produzione raggiungono api_base_url / internal_rtmp_url dei nodi Cloud (es. http://10.0.0.9:9997) senza esporre Redis/Postgres/MediaMTX API su Internet.

Setup consigliato (una tantum)

  1. Su Hetzner Console: crea Network privata (es. 10.0.0.0/16) nella location fsn1, subnet 10.0.1.0/24 per i server Cloud.
  2. Imposta HCLOUD_NETWORK_ID=<id> così i nodi stream entrano nella network al create.
  3. Su Proxmox (VM o LXC gateway): installa WireGuard; peer verso una VM “gateway” Cloud sempre on (CX22 piccola) oppure peer site-to-site verso un server Cloud fisso.
  4. Alternativa più semplice per i primi test: Tailscale su Proxmox host + su ogni stream-node (cloud-init) — stesso effetto, meno ops.
  5. Firewall Hetzner Cloud:
    • WAN: 1935/tcp (RTMP), 443/tcp o 8888 se HLS diretto
    • Solo rete privata / WG: 9997 (MediaMTX API), niente Postgres/Redis sui nodi stream
  6. Verifica da Rails: curl http://<private_ip>:9997/v3/paths/list

Finché WireGuard/Tailscale non è pronto, api_base_url punta comunque alla private IP (o pubblica se manca private_net): il path MediaMTX da Create fallirà dal control plane se non raggiungibile — provisionare nodi Cloud solo dopo connettività privata OK, oppure smoke con API su IP pubblico temporaneo + firewall allowlist IP casa.

ENV produzione (stream autoscale)

HCLOUD_TOKEN=...
HCLOUD_LOCATION=fsn1
HCLOUD_SERVER_TYPE=cpx21
HCLOUD_IMAGE=debian-12
HCLOUD_SSH_KEY=matchlivetv-stream
HCLOUD_NETWORK_ID=          # opzionale
HCLOUD_USER_DATA_FILE=/opt/matchlivetv/infra/stream-node/cloud-init.yaml

STREAM_DNS_ZONE=mltv-stream.net
STREAM_DNS_TTL=60
STREAM_CLOUD_DNS_SUFFIX=mltv-stream.net
STREAM_CLOUD_MAX_PUBLISHERS=4
STREAM_NODE_ENV=prod

# Default lab sicuro; in prod per overflow usa hetzner esplicitamente dal bottone admin
STREAM_CLOUD_PROVIDER=local_lab
STREAM_DNS_PROVIDER=lab