"Kubernetes het latency met 10 keer verhoog": wie is hiervoor te blameer?

Let wel. vertaal.: Hierdie artikel, geskryf deur Galo Navarro, wat die pos van hoofsagteware-ingenieur by die Europese maatskappy Adevinta beklee, is 'n fassinerende en leersame "ondersoek" op die gebied van infrastruktuurbedrywighede. Die oorspronklike titel daarvan is effens uitgebrei in vertaling vir 'n rede wat die skrywer heel aan die begin verduidelik het.

"Kubernetes het latency met 10 keer verhoog": wie is hiervoor te blameer?

Nota van die skrywer: Lyk soos hierdie pos aangetrokke baie meer aandag as wat verwag is. Ek kry steeds kwaai kommentaar dat die titel van die artikel misleidend is en dat sommige lesers hartseer is. Ek verstaan ​​die redes vir wat gebeur, daarom, ten spyte van die risiko om die hele intrige te verwoes, wil ek jou dadelik vertel waaroor hierdie artikel gaan. 'n Vreemde ding wat ek gesien het terwyl spanne na Kubernetes migreer, is dat wanneer 'n probleem opduik (soos verhoogde latensie na 'n migrasie), die eerste ding wat die skuld kry, Kubernetes is, maar dan blyk dit dat die orkeseerder nie regtig moet blameer. Hierdie artikel vertel van een so 'n geval. Sy naam herhaal die uitroep van een van ons ontwikkelaars (later sal jy sien dat Kubernetes niks daarmee te doen het nie). U sal geen verrassende onthullings oor Kubernetes hier vind nie, maar u kan 'n paar goeie lesse oor komplekse stelsels verwag.

'n Paar weke gelede het my span 'n enkele mikrodiens na 'n kernplatform migreer wat CI/CD, 'n Kubernetes-gebaseerde looptyd, statistieke en ander goedjies ingesluit het. Die skuif was van 'n proefaard: ons het beplan om dit as basis te neem en ongeveer 150 meer dienste in die komende maande oor te dra. Almal van hulle is verantwoordelik vir die bedryf van sommige van die grootste aanlynplatforms in Spanje (Infojobs, Fotocasa, ens.).

Nadat ons die toepassing na Kubernetes ontplooi het en 'n bietjie verkeer daarheen herlei het, het 'n kommerwekkende verrassing op ons gewag. Vertraag (latency) versoeke in Kubernetes was 10 keer hoër as in EC2. Oor die algemeen was dit nodig om óf 'n oplossing vir hierdie probleem te vind, óf die migrasie van die mikrodiens (en moontlik die hele projek) te laat vaar.

Waarom is latency soveel hoër in Kubernetes as in EC2?

Om die bottelnek te vind, het ons metrieke langs die hele versoekpad versamel. Ons argitektuur is eenvoudig: 'n API-poort (Zuul) gevolmagtigdes versoeke na mikrodiensgevalle in EC2 of Kubernetes. In Kubernetes gebruik ons ​​NGINX Ingress Controller, en die backends is gewone voorwerpe soos Ontplooiing met 'n JVM-toepassing op die Spring-platform.

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

Dit het gelyk of die probleem verband hou met aanvanklike latensie in die agterkant (ek het die probleemarea op die grafiek as "xx" gemerk). Op EC2 het die aansoekreaksie ongeveer 20ms geneem. In Kubernetes het die vertraging tot 100-200 ms.

Ons het die waarskynlike verdagtes wat verband hou met die looptydverandering vinnig van die hand gewys. Die JVM-weergawe bly dieselfde. Houeriseringsprobleme het ook niks daarmee te doen gehad nie: die toepassing het reeds suksesvol in houers op EC2 geloop. Laai tans? Maar ons het hoë latensies waargeneem, selfs teen 1 versoek per sekonde. Pouses vir vullisversameling kan ook afgeskeep word.

Een van ons Kubernetes-administrateurs het gewonder of die toepassing eksterne afhanklikhede het omdat DNS-navrae soortgelyke probleme in die verlede veroorsaak het.

Hipotese 1: DNS naam resolusie

Vir elke versoek kry ons toepassing een tot drie keer toegang tot 'n AWS Elasticsearch-instansie in 'n domein soos elastic.spain.adevinta.com. Binne-in ons houers daar is 'n dop, sodat ons kan kyk of soek na 'n domein eintlik lank neem.

DNS-navrae vanaf houer:

[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

Soortgelyke versoeke van een van die EC2-gevalle waar die toepassing loop:

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

As in ag geneem word dat die soektog ongeveer 30 ms geneem het, het dit duidelik geword dat DNS-resolusie met toegang tot Elasticsearch inderdaad bygedra het tot die toename in latensie.

Dit was egter vreemd om twee redes:

  1. Ons het reeds 'n ton Kubernetes-toepassings wat met AWS-hulpbronne interaksie het sonder om aan hoë latensie te ly. Wat ook al die rede is, dit hou spesifiek verband met hierdie saak.
  2. Ons weet dat die JVM DNS-kas in die geheue doen. In ons beelde word die TTL-waarde ingeskryf $JAVA_HOME/jre/lib/security/java.security en stel op 10 sekondes: networkaddress.cache.ttl = 10. Met ander woorde, die JVM behoort alle DNS-navrae vir 10 sekondes te kas.

Om die eerste hipotese te bevestig, het ons besluit om vir 'n rukkie op te hou om DNS te bel en te kyk of die probleem weg is. Eerstens het ons besluit om die toepassing te herkonfigureer sodat dit direk met Elasticsearch gekommunikeer het deur IP-adres, eerder as deur 'n domeinnaam. Dit sal kodeveranderings en 'n nuwe ontplooiing vereis, so ons het eenvoudig die domein na sy IP-adres in gekarteer /etc/hosts:

34.55.5.111 elastic.spain.adevinta.com

Nou het die houer amper onmiddellik 'n IP ontvang. Dit het 'n mate van verbetering tot gevolg gehad, maar ons was net effens nader aan die verwagte vertragingsvlakke. Alhoewel DNS-resolusie lank geneem het, het die werklike rede ons steeds ontwyk.

Diagnose via netwerk

Ons het besluit om verkeer vanaf die houer te ontleed met behulp van tcpdumpom te sien wat presies op die netwerk gebeur:

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

Ons het toe verskeie versoeke gestuur en hul opname afgelaai (kubectl cp my-service:/capture.pcap capture.pcap) vir verdere ontleding in Wireshark.

Daar was niks verdag oor die DNS-navrae nie (behalwe vir een dingetjie waaroor ek later sal praat). Maar daar was sekere eienaardighede in die manier waarop ons diens elke versoek hanteer het. Hieronder is 'n skermskoot van die opname wat wys dat die versoek aanvaar word voordat die antwoord begin:

"Kubernetes het latency met 10 keer verhoog": wie is hiervoor te blameer?

Pakketnommers word in die eerste kolom getoon. Vir duidelikheid het ek die verskillende TCP-strome kleurgekodeer.

Die groen stroom wat met pakkie 328 begin, wys hoe die kliënt (172.17.22.150) 'n TCP-verbinding met die houer tot stand gebring het (172.17.36.147). Na die aanvanklike handdruk (328-330), pakkie 331 gebring HTTP GET /v1/.. — 'n inkomende versoek aan ons diens. Die hele proses het 1 ms geneem.

Die grys stroom (van pakkie 339) wys dat ons diens 'n HTTP-versoek na die Elasticsearch-instansie gestuur het (daar is geen TCP-handdruk nie omdat dit 'n bestaande verbinding gebruik). Dit het 18 ms geneem.

Tot dusver is alles in orde, en die tye stem min of meer ooreen met die verwagte vertragings (20-30 ms wanneer gemeet vanaf die kliënt).

Die blou gedeelte neem egter 86ms. Wat gaan daarin aan? Met pakkie 333 het ons diens 'n HTTP GET-versoek gestuur na /latest/meta-data/iam/security-credentials, en onmiddellik daarna, oor dieselfde TCP-verbinding, nog 'n GET-versoek na /latest/meta-data/iam/security-credentials/arn:...

Ons het gevind dat dit met elke versoek deur die hele spoor herhaal word. DNS-resolusie is inderdaad 'n bietjie stadiger in ons houers (die verduideliking vir hierdie verskynsel is nogal interessant, maar ek sal dit stoor vir 'n aparte artikel). Dit het geblyk dat die oorsaak van die lang vertragings oproepe na die AWS Instance Metadata-diens op elke versoek was.

Hipotese 2: onnodige oproepe na AWS

Beide eindpunte behoort aan AWS Instance Metadata API. Ons mikrodiens gebruik hierdie diens terwyl Elasticsearch loop. Albei oproepe is deel van die basiese magtigingsproses. Die eindpunt wat op die eerste versoek verkry word, gee die IAM-rol uit wat met die instansie geassosieer word.

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

Die tweede versoek vra die tweede eindpunt vir tydelike toestemmings vir hierdie geval:

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

Die kliënt kan dit vir 'n kort tydperk gebruik en moet van tyd tot tyd nuwe sertifikate bekom (voordat dit is Expiration). Die model is eenvoudig: AWS roteer tydelike sleutels gereeld vir sekuriteitsredes, maar kliënte kan dit vir 'n paar minute in die kas kas om te vergoed vir die prestasieboete wat verband hou met die verkryging van nuwe sertifikate.

Die AWS Java SDK behoort die verantwoordelikheid vir die organisering van hierdie proses oor te neem, maar om een ​​of ander rede gebeur dit nie.

Nadat ons probleme op GitHub gesoek het, het ons op 'n probleem afgekom #1921. Sy het ons gehelp om te bepaal in watter rigting om verder te "grawe".

Die AWS SDK dateer sertifikate op wanneer een van die volgende toestande voorkom:

  • Vervaldatum (Expiration) Val in EXPIRATION_THRESHOLD, hardkodeer tot 15 minute.
  • Meer tyd het verloop sedert die laaste poging om sertifikate te hernu as REFRESH_THRESHOLD, hardkodeer vir 60 minute.

Om die werklike vervaldatum van die sertifikate wat ons ontvang te sien, het ons die bogenoemde cURL-opdragte vanaf beide die houer en die EC2-instansie uitgevoer. Die geldigheidstydperk van die sertifikaat wat van die houer ontvang is, blyk baie korter te wees: presies 15 minute.

Nou het alles duidelik geword: vir die eerste versoek het ons diens tydelike sertifikate ontvang. Aangesien hulle nie vir meer as 15 minute geldig was nie, het die AWS SDK besluit om hulle op te dateer die volgende keer dat hulle versoek word. En dit het met elke versoek gebeur.

Waarom het die geldigheidstydperk van sertifikate korter geword?

AWS Instance Metadata is ontwerp om met EC2-gevalle te werk, nie Kubernetes nie. Aan die ander kant wou ons nie die toepassingskoppelvlak verander nie. Hiervoor het ons gebruik KIAM - 'n instrument wat, met behulp van agente op elke Kubernetes-nodus, gebruikers (ingenieurs wat toepassings na 'n groepie ontplooi) toelaat om IAM-rolle aan houers in peule toe te wys asof dit EC2-gevalle is. KIAM onderskep oproepe na die AWS Instance Metadata-diens en verwerk dit vanaf sy kas, nadat hulle dit voorheen van AWS ontvang het. Uit die toepassingsoogpunt verander niks nie.

KIAM verskaf korttermynsertifikate aan peule. Dit maak sin as in ag geneem word dat die gemiddelde lewensduur van 'n peul korter is as dié van 'n EC2-geval. Verstek geldigheidstydperk vir sertifikate gelyk aan dieselfde 15 minute.

As gevolg hiervan, as u beide verstekwaardes bo-op mekaar oorlê, ontstaan ​​'n probleem. Elke sertifikaat wat aan 'n aansoek verskaf word, verval na 15 minute. Die AWS Java SDK dwing egter 'n hernuwing af van enige sertifikaat wat minder as 15 minute oor het voor sy vervaldatum.

As gevolg hiervan word die tydelike sertifikaat gedwing om met elke versoek hernu te word, wat 'n paar oproepe na die AWS API behels en 'n aansienlike toename in latensie veroorsaak. In AWS Java SDK het ons gevind funksie versoek, wat 'n soortgelyke probleem noem.

Die oplossing blyk eenvoudig te wees. Ons het KIAM eenvoudig herkonfigureer om sertifikate met 'n langer geldigheidstydperk aan te vra. Sodra dit gebeur het, het versoeke begin vloei sonder die deelname van die AWS Metadata-diens, en die vertraging het tot selfs laer vlakke gedaal as in EC2.

Bevindinge

Gebaseer op ons ervaring met migrasies, is een van die mees algemene bronne van probleme nie foute in Kubernetes of ander elemente van die platform nie. Dit spreek ook geen fundamentele gebreke aan in die mikrodienste wat ons oordra nie. Probleme ontstaan ​​dikwels bloot omdat ons verskillende elemente saamvoeg.

Ons meng komplekse stelsels saam wat nog nooit voorheen met mekaar in wisselwerking was nie, en verwag dat hulle saam 'n enkele, groter stelsel sal vorm. Helaas, hoe meer elemente, hoe meer ruimte vir foute, hoe hoër is die entropie.

In ons geval was die hoë vertraging nie die gevolg van foute of slegte besluite in Kubernetes, KIAM, AWS Java SDK of ons mikrodiens nie. Dit was die resultaat van die kombinasie van twee onafhanklike verstek instellings: een in KIAM, die ander in die AWS Java SDK. Afsonderlik geneem, maak beide parameters sin: die aktiewe sertifikaathernuwingsbeleid in die AWS Java SDK, en die kort geldigheidstydperk van sertifikate in KAIM. Maar wanneer jy dit saamstel, word die resultate onvoorspelbaar. Twee onafhanklike en logiese oplossings hoef nie sin te maak wanneer dit gekombineer word nie.

PS van vertaler

Jy kan meer leer oor die argitektuur van die KIAM-hulpprogram vir die integrasie van AWS IAM met Kubernetes by Hierdie artikel van sy skeppers.

Lees ook op ons blog:

Bron: will.com

Voeg 'n opmerking