One-cloud - datacenterniveau OS i Odnoklassniki

One-cloud - datacenterniveau OS i Odnoklassniki

Aloha, mennesker! Mit navn er Oleg Anastasyev, jeg arbejder hos Odnoklassniki i Platform-teamet. Og udover mig er der meget hardware, der arbejder i Odnoklassniki. Vi har fire datacentre med omkring 500 racks med mere end 8 tusinde servere. På et vist tidspunkt indså vi, at introduktionen af ​​et nyt ledelsessystem ville give os mulighed for at indlæse udstyr mere effektivt, lette adgangsstyring, automatisere (re)distribution af computerressourcer, fremskynde lanceringen af ​​nye tjenester og fremskynde svar til storstilede ulykker.

Hvad kom der ud af det?

Udover mig og en masse hardware, er der også folk, der arbejder med denne hardware: Ingeniører, der er placeret direkte i datacentre; netværkere, der opsætter netværkssoftware; administratorer eller SRE'er, der leverer infrastrukturresiliens; og udviklingsteams, hver af dem er ansvarlige for en del af portalens funktioner. Softwaren, de laver, fungerer sådan her:

One-cloud - datacenterniveau OS i Odnoklassniki

Brugeranmodninger modtages begge på hovedportalens fronter www.ok.ru, og på andre, for eksempel på musik-API-fronterne. For at behandle forretningslogikken kalder de applikationsserveren, som ved behandling af anmodningen kalder de nødvendige specialiserede mikrotjenester - one-graph (graf over sociale forbindelser), bruger-cache (cache af brugerprofiler) osv.

Hver af disse tjenester er installeret på mange maskiner, og hver af dem har ansvarlige udviklere, der er ansvarlige for modulernes funktion, deres drift og teknologiske udvikling. Alle disse tjenester kører på hardwareservere, og indtil for nylig lancerede vi præcis én opgave pr. server, dvs. den var specialiseret til en bestemt opgave.

Hvorfor det? Denne tilgang havde flere fordele:

  • lettet masseledelse. Lad os sige, at en opgave kræver nogle biblioteker, nogle indstillinger. Og så tildeles serveren til præcis én specifik gruppe, cfengine-politikken for denne gruppe er beskrevet (eller den er allerede beskrevet), og denne konfiguration rulles centralt og automatisk ud til alle servere i denne gruppe.
  • Forenklet diagnostik. Lad os sige, at du ser på den øgede belastning på den centrale processor og indser, at denne belastning kun kunne genereres af den opgave, der kører på denne hardwareprocessor. Søgningen efter nogen at give skylden ender meget hurtigt.
  • Forenklet overvågning. Hvis der er noget galt med serveren, melder monitoren det, og du ved præcis, hvem der har skylden.

En service bestående af flere replikaer tildeles flere servere - en til hver. Derefter allokeres computerressourcen til tjenesten meget enkelt: antallet af servere, tjenesten har, den maksimale mængde ressourcer, den kan forbruge. "Nemt" betyder her ikke, at det er nemt at bruge, men i den forstand, at ressourceallokering sker manuelt.

Denne tilgang gjorde det også muligt for os specialiserede jernkonfigurationer for en opgave, der kører på denne server. Hvis opgaven gemmer store mængder data, så bruger vi en 4U server med et chassis med 38 diske. Hvis opgaven er rent beregningsmæssig, så kan vi købe en billigere 1U-server. Dette er beregningsmæssigt effektivt. Denne tilgang giver os blandt andet mulighed for at bruge fire gange færre maskiner med en belastning, der kan sammenlignes med ét venligt socialt netværk.

En sådan effektivitet i brugen af ​​computerressourcer bør også sikre økonomisk effektivitet, hvis vi går ud fra, at det dyreste er servere. I lang tid var hardware den dyreste, og vi lagde en masse kræfter i at reducere prisen på hardware og kom med fejltolerancealgoritmer for at reducere kravene til hardwarepålidelighed. Og i dag er vi nået til det stadie, hvor prisen på serveren er holdt op med at være afgørende. Hvis du ikke overvejer de nyeste eksotiske ting, er den specifikke konfiguration af serverne i racket ligegyldig. Nu har vi et andet problem - prisen på den plads, som serveren optager i datacentret, det vil sige pladsen i racket.

Da vi indså, at dette var tilfældet, besluttede vi at beregne, hvor effektivt vi brugte stativerne.
Vi tog prisen på den mest kraftfulde server fra de økonomisk forsvarlige, beregnede, hvor mange sådanne servere vi kunne placere i racks, hvor mange opgaver vi ville køre på dem baseret på den gamle model "én server = en opgave" og hvor meget sådanne opgaver kunne udnytte udstyret. De talte og fældede tårer. Det viste sig, at vores effektivitet i at bruge stativer er omkring 11%. Konklusionen er indlysende: Vi skal øge effektiviteten ved at bruge datacentre. Det ser ud til, at løsningen er indlysende: du skal køre flere opgaver på én server på én gang. Men det er her, vanskelighederne begynder.

Massekonfiguration bliver dramatisk mere kompliceret - det er nu umuligt at tildele en gruppe til en server. Når alt kommer til alt, nu kan flere opgaver med forskellige kommandoer startes på én server. Derudover kan konfigurationen være modstridende for forskellige applikationer. Diagnose bliver også mere kompliceret: Hvis du ser øget CPU- eller diskforbrug på en server, ved du ikke, hvilken opgave der forårsager problemer.

Men det vigtigste er, at der ikke er nogen isolation mellem opgaver, der kører på samme maskine. Her er for eksempel en graf over den gennemsnitlige responstid for en serveropgave før og efter en anden beregningsapplikation blev lanceret på samme server, på ingen måde relateret til den første - responstiden for hovedopgaven er steget markant.

One-cloud - datacenterniveau OS i Odnoklassniki

Det er klart, at du skal køre opgaver enten i containere eller i virtuelle maskiner. Da næsten alle vores opgaver kører under ét OS (Linux) eller er tilpasset til det, behøver vi ikke understøtte mange forskellige styresystemer. Derfor er virtualisering ikke nødvendig; på grund af den ekstra overhead vil den være mindre effektiv end containerisering.

Som en implementering af containere til at køre opgaver direkte på servere er Docker en god kandidat: Filsystembilleder løser problemer med modstridende konfigurationer godt. Den kendsgerning, at billeder kan være sammensat af flere lag, giver os mulighed for betydeligt at reducere mængden af ​​data, der kræves for at implementere dem på infrastrukturen, og adskille fælles dele i separate basislag. Så vil de grundlæggende (og mest voluminøse) lag blive cache ret hurtigt gennem hele infrastrukturen, og for at levere mange forskellige typer applikationer og versioner skal der kun overføres små lag.

Derudover giver et færdigt register og billedmærkning i Docker os færdige primitiver til versionering og levering af kode til produktion.

Docker, som enhver anden lignende teknologi, giver os en vis grad af containerisolering ud af kassen. For eksempel hukommelsesisolering - hver beholder får en grænse for brugen af ​​maskinhukommelse, ud over hvilken den ikke vil forbruge. Du kan også isolere beholdere baseret på CPU-brug. For os var standardisolering dog ikke nok. Men mere om det nedenfor.

Direkte kørsel af containere på servere er kun en del af problemet. Den anden del er relateret til hosting af containere på servere. Du skal forstå, hvilken container der kan placeres på hvilken server. Dette er ikke så let en opgave, fordi containere skal placeres på servere så tæt som muligt uden at reducere deres hastighed. En sådan placering kan også være vanskelig ud fra et fejltolerancesynspunkt. Ofte ønsker vi at placere replikaer af den samme tjeneste i forskellige racks eller endda i forskellige rum i datacentret, så hvis et rack eller rum svigter, mister vi ikke alle servicereplikaerne med det samme.

At distribuere containere manuelt er ikke en mulighed, når du har 8 tusinde servere og 8-16 tusinde containere.

Derudover ønskede vi at give udviklere mere uafhængighed i ressourceallokering, så de selv kunne hoste deres tjenester i produktionen uden hjælp fra en administrator. Samtidig ønskede vi at bevare kontrollen, så nogle mindre tjenester ikke ville tære på alle ressourcerne i vores datacentre.

Vi har naturligvis brug for et kontrollag, der vil gøre dette automatisk.

Så vi kom til et enkelt og forståeligt billede, som alle arkitekter elsker: tre firkanter.

One-cloud - datacenterniveau OS i Odnoklassniki

one-cloud masters er en failover-klynge, der er ansvarlig for cloud-orkestrering. Udvikleren sender et manifest til masteren, som indeholder alle de oplysninger, der er nødvendige for at være vært for tjenesten. Baseret på det giver mesteren kommandoer til udvalgte håndlangere (maskiner designet til at køre containere). Minions har vores agent, som modtager kommandoen, udsteder sine kommandoer til Docker, og Docker konfigurerer linux-kernen til at starte den tilsvarende container. Udover at udføre kommandoer, rapporterer agenten løbende til masteren om ændringer i tilstanden af ​​både minionmaskinen og de containere, der kører på den.

Ressourceallokering

Lad os nu se på problemet med mere kompleks ressourceallokering for mange håndlangere.

En computerressource i én sky er:

  • Mængden af ​​processorkraft, der forbruges af en bestemt opgave.
  • Mængden af ​​tilgængelig hukommelse til opgaven.
  • Netværkstrafik. Hver af minions har en specifik netværksgrænseflade med begrænset båndbredde, så det er umuligt at fordele opgaver uden at tage højde for mængden af ​​data, de transmitterer over netværket.
  • Diske. Ud over pladsen til disse opgaver tildeler vi naturligvis også disktypen: HDD eller SSD. Diske kan betjene et begrænset antal anmodninger pr. sekund - IOPS. Til opgaver, der genererer mere IOPS, end en enkelt disk kan håndtere, tildeler vi derfor også "spindler" - det vil sige diskenheder, der udelukkende skal reserveres til opgaven.

Så for nogle tjenester, for eksempel for bruger-cache, kan vi registrere de forbrugte ressourcer på denne måde: 400 processorkerner, 2,5 TB hukommelse, 50 Gbit/s trafik i begge retninger, 6 TB HDD-plads placeret på 100 spindler. Eller i en mere velkendt form som denne:

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

Brugercache-tjenesteressourcer bruger kun en del af alle tilgængelige ressourcer i produktionsinfrastrukturen. Derfor vil jeg sikre mig, at bruger-cachen pludselig, på grund af en operatørfejl eller ej, bruger flere ressourcer, end der er allokeret til den. Det vil sige, at vi skal begrænse ressourcerne. Men hvad kunne vi binde kvoten til?

Lad os vende tilbage til vores meget forenklede diagram over komponenternes interaktion og gentegne det med flere detaljer - som dette:

One-cloud - datacenterniveau OS i Odnoklassniki

Hvad fanger dit øje:

  • Webfrontenden og musikken bruger isolerede klynger af den samme applikationsserver.
  • Vi kan skelne de logiske lag, som disse klynger tilhører: fronter, caches, datalagring og styringslag.
  • Frontenden er heterogen; den består af forskellige funktionelle undersystemer.
  • Caches kan også være spredt ud over det delsystem, hvis data de cachelagrer.

Lad os tegne billedet igen:

One-cloud - datacenterniveau OS i Odnoklassniki

Bah! Ja, vi ser et hierarki! Det betyder, at du kan fordele ressourcer i større bidder: tildel en ansvarlig udvikler til en node i dette hierarki, der svarer til det funktionelle undersystem (som "musik" på billedet), og tilknyt en kvote til det samme niveau i hierarkiet. Dette hierarki giver os også mulighed for at organisere tjenester mere fleksibelt for at lette administrationen. For eksempel opdeler vi hele nettet, da dette er en meget stor gruppering af servere, i flere mindre grupper, vist på billedet som gruppe1, gruppe2.

Ved at fjerne de ekstra linjer kan vi skrive hver knude på vores billede i en mere flade form: gruppe1.web.front, api.musik.front, bruger-cache.cache.

Sådan kommer vi til begrebet "hierarkisk kø". Den har et navn som "group1.web.front". Der tildeles en kvote for ressourcer og brugerrettigheder. Vi vil give personen fra DevOps rettighederne til at sende en service til køen, og sådan en medarbejder kan starte noget i køen, og personen fra OpsDev vil have administratorrettigheder, og nu kan han administrere køen, tildele folk der, give disse personer rettigheder osv. Tjenester, der kører på denne kø, vil køre inden for køens kvote. Hvis køens computerkvote ikke er nok til at udføre alle tjenester på én gang, vil de blive eksekveret sekventielt, og dermed danne selve køen.

Lad os se nærmere på tjenesterne. En tjeneste har et fuldt kvalificeret navn, som altid inkluderer navnet på køen. Så vil den forreste webtjeneste have navnet ok-web.gruppe1.web.front. Og den applikationsservertjeneste, den får adgang til, kaldes ok-app.gruppe1.web.front. Hver tjeneste har et manifest, som specificerer alle de nødvendige oplysninger til placering på specifikke maskiner: hvor mange ressourcer denne opgave bruger, hvilken konfiguration er nødvendig for den, hvor mange replikaer der skal være, egenskaber til håndtering af fejl i denne tjeneste. Og efter at tjenesten er placeret direkte på maskinerne, vises dens forekomster. De er også navngivet entydigt - som instansnummer og tjenestenavn: 1.ok-web.group1.web.front, 2.ok-web.group1.web.front, …

Dette er meget praktisk: Ved kun at se på navnet på den kørende container, kan vi straks finde ud af meget.

Lad os nu se nærmere på, hvad disse instanser rent faktisk udfører: opgaver.

Opgaveisolationsklasser

Alle opgaver i OK (og sandsynligvis overalt) kan opdeles i grupper:

  • Short Latency Opgaver - prod. For sådanne opgaver og tjenester er svarforsinkelsen (latency) meget vigtig, hvor hurtigt hver af anmodningerne vil blive behandlet af systemet. Eksempler på opgaver: webfronter, caches, applikationsservere, OLTP-lagring mv.
  • Beregningsproblemer - batch. Her er behandlingshastigheden for hver specifik anmodning ikke vigtig. For dem er det vigtigt, hvor mange beregninger denne opgave vil gøre i en vis (lang) periode (gennemstrømning). Disse vil være alle opgaver i MapReduce, Hadoop, machine learning, statistik.
  • Baggrundsopgaver - inaktiv. For sådanne opgaver er hverken latens eller gennemløb særlig vigtig. Dette omfatter forskellige tests, migreringer, genberegninger og konvertering af data fra et format til et andet. På den ene side ligner de beregnede, på den anden side er det lige meget for os, hvor hurtigt de bliver gennemført.

Lad os se, hvordan sådanne opgaver bruger ressourcer, for eksempel den centrale processor.

Opgaver med kort forsinkelse. En sådan opgave vil have et CPU-forbrugsmønster, der ligner dette:

One-cloud - datacenterniveau OS i Odnoklassniki

En anmodning fra brugeren modtages til behandling, opgaven begynder at bruge alle tilgængelige CPU-kerner, behandler den, returnerer et svar, venter på den næste anmodning og stopper. Den næste anmodning kom - igen valgte vi alt, hvad der var der, beregnede det og venter på den næste.

For at garantere den minimale latens for en sådan opgave, skal vi tage de maksimale ressourcer, den bruger og reservere det nødvendige antal kerner på minion (den maskine, der skal udføre opgaven). Så vil reservationsformlen for vores problem være som følger:

alloc: cpu = 4 (max)

og hvis vi har en minion-maskine med 16 kerner, så kan præcis fire sådanne opgaver placeres på den. Vi bemærker især, at det gennemsnitlige processorforbrug af sådanne opgaver ofte er meget lavt - hvilket er indlysende, da en betydelig del af tiden, opgaven venter på en anmodning og ikke gør noget.

Beregningsopgaver. Deres mønster vil være lidt anderledes:

One-cloud - datacenterniveau OS i Odnoklassniki

Det gennemsnitlige CPU-ressourceforbrug til sådanne opgaver er ret højt. Ofte ønsker vi, at en beregningsopgave skal gennemføres inden for en vis tid, så vi skal reservere det mindste antal processorer, den har brug for, så hele beregningen afsluttes inden for en acceptabel tid. Dens reservationsformel vil se sådan ud:

alloc: cpu = [1,*)

"Placer den venligst på en minion, hvor der er mindst én fri kerne, og så mange, som der er, vil den fortære alt."

Her er effektiviteten i brugen allerede meget bedre end på opgaver med kort forsinkelse. Men gevinsten bliver meget større, hvis du kombinerer begge typer opgaver på én minion-maskine og fordeler dens ressourcer på farten. Når en opgave med kort forsinkelse kræver en processor, modtager den den med det samme, og når ressourcerne ikke længere er nødvendige, overføres de til beregningsopgaven, det vil sige noget som dette:

One-cloud - datacenterniveau OS i Odnoklassniki

Men hvordan gør man det?

Lad os først se på prod og dens allok: cpu = 4. Vi skal reservere fire kerner. I Docker-kørsel kan dette gøres på to måder:

  • Brug af muligheden --cpuset=1-4, dvs. allokere fire specifikke kerner på maskinen til opgaven.
  • Brug --cpuquota=400_000 --cpuperiod=100_000, tildel en kvote for processortid, dvs. angiv, at hver 100 ms i realtid bruger opgaven ikke mere end 400 ms processortid. De samme fire kerner opnås.

Men hvilken af ​​disse metoder er velegnet?

cpuset ser ret attraktivt ud. Opgaven har fire dedikerede kerner, hvilket betyder, at processorcache vil fungere så effektivt som muligt. Dette har også en ulempe: vi skulle påtage os opgaven med at fordele beregninger på tværs af de ubelastede kerner på maskinen i stedet for OS, og det er en ret ikke-triviel opgave, især hvis vi forsøger at placere batch-opgaver på en sådan en maskine. Tests har vist, at muligheden med en kvote er bedre egnet her: På denne måde har operativsystemet mere frihed til at vælge kernen til at udføre opgaven på nuværende tidspunkt, og processortiden fordeles mere effektivt.

Lad os finde ud af, hvordan man foretager reservationer i Docker baseret på minimumsantallet af kerner. Kontingentet for batch-opgaver er ikke længere gældende, fordi der ikke er behov for at begrænse maksimum, det er nok kun at garantere minimum. Og her passer muligheden godt docker run --cpushares.

Vi blev enige om, at hvis en batch kræver garanti for mindst én kerne, så angiver vi --cpushares=1024, og hvis der er mindst to kerner, så angiver vi --cpushares=2048. CPU-andele forstyrrer ikke på nogen måde fordelingen af ​​processortid, så længe der er nok af den. Så hvis prod i øjeblikket ikke bruger alle sine fire kerner, er der intet, der begrænser batch-opgaver, og de kan bruge ekstra processortid. Men i en situation, hvor der er mangel på processorer, hvis prod har forbrugt alle fire af sine kerner og har nået sin kvote, vil den resterende processortid blive delt proportionalt med cpushares, dvs. i en situation med tre frie kerner, vil en være givet til en opgave med 1024 cpushares, og de resterende to vil blive givet til en opgave med 2048 cpushares.

Men at bruge kvote og aktier er ikke nok. Vi skal sikre os, at en opgave med en kort forsinkelse får prioritet frem for en batch-opgave ved tildeling af processortid. Uden en sådan prioritering vil batchopgaven optage al processortid på det tidspunkt, hvor den er nødvendig for prod. Der er ingen containerprioriteringsmuligheder i Docker run, men Linux CPU-planlægningspolitikker er nyttige. Du kan læse om dem i detaljer her, og inden for rammerne af denne artikel vil vi gennemgå dem kort:

  • SCHED_OTHER
    Som standard modtager alle normale brugerprocesser på en Linux-maskine.
  • SCHED_BATCH
    Designet til ressourcekrævende processer. Når en opgave placeres på en processor, indføres en såkaldt aktiveringsstraf: en sådan opgave er mindre tilbøjelig til at modtage processorressourcer, hvis den i øjeblikket bruges af en opgave med SCHED_OTHER
  • SCHED_IDLE
    En baggrundsproces med en meget lav prioritet, endda lavere end nice -19. Vi bruger vores open source-bibliotek en-nio, for at indstille den nødvendige politik, når containeren startes ved at ringe

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

Men selvom du ikke programmerer i Java, kan det samme gøres ved at bruge chrt-kommandoen:

chrt -i 0 $pid

Lad os opsummere alle vores isolationsniveauer i én tabel for klarhedens skyld:

Isoleringsklasse
Alloc eksempel
Docker-kørselsmuligheder
sched_setscheduler chrt*

Prod
cpu = 4
--cpuquota=400000 --cpuperiod=100000
SCHED_OTHER

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

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

*Hvis du laver chrt inde fra en container, har du muligvis brug for sys_nice-kapaciteten, fordi Docker som standard fjerner denne funktion, når containeren startes.

Men opgaver forbruger ikke kun processoren, men også trafik, hvilket påvirker latensen af ​​en netværksopgave endnu mere end den forkerte allokering af processorressourcer. Derfor ønsker vi naturligvis at få præcis det samme billede for trafikken. Det vil sige, at når en prod-opgave sender nogle pakker til netværket, begrænser vi den maksimale hastighed (formel alloc: lan=[*,500mbps) ), med hvilken prod kan gøre dette. Og for batch garanterer vi kun den minimale gennemstrømning, men begrænser ikke maksimum (formel alloc: lan=[10Mbps,*) ) I dette tilfælde bør prod-trafik have prioritet over batch-opgaver.
Her har Docker ikke nogen primitiver, som vi kan bruge. Men det kommer os til hjælp Linux Trafikkontrol. Vi var i stand til at opnå det ønskede resultat ved hjælp af disciplin Hierarkisk Fair Service Curve. Med dens hjælp skelner vi mellem to trafikklasser: højprioritet prod og lavprioritet batch/tomgang. Som et resultat er konfigurationen for udgående trafik sådan her:

One-cloud - datacenterniveau OS i Odnoklassniki

her er 1:0 "rod-qdisc" af hsfc-disciplinen; 1:1 - hsfc børneklasse med en samlet båndbreddegrænse på 8 Gbit/s, under hvilken børneklasserne for alle containere er placeret; 1:2 - hsfc-underklassen er fælles for alle batch- og inaktive opgaver med en "dynamisk" grænse, som diskuteres nedenfor. De resterende hsfc-underklasser er dedikerede klasser til aktuelt kørende prod-containere med grænser svarende til deres manifester - 450 og 400 Mbit/s. Hver hsfc-klasse er tildelt en qdisc-kø fq eller fq_codel, afhængigt af Linux-kerneversionen, for at undgå pakketab under trafikudbrud.

Typisk tjener tc-discipliner kun til at prioritere udgående trafik. Men vi vil også prioritere indgående trafik - når alt kommer til alt, kan en eller anden batch-opgave nemt vælge hele den indgående kanal og modtage for eksempel en stor batch af inputdata til map&reduce. Til dette bruger vi modulet ifb, som opretter en ifbX virtuel grænseflade for hver netværksgrænseflade og omdirigerer indgående trafik fra grænsefladen til udgående trafik på ifbX. Yderligere, for ifbX, arbejder alle de samme discipliner for at kontrollere udgående trafik, for hvilken hsfc-konfigurationen vil være meget ens:

One-cloud - datacenterniveau OS i Odnoklassniki

Under eksperimenterne fandt vi ud af, at hsfc viser de bedste resultater, når 1:2-klassen af ​​ikke-prioriteret batch-/tomgangstrafik er begrænset på minion-maskiner til ikke mere end en bestemt fri bane. Ellers har ikke-prioriteret trafik for stor indflydelse på forsinkelsen af ​​prod-opgaver. miniond bestemmer den aktuelle mængde ledig båndbredde hvert sekund, og måler det gennemsnitlige trafikforbrug for alle prod-opgaver for en given minion One-cloud - datacenterniveau OS i Odnoklassniki og trække det fra netværksgrænsefladebåndbredden One-cloud - datacenterniveau OS i Odnoklassniki med en lille margin, dvs.

One-cloud - datacenterniveau OS i Odnoklassniki

Bånd er defineret uafhængigt for indgående og udgående trafik. Og i henhold til de nye værdier omkonfigurerer miniond den ikke-prioriterede klassegrænse 1:2.

Således implementerede vi alle tre isolationsklasser: prod, batch og inaktiv. Disse klasser har stor indflydelse på opgavernes præstationskarakteristika. Derfor besluttede vi at placere denne egenskab øverst i hierarkiet, så når man ser på navnet på den hierarkiske kø, ville det umiddelbart være klart, hvad vi har at gøre med:

One-cloud - datacenterniveau OS i Odnoklassniki

Alle vores venner web и musik fronterne placeres så i hierarkiet under prod. Lad os f.eks. placere tjenesten under batch musikkatalog, som med jævne mellemrum kompilerer et katalog over numre fra et sæt mp3-filer, der er uploadet til Odnoklassniki. Et eksempel på en tjeneste under inaktiv ville være musik transformer, som normaliserer musikkens lydstyrkeniveau.

Med de ekstra linjer fjernet igen, kan vi skrive vores servicenavne fladere ved at tilføje opgaveisoleringsklassen til slutningen af ​​det fulde servicenavn: web.front.prod, catalog.music.batch, transformer.musik.tomgang.

Og nu, når vi ser på navnet på tjenesten, forstår vi ikke kun hvilken funktion den udfører, men også dens isolationsklasse, hvilket betyder dens kritikalitet osv.

Alt er fantastisk, men der er én bitter sandhed. Det er umuligt fuldstændigt at isolere opgaver, der kører på én maskine.

Hvad vi formåede at opnå: hvis batch forbruges intensivt kun CPU-ressourcer, så gør den indbyggede Linux CPU-planlægger sit arbejde meget godt, og der er praktisk talt ingen indflydelse på prod-opgaven. Men hvis denne batch-opgave begynder at arbejde aktivt med hukommelsen, vises den gensidige indflydelse allerede. Dette sker, fordi prod-opgaven "vaskes ud" af processorens hukommelsescaches - som følge heraf øges cache-misser, og processoren behandler prod-opgaven langsommere. En sådan batch-opgave kan øge latensen af ​​vores typiske prod-beholder med 10 %.

Isolering af trafik er endnu sværere på grund af det faktum, at moderne netværkskort har en intern kø af pakker. Hvis pakken fra batch-opgaven kommer der først, så vil den være den første, der sendes over kablet, og der kan ikke gøres noget ved det.

Derudover har vi indtil videre kun formået at løse problemet med at prioritere TCP-trafik: hsfc-tilgangen virker ikke for UDP. Og selv i tilfælde af TCP-trafik, hvis batch-opgaven genererer meget trafik, giver dette også omkring 10% stigning i forsinkelsen af ​​prod-opgaven.

fejltolerance

Et af målene ved udvikling af one-cloud var at forbedre fejltolerancen for Odnoklassniki. Derfor vil jeg gerne overveje mere detaljeret mulige scenarier for fejl og ulykker. Lad os starte med et simpelt scenario - en containerfejl.

Selve beholderen kan fejle på flere måder. Dette kan være en form for eksperiment, fejl eller fejl i manifestet, på grund af hvilken prod-opgaven begynder at forbruge flere ressourcer end angivet i manifestet. Vi havde en sag: en udvikler implementerede en kompleks algoritme, omarbejdede den mange gange, overtænkte sig selv og blev så forvirret, at problemet til sidst gik ind i en meget ikke-triviel løkke. Og da prod-opgaven har en højere prioritet end alle andre på de samme håndlangere, begyndte den at forbruge alle tilgængelige processorressourcer. I denne situation reddede isolation, eller rettere CPU-tidskvoten, dagen. Hvis en opgave får tildelt en kvote, vil opgaven ikke tære mere. Derfor mærkede batch- og andre prod-opgaver, der kørte på samme maskine, ikke noget.

Det andet mulige problem er, at beholderen falder. Og her redder genstartspolitikker os, alle kender dem, Docker selv gør et godt stykke arbejde. Næsten alle prod-opgaver har en altid genstartspolitik. Nogle gange bruger vi on_failure til batch-opgaver eller til fejlretning af prod-containere.

Hvad kan du gøre, hvis en hel minion ikke er tilgængelig?

Kør selvfølgelig beholderen på en anden maskine. Den interessante del her er, hvad der sker med den eller de IP-adresser, der er tildelt containeren.

Vi kan tildele containere de samme IP-adresser som de minion-maskiner, som disse containere kører på. Når containeren derefter startes på en anden maskine, ændres dens IP-adresse, og alle klienter skal forstå, at containeren er flyttet, og nu skal de gå til en anden adresse, hvilket kræver en separat Service Discovery-tjeneste.

Service Discovery er praktisk. Der findes mange løsninger på markedet med forskellige grader af fejltolerance til at organisere et serviceregister. Ofte implementerer sådanne løsninger load balancer-logik, lagrer yderligere konfiguration i form af KV-lagring osv.
Vi vil dog gerne undgå behovet for at implementere et separat register, fordi det vil betyde, at der indføres et kritisk system, der bruges af alle tjenester i produktionen. Det betyder, at dette er et potentielt fejlpunkt, og du skal vælge eller udvikle en meget fejltolerant løsning, som naturligvis er meget svær, tidskrævende og dyr.

Og endnu en stor ulempe: For at vores gamle infrastruktur kan fungere med den nye, skal vi omskrive absolut alle opgaver for at bruge en form for Service Discovery-system. Der er MEGET arbejde, og nogle steder er det næsten umuligt, når det kommer til enheder på lavt niveau, der fungerer på OS-kerneniveau eller direkte med hardwaren. Implementering af denne funktionalitet vha. etablerede løsningsmønstre, som f.eks sidevogn ville nogle steder betyde en ekstra belastning, andre - en komplikation af driften og yderligere fejlscenarier. Vi ønskede ikke at komplicere tingene, så vi besluttede at gøre brugen af ​​Service Discovery valgfri.

I one-cloud følger IP'en containeren, dvs. hver opgaveinstans har sin egen IP-adresse. Denne adresse er "statisk": den tildeles hver forekomst, når tjenesten først sendes til skyen. Hvis en tjeneste havde et andet antal forekomster i løbet af sin levetid, vil den i sidste ende blive tildelt lige så mange IP-adresser, som der var maksimale forekomster.

Efterfølgende ændres disse adresser ikke: De tildeles én gang og fortsætter med at eksistere i hele tjenestens levetid i produktionen. IP-adresser følger containere på tværs af netværket. Hvis containeren bliver overført til en anden minion, så følger adressen den.

Tilknytningen af ​​et tjenestenavn til dens liste over IP-adresser ændres således meget sjældent. Hvis du ser igen på navnene på de tjenesteforekomster, som vi nævnte i begyndelsen af ​​artiklen (1.ok-web.group1.web.front.prod, 2.ok-web.group1.web.front.prod, …), vil vi bemærke, at de ligner de FQDN'er, der bruges i DNS. Det er rigtigt, for at kortlægge navnene på tjenesteinstanser til deres IP-adresser bruger vi DNS-protokollen. Desuden returnerer denne DNS alle reserverede IP-adresser for alle containere - både kørende og stoppede (lad os sige, at der bruges tre replikaer, og vi har fem adresser reserveret der - alle fem vil blive returneret). Klienter, der har modtaget denne information, vil forsøge at etablere en forbindelse til alle fem replikaer - og dermed afgøre, hvilke der virker. Denne mulighed for at bestemme tilgængelighed er meget mere pålidelig; den involverer hverken DNS eller Service Discovery, hvilket betyder, at der ikke er nogen vanskelige problemer at løse med at sikre relevansen af ​​information og fejltolerance for disse systemer. Desuden kan vi i kritiske tjenester, som driften af ​​hele portalen afhænger af, slet ikke bruge DNS, men blot indtaste IP-adresser i konfigurationen.

Implementering af en sådan IP-overførsel bag containere kan være ikke-triviel - og vi vil se på, hvordan det fungerer med følgende eksempel:

One-cloud - datacenterniveau OS i Odnoklassniki

Lad os sige, at one-cloud masteren giver kommandoen til minion M1 om at køre 1.ok-web.group1.web.front.prod med adresse 1.1.1.1. Arbejder på en håndlangere FUGL, som annoncerer denne adresse til specielle servere rutereflektor. Sidstnævnte har en BGP-session med netværkshardwaren, hvortil ruten for adresse 1.1.1.1 på M1 er oversat. M1 dirigerer pakker inde i containeren ved hjælp af Linux. Der er tre rutereflektorservere, da dette er en meget kritisk del af one-cloud-infrastrukturen - uden dem vil netværket i one-cloud ikke fungere. Vi placerer dem i forskellige stativer, om muligt placeret i forskellige rum i datacentret, for at reducere sandsynligheden for, at alle tre fejler på samme tid.

Lad os nu antage, at forbindelsen mellem one-cloud masteren og M1 minion er tabt. One-cloud masteren vil nu handle ud fra den antagelse, at M1 har fejlet fuldstændigt. Det vil sige, at den vil give kommandoen til M2 minion at starte web.gruppe1.web.front.prod med samme adresse 1.1.1.1. Nu har vi to modstridende ruter på netværket for 1.1.1.1: på M1 og på M2. For at løse sådanne konflikter bruger vi Multi Exit Discriminator, som er specificeret i BGP-meddelelsen. Dette er et tal, der viser vægten af ​​den annoncerede rute. Blandt de modstridende ruter vil ruten med den laveste MED-værdi blive valgt. One-cloud masteren understøtter MED som en integreret del af container IP-adresser. For første gang skrives adressen med en tilstrækkelig stor MED = 1. I situationen med en sådan nødcontaineroverførsel reducerer skibsføreren MED, og ​​M000 vil allerede modtage kommandoen om at annoncere adressen 000 med MED = 2 1.1.1.1. Den instans, der kører på M999, vil forblive på, i dette tilfælde er der ingen forbindelse, og hans videre skæbne interesserer os kun lidt, indtil forbindelsen med mesteren er genoprettet, hvor han vil blive stoppet som et gammelt take.

ulykker

Alle datacenterstyringssystemer håndterer altid mindre fejl acceptabelt. Containeroverløb er normen næsten overalt.

Lad os se på, hvordan vi håndterer en nødsituation, såsom et strømsvigt i et eller flere rum i et datacenter.

Hvad betyder en ulykke for et datacenterstyringssystem? Først og fremmest er dette en massiv engangsfejl på mange maskiner, og kontrolsystemet skal migrere en masse containere på samme tid. Men hvis katastrofen er meget stor, kan det ske, at alle opgaver ikke kan omfordeles til andre håndlangere, fordi datacentrets ressourcekapacitet falder til under 100 % af belastningen.

Ofte er ulykker ledsaget af svigt af kontrollaget. Dette kan ske på grund af svigt af dets udstyr, men oftere på grund af det faktum, at ulykker ikke testes, og selve kontrollaget falder på grund af den øgede belastning.

Hvad kan du gøre ved alt dette?

Massemigrationer betyder, at der foregår et stort antal aktiviteter, migreringer og implementeringer i infrastrukturen. Hver af migreringerne kan tage noget tid påkrævet at levere og pakke containerbilleder ud til håndlangere, lancere og initialisere containere osv. Derfor er det ønskeligt, at vigtigere opgaver lanceres før mindre vigtige.

Lad os igen se på hierarkiet af tjenester, vi er bekendt med, og prøve at beslutte, hvilke opgaver vi vil køre først.

One-cloud - datacenterniveau OS i Odnoklassniki

Det er naturligvis de processer, der er direkte involveret i behandlingen af ​​brugeranmodninger, dvs. prod. Det angiver vi med placeringsprioritet — et nummer, der kan tildeles køen. Hvis en kø har en højere prioritet, placeres dens tjenester først.

På prod tildeler vi højere prioritet, 0; på batch - lidt lavere, 100; på tomgang - endnu lavere, 200. Prioriteter anvendes hierarkisk. Alle opgaver lavere i hierarkiet vil have en tilsvarende prioritet. Hvis vi ønsker, at caches inde i prod skal lanceres før frontends, så prioriterer vi cache = 0 og front subqueues = 1. Hvis vi f.eks. ønsker, at hovedportalen skal lanceres fra fronterne først, og kun musikfronten så kan vi tildele en lavere prioritet til sidstnævnte - 10.

Det næste problem er mangel på ressourcer. Så en stor mængde udstyr, hele haller i datacentret, fejlede, og vi relancerede så mange tjenester, at der nu ikke er ressourcer nok til alle. Du skal beslutte, hvilke opgaver du skal ofre for at holde de vigtigste kritiske tjenester kørende.

One-cloud - datacenterniveau OS i Odnoklassniki

I modsætning til placeringsprioritet kan vi ikke vilkårligt ofre alle batch-opgaver; nogle af dem er vigtige for driften af ​​portalen. Derfor har vi fremhævet særskilt forkøbsprioritet opgaver. Når den er placeret, kan en opgave med højere prioritet foregribe, dvs. stoppe, en opgave med lavere prioritet, hvis der ikke er flere frie håndlangere. I dette tilfælde vil en opgave med lav prioritet formentlig forblive uplaceret, det vil sige, at der ikke længere vil være en passende minion til den med tilstrækkelige ledige ressourcer.

I vores hierarki er det meget simpelt at angive en forhåndsprioritet, således at prod- og batch-opgaver foregriber eller stopper ledige opgaver, men ikke hinanden, ved at angive en prioritet for inaktiv lig med 200. Ligesom i tilfældet med placeringsprioritet, har vi kan bruge vores hierarki til at beskrive mere komplekse regler. Lad os for eksempel indikere, at vi ofrer musikfunktionen, hvis vi ikke har nok ressourcer til hovedwebportalen, og sætter prioriteten for de tilsvarende noder lavere: 10.

Hele DC ulykker

Hvorfor kan hele datacentret fejle? Element. Var et godt indlæg orkanen påvirkede arbejdet i datacentret. Elementerne kan betragtes som hjemløse, der engang brændte optikken i manifolden, og datacentret mistede fuldstændig kontakten med andre websteder. Årsagen til fejlen kan også være en menneskelig faktor: Operatøren vil udstede en sådan kommando, at hele datacentret falder. Dette kan ske på grund af en stor fejl. Generelt er datacentre kollaps ikke ualmindeligt. Dette sker for os en gang hvert par måneder.

Og det er det, vi gør for at forhindre nogen i at tweete #alive.

Den første strategi er isolation. Hver one-cloud-instans er isoleret og kan kun administrere maskiner i ét datacenter. Det vil sige, at tabet af en sky på grund af fejl eller forkerte operatørkommandoer er tabet af kun ét datacenter. Vi er klar til dette: Vi har en redundanspolitik, hvor replikaer af applikationen og data er placeret i alle datacentre. Vi bruger fejltolerante databaser og tester med jævne mellemrum for fejl.
Siden vi i dag har fire datacentre, betyder det fire separate, fuldstændigt isolerede forekomster af én sky.

Denne tilgang beskytter ikke kun mod fysisk fejl, men kan også beskytte mod operatørfejl.

Hvad kan man ellers gøre med den menneskelige faktor? Når en operatør giver skyen en mærkelig eller potentielt farlig kommando, kan han pludselig blive bedt om at løse et lille problem for at se, hvor godt han tænkte. For eksempel, hvis dette er en slags massestop af mange replikaer eller bare en mærkelig kommando - at reducere antallet af replikaer eller ændre navnet på billedet, og ikke kun versionsnummeret i det nye manifest.

One-cloud - datacenterniveau OS i Odnoklassniki

Resultaterne af

Karakteristiske træk ved one-cloud:

  • Hierarkisk og visuel navngivningsordning for tjenester og containere, som giver dig mulighed for meget hurtigt at finde ud af, hvad opgaven er, hvad den vedrører, og hvordan den fungerer, og hvem der har ansvaret for den.
  • Vi anvender vores teknik til at kombinere produkt- og batch-opgaver på håndlangere for at forbedre effektiviteten af ​​maskindeling. I stedet for cpuset bruger vi CPU-kvoter, shares, CPU-planlægningspolitikker og Linux QoS.
  • Det var ikke muligt fuldstændigt at isolere containere, der kørte på samme maskine, men deres gensidige indflydelse forbliver inden for 20 %.
  • At organisere tjenester i et hierarki hjælper med automatisk gendannelse af katastrofer ved hjælp af placerings- og forkøbsprioriteter.

Ofte stillede spørgsmål

Hvorfor tog vi ikke en færdig løsning?

  • Forskellige klasser af opgaveisolering kræver forskellig logik, når de placeres på håndlangere. Hvis prod-opgaver kan placeres ved blot at reservere ressourcer, skal der placeres batch- og inaktive opgaver, der sporer den faktiske udnyttelse af ressourcer på minion-maskiner.
  • Behovet for at tage hensyn til ressourcer, der forbruges af opgaver, såsom:
    • netværks båndbredde;
    • typer og "spindler" af diske.
  • Behovet for at angive prioriteterne for tjenester under nødberedskab, rettigheder og kvoter for kommandoer for ressourcer, som løses ved hjælp af hierarkiske køer i én sky.
  • Behovet for at have menneskelig navngivning af containere for at reducere responstiden på ulykker og hændelser
  • Umuligheden af ​​en engangsimplementering af Service Discovery; behovet for at sameksistere i lang tid med opgaver hostet på hardware-hosts - noget der løses ved at "statiske" IP-adresser følger containere, og som følge heraf behovet for unik integration med en stor netværksinfrastruktur.

Alle disse funktioner ville kræve væsentlige ændringer af eksisterende løsninger, så de passer til os, og efter at have vurderet arbejdsmængden indså vi, at vi kunne udvikle vores egen løsning med nogenlunde samme lønomkostninger. Men din løsning bliver meget nemmere at betjene og udvikle – den indeholder ikke unødvendige abstraktioner, der understøtter funktionalitet, som vi ikke har brug for.

Til dem, der læser de sidste linjer, tak for jeres tålmodighed og opmærksomhed!

Kilde: www.habr.com

Tilføj en kommentar