ProHoster > Օրագիր > Վարչակազմը > «Kubernetes-ը 10 անգամ ավելացրել է ուշացումը». ո՞վ է դրա մեղավորը:
«Kubernetes-ը 10 անգամ ավելացրել է ուշացումը». ո՞վ է դրա մեղավորը:
Նշում. թարգմ.Այս հոդվածը, որը գրվել է Գալո Նավարոյի կողմից, ով զբաղեցնում է եվրոպական Adevinta ընկերության գլխավոր ծրագրային ինժեների պաշտոնը, հետաքրքրաշարժ և ուսանելի «հետաքննություն» է ենթակառուցվածքների գործառնությունների ոլորտում: Նրա սկզբնական վերնագիրը թարգմանության մեջ փոքր-ինչ ընդարձակվել է այն պատճառով, որը հեղինակը բացատրում է հենց սկզբում։
Նշում հեղինակից: Կարծես այս գրառմանը ներգրավված սպասվածից շատ ավելի մեծ ուշադրություն։ Ես դեռ զայրացած մեկնաբանություններ եմ ստանում, որ հոդվածի վերնագիրը ապակողմնորոշիչ է, և որոշ ընթերցողներ տխրում են: Ես հասկանում եմ տեղի ունեցողի պատճառները, հետևաբար, չնայած ամբողջ ինտրիգը փչացնելու ռիսկին, ուզում եմ անմիջապես պատմել, թե ինչի մասին է այս հոդվածը։ Հետաքրքիր բան, որ ես տեսել եմ, երբ թիմերը տեղափոխվում են Կուբերնետես, այն է, որ երբ խնդիր է առաջանում (օրինակ՝ միգրացիայից հետո հետաձգման ավելացումը), առաջինը, որին մեղադրում են Կուբերնետեսը, բայց հետո պարզվում է, որ նվագախումբն իրականում չի ցանկանում մեղադրել. Այս հոդվածը պատմում է նման մի դեպքի մասին։ Դրա անունը կրկնում է մեր ծրագրավորողներից մեկի բացականչությունը (հետագայում կտեսնեք, որ Kubernetes-ը դրա հետ կապ չունի)։ Այստեղ Kubernetes-ի մասին որևէ զարմանալի բացահայտում չեք գտնի, բայց բարդ համակարգերի մասին կարող եք մի քանի լավ դաս ակնկալել:
Մի քանի շաբաթ առաջ իմ թիմը տեղափոխում էր մեկ միկրոսերվիս դեպի հիմնական հարթակ, որը ներառում էր CI/CD, Kubernetes-ի վրա հիմնված գործարկման ժամանակ, չափումներ և այլ առավելություններ: Այս քայլը փորձնական բնույթ էր կրում. մենք նախատեսում էինք հիմք ընդունել այն և մոտ 150 ծառայություններ փոխանցել առաջիկա ամիսներին: Նրանք բոլորն էլ պատասխանատու են Իսպանիայի մի քանի խոշոր առցանց հարթակների գործունեության համար (Infojobs, Fotocasa և այլն):
Այն բանից հետո, երբ մենք տեղադրեցինք հավելվածը Kubernetes-ում և որոշ տրաֆիկ վերահասցեավորեցինք դեպի այն, մեզ տագնապալի անակնկալ էր սպասվում: Հետաձգում (ուշացում) Kubernetes-ում հարցումները 10 անգամ ավելի շատ էին, քան EC2-ում: Ընդհանրապես, պետք էր կամ լուծում գտնել այս խնդրին, կամ հրաժարվել միկրոսերվիսի միգրացիայից (և, հնարավոր է, ամբողջ նախագծից):
Ինչո՞ւ է ուշացումը Kubernetes-ում այդքան ավելի բարձր, քան EC2-ում:
Խցանումը գտնելու համար մենք չափումներ ենք հավաքել հարցումների ողջ ճանապարհով: Մեր ճարտարապետությունը պարզ է. API gateway-ը (Zuul) վստահված անձը խնդրում է միկրոսպասարկման օրինակներ EC2-ում կամ Kubernetes-ում: Kubernetes-ում մենք օգտագործում ենք NGINX Ingress Controller, իսկ հետնամասերը սովորական օբյեկտներ են, ինչպիսիք են տեղակայումը JVM հավելվածով Spring հարթակում:
Թվում էր, թե խնդիրը կապված է սկզբնական հետաձգման հետ (ես գծապատկերում խնդրի տարածքը նշել եմ որպես «xx»): EC2-ում դիմումի պատասխանը տևեց մոտ 20 ms: Kubernetes-ում հետաձգումը բարձրացել է մինչև 100-200 ms:
Մենք արագ հեռացրինք գործարկման ժամանակի փոփոխության հետ կապված հավանական կասկածյալներին: JVM տարբերակը մնում է նույնը: Կոնտեյներացման խնդիրները նույնպես կապ չունեին դրա հետ. հավելվածն արդեն հաջողությամբ աշխատում էր EC2-ի բեռնարկղերում: Բեռնվում է Բայց մենք նկատեցինք բարձր հետաձգումներ նույնիսկ վայրկյանում 1 խնդրանքով: Աղբահանության դադարները նույնպես կարող են անտեսվել:
Մեր Kubernetes-ի ադմիններից մեկը հետաքրքրվեց, թե արդյոք հավելվածն ունի արտաքին կախվածություն, քանի որ DNS հարցումները նախկինում նմանատիպ խնդիրներ են առաջացրել:
Վարկած 1. DNS անվան լուծում
Յուրաքանչյուր հարցման համար մեր հավելվածը մեկից երեք անգամ մուտք է գործում AWS Elasticsearch օրինակ՝ նման տիրույթում elastic.spain.adevinta.com. Մեր տարաների ներսում կա պատյան, այնպես որ մենք կարող ենք ստուգել, թե արդյոք տիրույթի որոնումը իրականում երկար ժամանակ է պահանջում։
Հաշվի առնելով, որ որոնումը տևել է մոտ 30 մս, պարզ դարձավ, որ Elasticsearch մուտք գործելու ժամանակ DNS լուծումը իսկապես նպաստում է հետաձգման ավելացմանը:
Այնուամենայնիվ, սա տարօրինակ էր երկու պատճառով.
Մենք արդեն ունենք մի տոննա Kubernetes հավելվածներ, որոնք փոխազդում են AWS ռեսուրսների հետ՝ չտուժելով բարձր ուշացումից: Ինչ էլ որ լինի պատճառը, դա վերաբերում է կոնկրետ այս գործին:
Մենք գիտենք, որ JVM-ն կատարում է հիշողության մեջ DNS քեշավորում: Մեր պատկերներում TTL արժեքը գրված է $JAVA_HOME/jre/lib/security/java.security և սահմանել 10 վայրկյան. networkaddress.cache.ttl = 10. Այլ կերպ ասած, JVM-ը պետք է պահի բոլոր DNS հարցումները 10 վայրկյան:
Առաջին վարկածը հաստատելու համար մենք որոշեցինք դադարեցնել DNS զանգերը որոշ ժամանակով և տեսնել, թե արդյոք խնդիրը վերացել է: Նախ, մենք որոշեցինք վերակազմավորել հավելվածն այնպես, որ այն ուղղակիորեն հաղորդակցվի Elasticsearch-ի հետ IP հասցեով, այլ ոչ թե տիրույթի անվան միջոցով: Սա կպահանջի կոդի փոփոխություններ և նոր տեղակայում, այնպես որ մենք պարզապես քարտեզագրեցինք տիրույթը իր IP հասցեով /etc/hosts:
34.55.5.111 elastic.spain.adevinta.com
Այժմ կոնտեյները գրեթե ակնթարթորեն ստացավ IP: Սա հանգեցրեց որոշակի բարելավման, բայց մենք միայն մի փոքր ավելի մոտ էինք սպասվող հետաձգման մակարդակներին: Չնայած DNS-ի լուծումը երկար ժամանակ տևեց, իրական պատճառը դեռևս մեզանից խուսափում էր:
Ախտորոշում ցանցի միջոցով
Մենք որոշեցինք վերլուծել երթևեկությունը բեռնարկղից՝ օգտագործելով tcpdumpտեսնել, թե կոնկրետ ինչ է կատարվում ցանցում.
[root@be-851c76f696-alf8z /]# tcpdump -leni any -w capture.pcap
Այնուհետև մենք ուղարկեցինք մի քանի հարցումներ և ներբեռնեցինք դրանց գրավումը (kubectl cp my-service:/capture.pcap capture.pcap) հետագա վերլուծության համար Wireshark.
DNS հարցումների մեջ կասկածելի ոչինչ չկար (բացառությամբ մի փոքր բանի, որի մասին ես կխոսեմ ավելի ուշ): Բայց կային որոշակի տարօրինակություններ մեր ծառայության կողմից յուրաքանչյուր խնդրանքով: Ստորև ներկայացված է նկարահանման սքրինշոթը, որը ցույց է տալիս, որ հարցումն ընդունվել է մինչև պատասխանի սկիզբը.
Փաթեթի համարները ներկայացված են առաջին սյունակում: Պարզության համար ես գունավոր կոդավորել եմ TCP-ի տարբեր հոսքերը:
328 փաթեթով սկսվող կանաչ հոսքը ցույց է տալիս, թե ինչպես է հաճախորդը (172.17.22.150) TCP կապ հաստատել կոնտեյների հետ (172.17.36.147): Նախնական ձեռքսեղմումից հետո (328-330) բերվեց 331 փաթեթը HTTP GET /v1/.. — մուտքային հարցում մեր ծառայությանը: Ամբողջ գործընթացը տևեց 1 ms.
Մոխրագույն հոսքը (339 փաթեթից) ցույց է տալիս, որ մեր ծառայությունը HTTP հարցում է ուղարկել Elasticsearch օրինակին (չկա TCP ձեռքսեղմում, քանի որ այն օգտագործում է գոյություն ունեցող կապ): Սա տևեց 18 ms:
Առայժմ ամեն ինչ լավ է, և ժամանակները մոտավորապես համապատասխանում են սպասվող ուշացումներին (20-30 ms, երբ չափվում է հաճախորդից):
Այնուամենայնիվ, կապույտ հատվածը տեւում է 86 մս: Ի՞նչ է կատարվում դրա մեջ։ 333 փաթեթով մեր ծառայությունն ուղարկեց HTTP GET հարցում /latest/meta-data/iam/security-credentials, և դրանից անմիջապես հետո, նույն TCP կապի միջոցով, մեկ այլ GET հարցում /latest/meta-data/iam/security-credentials/arn:...
Մենք գտանք, որ դա կրկնվում է հետքի ընթացքում յուրաքանչյուր խնդրանքով: DNS լուծումը իսկապես մի փոքր ավելի դանդաղ է մեր կոնտեյներներում (այս երեւույթի բացատրությունը բավականին հետաքրքիր է, բայց ես այն կպահեմ առանձին հոդվածի համար): Պարզվեց, որ երկար ձգձգումների պատճառը յուրաքանչյուր հարցումով AWS Instance Metadata ծառայության զանգերն էին:
Վարկած 2. անհարկի զանգեր դեպի AWS
Երկու վերջնակետերը պատկանում են AWS Instance Metadata API. Մեր միկրոսերվիսը օգտագործում է այս ծառայությունը Elasticsearch-ը գործարկելիս: Երկու զանգերն էլ հիմնական թույլտվության գործընթացի մի մասն են: Վերջնական կետը, որը հասանելի է առաջին հարցումով, թողարկում է IAM դերը, որը կապված է օրինակի հետ:
Հաճախորդը կարող է դրանք օգտագործել կարճ ժամանակով և պետք է պարբերաբար ձեռք բերի նոր վկայականներ (նախքան դրանք Expiration) Մոդելը պարզ է. AWS-ը հաճախակի պտտում է ժամանակավոր ստեղները՝ անվտանգության նկատառումներից ելնելով, բայց հաճախորդները կարող են դրանք մի քանի րոպե պահել քեշում՝ փոխհատուցելու նոր վկայականներ ստանալու հետ կապված կատարողականի տույժը:
AWS Java SDK-ն պետք է ստանձնի այս գործընթացի կազմակերպման պատասխանատվությունը, բայց ինչ-ինչ պատճառներով դա տեղի չի ունենում:
GitHub-ում խնդիրներ փնտրելուց հետո մենք հանդիպեցինք խնդրի #1921. Նա օգնեց մեզ որոշել այն ուղղությունը, որով պետք է ավելի «փորել»:
AWS SDK-ն թարմացնում է վկայագրերը, երբ տեղի է ունենում հետևյալ պայմաններից մեկը.
Ժամկետի ժամկետը (Expiration) Ընկնել EXPIRATION_THRESHOLD, կոշտ կոդավորված մինչև 15 րոպե:
Ավելի շատ ժամանակ է անցել վկայականների թարմացման վերջին փորձից REFRESH_THRESHOLD, կոշտ կոդավորված 60 րոպե:
Մեր ստացած վկայագրերի վավերականության ժամկետը տեսնելու համար մենք գործարկեցինք վերը նշված cURL հրամանները ինչպես կոնտեյներից, այնպես էլ EC2 օրինակից: Բեռնարկղից ստացված վկայագրի վավերականության ժամկետը շատ ավելի կարճ է ստացվել՝ ուղիղ 15 րոպե։
Այժմ ամեն ինչ պարզ է դարձել՝ առաջին իսկ խնդրանքով մեր ծառայությունը ստացել է ժամանակավոր վկայականներ։ Քանի որ դրանք վավեր չէին ավելի քան 15 րոպե, AWS SDK-ն կորոշի թարմացնել դրանք հետագա խնդրանքով: Եվ դա տեղի էր ունենում յուրաքանչյուր խնդրանքով:
Ինչու՞ է կրճատվել վկայականների վավերականության ժամկետը։
AWS Instance Metadata-ն նախատեսված է ոչ թե Kubernetes, այլ EC2 օրինակների հետ աշխատելու համար: Մյուս կողմից, մենք չէինք ցանկանում փոխել հավելվածի ինտերֆեյսը: Դրա համար մենք օգտագործել ենք ՔԻԱՄ - գործիք, որը, օգտագործելով գործակալներ յուրաքանչյուր Kubernetes հանգույցի վրա, թույլ է տալիս օգտվողներին (ճարտարագետներին, որոնք հավելվածներ տեղադրում են կլաստերի վրա) IAM դերեր վերագրել կոնտեյներներին, կարծես դրանք EC2 օրինակներ լինեն: KIAM-ը գաղտնալսում է AWS Instance Metadata ծառայության զանգերը և մշակում դրանք իր քեշից՝ նախկինում դրանք ստանալով AWS-ից: Կիրառական տեսանկյունից ոչինչ չի փոխվում։
KIAM-ը կարճաժամկետ վկայականներ է տրամադրում պատիճներին: Սա խելամիտ է հաշվի առնելով, որ պատիճների կյանքի միջին տևողությունը ավելի կարճ է, քան EC2 օրինակը: Վկայականների լռելյայն վավերականության ժամկետը հավասար է նույն 15 րոպեին.
Արդյունքում, եթե դուք երկու լռելյայն արժեքները ծածկում եք միմյանց վրա, խնդիր է առաջանում: Դիմումին տրամադրված յուրաքանչյուր վկայագրի ժամկետը լրանում է 15 րոպեից հետո: Այնուամենայնիվ, AWS Java SDK-ն ստիպում է թարմացնել ցանկացած վկայագրի ժամկետը, որի ժամկետի ավարտին մնացել է 15 րոպեից պակաս:
Արդյունքում, ժամանակավոր վկայականը ստիպված է լինում թարմացնել յուրաքանչյուր հարցումով, ինչը ենթադրում է մի քանի զանգ դեպի AWS API և հանգեցնում է հետաձգման զգալի աճի: AWS Java SDK-ում մենք գտանք հատկությունների հարցում, որը նշում է նմանատիպ խնդիր։
Լուծումը պարզվեց. Մենք պարզապես վերակազմավորել ենք KIAM-ը՝ ավելի երկար գործողության ժամկետով վկայագրեր պահանջելու համար: Երբ դա տեղի ունեցավ, հարցումները սկսեցին հոսել առանց AWS Metadata ծառայության մասնակցության, և հետաձգումը իջավ նույնիսկ ավելի ցածր մակարդակների, քան EC2-ում:
Արդյունքները
Ելնելով միգրացիաների հետ կապված մեր փորձից՝ խնդիրների ամենատարածված աղբյուրներից մեկը Kubernetes-ի կամ հարթակի այլ տարրերի վրիպակներ չեն: Այն նաև չի անդրադառնում մեր կողմից տեղափոխվող միկրոծառայությունների որևէ հիմնարար թերության: Խնդիրները հաճախ առաջանում են պարզապես այն պատճառով, որ մենք միացնում ենք տարբեր տարրեր:
Մենք խառնում ենք բարդ համակարգեր, որոնք նախկինում երբեք չեն փոխազդել միմյանց հետ՝ ակնկալելով, որ նրանք միասին կկազմեն մեկ, ավելի մեծ համակարգ: Ավաղ, որքան շատ տարրեր, այնքան շատ սխալների տեղ, այնքան բարձր է էնտրոպիան:
Մեր դեպքում, բարձր հետաձգումը Kubernetes-ում, KIAM-ում, AWS Java SDK-ում կամ մեր միկրոսերվիսում սխալների կամ սխալ որոշումների արդյունք չէր: Դա երկու անկախ լռելյայն կարգավորումների համատեղման արդյունք էր՝ մեկը KIAM-ում, մյուսը՝ AWS Java SDK-ում: Առանձին վերցրած, երկու պարամետրերն էլ իմաստ ունեն. AWS Java SDK-ում վկայագրի ակտիվ թարմացման քաղաքականությունը և KAIM-ում վկայագրերի վավերականության կարճ ժամկետը: Բայց երբ դրանք միացնում ես, արդյունքները դառնում են անկանխատեսելի։ Պարտադիր չէ, որ երկու անկախ և տրամաբանական լուծումները համակցված իմաստ ունենան:
PS թարգմանչից
Դուք կարող եք ավելին իմանալ KIAM կոմունալ ծրագրի ճարտարապետության մասին՝ AWS IAM-ը Kubernetes-ի հետ ինտեգրելու համար այստեղ՝ այս հոդվածը իր ստեղծողներից։