Kubernetes.Ресурсы.Процессор (CPU) - теория. часть14

Thank you for reading this post, don't forget to subscribe!

В дан­ной ста­тье рас­смот­рим нюан­сы настрой­ки пара­мет­ров requests и limits в кон­тек­сте рабо­ты про­цес­со­ра (CPU). Давай­те разберемся!

Итак, что име­ет­ся в виду под CPU когда мы гово­рим о Kubernetes? Один CPU это экви­ва­лент “одно­го про­цес­сор­но­го ядра”, предо­став­ля­е­мо­го опе­ра­ци­он­ной систе­мой рабо­че­го узла, вне зави­си­мо­сти от того, какое это ядро - физи­че­ское (physical core), поток физи­че­ско­го ядра (hyper-thread), вир­ту­аль­ное ядро (напри­мер, EC2 vCPU, кото­рое по сути, тоже явля­ет­ся пото­ком физи­че­ко­го ядра).

В отли­чие от огра­ни­че­ний (limits) по памя­ти, лими­ты по CPU с точ­ки зре­ния Kubernetes явля­ют­ся “сжи­ма­е­мы­ми”, сле­до­ва­тель­но может рабо­тать так назы­ва­е­мый CPU Throttling - сни­же­ние часто­ты про­цес­со­ра, и, как след­ствие, про­из­во­ди­тель­но­сти. Когда вы уста­нав­ли­ва­е­те зна­че­ние limits для CPU:

вы на самом деле ука­зы­ва­е­те вре­мя исполь­зо­ва­ния про­цес­со­ра (CPU time) на всех доступ­ных про­цес­сор­ных ядрах рабо­че­го узла (ноды), а не “при­вя­зы­ва­е­те” свой кон­тей­нер к кон­крет­но­му ядру (или груп­пе ядер). Это зна­чит, что даже если вы ука­зы­ва­е­те в .limits.cpu чис­ло мень­шее обще­го коли­че­ства ядер ноды, то кон­тей­нер все рав­но будет “видеть” и исполь­зо­вать все ядра, огра­ни­чи­ва­ясь толь­ко вре­ме­нем их использования.

К при­ме­ру, если кон­тей­не­ру, кото­рый запус­ка­ет­ся на рабо­чем узле с общим коли­че­ством ядер 8, в зна­че­нии CPU limits уста­но­вить 4, то кон­тей­нер будет исполь­зо­вать экви­ва­лент 4-х ядер, рас­пре­де­лен­ных по всем 8 CPU ноды. В дан­ном при­ме­ре мак­си­маль­но допу­сти­мое исполь­зо­ва­ние про­цес­со­ра (CPU usage) на рабо­чем узле будет рав­нять­ся 50%.

Как это выгля­дит с точ­ки зре­ния Docker? Kubernetes управ­ля­ет огра­ни­че­ни­я­ми CPU пере­да­вая пара­мет­ры cpu-period и cpu-quota. Пара­метр cpu-period опре­де­ля­ет пери­од вре­ме­ни, в тече­нии кото­ро­го отсле­жи­ва­ет­ся исполь­зо­ва­ние про­цес­со­ра (CPU utilisation) кон­тей­не­ром и он все­гда равен 100000µs (100ms). Пара­метр cpu-quota - это общее коли­че­ство про­цес­сор­но­го вре­ме­ни, кото­рое кон­тей­нер может исполь­зо­вать в каж­дом cpu-period'е. Эти два пара­мет­ра вли­я­ют на рабо­ту CFS (абсо­лют­но чест­но­го пла­ни­ров­щи­ка ядра, Completely Fair Scheduler). Кон­крет­ный при­мер соот­вет­ствия зна­че­ний CPU limits зна­че­ни­ям cpu-quota в кон­фи­гу­ра­ции Docker:

Здесь limits 1 озна­ча­ет, что каж­дые 100ms кон­тей­не­ром могут исполь­зо­вать­ся 100% экви­ва­лен­та 1 про­цес­сор­но­го ядра рабо­че­го узла, limits 4 ука­зы­ва­ет, что кон­тей­нер может исполь­зо­вать 400% экви­ва­лен­та 1 ядра (ну или 100% про­цес­сор­ных 4-х ядер) и т.д. Не забы­ва­ем, что это исполь­зо­ва­ние “раз­ма­зы­ва­ет­ся” на все доступ­ные ядра рабо­чей ноды, без при­вяз­ки к кон­крет­ным ядрам. Бла­го­да­ря рабо­те “абсю­лют­но чест­но­го пла­ни­ров­щи­ка” (CFS), любо­му кон­тей­не­ру, пре­вы­ша­ю­ще­му свою кво­ту в дан­ный пери­од (име­ет­ся в виду cpu-period рас­смот­рен­ный выше), будет запре­ще­но исполь­зо­вать про­цес­сор до наступ­ле­ния сле­ду­ю­ще­го периода.

Напом­ню, что вы може­те ука­зать сколь­ко про­цес­сор­ных ядер (CPU) необ­хо­ди­мо для рабо­ты ваше­му кон­тей­не­ру с помо­щью пара­мет­ра requests - это зна­че­ние (важ­но!) учи­ты­ва­ет­ся пла­ни­ров­щи­ком Kubernetes при раз­ме­ще­нии кон­тей­не­ра на рабо­чих узлах кла­сте­ра (общее зна­че­ние пара­мет­ров CPU requests всех кон­тей­не­ров на кон­крет­ном рабо­чем узле не может быть боль­ше, чем общее коли­че­ство про­цес­сор­ных ядер дан­ной ноды).

Таким обра­зом, при исполь­зо­ва­нии requests, вам гаран­ти­ро­ван экви­ва­лент коли­че­ства ука­зан­ных CPU, но что про­изой­дет, если рабо­чий узел кла­сте­ра будет нахо­дить­ся под чрез­мер­ной нагруз­кой (исполь­зо­ва­ние про­цес­со­ра на 100% или вне­зап­ные скач­ки LA)? В этом слу­чае при­о­ри­тет исполь­зо­ва­ния про­цес­сор­но­го вре­ме­ни будет вычис­лять­ся исхо­дя из зна­че­ния, ука­зан­но­го в CPU requests и умно­жен­но­го на 1024 - резуль­тат будет пере­дан Docker'у как пара­метр cpu-shares. Это так назы­ва­е­мый “вес” - если все кон­тей­не­ры дан­но­го рабо­че­го узла име­ют оди­на­ко­вый вес, то они будут иметь оди­на­ко­вый при­о­ри­тет при пла­ни­ро­ва­нии и исполь­зо­ва­нии про­цес­сор­но­го вре­ме­ни при чрез­мер­ной нагруз­ке; если у кон­тей­не­ров рабо­че­го узла вес раз­ный, то кон­тей­нер с боль­шим весом будет иметь выс­ший при­о­ри­тет и полу­чит боль­ше про­цес­сор­но­го вре­ме­ни при чрез­мер­ной нагруз­ке про­цес­со­ра на рабо­чей ноде.

В преды­ду­щей ста­тье мы уже упо­ми­на­ли о QoS (клас­сах каче­ства сер­ви­са) - они спра­вед­ли­вы и в кон­тек­сте CPU. Исполь­зуя класс Burstable вы може­те полу­чить допол­ни­тель­ные пери­о­ды вре­ме­ни исполь­зо­ва­ния CPU (при усло­вии, что эти же ресур­сы не тре­бу­ют­ся дру­гим кон­тей­не­рам). Потен­ци­аль­но, это поз­во­ля­ет более эффек­тив­но исполь­зо­вать ресур­сы кла­сте­ра, прав­да, за счет боль­шей непред­ска­зу­е­мо­сти - повы­шен­ное исполь­зо­ва­ние CPU одним кон­тей­не­ром на рабо­чем узле повли­я­ет на “сосе­дей”, рабо­та­ю­щих на той же ноде кластера.

Опять же, если вы нови­чок в Kubernetes, луч­ше все­го обес­пе­чить класс сер­ви­са Guaranteed QoS, уста­нав­ли­вая зна­че­ния requests и limits оди­на­ко­вы­ми. Когда вы собе­ре­те боль­ше дан­ных (мет­рик) и луч­ше раз­бе­ре­тесь с исполь­зо­ва­ни­ем про­цес­сор­ных ресур­сов кон­тей­не­ра­ми, есть смысл начать исполь­зо­вать класс сер­ви­са Burstable QoS для обду­ман­ной опти­ми­за­ции рас­хо­дов на инфраструктуру.

Сколь­ко CPU сто­ит выде­лить кон­тей­не­ру при напи­са­нии мани­фе­ста? К сожа­ле­нию, не суще­ству­ет уни­вер­саль­но­го отве­та на этот вопрос - все зави­сит от харак­те­ри­стик ваше­го при­ло­же­ния, тре­бу­е­мой про­из­во­ди­тель­но­сти, места раз­ме­ще­ния кон­тей­не­ра, сто­и­мо­сти и т. д. Но если вы доста­точ­но хоро­шо зна­е­те, как рабо­та­ет ваше при­ло­же­ние “под капо­том” и при нали­чии при­лич­ных инстру­мен­тов для сбо­ра и ана­ли­за мет­рик (напри­мер, Prometheus) мож­но подо­брать опти­маль­ную кон­фи­гу­ра­цию. В край­нем слу­чае, мож­но даже полу­чить кое-какие циф­ры для ана­ли­за выпол­нив внут­ри кон­тей­не­ра команду:

Так мож­но полу­чить общее коли­че­ство пери­о­дов запус­ка, коли­че­ство раз, когда про­из­во­ди­тель­ность про­цес­со­ра для дан­но­го кон­тей­не­ра была при­ну­ди­тель­но сни­же­на (CPU Throttled) и общее вре­мя тро­тт­лин­га в наносекундах.