"Ang mga Kubernetes ay tumaas ng latency ng 10 beses": sino ang dapat sisihin para dito?

Tandaan. transl.: Ang artikulong ito, na isinulat ni Galo Navarro, na humahawak ng posisyon ng Principal Software Engineer sa European company na Adevinta, ay isang kaakit-akit at nakapagtuturo na "pagsisiyasat" sa larangan ng mga operasyon sa imprastraktura. Ang orihinal na pamagat nito ay bahagyang pinalawak sa pagsasalin sa kadahilanang ipinaliwanag ng may-akda sa simula pa lamang.

"Ang mga Kubernetes ay tumaas ng latency ng 10 beses": sino ang dapat sisihin para dito?

Paalala mula sa may-akda: Mukhang ang post na ito naaakit higit na pansin kaysa sa inaasahan. Nagagalit pa rin ako sa mga komento na ang pamagat ng artikulo ay nakaliligaw at ang ilang mga mambabasa ay nalulungkot. Naiintindihan ko ang mga dahilan para sa kung ano ang nangyayari, samakatuwid, sa kabila ng panganib na masira ang buong intriga, nais kong agad na sabihin sa iyo kung tungkol saan ang artikulong ito. Isang nakakagulat na bagay na nakita ko habang lumilipat ang mga team sa Kubernetes ay kapag may problema (gaya ng tumaas na latency pagkatapos ng paglipat), ang unang masisisi ay ang Kubernetes, ngunit lumalabas na ang orchestrator ay hindi talaga sisihin. Ang artikulong ito ay nagsasabi tungkol sa isang ganoong kaso. Inuulit ng pangalan nito ang tandang ng isa sa aming mga developer (mamaya makikita mo na walang kinalaman ang Kubernetes dito). Hindi ka makakahanap ng anumang nakakagulat na paghahayag tungkol sa Kubernetes dito, ngunit maaari mong asahan ang ilang magagandang aral tungkol sa mga kumplikadong sistema.

Ilang linggo na ang nakalipas, ang aking team ay naglilipat ng isang microservice sa isang pangunahing platform na may kasamang CI/CD, isang runtime na nakabatay sa Kubernetes, mga sukatan, at iba pang mga goodies. Ang paglipat ay likas na pagsubok: binalak naming gawin ito bilang batayan at ilipat ang humigit-kumulang 150 pang serbisyo sa mga darating na buwan. Lahat sila ay responsable para sa pagpapatakbo ng ilan sa mga pinakamalaking online na platform sa Spain (Infojobs, Fotocasa, atbp.).

Pagkatapos naming i-deploy ang application sa Kubernetes at i-redirect ang ilang trapiko dito, isang nakababahalang sorpresa ang naghihintay sa amin. Pagkaantala (latency) ang mga kahilingan sa Kubernetes ay 10 beses na mas mataas kaysa sa EC2. Sa pangkalahatan, kinakailangan na makahanap ng solusyon sa problemang ito, o iwanan ang paglipat ng microservice (at, marahil, ang buong proyekto).

Bakit mas mataas ang latency sa Kubernetes kaysa sa EC2?

Upang mahanap ang bottleneck, nangolekta kami ng mga sukatan sa buong path ng kahilingan. Ang aming arkitektura ay simple: isang API gateway (Zuul) ang humihiling ng mga proxy sa mga microservice na instance sa EC2 o Kubernetes. Sa Kubernetes ginagamit namin ang NGINX Ingress Controller, at ang mga backend ay mga ordinaryong bagay tulad ng paglawak na may JVM application sa Spring platform.

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

Ang problema ay tila nauugnay sa paunang latency sa backend (minarkahan ko ang lugar ng problema sa graph bilang "xx"). Sa EC2, ang tugon sa application ay tumagal ng humigit-kumulang 20ms. Sa Kubernetes, tumaas ang latency sa 100-200 ms.

Mabilis naming ibinasura ang mga malamang na suspek na nauugnay sa pagbabago ng runtime. Ang bersyon ng JVM ay nananatiling pareho. Ang mga problema sa containerization ay wala ring kinalaman dito: matagumpay na tumatakbo ang application sa mga container sa EC2. Naglo-load? Ngunit napansin namin ang mataas na latency kahit na sa 1 kahilingan bawat segundo. Ang mga paghinto para sa pagkolekta ng basura ay maaari ding mapabayaan.

Ang isa sa aming mga admin ng Kubernetes ay nagtaka kung ang application ay may mga panlabas na dependency dahil ang mga query sa DNS ay nagdulot ng mga katulad na isyu sa nakaraan.

Hypothesis 1: Resolusyon ng pangalan ng DNS

Para sa bawat kahilingan, ina-access ng aming application ang isang halimbawa ng AWS Elasticsearch nang isa hanggang tatlong beses sa isang domain na katulad elastic.spain.adevinta.com. Sa loob ng aming mga lalagyan may kabibi, para masuri natin kung talagang tumatagal ang paghahanap ng domain.

Mga query sa DNS mula sa lalagyan:

[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

Mga katulad na kahilingan mula sa isa sa mga instance ng EC2 kung saan tumatakbo ang application:

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

Isinasaalang-alang na ang paghahanap ay tumagal ng humigit-kumulang 30ms, naging malinaw na ang DNS resolution kapag ina-access ang Elasticsearch ay talagang nag-aambag sa pagtaas ng latency.

Gayunpaman, ito ay kakaiba sa dalawang kadahilanan:

  1. Mayroon na kaming isang toneladang Kubernetes application na nakikipag-ugnayan sa mga mapagkukunan ng AWS nang hindi dumaranas ng mataas na latency. Anuman ang dahilan, partikular na nauugnay ito sa kasong ito.
  2. Alam namin na ang JVM ay gumagawa ng in-memory DNS caching. Sa aming mga larawan, ang halaga ng TTL ay nakasulat sa $JAVA_HOME/jre/lib/security/java.security at itakda sa 10 segundo: networkaddress.cache.ttl = 10. Sa madaling salita, dapat i-cache ng JVM ang lahat ng mga query sa DNS sa loob ng 10 segundo.

Upang kumpirmahin ang unang hypothesis, nagpasya kaming ihinto ang pagtawag sa DNS saglit at tingnan kung nawala ang problema. Una, nagpasya kaming muling i-configure ang application upang direktang makipag-ugnayan ito sa Elasticsearch sa pamamagitan ng IP address, sa halip na sa pamamagitan ng isang domain name. Mangangailangan ito ng mga pagbabago sa code at isang bagong deployment, kaya na-map lang namin ang domain sa IP address nito /etc/hosts:

34.55.5.111 elastic.spain.adevinta.com

Ngayon ang lalagyan ay nakatanggap ng isang IP halos kaagad. Nagresulta ito sa ilang pagpapabuti, ngunit mas malapit lang kami sa inaasahang antas ng latency. Bagama't tumagal ng mahabang panahon ang paglutas ng DNS, hindi pa rin kami nakatakas sa totoong dahilan.

Diagnostics sa pamamagitan ng network

Nagpasya kaming suriin ang trapiko mula sa container na ginagamit tcpdumpupang makita kung ano ang eksaktong nangyayari sa network:

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

Pagkatapos ay nagpadala kami ng ilang kahilingan at na-download ang kanilang pagkuha (kubectl cp my-service:/capture.pcap capture.pcap) para sa karagdagang pagsusuri sa Wireshark.

Walang kahina-hinala tungkol sa mga query sa DNS (maliban sa isang maliit na bagay na pag-uusapan ko mamaya). Ngunit may ilang mga kakaiba sa paraan ng paghawak ng aming serbisyo sa bawat kahilingan. Nasa ibaba ang isang screenshot ng pagkuha na nagpapakita ng kahilingan na tinatanggap bago magsimula ang tugon:

"Ang mga Kubernetes ay tumaas ng latency ng 10 beses": sino ang dapat sisihin para dito?

Ang mga numero ng package ay ipinapakita sa unang column. Para sa kalinawan, nilagyan ko ng kulay ang iba't ibang daloy ng TCP.

Ang berdeng stream na nagsisimula sa packet 328 ay nagpapakita kung paano ang kliyente (172.17.22.150) ay nagtatag ng TCP na koneksyon sa container (172.17.36.147). Pagkatapos ng unang pakikipagkamay (328-330), dinala ang package 331 HTTP GET /v1/.. — isang papasok na kahilingan sa aming serbisyo. Ang buong proseso ay tumagal ng 1 ms.

Ipinapakita ng gray na stream (mula sa packet 339) na nagpadala ang aming serbisyo ng HTTP na kahilingan sa Elasticsearch instance (walang TCP handshake dahil gumagamit ito ng kasalukuyang koneksyon). Tumagal ito ng 18ms.

Sa ngayon ay maayos ang lahat, at ang mga oras ay halos tumutugma sa inaasahang pagkaantala (20-30 ms kapag sinusukat mula sa kliyente).

Gayunpaman, ang asul na seksyon ay tumatagal ng 86ms. Ano ang nangyayari sa loob nito? Sa packet 333, nagpadala ang aming serbisyo ng HTTP GET request sa /latest/meta-data/iam/security-credentials, at pagkatapos nito, sa parehong koneksyon ng TCP, isa pang kahilingan sa GET /latest/meta-data/iam/security-credentials/arn:...

Nalaman namin na paulit-ulit ito sa bawat kahilingan sa buong trace. Ang resolusyon ng DNS ay talagang mas mabagal sa aming mga lalagyan (ang paliwanag para sa hindi pangkaraniwang bagay na ito ay medyo kawili-wili, ngunit ise-save ko ito para sa isang hiwalay na artikulo). Lumalabas na ang sanhi ng mahabang pagkaantala ay mga tawag sa serbisyo ng AWS Instance Metadata sa bawat kahilingan.

Hypothesis 2: mga hindi kinakailangang tawag sa AWS

Parehong kabilang sa mga endpoint AWS Instance Metadata API. Ginagamit ng aming microservice ang serbisyong ito habang pinapatakbo ang Elasticsearch. Ang parehong mga tawag ay bahagi ng pangunahing proseso ng awtorisasyon. Ang endpoint na na-access sa unang kahilingan ay nagbibigay ng tungkulin ng IAM na nauugnay sa instance.

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

Ang pangalawang kahilingan ay humihingi sa pangalawang endpoint para sa mga pansamantalang pahintulot para sa pagkakataong ito:

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

Maaaring gamitin ng kliyente ang mga ito sa maikling panahon at dapat pana-panahong kumuha ng mga bagong sertipiko (bago ang mga ito Expiration). Ang modelo ay simple: madalas na iniikot ng AWS ang mga pansamantalang key para sa mga kadahilanang panseguridad, ngunit maaaring i-cache ng mga kliyente ang mga ito sa loob ng ilang minuto upang mabayaran ang parusa sa pagganap na nauugnay sa pagkuha ng mga bagong certificate.

Dapat sakupin ng AWS Java SDK ang responsibilidad para sa pag-aayos ng prosesong ito, ngunit sa ilang kadahilanan ay hindi ito nangyayari.

Pagkatapos maghanap ng mga isyu sa GitHub, may nakita kaming problema #1921. Tinulungan niya kaming matukoy ang direksyon kung saan "maghuhukay" pa.

Ina-update ng AWS SDK ang mga certificate kapag nangyari ang isa sa mga sumusunod na kundisyon:

  • Petsa ng pagkawalang bisa (Expiration) Nahulog sa EXPIRATION_THRESHOLD, na-hardcode sa 15 minuto.
  • Mas maraming oras ang lumipas mula noong huling pagtatangka na mag-renew ng mga sertipiko REFRESH_THRESHOLD, na-hardcode sa loob ng 60 minuto.

Upang makita ang aktwal na petsa ng pag-expire ng mga certificate na natatanggap namin, pinatakbo namin ang mga cURL na command sa itaas mula sa container at sa EC2 instance. Ang panahon ng bisa ng sertipiko na natanggap mula sa lalagyan ay naging mas maikli: eksaktong 15 minuto.

Ngayon ang lahat ay naging malinaw: para sa unang kahilingan, ang aming serbisyo ay nakatanggap ng mga pansamantalang sertipiko. Dahil hindi wasto ang mga ito nang higit sa 15 minuto, magpapasya ang AWS SDK na i-update ang mga ito sa isang kasunod na kahilingan. At nangyari ito sa bawat kahilingan.

Bakit naging mas maikli ang validity period ng mga certificate?

Ang AWS Instance Metadata ay idinisenyo upang gumana sa mga instance ng EC2, hindi sa Kubernetes. Sa kabilang banda, hindi namin nais na baguhin ang interface ng application. Para dito ginamit namin KIAM - isang tool na, gamit ang mga ahente sa bawat Kubernetes node, ay nagbibigay-daan sa mga user (mga inhinyero na nagde-deploy ng mga application sa isang cluster) na magtalaga ng mga tungkulin ng IAM sa mga container sa mga pod na parang mga EC2 instance ang mga ito. Hinaharang ng KIAM ang mga tawag sa serbisyo ng AWS Instance Metadata at pinoproseso ang mga ito mula sa cache nito, na dati nang natanggap ang mga ito mula sa AWS. Mula sa pananaw ng aplikasyon, walang pagbabago.

Nagbibigay ang KIAM ng mga panandaliang sertipiko sa mga pod. Makatuwiran ito kung isasaalang-alang na ang average na habang-buhay ng isang pod ay mas maikli kaysa sa isang EC2 instance. Default na panahon ng bisa para sa mga sertipiko katumbas ng parehong 15 minuto.

Bilang isang resulta, kung i-overlay mo ang parehong mga default na halaga sa ibabaw ng bawat isa, isang problema ang lilitaw. Ang bawat sertipiko na ibinigay sa isang aplikasyon ay mag-e-expire pagkatapos ng 15 minuto. Gayunpaman, pinipilit ng AWS Java SDK ang pag-renew ng anumang certificate na wala pang 15 minuto ang natitira bago ang petsa ng pag-expire nito.

Bilang resulta, ang pansamantalang certificate ay napipilitang i-renew sa bawat kahilingan, na nangangailangan ng ilang tawag sa AWS API at nagreresulta sa isang makabuluhang pagtaas sa latency. Sa AWS Java SDK nakita namin hiling sa tampok, na nagbabanggit ng katulad na problema.

Ang solusyon ay naging simple. Ni-reconfigure lang namin ang KIAM para humiling ng mga certificate na may mas mahabang panahon ng bisa. Sa sandaling nangyari ito, nagsimulang dumaloy ang mga kahilingan nang walang paglahok ng serbisyo ng AWS Metadata, at bumaba ang latency sa mas mababang antas kaysa sa EC2.

Natuklasan

Batay sa aming karanasan sa mga paglilipat, isa sa mga pinakakaraniwang pinagmumulan ng mga problema ay hindi mga bug sa Kubernetes o iba pang elemento ng platform. Hindi rin nito tinutugunan ang anumang pangunahing mga bahid sa mga microservice na aming ini-port. Madalas lumitaw ang mga problema dahil lamang sa pinagsama-sama natin ang iba't ibang elemento.

Pinagsasama-sama namin ang mga kumplikadong sistema na hindi kailanman nakikipag-ugnayan sa isa't isa, inaasahan na magkasama silang bubuo ng isang solong, mas malaking sistema. Naku, mas maraming elemento, mas maraming puwang para sa mga error, mas mataas ang entropy.

Sa aming kaso, ang mataas na latency ay hindi resulta ng mga bug o masamang desisyon sa Kubernetes, KIAM, AWS Java SDK, o aming microservice. Ito ay resulta ng pagsasama-sama ng dalawang independiyenteng default na mga setting: isa sa KIAM, ang isa pa sa AWS Java SDK. Kung hiwalay, ang parehong parameter ay may katuturan: ang aktibong patakaran sa pag-renew ng certificate sa AWS Java SDK, at ang maikling panahon ng validity ng mga certificate sa KAIM. Ngunit kapag pinagsama mo ang mga ito, ang mga resulta ay nagiging hindi mahuhulaan. Dalawang independiyente at lohikal na solusyon ay hindi kailangang magkaroon ng kahulugan kapag pinagsama.

PS mula sa tagasalin

Maaari kang matuto nang higit pa tungkol sa arkitektura ng KIAM utility para sa pagsasama ng AWS IAM sa Kubernetes sa artikulong ito mula sa mga tagalikha nito.

Basahin din sa aming blog:

Pinagmulan: www.habr.com

Magdagdag ng komento