Kubernetes: чаму так важна наладзіць кіраванне рэсурсамі сістэмы?

Як правіла, заўсёды ўзнікае неабходнасць забяспечыць выдзелены пул рэсурсаў які-небудзь з дадаткам для яго карэктнай і стабільнай працы. Але што, калі на адных і тых жа магутнасцях працуюць адразу некалькі прыкладанняў? Як забяспечыць мінімальна неабходнымі рэсурсамі кожны з іх? Якім чынам можна абмежаваць спажыванне рэсурсаў? Як правільна размеркаваць нагрузку паміж нодамі? Як забяспечыць працу механізму гарызантальнага маштабавання ў выпадку росту нагрузкі на прыкладанні?

Kubernetes: чаму так важна наладзіць кіраванне рэсурсамі сістэмы?

Пачаць трэба з таго, якія асноўныя тыпы рэсурсаў існуюць у сістэме - гэта, вядома ж, працэсарны час і аператыўная памяць. У маніфестах k8s гэтыя тыпы рэсурсаў вымяраюцца ў наступных адзінках:

  • CPU - у ядрах
  • RAM - у байтах

Прычым для кожнага рэсурсу ёсць магчымасць задаваць два тыпы патрабаванняў. запытамі и рамкі. Requests - апісвае мінімальныя патрабаванні да свабодных рэсурсаў ноды для запуску кантэйнера (і пода ў цэлым), у той час як limits устанаўлівае жорсткае абмежаванне рэсурсаў, даступных кантэйнеру.

Важна разумець, што ў маніфесце не абавязкова відавочна вызначаць абодва тыпы, пры гэтым паводзіны будуць наступнымі:

  • Калі відавочна зададзены толькі limits рэсурсу, то requests для гэтага рэсурсу аўтаматычна прымае значэнне, роўнае limits (у гэтым можна пераканацца, выклікаўшы describe сутнасці). Г.зн. фактычна праца кантэйнера будзе абмежавана такой жа колькасцю рэсурсаў, якую ён патрабуе для свайго запуску.
  • Калі для рэсурсу відавочна зададзены толькі requests, то ніякіх абмежаванняў зверху на гэты рэсурс не задаецца - г.зн. кантэйнер абмежаваны толькі рэсурсамі самай ноды.

Таксама існуе магчымасць наладзіць кіраванне рэсурсамі не толькі на ўзроўні пэўнага кантэйнера, але і на ўзроўні namespace пры дапамозе наступных сутнасцяў:

  • LimitRange — апісвае палітыку абмежавання на ўзроўні кантэйнера/пода ў ns і патрэбна для таго, каб апісаць дэфолтныя абмежаванні на кантэйнер/пад, а таксама прадухіляць стварэнне заведама тлустых кантэйнераў/подаў (ці наадварот), абмяжоўваць іх колькасць і вызначаць магчымую розніцу значэнняў у limits і requests
  • ResourceQuotas — апісваюць палітыку абмежавання ў цэлым па ўсіх кантэйнерах у ns і выкарыстоўваецца, як правіла, для размежавання рэсурсаў па асяродках (карысна, калі асяроддзі цвёрда не размежаваны на ўзроўні нод)

Ніжэй прыведзены прыклады маніфестаў, дзе ўстанаўліваюцца абмежаванні на рэсурсы:

  • На ўзроўні канкрэтнага кантэйнера:

    containers:
    - name: app-nginx
      image: nginx
      resources:
        requests:
          memory: 1Gi
        limits:
          cpu: 200m

    Г.зн. у дадзеным выпадку для запуску кантэйнера з nginx запатрабуецца прынамсі наяўнасць вольных 1G ОП і 0.2 CPU на нодзе, пры гэтым максімум кантэйнер можа з'есці 0.2 CPU і ўсю даступную ОП на нодзе.

  • На ўзроўні цэлага ns:

    apiVersion: v1
    kind: ResourceQuota
    metadata:
      name: nxs-test
    spec:
      hard:
        requests.cpu: 300m
        requests.memory: 1Gi
        limits.cpu: 700m
        limits.memory: 2Gi

    Г.зн. сума ўсіх request кантэйнераў у дэфолтным ns не можа перавышаць 300m для CPU і 1G для АП, а сума ўсіх limit - 700m для CPU і 2G для ОП.

  • Дэфолтныя абмежаванні для кантэйнераў у ns:

    apiVersion: v1
    kind: LimitRange
    metadata:
      name: nxs-limit-per-container
    spec:
     limits:
       - type: Container
         defaultRequest:
           cpu: 100m
           memory: 1Gi
         default:
           cpu: 1
           memory: 2Gi
         min:
           cpu: 50m
           memory: 500Mi
         max:
           cpu: 2
           memory: 4Gi

    Г.зн. у дэфолтным namespace для ўсіх кантэйнераў па змаўчанні будзе ўсталяваны request у 100m для CPU і 1G для АП, limit - 1 CPU і 2G. Пры гэтым таксама ўсталявана абмежаванне на магчымыя значэнне ў request/limit для CPU (50m < x < 2) і RAM (500M < x < 4G).

  • Абмежаванні на ўзроўні подаў ns:

    apiVersion: v1
    kind: LimitRange
    metadata:
     name: nxs-limit-pod
    spec:
     limits:
     - type: Pod
       max:
         cpu: 4
         memory: 1Gi

    Г.зн. для кожнага пода ў дэфолтным ns будзе ўсталявана абмежаванне ў 4 vCPU і 1G.

Цяпер хацелася б расказаць, якія перавагі можа нам даць устаноўка гэтых абмежаванняў.

Механізм балансавання нагрузкі паміж нодамі

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

  1. фільтраванне
  2. Ранжыраванне

Г.зн. паводле апісанай палітыкі першапачаткова выбіраюцца ноды, на якіх магчымы запуск пода на аснове набору прэдыкатывы (у тым ліку правяраецца ці дастаткова ў ноды рэсурсаў для запуску пода – PodFitsResources), а затым для кожнай з гэтых нод, паводле прыярытэты налічваюцца акуляры (у тым ліку, чым больш свабодных рэсурсаў у ноды - тым больш ачкоў ёй прысвойваецца - LeastResourceAllocation/LeastRequestedPriority/BalancedResourceAllocation) і пад запускаецца на нодзе з найбольшай колькасцю ачкоў (калі гэтай умове задавальняюць адразу некалькі нод, то выбіраецца .

Пры гэтым трэба разумець, што scheduler пры адзнацы даступных рэсурсаў ноды арыентуецца на дадзеныя, якія захоўваюцца ў etcd - г.зн. на суму requested/limit рэсурсу кожнага пода, запушчанага на гэтай нодзе, але не на фактычнае спажыванне рэсурсаў. Гэтую інфармацыю можна атрымаць у выснове каманды kubectl describe node $NODE, Напрыклад:

# kubectl describe nodes nxs-k8s-s1
..
Non-terminated Pods:         (9 in total)
  Namespace                  Name                                         CPU Requests  CPU Limits  Memory Requests  Memory Limits  AGE
  ---------                  ----                                         ------------  ----------  ---------------  -------------  ---
  ingress-nginx              nginx-ingress-controller-754b85bf44-qkt2t    0 (0%)        0 (0%)      0 (0%)           0 (0%)         233d
  kube-system                kube-flannel-26bl4                           150m (0%)     300m (1%)   64M (0%)         500M (1%)      233d
  kube-system                kube-proxy-exporter-cb629                    0 (0%)        0 (0%)      0 (0%)           0 (0%)         233d
  kube-system                kube-proxy-x9fsc                             0 (0%)        0 (0%)      0 (0%)           0 (0%)         233d
  kube-system                nginx-proxy-k8s-worker-s1                    25m (0%)      300m (1%)   32M (0%)         512M (1%)      233d
  nxs-monitoring             alertmanager-main-1                          100m (0%)     100m (0%)   425Mi (1%)       25Mi (0%)      233d
  nxs-logging                filebeat-lmsmp                               100m (0%)     0 (0%)      100Mi (0%)       200Mi (0%)     233d
  nxs-monitoring             node-exporter-v4gdq                          112m (0%)     122m (0%)   200Mi (0%)       220Mi (0%)     233d
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  Resource           Requests           Limits
  --------           --------           ------
  cpu                487m (3%)          822m (5%)
  memory             15856217600 (2%)  749976320 (3%)
  ephemeral-storage  0 (0%)             0 (0%)

Тут мы бачым усе поды, запушчаныя на канкрэтнай нодзе, а таксама рэсурсы, якія запытвае кожны з подаў. А вось як выглядаюць логі scheduler пры запуску пода cronjob-cron-events-1573793820-xt6q9.

лог

I1115 07:57:21.637791       1 scheduling_queue.go:908] About to try and schedule pod nxs-stage/cronjob-cron-events-1573793820-xt6q9                                                                                                                                           
I1115 07:57:21.637804       1 scheduler.go:453] Attempting to schedule pod: nxs-stage/cronjob-cron-events-1573793820-xt6q9                                                                                                                                                    
I1115 07:57:21.638285       1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s5 is allowed, Node is running only 16 out of 110 Pods.                                                                               
I1115 07:57:21.638300       1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s6 is allowed, Node is running only 20 out of 110 Pods.                                                                               
I1115 07:57:21.638322       1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s3 is allowed, Node is running only 20 out of 110 Pods.                                                                               
I1115 07:57:21.638322       1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s4 is allowed, Node is running only 17 out of 110 Pods.                                                                               
I1115 07:57:21.638334       1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s10 is allowed, Node is running only 16 out of 110 Pods.                                                                              
I1115 07:57:21.638365       1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s12 is allowed, Node is running only 9 out of 110 Pods.                                                                               
I1115 07:57:21.638334       1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s11 is allowed, Node is running only 11 out of 110 Pods.                                                                              
I1115 07:57:21.638385       1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s1 is allowed, Node is running only 19 out of 110 Pods.                                                                               
I1115 07:57:21.638402       1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s2 is allowed, Node is running only 21 out of 110 Pods.                                                                               
I1115 07:57:21.638383       1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s9 is allowed, Node is running only 16 out of 110 Pods.                                                                               
I1115 07:57:21.638335       1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s8 is allowed, Node is running only 18 out of 110 Pods.                                                                               
I1115 07:57:21.638408       1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s13 is allowed, Node is running only 8 out of 110 Pods.                                                                               
I1115 07:57:21.638478       1 predicates.go:1369] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s10 is allowed, existing pods anti-affinity terms satisfied.                                                                         
I1115 07:57:21.638505       1 predicates.go:1369] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s8 is allowed, existing pods anti-affinity terms satisfied.                                                                          
I1115 07:57:21.638577       1 predicates.go:1369] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s9 is allowed, existing pods anti-affinity terms satisfied.                                                                          
I1115 07:57:21.638583       1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s7 is allowed, Node is running only 25 out of 110 Pods.                                                                               
I1115 07:57:21.638932       1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: BalancedResourceAllocation, capacity 39900 millicores 66620178432 memory bytes, total request 2343 millicores 9640186880 memory bytes, score 9        
I1115 07:57:21.638946       1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: LeastResourceAllocation, capacity 39900 millicores 66620178432 memory bytes, total request 2343 millicores 9640186880 memory bytes, score 8           
I1115 07:57:21.638961       1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: BalancedResourceAllocation, capacity 39900 millicores 66620170240 memory bytes, total request 4107 millicores 11307422720 memory bytes, score 9        
I1115 07:57:21.638971       1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: BalancedResourceAllocation, capacity 39900 millicores 66620178432 memory bytes, total request 5847 millicores 24333637120 memory bytes, score 7        
I1115 07:57:21.638975       1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: LeastResourceAllocation, capacity 39900 millicores 66620170240 memory bytes, total request 4107 millicores 11307422720 memory bytes, score 8           
I1115 07:57:21.638990       1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: LeastResourceAllocation, capacity 39900 millicores 66620178432 memory bytes, total request 5847 millicores 24333637120 memory bytes, score 7           
I1115 07:57:21.639022       1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s10: TaintTolerationPriority, Score: (10)                                                                                                        
I1115 07:57:21.639030       1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s8: TaintTolerationPriority, Score: (10)                                                                                                         
I1115 07:57:21.639034       1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s9: TaintTolerationPriority, Score: (10)                                                                                                         
I1115 07:57:21.639041       1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s10: NodeAffinityPriority, Score: (0)                                                                                                            
I1115 07:57:21.639053       1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s8: NodeAffinityPriority, Score: (0)                                                                                                             
I1115 07:57:21.639059       1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s9: NodeAffinityPriority, Score: (0)                                                                                                             
I1115 07:57:21.639061       1 interpod_affinity.go:237] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: InterPodAffinityPriority, Score: (0)                                                                                                                   
I1115 07:57:21.639063       1 selector_spreading.go:146] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: SelectorSpreadPriority, Score: (10)                                                                                                                   
I1115 07:57:21.639073       1 interpod_affinity.go:237] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: InterPodAffinityPriority, Score: (0)                                                                                                                    
I1115 07:57:21.639077       1 selector_spreading.go:146] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: SelectorSpreadPriority, Score: (10)                                                                                                                    
I1115 07:57:21.639085       1 interpod_affinity.go:237] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: InterPodAffinityPriority, Score: (0)                                                                                                                    
I1115 07:57:21.639088       1 selector_spreading.go:146] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: SelectorSpreadPriority, Score: (10)                                                                                                                    
I1115 07:57:21.639103       1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s10: SelectorSpreadPriority, Score: (10)                                                                                                         
I1115 07:57:21.639109       1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s8: SelectorSpreadPriority, Score: (10)                                                                                                          
I1115 07:57:21.639114       1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s9: SelectorSpreadPriority, Score: (10)                                                                                                          
I1115 07:57:21.639127       1 generic_scheduler.go:781] Host nxs-k8s-s10 => Score 100037                                                                                                                                                                            
I1115 07:57:21.639150       1 generic_scheduler.go:781] Host nxs-k8s-s8 => Score 100034                                                                                                                                                                             
I1115 07:57:21.639154       1 generic_scheduler.go:781] Host nxs-k8s-s9 => Score 100037                                                                                                                                                                             
I1115 07:57:21.639267       1 scheduler_binder.go:269] AssumePodVolumes for pod "nxs-stage/cronjob-cron-events-1573793820-xt6q9", node "nxs-k8s-s10"                                                                                                               
I1115 07:57:21.639286       1 scheduler_binder.go:279] AssumePodVolumes for pod "nxs-stage/cronjob-cron-events-1573793820-xt6q9", node "nxs-k8s-s10": all PVCs bound and nothing to do                                                                             
I1115 07:57:21.639333       1 factory.go:733] Attempting to bind cronjob-cron-events-1573793820-xt6q9 to nxs-k8s-s10

Тут мы бачым, што першапачаткова scheduler ажыццяўляе фільтраванне і фармуе спіс з 3-х нод, на якіх магчымы запуск (nxs-k8s-s8, nxs-k8s-s9, nxs-k8s-s10). Затым ажыццяўляе падлік ачкоў па некалькіх параметрах (у тым ліку BalancedResourceAllocation, LeastResourceAllocation) для кожнай з гэтых нод з мэтай вызначыць найболей падыходны вузел. У канчатковым выніку пад плануецца на нодзе з найбольшай колькасцю ачкоў (тут адразу дзве ноды маюць аднолькавую колькасць ачкоў 100037, таму выбіраецца выпадковая з іх - nxs-k8s-s10).

Выснова: калі на нодзе працуюць поды, для якіх не зададзены абмежаванні, то для k8s (з пункту гледжання спажывання рэсурсаў) гэта будзе раўнасільна таму, як калі б на гэтай нодзе такія поды ўвогуле адсутнічалі. Таму, калі ў вас, умоўна, ёсць pod з пражэрлівым працэсам (напрыклад, wowza) і для яго не зададзены абмежаванні, тое можа паўстаць такая сітуацыя, калі фактычна дадзены пад з'еў усе рэсурсы ноды, але пры гэтым для k8s гэта нода лічыцца ненагружанай і ёй будуць налічвацца такая ж колькасць ачкоў пры ранжыраванні (менавіта ў пунктах з ацэнкай даступных рэсурсаў), як і надзе, на якой няма працоўных подаў, што ў канчатковым выніку можа прывесці да нераўнамернага размеркавання нагрузкі паміж нодамі.

Высяленне пода

Як вядома – кожнаму поду прысвойваецца адзін з 3 QoS-класаў:

  1. guaranuted - прызначаецца тады, як для кожнага кантэйнера ў подзе для memory і cpu зададзены request і limit, прычым гэтыя значэнні павінны супадаць
  2. burstable - Хоць бы адзін кантэйнер у подзе мае request і limit, пры гэтым request < limit
  3. лепшыя намаганні - калі ні адзін кантэйнер у подзе не абмежаваны па рэсурсах

Пры гэтым, калі на нодзе назіраецца недахоп рэсурсаў (дыска, памяці), kubelet пачынае ранжыраваць і высяляць под'ы па вызначаным алгарытме, які ўлічвае прыярытэт пода і яго QoS-клас. Напрыклад, калі гаворка ідзе аб RAM, то на аснове QoS класа налічваюцца акуляры па наступным прынцыпе:

  • Гарантаваны: -998
  • BestEffort: 1000
  • Burstable: min(max(2, 1000 - (1000 * memoryRequestBytes) / machineMemoryCapacityBytes), 999)

Г.зн. пры аднолькавым прыярытэце, kubelet у першую чаргу будзе высяляць з ноды поды з QoS-класам best effort.

Выснова: калі вы жадаеце паменшыць верагоднасць высялення патрэбнага пода з ноды ў выпадку недахопу рэсурсаў на ёй, то нароўні з прыярытэтам неабходна таксама паклапаціцца і аб заданні request/limit для яго.

Механізм гарызантальнага аўтамаштабавання подаў прыкладання (HPA)

Калі стаіць задача аўтаматычна павялічваць і памяншаць колькасць pod у залежнасці ад выкарыстання рэсурсаў (сістэмнага - CPU / RAM або карыстацкага - rps) у яе вырашэнні можа дапамагчы такая сутнасць k8s як HPA (Horizontal Pod Autoscaler). Алгарытм якога заключаецца ў наступным:

  1. Вызначаюцца бягучыя паказанні назіранага рэсурсу (currentMetricValue)
  2. Вызначаюцца жаданыя значэнні для рэсурсу (desiredMetricValue), якія для сістэмных рэсурсаў задаюцца пры дапамозе request
  3. Вызначаецца бягучая колькасць рэплік (currentReplicas)
  4. Па наступнай формуле разлічваецца жаданая колькасць рэплік (desiredReplicas)
    desiredReplicas = [ currentReplicas * ( currentMetricValue / desiredMetricValue )]

Пры гэтым маштабавання не адбудзецца, калі каэфіцыент (currentMetricValue / desiredMetricValue) блізкі да 1 (пры гэтым дапушчальную хібнасць мы можам задаваць самі, па змаўчанні яна роўная 0.1).

Разгледзім працу hpa на прыкладзе дадатку app-test (апісанае як Deployment), дзе неабходна змяняць колькасць рэплік, у залежнасці ад спажывання CPU:

  • Маніфест прыкладання

    kind: Deployment
    apiVersion: apps/v1beta2
    metadata:
    name: app-test
    spec:
    selector:
    matchLabels:
    app: app-test
    replicas: 2
    template:
    metadata:
    labels:
    app: app-test
    spec:
    containers:
    - name: nginx
    image: registry.nixys.ru/generic-images/nginx
    imagePullPolicy: Always
    resources:
    requests:
    cpu: 60m
    ports:
    - name: http
    containerPort: 80
    - name: nginx-exporter
    image: nginx/nginx-prometheus-exporter
    resources:
    requests:
    cpu: 30m
    ports:
    - name: nginx-exporter
    containerPort: 9113
    args:
    - -nginx.scrape-uri
    - http://127.0.0.1:80/nginx-status

    Г.зн. мы бачым, што пад з дадаткам першапачаткова запускаецца ў двух экземплярах, кожны з якіх змяшчае два кантэйнера nginx і nginx-exporter, для кожнага з якіх зададзены запытамі для CPU.

  • Маніфест HPA

    apiVersion: autoscaling/v2beta2
    kind: HorizontalPodAutoscaler
    metadata:
    name: app-test-hpa
    spec:
    maxReplicas: 10
    minReplicas: 2
    scaleTargetRef:
    apiVersion: extensions/v1beta1
    kind: Deployment
    name: app-test
    metrics:
    - type: Resource
    resource:
    name: cpu
    target:
    type: Utilization
    averageUtilization: 30

    Г.зн. мы стварылі hpa, які будзе сачыць за Deployment app-test і рэгуляваць колькасць подаў з дадаткам на аснове паказчыка cpu (мы чакаем, што пад павінен спажываць 30% адсоткаў ад запытанага ім CPU), пры гэтым колькасць рэплік знаходзіцца ў прамежку 2/10.

    Цяпер, разгледзім механізм працы hpa, калі падаць нагрузку на адзін з подаў:

     # kubectl top pod
    NAME                                                   CPU(cores)   MEMORY(bytes)
    app-test-78559f8f44-pgs58            101m         243Mi
    app-test-78559f8f44-cj4jz            4m           240Mi

Разам маем наступнае:

  • Жаданае значэнне (desiredMetricValue) - паводле налад hpa у нас роўна 30%
  • Бягучы значэнне (currentMetricValue) – для разліку controller-manager разлічвае сярэдняе значэнне спажывання рэсурсу ў%, г.зн. умоўна робіць наступнае:
    1. Атрымлівае абсалютныя значэнні метрык подаў з metric-сервера, г.зн. 101m і 4m
    2. Вылічвае сярэдняе абсалютнае значэнне, г.зн. (101m + 4m) / 2 = 53m
    3. Атрымлівае абсалютнае значэнне для жаданага спажывання рэсурсу (для гэтага сумуюцца request усіх кантэйнераў) 60m + 30m = 90m
    4. Разлічвае сярэдні працэнт спажывання CPU адносна request пода, г.зн. 53m / 90m * 100% = 59%

Цяпер у нас ёсць усё неабходнае для таго, каб вызначыць, ці трэба змяняць колькасць рэплік, для гэтага разлічваем каэфіцыент:

ratio = 59% / 30% = 1.96

Г.зн. колькасць рэплік павінна быць павялічана ў ~2 разы і скласці [2 * 1.96] = 4.

Выснову: Як можна заўважыць, для таго каб гэты механізм працаваў неабходнай умовай з'яўляецца ў тым ліку наяўнасць requests для ўсіх кантэйнераў у назіраным подзе.

Механізм гарызантальнага аўтамаштабавання нод (Cluster Autoscaler)

Для таго, каб нівеліраваць негатыўны ўплыў на сістэму пры воплесках нагрузкі, наяўнасць наладжанага hpa бывае нядосыць. Напрыклад, паводле налад у hpa controller manager прымае рашэнне аб тым, што колькасць рэплік неабходна павялічыць у 2 разы, аднак на нодах няма свабодных рэсурсаў для запуску такой колькасці подаў (г.зн. нода не можа даць запытаныя рэсурсы пода requests) і гэтыя поды пераходзяць у стан Pending.

У гэтым выпадку, калі ў правайдэра маецца адпаведны IaaS/PaaS (напрыклад, GKE/GCE, AKS, EKS і г.д.), нам можа дапамагчы такая прылада, як Node Autoscaler. Ён дазваляе задаць максімальную і мінімальную колькасць нод у кластары і аўтаматычна рэгуляваць бягучую колькасць нод (шляхам звароту да API хмарнага правайдэра для замовы/выдалення ноды), калі назіраецца недахоп рэсурсаў у кластары і поды не могуць быць запланаваны (знаходзяцца ў стане Pending).

Выснову: для магчымасці аўтамаштабавання нод неабходна задаваць requests у кантэйнерах подаў, каб k8s мог правільна ацаніць нагружанасць нод і адпаведна паведамляць, што для запуску чарговага пода рэсурсаў у кластары няма.

Заключэнне

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

  1. Для больш дакладнай працы scheduler у частцы балансавання нагрузкі паміж нодамі k8s
  2. Для памяншэння верагоднасці ўзнікнення падзеі "высяленне пода"
  3. Для працы гарызантальнага аўтамаштабавання подаў прыкладання (HPA)
  4. Для працы гарызантальнага аўтамаштабавання нод (Cluster Autoscaling) у хмарных правайдэраў

Таксама чытайце іншыя артыкулы ў нашым блогу:

Крыніца: habr.com

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