Arquitectura de l'equilibri de càrrega de xarxa a Yandex.Cloud

Arquitectura de l'equilibri de càrrega de xarxa a Yandex.Cloud
Hola, sóc Sergey Elantsev, em desenvolupo equilibrador de càrrega de xarxa a Yandex.Cloud. Anteriorment, vaig dirigir el desenvolupament de l'equilibrador L7 per al portal Yandex: els companys fan broma que faci el que faci, resulta que és un equilibrador. Explicaré als lectors Habr com gestionar la càrrega en una plataforma al núvol, quina considerem l'eina ideal per assolir aquest objectiu i com estem avançant cap a la creació d'aquesta eina.

Primer, introduïm alguns termes:

  • VIP (IP virtual): adreça IP de l'equilibrador
  • Servidor, backend, instància: una màquina virtual que executa una aplicació
  • RIP (IP real): adreça IP del servidor
  • Healthcheck: comprova la preparació del servidor
  • Zona de disponibilitat, AZ - infraestructura aïllada en un centre de dades
  • Regió - una unió de diferents AZ

Els equilibradors de càrrega resolen tres tasques principals: realitzen l'equilibri en si mateix, milloren la tolerància a errors del servei i simplifiquen la seva escala. La tolerància a errors s'assegura mitjançant la gestió automàtica del trànsit: l'equilibrador controla l'estat de l'aplicació i exclou de l'equilibri les instàncies que no superen la comprovació de vida. L'escalat es garanteix distribuint uniformement la càrrega entre instàncies, així com actualitzant la llista d'instàncies sobre la marxa. Si l'equilibri no és prou uniforme, algunes de les instàncies rebran una càrrega que supera el seu límit de capacitat i el servei es tornarà menys fiable.

Sovint, un equilibrador de càrrega es classifica per la capa de protocol del model OSI en què s'executa. El Cloud Balancer funciona al nivell TCP, que correspon a la quarta capa, L4.

Passem a una visió general de l'arquitectura de l'equilibrador de núvol. A poc a poc anirem augmentant el nivell de detall. Dividim els components de l'equilibrador en tres classes. La classe del pla de configuració és responsable de la interacció de l'usuari i emmagatzema l'estat objectiu del sistema. El pla de control emmagatzema l'estat actual del sistema i gestiona els sistemes des de la classe del pla de dades, que són directament responsables de lliurar el trànsit dels clients a les vostres instàncies.

Pla de dades

El trànsit acaba en dispositius cars anomenats encaminadors de frontera. Per augmentar la tolerància a errors, diversos dispositius d'aquest tipus funcionen simultàniament en un centre de dades. A continuació, el trànsit va als equilibradors, que anuncien adreces IP anycast a tots els AZ mitjançant BGP per als clients. 

Arquitectura de l'equilibri de càrrega de xarxa a Yandex.Cloud

El trànsit es transmet per ECMP: aquesta és una estratègia d'encaminament segons la qual hi pot haver diverses rutes igualment bones cap a l'objectiu (en el nostre cas, l'objectiu serà l'adreça IP de destinació) i es poden enviar paquets per qualsevol d'ells. També donem suport al treball en diverses zones de disponibilitat segons el següent esquema: anunciem una adreça a cada zona, el trànsit va a la més propera i no supera els seus límits. Més endavant en el post veurem amb més detall què passa amb el trànsit.

Pla de configuració

 
El component clau del pla de configuració és l'API, a través de la qual es realitzen operacions bàsiques amb equilibradors: crear, esborrar, canviar la composició d'instàncies, obtenir resultats de controls de salut, etc. D'una banda, es tracta d'una API REST, i de l'altra. una altra, nosaltres al núvol fem servir molt sovint el marc gRPC, de manera que "traduïm" REST a gRPC i després només fem servir gRPC. Qualsevol sol·licitud condueix a la creació d'una sèrie de tasques idempotents asíncrones que s'executen en un grup comú de treballadors de Yandex.Cloud. Les tasques s'escriuen de manera que es poden suspendre en qualsevol moment i després reiniciar-les. Això garanteix escalabilitat, repetibilitat i registre de les operacions.

Arquitectura de l'equilibri de càrrega de xarxa a Yandex.Cloud

Com a resultat, la tasca de l'API farà una sol·licitud al controlador del servei d'equilibri, que està escrita a Go. Pot afegir i eliminar equilibradors, canviar la composició dels backends i la configuració. 

Arquitectura de l'equilibri de càrrega de xarxa a Yandex.Cloud

El servei emmagatzema el seu estat a Yandex Database, una base de dades gestionada distribuïda que aviat podreu utilitzar. A Yandex.Cloud, com ja va dir, s'aplica el concepte de menjar per a gossos: si nosaltres mateixos utilitzem els nostres serveis, els nostres clients també estaran encantats d'utilitzar-los. La base de dades Yandex és un exemple de la implementació d'aquest concepte. Emmagatzemem totes les nostres dades a YDB, i no hem de pensar en mantenir i escalar la base de dades: aquests problemes se'ns solucionen, utilitzem la base de dades com a servei.

Tornem al controlador de l'equilibrador. La seva tasca és desar informació sobre l'equilibrador i enviar una tasca per comprovar la preparació de la màquina virtual al controlador del control de salut.

Controlador de control de salut

Rep sol·licituds per canviar les regles de comprovació, les desa a YDB, distribueix tasques entre els nodes de control de salut i agrega els resultats, que després es guarden a la base de dades i s'envien al controlador de l'equilibri de càrrega. Al seu torn, envia una sol·licitud per canviar la composició del clúster al pla de dades al node d'equilibri de càrrega, que tractaré a continuació.

Arquitectura de l'equilibri de càrrega de xarxa a Yandex.Cloud

Parlem més sobre els controls de salut. Es poden dividir en diverses classes. Les auditories tenen diferents criteris d'èxit. Les comprovacions TCP han d'establir correctament una connexió en un període de temps determinat. Les comprovacions HTTP requereixen tant una connexió correcta com una resposta amb un codi d'estat 200.

A més, els controls difereixen en la classe d'acció: són actius i passius. Les comprovacions passives simplement supervisen el que passa amb el trànsit sense prendre cap acció especial. Això no funciona molt bé a l'L4 perquè depèn de la lògica dels protocols de nivell superior: a l'L4 no hi ha informació sobre quant de temps va trigar l'operació o si la connexió ha estat bona o dolenta. Les comprovacions actives requereixen que l'equilibrador enviï sol·licituds a cada instància del servidor.

La majoria dels equilibradors de càrrega realitzen ells mateixos comprovacions de la vivacitat. A Cloud, vam decidir separar aquestes parts del sistema per augmentar l'escalabilitat. Aquest enfocament ens permetrà augmentar el nombre de compensadors mantenint el nombre de sol·licituds de control de salut al servei. Les comprovacions es realitzen mitjançant nodes de control de salut separats, a través dels quals es fragmenten i es repliquen els objectius de comprovació. No podeu realitzar comprovacions des d'un amfitrió, ja que pot fallar. Aleshores no obtindrem l'estat de les instàncies que va comprovar. Realitzem comprovacions en qualsevol de les instàncies d'almenys tres nodes de control de salut. Compartim els propòsits de les comprovacions entre nodes mitjançant algorismes de hash coherents.

Arquitectura de l'equilibri de càrrega de xarxa a Yandex.Cloud

Separar l'equilibri i el control de salut pot provocar problemes. Si el node de control de salut fa peticions a la instància, sense passar per alt l'equilibrador (que actualment no està donant servei al trànsit), aleshores es planteja una situació estranya: el recurs sembla estar viu, però el trànsit no hi arribarà. Solucionem aquest problema d'aquesta manera: tenim la garantia d'iniciar el trànsit de control de salut mitjançant equilibradors. És a dir, l'esquema per moure paquets amb trànsit dels clients i dels controls de salut difereix mínimament: en ambdós casos, els paquets arribaran als equilibradors, que els lliuraran als recursos objectiu.

La diferència és que els clients fan sol·licituds a VIP, mentre que els controls de salut fan sol·licituds a cada RIP individual. Aquí sorgeix un problema interessant: donem als nostres usuaris l'oportunitat de crear recursos en xarxes IP grises. Imaginem que hi ha dos propietaris de núvols diferents que han amagat els seus serveis darrere dels equilibradors. Cadascun d'ells té recursos a la subxarxa 10.0.0.1/24, amb les mateixes adreces. Heu de ser capaços de distingir-los d'alguna manera, i aquí heu de submergir-vos en l'estructura de la xarxa virtual Yandex.Cloud. És millor conèixer més detalls a vídeo de about:cloud event, és important per a nosaltres ara que la xarxa és de diverses capes i té túnels que es poden distingir per l'identificador de subxarxa.

Els nodes de HealthCheck contacten amb equilibradors mitjançant les anomenades adreces quasi-IPv6. Una quasi-adreça és una adreça IPv6 amb una adreça IPv4 i un identificador de subxarxa d'usuari incrustats dins. El trànsit arriba a l'equilibrador, que n'extreu l'adreça del recurs IPv4, substitueix IPv6 per IPv4 i envia el paquet a la xarxa de l'usuari.

El trànsit invers va de la mateixa manera: l'equilibrador veu que la destinació és una xarxa grisa de HealthCheckers i converteix IPv4 a IPv6.

VPP: el cor del pla de dades

L'equilibrador s'implementa mitjançant la tecnologia de processament de paquets vectorials (VPP), un marc de Cisco per al processament per lots del trànsit de xarxa. En el nostre cas, el marc funciona a la part superior de la biblioteca de gestió de dispositius de xarxa d'espai d'usuari: Data Plane Development Kit (DPDK). Això garanteix un alt rendiment de processament de paquets: es produeixen moltes menys interrupcions al nucli i no hi ha canvis de context entre l'espai del nucli i l'espai d'usuari. 

VPP va encara més enllà i extreu encara més el rendiment del sistema combinant paquets en lots. Els guanys de rendiment provenen de l'ús agressiu de la memòria cau en els processadors moderns. S'utilitzen tant cachés de dades (els paquets es processen en "vectors", les dades estan a prop l'un de l'altre) i cachés d'instruccions: a VPP, el processament de paquets segueix un gràfic, els nodes del qual contenen funcions que realitzen la mateixa tasca.

Per exemple, el processament dels paquets IP a VPP es produeix en l'ordre següent: primer, les capçaleres dels paquets s'analitzan al node d'anàlisi i després s'envien al node, que reenvia els paquets segons les taules d'encaminament.

Una mica hardcore. Els autors de VPP no toleren compromisos en l'ús de la memòria cau del processador, de manera que el codi típic per processar un vector de paquets conté vectorització manual: hi ha un bucle de processament en el qual es processa una situació com "tenim quatre paquets a la cua", després el mateix per a dos, després - per a un. Les instruccions d'obtenció prèvia s'utilitzen sovint per carregar dades a la memòria cau per accelerar l'accés a elles en iteracions posteriors.

n_left_from = frame->n_vectors;
while (n_left_from > 0)
{
    vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
    // ...
    while (n_left_from >= 4 && n_left_to_next >= 2)
    {
        // processing multiple packets at once
        u32 next0 = SAMPLE_NEXT_INTERFACE_OUTPUT;
        u32 next1 = SAMPLE_NEXT_INTERFACE_OUTPUT;
        // ...
        /* Prefetch next iteration. */
        {
            vlib_buffer_t *p2, *p3;

            p2 = vlib_get_buffer (vm, from[2]);
            p3 = vlib_get_buffer (vm, from[3]);

            vlib_prefetch_buffer_header (p2, LOAD);
            vlib_prefetch_buffer_header (p3, LOAD);

            CLIB_PREFETCH (p2->data, CLIB_CACHE_LINE_BYTES, STORE);
            CLIB_PREFETCH (p3->data, CLIB_CACHE_LINE_BYTES, STORE);
        }
        // actually process data
        /* verify speculative enqueues, maybe switch current next frame */
        vlib_validate_buffer_enqueue_x2 (vm, node, next_index,
                to_next, n_left_to_next,
                bi0, bi1, next0, next1);
    }

    while (n_left_from > 0 && n_left_to_next > 0)
    {
        // processing packets by one
    }

    // processed batch
    vlib_put_next_frame (vm, node, next_index, n_left_to_next);
}

Així, Healthchecks parlen per IPv6 amb el VPP, que els converteix en IPv4. Això es fa mitjançant un node del gràfic, que anomenem NAT algorítmic. Per al trànsit invers (i la conversió d'IPv6 a IPv4) hi ha el mateix node NAT algorítmic.

Arquitectura de l'equilibri de càrrega de xarxa a Yandex.Cloud

El trànsit directe dels clients de l'equilibrador passa pels nodes gràfics, que realitzen l'equilibri mateix. 

Arquitectura de l'equilibri de càrrega de xarxa a Yandex.Cloud

El primer node són les sessions adhesives. Emmagatzema el hash de 5-tuple per a les sessions establertes. 5-tuple inclou l'adreça i el port del client des del qual es transmet la informació, l'adreça i els ports dels recursos disponibles per rebre trànsit, així com el protocol de xarxa. 

El hash de 5 tuples ens ajuda a realitzar menys càlculs al node hash coherent posterior, així com a gestionar millor els canvis de la llista de recursos darrere de l'equilibrador. Quan un paquet per al qual no hi ha sessió arriba a l'equilibrador, s'envia al node hashing coherent. Aquí és on es produeix l'equilibri mitjançant hashing coherent: seleccionem un recurs de la llista de recursos "en directe" disponibles. A continuació, els paquets s'envien al node NAT, que en realitat substitueix l'adreça de destinació i torna a calcular les sumes de control. Com podeu veure, seguim les regles de VPP: com m'agrada, agrupant càlculs similars per augmentar l'eficiència de la memòria cau del processador.

Hashing coherent

Per què l'hem escollit i què és? Primer, considerem la tasca anterior: seleccionar un recurs de la llista. 

Arquitectura de l'equilibri de càrrega de xarxa a Yandex.Cloud

Amb un hash inconsistent, es calcula el hash del paquet entrant i es selecciona un recurs de la llista per la resta de dividint aquest hash pel nombre de recursos. Mentre la llista es mantingui sense canvis, aquest esquema funciona bé: sempre enviem paquets amb la mateixa tupla 5 a la mateixa instància. Si, per exemple, algun recurs deixa de respondre als controls de salut, l'elecció canviarà per a una part significativa dels hash. Les connexions TCP del client es trencaran: un paquet que abans ha arribat a la instància A pot començar a arribar a la instància B, que no està familiaritzada amb la sessió d'aquest paquet.

El hashing coherent resol el problema descrit. La manera més senzilla d'explicar aquest concepte és aquesta: imagineu-vos que teniu un anell al qual distribuïu recursos mitjançant hash (per exemple, per IP:port). Seleccionar un recurs és girar la roda per un angle, que ve determinat pel hash del paquet.

Arquitectura de l'equilibri de càrrega de xarxa a Yandex.Cloud

Això minimitza la redistribució del trànsit quan canvia la composició dels recursos. La supressió d'un recurs només afectarà la part de l'anell hashing coherent on es trobava el recurs. Afegir un recurs també canvia la distribució, però tenim un node de sessions enganxós, que ens permet no canviar les sessions ja establertes a recursos nous.

Hem observat què passa amb el trànsit dirigit entre l'equilibrador i els recursos. Vegem ara el trànsit de tornada. Segueix el mateix patró que el trànsit de comprovació: mitjançant NAT algorítmic, és a dir, mitjançant NAT 44 invers per al trànsit de client i mitjançant NAT 46 per al trànsit de controls de salut. Ens adherim al nostre propi esquema: unifiquem el trànsit de controls de salut i el trànsit real d'usuaris.

Loadbalancer-node i components muntats

El servei local - loadbalancer-node informa de la composició dels equilibradors i dels recursos a VPP. Es subscriu al flux d'esdeveniments del controlador-equilibrador de càrrega i és capaç de representar la diferència entre l'estat actual del VPP i l'estat objectiu rebut del controlador. Tenim un sistema tancat: els esdeveniments de l'API arriben al controlador de l'equilibrador, que assigna tasques al controlador del control de salut per comprovar la "vivència" dels recursos. Això, al seu torn, assigna tasques al node de comprovació de salut i agrega els resultats, després dels quals els envia de nou al controlador de l'equilibrador. Loadbalancer-node se subscriu als esdeveniments del controlador i canvia l'estat del VPP. En aquest sistema, cada servei només sap el que és necessari sobre els serveis veïns. El nombre de connexions és limitat i tenim la capacitat d'operar i escalar diferents segments de manera independent.

Arquitectura de l'equilibri de càrrega de xarxa a Yandex.Cloud

Quins problemes es van evitar?

Tots els nostres serveis en el pla de control estan escrits en Go i tenen bones característiques d'escalat i fiabilitat. Go té moltes biblioteques de codi obert per construir sistemes distribuïts. Utilitzem de manera activa GRPC, tots els components contenen una implementació de codi obert de descobriment de serveis: els nostres serveis supervisen el rendiment dels altres, poden canviar-ne la composició de manera dinàmica i això ho hem relacionat amb l'equilibri GRPC. Per a mètriques, també fem servir una solució de codi obert. En el pla de dades, vam obtenir un rendiment decent i una gran reserva de recursos: va resultar molt difícil muntar un suport en el qual poguéssim confiar en el rendiment d'un VPP, en lloc d'una targeta de xarxa de ferro.

Problemes i solucions

Què no va funcionar tan bé? Go té una gestió automàtica de la memòria, però encara es produeixen fuites de memòria. La manera més senzilla d'afrontar-los és executar goroutines i recordar-ne d'acabar. Menjar: mireu el consum de memòria dels vostres programes Go. Sovint un bon indicador és el nombre de goroutines. Hi ha un avantatge en aquesta història: a Go és fàcil obtenir dades d'execució: consum de memòria, nombre de goroutines en execució i molts altres paràmetres.

A més, pot ser que Go no sigui la millor opció per a proves funcionals. Són bastant detallats i l'enfocament estàndard d'"executar-ho tot en CI en un lot" no és molt adequat per a ells. El fet és que les proves funcionals requereixen més recursos i provoquen temps d'espera real. Per això, les proves poden fallar perquè la CPU està ocupada amb proves d'unitat. Conclusió: si és possible, realitzeu proves "pesades" per separat de les proves unitàries. 

L'arquitectura d'esdeveniments de microservei és més complexa que un monòlit: recollir registres en desenes de màquines diferents no és molt convenient. Conclusió: si feu microserveis, penseu immediatament en el rastreig.

Els nostres plans

Llançarem un equilibrador intern, un equilibrador IPv6, afegirem suport per als scripts de Kubernetes, continuarem dividint els nostres serveis (actualment només es fragmenten el healthcheck-node i el healthcheck-ctrl), afegirem nous controls de salut i també implementarem l'agregació intel·ligent de comprovacions. Estem considerant la possibilitat de fer els nostres serveis encara més independents, de manera que no es comuniquin directament entre ells, sinó mitjançant una cua de missatges. Recentment ha aparegut un servei compatible amb SQS al núvol Cua de missatges Yandex.

Recentment, va tenir lloc el llançament públic de Yandex Load Balancer. Explora documentació al servei, gestioneu els equilibradors d'una manera convenient per a vosaltres i augmenteu la tolerància a errors dels vostres projectes!

Font: www.habr.com

Afegeix comentari