"Kubernetes pliigis latentecon de 10 fojojn": kiu kulpas pri tio?

Notu. transl.: Ĉi tiu artikolo, verkita de Galo Navarro, kiu okupas la postenon de Ĉefa Programaro-Inĝeniero ĉe la eŭropa kompanio Adevinta, estas fascina kaj instrua "enketo" en la kampo de infrastrukturaj operacioj. Ĝia origina titolo estis iomete pligrandigita en traduko pro kialo, kiun la aŭtoro klarigas ĉe la komenco mem.

"Kubernetes pliigis latentecon de 10 fojojn": kiu kulpas pri tio?

Noto de la aŭtoro: Aspektas kiel ĉi tiu afiŝo altirita multe pli da atento ol atendite. Mi ankoraŭ ricevas kolerajn komentojn, ke la titolo de la artikolo estas misgvida kaj ke kelkaj legantoj malĝojas. Mi komprenas la kialojn de tio, kio okazas, do, malgraŭ la risko ruinigi la tutan intrigon, mi volas tuj diri al vi, pri kio temas ĉi tiu artikolo. Kurioza afero, kiun mi vidis dum teamoj migras al Kubernetes, estas, ke kiam ajn problemo ekestas (kiel ekzemple pliigita latenteco post migrado), la unua afero, kiu estas kulpigita, estas Kubernetes, sed tiam rezultas, ke la orkestranto vere ne estas. kulpo. Ĉi tiu artikolo rakontas pri unu tia kazo. Ĝia nomo ripetas la ekkrion de unu el niaj programistoj (poste vi vidos, ke Kubernetes havas nenion komunan kun ĝi). Vi ne trovos surprizajn revelaciojn pri Kubernetes ĉi tie, sed vi povas atendi kelkajn bonajn lecionojn pri kompleksaj sistemoj.

Antaŭ kelkaj semajnoj, mia teamo migris ununuran mikroservon al kerna platformo, kiu inkludis CI/KD, rultempon bazitan en Kubernetes, metrikojn kaj aliajn bonaĵojn. La movo estis de prova naturo: ni planis preni ĝin kiel bazon kaj transdoni proksimume 150 pliajn servojn en la venontaj monatoj. Ĉiuj ili respondecas pri la funkciado de kelkaj el la plej grandaj retaj platformoj en Hispanio (Infojobs, Fotocasa, ktp.).

Post kiam ni deplojis la aplikaĵon al Kubernetes kaj redirektis iom da trafiko al ĝi, alarma surprizo atendis nin. Prokrasto (latenteco) petoj en Kubernetes estis 10 fojojn pli altaj ol en EC2. Ĝenerale, necesis aŭ trovi solvon al ĉi tiu problemo, aŭ forlasi la migradon de la mikroservo (kaj, eble, la tutan projekton).

Kial la latenco estas tiom pli alta en Kubernetes ol en EC2?

Por trovi la botelon, ni kolektis metrikojn laŭ la tuta peta vojo. Nia arkitekturo estas simpla: API-enirejo (Zuul) sendas petojn al mikroservoj en EC2 aŭ Kubernetes. En Kubernetes ni uzas NGINX Ingress Controller, kaj la backends estas ordinaraj objektoj kiel deplojo kun JVM-aplikaĵo sur la Spring-platformo.

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

La problemo ŝajnis rilati al komenca latenteco en la backend (mi markis la problemareon sur la grafikaĵo kiel "xx"). Sur EC2, la aplika respondo daŭris ĉirkaŭ 20ms. En Kubernetes, la latenteco pliiĝis al 100-200 ms.

Ni rapide forĵetis la verŝajnajn suspektatojn rilatajn al la rultempa ŝanĝo. La JVM-versio restas la sama. Problemoj pri kontenigo ankaŭ havis nenion komunan kun ĝi: la aplikaĵo jam funkciis sukcese en ujoj sur EC2. Ĉu ŝargi? Sed ni observis altajn latentecojn eĉ je 1 peto por sekundo. Paŭzoj por rubkolekto ankaŭ povus esti neglektitaj.

Unu el niaj administrantoj de Kubernetes scivolis ĉu la aplikaĵo havas eksterajn dependecojn ĉar DNS-demandoj kaŭzis similajn problemojn en la pasinteco.

Hipotezo 1: DNS-nomrezolucio

Por ĉiu peto, nia aplikaĵo aliras AWS Elasticsearch-instancon unu ĝis tri fojojn en domajno kiel elastic.spain.adevinta.com. Ene de niaj ujoj estas konko, do ni povas kontroli ĉu serĉi domajnon efektive prenas longan tempon.

DNS-demandoj el ujo:

[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

Similaj petoj de unu el la EC2-kazoj kie la aplikaĵo funkcias:

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

Konsiderante, ke la serĉo daŭris ĉirkaŭ 30ms, evidentiĝis, ke DNS-rezolucio alirante Elasticsearch efektive kontribuis al la pliiĝo de latenteco.

Tamen, ĉi tio estis stranga pro du kialoj:

  1. Ni jam havas multon da Kubernetes-aplikoj, kiuj interagas kun AWS-resursoj sen suferi altan latentecon. Kia ajn la kialo, ĝi rilatas specife al ĉi tiu kazo.
  2. Ni scias, ke la JVM faras en-memoran DNS-kaŝmemoron. En niaj bildoj, la TTL-valoro estas skribita $JAVA_HOME/jre/lib/security/java.security kaj agordu al 10 sekundoj: networkaddress.cache.ttl = 10. Alivorte, la JVM devus konservi ĉiujn DNS-demandojn dum 10 sekundoj.

Por konfirmi la unuan hipotezon, ni decidis ĉesi voki DNS por iom da tempo kaj vidi ĉu la problemo malaperis. Unue, ni decidis reagordi la aplikaĵon por ke ĝi komunikiĝu rekte kun Elasticsearch per IP-adreso, prefere ol per domajna nomo. Ĉi tio postulus kodŝanĝojn kaj novan disfaldon, do ni simple mapis la domajnon al ĝia IP-adreso /etc/hosts:

34.55.5.111 elastic.spain.adevinta.com

Nun la ujo ricevis IP preskaŭ tuj. Ĉi tio rezultigis iom da plibonigo, sed ni estis nur iomete pli proksimaj al la atendataj latentecaj niveloj. Kvankam DNS-rezolucio daŭris longan tempon, la vera kialo ankoraŭ eskapis nin.

Diagnozo per reto

Ni decidis analizi trafikon de la ujo uzante tcpdumppor vidi kio ĝuste okazas en la reto:

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

Ni tiam sendis plurajn petojn kaj elŝutis ilian kapton (kubectl cp my-service:/capture.pcap capture.pcap) por plia analizo en Wireshark.

Estis nenio suspektinda pri la DNS-demandoj (krom unu eta afero, pri kiu mi parolos poste). Sed estis certaj strangaĵoj en la maniero, kiel nia servo traktis ĉiun peton. Malsupre estas ekrankopio de la kapto montrante la peton akceptitan antaŭ ol la respondo komenciĝas:

"Kubernetes pliigis latentecon de 10 fojojn": kiu kulpas pri tio?

Pakaj nombroj estas montritaj en la unua kolumno. Por klareco, mi kolorkodis la malsamajn TCP-riveretojn.

La verda rivereto komencanta per pako 328 montras kiel la kliento (172.17.22.150) establis TCP-konekton al la ujo (172.17.36.147). Post la komenca manpremo (328-330), pako 331 alportis HTTP GET /v1/.. — alvenanta peto al nia servo. La tuta procezo daŭris 1 ms.

La griza fluo (de pako 339) montras, ke nia servo sendis HTTP-peton al la petskribo Elasticsearch (ne ekzistas TCP-manpremo ĉar ĝi uzas ekzistantan konekton). Ĉi tio daŭris 18 ms.

Ĝis nun ĉio estas en ordo, kaj la tempoj proksimume respondas al la atendataj prokrastoj (20-30 ms kiam mezurite de la kliento).

Tamen, la blua sekcio prenas 86ms. Kio okazas en ĝi? Kun pako 333, nia servo sendis HTTP GET peton al /latest/meta-data/iam/security-credentials, kaj tuj post ĝi, per la sama TCP-konekto, alia GET-peto al /latest/meta-data/iam/security-credentials/arn:...

Ni trovis, ke ĉi tio ripetiĝis kun ĉiu peto tra la spuro. DNS-rezolucio ja estas iom pli malrapida en niaj ujoj (la klarigo pri tiu ĉi fenomeno estas sufiĉe interesa, sed mi konservos ĝin por aparta artikolo). Montriĝis, ke la kaŭzo de la longaj prokrastoj estis vokoj al la servo de Metadatumoj de AWS Instance sur ĉiu peto.

Hipotezo 2: nenecesaj vokoj al AWS

Ambaŭ finpunktoj apartenas al AWS Instance Metadata API. Nia mikroservo uzas ĉi tiun servon dum funkciado de Elasticsearch. Ambaŭ alvokoj estas parto de la baza rajtiga procezo. La finpunkto kiu estas alirita sur la unua peto eldonas la IAM-rolon asociitan kun la kazo.

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

La dua peto petas la duan finpunkton por provizoraj permesoj por ĉi tiu kazo:

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

La kliento povas uzi ilin por mallonga tempodaŭro kaj devas periode akiri novajn atestilojn (antaŭ ol ili estas Expiration). La modelo estas simpla: AWS ofte turnas provizorajn ŝlosilojn pro sekurecaj kialoj, sed klientoj povas konservi ilin en kaŝmemoro dum kelkaj minutoj por kompensi la rendimentan punon asociitan kun akiro de novaj atestiloj.

La AWS Java SDK devus transpreni la respondecon pri organizado de ĉi tiu procezo, sed ial tio ne okazas.

Post serĉado de problemoj en GitHub, ni trovis problemon #1921. Ŝi helpis nin determini la direkton en kiu "fosi" plu.

La AWS SDK ĝisdatigas atestojn kiam okazas unu el la sekvaj kondiĉoj:

  • Limdato (Expiration) Fali en EXPIRATION_THRESHOLD, malmola kodita al 15 minutoj.
  • Pli da tempo pasis ekde la lasta provo renovigi atestojn ol REFRESH_THRESHOLD, malmola kodita dum 60 minutoj.

Por vidi la realan limdaton de la atestiloj, kiujn ni ricevas, ni rulis la suprajn cURL-komandojn de kaj la ujo kaj la EC2-instanco. La valideca periodo de la atestilo ricevita de la ujo montriĝis multe pli mallonga: ĝuste 15 minutoj.

Nun ĉio evidentiĝis: por la unua peto, nia servo ricevis provizorajn atestojn. Ĉar ili ne validis dum pli ol 15 minutoj, la AWS SDK decidus ĝisdatigi ilin laŭ posta peto. Kaj ĉi tio okazis kun ĉiu peto.

Kial la valideca periodo de atestiloj mallongiĝis?

AWS Instance Metadatumoj estas dizajnitaj por funkcii kun EC2-instancoj, ne Kubernetes. Aliflanke, ni ne volis ŝanĝi la aplikan interfacon. Por tio ni uzis KIAM - ilo kiu, uzante agentojn sur ĉiu Kubernetes-nodo, permesas al uzantoj (inĝenieroj deplojantaj aplikojn al areto) asigni IAM-rolojn al ujoj en podoj kvazaŭ ili estus EC2-instancoj. KIAM kaptas vokojn al la servo de AWS Instance Metadata kaj prilaboras ilin el sia kaŝmemoro, antaŭe ricevinte ilin de AWS. De la aplika vidpunkto, nenio ŝanĝiĝas.

KIAM liveras mallongperspektivajn atestilojn al balgoj. Ĉi tio havas sencon konsiderante ke la averaĝa vivdaŭro de balgo estas pli mallonga ol EC2-instanco. Defaŭlta validperiodo por atestiloj egala al la samaj 15 minutoj.

Kiel rezulto, se vi supermetas ambaŭ defaŭltajn valorojn unu sur la alia, problemo ekestas. Ĉiu atestilo donita al aplikaĵo eksvalidiĝas post 15 minutoj. Tamen, la AWS Java SDK devigas renovigon de iu ajn atestilo, kiu restas malpli ol 15 minutoj antaŭ sia limdato.

Kiel rezulto, la provizora atestilo estas devigita esti renovigita kun ĉiu peto, kio implicas kelkajn alvokojn al la AWS API kaj kaŭzas signifan pliiĝon de latencia. En AWS Java SDK ni trovis trajto peto, kiu mencias similan problemon.

La solvo montriĝis simpla. Ni simple reagordis KIAM por peti atestojn kun pli longa validecperiodo. Post kiam tio okazis, petoj komencis flui sen la partopreno de la AWS Metadatuma servo, kaj la latenteco falis al eĉ pli malaltaj niveloj ol en EC2.

trovoj

Surbaze de nia sperto pri migradoj, unu el la plej oftaj fontoj de problemoj ne estas cimoj en Kubernetes aŭ aliaj elementoj de la platformo. Ĝi ankaŭ ne traktas iujn fundamentajn difektojn en la mikroservoj, kiujn ni portas. Problemoj ofte aperas simple ĉar ni kunmetas malsamajn elementojn.

Ni miksas kune kompleksajn sistemojn, kiuj neniam antaŭe interagis inter si, atendante ke kune ili formos ununuran, pli grandan sistemon. Ve, ju pli da elementoj, des pli da loko por eraroj, des pli alta la entropio.

En nia kazo, la alta latenteco ne estis la rezulto de eraroj aŭ malbonaj decidoj en Kubernetes, KIAM, AWS Java SDK aŭ nia mikroservo. Ĝi estis la rezulto de kombinado de du sendependaj defaŭltaj agordoj: unu en KIAM, la alia en la AWS Java SDK. Prenitaj aparte, ambaŭ parametroj havas sencon: la aktiva atestilrenoviga politiko en la AWS Java SDK, kaj la mallonga validecperiodo de atestiloj en KAIM. Sed kiam vi kunigas ilin, la rezultoj fariĝas neantaŭvideblaj. Du sendependaj kaj logikaj solvoj ne devas havi sencon kiam kombinitaj.

PS de tradukisto

Vi povas lerni pli pri la arkitekturo de la KIAM-utilo por integri AWS IAM kun Kubernetes ĉe ĉi tiu artikolo de ĝiaj kreintoj.

Legu ankaŭ en nia blogo:

fonto: www.habr.com

Aldoni komenton