"Kubernetes rriti vonesën me 10 herë": kush është fajtor për këtë?

Shënim. përkth.: Ky artikull, i shkruar nga Galo Navarro, i cili mban pozicionin e Inxhinierit Kryesor Softuerësh në kompaninë evropiane Adevinta, është një “hetim” magjepsës dhe udhëzues në fushën e operacioneve të infrastrukturës. Titulli origjinal i tij u zgjerua pak në përkthim për një arsye që autori e shpjegon që në fillim.

"Kubernetes rriti vonesën me 10 herë": kush është fajtor për këtë?

Shënim nga autori: Duket si ky postim të tërhequr shumë më tepër vëmendje sesa pritej. Ende kam komente të zemëruara se titulli i artikullit është mashtrues dhe se disa lexues janë të trishtuar. Unë i kuptoj arsyet e asaj që po ndodh, prandaj, pavarësisht rrezikut për të prishur të gjithë intrigën, dua t'ju tregoj menjëherë se për çfarë bëhet fjalë në këtë artikull. Një gjë kurioze që kam parë ndërsa ekipet migrojnë në Kubernetes është se sa herë që lind një problem (siç është rritja e vonesës pas një migrimi), gjëja e parë që fajësohet është Kubernetes, por më pas rezulton se orkestratori nuk është në të vërtetë fajësojnë. Ky artikull tregon për një rast të tillë. Emri i tij përsërit thirrjen e një prej zhvilluesve tanë (më vonë do të shihni që Kubernetes nuk ka asnjë lidhje me të). Nuk do të gjeni ndonjë zbulim befasues rreth Kubernetes këtu, por mund të prisni disa mësime të mira rreth sistemeve komplekse.

Disa javë më parë, ekipi im po migronte një mikroshërbim të vetëm në një platformë bazë që përfshinte CI/CD, një kohë ekzekutimi të bazuar në Kubernetes, metrikë dhe të mira të tjera. Lëvizja ishte e një natyre provë: ne planifikuam ta merrnim atë si bazë dhe të transferonim rreth 150 shërbime të tjera në muajt e ardhshëm. Të gjithë ata janë përgjegjës për funksionimin e disa prej platformave më të mëdha online në Spanjë (Infojobs, Fotocasa, etj.).

Pasi vendosëm aplikacionin në Kubernetes dhe ridrejtuam disa trafik në të, na priste një surprizë alarmante. Vonesa (latente) kërkesat në Kubernetes ishin 10 herë më të larta se në EC2. Në përgjithësi, ishte e nevojshme ose të gjehej një zgjidhje për këtë problem, ose të braktisej migrimi i mikroshërbimit (dhe, ndoshta, i gjithë projekti).

Pse vonesa është kaq më e lartë në Kubernetes sesa në EC2?

Për të gjetur pengesën, ne mblodhëm metrikë përgjatë gjithë shtegut të kërkesës. Arkitektura jonë është e thjeshtë: një portë API (Zuul) proxon kërkesat për instancat e mikroshërbimit në EC2 ose Kubernetes. Në Kubernetes ne përdorim NGINX Ingress Controller, dhe prapavijat janë objekte të zakonshme si shpërndarje me një aplikacion JVM në platformën Spring.

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

Problemi dukej se lidhej me vonesën fillestare në fund (e shënova zonën e problemit në grafik si "xx"). Në EC2, përgjigja e aplikacionit zgjati rreth 20 ms. Në Kubernetes, vonesa u rrit në 100-200 ms.

Ne i hodhëm poshtë me shpejtësi të dyshuarit e mundshëm në lidhje me ndryshimin e kohës së ekzekutimit. Versioni JVM mbetet i njëjtë. Problemet e kontejnerizimit gjithashtu nuk kishin të bënin me të: aplikacioni tashmë po funksiononte me sukses në kontejnerë në EC2. Po ngarkohet? Por ne kemi vërejtur vonesa të larta edhe me 1 kërkesë në sekondë. Pushimet për mbledhjen e mbeturinave gjithashtu mund të neglizhohen.

Një nga administratorët tanë të Kubernetes pyeti veten nëse aplikacioni kishte varësi të jashtme sepse pyetjet DNS kishin shkaktuar probleme të ngjashme në të kaluarën.

Hipoteza 1: Rezolucioni i emrit të DNS

Për çdo kërkesë, aplikacioni ynë akseson një shembull AWS Elasticsearch një deri në tre herë në një domen si elastic.spain.adevinta.com. Brenda kontejnerëve tanë ka një guaskë, kështu që ne mund të kontrollojmë nëse kërkimi për një domen kërkon një kohë të gjatë.

Pyetjet DNS nga kontejneri:

[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

Kërkesa të ngjashme nga një prej rasteve EC2 ku aplikacioni po ekzekutohet:

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

Duke marrë parasysh që kërkimi zgjati rreth 30 ms, u bë e qartë se rezolucioni i DNS kur hyni në Elasticsearch po kontribuonte me të vërtetë në rritjen e vonesës.

Megjithatë, kjo ishte e çuditshme për dy arsye:

  1. Ne tashmë kemi një ton aplikacionesh Kubernetes që ndërveprojnë me burimet AWS pa vuajtur nga vonesa e lartë. Cilado qoftë arsyeja, ajo lidhet veçanërisht me këtë rast.
  2. Ne e dimë se JVM bën memorien e DNS në memorie. Në imazhet tona, vlera TTL është shkruar në $JAVA_HOME/jre/lib/security/java.security dhe vendoseni në 10 sekonda: networkaddress.cache.ttl = 10. Me fjalë të tjera, JVM duhet të ruajë të gjitha pyetjet DNS për 10 sekonda.

Për të konfirmuar hipotezën e parë, vendosëm të ndalojmë së telefonuari DNS për një kohë dhe të shohim nëse problemi u largua. Së pari, vendosëm të rikonfiguronim aplikacionin në mënyrë që ai të komunikonte drejtpërdrejt me Elasticsearch me adresë IP, dhe jo përmes një emri domeni. Kjo do të kërkonte ndryshime të kodit dhe një vendosje të re, kështu që ne thjesht vendosëm domenin në adresën e tij IP /etc/hosts:

34.55.5.111 elastic.spain.adevinta.com

Tani kontejneri mori një IP pothuajse menjëherë. Kjo rezultoi në disa përmirësime, por ne ishim vetëm pak më afër niveleve të pritura të vonesës. Megjithëse zgjidhja e DNS mori një kohë të gjatë, arsyeja e vërtetë ende na shpëtoi.

Diagnostifikimi përmes rrjetit

Ne vendosëm të analizonim trafikun nga kontejneri duke përdorur tcpdumppër të parë se çfarë po ndodh saktësisht në rrjet:

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

Më pas dërguam disa kërkesa dhe shkarkuam kapjen e tyre (kubectl cp my-service:/capture.pcap capture.pcap) për analiza të mëtejshme në Wireshark.

Nuk kishte asgjë të dyshimtë në lidhje me pyetjet DNS (përveç një gjëje të vogël për të cilën do të flas më vonë). Por kishte disa çudira në mënyrën se si shërbimi ynë trajtoi çdo kërkesë. Më poshtë është një pamje e fotografisë që tregon se kërkesa pranohet përpara se të fillojë përgjigja:

"Kubernetes rriti vonesën me 10 herë": kush është fajtor për këtë?

Numrat e paketave tregohen në kolonën e parë. Për qartësi, unë kam koduar me ngjyra rrjedhat e ndryshme TCP.

Rrjedha e gjelbër që fillon me paketën 328 tregon se si klienti (172.17.22.150) krijoi një lidhje TCP me kontejnerin (172.17.36.147). Pas shtrëngimit fillestar të duarve (328-330), solli paketa 331 HTTP GET /v1/.. — një kërkesë hyrëse në shërbimin tonë. I gjithë procesi zgjati 1 ms.

Transmetimi gri (nga paketa 339) tregon se shërbimi ynë dërgoi një kërkesë HTTP në shembullin Elasticsearch (nuk ka shtrëngim duarsh TCP sepse po përdor një lidhje ekzistuese). Kjo mori 18 ms.

Deri më tani gjithçka është në rregull, dhe kohët përafërsisht korrespondojnë me vonesat e pritura (20-30 ms kur maten nga klienti).

Sidoqoftë, seksioni blu merr 86 ms. Çfarë po ndodh në të? Me paketën 333, shërbimi ynë dërgoi një kërkesë HTTP GET /latest/meta-data/iam/security-credentials, dhe menjëherë pas saj, mbi të njëjtën lidhje TCP, një tjetër kërkesë GET për /latest/meta-data/iam/security-credentials/arn:...

Ne zbuluam se kjo përsëritej me çdo kërkesë gjatë gjithë gjurmës. Rezolucioni i DNS është me të vërtetë pak më i ngadalshëm në kontejnerët tanë (shpjegimi për këtë fenomen është mjaft interesant, por unë do ta ruaj për një artikull të veçantë). Doli se shkaku i vonesave të gjata ishin thirrjet në shërbimin AWS Instance Metadata për çdo kërkesë.

Hipoteza 2: thirrje të panevojshme në AWS

Të dyja pikat fundore i përkasin AWS Instance Metadata API. Mikroshërbimi ynë e përdor këtë shërbim gjatë ekzekutimit të Elasticsearch. Të dyja thirrjet janë pjesë e procesit bazë të autorizimit. Pika përfundimtare që aksesohet në kërkesën e parë lëshon rolin IAM të lidhur me shembullin.

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

Kërkesa e dytë kërkon nga pika e dytë përfundimtare leje të përkohshme për këtë shembull:

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

Klienti mund t'i përdorë ato për një periudhë të shkurtër kohore dhe duhet të marrë periodikisht certifikata të reja (përpara se të jenë Expiration). Modeli është i thjeshtë: AWS rrotullon çelësat e përkohshëm shpesh për arsye sigurie, por klientët mund t'i ruajnë në memorien e tyre për disa minuta për të kompensuar dënimin e performancës që lidhet me marrjen e certifikatave të reja.

AWS Java SDK duhet të marrë përsipër përgjegjësinë për organizimin e këtij procesi, por për disa arsye kjo nuk ndodh.

Pas kërkimit të problemeve në GitHub, hasëm në një problem # 1921. Ajo na ndihmoi të përcaktojmë drejtimin në të cilin të "gërmojmë" më tej.

AWS SDK përditëson certifikatat kur ndodh një nga kushtet e mëposhtme:

  • Data e skadimit (Expiration) Bie ne EXPIRATION_THRESHOLD, e koduar në 15 minuta.
  • Ka kaluar më shumë kohë nga përpjekja e fundit për të rinovuar certifikatat REFRESH_THRESHOLD, e koduar për 60 minuta.

Për të parë datën aktuale të skadimit të certifikatave që marrim, ne ekzekutuam komandat e mësipërme cURL si nga kontejneri ashtu edhe nga shembulli EC2. Periudha e vlefshmërisë së certifikatës së marrë nga kontejneri doli të ishte shumë më e shkurtër: saktësisht 15 minuta.

Tani gjithçka është bërë e qartë: për kërkesën e parë, shërbimi ynë mori certifikata të përkohshme. Meqenëse ato nuk ishin të vlefshme për më shumë se 15 minuta, AWS SDK do të vendoste t'i përditësonte ato në një kërkesë të mëvonshme. Dhe kjo ndodhte me çdo kërkesë.

Pse është shkurtuar periudha e vlefshmërisë së certifikatave?

Metadatat e Instancës AWS janë krijuar për të punuar me instancat EC2, jo me Kubernetes. Nga ana tjetër, ne nuk donim të ndryshonim ndërfaqen e aplikacionit. Për këtë kemi përdorur KIAM - një mjet që, duke përdorur agjentë në çdo nyje Kubernetes, i lejon përdoruesit (inxhinierët që vendosin aplikacione në një grup) të caktojnë rolet IAM në kontejnerët në pods sikur të ishin instanca EC2. KIAM përgjon thirrjet në shërbimin AWS Instance Metadata dhe i përpunon ato nga cache e tij, pasi i ka marrë më parë nga AWS. Nga pikëpamja e aplikimit, asgjë nuk ndryshon.

KIAM furnizon çertifikata afatshkurtër për pods. Kjo ka kuptim duke pasur parasysh se jetëgjatësia mesatare e një pod është më e shkurtër se ajo e një shembulli EC2. Periudha e parazgjedhur e vlefshmërisë për certifikatat e barabartë me të njëjtat 15 minuta.

Si rezultat, nëse mbivendosni të dyja vlerat e paracaktuara mbi njëra-tjetrën, lind një problem. Çdo certifikatë e dhënë në një aplikacion skadon pas 15 minutash. Sidoqoftë, AWS Java SDK detyron një rinovim të çdo certifikate që ka më pak se 15 minuta para datës së skadimit.

Si rezultat, certifikata e përkohshme detyrohet të rinovohet me çdo kërkesë, e cila përfshin disa thirrje në API AWS dhe shkakton një rritje të konsiderueshme të vonesës. Ne gjetëm në AWS Java SDK kërkesa e veçorisë, i cili përmend një problem të ngjashëm.

Zgjidhja doli të jetë e thjeshtë. Ne thjesht e rikonfiguruam KIAM-in për të kërkuar certifikata me një periudhë më të gjatë vlefshmërie. Sapo ndodhi kjo, kërkesat filluan të rrjedhin pa pjesëmarrjen e shërbimit AWS Metadata dhe vonesa ra në nivele edhe më të ulëta se në EC2.

Gjetjet

Bazuar në përvojën tonë me migrimet, një nga burimet më të zakonshme të problemeve nuk janë gabimet në Kubernetes ose elementë të tjerë të platformës. Ai gjithashtu nuk adreson ndonjë defekt thelbësor në mikroshërbimet që po transportojmë. Problemet shpesh lindin thjesht sepse ne bashkojmë elementë të ndryshëm.

Ne përziejmë së bashku sisteme komplekse që nuk kanë ndërvepruar kurrë më parë me njëri-tjetrin, duke pritur që së bashku të formojnë një sistem të vetëm, më të madh. Mjerisht, sa më shumë elementë, sa më shumë hapësirë ​​për gabime, aq më e lartë është entropia.

Në rastin tonë, vonesa e lartë nuk ishte rezultat i gabimeve ose vendimeve të këqija në Kubernetes, KIAM, AWS Java SDK ose mikroshërbimin tonë. Ishte rezultat i kombinimit të dy cilësimeve të pavarura të paracaktuara: një në KIAM, tjetri në AWS Java SDK. Marrë veçmas, të dy parametrat kanë kuptim: politika aktive e rinovimit të certifikatës në AWS Java SDK dhe periudha e shkurtër e vlefshmërisë së certifikatave në KAIM. Por kur i bashkoni, rezultatet bëhen të paparashikueshme. Dy zgjidhje të pavarura dhe logjike nuk duhet të kenë kuptim kur kombinohen.

PS nga përkthyesi

Mund të mësoni më shumë rreth arkitekturës së mjetit KIAM për integrimin e AWS IAM me Kubernetes në Ky artikull nga krijuesit e saj.

Lexoni gjithashtu në blogun tonë:

Burimi: www.habr.com

Shto një koment