CPU-ліміты і агрэсіўны тротлінг у Kubernetes

Заўв. перав.: гэтая павучальная гісторыя Omio - еўрапейскага агрэгатара падарожжаў - праводзіць чытачоў ад базавай тэорыі да займальных практычных тонкасцяў у канфігурацыі Kubernetes. Знаёмства з такімі выпадкамі дапамагае не толькі пашыраць кругагляд, але і прадухіляць нетрывіяльныя праблемы.

CPU-ліміты і агрэсіўны тротлінг у Kubernetes

Ці даводзілася вам сутыкацца з тым, што дадатак «захрасаў» на месцы, пераставала адказваць на запыты аб праверцы стану (health check'і) і вы не маглі зразумець прычыну такіх паводзін? Адно з магчымых тлумачэнняў злучана з лімітам квот на рэсурсы CPU. Аб ім і пайдзе прамову ў гэтым артыкуле.

TL; DR:
Мы настойліва раім адмовіцца ад CPU limit'аў у Kubernetes (ці адключыць квоты CFS у Kubelet), калі выкарыстоўваецца версія ядра Linux з памылкай CFS-квот. У ядры маецца сур'ёзны і добра вядомы баг, які прыводзіць да залішняй тратлінгу і затрымкам
.

У Omio уся інфраструктура кіруецца Kubernetes. Усе нашы stateful-і stateless-нагрузкі працуюць выключна на Kubernetes (мы выкарыстоўваем Google Kubernetes Engine). У апошнія паўгода мы сталі назіраць рандомныя падтармажванні. Прыкладанні завісаюць ці перастаюць адказваць на health check'і, губляюць сувязь з сеткай і да т.п. Падобныя паводзіны доўга ставілі нас у тупік, і, нарэшце, мы вырашылі заняцца праблемай ушчыльную.

Кароткі змест артыкула:

  • Некалькі слоў аб кантэйнерах і Kubernetes;
  • Як рэалізаваны CPU request'ы і limit'ы;
  • Як CPU limit працуе ў асяроддзях з некалькімі ядрамі;
  • Як адсочваць тротлінг CPU;
  • Рашэнне праблемы і нюансы.

Некалькі слоў аб кантэйнерах і Kubernetes

Kubernetes, у сутнасці, з'яўляецца сучасным стандартам у свеце інфраструктуры. Яго асноўная задача - аркестроўка кантэйнераў.

кантэйнеры

У мінулым нам даводзілася ствараць артэфакты накшталт Java JAR'ов/WAR'ов, Python Egg'ов ці выкананых файлаў для наступнага запуску на серверах. Аднак, каб прымусіць іх функцыянаваць, даводзілася праробліваць дадатковую працу: усталёўваць асяроддзе выканання (Java/Python), размяшчаць неабходныя файлы ў патрэбных месцах, забяспечваць сумяшчальнасць з пэўнай версіяй аперацыйнай сістэмы і т.д. Іншымі словамі, даводзілася надаваць пільную ўвагу кіраванню канфігурацыямі (што часта служыла прычынай разладаў паміж распрацоўшчыкамі і сістэмнымі адміністратарамі).

Кантэйнеры ўсё змянілі. Зараз артэфактам выступае кантэйнерная выява. Яго можна прадставіць у выглядзе гэткага пашыранага выкананага файла, які змяшчае не толькі праграму, але і паўнавартаснае асяроддзе выканання (Java/Python/…), а таксама неабходныя файлы/пакеты, прадусталяваныя і гатовыя да запуску. Кантэйнеры можна разгортваць і запускаць на розных серверах без якіх-небудзь дадатковых дзеянняў.

Акрамя таго, кантэйнеры працуюць ва ўласным асяроддзі-пясочніцы. У іх ёсць свой уласны віртуальны сеткавы адаптар, свая файлавая сістэма з абмежаваным доступам, свая іерархія працэсаў, свае абмежаванні на CPU і памяць і т. д. Усё гэта рэалізавана дзякуючы асаблівай падсістэме ядра Linux - namespaces (прасторы імёнаў).

Kubernetes

Як было сказана раней, Kubernetes - гэта аркестратар кантэйнераў. Ён працуе наступным чынам: вы дае яму пул машын, а затым кажаце: "Гэй, Kubernetes, запусці-ка дзесяць асобнікаў майго кантэйнера з 2 працэсарамі і 3 Гб памяці на кожны, і падтрымлівай іх у працоўным стане!". Kubernetes паклапоціцца пра ўсё астатняе. Ён знойдзе вольныя магутнасці, запусціць кантэйнеры і будзе перазапускаць іх пры неабходнасці, выкаціць абнаўленне пры змене версій і г.д. Па сутнасці, Kubernetes дазваляе абстрагавацца ад апаратнага складніка і робіць усю разнастайнасць сістэм прыдатным для разгортвання і працы прыкладанняў.

CPU-ліміты і агрэсіўны тротлінг у Kubernetes
Kubernetes з пункту гледжання простага абывацеля

Што такое request'ы і limit'ы ў Kubernetes

Окей, мы разабраліся з кантэйнерамі і Kubernetes. Таксама мы ведаем, што некалькі кантэйнераў могуць знаходзіцца на адной машыне.

Можна правесці аналогію з камунальнай кватэрай. Бярэцца прасторнае памяшканне (машыны / вузлы) і здаецца некалькім арандатарам (кантэйнерам). Kubernetes выступае ў ролі рыэлтара. Узнікае пытанне, як утрымаць кватарантаў ад канфліктаў адзін з адным? Што, калі адзін з іх, скажам, вырашыць заняць ванны пакой на паўдня?

Менавіта тут у гульню ўступаюць request'ы і limit'ы. CPU Запыт патрэбен выключна для планавання. Гэта нешта накшталт "спісу жаданняў" кантэйнера, і выкарыстоўваецца ён для падбору самага прыдатнага вузла. У той жа час CPU Limit можна параўнаць з дамовай арэнды - як толькі мы падбярэм вузел для кантэйнера, той не зможа выйсці за ўсталяваныя межы. І вось тут узнікае праблема…

Як рэалізаваны request'ы і limit'ы ў Kubernetes

Kubernetes выкарыстоўвае ўбудаваны ў ядро ​​механізм тротлінга (пропускі тактаў) для рэалізацыі CPU limit'аў. Калі дадатак перавышае ліміт, уключаецца тротлінг (г.зн. яно атрымлівае менш тактаў CPU). Request'ы і limit'ы для памяці арганізаваны інакш, таму іх лягчэй выявіць. Для гэтага дастаткова праверыць апошні статус перазапуску pod'а: ці не з'яўляецца ён "OOMKilled". З тротлінгам CPU усё не так проста, паколькі K8s робіць даступнымі толькі метрыкі па выкарыстанні, а не па cgroups.

CPU Request

CPU-ліміты і агрэсіўны тротлінг у Kubernetes
Як рэалізаваны CPU request

Для прастаты давайце разгледзім працэс на прыкладзе машыны з 4-ядзерным CPU.

K8s выкарыстоўвае механізм кантрольных груп (cgroups) для кіравання размеркаваннем рэсурсаў (памяці і працэсара). Для яго даступная іерархічная мадэль: нашчадак успадкоўвае limit'ы бацькоўскай групы. Падрабязнасці размеркавання захоўваюцца ў віртуальнай файлавай сістэме (/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 долі, пры гэтым падгрупе kuberpod выдзелена 4096 доляй. Як такое магчыма: бо каранёвай групе даступныя за ўсё 4096 доляй, а сума доляй яе нашчадкаў значна перавышае гэты лік (6144)? Справа ў тым, што значэнне мае лагічны сэнс, таму планавальнік Linux (CFS) выкарыстае яго для прапарцыйнага размеркавання рэсурсаў CPU. У нашым выпадку першыя дзве групы атрымліваюць па 680 рэальных доляй (16,6% ад 4096), а kubepod атрымлівае пакінутыя 2736 доляй. У выпадку прастою першыя дзве групы не будуць выкарыстоўваць выдзеленыя рэсурсы.

На шчасце, у планавальніку ёсць механізм, які дазваляе пазбегнуць страты невыкарыстоўваных рэсурсаў CPU. Ён перадае «прастойваюць» магутнасці ў глабальны пул, з якога яны размяркоўваюцца па групах, якія маюць патрэбу ў дадатковых магутнасцях працэсара (перадача адбываецца партыямі, каб пазбегнуць страт ад акруглення). Аналагічны метад прымяняецца і да ўсіх нашчадкаў нашчадкаў.

Гэты механізм забяспечвае справядлівае размеркаванне магутнасцяў працэсара і сочыць за тым, каб ніводны працэс не «краў» рэсурсы ў іншых.

CPU Limit

Нягледзячы на ​​тое, што канфігурацыі limit'аў і request'аў у K8s выглядаюць падобна, іх рэалізацыя кардынальна адрозніваецца: гэта самая якая ўводзіць у памылку і найменш задакументаваная частка.

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 мс, доступ да рэсурсаў працэсара аднаўляецца яшчэ праз 80 мс

Дапушчальны, вы ўсталявалі CPU limit на 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 - Лік throttled-перыядаў у складзе nr_periods;
  • throttled_time - сукупны throttled-час у нанасекундах.

CPU-ліміты і агрэсіўны тротлінг у Kubernetes

Што ж насамрэч адбываецца?

У выніку мы атрымліваем высокі тротлінг ва ўсіх прыкладаннях. Часам ён у паўтара раза мацней разліковага!

Гэта прыводзіць да розных памылак – збоям праверак гатоўнасці (readiness), завісання кантэйнераў, разрывам сеткавых падлучэнняў, таймаўтам ўнутры сэрвісных выклікаў. У канчатковым рахунку гэта выяўляецца ў павялічанай затрымцы і падвышэнні колькасці памылак.

Рашэнне і наступствы

Тут усё проста. Мы адмовіліся ад limit'ов CPU і заняліся абнаўленнем ядра АС у кластарах на самую свежую версію, у якой баг быў выпраўлены. Колькасць памылак (HTTP 5xx) у нашых сэрвісах адразу ж значна ўпала:

Памылкі HTTP 5xx

CPU-ліміты і агрэсіўны тротлінг у Kubernetes
Памылкі HTTP 5xx аднаго крытычна важнага сэрвісу

Час водгуку p95

CPU-ліміты і агрэсіўны тротлінг у Kubernetes
Затрымка запытаў крытычна важнага сэрвісу, 95-я працэнты

Выдаткі на эксплуатацыю

CPU-ліміты і агрэсіўны тротлінг у Kubernetes
Колькасць выдаткаваных экзэмпляра-гадзін

У чым падвох?

Як было сказана ў пачатку артыкула:

Можна правесці аналогію з камунальнай кватэрай ... Kubernetes выступае ў ролі рыэлтара. Але як утрымаць кватарантаў ад канфліктаў адно з адным? Што, калі адзін з іх, скажам, вырашыць заняць ванны пакой на паўдня?

Вось у чым падвох. Адзін нядбайны кантэйнер можа паглынуць усе даступныя рэсурсы працэсара на машыне. Калі ў вас тлумачальны стэк прыкладанняў (напрыклад, належным чынам настроены JVM, Go, Node VM), тады гэта не праблема: можна працаваць у такіх умовах на працягу працяглага часу. Але калі прыкладанні аптымізаваны дрэнна ці зусім не аптымізаваны (FROM java:latest), сітуацыя можа выйсці з-пад кантролю. У нас у Omio маюцца аўтаматызаваныя базавыя Dockerfiles з адэкватнымі наладамі па змаўчанні для стэка асноўных моў, таму падобнай праблемы не існавала.

Мы рэкамендуем назіраць за метрыкамі ВЫКАРЫСТАННЕ (выкарыстанне, насычэнне і памылкі), затрымкамі API і частатой з'яўлення памылак. Сачыце за тым, каб вынікі адпавядалі чаканням.

Спасылкі

Такая наша гісторыя. Наступныя матэрыялы моцна дапамаглі разабрацца ў тым, што адбываецца:

Справаздачы пра памылкі Kubernetes:

Ці сутыкаліся вы з падобнымі праблемамі ў сваёй практыцы ці валодаеце досведам, звязаным з тротлінгам у кантэйнерызаваных production-асяроддзях? Падзяліцеся сваёй гісторыяй у каментарах!

PS ад перакладчыка

Чытайце таксама ў нашым блогу:

Крыніца: habr.com

Дадаць каментар