„Kubernetes padidino delsą 10 kartų“: kas dėl to kaltas?

Pastaba. vert.: Šis straipsnis, parašytas Galo Navarro, einančio pagrindinio programinės įrangos inžinieriaus pareigas Europos įmonėje Adevinta, yra žavus ir pamokantis „tyrimas“ infrastruktūros operacijų srityje. Jo originalus pavadinimas buvo šiek tiek išplėstas vertime dėl priežasties, kurią autorius paaiškina pačioje pradžioje.

„Kubernetes padidino delsą 10 kartų“: kas dėl to kaltas?

Pastaba iš autoriaus: Atrodo kaip šis įrašas patraukė daug daugiau dėmesio nei tikėtasi. Vis dar sulaukiu piktų komentarų, kad straipsnio pavadinimas klaidinantis, o kai kurie skaitytojai nuliūdę. Suprantu to, kas vyksta, priežastis, todėl, nepaisant rizikos sugriauti visą intrigą, noriu iš karto pasakyti, apie ką šis straipsnis. Įdomus dalykas, kurį mačiau komandoms pereinant prie „Kubernetes“, yra tai, kad iškilus problemai (pvz., padidėjus delsai po perkėlimo), pirmiausia kaltinama „Kubernetes“, bet tada paaiškėja, kad orkestrantas iš tikrųjų nėra kaltinti. Šiame straipsnyje pasakojama apie vieną tokį atvejį. Jo pavadinimas pakartoja vieno iš mūsų kūrėjų šūksnį (vėliau pamatysite, kad Kubernetes neturi nieko bendra). Čia nerasite jokių stebinančių apreiškimų apie Kubernetes, tačiau galite tikėtis kelių gerų pamokų apie sudėtingas sistemas.

Prieš porą savaičių mano komanda perkėlė vieną mikropaslaugą į pagrindinę platformą, apimančią CI / CD, „Kubernetes“ pagrįstą vykdymo laiką, metriką ir kitas gėrybes. Perkėlimas buvo bandomojo pobūdžio: planavome jį remtis ir per artimiausius mėnesius perduoti dar maždaug 150 paslaugų. Visi jie yra atsakingi už kai kurių didžiausių internetinių platformų Ispanijoje (Infojobs, Fotocasa ir kt.) veiklą.

Įdiegus programą Kubernetes ir nukreipus į ją tam tikrą srautą, mūsų laukė nerimą kelianti staigmena. Delsimas (latencija) Kubernetes užklausų buvo 10 kartų daugiau nei EC2. Apskritai reikėjo arba rasti šios problemos sprendimą, arba atsisakyti mikropaslaugos (o galbūt ir viso projekto) migracijos.

Kodėl Kubernetes delsa yra daug didesnė nei EC2?

Norėdami rasti kliūtis, surinkome viso užklausos kelio metrikas. Mūsų architektūra paprasta: API šliuzas (Zuul) perduoda užklausas mikro paslaugų egzemplioriams EC2 arba Kubernetes. „Kubernetes“ sistemoje naudojame „NGINX Ingress Controller“, o užpakalinės programos yra įprasti objektai, tokie kaip diegimo su JVM programa Spring platformoje.

                                  EC2
                            +---------------+
                            |  +---------+  |
                            |  |         |  |
                       +-------> BACKEND |  |
                       |    |  |         |  |
                       |    |  +---------+  |                   
                       |    +---------------+
             +------+  |
Public       |      |  |
      -------> ZUUL +--+
traffic      |      |  |              Kubernetes
             +------+  |    +-----------------------------+
                       |    |  +-------+      +---------+ |
                       |    |  |       |  xx  |         | |
                       +-------> NGINX +------> BACKEND | |
                            |  |       |  xx  |         | |
                            |  +-------+      +---------+ |
                            +-----------------------------+

Atrodė, kad problema yra susijusi su pradine delsa užpakalinėje programoje (problemos sritį grafike pažymėjau kaip „xx“). EC2 programoje atsakymas užtruko apie 20 ms. „Kubernetes“ delsa padidėjo iki 100–200 ms.

Greitai atmetėme galimus įtariamuosius, susijusius su vykdymo laiko pakeitimu. JVM versija išlieka ta pati. Konteinerių problemos taip pat neturėjo nieko bendra: programa jau sėkmingai veikė EC2 konteineriuose. Įkeliama? Tačiau mes pastebėjome didelį delsą net esant 1 užklausai per sekundę. Taip pat gali būti nepaisoma šiukšlių surinkimo pauzės.

Vienas iš mūsų „Kubernetes“ administratorių pasidomėjo, ar programa turi išorinių priklausomybių, nes anksčiau DNS užklausos sukėlė panašių problemų.

1 hipotezė: DNS vardo sprendimas

Kiekvienai užklausai mūsų programa vieną ar tris kartus pasiekia AWS Elasticsearch egzempliorių tokiame domene kaip elastic.spain.adevinta.com. Mūsų konteinerių viduje yra apvalkalas, todėl galime patikrinti, ar domeno paieška iš tiesų užtrunka ilgai.

DNS užklausos iš konteinerio:

[root@be-851c76f696-alf8z /]# while true; do dig "elastic.spain.adevinta.com" | grep time; sleep 2; done
;; Query time: 22 msec
;; Query time: 22 msec
;; Query time: 29 msec
;; Query time: 21 msec
;; Query time: 28 msec
;; Query time: 43 msec
;; Query time: 39 msec

Panašios užklausos iš vieno iš EC2 atvejų, kai veikia programa:

bash-4.4# while true; do dig "elastic.spain.adevinta.com" | grep time; sleep 2; done
;; Query time: 77 msec
;; Query time: 0 msec
;; Query time: 0 msec
;; Query time: 0 msec
;; Query time: 0 msec

Atsižvelgiant į tai, kad paieška užtruko apie 30 ms, tapo aišku, kad DNS skyra pasiekiant Elasticsearch iš tikrųjų prisidėjo prie delsos padidėjimo.

Tačiau tai buvo keista dėl dviejų priežasčių:

  1. Jau turime daugybę „Kubernetes“ programų, kurios sąveikauja su AWS ištekliais nepatirdamos didelės delsos. Kad ir kokia būtų priežastis, ji konkrečiai susijusi su šiuo atveju.
  2. Žinome, kad JVM atlieka DNS talpyklos kaupimą atmintyje. Mūsų vaizduose TTL reikšmė parašyta $JAVA_HOME/jre/lib/security/java.security ir nustatykite 10 sekundžių: networkaddress.cache.ttl = 10. Kitaip tariant, JVM turėtų saugoti visas DNS užklausas talpykloje 10 sekundžių.

Norėdami patvirtinti pirmąją hipotezę, nusprendėme kuriam laikui nebeskambinti DNS ir pažiūrėti, ar problema išnyko. Pirma, nusprendėme iš naujo sukonfigūruoti programą, kad ji tiesiogiai bendrautų su Elasticsearch pagal IP adresą, o ne per domeno pavadinimą. Tam reikės kodo pakeitimų ir naujo diegimo, todėl tiesiog susiejome domeną su jo IP adresu /etc/hosts:

34.55.5.111 elastic.spain.adevinta.com

Dabar konteineris beveik akimirksniu gavo IP. Dėl to šiek tiek pagerėjome, bet tik šiek tiek priartėjome prie numatomų delsos lygių. Nors DNS sprendimas užtruko ilgai, tikroji priežastis vis tiek mums nepavyko.

Diagnostika per tinklą

Nusprendėme analizuoti srautą iš konteinerio naudojant tcpdumpNorėdami pamatyti, kas tiksliai vyksta tinkle:

[root@be-851c76f696-alf8z /]# tcpdump -leni any -w capture.pcap

Tada išsiuntėme keletą užklausų ir atsisiuntėme jų fiksavimą (kubectl cp my-service:/capture.pcap capture.pcap) tolesnei analizei wireshark.

DNS užklausose nebuvo nieko įtartino (išskyrus vieną smulkmeną, apie kurią pakalbėsiu vėliau). Tačiau buvo tam tikrų keistenybių, kaip mūsų tarnyba nagrinėjo kiekvieną užklausą. Toliau pateikiama fiksavimo ekrano kopija, rodanti, kad užklausa priimta prieš pradedant atsakymą:

„Kubernetes padidino delsą 10 kartų“: kas dėl to kaltas?

Pakuotės numeriai rodomi pirmame stulpelyje. Aiškumo dėlei skirtingus TCP srautus užkodavau spalvomis.

Žalias srautas, prasidedantis 328 paketu, rodo, kaip klientas (172.17.22.150) užmezgė TCP ryšį su konteineriu (172.17.36.147). Po pirminio rankos paspaudimo (328-330) atnešė paketą 331 HTTP GET /v1/.. — mūsų tarnybai gaunama užklausa. Visas procesas truko 1 ms.

Pilkas srautas (iš 339 paketo) rodo, kad mūsų paslauga Elasticsearch egzemplioriui išsiuntė HTTP užklausą (nėra TCP rankos paspaudimo, nes ji naudoja esamą ryšį). Tai užtruko 18 ms.

Kol kas viskas gerai, o laikai maždaug atitinka numatomus vėlavimus (20-30 ms matuojant nuo kliento).

Tačiau mėlyna sekcija trunka 86 ms. Kas jame vyksta? Su 333 paketu mūsų paslauga išsiuntė HTTP GET užklausą /latest/meta-data/iam/security-credentials, o iškart po jo per tą patį TCP ryšį kitą GET užklausą į /latest/meta-data/iam/security-credentials/arn:...

Pastebėjome, kad tai kartojosi su kiekvienu prašymu per visą pėdsaką. DNS raiška mūsų konteineriuose tikrai yra šiek tiek lėtesnė (šio reiškinio paaiškinimas gana įdomus, bet pasiliksiu jį atskiram straipsniui). Paaiškėjo, kad ilgų vėlavimų priežastis buvo skambučiai į AWS egzempliorių metaduomenų paslaugą pagal kiekvieną užklausą.

2 hipotezė: nereikalingi skambučiai į AWS

Abu galutiniai taškai priklauso AWS egzempliorių metaduomenų API. Mūsų mikropaslauga naudoja šią paslaugą vykdydama Elasticsearch. Abu skambučiai yra pagrindinio leidimo proceso dalis. Galutinis taškas, pasiekiamas pagal pirmą užklausą, išduoda su egzemplioriumi susietą IAM vaidmenį.

/ # curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
arn:aws:iam::<account_id>:role/some_role

Antroji užklausa prašo antrojo galutinio taško suteikti laikinus leidimus šiam atvejui:

/ # curl http://169.254.169.254/latest/meta-data/iam/security-credentials/arn:aws:iam::<account_id>:role/some_role`
{
    "Code" : "Success",
    "LastUpdated" : "2012-04-26T16:39:16Z",
    "Type" : "AWS-HMAC",
    "AccessKeyId" : "ASIAIOSFODNN7EXAMPLE",
    "SecretAccessKey" : "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
    "Token" : "token",
    "Expiration" : "2017-05-17T15:09:54Z"
}

Klientas gali jais naudotis trumpą laiką ir turi periodiškai gauti naujus sertifikatus (prieš juos išduodant Expiration). Modelis yra paprastas: AWS saugumo sumetimais dažnai kaitalioja laikinuosius raktus, tačiau klientai gali keletą minučių juos išsaugoti talpykloje, kad kompensuotų našumo nuobaudą, susijusią su naujų sertifikatų gavimu.

AWS Java SDK turėtų perimti atsakomybę už šio proceso organizavimą, tačiau dėl tam tikrų priežasčių tai neįvyksta.

Ieškodami problemų „GitHub“, susidūrėme su problema # 1921. Ji padėjo mums nustatyti kryptį, kuria „kasti“ toliau.

AWS SDK atnaujina sertifikatus, kai įvyksta viena iš šių sąlygų:

  • Galiojimo pabaigos data (Expiration) Įkristi į EXPIRATION_THRESHOLD, užkoduota iki 15 minučių.
  • Nuo paskutinio bandymo atnaujinti sertifikatus praėjo daugiau laiko REFRESH_THRESHOLD, užkoduotas 60 minučių.

Norėdami pamatyti tikrąją gautų sertifikatų galiojimo datą, paleidome aukščiau nurodytas cURL komandas iš konteinerio ir EC2 egzemplioriaus. Iš konteinerio gauto sertifikato galiojimo laikas pasirodė gerokai trumpesnis: lygiai 15 minučių.

Dabar viskas paaiškėjo: už pirmą užklausą mūsų tarnyba gavo laikinus pažymėjimus. Kadangi jie galiojo ne ilgiau nei 15 minučių, AWS SDK nuspręstų juos atnaujinti gavęs vėlesnę užklausą. Ir tai atsitiko su kiekvienu prašymu.

Kodėl sutrumpėjo sertifikatų galiojimo laikas?

AWS egzempliorių metaduomenys skirti dirbti su EC2 egzemplioriais, o ne su „Kubernetes“. Kita vertus, mes nenorėjome keisti programos sąsajos. Tam naudojome KIAM - įrankis, kuris, naudojant agentus kiekviename Kubernetes mazge, leidžia vartotojams (inžinieriams, diegiantiems programas į klasterį) priskirti IAM vaidmenis talpyklose, tarsi jie būtų EC2 egzemplioriai. KIAM perima skambučius į AWS egzempliorių metaduomenų paslaugą ir apdoroja juos iš savo talpyklos, prieš tai gavusi juos iš AWS. Taikymo požiūriu niekas nesikeičia.

KIAM ankštims tiekia trumpalaikius sertifikatus. Tai prasminga atsižvelgiant į tai, kad vidutinė ankšties eksploatavimo trukmė yra trumpesnė nei EC2 egzemplioriaus. Numatytasis sertifikatų galiojimo laikas lygi toms pačioms 15 minučių.

Dėl to, jei abi numatytąsias vertes perdengiate viena ant kitos, iškyla problema. Kiekvienas paraiškai pateiktas sertifikatas nustoja galioti po 15 minučių. Tačiau AWS Java SDK priverčia atnaujinti bet kokį sertifikatą, kurio galiojimo laikas liko mažiau nei 15 minučių.

Dėl to laikinasis sertifikatas yra priverstas atnaujinti su kiekviena užklausa, o tai reiškia keletą iškvietimų į AWS API ir žymiai padidina delsą. AWS Java SDK radome funkcijos užklausa, kuriame minima panaši problema.

Sprendimas pasirodė paprastas. Mes tiesiog perkonfigūravome KIAM, kad prašytume ilgesnio galiojimo laikotarpio sertifikatų. Kai tai atsitiko, užklausos pradėjo plūsti nedalyvaujant AWS metaduomenų tarnybai, o delsa sumažėjo iki dar žemesnio lygio nei EC2.

išvados

Remiantis mūsų patirtimi, susijusia su migravimu, vienas iš dažniausiai pasitaikančių problemų šaltinių nėra „Kubernetes“ ar kitų platformos elementų klaidos. Tai taip pat nepašalina jokių esminių mikropaslaugų, kurias perkeliame, trūkumų. Problemų dažnai kyla vien todėl, kad sujungiame skirtingus elementus.

Sumaišome sudėtingas sistemas, kurios niekada anksčiau nebuvo sąveikaujančios viena su kita, tikėdamiesi, kad jos kartu sudarys vieną didesnę sistemą. Deja, kuo daugiau elementų, tuo daugiau vietos klaidoms, tuo didesnė entropija.

Mūsų atveju didelė delsa atsirado ne dėl Kubernetes, KIAM, AWS Java SDK ar mūsų mikropaslaugos klaidų ar netinkamų sprendimų. Tai buvo dviejų nepriklausomų numatytųjų nustatymų derinimo rezultatas: vienas KIAM, kitas AWS Java SDK. Vertinant atskirai, prasmingi abu parametrai: aktyvi sertifikato atnaujinimo politika AWS Java SDK ir trumpas sertifikatų galiojimo laikotarpis KAIM. Tačiau juos sujungus, rezultatai tampa nenuspėjami. Du nepriklausomi ir logiški sprendimai nebūtinai turi prasmės, kai jie derinami.

PS iš vertėjo

Daugiau apie KIAM programos, skirtos AWS IAM integravimui su Kubernetes, architektūrą galite sužinoti adresu Šis straipsnis iš jo kūrėjų.

Taip pat skaitykite mūsų tinklaraštyje:

Šaltinis: www.habr.com

Добавить комментарий