Създаване на допълнителен kube-scheduler с персонализиран набор от правила за планиране

Създаване на допълнителен kube-scheduler с персонализиран набор от правила за планиране

Kube-scheduler е неотменим компонент на Kubernetes, който отговаря за планирането на подовете върху нодовете в съответствие с определените политики. Често при експлоатацията на Kubernetes клъстера не мислим за това по какви точно политики се планират подовете, тъй като наборът от политики на дефолтния kube-scheduler е подходящ за повечето ежедневни задачи. Въпреки това, възникват ситуации, когато е важно да управляваме процеса на разпределение на подовете по-прецизно, и за решаването на тази задача има два пътя:

  1. Да създадем kube-scheduler с персонализиран набор от правила
  2. Да напишем собствен scheduler и да го научим да работи с API заявките на сървъра

В рамките на тази статия ще опиша реализацията именно на първата точка за решаване на проблема с неравномерното планиране на подовете в един от нашите проекти.

Кратка представа за работата на kube-scheduler

Струва си да се отбележи, че kube-scheduler не отговаря за непосредственото планиране на подовете — той отговаря единствено за определянето на нода, на която трябва да бъде разположен под. С други думи, резултатът от работата на kube-scheduler е името на нода, което той връща на API-сервера при заявка за планиране и на това неговата работа приключва.

Първо, kube-scheduler съставя списък с нодовете, на които може да бъде планиран подът в съответствие с политиките на predicates. След това всяка нода от този списък получава определен брой точки в зависимост от политиките на priorities. В резултат се избира нода, получила максимален брой точки. Ако има ноди с равен максимален резултат, се избира произволна от тях. С информация за списъка и описание на политиките на predicates (филтриране) и priorities (оценяване) можете да се запознаете в документацията.

Описание на същността на проблема

Въпреки че управляваме много различни Kubernetes клъстери в Nixys, за първи път се сблъскахме с проблема с планирането на подове наскоро, когато за един от нашите проекти възникна необходимостта от стартиране на голям брой периодични задачи (~100 инстанции CronJob). За да опростим максимално описанието на проблема, ще вземем за пример един микросервис, в който веднъж в минутата се изпълнява cron-задача, създаваща определено натоварване на CPU. За работата на cron-задачата бяха предвидени три абсолютно идентични по характеристики ноди (по 24 vCPU на всяка).

Въпреки това, не можем точно да кажем колко време ще отнеме изпълнението на CronJob, тъй като обемът на входните данни постоянно се променя. ВAverage в нормална работа на kube-scheduler’a, на всяка нода работят 3-4 екземпляра на задачата, които създават около 20-30% натоварване на CPU на всяка нода:

Създаване на допълнителен kube-scheduler с персонализиран набор от правила за планиране

Проблемът е, че понякога подовете на cron-задачите не се планираха на една от трите ноди. Тоест, в някакъв момент на времето на една от нодите не се е планирал нито един под, докато на другите две ноди работеха по 6-8 екземпляра на задачата, създавайки около 40-60% натоварване на CPU:

Създаване на допълнителен kube-scheduler с персонализиран набор от правила за планиране

Проблемът се повтаряше с абсолютно случайна периодичност и от време на време корелираше с момента на разгръщането на нова версия на кода.

След като увеличихме нивото на логиране на kube-scheduler’a до 10 (-v=10), започнахме да записваме колко точки събира всяка от нодите в процеса на оценка. При нормалната работа на планирането в логовете можеше да се види следната информация:

resource_allocation.go:78] cronjob-1574828880-mn7m4 -> Node03: BalancedResourceAllocation, capacity 23900 millicores 67167186944 memory bytes, total request 1387 millicores 4161694720 memory bytes, score 9
resource_allocation.go:78] cronjob-1574828880-mn7m4 -> Node02: BalancedResourceAllocation, capacity 23900 millicores 67167186944 memory bytes, total request 1347 millicores 4444810240 memory bytes, score 9
resource_allocation.go:78] cronjob-1574828880-mn7m4 -> Node03: LeastResourceAllocation, capacity 23900 millicores 67167186944 memory bytes, total request 1387 millicores 4161694720 memory bytes, score 9
resource_allocation.go:78] cronjob-1574828880-mn7m4 -> Node01: BalancedResourceAllocation, capacity 23900 millicores 67167186944 memory bytes, total request 1687 millicores 4790840320 memory bytes, score 9
resource_allocation.go:78] cronjob-1574828880-mn7m4 -> Node02: LeastResourceAllocation, capacity 23900 millicores 67167186944 memory bytes, total request 1347 millicores 4444810240 memory bytes, score 9
resource_allocation.go:78] cronjob-1574828880-mn7m4 -> Node01: LeastResourceAllocation, capacity 23900 millicores 67167186944 memory bytes, total request 1687 millicores 4790840320 memory bytes, score 9
generic_scheduler.go:726] cronjob-1574828880-mn7m4_project-stage -> Node01: NodeAffinityPriority, Score: (0)                                                                                       
generic_scheduler.go:726] cronjob-1574828880-mn7m4_project-stage -> Node02: NodeAffinityPriority, Score: (0)                                                                                       
generic_scheduler.go:726] cronjob-1574828880-mn7m4_project-stage -> Node03: NodeAffinityPriority, Score: (0)                                                                                       
interpod_affinity.go:237] cronjob-1574828880-mn7m4 -> Node01: InterPodAffinityPriority, Score: (0)                                                                                                        
generic_scheduler.go:726] cronjob-1574828880-mn7m4_project-stage -> Node01: TaintTolerationPriority, Score: (10)                                                                                   
interpod_affinity.go:237] cronjob-1574828880-mn7m4 -> Node02: InterPodAffinityPriority, Score: (0)                                                                                                        
generic_scheduler.go:726] cronjob-1574828880-mn7m4_project-stage -> Node02: TaintTolerationPriority, Score: (10)                                                                                   
selector_spreading.go:146] cronjob-1574828880-mn7m4 -> Node01: SelectorSpreadPriority, Score: (10)                                                                                                        
interpod_affinity.go:237] cronjob-1574828880-mn7m4 -> Node03: InterPodAffinityPriority, Score: (0)                                                                                                        
generic_scheduler.go:726] cronjob-1574828880-mn7m4_project-stage -> Node03: TaintTolerationPriority, Score: (10)                                                                                   
selector_spreading.go:146] cronjob-1574828880-mn7m4 -> Node02: SelectorSpreadPriority, Score: (10)                                                                                                        
selector_spreading.go:146] cronjob-1574828880-mn7m4 -> Node03: SelectorSpreadPriority, Score: (10)                                                                                                        
generic_scheduler.go:726] cronjob-1574828880-mn7m4_project-stage -> Node01: SelectorSpreadPriority, Score: (10)                                                                                    
generic_scheduler.go:726] cronjob-1574828880-mn7m4_project-stage -> Node02: SelectorSpreadPriority, Score: (10)                                                                                    
generic_scheduler.go:726] cronjob-1574828880-mn7m4_project-stage -> Node03: SelectorSpreadPriority, Score: (10)                                                                                    
generic_scheduler.go:781] Host Node01 -> Score 100043                                                                                                                                                                        
generic_scheduler.go:781] Host Node02 -> Score 100043                                                                                                                                                                        
generic_scheduler.go:781] Host Node03 -> Score 100043

Т.е. съдейки по информацията, получена от логовете, всяка от нодите е събирала равно количество окончателни точки и за планиране е била избирана произволна. В момента на проблемното планиране логовете изглеждаха по следния начин:

resource_allocation.go:78] cronjob-1574211360-bzfkr -> Node02: BalancedResourceAllocation, capacity 23900 millicores 67167186944 memory bytes, total request 1587 millicores 4581125120 memory bytes, score 9
resource_allocation.go:78] cronjob-1574211360-bzfkr -> Node03: BalancedResourceAllocation, capacity 23900 millicores 67167186944 memory bytes, total request 1087 millicores 3532549120 memory bytes, score 9
resource_allocation.go:78] cronjob-1574211360-bzfkr -> Node02: LeastResourceAllocation, capacity 23900 millicores 67167186944 memory bytes, total request 1587 millicores 4581125120 memory bytes, score 9
resource_allocation.go:78] cronjob-1574211360-bzfkr -> Node01: BalancedResourceAllocation, capacity 23900 millicores 67167186944 memory bytes, total request 987 millicores 3322833920 memory bytes, score 9
resource_allocation.go:78] cronjob-1574211360-bzfkr -> Node01: LeastResourceAllocation, capacity 23900 millicores 67167186944 memory bytes, total request 987 millicores 3322833920 memory bytes, score 9 
resource_allocation.go:78] cronjob-1574211360-bzfkr -> Node03: LeastResourceAllocation, capacity 23900 millicores 67167186944 memory bytes, total request 1087 millicores 3532549120 memory bytes, score 9
interpod_affinity.go:237] cronjob-1574211360-bzfkr -> Node03: InterPodAffinityPriority, Score: (0)                                                                                                        
interpod_affinity.go:237] cronjob-1574211360-bzfkr -> Node02: InterPodAffinityPriority, Score: (0)                                                                                                        
interpod_affinity.go:237] cronjob-1574211360-bzfkr -> Node01: InterPodAffinityPriority, Score: (0)                                                                                                        
generic_scheduler.go:726] cronjob-1574211360-bzfkr_project-stage -> Node03: TaintTolerationPriority, Score: (10)                                                                                   
selector_spreading.go:146] cronjob-1574211360-bzfkr -> Node03: SelectorSpreadPriority, Score: (10)                                                                                                        
selector_spreading.go:146] cronjob-1574211360-bzfkr -> Node02: SelectorSpreadPriority, Score: (10)                                                                                                        
generic_scheduler.go:726] cronjob-1574211360-bzfkr_project-stage -> Node02: TaintTolerationPriority, Score: (10)                                                                                   
selector_spreading.go:146] cronjob-1574211360-bzfkr -> Node01: SelectorSpreadPriority, Score: (10)                                                                                                        
generic_scheduler.go:726] cronjob-1574211360-bzfkr_project-stage -> Node03: NodeAffinityPriority, Score: (0)                                                                                       
generic_scheduler.go:726] cronjob-1574211360-bzfkr_project-stage -> Node03: SelectorSpreadPriority, Score: (10)                                                                                    
generic_scheduler.go:726] cronjob-1574211360-bzfkr_project-stage -> Node02: SelectorSpreadPriority, Score: (10)                                                                                    
generic_scheduler.go:726] cronjob-1574211360-bzfkr_project-stage -> Node01: TaintTolerationPriority, Score: (10)                                                                                   
generic_scheduler.go:726] cronjob-1574211360-bzfkr_project-stage -> Node02: NodeAffinityPriority, Score: (0)                                                                                       
generic_scheduler.go:726] cronjob-1574211360-bzfkr_project-stage -> Node01: NodeAffinityPriority, Score: (0)                                                                                       
generic_scheduler.go:726] cronjob-1574211360-bzfkr_project-stage -> Node01: SelectorSpreadPriority, Score: (10)                                                                                    
generic_scheduler.go:781] Host Node03 -> Score 100041                                                                                                                                                                        
generic_scheduler.go:781] Host Node02 -> Score 100041                                                                                                                                                                        
generic_scheduler.go:781] Host Node01 -> Score 100038

От които е видно, че един от възлите е натрупал по-малко точки в крайна сметка от останалите, и затова планирането е било извършвано само на два възла, които са получили максимален резултат. Така ние окончателно се уверихме, че проблемът е именно в планирането на подовете.

Следващият алгоритъм за решаване на проблема ни беше очевиден — да анализираме логовете, да разберем по какъв точно приоритет възелът не е събрал точки и, при необходимост, да коригираме политиките на дефолтния kube-scheduler. Обаче тук се сблъскахме с две съществени трудности:

  1. На максималното ниво на логиране (10) се отразява набор от точки само по някои приоритети. В предоставения по-горе откъс от логовете може да се забележи, че по всички приоритети, отразени в логовете, възлите събират едно и също количество точки при нормално и проблемно планиране, но окончателният резултат в случай на проблемно планиране се различава. Така можем да направим извода, че по някои приоритети точките се изчисляват „зад кадър“, и нямаме никаква възможност да разберем по какъв точно приоритет възелът не е събрал точки. Тази проблема подробно описахме в проблем репозитория на Kubernetes в Github. По време на написването на статията получихме отговор от разработчиците, че поддръжката на логиране ще бъде добавена в обновленията на Kubernetes v1.15, v1.16 и v1.17.
  2. Няма прост начин да разберем с какъв конкретен набор от политики в момента работи kube-scheduler. Да, в документацията този списък е изброен, но в него няма информация какви точно тежести са зададени на всяка от политиките prioritets. Да видите тежестите или да редактирате политиките на дефолтния kube-scheduler може само в изходниците..

Следва да се отбележи, че веднъж успяхме да установим, че възелът не е събирал точки по политиката ImageLocalityPriority, която присъжда точки на възела, ако на него вече има образ, необходим за стартиране на приложението. Т.е. в момента на внедряване на нова версия на приложението cron-задачата успяваше да се стартира на два възла, изтегляйки на тях нов образ от docker registry, и по този начин двата възла получаваха много по-висок общ резултат в сравнение с третия.

Както вече споменах, в логовете не виждаме информация за оценката на политиката ImageLocalityPriority, затова, за да проверим предположението си, пуснахме образ с новата версия на приложението на третата нода, след което планирането проработи коректно. Поради политиката ImageLocalityPriority, проблемът с планирането се наблюдаваше рядко, обикновено беше свързан с нещо друго. Тъй като не можехме да дебъгваме напълно всяка от политиките в списъка с приоритети на дефолтния kube-scheduler, възникна необходимост от гъвкаво управление на политиките за планиране на подовете.

Формулиране на задачата

Искахме решението на проблема да бъде колкото се може по-точно, тоест основните същности на Kubernetes (тук става въпрос за дефолтния kube-scheduler) трябва да останат непроменени. Не искахме да решаваме проблема на едно място и да го създаваме на друго. Така стигнахме до два варианта за решение на проблема, които бяха споменати във въведението на статията — създаване на допълнителен scheduler или писане на свой собствен. Основното изискване за планиране на cron задачи е равномерното разпределение на натоварването между трите ноди. Това изискване може да бъде удовлетворено с вече съществуващите политики на kube-scheduler, така че за решението на нашата задача няма смисъл да пишем собствен scheduler.

Инструкцията за създаване и Deployment на допълнителния kube-scheduler е описана в документацията. Въпреки това, сметнахме, че само съществуването на Deployment не е достатъчно за осигуряване на отказоустойчивост в работата на толкова критичен сервис като kube-scheduler, затова решихме да разширим нов kube-scheduler като Static Pod, който ще се наблюдава директно от Kubelet. Така станаха ясни следните изисквания към новия kube-scheduler:

  1. Сервисът трябва да бъде разположен като Static Pod на всички мастери в клъстера
  2. Трябва да бъде предвидена отказоустойчивост в случай на недостъпност на активния под с kube-scheduler
  3. Основният приоритет при планирането трябва да бъде количеството налични ресурси на нодата (LeastRequestedPriority)

Реализация на решението

Важно е да се отбележи, че всички работи ще се извършват в Kubernetes v1.14.7, тъй като именно тази версия беше използвана в проекта. Нека започнем с написването на манифест за нашия нов kube-scheduler. За основа ще вземем манифеста на дефолтния ( /etc/kubernetes/manifests/kube-scheduler.yaml) и ще го приведем в следния вид:

вид: Pod
метаданни:
  етикети:
    компонент: scheduler
    ниво: control-plane
  име: kube-scheduler-cron
  пространство: kube-system
спецификации:
      контейнери:
      - команда:
        - /usr/local/bin/kube-scheduler
        - --address=0.0.0.0
        - --port=10151
        - --secure-port=10159
        - --config=/etc/kubernetes/scheduler-custom.conf
        - --authentication-kubeconfig=/etc/kubernetes/scheduler.conf
        - --authorization-kubeconfig=/etc/kubernetes/scheduler.conf
        - --v=2
        изображение: gcr.io/google-containers/kube-scheduler:v1.14.7
        политикаPull на изображение: IfNotPresent
        livenessProbe:
          прагове за неуспех: 8
          httpGet:
            хост: 127.0.0.1
            път: /healthz
            порт: 10151
            схема: HTTP
          начално забавяне в секунди: 15
          време за изчакване в секунди: 15
        име: kube-scheduler-cron-container
        ресурси:
          заявки:
            cpu: '0.1'
        обемниМонтажи:
        - монтаженПът: /etc/kubernetes/scheduler.conf
          име: kube-config
          само-за-четене: true
        - монтаженПът: /etc/localtime
          име: localtime
          само-за-четене: true
        - монтаженПът: /etc/kubernetes/scheduler-custom.conf
          име: scheduler-config
          само-за-четене: true
        - монтаженПът: /etc/kubernetes/scheduler-custom-policy-config.json
          име: policy-config
          само-за-четене: true
      хостоваМрежа: true
      име на клас приоритет: system-cluster-critical
      обеми:
      - хостПът:
          път: /etc/kubernetes/scheduler.conf
          тип: FileOrCreate
        име: kube-config
      - хостПът:
          път: /etc/localtime
        име: localtime
      - хостПът:
          път: /etc/kubernetes/scheduler-custom.conf
          тип: FileOrCreate
        име: scheduler-config
      - хостПът:
          път: /etc/kubernetes/scheduler-custom-policy-config.json
          тип: FileOrCreate
        име: policy-config

Кратък преглед на основните промени:

  1. Променихме името на пода и контейнера на kube-scheduler-cron
  2. Указахме използването на портовете 10151 и 10159, тъй като е зададена опцията хостоваМрежа: true и не можем да използваме същите портове, които използва стандартният kube-scheduler (10251 и 10259)
  3. С параметъра —config указахме файла с конфигурация, с който трябва да стартира услугата
  4. Настроихме монтиране на файл с конфигурация (scheduler-custom.conf) и файл с политики на планирането (scheduler-custom-policy-config.json) от хоста

Не забравяйте, че нашият kube-scheduler ще изисква права, подобни на тези на стандартния. Редактираме неговата кластерна роля:

kubectl edit clusterrole system:kube-scheduler

...
   имена на ресурси:
    - kube-scheduler
    - kube-scheduler-cron
...

Сега ще разгледаме какво трябва да съдържа файлът с конфигурация и файлът с политиките за планиране:

  • Файлът с конфигурация (scheduler-custom.conf)
    За да получите конфигурацията на стандартния kube-scheduler, е необходимо да използвате параметъра --write-config-to от документацията. Получената конфигурация ще се постави във файла /etc/kubernetes/scheduler-custom.conf и ще бъде приведена в следния вид:

apiVersion: kubescheduler.config.k8s.io/v1alpha1
kind: KubeSchedulerConfiguration
schedulerName: kube-scheduler-cron
bindTimeoutSeconds: 600
clientConnection:
  acceptContentTypes: ""
  burst: 100
  contentType: application/vnd.kubernetes.protobuf
  kubeconfig: /etc/kubernetes/scheduler.conf
  qps: 50
disablePreemption: false
enableContentionProfiling: false
enableProfiling: false
failureDomains: kubernetes.io/hostname,failure-domain.beta.kubernetes.io/zone,failure-domain.beta.kubernetes.io/region
hardPodAffinitySymmetricWeight: 1
healthzBindAddress: 0.0.0.0:10151
leaderElection:
  leaderElect: true
  leaseDuration: 15s
  lockObjectName: kube-scheduler-cron
  lockObjectNamespace: kube-system
  renewDeadline: 10s
  resourceLock: endpoints
  retryPeriod: 2s
metricsBindAddress: 0.0.0.0:10151
percentageOfNodesToScore: 0
algorithmSource:
   policy:
     file:
       path: "/etc/kubernetes/scheduler-custom-policy-config.json"

Кратък преглед на основните промени:

  1. Зададохме името на нашата услуга kube-scheduler-cron в schedulerName.
  2. В параметъра lockObjectName също трябва да зададем името на нашата услуга и да се уверим, че параметърът leaderElect е зададен на true (ако имате само една мастер-нода, можете да зададете false).
  3. Зададохме пътя към файла с описание на политиките за планиране в параметъра algorithmSource.

Трябва да се спрем по-подробно на втория пункт, където редактираме параметрите за ключа leaderElection. За да осигурим отказоустойчивост, активирахме (leaderElect) процеса на избор на лидер (мастера) между подовете на нашия kube-scheduler с помощта на единен за тях endpoint (resourceLock) с име kube-scheduler-cron (lockObjectName) в пространства с име kube-system (lockObjectNamespace). Как в Kubernetes се осигурява висока наличност на основни компоненти (включително kube-scheduler), можете да се запознаете с статията.

  • Файлът с политиките за планиране (scheduler-custom-policy-config.json)
    Както вече споменах, можем да разберем с какви точно политики работи дефолтният kube-scheduler, само анализирайки неговия код. Тоест, не можем да получим файл с политиките за планиране на дефолтния kube-scheduler по аналогия с конфигурационния файл. Ще опишем интересуващите ни политики за планиране в файла /etc/kubernetes/scheduler-custom-policy-config.json по следния начин:

{
  "kind": "Policy",
  "apiVersion": "v1",
  "predicates": [
    {
      "name": "GeneralPredicates"
    }
  ],
  "priorities": [
    {
      "name": "ServiceSpreadingPriority",
      "weight": 1
    },
    {
      "name": "EqualPriority",
      "weight": 1
    },
    {
      "name": "LeastRequestedPriority",
      "weight": 1
    },
    {
      "name": "NodePreferAvoidPodsPriority",
      "weight": 10000
    },
    {
      "name": "NodeAffinityPriority",
      "weight": 1
    }
  ],
  "hardPodAffinitySymmetricWeight" : 10,
  "alwaysCheckAllPredicates" : false
}

Така че, kube-scheduler първо съставя списък на нодовете, на които може да бъде планиран под, в съответствие с политиката GeneralPredicates (която включва набор от политики PodFitsResources, PodFitsHostPorts, HostName и MatchNodeSelector). След това се оценява всеки нод в съответствие с набор от политики в масива priorities. За да изпълним условията на нашата задача, считахме, че този набор от политики ще бъде оптимално решение. Напомням, че наборът от политики с подробно описание е наличен в документацията. За да изпълните задачата си, можете просто да промените набора от използвани политики и да им зададете съответните тегла.

Манифестът на новия kube-scheduler, който създадохме в началото на главата, ще наречем kube-scheduler-custom.yaml и ще го разположим на следния път /etc/kubernetes/manifests на трите мастер нода. Ако всичко е изпълнено правилно, Kubelet на всеки нод ще стартира под, а в логовете на нашия нов kube-scheduler ще видим информация, че нашият файл с политики е приложен успешно:

Създаване на планировчик от конфигурация: {{ } [{GeneralPredicates } ] [{ServiceSpreadingPriority 1 } {EqualPriority 1 } {LeastRequestedPriority 1 } {NodePreferAvoidPodsPriority 10000 } {NodeAffinityPriority 1 } ] [] 10 false}
Регистриране на предикат: GeneralPredicates
Тип на предикат GeneralPredicates вече е регистриран, повторно използване.
Регистриране на приоритет: ServiceSpreadingPriority
Тип на приоритет ServiceSpreadingPriority вече е регистриран, повторно използване.
Регистриране на приоритет: EqualPriority
Тип на приоритет EqualPriority вече е регистриран, повторно използване.
Регистриране на приоритет: LeastRequestedPriority
Тип на приоритет LeastRequestedPriority вече е регистриран, повторно използване.
Регистриране на приоритет: NodePreferAvoidPodsPriority
Тип на приоритет NodePreferAvoidPodsPriority вече е регистриран, повторно използване.
Регистриране на приоритет: NodeAffinityPriority
Тип на приоритет NodeAffinityPriority вече е регистриран, повторно използване.
Създаване на планировчик с предикати 'map[GeneralPredicates:{}]' и функции за приоритет 'map[EqualPriority:{} LeastRequestedPriority:{} NodeAffinityPriority:{} NodePreferAvoidPodsPriority:{} ServiceSpreadingPriority:{}]'

Сега остава само да укажем в спецификацията на нашата CronJob, че всички заявки за планиране на нейния под трябва да се обработват от нашия нов kube-scheduler:

...
 jobTemplate:
    spec:
      template:
        spec:
          schedulerName: kube-scheduler-cron
...

Заключение

В крайна сметка получихме допълнителен kube-scheduler с уникален набор от политики за планиране, за работата на който следи директно kubelet. Освен това настроихме избор на нов лидер между подовете на нашия kube-scheduler в случай, че старият лидер по някаква причина стане недостъпен.

Обикновените приложения и услуги продължават да се планират чрез подразбиращия се kube-scheduler, а всички cron-задачи са напълно прехвърлени на новия. Натоварването, създавано от cron-задачите, сега се разпределя равномерно между всички нодове. Като се има предвид, че по-голямата част от cron-задачите се изпълняват на същите нодове, на които работят основните приложения на проекта, това значително намали риска от преместване на подовете заради недостатъчни ресурси. След въвеждането на допълнителен kube-scheduler, проблеми с неравномерното планиране на cron-задачите вече не възникваха.

Също така прочетете други статии в нашия блог:

Източник: habr.com

Купете надежден хостинг за сайтове с защита от DDoS, VPS VDS сървъри 🔥 Купете надежден хостинг за сайтове с защита от DDoS, VPS VDS сървъри | ProHoster