One-cloud - datasenternivå OS i Odnoklassniki

One-cloud - datasenternivå OS i Odnoklassniki

Aloha, folkens! Mitt navn er Oleg Anastasyev, jeg jobber i Odnoklassniki i plattformteamet. Og foruten meg, er det mye maskinvare som fungerer i Odnoklassniki. Vi har fire datasentre med ca 500 rack med mer enn 8 tusen servere. På et visst tidspunkt innså vi at innføringen av et nytt styringssystem ville tillate oss å laste utstyr mer effektivt, forenkle tilgangsadministrasjon, automatisere (om)distribusjon av dataressurser, fremskynde lanseringen av nye tjenester og fremskynde svar. til store ulykker.

Hva kom ut av det?

Foruten meg og en haug med maskinvare, er det også folk som jobber med denne maskinvaren: ingeniører som befinner seg direkte i datasentre; nettverkere som setter opp nettverksprogramvare; administratorer, eller SREer, som gir infrastruktur motstandskraft; og utviklingsteam, hver av dem er ansvarlig for deler av portalens funksjoner. Programvaren de lager fungerer omtrent slik:

One-cloud - datasenternivå OS i Odnoklassniki

Brukerforespørsler mottas både på frontene av hovedportalen www.ok.ru, og på andre, for eksempel på musikk-API-frontene. For å behandle forretningslogikken ringer de applikasjonsserveren, som ved behandling av forespørselen kaller de nødvendige spesialiserte mikrotjenestene - en graf (graf over sosiale forbindelser), brukerbuffer (cache av brukerprofiler), etc.

Hver av disse tjenestene er distribuert på mange maskiner, og hver av dem har ansvarlige utviklere som er ansvarlige for modulenes funksjon, drift og teknologisk utvikling. Alle disse tjenestene kjører på maskinvareservere, og inntil nylig lanserte vi nøyaktig én oppgave per server, det vil si at den var spesialisert for en spesifikk oppgave.

Hvorfor det? Denne tilnærmingen hadde flere fordeler:

  • Lettet masseledelse. La oss si at en oppgave krever noen biblioteker, noen innstillinger. Og så blir serveren tilordnet nøyaktig én spesifikk gruppe, cfengine-policyen for denne gruppen er beskrevet (eller den er allerede beskrevet), og denne konfigurasjonen rulles sentralt og automatisk ut til alle servere i denne gruppen.
  • Forenklet diagnostikk. La oss si at du ser på den økte belastningen på den sentrale prosessoren og innser at denne belastningen kun kan genereres av oppgaven som kjører på denne maskinvareprosessoren. Jakten på noen å klandre tar slutt veldig raskt.
  • Forenklet overvåkning. Hvis noe er galt med serveren, rapporterer monitoren det, og du vet nøyaktig hvem som har skylden.

En tjeneste som består av flere replikaer tildeles flere servere - en for hver. Deretter allokeres dataressursen for tjenesten veldig enkelt: antall servere tjenesten har, den maksimale mengden ressurser den kan forbruke. "Enkelt" betyr her ikke at det er enkelt å bruke, men i den forstand at ressursallokering gjøres manuelt.

Denne tilnærmingen tillot oss også å gjøre det spesialiserte jernkonfigurasjoner for en oppgave som kjører på denne serveren. Hvis oppgaven lagrer store mengder data, så bruker vi en 4U-server med et chassis med 38 disker. Hvis oppgaven er rent beregningsmessig, kan vi kjøpe en billigere 1U-server. Dette er beregningsmessig effektivt. Blant annet lar denne tilnærmingen oss bruke fire ganger færre maskiner med en belastning som kan sammenlignes med ett vennlig sosialt nettverk.

Slik effektivitet i bruken av dataressurser bør også sikre økonomisk effektivitet, dersom vi går ut fra at det dyreste er servere. Lenge var maskinvaren den dyreste, og vi la mye arbeid i å redusere prisen på maskinvare, og kom opp med feiltoleransealgoritmer for å redusere kravene til maskinvarepålitelighet. Og i dag har vi nådd det stadiet der prisen på serveren har sluttet å være avgjørende. Hvis du ikke vurderer de siste eksotiske tingene, spiller den spesifikke konfigurasjonen av serverne i racket ingen rolle. Nå har vi et annet problem - prisen på plassen okkupert av serveren i datasenteret, det vil si plassen i stativet.

Da vi innså at dette var tilfelle, bestemte vi oss for å beregne hvor effektivt vi brukte stativene.
Vi tok prisen på den kraftigste serveren fra de økonomisk forsvarlige, regnet ut hvor mange slike servere vi kunne plassere i rack, hvor mange oppgaver vi ville kjøre på dem basert på den gamle modellen "én server = en oppgave" og hvor mye slike oppgaver kunne utnytte utstyret. De telte og felte tårer. Det viste seg at vår effektivitet i bruk av stativer er ca. 11 %. Konklusjonen er åpenbar: vi må øke effektiviteten ved bruk av datasentre. Det ser ut til at løsningen er åpenbar: du må kjøre flere oppgaver på en server samtidig. Men det er her vanskene begynner.

Massekonfigurasjon blir dramatisk mer komplisert - det er nå umulig å tilordne en gruppe til en server. Tross alt, nå kan flere oppgaver med forskjellige kommandoer startes på en server. I tillegg kan konfigurasjonen være motstridende for forskjellige applikasjoner. Diagnose blir også mer komplisert: Hvis du ser økt CPU- eller diskforbruk på en server, vet du ikke hvilken oppgave som forårsaker problemer.

Men det viktigste er at det ikke er noen isolasjon mellom oppgaver som kjører på samme maskin. Her er for eksempel en graf over gjennomsnittlig responstid for en serveroppgave før og etter at en annen beregningsapplikasjon ble lansert på samme server, på ingen måte relatert til den første – responstiden til hovedoppgaven har økt betydelig.

One-cloud - datasenternivå OS i Odnoklassniki

Selvfølgelig må du kjøre oppgaver enten i containere eller i virtuelle maskiner. Siden nesten alle oppgavene våre kjører under ett OS (Linux) eller er tilpasset for det, trenger vi ikke støtte mange forskjellige operativsystemer. Følgelig er virtualisering ikke nødvendig; på grunn av ekstra overhead vil det være mindre effektivt enn containerisering.

Som en implementering av containere for å kjøre oppgaver direkte på servere, er Docker en god kandidat: filsystembilder løser problemer med motstridende konfigurasjoner godt. Det faktum at bilder kan bestå av flere lag gjør at vi kan redusere mengden data som kreves for å distribuere dem på infrastrukturen betydelig, og separere felles deler i separate basislag. Da vil de grunnleggende (og mest voluminøse) lagene bufres ganske raskt gjennom hele infrastrukturen, og for å levere mange forskjellige typer applikasjoner og versjoner er det kun små lag som må overføres.

I tillegg gir et ferdig register og bildemerking i Docker oss ferdige primitiver for versjonering og levering av kode til produksjon.

Docker, som enhver annen lignende teknologi, gir oss et visst nivå av containerisolasjon ut av esken. For eksempel minneisolasjon - hver beholder er gitt en grense for bruk av maskinminne, utover hvilken den ikke vil forbruke. Du kan også isolere beholdere basert på CPU-bruk. For oss var imidlertid ikke standard isolasjon nok. Men mer om det nedenfor.

Direkte kjøring av containere på servere er bare en del av problemet. Den andre delen er relatert til hosting av containere på servere. Du må forstå hvilken container som kan plasseres på hvilken server. Dette er ikke en lett oppgave, fordi containere må plasseres på servere så tett som mulig uten å redusere hastigheten. Slik plassering kan også være vanskelig ut fra et feiltoleransesynspunkt. Ofte ønsker vi å plassere kopier av den samme tjenesten i forskjellige rack eller til og med i forskjellige rom i datasenteret, slik at hvis et rack eller rom svikter, mister vi ikke alle tjenestereplikaene umiddelbart.

Å distribuere containere manuelt er ikke et alternativ når du har 8 tusen servere og 8-16 tusen containere.

I tillegg ønsket vi å gi utviklere mer uavhengighet i ressursallokering slik at de selv kunne hoste sine tjenester i produksjon, uten hjelp fra en administrator. Samtidig ønsket vi å opprettholde kontrollen slik at noen mindre tjenester ikke skulle forbruke alle ressursene til datasentrene våre.

Selvfølgelig trenger vi et kontrolllag som vil gjøre dette automatisk.

Så vi kom til et enkelt og forståelig bilde som alle arkitekter elsker: tre firkanter.

One-cloud - datasenternivå OS i Odnoklassniki

one-cloud masters er en failover-klynge som er ansvarlig for skyorkestrering. Utvikleren sender et manifest til masteren, som inneholder all nødvendig informasjon for å være vert for tjenesten. Basert på det gir mesteren kommandoer til utvalgte undersåtter (maskiner designet for å kjøre containere). Minions har vår agent, som mottar kommandoen, utsteder kommandoene til Docker, og Docker konfigurerer linux-kjernen til å starte den tilsvarende beholderen. I tillegg til å utføre kommandoer, rapporterer agenten kontinuerlig til masteren om endringer i tilstanden til både minion-maskinen og containerne som kjører på den.

Ressurstildeling

La oss nå se på problemet med mer kompleks ressursallokering for mange undersåtter.

En dataressurs i én sky er:

  • Mengden prosessorkraft som forbrukes av en spesifikk oppgave.
  • Mengden minne som er tilgjengelig for oppgaven.
  • Nettverkstrafikk. Hver av minions har et spesifikt nettverksgrensesnitt med begrenset båndbredde, så det er umulig å fordele oppgaver uten å ta hensyn til mengden data de overfører over nettverket.
  • Disker. I tillegg, selvfølgelig, til plassen for disse oppgavene, tildeler vi også typen disk: HDD eller SSD. Disker kan betjene et begrenset antall forespørsler per sekund - IOPS. Derfor, for oppgaver som genererer mer IOPS enn en enkelt disk kan håndtere, tildeler vi også "spindler" - det vil si diskenheter som utelukkende må være reservert for oppgaven.

Så for noen tjenester, for eksempel for bruker-cache, kan vi registrere de forbrukte ressursene på denne måten: 400 prosessorkjerner, 2,5 TB minne, 50 Gbit/s trafikk i begge retninger, 6 TB HDD-plass plassert på 100 spindler. Eller i en mer kjent form som dette:

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

Ressurser for brukerbuffertjenester bruker bare en del av alle tilgjengelige ressurser i produksjonsinfrastrukturen. Derfor vil jeg sørge for at plutselig, på grunn av en operatørfeil eller ikke, bruker cachen ikke bruker mer ressurser enn det som er allokert til den. Det vil si at vi må begrense ressursene. Men hva kunne vi binde kvoten til?

La oss gå tilbake til vårt sterkt forenklede diagram over samspillet mellom komponenter og tegne det på nytt med flere detaljer - som dette:

One-cloud - datasenternivå OS i Odnoklassniki

Hva fanger oppmerksomheten din:

  • Nettgrensesnittet og musikken bruker isolerte klynger fra samme applikasjonsserver.
  • Vi kan skille de logiske lagene som disse klyngene tilhører: fronter, cacher, datalagring og administrasjonslag.
  • Frontenden er heterogen; den består av forskjellige funksjonelle undersystemer.
  • Cacher kan også bli spredt over delsystemet hvis data de cacher.

La oss tegne bildet på nytt:

One-cloud - datasenternivå OS i Odnoklassniki

Bah! Ja, vi ser et hierarki! Dette betyr at du kan fordele ressurser i større deler: tilordne en ansvarlig utvikler til en node i dette hierarkiet som tilsvarer det funksjonelle undersystemet (som "musikk" på bildet), og tilknytt en kvote til samme nivå i hierarkiet. Dette hierarkiet lar oss også organisere tjenester mer fleksibelt for enkel administrasjon. For eksempel deler vi hele nettet, siden dette er en veldig stor gruppering av servere, i flere mindre grupper, vist på bildet som gruppe1, gruppe2.

Ved å fjerne de ekstra linjene kan vi skrive hver node av bildet vårt i en flatere form: gruppe1.web.front, api.musikk.front, bruker-cache.cache.

Dette er hvordan vi kommer til begrepet "hierarkisk kø". Den har et navn som "group1.web.front". Det er tildelt en kvote for ressurser og brukerrettigheter. Vi vil gi personen fra DevOps rettighetene til å sende en tjeneste til køen, og en slik ansatt kan starte noe i køen, og personen fra OpsDev vil ha administratorrettigheter, og nå kan han administrere køen, tildele folk der, gi disse personene rettigheter osv. Tjenester som kjører på denne køen vil kjøre innenfor køens kvote. Hvis køens databehandlingskvote ikke er nok til å utføre alle tjenester samtidig, vil de bli utført sekvensielt, og dermed danne selve køen.

La oss se nærmere på tjenestene. En tjeneste har et fullt kvalifisert navn, som alltid inkluderer navnet på køen. Da vil frontwebtjenesten ha navnet ok-web.gruppe1.web.front. Og applikasjonsservertjenesten den får tilgang til vil bli kalt ok-app.gruppe1.web.front. Hver tjeneste har et manifest som spesifiserer all nødvendig informasjon for plassering på spesifikke maskiner: hvor mange ressurser denne oppgaven bruker, hvilken konfigurasjon er nødvendig for den, hvor mange replikaer det skal være, egenskaper for håndtering av feil i denne tjenesten. Og etter at tjenesten er plassert direkte på maskinene, vises forekomstene. De er også navngitt entydig - som forekomstnummer og tjenestenavn: 1.ok-web.group1.web.front, 2.ok-web.group1.web.front, …

Dette er veldig praktisk: ved kun å se på navnet på den løpende beholderen, kan vi umiddelbart finne ut mye.

La oss nå se nærmere på hva disse forekomstene faktisk utfører: oppgaver.

Oppgaveisolasjonsklasser

Alle oppgaver i OK (og sannsynligvis overalt) kan deles inn i grupper:

  • Short Latency Tasks - prod. For slike oppgaver og tjenester er svarforsinkelsen (latens) veldig viktig, hvor raskt hver av forespørslene vil bli behandlet av systemet. Eksempler på oppgaver: webfronter, cacher, applikasjonsservere, OLTP-lagring osv.
  • Regneproblemer - batch. Her er behandlingshastigheten for hver spesifikk forespørsel ikke viktig. For dem er det viktig hvor mange beregninger denne oppgaven vil gjøre i løpet av en viss (lang) tidsperiode (gjennomstrømning). Dette vil være alle oppgaver til MapReduce, Hadoop, maskinlæring, statistikk.
  • Bakgrunnsoppgaver - inaktiv. For slike oppgaver er verken latens eller gjennomstrømning veldig viktig. Dette inkluderer ulike tester, migreringer, omberegninger og konvertering av data fra ett format til et annet. På den ene siden ligner de beregnede, på den annen side spiller det ingen rolle for oss hvor raskt de blir fullført.

La oss se hvordan slike oppgaver bruker ressurser, for eksempel sentralprosessoren.

Oppgaver med kort forsinkelse. En slik oppgave vil ha et CPU-forbruksmønster som ligner på dette:

One-cloud - datasenternivå OS i Odnoklassniki

En forespørsel fra brukeren mottas for behandling, oppgaven begynner å bruke alle tilgjengelige CPU-kjerner, behandler den, returnerer et svar, venter på neste forespørsel og stopper. Neste forespørsel kom - igjen valgte vi alt som var der, beregnet det og venter på neste.

For å garantere minimum latens for en slik oppgave, må vi ta de maksimale ressursene den bruker og reservere det nødvendige antallet kjerner på minion (maskinen som skal utføre oppgaven). Da vil reservasjonsformelen for problemet vårt være som følger:

alloc: cpu = 4 (max)

og hvis vi har en minion-maskin med 16 kjerner, så kan nøyaktig fire slike oppgaver plasseres på den. Vi legger spesielt merke til at det gjennomsnittlige prosessorforbruket for slike oppgaver ofte er svært lavt – noe som er åpenbart, siden en betydelig del av tiden oppgaven venter på en forespørsel og ikke gjør noe.

Regneoppgaver. Mønsteret deres vil være litt annerledes:

One-cloud - datasenternivå OS i Odnoklassniki

Gjennomsnittlig CPU-ressursforbruk for slike oppgaver er ganske høyt. Ofte ønsker vi at en beregningsoppgave skal fullføres i løpet av en viss tid, så vi må reservere minimum antall prosessorer den trenger, slik at hele beregningen fullføres på en akseptabel tid. Reservasjonsformelen vil se slik ut:

alloc: cpu = [1,*)

"Vennligst plasser den på en minion der det er minst én ledig kjerne, og så mange som det er, vil den sluke alt."

Her er brukseffektiviteten allerede mye bedre enn på oppgaver med kort forsinkelse. Men gevinsten blir mye større hvis du kombinerer begge typer oppgaver på én minion-maskin og fordeler ressursene mens du er på farten. Når en oppgave med kort forsinkelse krever en prosessor, mottar den den umiddelbart, og når ressursene ikke lenger er nødvendige, overføres de til beregningsoppgaven, dvs. noe sånt som dette:

One-cloud - datasenternivå OS i Odnoklassniki

Men hvordan gjør jeg det?

La oss først se på prod og allokering: cpu = 4. Vi må reservere fire kjerner. I Docker run kan dette gjøres på to måter:

  • Med alternativ --cpuset=1-4, dvs. allokere fire spesifikke kjerner på maskinen til oppgaven.
  • Bruk --cpuquota=400_000 --cpuperiod=100_000, tilordne en kvote for prosessortid, dvs. angi at hver 100 ms sanntid oppgaven bruker ikke mer enn 400 ms prosessortid. De samme fire kjernene oppnås.

Men hvilken av disse metodene passer?

cpuset ser ganske attraktivt ut. Oppgaven har fire dedikerte kjerner, noe som gjør at prosessorcacher vil fungere så effektivt som mulig. Dette har også en ulempe: vi må påta oss oppgaven med å distribuere beregninger over de ubelastede kjernene til maskinen i stedet for OS, og dette er en ganske ikke-triviell oppgave, spesielt hvis vi prøver å plassere batchoppgaver på en slik maskin. Tester har vist at alternativet med kvote er bedre egnet her: På denne måten har operativsystemet større frihet til å velge kjernen for å utføre oppgaven i det aktuelle øyeblikket og prosessortiden fordeles mer effektivt.

La oss finne ut hvordan du gjør reservasjoner i Docker basert på minimum antall kjerner. Kvoten for batchoppgaver er ikke lenger gjeldende, fordi det ikke er behov for å begrense maksimum, det er nok å bare garantere minimum. Og her passer alternativet godt docker run --cpushares.

Vi ble enige om at hvis en batch krever en garanti for minst én kjerne, så indikerer vi --cpushares=1024, og hvis det er minst to kjerner, indikerer vi --cpushares=2048. CPU-andeler forstyrrer ikke på noen måte fordelingen av prosessortid så lenge det er nok av det. Derfor, hvis prod for øyeblikket ikke bruker alle sine fire kjerner, er det ingenting som begrenser batchoppgaver, og de kan bruke ekstra prosessortid. Men i en situasjon der det er mangel på prosessorer, hvis prod har konsumert alle fire kjerner og har nådd sin kvote, vil den gjenværende prosessortiden deles proporsjonalt med cpushares, dvs. i en situasjon med tre ledige kjerner, vil en være gitt til en oppgave med 1024 cpushares, og de resterende to vil bli gitt til en oppgave med 2048 cpushares.

Men å bruke kvote og aksjer er ikke nok. Vi må sørge for at en oppgave med kort forsinkelse får prioritet over en batchoppgave ved tildeling av prosessortid. Uten slik prioritering vil batchoppgaven ta opp all prosessortiden i det øyeblikket den er nødvendig av prod. Det er ingen alternativer for beholderprioritering i Docker run, men Linux CPU-planleggerpolicyer kommer godt med. Du kan lese om dem i detalj her, og innenfor rammen av denne artikkelen vil vi gå gjennom dem kort:

  • SCHED_OTHER
    Som standard mottar alle normale brukerprosesser på en Linux-maskin.
  • SCHED_BATCH
    Designet for ressurskrevende prosesser. Når du plasserer en oppgave på en prosessor, introduseres en såkalt aktiveringsstraff: en slik oppgave er mindre sannsynlig å motta prosessorressurser hvis den brukes av en oppgave med SCHED_OTHER
  • SCHED_IDLE
    En bakgrunnsprosess med svært lav prioritet, enda lavere enn nice -19. Vi bruker vårt åpen kildekode-bibliotek en-nio, for å angi den nødvendige policyen når du starter beholderen ved å ringe

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

Men selv om du ikke programmerer i Java, kan det samme gjøres ved å bruke chrt-kommandoen:

chrt -i 0 $pid

La oss oppsummere alle isolasjonsnivåene våre i én tabell for klarhet:

Isolasjonsklasse
Alloc Eksempel
Alternativer for Docker-kjøring
sched_setscheduler chrt*

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

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

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

*Hvis du gjør chrt fra innsiden av en container, kan det hende du trenger sys_nice-funksjonen, fordi Docker som standard fjerner denne muligheten når du starter containeren.

Men oppgaver bruker ikke bare prosessoren, men også trafikk, noe som påvirker ventetiden til en nettverksoppgave enda mer enn feil allokering av prosessorressurser. Derfor ønsker vi naturligvis å få akkurat det samme bildet for trafikken. Det vil si at når en prod-oppgave sender noen pakker til nettverket, begrenser vi maksimalhastigheten (formel allokering: lan=[*,500mbps) ), med hvilken prod kan gjøre dette. Og for batch garanterer vi bare minimum gjennomstrømning, men begrenser ikke maksimum (formel alloc: lan=[10Mbps,*) ) I dette tilfellet bør prod-trafikk prioriteres fremfor batchoppgaver.
Her har ikke Docker noen primitiver vi kan bruke. Men det kommer oss til hjelp Linux Trafikkkontroll. Vi klarte å oppnå ønsket resultat ved hjelp av disiplin Hierarkisk rettferdig tjenestekurve. Med dens hjelp skiller vi to klasser av trafikk: høyprioritet prod og lavprioritet batch/tomgang. Som et resultat er konfigurasjonen for utgående trafikk slik:

One-cloud - datasenternivå OS i Odnoklassniki

her er 1:0 "root qdisc" til hsfc-disiplinen; 1:1 - hsfc barneklasse med en total båndbreddegrense på 8 Gbit/s, der barneklassene til alle containere er plassert; 1:2 - hsfc-underklassen er felles for alle batch- og inaktive oppgaver med en "dynamisk" grense, som diskuteres nedenfor. De resterende hsfc-barneklassene er dedikerte klasser for for tiden kjørende prod-containere med grenser som tilsvarer deres manifester - 450 og 400 Mbit/s. Hver hsfc-klasse er tildelt en qdisc-kø fq eller fq_codel, avhengig av Linux-kjerneversjonen, for å unngå pakketap under trafikkutbrudd.

Vanligvis tjener tc-disipliner kun til å prioritere utgående trafikk. Men vi ønsker å prioritere innkommende trafikk også - tross alt kan en batchoppgave lett velge hele den innkommende kanalen, motta for eksempel en stor batch med inndata for map&reduce. Til dette bruker vi modulen ifb, som lager et virtuelt ifbX-grensesnitt for hvert nettverksgrensesnitt og omdirigerer innkommende trafikk fra grensesnittet til utgående trafikk på ifbX. Videre, for ifbX, fungerer alle de samme disiplinene for å kontrollere utgående trafikk, som hsfc-konfigurasjonen vil være veldig lik for:

One-cloud - datasenternivå OS i Odnoklassniki

I løpet av eksperimentene fant vi ut at hsfc viser de beste resultatene når 1:2-klassen med ikke-prioritert batch-/tomgangstrafikk er begrenset på minion-maskiner til ikke mer enn en viss fri bane. Ellers har ikke-prioritert trafikk for stor innvirkning på ventetiden til prod-oppgaver. miniond bestemmer gjeldende mengde ledig båndbredde hvert sekund, og måler det gjennomsnittlige trafikkforbruket for alle produksjonsoppgaver til en gitt minion One-cloud - datasenternivå OS i Odnoklassniki og trekke det fra nettverksgrensesnittets båndbredde One-cloud - datasenternivå OS i Odnoklassniki med liten margin, dvs.

One-cloud - datasenternivå OS i Odnoklassniki

Bånd er definert uavhengig for innkommende og utgående trafikk. Og i henhold til de nye verdiene, rekonfigurerer miniond den ikke-prioriterte klassegrensen 1:2.

Dermed implementerte vi alle tre isolasjonsklassene: prod, batch og inaktiv. Disse klassene påvirker i stor grad ytelsesegenskapene til oppgaver. Derfor bestemte vi oss for å plassere dette attributtet øverst i hierarkiet, slik at når man ser på navnet på den hierarkiske køen, vil det umiddelbart være klart hva vi har å gjøre med:

One-cloud - datasenternivå OS i Odnoklassniki

Alle vennene våre web и musikk frontene plasseres da i hierarkiet under prod. La oss for eksempel plassere tjenesten under batch musikkkatalog, som med jevne mellomrom kompilerer en katalog med spor fra et sett med mp3-filer lastet opp til Odnoklassniki. Et eksempel på en tjeneste under inaktiv vil være musikk transformator, som normaliserer musikkvolumnivået.

Med de ekstra linjene fjernet igjen, kan vi skrive tjenestenavnene våre flatere ved å legge til oppgaveisolasjonsklassen på slutten av det fullstendige tjenestenavnet: web.front.prod, catalog.music.batch, transformator.musikk.tomgang.

Og nå, når vi ser på navnet på tjenesten, forstår vi ikke bare hvilken funksjon den utfører, men også dens isolasjonsklasse, som betyr dens kritikalitet osv.

Alt er flott, men det er en bitter sannhet. Det er umulig å fullstendig isolere oppgaver som kjører på én maskin.

Hva vi klarte å oppnå: hvis batch forbruker intensivt bare CPU-ressurser, så gjør den innebygde Linux CPU-planleggeren jobben sin veldig bra, og det er praktisk talt ingen innvirkning på prod-oppgaven. Men hvis denne batchoppgaven begynner å aktivt arbeide med minne, vises den gjensidige påvirkningen allerede. Dette skjer fordi prod-oppgaven "vaskes ut" av prosessorens minnebuffere - som et resultat øker cache-missene, og prosessoren behandler prod-oppgaven saktere. En slik batchoppgave kan øke ventetiden til vår typiske produktbeholder med 10 %.

Å isolere trafikk er enda vanskeligere på grunn av at moderne nettverkskort har en intern kø av pakker. Hvis pakken fra batchoppgaven kommer dit først, vil den være den første som sendes over kabelen, og ingenting kan gjøres med det.

I tillegg har vi så langt bare klart å løse problemet med å prioritere TCP-trafikk: hsfc-tilnærmingen fungerer ikke for UDP. Og selv når det gjelder TCP-trafikk, hvis batch-oppgaven genererer mye trafikk, gir dette også ca. 10 % økning i forsinkelsen av prod-oppgaven.

feiltoleranse

Et av målene med utviklingen av én sky var å forbedre feiltoleransen til Odnoklassniki. Derfor vil jeg gjerne vurdere mer detaljert mulige scenarier for feil og ulykker. La oss starte med et enkelt scenario - en containerfeil.

Selve beholderen kan svikte på flere måter. Dette kan være en slags eksperiment, feil eller feil i manifestet, på grunn av at prod-oppgaven begynner å forbruke mer ressurser enn angitt i manifestet. Vi hadde en sak: en utvikler implementerte en kompleks algoritme, omarbeidet den mange ganger, overtenkte seg selv og ble så forvirret at problemet til slutt ble sluppet på en veldig ikke-triviell måte. Og siden prod-oppgaven har høyere prioritet enn alle andre på de samme minions, begynte den å forbruke alle tilgjengelige prosessorressurser. I denne situasjonen reddet isolasjon, eller snarere CPU-tidskvoten, dagen. Hvis en oppgave får tildelt en kvote, vil ikke oppgaven forbruke mer. Derfor merket ikke batch- og andre prodoppgaver som kjørte på samme maskin noe.

Det andre mulige problemet er at beholderen faller. Og her redder omstartspolicyer oss, alle kjenner dem, Docker selv gjør en god jobb. Nesten alle prod-oppgaver har en alltid omstart-policy. Noen ganger bruker vi on_failure for batchoppgaver eller for feilsøking av prod-beholdere.

Hva kan du gjøre hvis en hel minion er utilgjengelig?

Kjør selvsagt beholderen på en annen maskin. Den interessante delen her er hva som skjer med IP-adressen(e) som er tildelt beholderen.

Vi kan tildele containere de samme IP-adressene som minion-maskinene som disse containerne kjører på. Så, når beholderen startes på en annen maskin, endres IP-adressen, og alle klienter må forstå at beholderen har flyttet, og nå må de gå til en annen adresse, som krever en separat tjenesteoppdagelsestjeneste.

Service Discovery er praktisk. Det finnes mange løsninger på markedet med ulik grad av feiltoleranse for organisering av et tjenesteregister. Ofte implementerer slike løsninger lastbalanseringslogikk, lagrer tilleggskonfigurasjon i form av KV-lagring, etc.
Vi vil imidlertid unngå behovet for å implementere et eget register, fordi dette vil innebære å innføre et kritisk system som brukes av alle tjenester i produksjon. Dette betyr at dette er et potensielt feilpunkt, og du må velge eller utvikle en veldig feiltolerant løsning, som åpenbart er svært vanskelig, tidkrevende og kostbar.

Og enda en stor ulempe: for at den gamle infrastrukturen vår skal fungere med den nye, må vi omskrive absolutt alle oppgaver for å bruke et slags Service Discovery-system. Det er MYE arbeid, og noen steder er det nesten umulig når det kommer til enheter på lavt nivå som fungerer på OS-kjernenivå eller direkte med maskinvaren. Implementering av denne funksjonaliteten ved bruk av etablerte løsningsmønstre, som f.eks sidevogn ville bety noen steder en ekstra belastning, andre steder - en komplikasjon av drift og ytterligere feilscenarier. Vi ønsket ikke å komplisere ting, så vi bestemte oss for å gjøre bruken av Service Discovery valgfri.

I én sky følger IP-en beholderen, det vil si at hver oppgaveinstans har sin egen IP-adresse. Denne adressen er "statisk": den tildeles hver forekomst når tjenesten først sendes til skyen. Hvis en tjeneste hadde et annet antall forekomster i løpet av levetiden, vil den til slutt bli tildelt like mange IP-adresser som det var maksimale forekomster.

Deretter endres ikke disse adressene: de tildeles én gang og fortsetter å eksistere gjennom hele levetiden til tjenesten i produksjon. IP-adresser følger beholdere på tvers av nettverket. Hvis containeren blir overført til en annen minion, vil adressen følge den.

Tilordningen av et tjenestenavn til listen over IP-adresser endres derfor svært sjelden. Hvis du ser igjen på navnene på tjenesteforekomstene som vi nevnte i begynnelsen av artikkelen (1.ok-web.group1.web.front.prod, 2.ok-web.group1.web.front.prod, …), vil vi legge merke til at de ligner FQDN-ene som brukes i DNS. Det er riktig, for å kartlegge navnene på tjenesteforekomster til deres IP-adresser, bruker vi DNS-protokollen. Dessuten returnerer denne DNS-en alle reserverte IP-adresser til alle containere - både kjørende og stoppede (la oss si at tre replikaer brukes, og vi har fem adresser reservert der - alle fem vil bli returnert). Klienter, etter å ha mottatt denne informasjonen, vil prøve å etablere en forbindelse med alle fem replikaene - og dermed finne ut hvilke som fungerer. Dette alternativet for å bestemme tilgjengelighet er mye mer pålitelig; det involverer verken DNS eller Service Discovery, noe som betyr at det ikke er noen vanskelige problemer å løse for å sikre relevansen av informasjon og feiltoleranse for disse systemene. Dessuten, i kritiske tjenester som driften av hele portalen avhenger av, kan vi ikke bruke DNS i det hele tatt, men bare angi IP-adresser i konfigurasjonen.

Implementering av slik IP-overføring bak containere kan være ikke-trivielt – og vi skal se på hvordan det fungerer med følgende eksempel:

One-cloud - datasenternivå OS i Odnoklassniki

La oss si at one-cloud master gir kommandoen til minion M1 å kjøre 1.ok-web.gruppe1.web.front.prod med adresse 1.1.1.1. Jobber på en minion FUGL, som annonserer denne adressen til spesielle servere rutereflektor. Sistnevnte har en BGP-sesjon med nettverksmaskinvaren, hvor ruten til adresse 1.1.1.1 på M1 er oversatt. M1 ruter pakker inne i containeren ved hjelp av Linux. Det er tre rutereflektorservere, siden dette er en svært kritisk del av én-sky-infrastrukturen – uten dem vil ikke nettverket i én-sky fungere. Vi plasserer dem i forskjellige stativer, om mulig plassert i forskjellige rom i datasenteret, for å redusere sannsynligheten for at alle tre svikter samtidig.

La oss nå anta at forbindelsen mellom one-cloud master og M1 minion er tapt. En-sky-masteren vil nå handle ut fra en antagelse om at M1 har feilet fullstendig. Det vil si at den vil gi kommandoen til M2 minion om å starte web.gruppe1.web.front.prod med samme adresse 1.1.1.1. Nå har vi to motstridende ruter på nettverket for 1.1.1.1: på M1 og på M2. For å løse slike konflikter bruker vi Multi Exit Discriminator, som er spesifisert i BGP-kunngjøringen. Dette er et tall som viser vekten av den annonserte ruten. Blant de motstridende rutene vil ruten med lavere MED-verdi velges. One-cloud-masteren støtter MED som en integrert del av container-IP-adresser. For første gang skrives adressen med en tilstrekkelig stor MED = 1 000 000. I situasjonen med en slik nødcontaineroverføring reduserer masteren MED, og ​​M2 vil allerede motta kommandoen om å annonsere adressen 1.1.1.1 med MED = 999 999. Forekomsten som kjører på M1 vil forbli i dette tilfellet, det er ingen forbindelse, og hans videre skjebne interesserer oss lite før forbindelsen med masteren er gjenopprettet, da han vil bli stoppet som en gammel take.

ulykker

Alle datasenterstyringssystemer håndterer alltid mindre feil på en akseptabel måte. Beholderoverløp er normen nesten overalt.

La oss se på hvordan vi håndterer en nødsituasjon, for eksempel et strømbrudd i ett eller flere rom i et datasenter.

Hva betyr en ulykke for et datasenterstyringssystem? For det første er dette en massiv engangsfeil på mange maskiner, og kontrollsystemet må migrere mange containere samtidig. Men hvis katastrofen er veldig stor, kan det hende at alle oppgaver ikke kan omfordeles til andre undersåtter, fordi ressurskapasiteten til datasenteret faller under 100% av belastningen.

Ofte er ulykker ledsaget av svikt i kontrolllaget. Dette kan skje på grunn av feil på utstyret, men oftere på grunn av det faktum at ulykker ikke blir testet, og selve kontrolllaget faller på grunn av økt belastning.

Hva kan du gjøre med alt dette?

Massemigrasjoner betyr at det er et stort antall aktiviteter, migrasjoner og distribusjoner som skjer i infrastrukturen. Hver av migreringene kan ta noe tid som kreves for å levere og pakke ut containerbilder til undersåtter, lansere og initialisere containere osv. Derfor er det ønskelig at viktigere oppgaver lanseres før mindre viktige.

La oss se igjen på hierarkiet av tjenester vi er kjent med og prøve å bestemme hvilke oppgaver vi vil kjøre først.

One-cloud - datasenternivå OS i Odnoklassniki

Selvfølgelig er dette prosessene som er direkte involvert i behandlingen av brukerforespørsler, dvs. prod. Vi indikerer dette med plasseringsprioritet — et nummer som kan tildeles køen. Hvis en kø har høyere prioritet, plasseres dens tjenester først.

På prod tildeler vi høyere prioriteter, 0; på batch - litt lavere, 100; på tomgang - enda lavere, 200. Prioriteringer brukes hierarkisk. Alle oppgaver lavere i hierarkiet vil ha en tilsvarende prioritet. Hvis vi vil at cacher inne i prod skal lanseres før frontends, så prioriterer vi cache = 0 og front subqueues = 1. Hvis vi for eksempel ønsker at hovedportalen skal lanseres fra frontene først, og kun musikkfronten. da kan vi gi en lavere prioritet til sistnevnte - 10.

Det neste problemet er mangel på ressurser. Så en stor mengde utstyr, hele haller i datasenteret, sviktet, og vi relanserte så mange tjenester at det nå ikke er nok ressurser til alle. Du må bestemme hvilke oppgaver du skal ofre for å holde de viktigste kritiske tjenestene i gang.

One-cloud - datasenternivå OS i Odnoklassniki

I motsetning til plasseringsprioritet kan vi ikke vilkårlig ofre alle batchoppgaver; noen av dem er viktige for driften av portalen. Derfor har vi fremhevet separat forkjøpsprioritet oppgaver. Når den er plassert, kan en oppgave med høyere prioritet forhindre, dvs. stoppe, en oppgave med lavere prioritet hvis det ikke er flere ledige undersåtter. I dette tilfellet vil en oppgave med lav prioritet sannsynligvis forbli uplassert, det vil si at det ikke lenger vil være en passende minion for den med nok ledige ressurser.

I vårt hierarki er det veldig enkelt å spesifisere en forhåndsprioritet slik at prod- og batchoppgaver foregriper eller stopper ledige oppgaver, men ikke hverandre, ved å spesifisere en prioritet for inaktiv lik 200. Akkurat som i tilfellet med plasseringsprioritet, kan bruke vårt hierarki for å beskrive mer komplekse regler. La oss for eksempel indikere at vi ofrer musikkfunksjonen hvis vi ikke har nok ressurser til hovednettportalen, og setter prioriteten for de tilsvarende nodene lavere: 10.

Hele DC-ulykker

Hvorfor kan hele datasenteret mislykkes? Element. Var et bra innlegg orkanen påvirket arbeidet til datasenteret. Elementene kan betraktes som hjemløse som en gang brente optikken i manifolden, og datasenteret mistet fullstendig kontakten med andre nettsteder. Årsaken til feilen kan også være en menneskelig faktor: Operatøren vil gi en slik kommando at hele datasenteret vil falle. Dette kan skje på grunn av en stor feil. Generelt sett er det ikke uvanlig at datasentre kollapser. Dette skjer med oss ​​en gang i måneden.

Og dette er hva vi gjør for å forhindre at noen tvitrer #alive.

Den første strategien er isolasjon. Hver en-sky-forekomst er isolert og kan administrere maskiner i bare ett datasenter. Det vil si at tap av en sky på grunn av feil eller feil operatørkommandoer er tap av bare ett datasenter. Vi er klare for dette: vi har en redundanspolicy der kopier av applikasjonen og data er plassert i alle datasentre. Vi bruker feiltolerante databaser og tester med jevne mellomrom for feil.
Siden vi i dag har fire datasentre, betyr det fire separate, fullstendig isolerte forekomster av én sky.

Denne tilnærmingen beskytter ikke bare mot fysisk feil, men kan også beskytte mot operatørfeil.

Hva annet kan gjøres med den menneskelige faktoren? Når en operatør gir skyen en merkelig eller potensielt farlig kommando, kan han plutselig bli bedt om å løse et lite problem for å se hvor godt han tenkte. For eksempel, hvis dette er en slags massestopp av mange kopier eller bare en merkelig kommando - å redusere antall kopier eller endre navnet på bildet, og ikke bare versjonsnummeret i det nye manifestet.

One-cloud - datasenternivå OS i Odnoklassniki

Resultater av

Karakteristiske trekk ved one-cloud:

  • Hierarkisk og visuell navneskjema for tjenester og containere, som lar deg veldig raskt finne ut hva oppgaven er, hva den relaterer seg til og hvordan den fungerer og hvem som har ansvaret for den.
  • Vi anvender vår teknikk for å kombinere produkt- og batch-oppgaver på undersåtter for å forbedre effektiviteten av maskindeling. I stedet for cpuset bruker vi CPU-kvoter, delinger, CPU-planleggerpolicyer og Linux QoS.
  • Det var ikke mulig å isolere containere som kjørte på samme maskin fullstendig, men deres gjensidige påvirkning forblir innenfor 20 %.
  • Organisering av tjenester i et hierarki hjelper med automatisk gjenoppretting etter katastrofe plassering og forkjøpsprioriteringer.

FAQ

Hvorfor tok vi ikke en ferdig løsning?

  • Ulike klasser av oppgaveisolering krever forskjellig logikk når de plasseres på undersåtter. Hvis prod-oppgaver kan plasseres ved ganske enkelt å reservere ressurser, må batch- og inaktive oppgaver plasseres for å spore den faktiske ressursbruken på minion-maskiner.
  • Behovet for å ta hensyn til ressurser som forbrukes av oppgaver, for eksempel:
    • nettverksbåndbredde;
    • typer og "spindler" av disker.
  • Behovet for å angi prioriteringer av tjenester under beredskap, rettigheter og kvoter for kommandoer for ressurser, som løses ved hjelp av hierarkiske køer i én sky.
  • Behovet for å ha menneskelig navngivning av containere for å redusere responstiden på ulykker og hendelser
  • Umuligheten av en engangs utbredt implementering av Service Discovery; behovet for å sameksistere i lang tid med oppgaver hostet på maskinvareverter - noe som løses ved at "statiske" IP-adresser følger containere, og som en konsekvens av behovet for unik integrasjon med en stor nettverksinfrastruktur.

Alle disse funksjonene ville kreve betydelige modifikasjoner av eksisterende løsninger for å passe oss, og etter å ha vurdert arbeidsmengden innså vi at vi kunne utvikle vår egen løsning med omtrent de samme arbeidskostnadene. Men løsningen din vil være mye enklere å drifte og utvikle – den inneholder ikke unødvendige abstraksjoner som støtter funksjonalitet som vi ikke trenger.

Til de som leser de siste linjene, takk for tålmodigheten og oppmerksomheten!

Kilde: www.habr.com

Legg til en kommentar