Sistema operatiu d'un sol núvol: centre de dades a Odnoklassniki

Sistema operatiu d'un sol núvol: centre de dades a Odnoklassniki

Ai, gent! Em dic Oleg Anastasyev, treballo a Odnoklassniki a l'equip de la Plataforma. I a més de mi, hi ha molt de maquinari funcionant a Odnoklassniki. Disposem de quatre centres de dades amb uns 500 bastidors amb més de 8 mil servidors. En un moment determinat, ens vam adonar que la introducció d'un nou sistema de gestió ens permetria carregar equips de manera més eficient, facilitar la gestió d'accés, automatitzar la (re)distribució dels recursos informàtics, accelerar el llançament de nous serveis i accelerar les respostes. a accidents a gran escala.

Què en va sortir?

A més de mi i un munt de maquinari, també hi ha gent que treballa amb aquest maquinari: enginyers que es troben directament als centres de dades; usuaris de xarxa que configuren programari de xarxa; administradors, o SRE, que proporcionen resiliència de la infraestructura; i equips de desenvolupament, cadascun d'ells és responsable d'una part de les funcions del portal. El programari que creen funciona com això:

Sistema operatiu d'un sol núvol: centre de dades a Odnoklassniki

Les sol·licituds dels usuaris es reben tant a les façanes del portal principal www.ok.ru, i en altres, per exemple en els fronts de l'API de música. Per processar la lògica empresarial, criden al servidor d'aplicacions, que, en processar la sol·licitud, crida als microserveis especialitzats necessaris: un gràfic (gràfic de connexions socials), memòria cau d'usuari (caché de perfils d'usuari), etc.

Cadascun d'aquests serveis es desplega en moltes màquines, i cadascun d'ells té desenvolupadors responsables responsables del funcionament dels mòduls, del seu funcionament i del desenvolupament tecnològic. Tots aquests serveis funcionen en servidors de maquinari, i fins fa poc vam llançar exactament una tasca per servidor, és a dir, estava especialitzada per a una tasca concreta.

Per què això? Aquest enfocament tenia diversos avantatges:

  • Alleujat gestió de masses. Suposem que una tasca requereix algunes biblioteques, alguns paràmetres. A continuació, el servidor s'assigna exactament a un grup específic, es descriu la política de cfengine per a aquest grup (o ja s'ha descrit) i aquesta configuració es desplega de manera centralitzada i automàtica a tots els servidors d'aquest grup.
  • Simplificat diagnòstics. Suposem que observeu l'augment de la càrrega del processador central i que us adoneu que aquesta càrrega només es pot generar per la tasca que s'executa en aquest processador de maquinari. La recerca d'algú a qui culpar s'acaba molt ràpidament.
  • Simplificat seguiment. Si alguna cosa no funciona amb el servidor, el monitor ho informa i saps exactament qui té la culpa.

A un servei que consta de diverses rèpliques se li assignen diversos servidors, un per a cadascun. Aleshores, el recurs informàtic per al servei s'assigna de manera molt senzilla: el nombre de servidors que té el servei, la quantitat màxima de recursos que pot consumir. "Fàcil" aquí no vol dir que sigui fàcil d'utilitzar, sinó en el sentit que l'assignació de recursos es fa manualment.

Aquest plantejament també ens va permetre fer configuracions de ferro especialitzades per a una tasca que s'executa en aquest servidor. Si la tasca emmagatzema grans quantitats de dades, utilitzarem un servidor 4U amb un xassís amb 38 discs. Si la tasca és purament computacional, podem comprar un servidor 1U més barat. Això és computacionalment eficient. Entre altres coses, aquest enfocament ens permet utilitzar quatre vegades menys màquines amb una càrrega comparable a una xarxa social amigable.

Aquesta eficiència en l'ús dels recursos informàtics també hauria de garantir l'eficiència econòmica, si partim de la premissa que el més car són els servidors. Durant molt de temps, el maquinari va ser el més car i ens vam esforçar molt a reduir el preu del maquinari, creant algorismes de tolerància a errors per reduir els requisits de fiabilitat del maquinari. I avui hem arribat a l'etapa en què el preu del servidor ha deixat de ser decisiu. Si no teniu en compte els exòtics més recents, no importa la configuració específica dels servidors del bastidor. Ara tenim un altre problema: el preu de l'espai que ocupa el servidor al centre de dades, és a dir, l'espai al bastidor.

En adonar-nos que aquest era el cas, vam decidir calcular amb quina eficàcia estàvem utilitzant els bastidors.
Vam agafar el preu del servidor més potent dels que es justificaven econòmicament, vam calcular quants servidors d'aquest tipus podríem col·locar als bastidors, quantes tasques hi executaríem segons el model antic "un servidor = una tasca" i quant les tasques podrien utilitzar l'equip. Van comptar i vessar llàgrimes. Va resultar que la nostra eficiència en l'ús de bastidors és d'un 11%. La conclusió és òbvia: hem d'augmentar l'eficiència de l'ús dels centres de dades. Sembla que la solució és òbvia: cal executar diverses tasques en un servidor alhora. Però aquí és on comencen les dificultats.

La configuració massiva es fa molt més complicada: ara és impossible assignar cap grup a un servidor. Després de tot, ara es poden llançar diverses tasques de diferents ordres en un servidor. A més, la configuració pot ser conflictiva per a diferents aplicacions. El diagnòstic també es fa més complicat: si veieu un augment del consum de CPU o disc en un servidor, no sabeu quina tasca està causant problemes.

Però el més important és que no hi ha cap aïllament entre les tasques que s'executen a la mateixa màquina. Aquí, per exemple, hi ha un gràfic del temps de resposta mitjà d'una tasca del servidor abans i després que s'hagi llançat una altra aplicació computacional al mateix servidor, de cap manera relacionada amb la primera: el temps de resposta de la tasca principal ha augmentat significativament.

Sistema operatiu d'un sol núvol: centre de dades a Odnoklassniki

Òbviament, cal executar tasques en contenidors o en màquines virtuals. Com que gairebé totes les nostres tasques funcionen amb un sistema operatiu (Linux) o estan adaptades per a aquest, no necessitem suportar molts sistemes operatius diferents. En conseqüència, la virtualització no és necessària; a causa de la sobrecàrrega addicional, serà menys eficient que la contenidorització.

Com a implementació de contenidors per executar tasques directament als servidors, Docker és un bon candidat: les imatges del sistema de fitxers resolen bé els problemes amb configuracions conflictives. El fet que les imatges puguin estar compostes per diverses capes ens permet reduir significativament la quantitat de dades necessàries per desplegar-les a la infraestructura, separant les parts comunes en capes base separades. Aleshores, les capes bàsiques (i més voluminoses) s'emmagatzemaran a la memòria cau amb força rapidesa a tota la infraestructura, i per oferir molts tipus diferents d'aplicacions i versions, només caldrà transferir capes petites.

A més, un registre ja fet i un etiquetatge d'imatges a Docker ens ofereixen primitives preparades per a versions i lliurament de codi a producció.

Docker, com qualsevol altra tecnologia similar, ens proporciona un cert nivell d'aïllament dels contenidors fora de la caixa. Per exemple, aïllament de memòria: cada contenidor té un límit d'ús de la memòria de la màquina, més enllà del qual no es consumirà. També podeu aïllar contenidors segons l'ús de la CPU. Per a nosaltres, però, l'aïllament estàndard no era suficient. Però més sobre això a continuació.

L'execució directa de contenidors als servidors és només una part del problema. L'altra part està relacionada amb l'allotjament de contenidors als servidors. Heu d'entendre quin contenidor es pot col·locar en quin servidor. No és una tasca tan fàcil, perquè els contenidors s'han de col·locar als servidors amb la màxima densitat possible sense reduir-ne la velocitat. Aquesta col·locació també pot ser difícil des del punt de vista de la tolerància a fallades. Sovint volem col·locar rèpliques d'un mateix servei en diferents bastidors o fins i tot en diferents sales del centre de dades, de manera que si un bastidor o sala falla, no perdem immediatament totes les rèpliques del servei.

Distribuir contenidors manualment no és una opció quan tens 8 mil servidors i 8-16 mil contenidors.

A més, volíem donar als desenvolupadors més independència en l'assignació de recursos perquè ells mateixos poguessin allotjar els seus serveis en producció, sense l'ajuda d'un administrador. Al mateix temps, volíem mantenir el control perquè algun servei menor no consumís tots els recursos dels nostres centres de dades.

Òbviament, necessitem una capa de control que ho faci automàticament.

Així que vam arribar a una imatge senzilla i entenedora que tots els arquitectes encanten: tres quadrats.

Sistema operatiu d'un sol núvol: centre de dades a Odnoklassniki

one-cloud masters és un clúster de failover responsable de l'orquestració del núvol. El desenvolupador envia un manifest al mestre, que conté tota la informació necessària per allotjar el servei. En base a això, el mestre dóna ordres als minions seleccionats (màquines dissenyades per executar contenidors). Els minions tenen el nostre agent, que rep l'ordre, emet les seves ordres a Docker i Docker configura el nucli de Linux per llançar el contenidor corresponent. A més d'executar ordres, l'agent informa contínuament al mestre sobre els canvis en l'estat tant de la màquina minion com dels contenidors que s'hi executen.

Assignació de recursos

Vegem ara el problema de l'assignació de recursos més complexa per a molts minions.

Un recurs informàtic en un núvol és:

  • La quantitat de potència del processador consumida per una tasca específica.
  • La quantitat de memòria disponible per a la tasca.
  • Trànsit de xarxa. Cadascun dels minions té una interfície de xarxa específica amb ample de banda limitat, per la qual cosa és impossible distribuir les tasques sense tenir en compte la quantitat de dades que transmeten per la xarxa.
  • Discos. A més, òbviament, a l'espai per a aquestes tasques, també hi assignem el tipus de disc: HDD o SSD. Els discs poden servir un nombre finit de peticions per segon: IOPS. Per tant, per a les tasques que generen més IOPS del que pot gestionar un sol disc, també assignem "eixos", és a dir, dispositius de disc que s'han de reservar exclusivament per a la tasca.

Aleshores, per a algun servei, per exemple per a la memòria cau d'usuari, podem registrar els recursos consumits d'aquesta manera: 400 nuclis de processador, 2,5 TB de memòria, 50 Gbit/s de trànsit en ambdues direccions, 6 TB d'espai HDD situat en 100 eixos. O d'una forma més familiar com aquesta:

alloc:
    cpu: 400
    mem: 2500
    lan_in: 50g
    lan_out: 50g
    hdd:100x6T

Els recursos del servei de memòria cau d'usuari només consumeixen una part de tots els recursos disponibles a la infraestructura de producció. Per tant, vull assegurar-me que de sobte, a causa d'un error de l'operador o no, la memòria cau d'usuari no consumeix més recursos dels que se li assignen. És a dir, hem de limitar els recursos. Però a què podríem lligar la quota?

Tornem al nostre diagrama molt simplificat de com interactuen els components i redibuixem-lo amb més detalls, com aquest:

Sistema operatiu d'un sol núvol: centre de dades a Odnoklassniki

Què et crida l'atenció:

  • La interfície web i la música utilitzen clústers aïllats del mateix servidor d'aplicacions.
  • Podem distingir les capes lògiques a les quals pertanyen aquests clústers: fronts, cachés, emmagatzematge de dades i capa de gestió.
  • El frontend és heterogeni; consta de diferents subsistemes funcionals.
  • Les memòria cau també es poden dispersar pel subsistema les dades del qual emmagatzemen a la memòria cau.

Tornem a dibuixar la imatge:

Sistema operatiu d'un sol núvol: centre de dades a Odnoklassniki

Bah! Sí, veiem una jerarquia! Això vol dir que podeu distribuir els recursos en fragments més grans: assigneu un desenvolupador responsable a un node d'aquesta jerarquia corresponent al subsistema funcional (com "música" a la imatge) i adjunteu una quota al mateix nivell de la jerarquia. Aquesta jerarquia també ens permet organitzar els serveis de manera més flexible per facilitar la gestió. Per exemple, dividim tota la web, ja que es tracta d'una agrupació molt gran de servidors, en diversos grups més petits, que es mostren a la imatge com a grup1, grup2.

En eliminar les línies addicionals, podem escriure cada node de la nostra imatge en una forma més plana: grup1.web.front, api.music.front, user-cache.cache.

Així és com arribem al concepte de "cua jeràrquica". Té un nom com "group1.web.front". Se li assigna una quota de recursos i drets d'usuari. Donarem a la persona de DevOps els drets per enviar un servei a la cua, i aquest empleat pot llançar alguna cosa a la cua, i la persona d'OpsDev tindrà drets d'administrador, i ara pot gestionar la cua, assignar-hi persones, concedir drets a aquestes persones, etc. Els serveis que s'executen en aquesta cua s'executaran dins de la quota de la cua. Si la quota de càlcul de la cua no és suficient per executar tots els serveis alhora, s'executaran seqüencialment, formant així la pròpia cua.

Mirem més de prop els serveis. Un servei té un nom totalment qualificat, que sempre inclou el nom de la cua. Aleshores, el servei web frontal tindrà el nom ok-web.group1.web.front. I es trucarà el servei de servidor d'aplicacions al qual accedeix ok-app.group1.web.front. Cada servei té un manifest, que especifica tota la informació necessària per a la col·locació en màquines específiques: quants recursos consumeix aquesta tasca, quina configuració es necessita per a ella, quantes rèpliques hi hauria d'haver, propietats per gestionar els errors d'aquest servei. I després que el servei es col·loqui directament a les màquines, apareixen les seves instàncies. També s'anomenen sense ambigüitats, com el número d'instància i el nom del servei: 1.ok-web.group1.web.front, 2.ok-web.group1.web.front, …

Això és molt convenient: mirant només el nom del contenidor en funcionament, immediatament podem esbrinar moltes coses.

Ara fem una ullada més de prop al que realitzen aquestes instàncies: tasques.

Classes d'aïllament de tasques

Totes les tasques a OK (i probablement a tot arreu) es poden dividir en grups:

  • Tasques de latència curta - prod. Per a aquestes tasques i serveis, el retard de resposta (latència) és molt important, la rapidesa amb què el sistema processarà cadascuna de les sol·licituds. Exemples de tasques: fronts web, memòria cau, servidors d'aplicacions, emmagatzematge OLTP, etc.
  • Problemes de càlcul - lot. Aquí, la velocitat de processament de cada sol·licitud específica no és important. Per a ells, és important quants càlculs farà aquesta tasca en un període de temps (llarg) determinat. Aquestes seran qualsevol tasca de MapReduce, Hadoop, aprenentatge automàtic, estadístiques.
  • Tasques de fons: inactiu. Per a aquestes tasques, ni la latència ni el rendiment són molt importants. Això inclou diverses proves, migracions, recàlculs i conversió de dades d'un format a un altre. D'una banda, són semblants a les calculades, d'altra banda, no ens importa molt la rapidesa amb què es completen.

Vegem com aquestes tasques consumeixen recursos, per exemple, el processador central.

Tasques de curt retard. Aquesta tasca tindrà un patró de consum de CPU similar a aquest:

Sistema operatiu d'un sol núvol: centre de dades a Odnoklassniki

Es rep una sol·licitud de l'usuari per processar-la, la tasca comença a utilitzar tots els nuclis de CPU disponibles, la processa, retorna una resposta, espera la següent sol·licitud i s'atura. Va arribar la següent sol·licitud: de nou vam triar tot el que hi havia, ho vam calcular i estem esperant la següent.

Per garantir la latència mínima per a una tasca d'aquest tipus, hem d'agafar els màxims recursos que consumeix i reservar el nombre de nuclis necessaris al minion (la màquina que executarà la tasca). Aleshores, la fórmula de reserva per al nostre problema serà la següent:

alloc: cpu = 4 (max)

i si tenim una màquina minion amb 16 nuclis, es poden col·locar-hi exactament quatre tasques. Observem especialment que el consum mitjà de processador d'aquestes tasques sovint és molt baix, cosa que és evident, ja que una part important del temps la tasca espera una sol·licitud i no fa res.

Tasques de càlcul. El seu patró serà lleugerament diferent:

Sistema operatiu d'un sol núvol: centre de dades a Odnoklassniki

El consum mitjà de recursos de CPU per a aquestes tasques és força elevat. Sovint volem que una tasca de càlcul es completi en un període de temps determinat, per la qual cosa hem de reservar el nombre mínim de processadors que necessita perquè tot el càlcul es completi en un temps acceptable. La seva fórmula de reserva serà així:

alloc: cpu = [1,*)

"Si us plau, col·loqueu-lo en un esbirro on hi hagi almenys un nucli lliure, i tants com n'hi hagi, ho devorarà tot".

Aquí l'eficiència d'ús ja és molt millor que en tasques amb un curt retard. Però el guany serà molt més gran si combineu ambdós tipus de tasques en una màquina de minions i distribuïu els seus recursos sobre la marxa. Quan una tasca amb un curt retard requereix un processador, aquest el rep immediatament i, quan els recursos ja no són necessaris, es transfereixen a la tasca computacional, és a dir, una cosa així:

Sistema operatiu d'un sol núvol: centre de dades a Odnoklassniki

Però, com fer-ho?

Primer, mirem el prod i el seu alloc: CPU = 4. Hem de reservar quatre nuclis. A l'execució de Docker, això es pot fer de dues maneres:

  • Utilitzant l'opció --cpuset=1-4, és a dir, assigneu quatre nuclis específics de la màquina a la tasca.
  • Utilitzeu --cpuquota=400_000 --cpuperiod=100_000, assigneu una quota de temps de processador, és a dir, indiqueu que cada 100 ms de temps real la tasca no consumeix més de 400 ms de temps de processador. S'obtenen els mateixos quatre nuclis.

Però quin d'aquests mètodes és adequat?

cpuset sembla força atractiu. La tasca té quatre nuclis dedicats, la qual cosa significa que la memòria cau del processador funcionarà de la manera més eficient possible. Això també té un inconvenient: hauríem d'assumir la tasca de distribuir els càlculs entre els nuclis descarregats de la màquina en comptes del sistema operatiu, i aquesta és una tasca no trivial, sobretot si intentem col·locar tasques per lots en aquest tipus de tasques. màquina. Les proves han demostrat que l'opció amb quota és més adequada aquí: d'aquesta manera el sistema operatiu té més llibertat a l'hora d'escollir el nucli per realitzar la tasca en el moment actual i el temps del processador es distribueix de manera més eficient.

Anem a descobrir com fer reserves a Docker en funció del nombre mínim de nuclis. La quota de tasques per lots ja no és aplicable, perquè no cal limitar el màxim, n'hi ha prou amb garantir el mínim. I aquí l'opció encaixa bé docker run --cpushares.

Vam acordar que si un lot requereix una garantia per almenys un nucli, llavors ho indiquem --cpushares=1024, i si hi ha almenys dos nuclis, llavors ho indiquem --cpushares=2048. Les accions de CPU no interfereixen de cap manera amb la distribució del temps del processador sempre que n'hi hagi prou. Per tant, si prod no utilitza actualment els seus quatre nuclis, no hi ha res que limiti les tasques per lots i poden utilitzar temps addicional del processador. Però en una situació en què hi ha escassetat de processadors, si prod ha consumit els quatre nuclis i ha arribat a la seva quota, el temps restant del processador es dividirà proporcionalment a cpushares, és a dir, en una situació de tres nuclis lliures, un serà donat a una tasca amb 1024 cpushares, i els dos restants es donaran a una tasca amb 2048 cpushares.

Però utilitzar quotes i accions no és suficient. Hem d'assegurar-nos que una tasca amb un curt retard tingui prioritat sobre una tasca per lots a l'hora d'assignar el temps del processador. Sense aquesta priorització, la tasca per lots ocuparà tot el temps del processador en el moment en què ho necessiti el producte. No hi ha opcions de priorització de contenidors a l'execució de Docker, però les polítiques de planificació de CPU de Linux són útils. Podeu llegir-los en detall aquí, i en el marc d'aquest article els repassarem breument:

  • SCHED_OTHER
    Per defecte, tots els processos d'usuari normals d'una màquina Linux reben.
  • SCHED_BATCH
    Dissenyat per a processos que consumeixen molts recursos. Quan es col·loca una tasca en un processador, s'introdueix l'anomenada penalització d'activació: és menys probable que aquesta tasca rebi recursos del processador si actualment l'està utilitzant una tasca amb SCHED_OTHER
  • SCHED_IDLE
    Un procés de fons amb una prioritat molt baixa, fins i tot inferior a la agradable -19. Utilitzem la nostra biblioteca de codi obert un-nio, per tal d'establir la política necessària en iniciar el contenidor trucant

one.nio.os.Proc.sched_setscheduler( pid, Proc.SCHED_IDLE )

Però fins i tot si no programeu en Java, el mateix es pot fer amb l'ordre chrt:

chrt -i 0 $pid

Resumim tots els nostres nivells d'aïllament en una taula per a més claredat:

Classe d'aïllament
Exemple d'alloc
Opcions d'execució de Docker
sched_setscheduler chrt*

Punxar
CPU = 4
--cpuquota=400000 --cpuperiod=100000
SCHED_OTHER

Lot
CPU = [1, *)
--cpushares=1024
SCHED_BATCH

ociós
CPU= [2, *)
--cpushares=2048
SCHED_IDLE

*Si feu chrt des d'un contenidor, és possible que necessiteu la capacitat sys_nice, perquè de manera predeterminada, Docker elimina aquesta capacitat quan s'inicia el contenidor.

Però les tasques no només consumeixen el processador, sinó també el trànsit, la qual cosa afecta la latència d'una tasca de xarxa encara més que l'assignació incorrecta dels recursos del processador. Per tant, naturalment volem obtenir exactament la mateixa imatge per al trànsit. És a dir, quan una tasca de producte envia alguns paquets a la xarxa, limitem la velocitat màxima (fórmula alloc: lan=[*,500mbps) ), amb el qual prod pot fer-ho. I per lots garantim només el rendiment mínim, però no limitem el màxim (fórmula alloc: lan=[10 Mbps,*) ) En aquest cas, el trànsit de productes hauria de tenir prioritat sobre les tasques per lots.
Aquí Docker no té cap element primitiu que puguem utilitzar. Però ens ve en ajuda Control de trànsit de Linux. Hem pogut aconseguir el resultat desitjat amb l'ajuda de la disciplina Corba de servei justa jeràrquica. Amb la seva ajuda, distingim dues classes de trànsit: producte d'alta prioritat i lot/inactivitat de baixa prioritat. Com a resultat, la configuració per al trànsit de sortida és així:

Sistema operatiu d'un sol núvol: centre de dades a Odnoklassniki

aquí 1:0 és el "qdisc arrel" de la disciplina hsfc; 1:1: classe secundària hsfc amb un límit d'amplada de banda total de 8 Gbit/s, sota el qual es col·loquen les classes secundàries de tots els contenidors; 1:2: la classe secundària hsfc és comuna a totes les tasques per lots i inactives amb un límit "dinàmic", que es comenta a continuació. La resta de classes secundàries hsfc són classes dedicades per als contenidors de productes que s'executen actualment amb límits corresponents als seus manifests: 450 i 400 Mbit/s. A cada classe hsfc se li assigna una cua de qdisc fq o fq_codel, depenent de la versió del nucli de Linux, per evitar la pèrdua de paquets durant les ràfegues de trànsit.

Normalment, les disciplines tc serveixen per prioritzar només el trànsit de sortida. Però també volem prioritzar el trànsit entrant; després de tot, alguna tasca per lots pot seleccionar fàcilment tot el canal d'entrada, rebent, per exemple, un gran lot de dades d'entrada per a map&reduce. Per a això fem servir el mòdul ifb, que crea una interfície virtual ifbX per a cada interfície de xarxa i redirigeix ​​el trànsit entrant des de la interfície al trànsit sortint a ifbX. A més, per a ifbX, totes les mateixes disciplines funcionen per controlar el trànsit de sortida, per al qual la configuració de hsfc serà molt similar:

Sistema operatiu d'un sol núvol: centre de dades a Odnoklassniki

Durant els experiments, vam descobrir que hsfc mostra els millors resultats quan la classe 1:2 de trànsit per lots/inactius no prioritaris es limita a les màquines minion a no més d'un carril lliure determinat. En cas contrari, el trànsit no prioritari té massa impacte en la latència de les tasques de producte. miniond determina la quantitat actual d'ample de banda lliure cada segon, mesurant el consum mitjà de trànsit de totes les tasques de producció d'un minion determinat. Sistema operatiu d'un sol núvol: centre de dades a Odnoklassniki i restant-lo de l'ample de banda de la interfície de xarxa Sistema operatiu d'un sol núvol: centre de dades a Odnoklassniki amb un petit marge, és a dir.

Sistema operatiu d'un sol núvol: centre de dades a Odnoklassniki

Les bandes es defineixen de manera independent per al trànsit entrant i sortint. I segons els nous valors, miniond reconfigura el límit de classe no prioritària 1:2.

Així, vam implementar les tres classes d'aïllament: prod, batch i idle. Aquestes classes influeixen molt en les característiques de rendiment de les tasques. Per tant, vam decidir col·locar aquest atribut a la part superior de la jerarquia, de manera que en mirar el nom de la cua jeràrquica quedés immediatament clar de què estem tractant:

Sistema operatiu d'un sol núvol: centre de dades a Odnoklassniki

Tots els nostres amics web и música els fronts es col·loquen després a la jerarquia sota prod. Per exemple, a batch, col·loquem el servei catàleg de música, que recopila periòdicament un catàleg de cançons a partir d'un conjunt de fitxers mp3 penjats a Odnoklassniki. Un exemple de servei en inactiu seria transformador de música, que normalitza el nivell de volum de la música.

Amb les línies addicionals eliminades de nou, podem escriure els nostres noms de servei de manera més plana afegint la classe d'aïllament de tasques al final del nom complet del servei: web.front.prod, catàleg.música.lot, transformador.música.inactiva.

I ara, mirant el nom del servei, entenem no només quina funció fa, sinó també la seva classe d'aïllament, que significa la seva criticitat, etc.

Tot és genial, però hi ha una veritat amarga. És impossible aïllar completament les tasques que s'executen en una màquina.

El que hem aconseguit aconseguir: si el lot consumeix de manera intensiva només recursos de la CPU, llavors el planificador de CPU de Linux integrat fa la seva feina molt bé i pràcticament no hi ha cap impacte en la tasca de producte. Però si aquesta tasca per lots comença a funcionar activament amb la memòria, ja apareix la influència mútua. Això passa perquè la tasca de producte s'"esborra" de les memòria cau del processador; com a resultat, les falles de memòria cau augmenten i el processador processa la tasca de producte més lentament. Aquesta tasca per lots pot augmentar la latència del nostre contenidor de productes típics en un 10%.

Aïllar el trànsit és encara més difícil a causa del fet que les targetes de xarxa modernes tenen una cua interna de paquets. Si el paquet de la tasca per lots arriba primer, serà el primer que es transmetrà per cable i no es pot fer res.

A més, fins ara només hem aconseguit resoldre el problema de prioritzar el trànsit TCP: l'enfocament hsfc no funciona per a UDP. I fins i tot en el cas del trànsit TCP, si la tasca per lots genera molt de trànsit, això també augmenta al voltant d'un 10% en el retard de la tasca de producte.

falta de tolerància

Un dels objectius a l'hora de desenvolupar un sol núvol era millorar la tolerància a fallades d'Odnoklassniki. Per tant, a continuació m'agradaria considerar amb més detall possibles escenaris de fallades i accidents. Comencem amb un escenari senzill: una fallada del contenidor.

El propi contenidor pot fallar de diverses maneres. Pot ser algun tipus d'experiment, error o error al manifest, a causa del qual la tasca de producte comença a consumir més recursos dels que s'indica al manifest. Vam tenir un cas: un desenvolupador va implementar un algorisme complex, el va reelaborar moltes vegades, es va pensar i es va confondre tant que finalment el problema va entrar en un bucle molt no trivial. I com que la tasca de producte té una prioritat més alta que totes les altres en els mateixos minions, va començar a consumir tots els recursos disponibles del processador. En aquesta situació, l'aïllament, o més aviat la quota de temps de la CPU, va salvar el dia. Si a una tasca se li assigna una quota, la tasca no consumirà més. Per tant, les tasques per lots i altres productes que s'executaven a la mateixa màquina no van notar res.

El segon problema possible és la caiguda del contenidor. I aquí les polítiques de reinici ens salven, tothom les coneix, el propi Docker fa un gran treball. Gairebé totes les tasques de producte tenen una política de reinici sempre. De vegades fem servir on_failure per a tasques per lots o per depurar contenidors de productes.

Què pots fer si un esbirro sencer no està disponible?

Òbviament, executeu el contenidor en una altra màquina. La part interessant aquí és què passa amb les adreces IP assignades al contenidor.

Podem assignar als contenidors les mateixes adreces IP que les màquines minions on funcionen aquests contenidors. Aleshores, quan el contenidor s'inicia en una altra màquina, la seva adreça IP canvia i tots els clients han d'entendre que el contenidor s'ha mogut i ara han d'anar a una adreça diferent, que requereix un servei de descoberta de serveis independent.

El descobriment del servei és convenient. Hi ha moltes solucions al mercat de diferents graus de tolerància a errors per organitzar un registre de serveis. Sovint, aquestes solucions implementen la lògica d'equilibrador de càrrega, emmagatzemen una configuració addicional en forma d'emmagatzematge KV, etc.
No obstant això, voldríem evitar la necessitat d'implementar un registre separat, perquè això suposaria introduir un sistema crític que sigui utilitzat per tots els serveis en producció. Això vol dir que es tracta d'un punt de fallada potencial i cal triar o desenvolupar una solució molt tolerant a errors, que òbviament és molt difícil, requereix molt de temps i és cara.

I un gran inconvenient més: perquè la nostra antiga infraestructura funcioni amb la nova, hauríem de reescriure absolutament totes les tasques per utilitzar algun tipus de sistema de descoberta de serveis. Hi ha MOLTA feina i, en alguns llocs, és gairebé impossible quan es tracta de dispositius de baix nivell que funcionen al nivell del nucli del sistema operatiu o directament amb el maquinari. Implementació d'aquesta funcionalitat mitjançant patrons de solució establerts, com ara sidecar significaria en alguns llocs una càrrega addicional, en altres, una complicació de l'operació i escenaris de fallada addicionals. No volíem complicar les coses, així que vam decidir que l'ús de Service Discovery fos opcional.

En un núvol, la IP segueix el contenidor, és a dir, cada instància de tasca té la seva pròpia adreça IP. Aquesta adreça és "estàtica": s'assigna a cada instància quan el servei s'envia per primera vegada al núvol. Si un servei ha tingut un nombre diferent d'instàncies durant la seva vida, al final se li assignaran tantes adreces IP com màxim d'instàncies hi hagi.

Posteriorment, aquestes adreces no canvien: s'assignen una vegada i continuen existint durant tota la vida del servei en producció. Les adreces IP segueixen els contenidors a tota la xarxa. Si el contenidor es transfereix a un altre esbirro, l'adreça el seguirà.

Per tant, l'assignació d'un nom de servei a la seva llista d'adreces IP canvia molt poques vegades. Si torneu a mirar els noms de les instàncies de servei que hem esmentat al principi de l'article (1.ok-web.group1.web.front.prod, 2.ok-web.group1.web.front.prod, …), ens adonarem que s'assemblen als FQDN utilitzats al DNS. És correcte, per assignar els noms de les instàncies de servei a les seves adreces IP, utilitzem el protocol DNS. A més, aquest DNS retorna totes les adreces IP reservades de tots els contenidors, tant en execució com aturats (diguem que s'utilitzen tres rèpliques i hi tenim cinc adreces reservades; es retornaran les cinc). Els clients, després d'haver rebut aquesta informació, intentaran establir una connexió amb les cinc rèpliques, i així determinar quines funcionen. Aquesta opció per determinar la disponibilitat és molt més fiable; no implica ni DNS ni Service Discovery, la qual cosa significa que no hi ha problemes difícils de resoldre per garantir la rellevància de la informació i la tolerància a errors d'aquests sistemes. A més, en els serveis crítics dels quals depèn el funcionament de tot el portal, no podem utilitzar gens DNS, sinó simplement introduir adreces IP a la configuració.

Implementar aquesta transferència d'IP darrere dels contenidors pot ser no trivial, i veurem com funciona amb l'exemple següent:

Sistema operatiu d'un sol núvol: centre de dades a Odnoklassniki

Suposem que el mestre d'un núvol dóna l'ordre al minion M1 perquè s'executi 1.ok-web.group1.web.front.prod amb l'adreça 1.1.1.1. Treballa en un esbirro NAT, que anuncia aquesta adreça a servidors especials reflector de ruta. Aquests últims tenen una sessió BGP amb el maquinari de xarxa, a la qual es tradueix la ruta de l'adreça 1.1.1.1 a M1. M1 encamina els paquets dins del contenidor mitjançant Linux. Hi ha tres servidors reflectors de ruta, ja que aquesta és una part molt crítica de la infraestructura d'un núvol; sense ells, la xarxa d'un núvol no funcionarà. Els col·loquem en bastidors diferents, si és possible situats en diferents sales del centre de dades, per reduir la probabilitat que els tres fallin alhora.

Suposem ara que es perd la connexió entre el mestre d'un núvol i el minion M1. El mestre d'un núvol actuarà ara en el supòsit que M1 ha fallat completament. És a dir, donarà l'ordre al minion M2 perquè es llanci web.group1.web.front.prod amb la mateixa adreça 1.1.1.1. Ara tenim dues rutes conflictives a la xarxa per a 1.1.1.1: a M1 i a M2. Per resoldre aquests conflictes, utilitzem el Discriminador de sortides múltiples, que s'especifica a l'anunci de BGP. Aquest és un número que mostra el pes de la ruta anunciada. Entre les rutes en conflicte, se seleccionarà la ruta amb el valor MED més baix. El mestre d'un núvol admet MED com a part integral de les adreces IP del contenidor. Per primera vegada, l'adreça s'escriu amb un MED prou gran = 1. En la situació d'aquesta transferència de contenidor d'emergència, el mestre redueix el MED i M000 ja rebrà l'ordre d'anunciar l'adreça 000 amb MED = 2 1.1.1.1. La instància que s'executa a l'M999 es mantindrà en aquest cas, no hi ha connexió, i el seu destí posterior ens interessa poc fins que no es restableixi la connexió amb el mestre, quan s'aturarà com una antiga presa.

accidents

Tots els sistemes de gestió del centre de dades sempre gestionen les fallades menors de manera acceptable. El desbordament dels contenidors és la norma gairebé a tot arreu.

Vegem com gestionem una emergència, com ara una fallada d'alimentació en una o més sales d'un centre de dades.

Què significa un accident per a un sistema de gestió de centres de dades? En primer lloc, es tracta d'una fallada puntual massiva de moltes màquines i el sistema de control ha de migrar molts contenidors alhora. Però si el desastre és a gran escala, pot passar que totes les tasques no es puguin reassignar a altres minions, perquè la capacitat de recursos del centre de dades cau per sota del 100% de la càrrega.

Sovint, els accidents van acompanyats d'una fallada de la capa de control. Això pot passar a causa de la fallada dels seus equips, però més sovint perquè no es posen a prova els accidents i la capa de control cau a causa de l'augment de la càrrega.

Què pots fer amb tot això?

Les migracions massives signifiquen que hi ha un gran nombre d'activitats, migracions i desplegaments a la infraestructura. Cadascuna de les migracions pot trigar el temps necessari per lliurar i desempaquetar imatges de contenidors als minions, llançar i inicialitzar contenidors, etc. Per tant, és desitjable que les tasques més importants es llancin abans que les menys importants.

Mirem de nou la jerarquia de serveis que coneixem i intentem decidir quines tasques volem executar primer.

Sistema operatiu d'un sol núvol: centre de dades a Odnoklassniki

Per descomptat, aquests són els processos que estan directament implicats en el tractament de les sol·licituds dels usuaris, és a dir, prod. Ho indiquem amb prioritat de col·locació — un número que es pot assignar a la cua. Si una cua té una prioritat més alta, els seus serveis es col·loquen primer.

A prod assignem prioritats més altes, 0; per lot - una mica més baix, 100; en inactiu, encara més baix, 200. Les prioritats s'apliquen jeràrquicament. Totes les tasques inferiors a la jerarquia tindran una prioritat corresponent. Si volem que les memòries cau a prod s'iniciïn abans de les interfícies, assignem prioritats a la memòria cau = 0 i a les subcues frontals = 1. Si, per exemple, volem que el portal principal s'iniciï primer des dels fronts, i només el frontal de la música. llavors, podem assignar una prioritat més baixa a aquest últim: 10.

El següent problema és la manca de recursos. Així doncs, una gran quantitat d'equips, sales senceres del centre de dades, van fallar i vam rellançar tants serveis que ara no hi ha prou recursos per a tothom. Heu de decidir quines tasques sacrificar per mantenir els principals serveis crítics en funcionament.

Sistema operatiu d'un sol núvol: centre de dades a Odnoklassniki

A diferència de la prioritat d'ubicació, no podem sacrificar indistintament totes les tasques per lots; algunes d'elles són importants per al funcionament del portal. Per tant, hem destacat per separat prioritat de preempció tasques. Quan es col·loca, una tasca de prioritat més alta pot anticipar, és a dir, aturar, una tasca de prioritat més baixa si no hi ha més súbditos lliures. En aquest cas, una tasca amb una prioritat baixa probablement romandrà sense col·locar, és a dir, ja no hi haurà un esbirro adequat amb prou recursos gratuïts.

A la nostra jerarquia, és molt senzill especificar una prioritat de preempció de manera que les tasques de prod i batch anticipin o aturen les tasques inactives, però no entre elles, especificant una prioritat per a inactiva igual a 200. Igual que en el cas de la prioritat de col·locació, podem utilitzar la nostra jerarquia per descriure regles més complexes. Per exemple, indiquem que sacrifiquem la funció de música si no disposem de recursos suficients per al portal web principal, establint la prioritat dels nodes corresponents més baixa: 10.

Accidents sencers de DC

Per què pot fallar tot el centre de dades? Element. Va ser un bon post l'huracà va afectar el treball del centre de dades. Els elements es poden considerar persones sense llar que una vegada van cremar l'òptica del col·lector i el centre de dades va perdre completament el contacte amb altres llocs. La causa de la fallada també pot ser un factor humà: l'operador emetrà una ordre que caurà tot el centre de dades. Això pot passar a causa d'un gran error. En general, el col·lapse dels centres de dades no és estrany. Això ens passa un cop cada pocs mesos.

I això és el que fem per evitar que ningú tuitei #viu.

La primera estratègia és l'aïllament. Cada instància d'un núvol està aïllada i només pot gestionar màquines en un centre de dades. És a dir, la pèrdua d'un núvol a causa d'errors o ordres incorrectes de l'operador és la pèrdua d'un sol centre de dades. Estem preparats per això: tenim una política de redundància en la qual les rèpliques de l'aplicació i les dades es troben a tots els centres de dades. Utilitzem bases de dades tolerants a errors i provem periòdicament per detectar errors.
Com que avui tenim quatre centres de dades, això significa quatre instàncies separades i completament aïllades d'un sol núvol.

Aquest enfocament no només protegeix contra fallades físiques, sinó que també pot protegir contra errors de l'operador.

Què més es pot fer amb el factor humà? Quan un operador dóna al núvol alguna ordre estranya o potencialment perillosa, de sobte se li pot demanar que solucioni un petit problema per veure com ho va pensar. Per exemple, si es tracta d'una mena d'aturada massiva de moltes rèpliques o només d'una ordre estranya, reduir el nombre de rèpliques o canviar el nom de la imatge, i no només el número de versió del nou manifest.

Sistema operatiu d'un sol núvol: centre de dades a Odnoklassniki

Resultats de

Característiques distintives d'un núvol:

  • Esquema de denominació jeràrquica i visual de serveis i contenidors, que permet conèixer molt ràpidament quina és la tasca, amb què es relaciona i com funciona i qui n'és el responsable.
  • Apliquem el nostre tècnica de combinació de productes i lotstasques als minions per millorar l'eficiència de compartir màquines. En lloc de cpuset, fem servir quotes de CPU, accions, polítiques de planificador de CPU i QoS de Linux.
  • No va ser possible aïllar completament els contenidors que funcionaven a la mateixa màquina, però la seva influència mútua es manté dins del 20%.
  • L'organització dels serveis en una jerarquia ajuda a utilitzar la recuperació automàtica en cas de desastre prioritats de col·locació i preempció.

FAQ

Per què no vam prendre una solució ja feta?

  • Les diferents classes d'aïllament de tasques requereixen una lògica diferent quan es col·loquen als minions. Si les tasques de producte es poden col·locar simplement reservant recursos, s'han de col·locar tasques per lots i inactives, fent un seguiment de la utilització real dels recursos a les màquines minions.
  • La necessitat de tenir en compte els recursos consumits per les tasques, com ara:
    • amplada de banda de xarxa;
    • tipus i "eixos" de discs.
  • La necessitat d'indicar les prioritats dels serveis durant la resposta d'emergència, els drets i les quotes d'ordres per als recursos, que es resol mitjançant cues jeràrquiques en un sol núvol.
  • La necessitat de disposar d'una denominació humana dels contenidors per reduir el temps de resposta davant d'accidents i incidents
  • La impossibilitat d'una implementació generalitzada única de Service Discovery; la necessitat de conviure durant molt de temps amb tasques allotjades en amfitrions de maquinari, cosa que es resol amb adreces IP "estàtiques" després de contenidors i, com a conseqüència, la necessitat d'una integració única amb una gran infraestructura de xarxa.

Totes aquestes funcions requeririen modificacions importants de les solucions existents per adaptar-nos a les nostres necessitats i, un cop avaluada la quantitat de treball, ens vam adonar que podríem desenvolupar la nostra pròpia solució amb aproximadament els mateixos costos laborals. Però la vostra solució serà molt més fàcil d'operar i desenvolupar: no conté abstraccions innecessàries que admetin una funcionalitat que no necessitem.

Als que llegiu les darreres línies, gràcies per la vostra paciència i atenció!

Font: www.habr.com

Afegeix comentari