Blog ·
Pool-uri de adrese IPv4 pentru un produs VPS
Adresele sunt singura resursă pe care nu o cumpărați într-o marți. Cum modelați pool-urile, de ce arbitrează baza de date și ce se întâmplă când unul seacă.
De Provibr
Tot restul dintr-un produs VPS este elastic. Vă trebuie mai mult CPU, cumpărați un nod. Vă trebuie mai mult disc, cumpărați disc. Vă trebuie mai multe adrese și sunteți pe o listă de așteptare, plătiți un intermediar sau completați un formular de justificare pentru un registru regional.
IPv4 este singura resursă care se poartă ca un bun fizic și, de obicei, primește cea mai puțină atenție la proiectare, pentru că la început aveți un /24 și 254 pare din belșug. Încetează să pară din belșug exact în punctul în care reproiectarea este scumpă.
Porniți de la ce vă dă cu adevărat un bloc
Numărul utilizabil nu este niciodată mărimea blocului. Două adrese se duc la rețea și la difuzare, iar încă una se duce la gateway. La dimensiuni mici asta nu este o eroare de rotunjire, este cea mai mare parte a blocului:
| Bloc | Adrese | Rețea + difuzare | Gateway | Vandabile | Pierdere |
|---|---|---|---|---|---|
| /24 | 256 | 2 | 1 | 253 | 1% |
| /27 | 32 | 2 | 1 | 29 | 9% |
| /28 | 16 | 2 | 1 | 13 | 19% |
| /29 | 8 | 2 | 1 | 5 | 38% |
| /30 | 4 | 2 | 1 | 1 | 75% |
Un /29 sună a opt adrese exact până când vindeți cinci dintre ele. Dacă vă cumpărați spațiul de adrese în blocuri mici, treceți pierderea în buget ca poziție separată; la /29 aruncați trei optimi din ce ați plătit.
Două limite merită înscrise în orice construiți. Acceptăm cel mult un /8, pentru că peste această mărime o bară de utilizare încetează să mai însemne ceva, și cel puțin un /30, pentru că /31 și /32 au zero adrese utilizabile după regulile obișnuite de gazdă, iar acceptarea lor produce doar un pool care nu poate aloca niciodată. Refuzul explicit este mai bun decât acceptarea urmată de goliciune.
Încă o subtilitate: adresa de gateway are voie să stea înăuntrul intervalului dumneavoastră de alocare și este pur și simplu sărită. Asta este deliberat, pentru că este aranjamentul cel mai ușor de urmărit pentru un client, dar înseamnă că capacitatea unui pool este un calcul și niciodată un număr stocat. Orice ține un contor de adrese libere va intra în contradicție cu realitatea în ziua în care cineva mută un gateway.
Modelați pool-urile după ce vindeți, nu după ce ați cumpărat
Unitatea pe care o vede un client este un plan. Unitatea pe care ați cumpărat-o de la un intermediar este un bloc. Cele două nu au aproape niciodată aceeași formă, iar legarea lor una de alta o ia razna prima dată când cumpărați un al doilea bloc.
Aranjamentul care a rezistat: un plan indică un pool, iar un pool este fie un singur prefix, fie un grup numit de prefixe. Adăugarea de capacitate la un produs înseamnă atunci adăugarea unui prefix la un grup, adică un singur rând. Alocatorul parcurge grupul în ordine și ia prima adresă pe care o poate obține, așa că un grup este și o ordine de golire: puneți în față blocul pe care vreți să îl consumați primul.
Sub-blocuri și greșeala care alocă de două ori, în tăcere
Clienții nu folosesc un /24 ca pe o singură rețea plată. Îl taie: o bucată în spatele unui router, o bucată pe alt VLAN, o bucată cu gateway propriu. Așa că un plan de adrese are nevoie de blocuri-copil.
Aici stă eșecul interesant și merită spus cu precizie, tocmai pentru că eșuează în tăcere.
Mecanismul care împiedică două comenzi simultane să primească aceeași adresă este un indice unic parțial pe (prefix, adresă), limitat la rândurile care nu au fost eliberate. Acel indice este per prefix. Dacă două rânduri de prefix pot împărți fiecare aceeași adresă, atunci, din punctul de vedere al indicelui, acelea sunt două adrese diferite, iar platforma va da același IP la două mașini fără ca absolut nimic să meargă prost în software.
Indicele nu poate repara asta. Trebuie să o facă structura. Așa că spațiul de adrese este o partiție strictă, iar regulile care îl fac să fie una sunt:
- Un copil se află în întregime înăuntrul părintelui său și este strict mai mic.
- Frații nu au voie să se suprapună.
- Părintele nu împarte nimic care cade înăuntrul unui bloc-copil. Din fereastra părintelui este decupat blocul întreg, nu doar intervalul de alocare al copilului, pentru că altfel răspunsul ar depinde de un interval pe care vreun administrator îl mută săptămâna viitoare.
- Imbricarea are un singur nivel.
Cu acestea patru, „cărui prefix îi aparține 192.0.2.70” are întotdeauna exact un răspuns, iar indicele existent este suficient.
Ce moștenește un copil este un set separat de decizii, iar răspunsurile nu sunt uniforme. Gateway: niciodată (un copil fără gateway propriu nu are motiv să existe). DNS: da, dacă propriul câmp este gol, pentru că serverele de rezolvare aparțin rețelei, nu blocului. Interval de alocare: numai acolo unde intervalul părintelui chiar atinge copilul, de vreme ce intervalul părintelui este exprimat în adresele părintelui și de obicei nu se suprapune deloc peste copil; moștenirea lui orbește produce o fereastră goală.
Lăsați baza de date să arbitreze
Tentația, când sosesc două comenzi deodată, este să blocați ceva sau să țineți un contor. Amândouă sunt mai rele decât răspunsul plictisitor.
Ambele comenzi calculează aceeași „cea mai mică adresă liberă” și amândouă încearcă să insereze. O inserare câștigă, cealaltă lovește încălcarea de unicitate, iar cea care pierde trece la următorul candidat. Baza de date este singura parte care vede fiecare scriitor, inclusiv pe cel din alt proces, alt lucrător sau altă filă de browser.
Corolarul contează când scrieți codul: nu verificați întâi. A citi „este liberă adresa asta” și apoi a insera deschide exact fereastra pe care constrângerea există ca să o închidă. Inserarea eșuată este răspunsul la „era liberă”, și este singurul de încredere.
Un detaliu de implementare care prinde lumea pe picior greșit: multe ORM-uri împachetează erorile de driver într-un tip propriu de eroare, așa că SQLSTATE-ul pe care îl vreți nu se află pe excepția pe care o prindeți, ci pe cauza ei. Verificați întregul lanț și folosiți numele constrângerii ca să decideți ce mesaj arătați. Un singur tabel are adesea patru reguli de unicitate diferite, iar a le face pe toate să spună același lucru trimite clientul la câmpul greșit.
Ce se întâmplă când un pool seacă
Trei eșecuri diferite se ascund în spatele lui „nicio adresă disponibilă”, iar amestecarea lor produce de fiecare dată un tichet de asistență:
| Situație | Ce înseamnă | Ce ar trebui să citească clientul |
|---|---|---|
| Planul nu indică niciun pool | Configurare, nu capacitate | Planul nu are pool de adrese |
| Un prefix are date inutilizabile | Un rând stricat, restul în regulă | Aceste prefixe nu au putut fi citite |
| Toți candidații sunt ocupați | Epuizare reală | Acest pool este plin, și iată care |
Cel din mijloc merită codul în plus. Un singur prefix cu date imposibil de citit nu ar trebui să blocheze celelalte prefixe din grupul său; notați-l, încercați restul și raportați-l doar dacă nu a mers absolut nimic. Altfel, un singur rând stricat doboară plasarea comenzilor pentru o linie de produse întreagă.
Iar mesajul de epuizare ar trebui să numească prefixul și CIDR-ul lui. „Niciun IP disponibil” trimite pe cineva să citească jurnale. „Pool plin: customer-block-2 (198.51.100.0/28)” îl trimite să cumpere adrese.
Dați adresele înapoi, și fiți atenți la când
Eliberarea este jumătatea care pierde capacitate în tăcere.
Două reguli care s-au dovedit a conta mai mult decât ne așteptam. Întâi, eliberarea nu înseamnă ștergerea rândului: ea pune o marcă temporală de eliberare. Indicele unic parțial se uită doar la rândurile vii, așa că adresa este imediat refolosibilă, în timp ce la „cine a avut 192.0.2.10 anul trecut” se poate în continuare răspunde. Acesta este cel mai mare motiv pentru care să faceți administrare de adrese, și este întrebarea pe care o pune o reclamație de abuz.
Apoi, eliberarea are loc când mașina este confirmată ca dispărută, nu când a fost cerută ștergerea. Doar lucrul care stă lângă hipervizor știe că mașina chiar a dispărut; eliberarea la trimiterea comenzii dă adresa următorului client în timp ce mașina veche încă rulează și încă răspunde pe ea. Cazul-oglindă este o comandă care eșuează la jumătate: acolo alocarea este aruncată, nu eliberată, pentru că nu a fost niciodată configurată pe nimic.
Există aici o asimetrie deliberată care merită semnalată, pentru că, cu un strat mai sus, facem alegerea opusă. Adresele se refolosesc, cea mai mică liberă prima. Identificatorii de mașini nu se refolosesc niciodată. Un mesaj de stare întârziat despre o mașină ștearsă care aterizează pe un rând nou cu același identificator strică istoricul a două mașini; o adresă nu poartă istoric în felul acesta, iar a nu refolosi niciodată una ar epuiza definitiv un /24 după 254 de mașini. Același produs, reguli opuse, motive diferite.
Rezervările stau în același tabel cu alocările
Va trebui să țineți adrese deoparte: un balansor de sarcină, un gateway pe un bloc pe care nu l-ați pus încă în funcțiune, cele cinci adrese pe care cineva le-a promis unui client pentru trimestrul viitor.
Puneți-le în același tabel cu alocările reale. Fiecare cititor care întreabă „ce este ocupat”, alocatorul, bara de utilizare, lista de sugestii dintr-un câmp de adresă, le numără atunci automat și nu există o a doua evidență care să se desincronizeze. Un tabel separat de rezervări este o a doua sursă a adevărului, care va intra în contradicție cu prima în mai puțin de o lună.
Cu o singură excepție, ușor de greșit: o adresă rezervată nu trebuie să ajungă niciodată în lista de permisiuni de firewall a unei mașini. Acea listă este construită din alocările care aparțin unui singur server, iar o rezervare nu aparține niciunui server. Greșiți asta și mașina unui client începe să accepte trafic adresat balansorului de sarcină al altcuiva.
Și IPv6
Recunoaștem intrările IPv6 și le refuzăm politicos, în loc să le acceptăm și să calculăm ceva plauzibil: un traseu IPv6 care merge pe jumătate este mai periculos decât niciunul, pentru că numerele pe care le produce arată corect. Decizia ieftină care se plătește singură este stocarea adreselor, a gateway-urilor și a serverelor DNS ca text, nu ca număr întreg pe 32 de biți, astfel încât adăugarea IPv6 mai târziu să fie cod și nu o migrare a fiecărui rând pe care îl aveți.
Varianta scurtă
- Treceți pierderea în buget. Un /29 înseamnă cinci adrese vandabile, nu opt.
- Îndreptați planurile către pool-uri, iar pool-urile către prefixe. Cumpărarea de spațiu în plus ar trebui să fie un singur rând.
- Dacă permiteți sub-blocuri, faceți din spațiul de adrese o partiție strictă, altfel regula de unicitate care vă protejează este, în tăcere, per prefix și nu protejează nimic.
- Lăsați constrângerea de unicitate să decidă cursa. Nu verificați niciodată întâi.
- Eliberați la confirmare, nu la cerere, și păstrați istoricul.
- Numiți pool-ul în mesajul de epuizare. Este diferența dintre un tichet și o comandă de achiziție.
Asta a ieșit din construirea administrării de adrese în Provibr, unde adresele sunt emise când se plasează o comandă și sunt date înapoi când agentul confirmă că mașina chiar a dispărut.