Korrigjimi i vonesës së rrjetit në Kubernetes

Korrigjimi i vonesës së rrjetit në Kubernetes

Nja dy vjet më parë Kubernetes diskutuar tashmë në blogun zyrtar të GitHub. Që atëherë, ajo është bërë teknologjia standarde për vendosjen e shërbimeve. Kubernetes tani menaxhon një pjesë të konsiderueshme të shërbimeve të brendshme dhe publike. Ndërsa grupimet tona u rritën dhe kërkesat e performancës u bënë më të rrepta, filluam të vëmë re se disa shërbime në Kubernetes po përjetonin në mënyrë sporadike vonesë që nuk mund të shpjegohej nga ngarkesa e vetë aplikacionit.

Në thelb, aplikacionet përjetojnë vonesë të rastësishme të rrjetit deri në 100 ms ose më shumë, duke rezultuar në ndërprerje kohore ose riprovime. Shërbimet pritej të ishin në gjendje t'u përgjigjeshin kërkesave shumë më shpejt se 100ms. Por kjo është e pamundur nëse vetë lidhja kërkon kaq shumë kohë. Më vete, ne vëzhguam pyetje shumë të shpejta të MySQL që duhet të zgjasin milisekonda, dhe MySQL përfundoi në milisekonda, por nga këndvështrimi i aplikacionit kërkues, përgjigja mori 100 ms ose më shumë.

Menjëherë u bë e qartë se problemi ndodhi vetëm kur lidhej me një nyje Kubernetes, edhe nëse thirrja vinte nga jashtë Kubernetes. Mënyra më e lehtë për të riprodhuar problemin është në një test Vegeta, i cili funksionon nga çdo host i brendshëm, teston shërbimin Kubernetes në një port specifik dhe regjistron në mënyrë sporadike vonesë të lartë. Në këtë artikull, ne do të shikojmë se si arritëm të gjurmojmë shkakun e këtij problemi.

Eliminimi i kompleksitetit të panevojshëm në zinxhirin që çon në dështim

Duke riprodhuar të njëjtin shembull, ne donim të ngushtonim fokusin e problemit dhe të hiqnim shtresat e panevojshme të kompleksitetit. Fillimisht, kishte shumë elementë në rrjedhën midis Vegetës dhe pods Kubernetes. Për të identifikuar një problem më të thellë të rrjetit, duhet të përjashtoni disa prej tyre.

Korrigjimi i vonesës së rrjetit në Kubernetes

Klienti (Vegeta) krijon një lidhje TCP me çdo nyje në grup. Kubernetes funksionon si një rrjet mbivendosje (në krye të rrjetit ekzistues të qendrës së të dhënave) që përdor IPIP, d.m.th., ai kapsulon paketat IP të rrjetit të mbivendosjes brenda paketave IP të qendrës së të dhënave. Kur lidheni me nyjen e parë, kryhet përkthimi i adresës së rrjetit Përkthimi i Adresës së Rrjetit (NAT) gjendje për të përkthyer adresën IP dhe portin e nyjës Kubernetes në adresën IP dhe portin në rrjetin e mbivendosjes (veçanërisht, pod me aplikacionin). Për paketat hyrëse, kryhet sekuenca e kundërt e veprimeve. Është një sistem kompleks me shumë gjendje dhe shumë elementë që përditësohen dhe ndryshojnë vazhdimisht ndërsa shërbimet vendosen dhe zhvendosen.

Shërbim tcpdump në testin Vegeta ka një vonesë gjatë shtrëngimit të duarve TCP (midis SYN dhe SYN-ACK). Për të hequr këtë kompleksitet të panevojshëm, mund të përdorni hping3 për "ping" të thjeshta me pako SYN. Ne kontrollojmë nëse ka një vonesë në paketën e përgjigjes dhe më pas rivendosim lidhjen. Ne mund t'i filtrojmë të dhënat për të përfshirë vetëm paketa më të mëdha se 100 ms dhe të marrim një mënyrë më të lehtë për të riprodhuar problemin sesa testi i shtresës së plotë të rrjetit 7 të Vegeta-s. Këtu janë "ping" të nyjeve Kubernetes duke përdorur TCP SYN/SYN-ACK në shërbimin "node port" (30927) në intervale 10ms, të filtruar nga përgjigjet më të ngadalta:

theojulienne@shell ~ $ sudo hping3 172.16.47.27 -S -p 30927 -i u10000 | egrep --line-buffered 'rtt=[0-9]{3}.'

len=46 ip=172.16.47.27 ttl=59 DF id=0 sport=30927 flags=SA seq=1485 win=29200 rtt=127.1 ms

len=46 ip=172.16.47.27 ttl=59 DF id=0 sport=30927 flags=SA seq=1486 win=29200 rtt=117.0 ms

len=46 ip=172.16.47.27 ttl=59 DF id=0 sport=30927 flags=SA seq=1487 win=29200 rtt=106.2 ms

len=46 ip=172.16.47.27 ttl=59 DF id=0 sport=30927 flags=SA seq=1488 win=29200 rtt=104.1 ms

len=46 ip=172.16.47.27 ttl=59 DF id=0 sport=30927 flags=SA seq=5024 win=29200 rtt=109.2 ms

len=46 ip=172.16.47.27 ttl=59 DF id=0 sport=30927 flags=SA seq=5231 win=29200 rtt=109.2 ms

Mund të bëjë menjëherë vëzhgimin e parë. Duke gjykuar nga numrat e sekuencës dhe oraret, është e qartë se këto nuk janë bllokime të njëhershme. Vonesa shpesh grumbullohet dhe përfundimisht përpunohet.

Më pas, ne duam të zbulojmë se cilët komponentë mund të përfshihen në shfaqjen e mbingarkesës. Ndoshta këto janë disa nga qindra rregullat iptables në NAT? Apo ka ndonjë problem me tunelimin IPIP në rrjet? Një mënyrë për ta kontrolluar këtë është të testoni çdo hap të sistemit duke e eliminuar atë. Çfarë ndodh nëse hiqni logjikën NAT dhe firewall, duke lënë vetëm pjesën IPIP:

Korrigjimi i vonesës së rrjetit në Kubernetes

Për fat të mirë, Linux e bën të lehtë qasjen drejtpërdrejt në shtresën e mbivendosjes IP nëse makina është në të njëjtin rrjet:

theojulienne@kube-node-client ~ $ sudo hping3 10.125.20.64 -S -i u10000 | egrep --line-buffered 'rtt=[0-9]{3}.'

len=40 ip=10.125.20.64 ttl=64 DF id=0 sport=0 flags=RA seq=7346 win=0 rtt=127.3 ms

len=40 ip=10.125.20.64 ttl=64 DF id=0 sport=0 flags=RA seq=7347 win=0 rtt=117.3 ms

len=40 ip=10.125.20.64 ttl=64 DF id=0 sport=0 flags=RA seq=7348 win=0 rtt=107.2 ms

Duke gjykuar nga rezultatet, problemi mbetet akoma! Kjo përjashton iptables dhe NAT. Pra problemi është TCP? Le të shohim se si shkon një ping i rregullt ICMP:

theojulienne@kube-node-client ~ $ sudo hping3 10.125.20.64 --icmp -i u10000 | egrep --line-buffered 'rtt=[0-9]{3}.'

len=28 ip=10.125.20.64 ttl=64 id=42594 icmp_seq=104 rtt=110.0 ms

len=28 ip=10.125.20.64 ttl=64 id=49448 icmp_seq=4022 rtt=141.3 ms

len=28 ip=10.125.20.64 ttl=64 id=49449 icmp_seq=4023 rtt=131.3 ms

len=28 ip=10.125.20.64 ttl=64 id=49450 icmp_seq=4024 rtt=121.2 ms

len=28 ip=10.125.20.64 ttl=64 id=49451 icmp_seq=4025 rtt=111.2 ms

len=28 ip=10.125.20.64 ttl=64 id=49452 icmp_seq=4026 rtt=101.1 ms

len=28 ip=10.125.20.64 ttl=64 id=50023 icmp_seq=4343 rtt=126.8 ms

len=28 ip=10.125.20.64 ttl=64 id=50024 icmp_seq=4344 rtt=116.8 ms

len=28 ip=10.125.20.64 ttl=64 id=50025 icmp_seq=4345 rtt=106.8 ms

len=28 ip=10.125.20.64 ttl=64 id=59727 icmp_seq=9836 rtt=106.1 ms

Rezultatet tregojnë se problemi nuk është zhdukur. Ndoshta ky është një tunel IPIP? Le ta thjeshtojmë testin më tej:

Korrigjimi i vonesës së rrjetit në Kubernetes

A dërgohen të gjitha paketat midis këtyre dy hosteve?

theojulienne@kube-node-client ~ $ sudo hping3 172.16.47.27 --icmp -i u10000 | egrep --line-buffered 'rtt=[0-9]{3}.'

len=46 ip=172.16.47.27 ttl=61 id=41127 icmp_seq=12564 rtt=140.9 ms

len=46 ip=172.16.47.27 ttl=61 id=41128 icmp_seq=12565 rtt=130.9 ms

len=46 ip=172.16.47.27 ttl=61 id=41129 icmp_seq=12566 rtt=120.8 ms

len=46 ip=172.16.47.27 ttl=61 id=41130 icmp_seq=12567 rtt=110.8 ms

len=46 ip=172.16.47.27 ttl=61 id=41131 icmp_seq=12568 rtt=100.7 ms

len=46 ip=172.16.47.27 ttl=61 id=9062 icmp_seq=31443 rtt=134.2 ms

len=46 ip=172.16.47.27 ttl=61 id=9063 icmp_seq=31444 rtt=124.2 ms

len=46 ip=172.16.47.27 ttl=61 id=9064 icmp_seq=31445 rtt=114.2 ms

len=46 ip=172.16.47.27 ttl=61 id=9065 icmp_seq=31446 rtt=104.2 ms

Ne e kemi thjeshtuar situatën në dy nyje Kubernetes që i dërgojnë njëri-tjetrit çdo paketë, madje edhe një ping ICMP. Ata ende shohin vonesë nëse hosti i synuar është "i keq" (disa më keq se të tjerët).

Tani pyetja e fundit: pse vonesa ndodh vetëm në serverët kube-node? Dhe a ndodh kur kube-node është dërguesi apo marrësi? Për fat të mirë, kjo është gjithashtu mjaft e lehtë për t'u kuptuar duke dërguar një paketë nga një host jashtë Kubernetes, por me të njëjtin marrës "të njohur të keq". Siç mund ta shihni, problemi nuk është zhdukur:

theojulienne@shell ~ $ sudo hping3 172.16.47.27 -p 9876 -S -i u10000 | egrep --line-buffered 'rtt=[0-9]{3}.'

len=46 ip=172.16.47.27 ttl=61 DF id=0 sport=9876 flags=RA seq=312 win=0 rtt=108.5 ms

len=46 ip=172.16.47.27 ttl=61 DF id=0 sport=9876 flags=RA seq=5903 win=0 rtt=119.4 ms

len=46 ip=172.16.47.27 ttl=61 DF id=0 sport=9876 flags=RA seq=6227 win=0 rtt=139.9 ms

len=46 ip=172.16.47.27 ttl=61 DF id=0 sport=9876 flags=RA seq=7929 win=0 rtt=131.2 ms

Më pas do të ekzekutojmë të njëjtat kërkesa nga nyja kube e burimit të mëparshëm te hosti i jashtëm (i cili përjashton hostin burim pasi ping përfshin një komponent RX dhe TX):

theojulienne@kube-node-client ~ $ sudo hping3 172.16.33.44 -p 9876 -S -i u10000 | egrep --line-buffered 'rtt=[0-9]{3}.'
^C
--- 172.16.33.44 hping statistic ---
22352 packets transmitted, 22350 packets received, 1% packet loss
round-trip min/avg/max = 0.2/7.6/1010.6 ms

Duke ekzaminuar kapjen e paketave të vonesës, ne morëm disa informacione shtesë. Konkretisht, që dërguesi (poshtë) e sheh këtë afat kohor, por marrësi (lart) jo - shiko kolonën Delta (në sekonda):

Korrigjimi i vonesës së rrjetit në Kubernetes

Përveç kësaj, nëse shikoni ndryshimin në rendin e paketave TCP dhe ICMP (sipas numrave të sekuencës) në anën e marrësit, paketat ICMP mbërrijnë gjithmonë në të njëjtën sekuencë në të cilën janë dërguar, por me kohë të ndryshme. Në të njëjtën kohë, paketat TCP ndonjëherë ndërlidhen dhe disa prej tyre ngecin. Në veçanti, nëse shqyrtoni portat e paketave SYN, ato janë në rregull nga ana e dërguesit, por jo nga ana e marrësit.

Ka një ndryshim delikate se si kartat e rrjetit serverët modernë (si ata në qendrën tonë të të dhënave) përpunojnë paketa që përmbajnë TCP ose ICMP. Kur një paketë mbërrin, përshtatësi i rrjetit "hashes atë për lidhje", domethënë, ai përpiqet të thyejë lidhjet në radhë dhe të dërgojë secilën radhë në një bërthamë të veçantë procesori. Për TCP, ky hash përfshin adresën IP të burimit dhe destinacionit dhe portin. Me fjalë të tjera, çdo lidhje hash (potencialisht) ndryshe. Për ICMP, vetëm adresat IP janë hash, pasi nuk ka porte.

Një tjetër vëzhgim i ri: gjatë kësaj periudhe ne shohim vonesa ICMP në të gjitha komunikimet midis dy hosteve, por TCP jo. Kjo na tregon se shkaku ka të ngjarë të lidhet me hashimin e radhëve RX: mbingarkesa është pothuajse me siguri në përpunimin e paketave RX, jo në dërgimin e përgjigjeve.

Kjo eliminon dërgimin e paketave nga lista e shkaqeve të mundshme. Tani e dimë se problemi i përpunimit të paketave është në anën e pranimit në disa serverë kube-node.

Kuptimi i përpunimit të paketave në kernel Linux

Për të kuptuar pse problemi ndodh te marrësi në disa serverë kube-node, le të shohim se si kerneli Linux përpunon paketat.

Duke u kthyer në zbatimin më të thjeshtë tradicional, karta e rrjetit merr paketën dhe dërgon ndërpres kernel Linux që ka një paketë që duhet të përpunohet. Kerneli ndalon punën tjetër, kalon kontekstin në mbajtësin e ndërprerjeve, përpunon paketën dhe më pas kthehet në detyrat aktuale.

Korrigjimi i vonesës së rrjetit në Kubernetes

Ky ndërrim i kontekstit është i ngadalshëm: vonesa mund të mos ketë qenë e dukshme në kartat e rrjetit 10 Mbps në vitet '90, por në kartat moderne 10G me një xhiro maksimale prej 15 milionë paketash në sekondë, çdo bërthamë e një serveri të vogël me tetë bërthama mund të ndërpritet miliona. herë në sekondë.

Për të mos trajtuar vazhdimisht ndërprerjet, shumë vite më parë shtoi Linux NAPI: API i rrjetit që përdorin të gjithë drejtuesit modernë për të përmirësuar performancën me shpejtësi të lartë. Me shpejtësi të ulët kerneli ende merr ndërprerje nga karta e rrjetit në mënyrën e vjetër. Sapo të mbërrijnë paketa të mjaftueshme që tejkalojnë pragun, kerneli çaktivizon ndërprerjet dhe në vend të kësaj fillon të analizojë përshtatësin e rrjetit dhe të marrë paketat në copa. Përpunimi kryhet në softirq, pra në konteksti i ndërprerjeve të softuerit pas thirrjeve të sistemit dhe ndërprerjeve të harduerit, kur kerneli (në krahasim me hapësirën e përdoruesit) po funksionon tashmë.

Korrigjimi i vonesës së rrjetit në Kubernetes

Kjo është shumë më e shpejtë, por shkakton një problem tjetër. Nëse ka shumë paketa, atëherë e gjithë koha shpenzohet për përpunimin e paketave nga karta e rrjetit, dhe proceset e hapësirës së përdoruesit nuk kanë kohë për të zbrazur në të vërtetë këto radhë (leximi nga lidhjet TCP, etj.). Përfundimisht radhët mbushen dhe ne fillojmë të hedhim pako. Në një përpjekje për të gjetur një balancë, kerneli vendos një buxhet për numrin maksimal të paketave të përpunuara në kontekstin softirq. Pasi të tejkalohet ky buxhet, zgjohet një fije e veçantë ksoftirqd (do të shihni njërën prej tyre ps për bërthamë) që trajton këto softirq jashtë shtegut normal të syscall/ndërprerjes. Ky thread është planifikuar duke përdorur planifikuesin standard të procesit, i cili përpiqet të shpërndajë burimet në mënyrë të drejtë.

Korrigjimi i vonesës së rrjetit në Kubernetes

Pasi të keni studiuar se si kerneli përpunon paketat, mund të shihni se ekziston një mundësi e caktuar e mbingarkesës. Nëse telefonatat softirq pranohen më rrallë, paketat do të duhet të presin për ca kohë për t'u përpunuar në radhën RX në kartën e rrjetit. Kjo mund të jetë për shkak të disa detyrave që bllokojnë bërthamën e procesorit, ose diçka tjetër po e pengon bërthamën të funksionojë softirq.

Ngushtimi i përpunimit deri në thelbin ose metodën

Vonesat e Softirq janë vetëm një supozim për momentin. Por ka kuptim, dhe ne e dimë se po shohim diçka shumë të ngjashme. Pra, hapi tjetër është konfirmimi i kësaj teorie. Dhe nëse vërtetohet, atëherë gjeni arsyen e vonesave.

Le të kthehemi te paketat tona të ngadalta:

len=46 ip=172.16.53.32 ttl=61 id=29573 icmp_seq=1953 rtt=99.3 ms

len=46 ip=172.16.53.32 ttl=61 id=29574 icmp_seq=1954 rtt=89.3 ms

len=46 ip=172.16.53.32 ttl=61 id=29575 icmp_seq=1955 rtt=79.2 ms

len=46 ip=172.16.53.32 ttl=61 id=29576 icmp_seq=1956 rtt=69.1 ms

len=46 ip=172.16.53.32 ttl=61 id=29577 icmp_seq=1957 rtt=59.1 ms

len=46 ip=172.16.53.32 ttl=61 id=29790 icmp_seq=2070 rtt=75.7 ms

len=46 ip=172.16.53.32 ttl=61 id=29791 icmp_seq=2071 rtt=65.6 ms

len=46 ip=172.16.53.32 ttl=61 id=29792 icmp_seq=2072 rtt=55.5 ms

Siç u diskutua më parë, këto pako ICMP hashohen në një radhë të vetme RX NIC dhe përpunohen nga një bërthamë e vetme CPU. Nëse duam të kuptojmë se si funksionon Linux, është e dobishme të dimë se ku (në cilën bërthamë CPU) dhe si (softirq, ksoftirqd) përpunohen këto paketa për të gjurmuar procesin.

Tani është koha për të përdorur mjete që ju lejojnë të monitoroni kernelin Linux në kohë reale. Këtu kemi përdorur BCC. Ky grup mjetesh ju lejon të shkruani programe të vogla C që lidhin funksione arbitrare në kernel dhe i ruajnë ngjarjet në një program Python në hapësirën e përdoruesit që mund t'i përpunojë ato dhe t'jua kthejë rezultatin. Lidhja e funksioneve arbitrare në kernel është një çështje komplekse, por programi është krijuar për siguri maksimale dhe është krijuar për të gjurmuar saktësisht llojin e çështjeve të prodhimit që nuk riprodhohen lehtësisht në një mjedis testimi ose zhvillimi.

Plani këtu është i thjeshtë: ne e dimë se kerneli përpunon këto ping ICMP, kështu që ne do të vendosim një goditje në funksionin e kernelit icmp_echo, i cili pranon një paketë të kërkesës për jehonë ICMP në hyrje dhe fillon dërgimin e një përgjigjeje echo ICMP. Ne mund të identifikojmë një paketë duke rritur numrin icmp_seq, i cili tregon hping3 выше.

Kod skenar bcc duket e ndërlikuar, por nuk është aq e frikshme sa duket. Funksioni icmp_echo përcjell struct sk_buff *skb: Kjo është një paketë me një "kërkesë echo". Mund ta gjurmojmë, të nxjerrim sekuencën echo.sequence (e cila krahasohet me icmp_seq nga hping3 выше), dhe dërgojeni në hapësirën e përdoruesit. Është gjithashtu i përshtatshëm për të kapur emrin/idn e procesit aktual. Më poshtë janë rezultatet që shohim drejtpërdrejt ndërsa kerneli përpunon paketat:

EMRI I PROCESIT TGID PID ICMP_SEQ 0 0 swapper/11 770 0 0 swapper/11 771 0 0 swapper/11 772 0 0 swapper/11 773 0 0 swapper/11 774 20041 20086 775 0 0 11 këmbyes/776 0 0 11 777 swapper/0 0 11 778 spokes-raport-s 4512

Këtu duhet theksuar se në kontekst softirq proceset që kanë bërë thirrjet e sistemit do të shfaqen si "procese" kur në fakt është kerneli që përpunon në mënyrë të sigurt paketat në kontekstin e kernelit.

Me këtë mjet ne mund të lidhim procese specifike me paketa specifike që tregojnë një vonesë prej hping3. Le ta bëjmë të thjeshtë grep në këtë kapje për vlera të caktuara icmp_seq. Paketat që përputhen me vlerat e mësipërme icmp_seq janë shënuar së bashku me RTT-në e tyre që kemi vërejtur më lart (në kllapa janë vlerat e pritura të RTT për paketat që kemi filtruar për shkak të vlerave RTT më pak se 50 ms):

TGID PID EMRI I PROCESIT ICMP_SEQ ** RTT -- 10137 10436 cadvisor 1951 10137 10436 cadvisor 1952 76 76 ksoftirqd/11 1953 ** 99ms 76 76 ksoftir 11 ms 1954 89 ksoftirqd 76 76 11 1955 ** 79ms 76 76 ksoftirqd/ 11 1956 ** 69 ms 76 76 ksoftirqd/11 1957 ** 59 ms 76 76 ksoftirqd/11 1958 ** (49 ms) 76 76 ksoftirqd/11 1959 ** (39 ms) ** (76 ms) ** 76 11 1960 ms (29 ms) irqd/ 76 76 ** (11ms) 1961 19 ksoftirqd/76 76 ** (11ms) -- 1962 9 cadvisor 10137 10436 2068 cadvisor 10137 10436 2069 76 ksoftirqd 76 11 ksoftirqd 2070 ms 75 76 ** 76 ms 11 2071 ksoftirqd/ 65 76 ** 76 ms 11 2072 ksoftirqd/ 55 76 ** (76 ms) 11 2073 ksoftirqd/45 76 ** (76 ms) 11 2074 ksoftirqd/35 76 ** (76 ms 11 ms) 2075 ms ) 25 76 ksoftirqd/76 11 ** (2076ms)

Rezultatet na tregojnë disa gjëra. Së pari, të gjitha këto paketa përpunohen nga konteksti ksoftirqd/11. Kjo do të thotë se për këtë çift të veçantë makinash, paketat ICMP u hash në bërthamën 11 në fundin marrës. Ne gjithashtu shohim se sa herë që ka një bllokim, ka paketa që përpunohen në kontekstin e thirrjes së sistemit cadvisor. atëherë ksoftirqd merr përsipër detyrën dhe përpunon radhën e grumbulluar: saktësisht numrin e paketave që janë grumbulluar pas cadvisor.

Fakti që menjëherë më parë funksionon gjithmonë cadvisor, nënkupton përfshirjen e tij në problem. Për ironi, qëllimi kadvisor - "analizoni përdorimin e burimeve dhe karakteristikat e performancës së kontejnerëve që funksionojnë" në vend që të shkaktojnë këtë problem të performancës.

Ashtu si me aspektet e tjera të kontejnerëve, këto janë të gjitha mjete shumë të avancuara dhe mund të pritet që të përjetojnë probleme të performancës në disa rrethana të paparashikuara.

Çfarë bën cadvisor që ngadalëson radhën e paketave?

Tani kemi një kuptim mjaft të mirë se si ndodh përplasja, çfarë procesi po e shkakton atë dhe në cilën CPU. Ne shohim që për shkak të bllokimit të vështirë, kerneli Linux nuk ka kohë për të planifikuar ksoftirqd. Dhe ne shohim që paketat përpunohen në kontekst cadvisor. Është logjike të supozohet se cadvisor lëshon një syscall të ngadaltë, pas së cilës të gjitha paketat e grumbulluara në atë kohë përpunohen:

Korrigjimi i vonesës së rrjetit në Kubernetes

Kjo është një teori, por si ta testojmë atë? Ajo që mund të bëjmë është të gjurmojmë bërthamën e CPU-së gjatë gjithë këtij procesi, të gjejmë pikën ku numri i paketave kalon buxhetin dhe thirret ksoftirqd, dhe më pas të shohim pak më tej për të parë se çfarë ekzaktësisht funksiononte në bërthamën e CPU-së pak para asaj pike. . Është njësoj si t'i bësh rreze X CPU-së çdo disa milisekonda. Do të duket diçka si kjo:

Korrigjimi i vonesës së rrjetit në Kubernetes

Në mënyrë të përshtatshme, e gjithë kjo mund të bëhet me mjetet ekzistuese. Për shembull, rekord perf kontrollon një bërthamë të caktuar të CPU-së në një frekuencë të caktuar dhe mund të gjenerojë një orar thirrjesh drejt sistemit që funksionon, duke përfshirë hapësirën e përdoruesit dhe kernelin Linux. Ju mund ta merrni këtë regjistrim dhe ta përpunoni duke përdorur një pirun të vogël të programit FlameGraph nga Brendan Gregg, i cili ruan rendin e gjurmës së stivit. Ne mund të ruajmë gjurmët e stivit të një rreshti çdo 1 ms, dhe më pas të theksojmë dhe ruajmë një mostër 100 milisekonda përpara se gjurma të godasë ksoftirqd:

# record 999 times a second, or every 1ms with some offset so not to align exactly with timers
sudo perf record -C 11 -g -F 999
# take that recording and make a simpler stack trace.
sudo perf script 2>/dev/null | ./FlameGraph/stackcollapse-perf-ordered.pl | grep ksoftir -B 100

Këtu janë rezultatet:

(сотни следов, которые выглядят похожими)

cadvisor;[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];entry_SYSCALL_64_after_swapgs;do_syscall_64;sys_read;vfs_read;seq_read;memcg_stat_show;mem_cgroup_nr_lru_pages;mem_cgroup_node_nr_lru_pages cadvisor;[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];entry_SYSCALL_64_after_swapgs;do_syscall_64;sys_read;vfs_read;seq_read;memcg_stat_show;mem_cgroup_nr_lru_pages;mem_cgroup_node_nr_lru_pages cadvisor;[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];entry_SYSCALL_64_after_swapgs;do_syscall_64;sys_read;vfs_read;seq_read;memcg_stat_show;mem_cgroup_iter cadvisor;[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];entry_SYSCALL_64_after_swapgs;do_syscall_64;sys_read;vfs_read;seq_read;memcg_stat_show;mem_cgroup_nr_lru_pages;mem_cgroup_node_nr_lru_pages cadvisor;[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];[cadvisor];entry_SYSCALL_64_after_swapgs;do_syscall_64;sys_read;vfs_read;seq_read;memcg_stat_show;mem_cgroup_nr_lru_pages;mem_cgroup_node_nr_lru_pages ksoftirqd/11;ret_from_fork;kthread;kthread;smpboot_thread_fn;smpboot_thread_fn;run_ksoftirqd;__do_softirq;net_rx_action;ixgbe_poll;ixgbe_clean_rx_irq;napi_gro_receive;netif_receive_skb_internal;inet_gro_receive;bond_handle_frame;__netif_receive_skb_core;ip_rcv_finish;ip_rcv;ip_forward_finish;ip_forward;ip_finish_output;nf_iterate;ip_output;ip_finish_output2;__dev_queue_xmit;dev_hard_start_xmit;ipip_tunnel_xmit;ip_tunnel_xmit;iptunnel_xmit;ip_local_out;dst_output;__ip_local_out;nf_hook_slow;nf_iterate;nf_conntrack_in;generic_packet;ipt_do_table;set_match_v4;ip_set_test;hash_net4_kadt;ixgbe_xmit_frame_ring;swiotlb_dma_mapping_error;hash_net4_test ksoftirqd/11;ret_from_fork;kthread;kthread;smpboot_thread_fn;smpboot_thread_fn;run_ksoftirqd;__do_softirq;net_rx_action;gro_cell_poll;napi_gro_receive;netif_receive_skb_internal;inet_gro_receive;__netif_receive_skb_core;ip_rcv_finish;ip_rcv;ip_forward_finish;ip_forward;ip_finish_output;nf_iterate;ip_output;ip_finish_output2;__dev_queue_xmit;dev_hard_start_xmit;dev_queue_xmit_nit;packet_rcv;tpacket_rcv;sch_direct_xmit;validate_xmit_skb_list;validate_xmit_skb;netif_skb_features;ixgbe_xmit_frame_ring;swiotlb_dma_mapping_error;__dev_queue_xmit;dev_hard_start_xmit;__bpf_prog_run;__bpf_prog_run

Ka shumë gjëra këtu, por gjëja kryesore është se ne gjejmë modelin "cadvisor para ksoftirqd" që pamë më herët në gjurmuesin e ICMP. Çfarë do të thotë?

Çdo linjë është një gjurmë CPU në një moment të caktuar në kohë. Çdo thirrje poshtë pirgut në një rresht ndahet me një pikëpresje. Në mes të rreshtave shohim syscallin që thirret: read(): .... ;do_syscall_64;sys_read; .... Pra, cadvisor shpenzon shumë kohë në thirrjen e sistemit read()lidhur me funksionet mem_cgroup_* (maja e pirgut të thirrjeve/fundi i linjës).

Është e papërshtatshme të shohësh në gjurmimin e një telefonate se çfarë saktësisht po lexohet, kështu që le të vrapojmë strace dhe le të shohim se çfarë bën cadvisor dhe të gjejmë thirrjet e sistemit më të gjata se 100 ms:

theojulienne@kube-node-bad ~ $ sudo strace -p 10137 -T -ff 2>&1 | egrep '<0.[1-9]'
[pid 10436] <... futex resumed> ) = 0 <0.156784>
[pid 10432] <... futex resumed> ) = 0 <0.258285>
[pid 10137] <... futex resumed> ) = 0 <0.678382>
[pid 10384] <... futex resumed> ) = 0 <0.762328>
[pid 10436] <... read resumed> "cache 154234880nrss 507904nrss_h"..., 4096) = 658 <0.179438>
[pid 10384] <... futex resumed> ) = 0 <0.104614>
[pid 10436] <... futex resumed> ) = 0 <0.175936>
[pid 10436] <... read resumed> "cache 0nrss 0nrss_huge 0nmapped_"..., 4096) = 577 <0.228091>
[pid 10427] <... read resumed> "cache 0nrss 0nrss_huge 0nmapped_"..., 4096) = 577 <0.207334>
[pid 10411] <... epoll_ctl resumed> ) = 0 <0.118113>
[pid 10382] <... pselect6 resumed> ) = 0 (Timeout) <0.117717>
[pid 10436] <... read resumed> "cache 154234880nrss 507904nrss_h"..., 4096) = 660 <0.159891>
[pid 10417] <... futex resumed> ) = 0 <0.917495>
[pid 10436] <... futex resumed> ) = 0 <0.208172>
[pid 10417] <... futex resumed> ) = 0 <0.190763>
[pid 10417] <... read resumed> "cache 0nrss 0nrss_huge 0nmapped_"..., 4096) = 576 <0.154442>

Siç mund ta prisni, ne shohim telefonata të ngadalta këtu read(). Nga përmbajtja e operacioneve dhe kontekstit të leximit mem_cgroup është e qartë se këto sfida read() referojuni dosjes memory.stat, i cili tregon përdorimin e memories dhe kufijtë e grupeve (teknologjia e izolimit të burimeve të Docker). Mjeti cadvisor kërkon këtë skedar për të marrë informacionin e përdorimit të burimeve për kontejnerët. Le të kontrollojmë nëse është kerneli ose cadvisor që bën diçka të papritur:

theojulienne@kube-node-bad ~ $ time cat /sys/fs/cgroup/memory/memory.stat >/dev/null

real 0m0.153s
user 0m0.000s
sys 0m0.152s
theojulienne@kube-node-bad ~ $

Tani mund të riprodhojmë gabimin dhe të kuptojmë se kerneli Linux po përballet me një patologji.

Pse funksioni i leximit është kaq i ngadaltë?

Në këtë fazë, është shumë më e lehtë të gjesh mesazhe nga përdoruesit e tjerë për probleme të ngjashme. Siç doli, në gjurmuesin cadvisor ky gabim u raportua si problemi i përdorimit të tepërt të CPU-së, thjesht askush nuk e vuri re që vonesa gjithashtu reflektohet rastësisht në grupin e rrjetit. Vërtet u vu re se cadvisor po konsumonte më shumë kohë CPU sesa pritej, por kësaj nuk iu kushtua shumë rëndësi, pasi serverët tanë kanë shumë burime CPU, kështu që problemi nuk u studiua me kujdes.

Problemi është se cgrupet marrin parasysh përdorimin e memories brenda hapësirës së emrave (kontejnerit). Kur të gjitha proceset në këtë cgroup dalin, Docker lëshon cgroup memorie. Megjithatë, "kujtesa" nuk është vetëm memorie procesore. Edhe pse vetë memoria e procesit nuk përdoret më, duket se kerneli ende cakton përmbajtje të ruajtura në memorie, të tilla si dhëmbët dhe inodet (metadatat e direktoriumit dhe skedarit), të cilat ruhen në memorien cgroup të memories. Nga përshkrimi i problemit:

cgroups zombie: cgroups që nuk kanë procese dhe janë fshirë, por ende kanë memorie të alokuar (në rastin tim, nga cache dentry, por mund të ndahet edhe nga cache e faqeve ose tmpfs).

Kontrolli i kernelit i të gjitha faqeve në cache gjatë çlirimit të një cgroup mund të jetë shumë i ngadalshëm, kështu që zgjidhet procesi dembel: prisni derisa këto faqe të kërkohen përsëri, dhe më pas pastroni cgroup-in kur memoria është realisht e nevojshme. Deri në këtë pikë, cgroup ende merret parasysh gjatë mbledhjes së statistikave.

Nga pikëpamja e performancës, ata sakrifikuan kujtesën për performancën: përshpejtuan pastrimin fillestar duke lënë pas disa memorie të ruajtura. Kjo është mirë. Kur kerneli përdor të fundit të memories së ruajtur, cgroup përfundimisht pastrohet, kështu që nuk mund të quhet "rrjedhje". Fatkeqësisht, zbatimi specifik i mekanizmit të kërkimit memory.stat në këtë version të kernelit (4.9), i kombinuar me sasinë e madhe të memories në serverët tanë, do të thotë se duhet shumë më tepër kohë për të rivendosur të dhënat më të fundit të ruajtura në memorie dhe për të pastruar zombitë e grupeve.

Rezulton se disa nga nyjet tona kishin aq shumë zombie të grupeve, saqë leximi dhe vonesa e kaluan një sekondë.

Zgjidhja për problemin e cadvisor është lirimi i menjëhershëm i memories së dentries/inodes në të gjithë sistemin, gjë që eliminon menjëherë vonesën e leximit si dhe vonesën e rrjetit në host, pasi pastrimi i cache-it aktivizon faqet e cgroupit të zombive të ruajtura në memorie dhe i çliron ato gjithashtu. Kjo nuk është një zgjidhje, por konfirmon shkakun e problemit.

Doli se në versionet më të reja të kernelit (4.19+) performanca e thirrjeve u përmirësua memory.stat, kështu që kalimi në këtë kernel e rregulloi problemin. Në të njëjtën kohë, ne kishim mjete për të zbuluar nyjet problematike në grupimet Kubernetes, për t'i kulluar ato me hijeshi dhe për t'i rindezur. Ne krehëm të gjitha grupet, gjetëm nyje me vonesë mjaft të lartë dhe i rindizëm ato. Kjo na dha kohë për të përditësuar OS në serverët e mbetur.

Duke përmbledhur

Për shkak se ky gabim ndaloi përpunimin e radhës RX NIC për qindra milisekonda, ai njëkohësisht shkaktoi vonesë të lartë në lidhjet e shkurtra dhe vonesë në mes të lidhjes, si p.sh. midis kërkesave MySQL dhe paketave të përgjigjeve.

Kuptimi dhe ruajtja e performancës së sistemeve më themelore, si Kubernetes, është kritike për besueshmërinë dhe shpejtësinë e të gjitha shërbimeve të bazuara në to. Çdo sistem që ju drejtoni përfiton nga përmirësimet e performancës së Kubernetes.

Burimi: www.habr.com

Shto një koment