"Kubernetes suurendas latentsust 10 korda": kes on selles süüdi?

Märge. tõlge: See artikkel, mille on kirjutanud Galo Navarro, kes töötab Euroopa ettevõtte Adevinta peatarkvarainsenerina, on põnev ja õpetlik "uurimine" infrastruktuuri toimimise valdkonnas. Selle algset pealkirja on tõlkes veidi laiendatud põhjusel, mida autor alguses selgitab.

"Kubernetes suurendas latentsust 10 korda": kes on selles süüdi?

Märkus autorilt: näeb välja nagu see postitus meelitas oodatust palju rohkem tähelepanu. Endiselt saan vihaseid kommentaare, et artikli pealkiri on eksitav ja mõni lugeja on kurb. Ma mõistan toimuva põhjuseid, seetõttu tahan hoolimata kogu intriigi rikkumise ohust teile kohe rääkida, millest see artikkel räägib. Kubernetesele siirdumisel olen näinud uudishimulikku asja, et alati, kui tekib probleem (nt suurenenud latentsus pärast migratsiooni), süüdistatakse esimese asjana Kubernetest, kuid siis selgub, et orkestrant pole tegelikult süüdistada. See artikkel räägib ühest sellisest juhtumist. Selle nimi kordab ühe meie arendaja hüüatust (hiljem näete, et Kubernetesil pole sellega midagi pistmist). Kubernetese kohta ei leia siit üllatavaid paljastusi, kuid võite oodata paar head õppetundi keeruliste süsteemide kohta.

Paar nädalat tagasi viis mu meeskond ühe mikroteenuse üle põhiplatvormile, mis sisaldas CI/CD-d, Kubernetese-põhist käituskeskkonda, mõõdikuid ja muid hüvesid. Kolimine oli proovilise iseloomuga: plaanisime selle aluseks võtta ja lähikuudel üle anda veel ligikaudu 150 teenust. Kõik nad vastutavad mõne Hispaania suurima veebiplatvormi (Infojobs, Fotocasa jne) toimimise eest.

Pärast rakenduse Kubernetesesse juurutamist ja liikluse sellele ümbersuunamist ootas meid murettekitav üllatus. Viivitus (latentsus) taotlused Kubernetesis olid 10 korda suuremad kui EC2-s. Üldiselt oli vaja sellele probleemile lahendus leida või mikroteenuse (ja võib-olla ka kogu projekti) migratsioonist loobuda.

Miks on Kubernetes latentsusaeg nii palju suurem kui EC2-s?

Kitsaskoha leidmiseks kogusime mõõdikuid kogu päringu tee ulatuses. Meie arhitektuur on lihtne: API lüüs (Zuul) edastab päringuid EC2 või Kubernetese mikroteenuse eksemplaridele. Kubernetesis kasutame NGINX Ingress Controllerit ja taustaprogrammid on tavalised objektid, nagu Deployment JVM-i rakendusega Spring platvormil.

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

Probleem tundus olevat seotud algse latentsusega taustaprogrammis (märkisin probleemipiirkonnaks graafikul "xx"). EC2 puhul võttis rakenduse vastus umbes 20 ms. Kubernetesis kasvas latentsusaeg 100-200 ms-ni.

Tühistasime kiiresti käitusaja muutusega seotud tõenäolised kahtlusalused. JVM-i versioon jääb samaks. Ka konteineritesse paigutamise probleemidel polnud sellega midagi pistmist: rakendus töötas juba edukalt EC2 konteinerites. Laadimine? Kuid me täheldasime kõrget latentsust isegi 1 päringul sekundis. Tähelepanuta võiks jätta ka prügiveo pausid.

Üks meie Kubernetese administraator mõtles, kas rakendusel on väliseid sõltuvusi, kuna DNS-päringud olid varem sarnaseid probleeme põhjustanud.

Hüpotees 1: DNS-i nime lahendamine

Iga päringu puhul pääseb meie rakendus AWS Elasticsearchi eksemplarile juurde üks kuni kolm korda sellises domeenis nagu elastic.spain.adevinta.com. Meie konteinerite sees seal on kest, et saaksime kontrollida, kas domeeni otsimine võtab tegelikult kaua aega.

DNS-päringud konteinerist:

[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

Sarnased päringud ühest EC2 eksemplarist, kus rakendus töötab:

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

Arvestades, et otsimine võttis aega umbes 30 ms, sai selgeks, et DNS-i eraldusvõime Elasticsearchi juurdepääsul aitas tõepoolest kaasa latentsusaja suurenemisele.

See oli aga kummaline kahel põhjusel:

  1. Meil on juba hulk Kubernetese rakendusi, mis suhtlevad AWS-i ressurssidega ilma kõrge latentsusaega. Olenemata põhjusest, on see konkreetselt selle juhtumiga seotud.
  2. Teame, et JVM teeb DNS-i vahemällu. Meie piltidel on TTL väärtus sisse kirjutatud $JAVA_HOME/jre/lib/security/java.security ja määrake 10 sekundiks: networkaddress.cache.ttl = 10. Teisisõnu peaks JVM kõik DNS-päringud 10 sekundiks vahemällu salvestama.

Esimese hüpoteesi kinnitamiseks otsustasime mõneks ajaks DNS-i helistamise lõpetada ja vaadata, kas probleem on kadunud. Esiteks otsustasime rakenduse ümber konfigureerida nii, et see suhtleks Elasticsearchiga otse IP-aadressi, mitte domeeninime kaudu. See nõuaks koodi muutmist ja uut juurutamist, seega seostasime domeeni lihtsalt selle IP-aadressiga /etc/hosts:

34.55.5.111 elastic.spain.adevinta.com

Nüüd sai konteiner IP peaaegu kohe. See tõi kaasa mõningase paranemise, kuid olime vaid veidi lähemal oodatud latentsustasemele. Kuigi DNS-i lahendamine võttis kaua aega, jäi tegelik põhjus meid siiski tabama.

Diagnostika võrgu kaudu

Otsustasime analüüsida konteineri liiklust kasutades tcpdumpet näha, mis võrgus täpselt toimub:

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

Seejärel saatsime mitu päringut ja laadisime alla nende jäädvustamise (kubectl cp my-service:/capture.pcap capture.pcap) edasiseks analüüsiks Wireshark.

DNS-päringutes polnud midagi kahtlast (v.a üks pisiasi, millest räägin hiljem). Kuid selles, kuidas meie teenus iga taotlust käsitles, oli teatud veidrusi. Allpool on jäädvustuse ekraanipilt, mis näitab taotluse vastuvõtmist enne vastuse algust:

"Kubernetes suurendas latentsust 10 korda": kes on selles süüdi?

Номера пакетов приведены в первом столбце. Для ясности я выделил цветом различные потоки TCP.

Roheline voog, mis algab paketiga 328, näitab, kuidas klient (172.17.22.150) lõi konteineriga (172.17.36.147) TCP-ühenduse. Peale esialgset käepigistust (328-330) tõi pakk 331 HTTP GET /v1/.. — meie teenusele saabuv päring. Kogu protsess võttis aega 1 ms.

Hall voog (paketist 339) näitab, et meie teenus saatis Elasticsearchi eksemplarile HTTP-päringu (TCP-käepigistus puudub, kuna see kasutab olemasolevat ühendust). Selleks kulus 18 ms.

Siiani on kõik korras ja ajad vastavad ligikaudu eeldatavatele viivitustele (kliendilt mõõdetuna 20-30 ms).

Sinine osa võtab aga 86 ms. Mis selles toimub? Paketiga 333 saatis meie teenus HTTP GET-päringu aadressile /latest/meta-data/iam/security-credentialsja kohe pärast seda sama TCP-ühenduse kaudu teine ​​GET-i päring /latest/meta-data/iam/security-credentials/arn:...

Leidsime, et see kordus iga taotlusega kogu jälje jooksul. DNS-i eraldusvõime on meie konteinerites tõepoolest veidi aeglasem (selgitus sellele nähtusele on üsna huvitav, kuid jätan selle eraldi artikli jaoks). Selgus, et pikkade viivituste põhjuseks olid kõned AWS-i eksemplari metaandmete teenusele iga päringu korral.

Hüpotees 2: tarbetud kõned AWS-ile

Mõlemad lõpp-punktid kuuluvad AWS-i eksemplari metaandmete API. Meie mikroteenus kasutab seda teenust Elasticsearchi käitamise ajal. Mõlemad kõned on osa põhilise autoriseerimise protsessist. Esimesel päringul juurdepääsetav lõpp-punkt väljastab eksemplariga seotud IAM-i rolli.

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

Teine päring küsib teiselt lõpp-punktilt selle eksemplari jaoks ajutisi õigusi:

/ # 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"
}

Klient saab neid kasutada lühikest aega ja peab perioodiliselt hankima uued sertifikaadid (enne kui need on Expiration). Mudel on lihtne: AWS vahetab turvakaalutlustel ajutisi võtmeid sageli, kuid kliendid saavad need mõneks minutiks vahemällu salvestada, et kompenseerida uute sertifikaatide saamisega seotud jõudlustrahvi.

AWS Java SDK peaks selle protsessi korraldamise üle võtma, kuid mingil põhjusel seda ei juhtu.

Pärast GitHubi probleemide otsimist avastasime probleemi #1921. Ta aitas meil määrata suuna, kuhu edasi kaevata.

AWS SDK värskendab sertifikaate, kui ilmneb üks järgmistest tingimustest.

  • Aegumiskuupäev (Expiration) Sisse kukkuma EXPIRATION_THRESHOLD, kõvakodeeritud 15 minutiks.
  • Viimasest sertifikaatide uuendamise katsest on möödunud rohkem aega kui REFRESH_THRESHOLD, kõvakodeeritud 60 minutit.

Saadud sertifikaatide tegeliku aegumiskuupäeva nägemiseks käivitasime ülaltoodud cURL-i käsud nii konteinerist kui ka EC2 eksemplarist. Konteinerist saadud sertifikaadi kehtivusaeg osutus tunduvalt lühemaks: täpselt 15 minutit.

Nüüd on kõik selgeks saanud: esimese päringu korral sai meie teenus ajutised sertifikaadid. Kuna need ei kehtinud kauem kui 15 minutit, otsustas AWS SDK neid hilisema taotluse korral värskendada. Ja seda juhtus iga taotlusega.

Miks on sertifikaatide kehtivusaeg lühenenud?

AWS-i eksemplari metaandmed on loodud töötama EC2 eksemplaridega, mitte Kubernetesiga. Teisest küljest ei tahtnud me rakenduse liidest muuta. Selleks kasutasime KIAM - tööriist, mis võimaldab iga Kubernetese sõlme agente kasutades kasutajatel (klastrisse rakendusi juurutavatel inseneridel) määrata IAM-i rolle kaustades olevatele konteineritele, nagu oleksid need EC2 eksemplarid. KIAM peatab kõned AWS-i eksemplari metaandmete teenusele ja töötleb neid oma vahemälust, olles need eelnevalt AWS-ilt saanud. Rakenduse seisukohast ei muutu midagi.

KIAM tarnib kaunadele lühiajalisi sertifikaate. See on mõttekas, arvestades, et kauna keskmine eluiga on lühem kui EC2 eksemplaril. Sertifikaatide vaikekehtivusaeg võrdne sama 15 minutiga.

Selle tulemusena tekib probleem, kui asetate mõlemad vaikeväärtused üksteise peale. Iga taotlusele lisatud sertifikaat aegub 15 minuti pärast. AWS Java SDK sunnib aga uuendama iga sertifikaati, mille aegumiskuupäevani on jäänud vähem kui 15 minutit.

Selle tulemusena on ajutist sertifikaati sunnitud uuendama iga päringuga, mis toob kaasa paar kõnet AWS API-le ja põhjustab latentsusaja märkimisväärset suurenemist. AWS Java SDK-s leidsime funktsioonitaotlus, mis mainib sarnast probleemi.

Lahendus osutus lihtsaks. Seadistasime KIAM-i lihtsalt ümber, et nõuda pikema kehtivusajaga sertifikaate. Kui see juhtus, hakkasid päringud voolama ilma AWS-i metaandmete teenuse osaluseta ja latentsus langes veelgi madalamale kui EC2-s.

Järeldused

Meie migratsioonikogemuse põhjal ei ole üks levinumaid probleemide allikaid Kubernetese või muude platvormi elementide vead. Samuti ei kõrvalda see meie teisaldatavate mikroteenuste põhilisi vigu. Probleemid tekivad sageli lihtsalt seetõttu, et paneme erinevad elemendid kokku.

Segame kokku keerulisi süsteeme, mis pole kunagi varem üksteisega suhelnud, eeldades, et koos moodustavad need ühtse suurema süsteemi. Paraku, mida rohkem elemente, seda rohkem ruumi vigadele, seda suurem on entroopia.

Meie puhul ei olnud kõrge latentsusaeg Kubernetes, KIAM, AWS Java SDK ega meie mikroteenuse vigade või halbade otsuste tagajärg. See oli kahe sõltumatu vaikeseadete kombineerimise tulemus: üks KIAM-is ja teine ​​AWS Java SDK-s. Eraldi võttes on mõttekad mõlemad parameetrid: aktiivne sertifikaadi uuendamise poliitika AWS Java SDK-s ja sertifikaatide lühike kehtivusaeg KAIM-is. Kuid kui need kokku panna, muutuvad tulemused ettearvamatuks. Kaks sõltumatut ja loogilist lahendust ei pea olema kombineeritud.

PS tõlkijalt

Lisateavet KIAM-i utiliidi arhitektuuri kohta AWS IAM-i integreerimiseks Kubernetesega saate aadressilt see artikkel selle loojatelt.

Loe ka meie blogist:

Allikas: www.habr.com

Lisa kommentaar