CPU սահմանափակումներ և ագրեսիվ տրոտլինգ Kubernetes- ում

Համար. թարգմանություն.: այս ուսուցողական պատմությունը Omio - Եվրոպայի ճանապարհորդական агрегատ - ընթերցողներին ներկայացնում է Kubernetes-ի կազմության հիմնական տեսություններից դեպի զվարճանքի գործնական մանրամասներ: Աշխատակերտերով ծանոթացումը ոչ միայն օգնում է ընդլայնել հորիզոնը, այլ նաև կանխարգելել ոչ սահուն խնդիրներ:

CPU սահմանափակումներ և ագրեսիվ տրոտլինգ Kubernetes- ում

Դուք երբևէ բախվել եք այն իրավիճակին, երբ ծրագիրը 'կանգնել' է, դադարում է արձագանքել առողջության ստուգման հարցերին (health check) և դուք չեք կարողանում հասկանալ այդ վարքի պատճառը: Նրանք, ովքեր կարող են լինել պատճառներից մեկը, կապված է CPU-ի ռեսուրսների քվոտայի սահմանափակման հետ: Այս հոդվածում հենց դրա մասին կներկայացվի:

TL;DR:
Մենք պնդում ենք հրաժարվելու CPU limit-ներից Kubernetes-ում (կամ անջատել CFS квոտները Kubelet-ում), եթե օգտագործվում է սխալ CFS-քվոտով Linux-ի kernel-ի տարբերակը: կա այս kernel-ում կային լուրջ և լավ հայտնի
.

սխալ, որը հանգեցնում է գերազանց թրոթլինգի և ուշացումի: Omio-ումբոլոր ենթակառուցվածքները կառավարում է Kubernetes-ով: Մեր բոլոր stateful և stateless բեռները բացառապես աշխատում են Kubernetes-ում (մենք օգտագործում ենք Google Kubernetes Engine): Վերջին 6 ամսվա ընթացքում մենք սկսել ենք դիտարկել պատահական սրամտություններ: Համակարգերն ընկնում են կամ դադարում են արձագանքել health check-ներին, կորցնում են կապը ցանցի հետ և այլն: նման վարքը երկար ժամանակ մեզ տրված տպավորում էր, և վերջապես որոշեցինք զբաղվել խնդրի հետ:

Հոդվածի ամփոփում:

  • Քիչ ձև containers-ի և Kubernetes-ի մասին;
  • Ինչպես են իրականացվում CPU request-ները և limit-ները;
  • Ինչպես է CPU limit-ը գործում բազմահոսանի միջավայրերում;
  • Ինչպես հետևել CPU թրոթլինգին;
  • Փայթի խնդրի լուծումը և նրբություններ:

Քիչ ձև containers-ի և Kubernetes-ի մասին:

Kubernetes-ը, ըստ էության, ժամանակակից ստանդարտ է ենթակառուցվածքների աշխարհում: Նրա հիմնական խնդիրը՝ containers-ի օրկեստրացիան:

Կոնտեյներներ

Մեր անցյալի մեջ ստիպված էինք ստեղծել արտաֆակտներ, ինչպիսիք են Java JAR-երը/WAR-երը, Python Egg-երը կամ կատարողական ֆայլեր հետագայում սերվերների վրա գործարկելու համար: Սակայն, որպեսզի դրանք աշխատեն, ստիպված էինք լրացուցիչ աշխատանք գործադրել՝ տեղադրելով գործարկման միջավայրը (Java/Python), անհրաժեշտ ֆայլերը դնելու ճիշտ տեղերում, համատեղելի լինել կոնկրետ օպերացիոն համակարգի տարբերակին և այլն: Ասմարվեստ բառերով, պետք է ուշադրություն դարձնել կոնֆիգուրացիաների կառավարմանը (ինչը հաճախ պատճառ էր հանդիսանում մշակողների և համակարգային ադմինիստրատորների միջև դժգոհությունների):

Containers-ները ամեն բան փոխեցին: Այժմ ամրագրումը հանդես է գալիս որպես containers-ի պատկեր: Այն կարելի է ներկայացնել որպես որոշակի ընդլայնված կատարողական ֆայլ, որը պարունակում է ոչ միայն ծրագիրը, այլ նաև լիարժեք գործարկման միջավայր (Java/Python/…), ինչպես նաև անհրաժեշտ ֆայլեր/փաթեթներ, նախնական տեղադրված և պատրաստ կառավարման: Containers-ները կարելի է տեղադրել և գործարկել տարբեր սերվերների վրա առանց որևէ լրացուցիչ գործողության:

Բացի այդ, կոնտեներները աշխատում են իրենց սեփական ավազանային միջավայրում: Նրանք ունեն իրենց սեփական վիրտուալ ցանցային ադապտեր, սահմանափակ մտքերում файловая система, սեփական գործընթացների հիերարխիա, CPU-ի և հիշողության սահմանափակումներ և այլն: Բոլորը դա իրականացվում է Linux-ի միջուկի հատուկ ենթահամակարգի՝ namespaces (համակարգի անուններ) օգնությամբ:

Kubernetes

Ինչպես ասված է առաջ, Kubernetes-ը կոնտեներների օրկեստրատոր է: Այն աշխատում է այսպես. դուք տրամադրում եք նրան մեքենաների պուլ, ապա ասում եք՝ "Hey, Kubernetes, գործարկիր իմ կոնտեների 10 օրինակ 2 պրոցեսորներով և 3 Գբ հիշողությամբ յուրաքանչյուր, և պահի դրանք աշխատանքային վիճակում!" Kubernetes-ը հոգում է մնացածի մասին: Այն գտնում է ազատ ռեսուրսները, գործարկում է կոնտեներները և երբժամանակ անհրաժեշտության դեպքում վերագործարկում, թողարկելու զննգում՝ տարբերակները փոխելիս և այլն: Ասել է թե, Kubernetes-ը թույլ է տալիս բացակայության մեջ լինել տեխնիկական բաղադրիչի վրա և դարձնում բոլոր համակարգերի բազմազանությունը հարմարավետ բոլորի մեկնարկման և աշխատանքի համար:

CPU սահմանափակումներ և ագրեսիվ տրոտլինգ Kubernetes- ում
Kubernetes-ը դիտելիս պարզ մարդուն

Ինչ է request-ները և limit-ները Kubernetes-ում

Լավ, մենք հասկացանք կոնտեներներն ու Kubernetes-ը: Մենք նաև գիտենք, որ մի քանի կոնտեներներ կարող են լինել մեկ մեքենայում:

Բեռնափոխադրումը կարող է համեմատվել կոմունալ բնակիչների առանձնակետի հետ: Հեղինակվում է ընդարձակ սենյակ (մեքենաներ / կապալառուներ) և վարձակալվում մի քանի վարձակալներին (կոնտեներներին): Kubernetes-ը հանդես է գալիս որպես անշարժ գույքի գործակալ: Հեռանկար է առաջանում, թե ինչպես պահպանել վարձակալներին միմյանցից հակամարտությունից: Ի՞նչ կլինի, եթե նրանցից մեկը, ասենք, որոշի զբաղեցնել լոգարանը կեսօրից:

Բոլոր արտահայտված request-ները և limit-ները կոնկրետ կերգան: CPU Request միայն պլանավորման համար է: Դա կարող է լինել մի տեսակ «ցանկություների ցուցակ» կոնտեների համար, որը օգտագործվում է ամենահամապատասխան կետը ընտրելու համար: Միաժամանակ CPU Limit, կարող է համեմատվել վարձակալության պայմանագրի հետ. երբ մենք ընտրելով նոդը կոնտեների համար, այն չի կարող անցնել սահմանված սահմաններից: Եվ այստեղ խնդիր է առաջանում...

Ինչպե՞ս են իրականացվում request-ները և limit-ները Kubernetes-ում

Kubernetes-ը օգտագործում է միջուկում համակցվող մեխանիզմ ` CPU limit-ների իրականացման համար: Եթե ծրագիրը գերազանցում է սահմանը, սկսվում է թեթևացման աշխատանք (այսինքն ՝ այն ստանում է ավելի քիչ CPU- ի շրջապտույտ): Հիշողության request-ները և limit-ները կազմաձևված են այլ կերպ, այնպես որ դրանք ավելի հեշտ է հայտնաբերել: Դրա համար պարզ է, որ վերջինի վերագործարկման որոշակը պետք է ստուգել ՝ արդյո՞ք այն "OOMKilled" չէ: CPU թեթևացման հետ ամեն բան չէ այն պարզ, քանի որ K8s-ը մատչելի է միայն օգտագործման նպատակային ցուցանիշներով, ոչ թե cgroups-ով:

CPU Request

CPU սահմանափակումներ և ագրեսիվ տրոտլինգ Kubernetes- ում
Ինչպես է իրականացված CPU request-ը

Հեշտացնելու համար գնանք գործընթացի մանրամասնության օրինակ մեքենայի վրա ՝ 4-միջուկային CPU-ով:

K8s-ը օգտագործում է վերահսկման խմբերի (cgroups) մեխանիզմը ռեսուրսների (հիշողության և պրոցեսորի) բաժանման կառավարման համար: Այսօր հասանելի է հիերարխիկ մոդել՝ մեկը ժառանգելով ծնողական խմբի սահմանափակումները: Վաճառքի մանրամասները պահպանվում են վիրտուալ ֆայլային համակարգում (/sys/fs/cgroup). Պրոցեսորի դեպքում це /sys/fs/cgroup/cpu,cpuacct/*.

K8s-ը օգտագործում է ֆայլը cpu.share պրոցեսորի ռեսուրսները բաժանելու համար: Մեր դեպքում արմատային վերահսկման խումբը ստանում է 4096 բաժին CPU ռեսուրսներից՝ 100% առկա պրոցեսորի հզորությունից (1 միջուկ = 1024; սա ֆիքսված արժեք է): արմատային խումբը բաժանում է ռեսուրսները համապարփակորեն՝ կախված որդիների բաժիններից, որը նշված է cpu.share, մինչդեռ նրանք, իրենց հերթին, վարվում են նույն կերպ իրենց հետեւողների հետ և այլն: Տիպիկ Kubernetes узле армատային վերահսկման խումբը ունի երեք որդի՝ system.slice, user.slice և kubepods. Երկու առաջին ենթախմբերը օգտագործվում են ռեսուրսները բաժանելու համար զգայուն համակարգի մրցույթների և K8s-ից դուրս օգտագործող ծրագրերի միջև: Վերջինն այն է՝ kubepods — ստեղծվում է Kubernetes-ի կողմից ռեսուրսների բաժանման համար pod-երի միջև:

Վերոնշյալ սխեմայում նշվում է, որ առաջին և երկրորդ ենթախմբերը ստացել են 1024 բաժին, մինչդեռ kubepod ենթախմբին հատկացվել է 4096 բաժին: Ինչպես կարելի է դա անել. ведь արմատային խմբին հասանելի են ընդամենը 4096 բաժին, իսկ նրա որդիների ընդհանուր բաժինը զգալիորեն գերազանցում է այս թիվը (6144)? Խնդիրը կայանում է նրանում, որ արժեքը տրամաբանական իմաստ ունի, այնպես որ Linux-ի ծրագրավորիչը (CFS) օգտագործում է այն՝ CPU ռեսուրսները համաչափ բաժանելու համար: Մեր դեպքում առաջին երկու խմբերը ստանում են 680 իրական բաժիններ (16,6% 4096-ի), իսկ kubepod-ը ստանում է մնացած 2736 բաժին: Եթե առաջին երկու խմբերը դադարում են, այս խմբերը չեն օգտագործի հատկացված ռեսուրսները:

Ցավոք, ծրագրավորիչում կա մեխանիզմ, որը կանխում է օգտագործված CPU ռեսուրսների կորուստը: Այն «դադարում» հզորությունները փոխանցում է գլոբալ ավազանում, որտեղ նրանք բաժանում են խմբերին, որոնք պահանջում են լրացուցիչ CPU հզորություն (փոխանցումն իրականացվում է խմբերով՝ կորուստները շրջելու համար): Այս մեթոդը նույնպես կիրառվում է բոլոր որդիների վրա:

Այս մեխանիզմը ապահովում է ռեսուրսների համաչափ բաժանումը CPU-ում և ուշադիր հետևում է, որպեսզի ոչ մի գործընթաց «խլի» ռեսուրսներ մյուսներից:

CPU Limit

Չնայած K8s-ում սահմանափակումների և պահանջների config-երը նման են, դրանց իրականացումը սկզբունքորեն տարբեր է: Սա ամենահետաքրքիր և ամենափոքր փաստաթղթավորված մասն է:

K8s-ը օգտագործում է CFS-ի քվոտաների մեխանիզմը սահմանափակումների իրագործման համար: Ցանկերը սահմանվում են cfs_period_us և cfs_quota_us cgroup-ում (այնտեղ է գտնվում նաև ֆայլը cpu.share).

Հակառակ cpu.share, քվոտան հիմնված է ժամանակի շրջանի, ոչ թե առկա պրոցեսորի հզորության վրա: cfs_period_us պարբերության տևողությունը (դարաշրջան) միշտ կազմում է 100000 մկվ (100 մս). K8s-ում հնարավոր է փոփոխել այս արժեքը, սակայն այն հասանելի է միայն ալֆա տարբերակում: Տիրորդը օգտագործում է դարաշրջանը օգտագործված քվոտայի վերակտիվացման համար: Երկրորդ ֆայլը, cfs_quota_us, սահմանում է յուրաքանչյուր դարաշրջանում հասանելի ժամանակը (քվոտան): Ուշադրություն դարձրեք, որ այն նույնպես նշվում է միկրոպրկերով: Քվոտան կարող է գերազանցել դարաշրջանի տևողությունը, այլաբառ՝ այն կարող է ավելի լինել, քան 100 մս:

Եկեք դիտարկենք երկու սցենար 16-միջուկային մեքենաներում (մեր Omio-ում ամենատարածված տեսակի համակարգիչներն են):

CPU սահմանափակումներ և ագրեսիվ տրոտլինգ Kubernetes- ում
Սցենար 1: 2 միջուկ և 200 մս սահմանափակում: Ոչ մի խափանում չի տեղի ունենում

CPU սահմանափակումներ և ագրեսիվ տրոտլինգ Kubernetes- ում
Սցենար 2: 10 միջուկ և 200 մս սահմանափակում: Խափանումները սկսվում են 20 մս-ից հետո, CPU ռեսուրսների հասանելիությունը վերականգնվում է ևս 80 մս-ից հետո

فرضենք, դուք սահմանել եք CPU սահմանափակումը 2 միջուկների համար; Kubernetes-ը այս արժեքը կվերածի 200 մս-ի: Դա նշանակում է, որ կոնտեյները կարող է օգտագործել առավելագույնը 200 մս պրոցեսորի ժամանակ առանց խափանումների:

Այս պահին սկսվում է ամենա հետաքրքիր մասը: Ինչպես ասվեց վերևում, հասանելի քվոտան կազմում է 200 մս: Եթե դուք ունեք զուգահեռ աշխատող տասը միջուկ 12-միջուկային մեքենայում (տեսեք սցենարի 2-ը), այն ժամանակ, երբ բոլոր մյուս pod-ը նորակում են, քվոտան կհասնի վերջը ընդամենը 20 մս-ի ընթացքում (բացատրելով, որ 10 * 20 մս = 200 մս), և այդ pod-ի բոլոր միջուկները «հայցվելու» են (խափանում) 80 մս-ի ընթացքում: Ցավոք, դա ավելի դժվարացնում է տեղի ունեցող արդեն նշված պլանավորողի սխալը, որի պատճառով տեղի է ունենում ավելորդ խափանումներ, և կոնտեյները չի կարող օգտագործել նույնիսկ ունեցած քվոտան:

Ինչպես գնահատել խափանումները pod-երում:

Սովորեն անցեք pod-ին և գործարկեք cat /sys/fs/cgroup/cpu/cpu.stat.

  • nr_periods — պլանավորողի ընդհանուր ժամանակահատվածները;
  • nr_throttled — խափանված ժամանակահատվածների թիվը՝ nr_periods;
  • throttled_time — խափանված ժամանակի համակցված գումարը նանոսекունդներում:

CPU սահմանափակումներ և ագրեսիվ տրոտլինգ Kubernetes- ում

Ի՞նչ տեղի կունենա իրականում:

Վերջում մենք ստանում ենք բարձր խափանում բոլոր առկայ բաշխումներում: Иногда оно в ինն անգամ ուժից ավելի ուժեղ է, քան հաշվարկված!

Այն հանգեցնում է տարբեր սխալների՝ պատրաստունակության (readiness) ծօող, կոնտեյների ճնշման, ցանցային կապերի ուզմիջեւ, սպասման ժամկետների բարձրացման: Ի վերջո, սա հանգեցնում է բարձրացման ուշելու և սխալների քանակի:

Նյութի լուծում և հետևանքներ

Այստեղ ամեն ինչ պարզ է: Մենք հրաժարվել ենք CPU սահմանափակումներից և զբաղված ենք օպերացիոն համակարգի միջուկի թարմացմամբ ամեն նորագույն տարբերակում, որտեղ սխալը շտկվել է: Մեր խservicesերում HTTP 5xx սխալների թիվը անմիջապես բավականաչափ նվազեց:

HTTP 5xx սխալներ

CPU սահմանափակումներ և ագրեսիվ տրոտլինգ Kubernetes- ում
HTTP 5xx սխալներ մեկ կարևոր ծառայության համար

p95 պատասխան ժամանակը

CPU սահմանափակումներ և ագրեսիվ տրոտլինգ Kubernetes- ում
Կրիտիկական սպասարկման հարցումների ուշացումը, 95-րդ տոկոսանիշը:

Զարգացման ծախսեր

CPU սահմանափակումներ և ագրեսիվ տրոտլինգ Kubernetes- ում
Ծախսված օրինակ-ժամերի թիվը

Ի՞նչ է պակասում:

Ինչպես նշված է հոդվածի սկզբում:

Մոտեցումը կարող է համեմատվել կոմունալ բնակարանի հետ… Kubernetes-ը գործում է կամավորի դերում: Բայց ինչպես կանխել բնակիչների միջև հակամարտություններ: Ինչ կլինի, եթե նրանք, ասենք, որոշեն զբաղեցնել լուսասրահը կեսօրից մինչև երեկո:

Այստեղ է հանդիսանում ծուղակն: Մի անհաջող կոնտեյներ կարող է поглотить բոլոր ներքին ռեսուրսները մեքենայում: Եթե لديك պատրաստակամ ծրագիր (օրինակ, պատշաճ կերպով կարգավորված JVM, Go, Node VM), ապա դա խնդիր չէ: Կարող եք աշխատել այդ պայմաններում երկար ժամանակ: Բայց եթե ծրագրերը վատ կամ ընդհանրապես չօգտագործված են (FROM java:latest), իրավիճակը կարող է դուրս գալ վերահսկողությունից: Մեր ընկերությունում կա ավտոմատացված հիմնական Dockerfiles, որոնք ունեն համարժեք կարգավորումներ ծրագրավորման լեզուների համար, այնպես որ նման խնդիրը չէին եղել:

Մենք խորհուրդ ենք տալիս վերահսկել մետրիկաները USE (օգտագործում, насыщение և սխալներ), API–ի ուշացումը և սխալների հաճախականությունը: Դիտեք, որ արդյունքները համապատասխանում են սպասելիքներին:

Հղումներ

Այսպիսին է մեր պատմությունը: Երեք նյութերը մեծապես օգնեցին պարզել, թե ինչ է տեղի ունենում:

Kubernetes-ի սխալի հաշվետվությունները:

Սեփականության ոլորտում նման խնդիրների հանդիպեցեք, կամ ունեք փորձ, կապված контейներացված production միջավայրերում throttling-ի հետ: Պատվիրեք ձեր պատմությունը մեկնաբանություններում!

P.S. թարգմանչից

Նաեւ կարդացեք մեր բլոգում:

Ընտանիք: habr.com

Գնել հուսալի հյուրընկալում DDoS պաշտպանությամբ, VPS VDS սերվերներով 🔥 Գնել հուսալի հյուրընկալում DDoS պաշտպանությամբ, VPS VDS սերվերներով | ProHoster