Blog ·
Cum alege un agent serverul de margine potrivit
DNS cu mai multe înregistrări A, margini de nivel 4 și PROXY protocol v2, cu măsurători: ce face un client cu mai multe adrese și de ce întoarcerea e grea.
De Provibr
Dacă rulați un agent pe hardware-ul clienților dumneavoastră, adresa pe care o apelează el trăiește în două locuri incomode: în DNS-ul public și în fișierul de acreditare pe care l-ați împărțit la înrolare. Amândouă sunt greu de schimbat ulterior, iar unul dintre ele spune întregului internet unde se află planul dumneavoastră de control.
Am pus un strat de servere de margine în fața planului nostru. Iată ce a cerut asta, inclusiv părțile care au apărut abia după ce au trecut prin el agenți adevărați.
Problema este adresa, nu traficul
Numele de gazdă al gateway-ului se rezolva la origine. Fiecare agent înrolat îl știa, la fel și oricine putea tasta dig. O inundare volumetrică țintită spre acea adresă doboară planul de control pentru toată lumea, iar după fapt nu se mai poate face nimic isteț.
Scopul era îngust, iar enunțarea lui ține proiectarea sinceră: adresa originii dispare de pe traseul folosit de agenți, așa că o inundare lovește o mașină care poate fi înlocuită în zece minute. În mod explicit nu era un scop ca marginile să protejeze conținutul. TLS mutual face deja asta de la un capăt la celălalt, iar o margine care ar putea inspecta traficul ar fi o înrăutățire.
Nivelul patru, pentru că terminarea TLS ar strica exact lucrul care contează
Proiectarea evidentă este un proxy invers care termină TLS la margine. Aici este cea greșită, din două motive specifice acestui fel de sistem.
Gateway-ul autentifică agenții pe certificatul lor de client, fixat față de o amprentă stocată per agent. Terminarea TLS la margine încheie acolo strângerea de mână mutuală, așa că originea ar trebui să aibă încredere în orice afirmă marginea. Și ar însemna copierea cheii private a gateway-ului pe câteva mașini virtuale ieftine al căror singur rost este să fie de unică folosință.
Așa că fiecare margine este un simplu redirector TCP. Vede text cifrat, nu ține niciun secret și nu rulează nicio aplicație, adică o suprafață suficient de mică încât „înlocuiește-o” răspunde la cele mai multe probleme.
Costă un singur lucru. Fiecare conexiune pare acum să pornească de la margine, așa că originea nu mai știe cine sună.
PROXY protocol v2 și diferența dintre o afirmație și o măsurătoare
Reparația standard este PROXY protocol: redirectorul pune înaintea fiecărei conexiuni un mic antet binar care poartă adresa originală. Gateway-ul îl analizează și pune adresa reală înapoi în jurnalele lui.
Decizia de proiectare care merită copiată este păstrarea a două valori în loc de una.
- Adresa din antet este ceea ce spune celălalt capăt că ar fi clientul. Este o afirmație.
- Adresa soclului exterior, mașina de la care au venit efectiv octeții, este ceva ce am măsurat noi înșine.
Un antet falsificat o poate face pe prima să mintă. A doua rămâne adevărată. Păstrarea lor ca două câmpuri separate înseamnă că o linie de jurnal poate fi citită peste ani fără să vă întrebați care dintre cele două este, și face ușor răspunsul sincer la „putem avea încredere în adresa IP”: de ea nu atârnă nimic în afară de o linie de jurnal, pentru că identitatea atârnă de certificatul partenerului.
Două note de implementare care ne-au scutit de necazuri:
Adulmecați, nu impuneți. O trecere la PROXY protocol nu este atomică. Dacă primii octeți arată ca un ClientHello de TLS și nu ca semnătura protocolului, împingeți-i înapoi în flux și înaintați conexiunea neatinsă. Asta face din desfășurarea analizorului și din activarea lui pe redirectoare doi pași independenți.
Țineți o hartă cu cheia pe conexiunea interioară. Odată ce octeții ajung la o bibliotecă gRPC, adresa partenerului este un port de buclă locală, iar atât agentul, cât și marginea au devenit de negăsit. Indexarea partenerului real după portul-sursă al conexiunii interne, cu curățare la închidere, este singurul loc în care supraviețuiește acea informație.
Acolo se ascunde și un mic câștig operațional: o conexiune care se deschide și se închide fără să trimită nimic nu produce nicio linie de jurnal. Verificările de sănătate fac asta neîncetat, iar nescrierea lor a eliminat zeci de mii de linii inutile pe zi.
Descoperirea: mulțimea de înregistrări A este lista
Cum află agentul că există mai mult de o margine? Variantele erau înregistrări TXT cu un conținut structurat, înregistrări SRV sau pur și simplu mai multe înregistrări A pe același nume.
Mai multe înregistrări A au câștigat pe un singur argument. Mulțimea A este deja lista locurilor la care te poți conecta. O înregistrare TXT ar fi o a doua sursă a adevărului pentru același fapt, iar cele două pot fi în dezacord. SRV ar adăuga ponderi de care nu avem nevoie, pentru că agentul măsoară singur în loc să i se spună pe care să o prefere. TTL-ul este scurt, așa că scoaterea unei margini este o schimbare în DNS plus un TTL de răbdare.
Ce face de fapt un client cu mai multe înregistrări A
Înainte de a construi orice logică de alegere, merita măsurat ce se întâmplă pe gratis. Un client HTTP modern încearcă adresele din mulțimea de înregistrări și își întinde timpul de așteptare la conectare peste ele. Am pus prima o adresă despre care știam că este moartă și am măsurat:
| Comportamentul adresei moarte | Timp până la conectarea la cea vie |
|---|---|
| Conexiune refuzată | 7 ms |
| Fără rută către gazdă | 7,5 ms |
| SYN aruncat în tăcere, 25 s timp de așteptare | 12,5 s |
| SYN aruncat în tăcere, 10 s timp de așteptare | 5,0 s |
Tiparul este destul de limpede ca să proiectați după el: respingerile ferme sunt gratuite, iar o gaură neagră costă aproximativ timpul de așteptare la conectare împărțit la numărul de adrese. Prin urmare, trecerea pe altă adresă funcționa deja fără nicio schimbare în agent, așa că schimbarea din DNS a putut fi livrată înaintea logicii de alegere. Înseamnă totodată că timpul dumneavoastră de așteptare la conectare este acum o funcție de câte înregistrări publicați, ceea ce nu este evident până când nu aveți acest tabel în față.
Trecerea pe altă adresă nu este alegere
Trecerea gratuită ia adresa care răspunde prima, ceea ce nu este același lucru cu cea mai bună. Un agent din Amsterdam a stat zile la rând pe o margine din altă țară, pentru că aceea a răspuns prima, iar sesiunea a trăit apoi o săptămână.
Așa că agentul măsoară. Amănuntele care au contat:
Sondați cu o strângere de mână TLS completă, nu cu o conectare TCP. O margine poate accepta o conexiune și să nu înainteze nimic, iar exact acesta este eșecul pe care sonda trebuie să îl prindă. O conectare seacă este primită la fel de voios de un redirector defect ca de unul care funcționează.
Dați fiecărei sonde bugetul ei, în paralel. O margine care acceptă și apoi amuțește poate lăsa o strângere de mână atârnând dincolo de timpul de așteptare la conectare, așa că bugetul per adresă stă alături de el, nu se sprijină pe el.
Păstrați tot clasamentul, nu câștigătorul. Adresa aleasă și lista ordonată intră în fișierul de stare al agentului, împreună cu mulțimea de adrese din care au fost măsurate. Asta ieftinește o repornire și înseamnă că o trecere pe altă adresă supraviețuiește unei reporniri, în loc ca agentul să se întoarcă voios la adresa care tocmai eșuase.
Invalidați pe mulțime, nu pe alegere. Dacă o margine este adăugată sau scoasă, măsurătoarea este veche chiar dacă adresa aleasă se mai află în mulțime.
Nu faceți nimic când nu este nimic de ales. Cu mai puțin de doi candidați se sare peste tot mecanismul, iar asta se întâmplă în dezvoltare, unde numele se rezolvă la o singură adresă.
Iar regula care le guvernează pe toate: alegerea nu are voie să devină niciodată un nou mod de eșec. DNS-ul refuză, nu răspunde nimeni, fișierul de stare nu poate fi scris: fiecare dintre aceste trasee se termină în „nicio alegere”, iar agentul se conectează exact așa cum o făcea înainte să existe toate acestea.
Întoarcerea este mai grea decât trecerea pe altă adresă
Trecerea este ușoară. Jumătatea grea este că după aceea agentul rămâne pe marginea mai lentă cât trăiește acea sesiune, ceea ce poate însemna săptămâni.
Așa că o veghe rulează alături de sesiunea vie și face două lucruri și nimic altceva. Încearcă adresele clasate deasupra celei curente, cu aceeași strângere de mână completă, și remăsoară totul când ultima măsurătoare este veche. Numerele și motivul fiecăruia:
- Un interval de cinci minute cu fluctuație completă, ca sondele unei flote să nu se sincronizeze. Fiecare minut costă de cinci ori mai mult și nu câștigă nimic, pentru că histerezisul de mai jos face oricum ca orice întoarcere să dureze două runde.
- O serie de două înainte de a comuta înapoi. O singură sondă reușită este o margine care tocmai a terminat de repornit.
- Două praguri pentru remăsurarea periodică: unul absolut de 20 ms și unul relativ de 25 %. Trebuie îndeplinite amândouă. Cel absolut singur ar aproba o mutare de la 100 ms la 79 ms; cel relativ singur ar aproba de la 4 ms la 3 ms. Două margini din același centru de date diferă cu mai puțin de o milisecundă, iar fără asta și-ar schimba locurile o dată la două zile.
Comutarea este o reconectare curată cu motivul ei propriu, aflată alături de celelalte încheieri planificate de sesiune și nu modelată ca o eroare. Asta contează mai mult decât pare: o eroare ar hrăni scara de așteptare exponențială, așa că un agent care se întoarce la marginea lui preferată ar fi pedepsit cu o întârziere crescândă pentru că face ce trebuie. Cutia de ieșire a mesajelor se reia, iar magazia de deduplicare are grijă ca nimic să nu fie executat de două ori.
Privind lucrul acesta din partea cealaltă
Platforma privește același lucru de la capătul ei, iar două amănunte de acolo merită furate.
Rezolvați numele așa cum o face agentul, prin rezolvatorul sistemului și nu printr-o interogare DNS directă. O interogare directă a eșuat de-a dreptul pe o mașină al cărei rezolvator arăta spre ea însăși, iar mai important este că rezolvatorul sistemului este cel folosit de traficul real, deci întoarce aceeași listă.
Luați două numere de la un singur soclu: conectarea TCP, care este dus-întorsul până la marginea însăși, și strângerea de mână TLS completă, care trebuie să străbată tunelul până la origine și înapoi. Sănătatea atârnă de al doilea. Un redirector cu un capăt din spate mort răspunde perfect la primul.
Ultima lecție este una de firewall. Docker ocolește complet firewallul gazdei. Regulile care spun că numai marginile pot ajunge la porturile gateway-ului trebuie să trăiască în lanțul pe care îl consultă Docker însuși, altfel sunt decorative deși par active. Iar acel lanț vede traficul în ambele sensuri, așa că propriile noastre sonde de ieșire de la origine către margini se potriveau cu același port de destinație și erau aruncate, făcând monitorul să raporteze fiecare margine drept căzută în timp ce fiecare agent era în regulă.
Ce nu rezolvă asta
Marginile nu protejează conținutul, iar a începe să le tratați ca pe o graniță de securitate ar fi o greșeală. O adresă care era căzută în timpul unei măsurători complete este redescoperită abia de următoarea, o lipsă cunoscută și acceptată. Iar TTL-ul din DNS este o limită de jos pentru cât de repede iese o margine din rotație, adică prețul plătit ca mulțimea de înregistrări A să fie lista.
Așa ajunge agentul la platformă la Provibr, unde el se conectează numai spre exterior și ține datele de autentificare pe gazda proprie a clientului.