„A Kubernetes 10-szeresére növelte a késleltetést”: ki a hibás ezért?

Jegyzet. ford.: Ez a cikk, amelyet Galo Navarro írt, aki az Adevinta európai vállalatnál a szoftvermérnöki posztot tölti be, lenyűgöző és tanulságos „vizsgálat” az infrastruktúra üzemeltetése terén. Eredeti címét a szerző a legelején kifejtett okból kissé kibővítették a fordításban.

„A Kubernetes 10-szeresére növelte a késleltetést”: ki a hibás ezért?

Megjegyzés a szerzőtől: Úgy néz ki, mint ez a bejegyzés vonzott a vártnál sokkal több figyelem. Még mindig kapok dühös megjegyzéseket, hogy a cikk címe félrevezető, és néhány olvasó elszomorodik. Megértem a történések okait, ezért az egész intrika tönkretételének kockázata ellenére azonnal el akarom mondani, miről szól ez a cikk. Különös dolog, amit láttam, amikor a csapatok áttérnek a Kubernetesre, az az, hogy amikor probléma merül fel (például megnövekedett késleltetés az átállás után), az első dolog, amit hibáztatnak, a Kubernetes, de aztán kiderül, hogy a hangszerelő nem igazán feddés. Ez a cikk egy ilyen esetről szól. A neve megismétli az egyik fejlesztőnk felkiáltását (később látni fogja, hogy a Kubernetesnek semmi köze hozzá). A Kubernetesről itt semmi meglepő kinyilatkoztatást nem találsz, de a komplex rendszerekkel kapcsolatban egy-két jó leckére számíthatsz.

Néhány héttel ezelőtt a csapatom egyetlen mikroszolgáltatást migrált egy olyan alapplatformra, amely CI/CD-t, Kubernetes-alapú futtatókörnyezetet, mérőszámokat és egyéb finomságokat tartalmazott. A lépés próba jellegű volt: azt terveztük, hogy ezt alapul vesszük, és a következő hónapokban további mintegy 150 szolgáltatást adunk át. Mindegyikük felelős Spanyolország néhány legnagyobb online platformjának (Infojobs, Fotocasa stb.) működéséért.

Miután telepítettük az alkalmazást a Kubernetesre, és átirányítottuk rá a forgalmat, riasztó meglepetés várt ránk. Késleltetés (késleltetés) Kubernetesben a kérések tízszerese voltak, mint az EC10-ben. Általában vagy megoldást kellett találni erre a problémára, vagy fel kell hagyni a mikroszolgáltatás (és esetleg az egész projekt) migrációjával.

Miért olyan nagyobb a késleltetés a Kubernetesben, mint az EC2-ben?

A szűk keresztmetszet megtalálása érdekében mérőszámokat gyűjtöttünk a teljes kérési útvonalon. Architektúránk egyszerű: egy API-átjáró (Zuul) proxyzik a kéréseket az EC2 vagy a Kubernetes mikroszolgáltatási példányaihoz. A Kubernetesben NGINX Ingress Controller-t használunk, és a háttérrendszerek közönséges objektumok, mint pl bevetés JVM alkalmazással a Spring platformon.

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

Úgy tűnt, hogy a probléma a háttérben lévő kezdeti késleltetéssel kapcsolatos (a grafikonon a problématerületet "xx"-ként jelöltem meg). Az EC2-n az alkalmazás válasza körülbelül 20 ms-ig tartott. Kubernetesben a várakozási idő 100-200 ms-ra nőtt.

Gyorsan elhárítottuk a futásidő változásával kapcsolatos valószínű gyanúsítottakat. A JVM változat változatlan marad. A konténerezési problémáknak sem volt köze ehhez: az alkalmazás már sikeresen futott konténerekben az EC2-n. Betöltés? De még másodpercenként 1 kérésnél is magas késleltetést figyeltünk meg. A szemétszállítás szüneteltetése is elhanyagolható.

Az egyik Kubernetes-rendszergazdánk azon töprengett, hogy az alkalmazásnak vannak-e külső függőségei, mert a DNS-lekérdezések korábban hasonló problémákat okoztak.

1. hipotézis: DNS névfeloldás

Alkalmazásunk minden kérés esetén egy-háromszor hozzáfér egy AWS Elasticsearch-példányhoz egy tartományban, például elastic.spain.adevinta.com. A konténereink belsejében van egy héj, így ellenőrizhetjük, hogy a domain keresése valóban hosszú ideig tart-e.

DNS-lekérdezések tárolóból:

[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

Hasonló kérések az EC2 egyik példányától, ahol az alkalmazás fut:

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

Figyelembe véve, hogy a keresés körülbelül 30 ms-ig tartott, világossá vált, hogy az Elasticsearch elérésekor a DNS-feloldás valóban hozzájárult a várakozási idő növekedéséhez.

Ez azonban két okból furcsa volt:

  1. Már most rengeteg Kubernetes-alkalmazásunk van, amelyek kölcsönhatásba lépnek az AWS-erőforrásokkal anélkül, hogy magas késleltetéstől szenvednének. Bármi legyen is az ok, ez kifejezetten erre az esetre vonatkozik.
  2. Tudjuk, hogy a JVM a memórián belüli DNS-gyorsítótárat végez. Képeinken a TTL érték be van írva $JAVA_HOME/jre/lib/security/java.security és állítsa 10 másodpercre: networkaddress.cache.ttl = 10. Más szavakkal, a JVM-nek 10 másodpercig gyorsítótárban kell tartania az összes DNS-lekérdezést.

Az első hipotézis megerősítése érdekében úgy döntöttünk, hogy egy időre leállítjuk a DNS hívását, és megnézzük, megszűnt-e a probléma. Először úgy döntöttünk, hogy az alkalmazást úgy konfiguráljuk át, hogy az közvetlenül IP-címen keresztül kommunikáljon az Elasticsearch-el, ne pedig domain néven keresztül. Ehhez kódmódosításra és új telepítésre lenne szükség, ezért egyszerűen leképeztük a tartományt az IP-címére /etc/hosts:

34.55.5.111 elastic.spain.adevinta.com

Most a konténer szinte azonnal kapott egy IP-címet. Ez némi javulást eredményezett, de csak kicsivel voltunk közelebb a várt késleltetési szintekhez. Bár a DNS feloldása sokáig tartott, a valódi ok még mindig elkerülte a figyelmünket.

Diagnosztika hálózaton keresztül

Úgy döntöttünk, hogy elemezzük a konténerből származó forgalmat tcpdumphogy megnézze, mi történik pontosan a hálózaton:

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

Ezután több kérést küldtünk, és letöltöttük a rögzítést (kubectl cp my-service:/capture.pcap capture.pcap) további elemzéshez Wireshark.

Nem volt semmi gyanús a DNS-lekérdezésekben (egy apróság kivételével, amiről később beszélek). De voltak furcsaságok abban, ahogyan szolgálatunk kezelte az egyes kéréseket. Az alábbiakban látható egy képernyőkép a rögzítésről, amelyen a kérés elfogadása látható a válasz megkezdése előtt:

„A Kubernetes 10-szeresére növelte a késleltetést”: ki a hibás ezért?

A csomagok száma az első oszlopban látható. Az egyértelműség kedvéért színkódoltam a különböző TCP-folyamokat.

A 328-as csomaggal kezdődő zöld adatfolyam azt mutatja, hogy az ügyfél (172.17.22.150) hogyan létesített TCP-kapcsolatot a tárolóhoz (172.17.36.147). A kezdeti kézfogás (328-330) után a 331-es csomagot hozta HTTP GET /v1/.. — egy bejövő kérés szolgáltatásunkhoz. Az egész folyamat 1 ms-ig tartott.

A szürke adatfolyam (a 339-es csomagból) azt mutatja, hogy szolgáltatásunk HTTP kérést küldött az Elasticsearch példánynak (nincs TCP kézfogás, mert meglévő kapcsolatot használ). Ez 18 ms-ig tartott.

Egyelőre minden rendben van, az idők nagyjából megfelelnek a várható késéseknek (klienstől mérve 20-30 ms).

A kék szakasz azonban 86 ms-t vesz igénybe. Mi történik benne? A 333-as csomaggal szolgáltatásunk HTTP GET kérést küldött a címre /latest/meta-data/iam/security-credentials, és közvetlenül utána ugyanazon a TCP-kapcsolaton keresztül egy másik GET-kérés /latest/meta-data/iam/security-credentials/arn:...

Azt tapasztaltuk, hogy ez minden kérésnél megismétlődött a nyomkövetés során. A DNS-feloldás valóban egy kicsit lassabb a konténereinkben (a jelenség magyarázata elég érdekes, de ezt egy külön cikknek tartom). Kiderült, hogy a hosszú késések oka az AWS Instance Metadata szolgáltatásának minden kérésnél történő hívása volt.

2. hipotézis: szükségtelen hívások az AWS-hez

Mindkét végpont tartozik AWS-példány metaadat API. Mikroszolgáltatásunk ezt a szolgáltatást használja az Elasticsearch futtatása közben. Mindkét hívás az alapengedélyezési folyamat része. Az első kérelemre elért végpont kiadja a példányhoz társított IAM-szerepet.

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

A második kérés ideiglenes engedélyeket kér a második végponttól ehhez a példányhoz:

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

Az ügyfél rövid ideig használhatja őket, és időnként új tanúsítványokat kell beszereznie (mielőtt megtörténik Expiration). A modell egyszerű: az AWS biztonsági okokból gyakran forgatja az ideiglenes kulcsokat, de az ügyfelek néhány percig gyorsítótárazhatják azokat, hogy kompenzálják az új tanúsítványok megszerzésével járó teljesítménybüntetést.

Az AWS Java SDK-nak át kell vennie a felelősséget ennek a folyamatnak a megszervezéséért, de valamiért ez nem történik meg.

Miután kerestük a problémákat a GitHubon, egy problémára bukkantunk #1921. Segített nekünk meghatározni azt az irányt, amelyben tovább kell „ásni”.

Az AWS SDK frissíti a tanúsítványokat, ha az alábbi feltételek valamelyike ​​bekövetkezik:

  • Lejárati dátum (Expiration) Beleesni EXPIRATION_THRESHOLD, 15 percre kódolva.
  • Több idő telt el a tanúsítványok legutóbbi megújítási kísérlete óta, mint REFRESH_THRESHOLD, 60 percig keménykódolt.

A kapott tanúsítványok tényleges lejárati dátumának megtekintéséhez a fenti cURL-parancsokat futtattuk mind a tárolóból, mind az EC2 példányból. A konténerből kapott igazolás érvényességi ideje jóval rövidebbnek bizonyult: pontosan 15 perc.

Most már minden világossá vált: az első megkeresésre ideiglenes tanúsítványokat kapott szolgáltatásunk. Mivel nem voltak érvényesek 15 percnél tovább, az AWS SDK úgy dönt, hogy egy későbbi kérésre frissíti őket. És ez minden kéréssel megtörtént.

Miért rövidült a tanúsítványok érvényességi ideje?

Az AWS-példány metaadatait az EC2-példányokkal való együttműködésre tervezték, nem a Kubernetes-példányokkal. Az alkalmazás felületén viszont nem akartunk változtatni. Erre használtuk KIAM - egy eszköz, amely az egyes Kubernetes-csomópontokon lévő ügynökök használatával lehetővé teszi a felhasználóknak (az alkalmazásokat egy fürtbe telepítő mérnököknek), hogy IAM-szerepköröket rendeljenek a podokban lévő tárolókhoz, mintha azok EC2-példányok lennének. A KIAM elfogja az AWS-példány metaadatszolgáltatásának hívását, és feldolgozza azokat a gyorsítótárából, miután korábban megkapta őket az AWS-től. Alkalmazási szempontból semmi sem változik.

A KIAM rövid távú tanúsítványokat biztosít a hüvelyeknek. Ez logikus, ha figyelembe vesszük, hogy a pod átlagos élettartama rövidebb, mint egy EC2 példányé. A tanúsítványok alapértelmezett érvényességi ideje egyenlő 15 perccel.

Ennek eredményeként, ha mindkét alapértelmezett értéket egymásra helyezi, probléma merül fel. Az alkalmazáshoz biztosított minden tanúsítvány 15 perc elteltével lejár. Az AWS Java SDK azonban minden olyan tanúsítvány megújítását kényszeríti, amelynek lejárati dátuma előtt kevesebb mint 15 perc van hátra.

Ennek eredményeként az ideiglenes tanúsítványt minden kéréssel meg kell újítani, ami az AWS API néhány hívását vonja maga után, és a késleltetés jelentős növekedését okozza. Az AWS Java SDK-ban találtuk funkciókérés, amely hasonló problémát említ.

A megoldás egyszerűnek bizonyult. Egyszerűen átkonfiguráltuk a KIAM-ot, hogy hosszabb érvényességi időtartamú tanúsítványokat kérjen. Miután ez megtörtént, a kérések az AWS Metadata szolgáltatás részvétele nélkül kezdtek folyni, és a várakozási idő még alacsonyabb szintre esett, mint az EC2-ben.

Álláspontja

A migrációval kapcsolatos tapasztalataink alapján az egyik leggyakoribb problémaforrás nem a Kubernetes vagy a platform egyéb elemeinek hibája. Nem javítja ki az általunk átvitt mikroszolgáltatások alapvető hibáit sem. A problémák gyakran csak azért merülnek fel, mert különböző elemeket rakunk össze.

Komplex rendszereket keverünk össze, amelyek még soha nem léptek kölcsönhatásba egymással, és arra számítunk, hogy együtt egyetlen, nagyobb rendszert alkotnak. Sajnos minél több elem, minél több hely a hibáknak, annál nagyobb az entrópia.

Esetünkben a magas késleltetés nem a Kubernetes, a KIAM, az AWS Java SDK vagy a mikroszolgáltatásunk hibáinak vagy rossz döntéseinek a következménye. Ez két független alapértelmezett beállítás kombinálásának eredménye volt: az egyik a KIAM-ban, a másik az AWS Java SDK-ban. Külön-külön mindkét paraméternek van értelme: az aktív tanúsítványmegújítási szabályzatnak az AWS Java SDK-ban, és a tanúsítványok rövid érvényességi időszakának a KAIM-ben. De ha összerakja őket, az eredmények kiszámíthatatlanok lesznek. Két független és logikus megoldásnak nem kell értelmesnek lennie, ha kombináljuk.

PS a fordítótól

Az AWS IAM és a Kubernetes integrálására szolgáló KIAM segédprogram architektúrájáról többet tudhat meg: ezt a cikket alkotóitól.

Olvassa el blogunkon is:

Forrás: will.com

Hozzászólás