Eén cloud - besturingssysteem op datacenterniveau in Odnoklassniki

Eén cloud - besturingssysteem op datacenterniveau in Odnoklassniki

Aloha, mensen! Mijn naam is Oleg Anastasyev, ik werk bij Odnoklassniki in het Platform-team. En naast mij werkt er veel hardware in Odnoklassniki. We hebben vier datacenters met ongeveer 500 racks met ruim 8 servers. Op een gegeven moment realiseerden we ons dat de introductie van een nieuw beheersysteem ons in staat zou stellen apparatuur efficiënter te laden, toegangsbeheer te vergemakkelijken, de (her)distributie van computerbronnen te automatiseren, de lancering van nieuwe diensten te versnellen en reacties te versnellen. tot grootschalige ongelukken.

Wat is er van gekomen?

Naast mij en een heleboel hardware zijn er ook mensen die met deze hardware werken: engineers die direct in datacenters gevestigd zijn; netwerkers die netwerksoftware opzetten; beheerders, of SRE's, die veerkracht van de infrastructuur bieden; en ontwikkelingsteams, elk van hen is verantwoordelijk voor een deel van de functies van het portaal. De software die ze maken werkt ongeveer als volgt:

Eén cloud - besturingssysteem op datacenterniveau in Odnoklassniki

Gebruikersverzoeken worden zowel op de voorkant van het hoofdportaal ontvangen www.ok.ru, en op andere, bijvoorbeeld op de fronten van de muziek-API. Om de bedrijfslogica te verwerken, bellen ze de applicatieserver, die bij het verwerken van het verzoek de nodige gespecialiseerde microservices oproept: één grafiek (grafiek van sociale verbindingen), gebruikerscache (cache van gebruikersprofielen), enz.

Elk van deze services wordt op veel machines ingezet en elk van hen heeft verantwoordelijke ontwikkelaars die verantwoordelijk zijn voor het functioneren van de modules, hun werking en de technologische ontwikkeling. Al deze diensten draaien op hardwareservers en tot voor kort lanceerden we precies één taak per server, dat wil zeggen dat deze gespecialiseerd was voor een specifieke taak.

Waarom is dat? Deze aanpak had verschillende voordelen:

  • Opgelucht massabeheer. Laten we zeggen dat een taak enkele bibliotheken en enkele instellingen vereist. En vervolgens wordt de server aan precies één specifieke groep toegewezen, wordt het cfengine-beleid voor deze groep beschreven (of is dat al beschreven), en wordt deze configuratie centraal en automatisch uitgerold naar alle servers in deze groep.
  • Vereenvoudigd diagnostiek. Stel dat u kijkt naar de toegenomen belasting van de centrale processor en beseft dat deze belasting alleen kan worden gegenereerd door de taak die op deze hardwareprocessor wordt uitgevoerd. De zoektocht naar een schuldige eindigt zeer snel.
  • Vereenvoudigd controle. Als er iets mis is met de server, meldt de monitor dit en weet u precies wie de schuldige is.

Aan een service die uit meerdere replica's bestaat, worden meerdere servers toegewezen: één voor elk. Vervolgens wordt de computerbron voor de dienst heel eenvoudig toegewezen: het aantal servers dat de dienst heeft, de maximale hoeveelheid hulpbronnen die deze kan verbruiken. “Gemakkelijk” betekent hier niet dat het gemakkelijk te gebruiken is, maar in de zin dat de toewijzing van middelen handmatig gebeurt.

Deze aanpak stelde ons ook in staat dit te doen gespecialiseerde ijzerconfiguraties voor een taak die op deze server wordt uitgevoerd. Als de taak grote hoeveelheden data opslaat, gebruiken we een 4U-server met een chassis met 38 schijven. Als de taak puur computationeel is, kunnen we een goedkopere 1U-server kopen. Dit is computationeel efficiënt. Dankzij deze aanpak kunnen we onder meer vier keer minder machines gebruiken met een belasting die vergelijkbaar is met die van één vriendelijk sociaal netwerk.

Een dergelijke efficiëntie bij het gebruik van computerbronnen zou ook economische efficiëntie moeten garanderen, als we ervan uitgaan dat servers het duurste zijn. Lange tijd was hardware het duurst, en we hebben veel moeite gestoken in het verlagen van de prijs van hardware, door fouttolerantie-algoritmen te bedenken om de vereisten voor hardwarebetrouwbaarheid te verminderen. En vandaag hebben we het stadium bereikt waarin de prijs van de server niet langer doorslaggevend is. Houd je geen rekening met de nieuwste exoten, dan doet de specifieke configuratie van de servers in het rack er niet toe. Nu hebben we nog een probleem: de prijs van de ruimte die de server in het datacenter inneemt, dat wil zeggen de ruimte in het rack.

Omdat we ons realiseerden dat dit het geval was, besloten we te berekenen hoe effectief we de racks gebruikten.
We hebben de prijs van de krachtigste server uit de economisch verantwoorde servers gehaald, berekend hoeveel van dergelijke servers we in racks konden plaatsen, hoeveel taken we erop zouden uitvoeren op basis van het oude model ‘één server = één taak’ en hoeveel van dergelijke servers we zouden kunnen uitvoeren. taken kunnen gebruik maken van de apparatuur. Ze telden en huilden. Het bleek dat onze efficiëntie bij het gebruik van racks ongeveer 11% bedraagt. De conclusie ligt voor de hand: we moeten de efficiëntie van het gebruik van datacenters vergroten. Het lijkt erop dat de oplossing voor de hand ligt: ​​u moet meerdere taken tegelijk op één server uitvoeren. Maar dit is waar de moeilijkheden beginnen.

Massaconfiguratie wordt dramatisch ingewikkelder: het is nu onmogelijk om één groep aan een server toe te wijzen. Nu kunnen er immers meerdere taken met verschillende commando's op één server worden gestart. Bovendien kan de configuratie voor verschillende toepassingen conflicterend zijn. De diagnose wordt ook ingewikkelder: als u een verhoogd CPU- of schijfverbruik op een server ziet, weet u niet welke taak problemen veroorzaakt.

Maar het belangrijkste is dat er geen isolatie is tussen taken die op dezelfde machine worden uitgevoerd. Hier is bijvoorbeeld een grafiek van de gemiddelde responstijd van een servertaak voordat en nadat een andere computertoepassing op dezelfde server werd gelanceerd, die op geen enkele manier verband houdt met de eerste - de responstijd van de hoofdtaak is aanzienlijk toegenomen.

Eén cloud - besturingssysteem op datacenterniveau in Odnoklassniki

Uiteraard moet je taken uitvoeren in containers of op virtuele machines. Omdat bijna al onze taken onder één besturingssysteem (Linux) draaien of daarvoor zijn aangepast, hoeven we niet veel verschillende besturingssystemen te ondersteunen. Dienovereenkomstig is virtualisatie niet nodig; vanwege de extra overhead zal het minder efficiënt zijn dan containerisatie.

Als implementatie van containers voor het rechtstreeks uitvoeren van taken op servers is Docker een goede kandidaat: bestandssysteemimages lossen problemen met conflicterende configuraties goed op. Het feit dat afbeeldingen uit meerdere lagen kunnen worden samengesteld, stelt ons in staat de hoeveelheid gegevens die nodig is om ze in de infrastructuur te implementeren aanzienlijk te verminderen, waardoor gemeenschappelijke delen in afzonderlijke basislagen worden gescheiden. Dan zullen de basislagen (en de meest omvangrijke) vrij snel in de cache worden opgeslagen over de gehele infrastructuur, en om veel verschillende soorten applicaties en versies te kunnen leveren, hoeven slechts kleine lagen te worden overgedragen.

Bovendien bieden een kant-en-klaar register en image-tagging in Docker ons kant-en-klare primitieven voor versiebeheer en het leveren van code aan productie.

Docker biedt ons, net als elke andere vergelijkbare technologie, kant-en-klaar een zekere mate van containerisolatie. Bijvoorbeeld geheugenisolatie: elke container krijgt een limiet voor het gebruik van machinegeheugen, waarboven deze niet meer verbruikt. U kunt containers ook isoleren op basis van CPU-gebruik. Voor ons was standaard isolatie echter niet voldoende. Maar daarover hieronder meer.

Het direct draaien van containers op servers is slechts een deel van het probleem. Het andere deel heeft betrekking op het hosten van containers op servers. U moet begrijpen welke container op welke server kan worden geplaatst. Dit is niet zo'n gemakkelijke taak, omdat containers zo dicht mogelijk op servers moeten worden geplaatst zonder hun snelheid te verminderen. Een dergelijke plaatsing kan ook moeilijk zijn vanuit het oogpunt van fouttolerantie. Vaak willen we replica’s van dezelfde dienst in verschillende racks of zelfs in verschillende kamers van het datacenter plaatsen, zodat als een rack of kamer uitvalt, we niet meteen alle servicereplica’s kwijtraken.

Handmatig containers distribueren is geen optie als je 8 servers en 8-16 containers hebt.

Daarnaast wilden we ontwikkelaars meer onafhankelijkheid geven bij het toewijzen van middelen, zodat ze hun services zelf in productie konden hosten, zonder de hulp van een beheerder. Tegelijkertijd wilden we de controle behouden, zodat een kleine dienst niet alle bronnen van onze datacenters zou opslokken.

Uiteraard hebben we een controlelaag nodig die dit automatisch doet.

Zo kwamen we tot een eenvoudig en begrijpelijk beeld waar alle architecten dol op zijn: drie vierkanten.

Eén cloud - besturingssysteem op datacenterniveau in Odnoklassniki

one-cloud masters is een failovercluster dat verantwoordelijk is voor cloudorkestratie. De ontwikkelaar stuurt een manifest naar de master, dat alle informatie bevat die nodig is om de service te hosten. Op basis hiervan geeft de meester opdrachten aan geselecteerde volgelingen (machines die zijn ontworpen om containers uit te voeren). De volgelingen hebben onze agent, die het commando ontvangt, zijn commando's aan Docker geeft, en Docker configureert de Linux-kernel om de bijbehorende container te starten. Naast het uitvoeren van opdrachten rapporteert de agent voortdurend aan de meester over veranderingen in de status van zowel de minion-machine als de containers die erop draaien.

Toewijzing van middelen

Laten we nu eens kijken naar het probleem van de complexere toewijzing van hulpbronnen voor veel volgelingen.

Een computerbron in één cloud is:

  • De hoeveelheid processorkracht die door een specifieke taak wordt verbruikt.
  • De hoeveelheid geheugen die beschikbaar is voor de taak.
  • Netwerk verkeer. Elk van de minions heeft een specifieke netwerkinterface met beperkte bandbreedte, dus het is onmogelijk om taken te verdelen zonder rekening te houden met de hoeveelheid gegevens die ze via het netwerk verzenden.
  • Schijven. Naast de ruimte voor deze taken wijzen we uiteraard ook het type schijf toe: HDD of SSD. Schijven kunnen een eindig aantal verzoeken per seconde verwerken - IOPS. Daarom wijzen we voor taken die meer IOPS genereren dan een enkele schijf aankan, ook 'spindels' toe, dat wil zeggen schijfapparaten die exclusief voor de taak moeten worden gereserveerd.

Vervolgens kunnen we voor sommige diensten, bijvoorbeeld voor gebruikerscache, de verbruikte bronnen op deze manier registreren: 400 processorkernen, 2,5 TB geheugen, 50 Gbit/s verkeer in beide richtingen, 6 TB HDD-ruimte op 100 spindels. Of in een meer bekende vorm zoals deze:

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

Servicebronnen in de gebruikerscache verbruiken slechts een deel van alle beschikbare bronnen in de productie-infrastructuur. Daarom wil ik ervoor zorgen dat de gebruikerscache plotseling, door een operatorfout of niet, niet meer bronnen verbruikt dan eraan zijn toegewezen. Dat wil zeggen dat we de middelen moeten beperken. Maar waar kunnen we het quotum aan koppelen?

Laten we terugkeren naar ons sterk vereenvoudigde diagram van de interactie van componenten en het opnieuw tekenen met meer details, zoals dit:

Eén cloud - besturingssysteem op datacenterniveau in Odnoklassniki

Wat in het oog springt:

  • De webfrontend en muziek gebruiken geïsoleerde clusters van dezelfde applicatieserver.
  • We kunnen de logische lagen onderscheiden waartoe deze clusters behoren: fronten, caches, dataopslag en beheerlaag.
  • De frontend is heterogeen; het bestaat uit verschillende functionele subsystemen.
  • Caches kunnen ook verspreid zijn over het subsysteem waarvan de gegevens in de cache worden opgeslagen.

Laten we de afbeelding opnieuw tekenen:

Eén cloud - besturingssysteem op datacenterniveau in Odnoklassniki

Bah! Ja, we zien een hiërarchie! Dit betekent dat je bronnen in grotere stukken kunt verdelen: wijs een verantwoordelijke ontwikkelaar toe aan een knooppunt van deze hiërarchie die overeenkomt met het functionele subsysteem (zoals 'muziek' in de afbeelding), en koppel een quotum aan hetzelfde niveau van de hiërarchie. Deze hiërarchie stelt ons ook in staat om diensten flexibeler te organiseren, wat het beheer vergemakkelijkt. Omdat dit een zeer grote groep servers is, verdelen we bijvoorbeeld het hele internet in verschillende kleinere groepen, in de afbeelding weergegeven als groep1, groep2.

Door de extra regels te verwijderen, kunnen we elk knooppunt van onze afbeelding in een vlakkere vorm schrijven: groep1.web.front, api.muziek.front, gebruiker-cache.cache.

Zo komen we tot het concept van ‘hiërarchische wachtrij’. Het heeft een naam zoals "group1.web.front". Er wordt een quotum voor bronnen en gebruikersrechten aan toegewezen. We geven de persoon van DevOps de rechten om een ​​dienst naar de wachtrij te sturen, en zo'n medewerker kan iets in de wachtrij starten, en de persoon van OpsDev heeft beheerdersrechten, en nu kan hij de wachtrij beheren, daar mensen toewijzen, geef deze mensen rechten, etc. Services die in deze wachtrij draaien, zullen binnen het quotum van de wachtrij draaien. Als het rekenquotum van de wachtrij niet voldoende is om alle services in één keer uit te voeren, worden ze opeenvolgend uitgevoerd en vormen zo de wachtrij zelf.

Laten we de diensten eens nader bekijken. Een service heeft een volledig gekwalificeerde naam, die altijd de naam van de wachtrij bevat. Dan heeft de frontwebservice de naam ok-web.group1.web.front. En de applicatieserverservice waartoe deze toegang heeft, wordt aangeroepen ok-app.group1.web.front. Elke service heeft een manifest dat alle benodigde informatie specificeert voor plaatsing op specifieke machines: hoeveel bronnen deze taak verbruikt, welke configuratie daarvoor nodig is, hoeveel replica's er moeten zijn, eigenschappen voor het afhandelen van fouten van deze service. En nadat de service rechtstreeks op de machines is geplaatst, verschijnen de exemplaren ervan. Ze worden ook ondubbelzinnig genoemd - als exemplaarnummer en servicenaam: 1.ok-web.groep1.web.front, 2.ok-web.groep1.web.front, …

Dit is erg handig: door alleen naar de naam van de lopende container te kijken, kunnen we meteen veel te weten komen.

Laten we nu eens nader bekijken wat deze instanties feitelijk uitvoeren: taken.

Taakisolatieklassen

Alle taken in OK (en waarschijnlijk overal) kunnen in groepen worden verdeeld:

  • Taken met korte latentie - prod. Voor dergelijke taken en services is de reactievertraging (latentie) erg belangrijk, hoe snel elk van de verzoeken door het systeem wordt verwerkt. Voorbeelden van taken: webfronten, caches, applicatieservers, OLTP-opslag, enz.
  • Rekenproblemen - batch. Hier is de verwerkingssnelheid van elk specifiek verzoek niet belangrijk. Voor hen is het belangrijk hoeveel berekeningen deze taak in een bepaalde (lange) tijdsperiode zal uitvoeren (throughput). Dit zijn alle taken van MapReduce, Hadoop, machine learning, statistieken.
  • Achtergrondtaken - inactief. Voor dergelijke taken zijn latentie en doorvoer niet erg belangrijk. Dit omvat verschillende tests, migraties, herberekeningen en conversie van gegevens van het ene formaat naar het andere. Aan de ene kant lijken ze op berekende, aan de andere kant maakt het ons niet zoveel uit hoe snel ze worden voltooid.

Laten we eens kijken hoe dergelijke taken bronnen verbruiken, bijvoorbeeld de centrale processor.

Taken met korte vertraging. Een dergelijke taak zal een CPU-verbruikspatroon hebben dat vergelijkbaar is met dit:

Eén cloud - besturingssysteem op datacenterniveau in Odnoklassniki

Er wordt een verzoek van de gebruiker ontvangen voor verwerking, de taak begint alle beschikbare CPU-kernen te gebruiken, verwerkt deze, retourneert een antwoord, wacht op het volgende verzoek en stopt. Het volgende verzoek kwam binnen - opnieuw kozen we alles wat er was, berekenden het en wachten op de volgende.

Om de minimale latentie voor een dergelijke taak te garanderen, moeten we de maximale bronnen gebruiken die deze verbruikt en het vereiste aantal cores reserveren op de minion (de machine die de taak zal uitvoeren). Dan is de reserveringsformule voor ons probleem als volgt:

alloc: cpu = 4 (max)

en als we een minionmachine hebben met 16 kernen, dan kunnen er precies vier van dergelijke taken op worden geplaatst. We merken vooral op dat het gemiddelde processorverbruik van dergelijke taken vaak erg laag is - wat duidelijk is, aangezien de taak een aanzienlijk deel van de tijd op een verzoek wacht en niets doet.

Rekentaken. Hun patroon zal iets anders zijn:

Eén cloud - besturingssysteem op datacenterniveau in Odnoklassniki

Het gemiddelde CPU-bronnenverbruik voor dergelijke taken is behoorlijk hoog. Vaak willen we dat een rekentaak binnen een bepaalde tijd wordt voltooid, dus moeten we het minimale aantal benodigde processors reserveren, zodat de hele berekening binnen een acceptabele tijd wordt voltooid. De reserveringsformule ziet er als volgt uit:

alloc: cpu = [1,*)

"Plaats het alsjeblieft op een minion waar er minstens één vrije kern is, en dan, hoeveel er ook zijn, zal het alles verslinden."

Hier is de gebruiksefficiëntie al veel beter dan bij taken met een korte vertraging. Maar de winst zal veel groter zijn als je beide soorten taken op één minion-machine combineert en de middelen onderweg verdeelt. Wanneer een taak met een korte vertraging een processor nodig heeft, ontvangt deze deze onmiddellijk, en wanneer de bronnen niet langer nodig zijn, worden ze overgedragen naar de rekentaak, d.w.z. zoiets als dit:

Eén cloud - besturingssysteem op datacenterniveau in Odnoklassniki

Maar hoe dat te doen?

Laten we eerst eens kijken naar prod en de toewijzing ervan: cpu = 4. We moeten vier cores reserveren. In Docker-run kan dit op twee manieren worden gedaan:

  • De optie gebruiken --cpuset=1-4, dat wil zeggen wijs vier specifieke kernen op de machine toe aan de taak.
  • Gebruiken --cpuquota=400_000 --cpuperiod=100_000Wijs een quotum toe voor de processortijd, d.w.z. geef aan dat elke 100 ms realtime de taak niet meer dan 400 ms processortijd verbruikt. Er worden dezelfde vier kernen verkregen.

Maar welke van deze methoden is geschikt?

cpuset ziet er behoorlijk aantrekkelijk uit. De taak heeft vier speciale kernen, wat betekent dat processorcaches zo efficiënt mogelijk zullen werken. Dit heeft ook een keerzijde: we zouden de taak op ons moeten nemen om berekeningen over de onbelaste kernen van de machine te verdelen in plaats van over het besturingssysteem, en dit is een nogal niet-triviale taak, vooral als we batchtaken op zo'n systeem proberen te plaatsen. machine. Uit tests is gebleken dat de optie met een quotum hier beter geschikt is: zo heeft het besturingssysteem meer vrijheid bij het kiezen van de kern om de taak op dat moment uit te voeren en wordt de processortijd efficiënter verdeeld.

Laten we eens kijken hoe we reserveringen kunnen maken in Docker op basis van het minimumaantal cores. Het quotum voor batchtaken is niet langer van toepassing, omdat het niet nodig is het maximum te beperken; het volstaat om alleen het minimum te garanderen. En hier past de optie goed docker run --cpushares.

Wij hebben afgesproken dat als er voor een batch minimaal één kern garantie nodig is, wij dat ook aangeven --cpushares=1024, en als er minimaal twee kernen zijn, geven we dat aan --cpushares=2048. Cpu-shares interfereren op geen enkele manier met de verdeling van de processortijd, zolang er maar voldoende van is. Dus als prod momenteel niet alle vier de kernen gebruikt, is er niets dat batchtaken beperkt en kunnen ze extra processortijd gebruiken. Maar in een situatie waarin er een tekort aan processors is, als prod alle vier zijn cores heeft verbruikt en zijn quotum heeft bereikt, zal de resterende processortijd proportioneel worden verdeeld over cpushares, dat wil zeggen dat in een situatie met drie vrije cores er één zal zijn gegeven aan een taak met 1024 cpushares, en de overige twee zullen worden gegeven aan een taak met 2048 cpushares.

Maar het gebruik van quota en aandelen is niet voldoende. We moeten ervoor zorgen dat een taak met een korte vertraging prioriteit krijgt boven een batchtaak bij het toewijzen van processortijd. Zonder een dergelijke prioriteitstelling zal de batchtaak alle processortijd in beslag nemen op het moment dat de productie deze nodig heeft. Er zijn geen opties voor het prioriteren van containers in Docker, maar Linux CPU-plannerbeleid is handig. Je kunt er uitgebreid over lezen hier, en in het kader van dit artikel zullen we ze kort doornemen:

  • SCHED_OTHER
    Standaard ontvangen alle normale gebruikersprocessen op een Linux-machine.
  • SCHED_BATCH
    Ontworpen voor resource-intensieve processen. Wanneer een taak op een processor wordt geplaatst, wordt een zogenaamde activeringsboete geïntroduceerd: het is minder waarschijnlijk dat een dergelijke taak processorbronnen ontvangt als deze momenteel wordt gebruikt door een taak met SCHED_OTHER
  • SCHED_IDLE
    Een achtergrondproces met een zeer lage prioriteit, zelfs lager dan mooi -19. Wij maken gebruik van onze open source bibliotheek één-nio, om het noodzakelijke beleid in te stellen bij het starten van de container door te bellen

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

Maar zelfs als u niet in Java programmeert, kunt u hetzelfde doen met de opdracht chrt:

chrt -i 0 $pid

Laten we al onze isolatieniveaus voor de duidelijkheid in één tabel samenvatten:

Isolatieklasse
Alloc-voorbeeld
Docker-uitvoeringsopties
sched_setscheduler chrt*

Porren
processor = 4
--cpuquota=400000 --cpuperiod=100000
SCHED_OTHER

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

Idle
CPU= [2, *)
--cpushares=2048
SCHED_IDLE

*Als je chrt vanuit een container doet, heb je mogelijk de mogelijkheid sys_nice nodig, omdat Docker deze mogelijkheid standaard verwijdert bij het starten van de container.

Maar taken verbruiken niet alleen de processor, maar ook verkeer, wat de latentie van een netwerktaak nog meer beïnvloedt dan de onjuiste toewijzing van processorbronnen. Daarom willen we voor het verkeer uiteraard precies hetzelfde beeld krijgen. Dat wil zeggen dat wanneer een prod-taak enkele pakketten naar het netwerk verzendt, we de maximale snelheid beperken (formule toewijzing: lan=[*,500mbps) ), waarmee prod dit kan doen. En voor batch garanderen we alleen de minimale doorvoer, maar beperken we de maximale niet (formule toewijzing: lan=[10Mbps,*) ) In dit geval moet het productverkeer voorrang krijgen op batchtaken.
Hier heeft Docker geen primitieven die we kunnen gebruiken. Maar het komt ons te hulp Linux-verkeersbeheer. Met behulp van discipline hebben we het gewenste resultaat kunnen bereiken Hiërarchische eerlijke servicecurve. Met behulp hiervan kunnen we twee soorten verkeer onderscheiden: prod met hoge prioriteit en batch/idle met lage prioriteit. Als gevolg hiervan is de configuratie voor uitgaand verkeer als volgt:

Eén cloud - besturingssysteem op datacenterniveau in Odnoklassniki

hier is 1:0 de “root qdisc” van de hsfc-discipline; 1:1 - hsfc kindklasse met een totale bandbreedtelimiet van 8 Gbit/s, waaronder de kindklassen van alle containers worden geplaatst; 1:2 - de onderliggende klasse hsfc is gemeenschappelijk voor alle batch- en inactieve taken met een “dynamische” limiet, die hieronder wordt besproken. De resterende onderliggende hsfc-klassen zijn speciale klassen voor momenteel actieve prod-containers met limieten die overeenkomen met hun manifesten: 450 en 400 Mbit/s. Aan elke hsfc-klasse wordt een qdisc-wachtrij fq of fq_codel toegewezen, afhankelijk van de Linux-kernelversie, om pakketverlies tijdens verkeersuitbarstingen te voorkomen.

Normaal gesproken dienen tc-disciplines om alleen uitgaand verkeer prioriteit te geven. Maar we willen ook prioriteit geven aan inkomend verkeer. Een batchtaak kan immers gemakkelijk het hele inkomende kanaal selecteren en bijvoorbeeld een grote batch invoergegevens ontvangen voor mapping&reduce. Hiervoor gebruiken wij de module alsb, dat voor elke netwerkinterface een virtuele ifbX-interface creëert en inkomend verkeer van de interface omleidt naar uitgaand verkeer op ifbX. Verder werken voor ifbX dezelfde disciplines om uitgaand verkeer te controleren, waarvoor de hsfc-configuratie zeer vergelijkbaar zal zijn:

Eén cloud - besturingssysteem op datacenterniveau in Odnoklassniki

Tijdens de experimenten kwamen we erachter dat hsfc de beste resultaten laat zien wanneer de 1:2-klasse van niet-prioritair batch/inactief verkeer op minion-machines wordt beperkt tot niet meer dan een bepaalde vrije rijstrook. Anders heeft niet-prioritair verkeer te veel invloed op de latentie van producttaken. minion bepaalt elke seconde de huidige hoeveelheid vrije bandbreedte en meet het gemiddelde verkeersverbruik van alle prod-taken van een bepaalde minion Eén cloud - besturingssysteem op datacenterniveau in Odnoklassniki en aftrekken van de bandbreedte van de netwerkinterface Eén cloud - besturingssysteem op datacenterniveau in Odnoklassniki met een kleine marge, d.w.z.

Eén cloud - besturingssysteem op datacenterniveau in Odnoklassniki

Banden worden onafhankelijk gedefinieerd voor inkomend en uitgaand verkeer. En volgens de nieuwe waarden herconfigureert Miniond de niet-prioritaire klassenlimiet 1:2.

Daarom hebben we alle drie de isolatieklassen geïmplementeerd: prod, batch en inactief. Deze klassen hebben een grote invloed op de prestatiekenmerken van taken. Daarom hebben we besloten om dit attribuut bovenaan de hiërarchie te plaatsen, zodat het bij het bekijken van de naam van de hiërarchische wachtrij meteen duidelijk zou zijn waar we mee te maken hebben:

Eén cloud - besturingssysteem op datacenterniveau in Odnoklassniki

Al onze vrienden web и muziek- de fronten worden vervolgens in de hiërarchie onder prod. geplaatst. Laten we bijvoorbeeld onder batch de service plaatsen muziek catalogus, die periodiek een catalogus met nummers samenstelt uit een reeks mp3-bestanden die naar Odnoklassniki zijn geüpload. Een voorbeeld van een service onder inactiviteit zou zijn muziek transformator, waarmee het volumeniveau van de muziek wordt genormaliseerd.

Nu de extra regels weer zijn verwijderd, kunnen we onze servicenamen platter schrijven door de taakisolatieklasse toe te voegen aan het einde van de volledige servicenaam: web.front.prod, catalogus.muziek.batch, transformator.muziek.inactief.

En nu we naar de naam van de dienst kijken, begrijpen we niet alleen welke functie deze vervult, maar ook de isolatieklasse ervan, wat de kriticiteit ervan betekent, enz.

Alles is geweldig, maar er is één bittere waarheid. Het is onmogelijk om taken die op één machine draaien volledig te isoleren.

Wat we hebben bereikt: als batch intensief consumeert alleen CPU-bronnen, dan doet de ingebouwde Linux CPU-planner zijn werk heel goed, en er is vrijwel geen impact op de prod-taak. Maar als deze batchtaak actief met het geheugen begint te werken, is de wederzijdse invloed al zichtbaar. Dit gebeurt omdat de prod-taak uit de geheugencaches van de processor wordt "weggespoeld", waardoor het aantal cache-missers toeneemt en de processor de prod-taak langzamer verwerkt. Een dergelijke batchtaak kan de latentie van onze typische productcontainer met 10% verhogen.

Het isoleren van verkeer is zelfs nog moeilijker vanwege het feit dat moderne netwerkkaarten een interne wachtrij met pakketten hebben. Als het pakket van de batchtaak als eerste daar aankomt, zal het het eerste zijn dat via de kabel wordt verzonden, en er kan niets aan worden gedaan.

Bovendien zijn we er tot nu toe alleen in geslaagd het probleem van het prioriteren van TCP-verkeer op te lossen: de hsfc-aanpak werkt niet voor UDP. En zelfs in het geval van TCP-verkeer, als de batchtaak veel verkeer genereert, levert dit ook een toename van ongeveer 10% op in de vertraging van de prod-taak.

fout tolerantie

Een van de doelen bij het ontwikkelen van one-cloud was het verbeteren van de fouttolerantie van Odnoklassniki. Daarom zou ik vervolgens de mogelijke scenario's van mislukkingen en ongevallen in meer detail willen bekijken. Laten we beginnen met een eenvoudig scenario: een containerstoring.

De container zelf kan op verschillende manieren falen. Dit kan een soort experiment, bug of fout in het manifest zijn, waardoor de prod-taak meer bronnen begint te verbruiken dan aangegeven in het manifest. We hadden een geval: een ontwikkelaar implementeerde een complex algoritme, herwerkte het vele malen, overdacht zichzelf en raakte zo in de war dat het probleem uiteindelijk op een niet-triviale manier in een lus terechtkwam. En omdat de prod-taak een hogere prioriteit heeft dan alle andere op dezelfde volgelingen, begon deze alle beschikbare processorbronnen te verbruiken. In deze situatie heeft isolatie, of beter gezegd het CPU-tijdquotum, de dag gered. Als aan een taak een quotum wordt toegewezen, zal de taak niet meer verbruiken. Daarom merkten batch- en andere productietaken die op dezelfde machine werden uitgevoerd, niets op.

Het tweede mogelijke probleem is dat de container valt. En hier redt het herstartbeleid ons, iedereen kent ze, Docker zelf doet geweldig werk. Bijna alle prod-taken hebben een beleid dat ze altijd opnieuw moeten opstarten. Soms gebruiken we on_failure voor batchtaken of voor het debuggen van productcontainers.

Wat kun je doen als een hele minion niet beschikbaar is?

Laat de container uiteraard op een andere machine draaien. Het interessante deel hier is wat er gebeurt met de IP-adressen die aan de container zijn toegewezen.

We kunnen containers dezelfde IP-adressen toewijzen als de minion-machines waarop deze containers draaien. Wanneer de container vervolgens op een andere machine wordt gestart, verandert het IP-adres ervan en moeten alle clients begrijpen dat de container is verplaatst en nu naar een ander adres moeten gaan, waarvoor een afzonderlijke Service Discovery-service vereist is.

Service Discovery is handig. Er zijn veel oplossingen op de markt met verschillende niveaus van fouttolerantie voor het organiseren van een serviceregister. Vaak implementeren dergelijke oplossingen load balancer-logica, slaan ze extra configuratie op in de vorm van KV-opslag, enz.
We willen echter voorkomen dat er een afzonderlijk register moet worden geïmplementeerd, omdat dit de introductie van een cruciaal systeem zou betekenen dat door alle services in de productie wordt gebruikt. Dit betekent dat dit een potentieel faalpunt is en dat u een zeer fouttolerante oplossing moet kiezen of ontwikkelen, wat uiteraard erg moeilijk, tijdrovend en duur is.

En nog een groot nadeel: om onze oude infrastructuur met de nieuwe te laten werken, zouden we absoluut alle taken moeten herschrijven om een ​​soort Service Discovery-systeem te gebruiken. Er is VEEL werk, en op sommige plaatsen is het bijna onmogelijk als het gaat om apparaten op laag niveau die op OS-kernelniveau of rechtstreeks met de hardware werken. Implementatie van deze functionaliteit met behulp van gevestigde oplossingspatronen, zoals zijspan zou op sommige plaatsen een extra belasting betekenen, op andere een complicatie van de bediening en extra faalscenario's. We wilden de zaken niet ingewikkelder maken, dus besloten we het gebruik van Service Discovery optioneel te maken.

In één cloud volgt het IP-adres de container, dat wil zeggen dat elke taakinstantie zijn eigen IP-adres heeft. Dit adres is “statisch”: het wordt aan elk exemplaar toegewezen wanneer de dienst voor het eerst naar de cloud wordt verzonden. Als een dienst tijdens zijn levensduur een ander aantal exemplaren heeft gehad, krijgt deze uiteindelijk evenveel IP-adressen toegewezen als er maximaal exemplaren waren.

Vervolgens veranderen deze adressen niet: ze worden één keer toegewezen en blijven bestaan ​​gedurende de hele levensduur van de dienst in productie. IP-adressen volgen containers over het netwerk. Als de container wordt overgedragen aan een andere minion, volgt het adres deze.

De toewijzing van een servicenaam aan de lijst met IP-adressen verandert dus zeer zelden. Als je nog eens kijkt naar de namen van de service-instanties die we aan het begin van het artikel noemden (1.ok-web.group1.web.front.prod, 2.ok-web.group1.web.front.prod, …), zullen we merken dat ze lijken op de FQDN's die in DNS worden gebruikt. Dat klopt, om de namen van service-instanties aan hun IP-adressen toe te wijzen, gebruiken we het DNS-protocol. Bovendien retourneert deze DNS alle gereserveerde IP-adressen van alle containers - zowel actieve als gestopte (laten we zeggen dat er drie replica's worden gebruikt, en we hebben daar vijf adressen gereserveerd - ze zullen alle vijf worden geretourneerd). Klanten die deze informatie hebben ontvangen, zullen proberen een verbinding tot stand te brengen met alle vijf replica's - en zo te bepalen welke werken. Deze optie voor het bepalen van de beschikbaarheid is veel betrouwbaarder; er is geen DNS of Service Discovery bij betrokken, wat betekent dat er geen moeilijke problemen hoeven op te lossen bij het waarborgen van de relevantie van informatie en fouttolerantie van deze systemen. Bovendien kunnen we bij kritieke services waarvan de werking van het hele portaal afhankelijk is, helemaal geen DNS gebruiken, maar eenvoudigweg IP-adressen in de configuratie invoeren.

Het implementeren van een dergelijke IP-overdracht achter containers kan niet triviaal zijn - en we zullen bekijken hoe het werkt met het volgende voorbeeld:

Eén cloud - besturingssysteem op datacenterniveau in Odnoklassniki

Laten we zeggen dat de one-cloud-master de opdracht geeft aan minion M1 om uit te voeren 1.ok-web.group1.web.front.prod met adres 1.1.1.1. Werkt op een minion VOGEL, die dit adres adverteert op speciale servers routereflector. Deze laatste hebben een BGP-sessie met de netwerkhardware, waarin de route van adres 1.1.1.1 op M1 wordt vertaald. M1 routeert pakketten binnen de container met behulp van Linux. Er zijn drie routereflectorservers, aangezien dit een zeer cruciaal onderdeel is van de one-cloud-infrastructuur - zonder hen zal het netwerk in one-cloud niet werken. We plaatsen ze in verschillende racks, indien mogelijk in verschillende kamers van het datacenter, om de kans te verkleinen dat ze alle drie tegelijkertijd uitvallen.

Laten we nu aannemen dat de verbinding tussen de one-cloud master en de M1 minion verloren is. De one-cloud master zal nu handelen in de veronderstelling dat M1 volledig heeft gefaald. Dat wil zeggen, het geeft het commando aan de M2-minion om te lanceren web.group1.web.front.prod met hetzelfde adres 1.1.1.1. Nu hebben we twee conflicterende routes op het netwerk voor 1.1.1.1: op M1 en op M2. Om dergelijke conflicten op te lossen, gebruiken we de Multi Exit Discriminator, die wordt gespecificeerd in de BGP-aankondiging. Dit is een getal dat het gewicht van de geadverteerde route weergeeft. Uit de conflicterende routes wordt de route met de lagere MED-waarde geselecteerd. De one-cloud master ondersteunt MED als integraal onderdeel van container-IP-adressen. Voor het eerst wordt het adres geschreven met een voldoende grote MED = 1. In de situatie van een dergelijke noodcontaineroverdracht verlaagt de kapitein de MED en ontvangt M000 al de opdracht om het adres 000 te adverteren met MED = 2. De instantie die op M1.1.1.1 draait, blijft in dit geval er is geen verbinding, en zijn verdere lot interesseert ons weinig totdat de verbinding met de meester is hersteld, wanneer hij zal worden gestopt als een oude opname.

ongeval

Alle datacenterbeheersystemen kunnen kleine storingen altijd op acceptabele wijze verwerken. Het overstromen van containers is bijna overal de norm.

Laten we eens kijken hoe we omgaan met een noodsituatie, zoals een stroomstoring in een of meer kamers van een datacenter.

Wat betekent een ongeval voor een datacenterbeheersysteem? Allereerst is dit een enorme eenmalige storing van veel machines, en het besturingssysteem moet veel containers tegelijkertijd migreren. Maar als de ramp zeer grootschalig is, kan het gebeuren dat niet alle taken opnieuw kunnen worden toegewezen aan andere volgelingen, omdat de resourcecapaciteit van het datacenter onder de 100% van de belasting daalt.

Vaak gaan ongevallen gepaard met het falen van de controlelaag. Dit kan gebeuren als gevolg van het falen van de apparatuur, maar vaker vanwege het feit dat ongevallen niet worden getest en de controlelaag zelf valt als gevolg van de verhoogde belasting.

Wat kun je hieraan doen?

Massale migraties betekenen dat er een groot aantal activiteiten, migraties en implementaties plaatsvinden in de infrastructuur. Bij elk van de migraties kan het enige tijd duren die nodig is om containerimages aan minions te leveren en uit te pakken, containers te starten en te initialiseren, enz. Daarom is het wenselijk dat belangrijkere taken worden gestart vóór minder belangrijke taken.

Laten we opnieuw kijken naar de hiërarchie van services waarmee we bekend zijn en proberen te beslissen welke taken we als eerste willen uitvoeren.

Eén cloud - besturingssysteem op datacenterniveau in Odnoklassniki

Uiteraard zijn dit de processen die direct betrokken zijn bij het verwerken van gebruikersverzoeken, d.w.z. prod. Wij geven dit aan met plaatsing prioriteit — een nummer dat aan de wachtrij kan worden toegewezen. Als een wachtrij een hogere prioriteit heeft, worden de services ervan als eerste geplaatst.

Op prod kennen we hogere prioriteiten toe, 0; per batch - iets lager, 100; bij inactiviteit - nog lager, 200. Prioriteiten worden hiërarchisch toegepast. Alle taken lager in de hiërarchie hebben een overeenkomstige prioriteit. Als we willen dat caches binnen prod vóór frontends worden gelanceerd, dan wijzen we prioriteiten toe aan cache = 0 en aan front subqueues = 1. Als we bijvoorbeeld willen dat het hoofdportaal eerst vanaf de fronten wordt gelanceerd, en alleen het muziekfront dan kunnen we aan de laatste een lagere prioriteit toekennen: 10.

Het volgende probleem is het gebrek aan middelen. Een grote hoeveelheid apparatuur, hele hallen van het datacenter, faalde dus en we hebben zoveel diensten opnieuw gelanceerd dat er nu niet genoeg middelen voor iedereen zijn. U moet beslissen welke taken u opoffert om de belangrijkste kritieke services draaiende te houden.

Eén cloud - besturingssysteem op datacenterniveau in Odnoklassniki

In tegenstelling tot plaatsingsprioriteit kunnen we niet zomaar alle batchtaken opofferen; sommige daarvan zijn belangrijk voor de werking van de portal. Daarom hebben we het afzonderlijk benadrukt voorrang op voorrang taken. Wanneer deze wordt geplaatst, kan een taak met een hogere prioriteit een taak met een lagere prioriteit voorrang geven, dat wil zeggen stoppen, als er geen vrije handlangers meer zijn. In dit geval zal een taak met een lage prioriteit waarschijnlijk ongeplaatst blijven, dat wil zeggen dat er niet langer een geschikte minion voor zal zijn met voldoende vrije middelen.

In onze hiërarchie is het heel eenvoudig om een ​​voorrangsprioriteit te specificeren, zodat prod- en batchtaken inactieve taken voorrang geven of stoppen, maar niet elkaar, door een prioriteit voor inactieve taken op te geven die gelijk is aan 200. Net als in het geval van plaatsingsprioriteit, kunnen onze hiërarchie gebruiken om complexere regels te beschrijven. Laten we bijvoorbeeld aangeven dat we de muziekfunctie opofferen als we niet genoeg bronnen hebben voor het hoofdwebportaal, en de prioriteit voor de overeenkomstige knooppunten lager instellen: 10.

Volledige DC-ongevallen

Waarom zou het hele datacenter falen? Element. Was een goed bericht de orkaan beïnvloedde de werkzaamheden van het datacenter. De elementen kunnen worden beschouwd als daklozen die ooit de optiek in het spruitstuk hebben verbrand en het datacenter het contact met andere locaties volledig heeft verloren. De oorzaak van het falen kan ook een menselijke factor zijn: de operator zal zo'n commando geven dat het hele datacenter omvalt. Dit kan gebeuren als gevolg van een grote bug. Over het algemeen is het instorten van datacenters niet ongewoon. Dit overkomt ons eens in de paar maanden.

En dit is wat we doen om te voorkomen dat iemand #levend tweet.

De eerste strategie is isolatie. Elke one-cloud-instantie is geïsoleerd en kan machines in slechts één datacenter beheren. Dat wil zeggen: het verlies van een cloud als gevolg van bugs of onjuiste operatoropdrachten is het verlies van slechts één datacenter. Wij zijn er klaar voor: we hebben een redundantiebeleid waarbij in alle datacenters replica’s van de applicatie en data staan. We maken gebruik van fouttolerante databases en testen periodiek op fouten.
Sinds vandaag hebben we vier datacenters, dat betekent vier afzonderlijke, volledig geïsoleerde exemplaren van één cloud.

Deze aanpak beschermt niet alleen tegen fysiek falen, maar kan ook beschermen tegen bedieningsfouten.

Wat kan er nog meer gedaan worden met de menselijke factor? Wanneer een operator de cloud een vreemd of potentieel gevaarlijk commando geeft, kan hem plotseling worden gevraagd een klein probleem op te lossen om te zien hoe goed hij dacht. Als dit bijvoorbeeld een soort massale stop is van veel replica's of gewoon een vreemd commando: het verminderen van het aantal replica's of het wijzigen van de naam van de afbeelding, en niet alleen het versienummer in het nieuwe manifest.

Eén cloud - besturingssysteem op datacenterniveau in Odnoklassniki

Resultaten van

Onderscheidende kenmerken van one-cloud:

  • Hiërarchisch en visueel naamgevingsschema voor services en containers, waarmee je heel snel kunt achterhalen wat de taak is, waar deze betrekking op heeft, hoe deze werkt en wie daarvoor verantwoordelijk is.
  • Wij passen onze techniek van het combineren van product- en batch-taken op volgelingen om de efficiëntie van het delen van machines te verbeteren. In plaats van cpuset gebruiken we CPU-quota, shares, CPU-plannerbeleid en Linux QoS.
  • Het was niet mogelijk om containers die op dezelfde machine draaien volledig te isoleren, maar hun onderlinge invloed blijft binnen de 20%.
  • Het organiseren van services in een hiërarchie helpt bij automatisch noodherstel met behulp van prioriteiten op het gebied van plaatsing en voorrang.

FAQ

Waarom hebben we niet voor een kant-en-klare oplossing gekozen?

  • Verschillende klassen van taakisolatie vereisen verschillende logica wanneer ze op minions worden geplaatst. Als prod-taken kunnen worden geplaatst door eenvoudigweg middelen te reserveren, dan moeten batch- en inactieve taken worden geplaatst, waarbij het daadwerkelijke gebruik van bronnen op minion-machines wordt gevolgd.
  • De noodzaak om rekening te houden met de middelen die worden verbruikt door taken, zoals:
    • netwerkbandbreedte;
    • typen en "spindels" van schijven.
  • De noodzaak om de prioriteiten van services aan te geven tijdens noodhulp, de rechten en quota's van commando's voor bronnen, wat wordt opgelost met behulp van hiërarchische wachtrijen in één cloud.
  • De noodzaak van een menselijke naamgeving van containers om de responstijd bij ongevallen en incidenten te verkorten
  • De onmogelijkheid van een eenmalige wijdverspreide implementatie van Service Discovery; de noodzaak om lange tijd naast elkaar te bestaan ​​met taken die worden gehost op hardwarehosts - iets dat wordt opgelost door “statische” IP-adressen die volgen op containers, en, als gevolg daarvan, de behoefte aan unieke integratie met een grote netwerkinfrastructuur.

Voor al deze functies zouden aanzienlijke aanpassingen van de bestaande oplossingen nodig zijn om bij ons te passen, en nadat we de hoeveelheid werk hadden beoordeeld, realiseerden we ons dat we onze eigen oplossing konden ontwikkelen met ongeveer dezelfde arbeidskosten. Maar uw oplossing zal veel eenvoudiger te bedienen en te ontwikkelen zijn - het bevat geen onnodige abstracties die functionaliteit ondersteunen die we niet nodig hebben.

Aan degenen die de laatste regels hebben gelezen: bedankt voor uw geduld en aandacht!

Bron: www.habr.com

Voeg een reactie