Tworzenie dodatkowego kube-scheduler’a z niestandardowym zestawem zasad harmonogramowania

Tworzenie dodatkowego kube-scheduler’a z niestandardowym zestawem zasad harmonogramowania

Kube-scheduler jest nieodłącznym składnikiem Kubernetes, który odpowiada za planowanie podów na nodach zgodnie z określonymi politykami. Często w trakcie eksploatacji klastra Kubernetes nie musimy zastanawiać się, według jakich polityk odbywa się planowanie podów, ponieważ zestaw polityk domyślnego kube-scheduler’a odpowiada większości codziennych zadań. Jednak są sytuacje, w których ważne jest, aby precyzyjnie zarządzać procesem przydzielania podów, a do realizacji tego zadania są dwa sposoby:

  1. Utworzyć kube-scheduler z dostosowanym zestawem reguł
  2. Napisanie własnego scheduler'a i nauczenie go pracy z zapytaniami API serwera

W ramach tego artykułu opiszę realizację pierwszego punktu w celu rozwiązania problemu nierównomiernego planowania podów w jednym z naszych projektów.

Krótki wstęp do działania kube-scheduler’a

Warto szczególnie podkreślić, że kube-scheduler nie odpowiada za bezpośrednie planowanie podów — jest odpowiedzialny jedynie za określenie nodu, na którym powinien znaleźć się pod. Innymi słowy, wynikiem pracy kube-scheduler’a jest nazwa nodu, którą zwraca API serwera na zapytanie o planowanie i na tym jego praca się kończy.

Najpierw kube-scheduler tworzy listę nodów, na które może być zaplanowany pod zgodnie z politykami predykatów. Następnie każda nod z tej listy otrzymuje określoną liczbę punktów zgodnie z politykami priorytetów. W efekcie wybierana jest nod, która zdobyła największą liczbę punktów. Jeśli są nodów, które zdobyły tę samą maksymalną liczbę punktów, wybierana jest losowo. Z listą i opisem polityk predykatów (filtrowanie) oraz priorytetów (punktacja) można zapoznać się w dokumentacji.

Opis problemu

Mimo że mamy do czynienia z wieloma różnymi klastrami Kubernetes w Nixys, po raz pierwszy z problemem planowania podów spotkaliśmy się dopiero niedawno, gdy w jednym z naszych projektów pojawiła się potrzeba uruchomienia dużej liczby okresowych zadań (~100 jednostek CronJob). Aby maksymalnie uprościć opis problemu, jako przykład weźmiemy jeden mikroserwis, w ramach którego co minutę uruchamiana jest zadanie cron, generujące pewne obciążenie CPU. Do realizacji zadania cron przydzielono trzy identyczne pod względem charakterystyki nody (24 vCPU na każdej).

Nie można dokładnie określić, ile czasu zajmie wykonanie zadania CronJob, ponieważ objętość danych wejściowych ciągle się zmienia. Średnio, przy normalnej pracy kube-scheduler'a, na każdej nodzie działa 3-4 instancje zadania, które generują ~20-30% obciążenia CPU każdej nody:

Tworzenie dodatkowego kube-scheduler’a z niestandardowym zestawem zasad harmonogramowania

Problem polega na tym, że czasami pody zadań cron przestawały być planowane na jednej z trzech nod. To znaczy, w pewnym momencie na jednej z nod nie planowano żadnego poda, podczas gdy na dwóch innych nodach działało po 6-8 instancji zadania, generując ~40-60% obciążenia CPU:

Tworzenie dodatkowego kube-scheduler’a z niestandardowym zestawem zasad harmonogramowania

Problem powtarzał się z absolutnie losową periodycznością i sporadycznie pokrywał się z momentem wdrożenia nowej wersji kodu.

Podnosząc poziom logowania kube-scheduler'a do poziomu 10 (-v=10), zaczęliśmy rejestrować, ile punktów zdobywa każda z nod w procesie oceny. Przy normalnej pracy planowania w logach można było zobaczyć następujące informacje:

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

Z tego, co widać na podstawie informacji uzyskanych z logów, każda z węzłów zdobyła równą liczbę punktów końcowych, a do planowania wybierano losowo. W chwili problematycznego planowania logi wyglądały następująco:

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

Z których wynika, że jeden z węzłów zdobył mniej punktów końcowych niż pozostałe, dlatego planowanie odbywało się tylko na dwóch węzłach, które zdobyły maksymalną liczbę punktów. W ten sposób upewniliśmy się, że problem leży właśnie w planowaniu podów.

Kolejny algorytm rozwiązania problemu był dla nas oczywisty – przeanalizować logi, zrozumieć, na jakim dokładnie priorytecie węzeł nie zdobył punktów i, w razie potrzeby, skorygować polityki domyślnego kube-schedulera. Jednak tutaj napotkaliśmy dwie istotne trudności:

  1. Na maksymalnym poziomie logowania (10) odzwierciedla się zestaw punktów tylko według niektórych priorytetów. W przytoczonym powyżej fragmencie logów można zauważyć, że we wszystkich priorytetach odzwierciedlonych w logach, węzły zdobywają tę samą liczbę punktów przy normalnym i problemowym planowaniu, jednak końcowy wynik w przypadku planowania problemowego się różni. W ten sposób można wyciągnąć wniosek, że według niektórych priorytetów punktacja odbywa się „poza kadrem”, i nie mamy możliwości zrozumienia, na jakim dokładnie priorytecie węzeł nie zdobył punktów. Problem ten szczegółowo opisaliśmy w issue repozytorium Kubernetes na Githubie. W momencie pisania artykułu otrzymaliśmy odpowiedź od deweloperów, że wsparcie dla logowania zostanie dodane w aktualizacjach Kubernetes v1.15, 1.16 i 1.17.
  2. Nie ma prostego sposobu, aby zrozumieć, z jakim dokładnie zestawem polityk obecnie działa kube-scheduler. Tak, w dokumentacji tej liście jest wymieniony, ale nie ma w niej informacji, jakie konkretny wagi są przypisane każdej z polityk priorites. Można zobaczyć wagi lub edytować polityki domyślnego kube-schedulera tylko w źródłach.

Warto zauważyć, że raz udało nam się ustalić, że węzeł nie zdobywał punktów według polityki ImageLocalityPriority, która przyznaje punkty węzłowi, jeśli na nim już znajduje się obraz potrzebny do uruchomienia aplikacji. To znaczy, w momencie wprowadzania nowej wersji aplikacji, zadanie cron zdążyło uruchomić się na dwóch węzłach, ściągając na nie nowy obraz z docker registry, a tym samym dwa węzły uzyskały większy wynik końcowy w porównaniu do trzeciego.

Jak już wspomniałem, w logach nie widzimy informacji na temat oceny polityki ImageLocalityPriority, dlatego aby sprawdzić moje przypuszczenie, przesłaliśmy obraz z nową wersją aplikacji na trzeci węzeł, po czym planowanie zaczęło działać poprawnie. Problem z planowaniem występował dość rzadko z powodu polityki ImageLocalityPriority, częściej był on związany z czymś innym. Z powodu braku możliwości pełnego debugowania każdej z polityk w liście priorites domyślnego kube-scheduler’a, pojawiła się potrzeba elastycznego zarządzania politykami planowania podów.

Sformułowanie zadania

Chcieliśmy, aby rozwiązanie problemu było jak najdokładniejsze, to znaczy podstawowe byty Kubernetes (tu chodzi o domyślnego kube-scheduler’a) powinny pozostać niezmienione. Nie chcieliśmy rozwiązywać problemów w jednym miejscu i tworzyć ich w innym. W ten sposób dotarliśmy do dwóch wariantów rozwiązania problemu, które zostały przedstawione we wprowadzeniu do artykułu — stworzenie dodatkowego scheduler’a lub napisanie własnego. Główne wymaganie dla planowania zadań cron to równomierne rozłożenie obciążenia na trzech węzłach. To wymaganie można zaspokoić już istniejącymi politykami kube-scheduler’a, dlatego nie ma sensu pisać własnego scheduler’a na potrzeby naszego zadania.

Instrukcje dotyczące tworzenia i wdrażania dodatkowego kube-scheduler’a opisane są w dokumentacji. Jednak wydaje nam się, że byty Deployment są niewystarczające do zapewnienia odporności na awarie w pracy tak krytycznej usługi jak kube-scheduler, dlatego postanowiliśmy wdrożyć nowy kube-scheduler jako Static Pod, którym bezpośrednio będzie zarządzać Kubelet. W związku z tym, mamy następujące wymagania wobec nowego kube-scheduler’a:

  1. Usługa musi być wdrożona jako Static Pod na wszystkich masterach klastra.
  2. Powinna być zapewniona odporność na wypadek niedostępności aktywnego podu z kube-schedulerem.
  3. Głównym priorytetem przy planowaniu powinno być ilość dostępnych zasobów na węźle (LeastRequestedPriority).

Implementacja rozwiązania.

Należy od razu zaznaczyć, że wszystkie prace będziemy przeprowadzać w Kubernetes v1.14.7, ponieważ ta wersja była używana w projekcie. Zacznijmy od napisania manifestu dla naszego nowego kube-scheduler’a. Jako podstawę weźmiemy manifest domyślny (\/etc\/kubernetes\/manifests\/kube-scheduler.yaml) i przekształcimy go do następującej postaci:

kind: Pod
metadata:
  labels:
    component: scheduler
    tier: control-plane
  name: kube-scheduler-cron
  namespace: kube-system
spec:
      containers:
      - command:
        - /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
        image: gcr.io/google-containers/kube-scheduler:v1.14.7
        imagePullPolicy: IfNotPresent
        livenessProbe:
          failureThreshold: 8
          httpGet:
            host: 127.0.0.1
            path: /healthz
            port: 10151
            scheme: HTTP
          initialDelaySeconds: 15
          timeoutSeconds: 15
        name: kube-scheduler-cron-container
        resources:
          requests:
            cpu: '0.1'
        volumeMounts:
        - mountPath: /etc/kubernetes/scheduler.conf
          name: kube-config
          readOnly: true
        - mountPath: /etc/localtime
          name: localtime
          readOnly: true
        - mountPath: /etc/kubernetes/scheduler-custom.conf
          name: scheduler-config
          readOnly: true
        - mountPath: /etc/kubernetes/scheduler-custom-policy-config.json
          name: policy-config
          readOnly: true
      hostNetwork: true
      priorityClassName: system-cluster-critical
      volumes:
      - hostPath:
          path: /etc/kubernetes/scheduler.conf
          type: FileOrCreate
        name: kube-config
      - hostPath:
          path: /etc/localtime
        name: localtime
      - hostPath:
          path: /etc/kubernetes/scheduler-custom.conf
          type: FileOrCreate
        name: scheduler-config
      - hostPath:
          path: /etc/kubernetes/scheduler-custom-policy-config.json
          type: FileOrCreate
        name: policy-config

Krótki przegląd głównych zmian:

  1. Zmieniliśmy nazwę podu i kontenera na kube-scheduler-cron
  2. Określiliśmy użycie portów 10151 i 10159, ponieważ zdefiniowano opcję hostNetwork: true i nie możemy używać tych samych portów, co domyślny kube-scheduler (10251 i 10259)
  3. Za pomocą parametru —config wskazaliśmy plik konfiguracyjny, z którego ma być uruchamiany serwis
  4. Skonfigurowaliśmy montowanie pliku konfiguracyjnego (scheduler-custom.conf) oraz pliku polityk planowania (scheduler-custom-policy-config.json) z hosta

Nie zapominajmy, że nasz kube-scheduler będzie wymagał uprawnień podobnych do domyślnych. Edytujemy jego rolę klastra:

kubectl edit clusterrole system:kube-scheduler

...
   resourceNames:
    - kube-scheduler
    - kube-scheduler-cron
...

Teraz porozmawiajmy o tym, co powinno znajdować się w pliku konfiguracyjnym i w pliku z politykami planowania:

  • Plik konfiguracyjny (scheduler-custom.conf)
    Aby uzyskać konfigurację domyślnego kube-scheduler’a, należy skorzystać z parametru --write-config-to z dokumentacji. Otrzymaną konfigurację umieścimy w pliku /etc/kubernetes/scheduler-custom.conf i przekształcimy w następujący sposób:

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"

Krótki przegląd głównych zmian:

  1. W polu schedulerName podano nazwę naszego serwisu kube-scheduler-cron.
  2. W parametrze lockObjectName również należy podać nazwę naszego serwisu i upewnić się, że parametr leaderElect jest ustawiony na true (w przypadku, gdy mamy jedną główną nodę, można ustawić wartość false).
  3. Podano ścieżkę do pliku z opisem polityk harmonogramowania w parametrze algorithmSource.

Warto dokładniej omówić drugi punkt, w którym edytujemy parametry dla klucza leaderElection. Aby zapewnić odporność na błędy, aktywowaliśmy (leaderElect) proces wyboru lidera (mastera) pomiędzy podami naszego kube-scheduler’a, wykorzystując wspólny dla nich endpoint (resourceLock) o nazwie kube-scheduler-cron (lockObjectName) w przestrzeni nazw kube-system (lockObjectNamespace). O tym, jak w Kubernetes zapewnia się wysoką dostępność kluczowych komponentów (w tym kube-scheduler), można przeczytać w artykuł.

  • Plik polityk harmonogramowania (scheduler-custom-policy-config.json)
    Jak już wcześniej pisałem — aby dowiedzieć się, z jakimi konkretnymi politykami działa domyślny kube-scheduler, możemy to zrobić tylko analizując jego kod. Nie możemy uzyskać pliku z politykami harmonogramowania domyślnego kube-scheduler’a, w analogii do pliku konfiguracyjnego. Opiszemy interesujące nas polityki harmonogramowania w pliku /etc/kubernetes/scheduler-custom-policy-config.json w następujący sposób:

{
  "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
}

W ten sposób kube-scheduler najpierw tworzy listę węzłów, na których możliwe jest zaplanowanie poda zgodnie z polityką GeneralPredicates (która obejmuje zestaw polityk PodFitsResources, PodFitsHostPorts, HostName i MatchNodeSelector). Następnie ocenia każdy węzeł według zestawu polityk w tablicy priorytetów. Aby spełnić wymogi naszego zadania, uznaliśmy, że taki zestaw polityk będzie optymalnym rozwiązaniem. Przypominam, że zestaw polityk z ich szczegółowym opisem jest dostępny w dokumentacji. Aby wykonać swoje zadanie, możesz po prostu zmienić zestaw używanych polityk i przypisać im odpowiednie wagi.

Manifest nowego kube-scheduler’a, który tworzyliśmy na początku rozdziału, nazwiemy kube-scheduler-custom.yaml i umieścimy w następującej lokalizacji /etc/kubernetes/manifests na trzech węzłach-mistrzach. Jeśli wszystko zostanie wykonane poprawnie, Kubelet na każdym węźle uruchomi pod, a w logach naszego nowego kube-scheduler’a zobaczymy informacje, że nasz plik z politykami został pomyślnie zastosowany:

Tworzenie scheduler’a z konfiguracji: {{ } [{GeneralPredicates } ] [{ServiceSpreadingPriority 1 } {EqualPriority 1 } {LeastRequestedPriority 1 } {NodePreferAvoidPodsPriority 10000 } {NodeAffinityPriority 1 } ] [] 10 false}
Rejestracja predykatu: GeneralPredicates
Typ predykatu GeneralPredicates już zarejestrowany, ponowne użycie.
Rejestracja priorytetu: ServiceSpreadingPriority
Typ priorytetu ServiceSpreadingPriority już zarejestrowany, ponowne użycie.
Rejestracja priorytetu: EqualPriority
Typ priorytetu EqualPriority już zarejestrowany, ponowne użycie.
Rejestracja priorytetu: LeastRequestedPriority
Typ priorytetu LeastRequestedPriority już zarejestrowany, ponowne użycie.
Rejestracja priorytetu: NodePreferAvoidPodsPriority
Typ priorytetu NodePreferAvoidPodsPriority już zarejestrowany, ponowne użycie.
Rejestracja priorytetu: NodeAffinityPriority
Typ priorytetu NodeAffinityPriority już zarejestrowany, ponowne użycie.
Tworzenie scheduler’a z predykatami dopasowania 'map[GeneralPredicates:{}]' oraz funkcjami priorytetowymi 'map[EqualPriority:{} LeastRequestedPriority:{} NodeAffinityPriority:{} NodePreferAvoidPodsPriority:{} ServiceSpreadingPriority:{}]'

Teraz pozostaje tylko wskazać w specyfikacji naszej CronJob’y, że wszystkie żądania planowania jej podów powinien obsługiwać nasz nowy kube-scheduler:

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

Podsumowanie

Ostatecznie otrzymaliśmy dodatkowego kube-scheduler’a z unikalnym zestawem polityk planowania, nad którym bezpośrednio czuwa kubelet. Ponadto skonfigurowaliśmy wybory nowego lidera między podami naszego kube-scheduler’a na wypadek, gdyby stary lider z jakiegoś powodu stał się niedostępny.

Zwykłe aplikacje i usługi są nadal planowane za pomocą domyślnego kube-scheduler, podczas gdy wszystkie zadania cron zostały w pełni przeniesione na nowy system. Obciążenie generowane przez zadania cron jest teraz równomiernie rozłożone na wszystkie węzły. Biorąc pod uwagę, że większość zadań cron jest wykonywana na tych samych węzłach, co główne aplikacje projektu, znacznie zmniejszyło to ryzyko przenoszenia podów z powodu braku zasobów. Po wdrożeniu dodatkowego kube-scheduler’a, problemy z nierównym planowaniem zadań cron już się nie pojawiały.

Przeczytaj również inne artykuły na naszym blogu:

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster