English· Español· Deutsch· Nederlands· Français· 日本語· ქართული· 繁體中文· 简体中文· Português· Русский· العربية· हिन्दी· Italiano· 한국어· Polski· Svenska· Türkçe· Українська· Tiếng Việt· Bahasa Indonesia

un

ospite
1 / ?
torna alle lezioni

Due direzioni di traffico, un'unica infrastruttura

Benvenuti

La maggior parte dei diagrammi architetturali mostra il traffico che va in una sola direzione: il client in alto, il server in basso, una freccia che punta verso il basso. Nella realtà, il traffico va in entrambe le direzioni.

Ingresso: i client esterni raggiungono i tuoi servizi attraverso questo percorso. Un reverse proxy ai margini della tua rete termina TLS, instrada le richieste e applica le politiche di accesso.

Uscita: i tuoi servizi raggiungono servizi esterni attraverso questo percorso. Chiamare l'API di un processore di pagamenti, recuperare un target webhook, inviare una richiesta a un partner. Spesso attraverso un forward proxy o un gateway NAT con una lista di autorizzazioni (allowlist).

Molte architetture iniziano con un'unica istanza che gestisce entrambe le funzioni. Funziona, finché non smette di farlo. Il modello di guasto è sottile, emerge solo dopo che sono presenti servizi interni sufficienti, e insegna una lezione importante sulla separazione delle responsabilità.

Al termine di questa lezione comprenderai:

- Perché ingress ed egress rappresentano pattern di traffico fondamentalmente diversi, con assi di scalabilità e modelli di guasto differenti

- Hairpin NAT e perché un proxy che tenta di connettersi a sé stesso fallisce

- La biforcazione architetturale: da un'unica istanza a due, e cosa possiede in modo esclusivo ciascuna di esse

- I vantaggi in termini di isolamento di sicurezza: ogni lato può bloccare l'accesso ai soli peer realmente autorizzati

- Come identificare quando il tuo design a istanza singola ha superato la soglia in cui la separazione diventa necessaria

Perché le direzioni richiedono strumenti diversi

Due carichi di lavoro diversi a un confine di rete

Caratteristiche del traffico in ingresso (Ingress):

- Inizializzato da parti esterne (l'intera internet)

- Il volume scala in base alla tua base utenti

- Terminazione TLS, instradamento delle richieste, limitazione della velocità per origine

- Preoccupazioni relative alla difesa a più livelli: DDoS, abusi, scraping

- L'IP pubblico deve accettare connessioni da chiunque

Caratteristiche del traffico in uscita (Egress):

- Iniziate dai tuoi stessi servizi (un insieme noto e limitato di client)

- Il volume scala in base ai tuoi pattern di chiamate service-to-service e alle chiamate verso API esterne

- Allowlisting degli IP di origine negli endpoint remoti (hai un unico IP in uscita fisso di cui i partner si fidano)

- Preoccupazioni relative alla difesa a più livelli: esfiltrazione di dati, servizi interni compromessi che effettuano chiamate in uscita

- Dovrebbe rifiutare le connessioni da chiunque non sia uno dei tuoi servizi

L'asimmetria chiave: l'ingress accetta traffico dal mondo; l'egress accetta traffico solo dai tuoi servizi. Metterli sulla stessa macchina significa che quella macchina deve essere simultaneamente raggiungibile dal mondo (per l'ingress) e raggiungibile solo dai tuoi servizi (per l'egress). Le regole del firewall che soddisfano l'uno lavorano contro l'altro.

Il percorso di crescita: un progetto minuscolo può nascondere entrambi dietro un unico IP e uno strumento, perché il volume è piccolo e la lista di IP dei partner è breve. Man mano che il progetto cresce, l'attrito tra i due ruoli aumenta e un giorno un specifico modello di guasto (hairpin NAT) impone la separazione.

Ingress vs egress: origini diverse, destinazioni diverse, requisiti diversi

Una piccola startup gestisce tutto (proxy inverso di ingresso, proxy forward di uscita / NAT, servizi interni) su un'unica VM con un solo IP pubblico. Sono in una fase iniziale in cui questo sembra accettabile. Indica due specifici modi di guasto o problemi operativi che questo design incontrerà crescendo e, per ciascuno, spiega la causa sottostante.

Il bug che forza la separazione

Una storia di guasto anonimizzata

Immagina un vero bivio architettonico che si verifica in flotta di produzione. I nomi sottostanti sono stati modificati; la forma è identica a quella che i team incontrano nel mondo reale.

Un'organizzazione gestisce un singolo server proxy su 203.0.113.5. Gestisce l'ingresso (porta 443 per gli utenti) e l'egresso (porta 1080 SOCKS5 per i servizi interni che effettuano chiamate in uscita). I servizi interni risiedono in sottoreti private e instradano tutto il traffico in uscita attraverso quel proxy SOCKS5 su 203.0.113.5:1080.

Uno dei servizi ospitato dietro lo stesso 203.0.113.5 è api.example.com. Il DNS pubblico risolve api.example.com a 203.0.113.5.

Ora un diverso servizio interno deve chiamare api.example.com. Il suo percorso in uscita:

1. Il servizio interno risolve api.example.com → 203.0.113.5

2. Il servizio interno invia la richiesta attraverso il proxy egress SOCKS5 su 203.0.113.5:1080

3. Il proxy tenta di aprire una connessione da sé stesso a 203.0.113.5:443

4. Connessione rifiutata. Il pacchetto dovrebbe uscire e rientrare nello stesso NAT, cosa che la maggior parte degli stack di rete rifiuta. Il proxy non può connettersi a sé stesso tramite il proprio IP pubblico.

Questo è il hairpin NAT: un pacchetto che esce da un NAT e deve rientrare nello stesso NAT per raggiungere la sua destinazione. Senza un supporto speciale per il hairpin nel layer di routing, il pacchetto viene scartato.

Perché emerge in ritardo

Nelle fasi iniziali del progetto, ogni servizio interno comunicava con altri servizi interni tramite hostname privati (internal-api.local) oppure non richiamava i servizi pubblici della propria organizzazione. Il percorso hairpin semplicemente non esisteva.

Poi una nuova funzionalità ha richiesto che il servizio A chiamasse api.example.com (un hostname pubblico). Il percorso hairpin si è attivato. Connessione rifiutata. Interruzione del servizio.

La correzione ha trattato il sintomo (forzando il resolver a restituire l'IP privato di api.example.com invece di quello pubblico). La causa radice: un singolo nodo stava svolgendo troppi ruoli.

Hairpin NAT: il pacchetto esce e non può rientrare nello stesso NAT

La biforcazione architetturale

Una scatola diventa due

La soluzione pulita: separare il proxy in due macchine.

Server di ingresso (IP pubblico 203.0.113.5):

- Caddy / proxy inverso sulle porte 80, 443

- I record DNS pubblici puntano qui

- Ospita api.example.com, app.example.com, ecc.

Server di uscita (diverso IP pubblico 203.0.113.99):

- SOCKS5 / proxy in avanti sulla porta 1080

- Il firewall limita le connessioni in entrata solo agli IP della subnet interna

- I servizi interni instradano tutto il traffico in uscita attraverso questo indirizzo

Cosa si ottiene:

1. Problema hairpin risolto. Un servizio interno che chiama api.example.com instrada il traffico in uscita tramite 203.0.113.99 (egress), che si connette poi normalmente a 203.0.113.5 (ingress, un IP diverso). Il loop NAT scompare perché i due IP risiedono su macchine diverse.

2. Isolamento della sicurezza. Il firewall del server egress può essere configurato per consentire l'accesso solo a un piccolo insieme di IP interni. Il firewall del server ingress rimane aperto al mondo esterno. Due set di regole separati, ciascuno che esprime chiaramente un ruolo specifico.

3. Scalabilità indipendente. La banda in ingresso scala in base agli utenti; la banda in uscita scala in base all'attività dei servizi interni. È possibile aggiornare l'uno senza toccare l'altro.

4. Isolamento dei guasti. Una configurazione errata dell'egress non interrompe più il sito pubblico. Un attacco DDoS contro il sito pubblico non esaurisce più la banda in uscita.

5. Modello mentale più chiaro. Ogni macchina ha un solo compito. Gli ingegneri possono ragionare sulle problematiche di ingress senza pensare all'egress, e viceversa.

Dopo la separazione, un servizio interno deve ancora chiamare `api.example.com`. Descrivi il nuovo percorso dei pacchetti dal servizio interno al backend dell'API. Includi: a quale IP si connette prima il servizio interno, cosa fa quella macchina con la richiesta, a quale IP la invia successivamente e dove va la risposta.

Due assi, due decisioni di dimensionamento

Scalabilità Indipendente

Prima della separazione, la crescita in una qualsiasi direzione metteva sotto stress la stessa macchina. Dopo la separazione, ogni direzione dispone della propria infrastruttura di provisioning.

Dimensionamento dell'Ingress: scala in base agli utenti. Le decisioni sulla capacità risiedono nel livello pubblico (più repliche del reverse proxy, VM più grandi, CDN in prima linea). Il budget di banda è calcolato in base al traffico degli utenti al picco.

Dimensionamento dell'Egress: scala in base al volume di chiamate interne da servizi a API esterne. È spesso dominato dalla consegna di webhook, dalle chiamate ai processori di pagamento o dal recupero di dati da terze parti. Il budget di banda è calcolato in base ai modelli di chiamata interni.

Isolamento dei guasti: un attacco DDoS contro l'ingress pubblico non consuma più la banda di egress (quelle chiamate al processore di pagamento vengono comunque eseguite). Un crash del proxy di egress non porta più giù il sito pubblico (gli utenti continuano ad accedere al sito; falliscono solo le chiamate in uscita interne).

SLO diversi: la disponibilità dell'ingress è importante per gli utenti (interruzione visibile del sito); la disponibilità dell'egress è importante per gli operatori (guasti in background che potrebbero richiedere più tempo per essere rilevati). Ogni lato può avere il proprio SLO.

Multipli Server di Egress

Una volta che il ruolo di egress è su una macchina dedicata, il passo successivo ovvio è eseguire più macchine di egress dietro un bilanciatore di carico per l'alta disponibilità. Ogni nuovo servizio interno punta al hostname di egress (che si risolve nel pool bilanciato) anziché a un singolo IP.

Stessa lezione del resto dei sistemi distribuiti: una volta che un livello diventa stateless e ha un proprio ruolo, si replica a basso costo.

Una nuova integrazione partner

La tua organizzazione gestisce la separazione ingress/egress come progettato. Il server egress ha un IP pubblico fisso (203.0.113.99) che hai autorizzato con tre API partner esistenti (un processatore di pagamenti, un gateway SMS, un provider di email).

Un team di prodotto vuole aggiungere una quarta integrazione: un sistema di consegna webhook che effettua chiamate di ritorno verso endpoint di clienti in tutto il mondo. Previsione del volume: 10.000 chiamate al minuto, con picchi fino a 30.000.

Decidi: questa nuova integrazione deve risiedere sul server egress esistente o richiede un percorso egress separato? Ragiona su banda, isolamento dei guasti e se le liste di autorizzazione dei partner esistenti necessitino di aggiornamenti in entrambi i casi.

Progettare un confine di rete per un servizio in crescita

Sintesi

Hai imparato perché ingress ed egress richiedono strumenti diversi, il guasto hairpin NAT che impone la separazione nelle flotte reali, e come la scalabilità indipendente, l'isolamento della sicurezza e l'isolamento dei guasti si ottengono una volta attuata la separazione.

Applica tutti e quattro i concetti.

Un'azienda SaaS di medie dimensioni gestisce tre sotto-dominio di prodotto (app, api, admin) per i propri utenti, oltre a quattro integrazioni in uscita (Stripe, Twilio, SendGrid, un sistema di webhook per i clienti). Attualmente tutto è dietro un singolo proxy su un unico IP pubblico. Hanno iniziato a ricevere segnalazioni di guasti hairpin intermittenti quando i servizi interni tentano di chiamare api.example.com. Vogliono progettare una soluzione permanente.

Proponi un'architettura di ingresso/uscita (ingress/egress) per questa azienda. Affronta i seguenti punti: quante macchine, quali IP svolgono quali ruoli, a cosa puntano i DNS di ciascun sottodominio, quali integrazioni in uscita condividono un percorso di uscita (egress) (e quali dovrebbero essere separati) e una concreta preoccupazione di monitoraggio che il nuovo design abilita e che il vecchio non consentiva.

Dove va questo corso da qui in avanti

Dove va questo corso da qui in avanti

Hai ora visto uno dei refactoring più puliti per la separazione delle responsabilità nei sistemi distribuiti: una macchina diventa due, ciascuna con un ruolo chiaro, e il sistema eredita vantaggi in termini di scalabilità, sicurezza e isolamento dei guasti lungo il percorso.

La prossima lezione (cs_distsys_failure_modes_and_blast_radius) estende il ragionamento sull'isolamento dei guasti. Leggerai un postmortem sanitizzato di DNS-SERVFAIL, identificherai il pattern di guasto a cascata e scriverai voci d'azione senza attribuzione di colpa che puntino ai sistemi invece che alle persone.

Lezione complementare: geometry_of_ingress_egress_separation ricalca la separazione come un grafo bipartito ed esplora i vertici di taglio, le partizioni di rete e ciò che la teoria dei grafi ti dice su un confine di rete.

Ben fatto. Avanti.