További kube-ütemező létrehozása egyéni ütemezési szabályokkal
A Kube-ütemező a Kubernetes szerves összetevője, amely a csomópontok közötti ütemezésért felelős a meghatározott házirendeknek megfelelően. A Kubernetes-fürt működése során gyakran nem kell gondolkodnunk azon, hogy pontosan milyen házirendekkel ütemezzük a pod-okat, mivel az alapértelmezett kube-ütemező házirendkészlete alkalmas a legtöbb mindennapi feladatra. Vannak azonban olyan helyzetek, amikor fontos számunkra a hüvelyek elosztásának finomhangolása, és ezt a feladatot kétféleképpen lehet elvégezni:
Hozzon létre kube-ütemezőt egyéni szabálykészlettel
Írja meg saját ütemezőjét, és tanítsa meg az API-kiszolgálókérésekkel való együttműködésre
Ebben a cikkben pontosan az első pont végrehajtását fogom leírni, amely megoldja az egyik projektünkben a podok egyenetlen ütemezésének problémáját.
Rövid bevezető a kube-scheduler'a munkájáról
Érdemes megjegyezni azt a tényt, hogy a kube-scheduler nem felelős közvetlenül a pod-ok ütemezéséért, hanem csak azért, hogy meghatározza azt a csomópontot, amelyre a podokat el kell helyezni. Más szóval, a kube-scheduler munkájának eredménye annak a csomópontnak a neve, amelyet az ütemezési kéréshez visszaküld az API-kiszolgálónak, és itt ér véget a munkája.
Először is, a kube-ütemező felsorolja azokat a csomópontokat, amelyekre egy pod ütemezhető a predikátum házirendek szerint. Ezen túlmenően a listából minden egyes csomópont bizonyos számú pontot kap a prioritások irányelveinek megfelelően. Ennek eredményeként a legmagasabb pontszámot elért csomópont kerül kiválasztásra. Ha vannak olyan csomópontok, amelyek maximális pontszáma megegyezik, akkor a rendszer véletlenszerűen választ ki. A predikátumok (szűrés) és prioritások (pontozás) házirendek listája és leírása itt található. dokumentáció.
A problémás test leírása
A Nixysben karbantartott nagyszámú Kubernetes-fürt ellenére először csak nemrégiben találkoztunk a pod-ok ütemezésének problémájával, amikor egyik projektünknél nagyszámú időszakos feladat (~ 100 CronJob entitás) futtatása vált szükségessé. Hogy a probléma leírását a lehető legegyszerűsítsük, példaként vegyünk egy mikroszolgáltatást, amelyen belül percenként egyszer elindul egy cron feladat, ami némi terhelést jelent a CPU-n. Három teljesen azonos csomópontot (mindegyiken 24 vCPU) osztottak ki a cron feladat elvégzéséhez.
Ugyanakkor nem lehet pontosan megmondani, hogy mennyi ideig fog futni a CronJob, mivel a bemeneti adatok mennyisége folyamatosan változik. Átlagosan a kube ütemező normál működése során minden csomópont 3-4 feladatpéldányt futtat, amelyek az egyes csomópontok CPU-jának terhelésének ~ 20-30%-át teszik ki:
Maga a probléma az, hogy a cron feladatsorok időnként leálltak ütemezni a három csomópont valamelyikére. Ez azt jelenti, hogy valamikor egyetlen pod sem volt betervezve az egyik csomópontra, miközben 6-8 feladatpéldány futott a másik két csomóponton, ami a CPU terhelésének ~ 40-60%-át jelentette:
A probléma teljesen véletlenszerű gyakorisággal ismétlődött, és időnként korrelált a kód új verziójának kibocsátásának pillanatával.
A kube-ütemező naplózási szintjének 10-es szintre emelésével (-v=10) elkezdtük rögzíteni, hogy az egyes csomópontok hány pontot szereznek az értékelési folyamatban. A normál ütemezés során a következő információk láthatók a naplókban:
Azok. A naplókból nyert információk alapján mindegyik csomópont azonos számú végső pontot ért el, és egy véletlenszerűt választottunk a tervezéshez. A probléma tervezése idején a naplók így néztek ki:
Ebből látható, hogy az egyik csomópont kevesebb összpontot ért el, mint a többi, ezért csak két maximális pontszámot elért csomópontra terveztünk. Így biztosak voltunk abban, hogy a probléma éppen a hüvelyek tervezésében rejlik.
A probléma megoldásának további algoritmusa nyilvánvaló volt számunkra - elemezzük a naplókat, megértsük, milyen prioritás mellett nem kapott pontot a csomópont, és ha szükséges, módosítsuk az alapértelmezett kube-ütemező házirendjeit. Itt azonban két jelentős nehézséggel kell szembenéznünk:
A maximális naplózási szint (10) csak bizonyos prioritások pontkészletét tükrözi. A fenti naplórészletben látható, hogy a naplókban megjelenített összes prioritás esetén a csomópontok ugyanannyi pontot érnek el normál és problémás ütemezésben, de problémaütemezés esetén más a végeredmény. Ebből arra következtethetünk, hogy bizonyos prioritásoknál a pontozás „a színfalak mögött” zajlik, és nem tudjuk megérteni, hogy a csomópont melyik prioritásért nem kapott pontot. Ezt a problémát részletesen leírtuk a kérdés Kubernetes adattár a Githubon. A cikk írásakor az a válasz érkezett a fejlesztőktől, hogy a Kubernetes v1.15,1.16, 1.17 és XNUMX frissítéseihez naplózási támogatást adnak.
Nem könnyű megérteni, hogy a kube-scheduler jelenleg melyik házirend-készlettel dolgozik. Igen, be dokumentáció ez a lista fel van sorolva, de nem tartalmaz információt arról, hogy milyen súlyok vannak beállítva az egyes prioritási irányelvekhez. Csak az alapértelmezett kube-ütemező súlyait láthatja, vagy módosíthatja a házirendeket források.
Érdemes megjegyezni, hogy miután sikerült kijavítanunk, hogy a csomópont nem kapott pontot az ImageLocalityPriority szabályzat szerint, amely pontokat ad a csomópontnak, ha már rendelkezik az alkalmazás futtatásához szükséges képpel. Azaz az alkalmazás új verziójának kibocsátásakor a cron feladatnak volt ideje lefutni két csomóponton, új lemezképet letölteni a docker registryből, és így két csomópont magasabb végső pontszámot kapott a harmadik.
Ahogy fentebb is írtam, a naplókban nem látunk információt az ImageLocalityPriority házirend kiértékeléséről, ezért feltételezésünk ellenőrzése érdekében a képet az alkalmazás új verziójával a harmadik csomópontra spooloztuk, ami után a tervezés megfelelően működött. Éppen az ImageLocalityPriority házirend miatt volt elég ritkán észlelhető az ütemezési probléma, gyakrabban kapcsolódott máshoz. Tekintettel arra, hogy az alapértelmezett kube-ütemező prioritásainak listájában nem tudtuk teljes mértékben hibakeresni az egyes házirendeket, szükségünk volt a pod ütemezési házirendek rugalmas kezelésére.
Probléma nyilatkozat
Azt akartuk, hogy a probléma megoldása a lehető legcélzottabb legyen, vagyis a Kubernetes fő entitásai (itt az alapértelmezett kube-ütemezőre gondolunk) változatlanok maradjanak. Nem akartunk egy problémát az egyik helyen megoldani, és a másik helyen létrehozni. Így két lehetőséghez jutottunk a probléma megoldására, amelyeket a cikk bevezetőjében közöltek - további ütemező létrehozása vagy saját írása. A cron feladatok ütemezésének fő követelménye a terhelés egyenletes elosztása a három csomópont között. Ezt a követelményt a meglévő kube-ütemező házirendek teljesíthetik, így nincs értelme saját ütemezőt írni a feladatunkhoz.
A további kube-ütemező létrehozására és telepítésére vonatkozó utasítások leírása a következő helyen található: dokumentáció. Úgy tűnt azonban számunkra, hogy a Deployment entitás nem elegendő a hibatűrés biztosításához egy olyan kritikus szolgáltatás működésében, mint a kube-ütemező, ezért úgy döntöttünk, hogy az új kube-ütemezőt Static Podként telepítjük, amelyet a Kubelet közvetlenül felügyel. . Így a következő követelmények vannak az új kube-ütemezővel szemben:
A szolgáltatást statikus podként kell üzembe helyezni az összes fürt főkiszolgálón
Feladatátvételt kell biztosítani arra az esetre, ha a kube-ütemezővel rendelkező aktív pod nem elérhető
A tervezés során a fő prioritás a csomóponton elérhető erőforrások mennyisége legyen (LeastRequestedPriority)
Megvalósítási megoldások
Azonnal meg kell jegyezni, hogy minden munkát a Kubernetes v1.14.7 verzióban fogunk elvégezni, mert ezt a verziót használták a projektben. Kezdjük azzal, hogy megírjuk az új kube-ütemezőnket. Vegyük az alapértelmezett jegyzéket (/etc/kubernetes/manifests/kube-scheduler.yaml), és állítsuk be a következő formába:
A pod és a tároló neve módosítva a következőre: kube-scheduler-cron
Az 10151-es és 10159-es portok használatára van megadva, ahogy az opció meghatározott hostNetwork: true és nem használhatjuk ugyanazokat a portokat, mint az alapértelmezett kube-ütemező (10251 és 10259)
A --config paraméterrel adja meg azt a konfigurációs fájlt, amellyel a szolgáltatásnak el kell indulnia
Úgy konfigurálva, hogy a konfigurációs fájlt (scheduler-custom.conf) és az ütemezési házirendfájlt (scheduler-custom-policy-config.json) csatolja a gazdagépről
Ne felejtsük el, hogy a kube-ütemezőnknek az alapértelmezetthez hasonló jogokra lesz szüksége. Szerkessze a fürt szerepét:
Most beszéljünk arról, hogy mit kell tartalmaznia a konfigurációs fájlnak és az ütemezési szabályzatfájlnak:
Konfigurációs fájl (scheduler-custom.conf)
Az alapértelmezett kube-ütemező konfigurációjának lekéréséhez a paramétert kell használnia --write-config-to A dokumentáció. Az eredményül kapott konfigurációt az /etc/kubernetes/scheduler-custom.conf fájlba helyezzük, és a következő formába hozzuk:
Állítsa be a schedulerName paramétert szolgáltatásunk kube-scheduler-cron nevére.
A paraméterben lockObjectName be kell állítanunk a szolgáltatásunk nevét is, és meg kell győződnünk arról, hogy a paraméter leaderElect állítsa igazra (ha egy főcsomópontja van, az értéket hamisra állíthatja).
Megadta a fájl elérési útját az ütemezési házirendek leírásával a paraméterben algorithmSource.
Érdemes részletesebben elidőzni a második bekezdésnél, ahol a kulcs paramétereit szerkesztjük leaderElection. A hibatűrés érdekében engedélyeztük (leaderElect) a vezető (master) kiválasztásának folyamata a kube-ütemezőnk podjai között egyetlen végpont használatával (resourceLock) nevű kube-scheduler-cron (lockObjectName) a kube-rendszer névtérben (lockObjectNamespace). Hogyan biztosítja a Kubernetes a fő összetevők (beleértve a kube-ütemezőt is) magas rendelkezésre állását, megtudhatja cikk.
Ütemezési házirend fájl (scheduler-custom-policy-config.json)
Mint korábban írtam, csak a kódját elemezve tudjuk meg, hogy az alapértelmezett kube-ütemező milyen konkrét házirendekkel működik. Ez azt jelenti, hogy a konfigurációs fájl analógiájára nem tudjuk beszerezni az alapértelmezett kube-scheduler ütemezési házirendjeit tartalmazó fájlt. Leírjuk a számunkra érdekes ütemezési házirendeket az /etc/kubernetes/scheduler-custom-policy-config.json fájlban az alábbiak szerint:
Tehát a kube-ütemező először felsorolja azokat a csomópontokat, amelyekre a Pod ütemezhető a GeneralPredicates házirendnek megfelelően (amely tartalmazza a PodFitsResources, PodFitsHostPorts, HostName és MatchNodeSelector házirendkészletet). Ezután minden csomópont kiértékelésre kerül a prioritási tömb házirendjei szerint. Feladatunk feltételeinek teljesítésére úgy gondoltuk, hogy egy ilyen szabályzat lenne a legjobb megoldás. Hadd emlékeztesselek arra, hogy egy házirend-készlet a részletes leírásukkal a következő helyen érhető el dokumentáció. A feladat elvégzéséhez egyszerűen módosíthatja a használt házirendek készletét, és megfelelő súlyokat rendelhet hozzájuk.
A fejezet elején létrehozott új kube-scheduler manifest kube-scheduler-custom.yaml lesz, és a /etc/kubernetes/manifests mappába kerül a három fő csomóponton. Ha minden megfelelően történik, a Kubelet elindítja a pod-ot minden csomóponton, és az új kube-ütemezőnk naplóiban látni fogjuk, hogy a házirendfájlunk sikeresen alkalmazásra került:
Creating scheduler from configuration: {{ } [{GeneralPredicates <nil>}] [{ServiceSpreadingPriority 1 <nil>} {EqualPriority 1 <nil>} {LeastRequestedPriority 1 <nil>} {NodePreferAvoidPodsPriority 10000 <nil>} {NodeAffinityPriority 1 <nil>}] [] 10 false}
Registering predicate: GeneralPredicates
Predicate type GeneralPredicates already registered, reusing.
Registering priority: ServiceSpreadingPriority
Priority type ServiceSpreadingPriority already registered, reusing.
Registering priority: EqualPriority
Priority type EqualPriority already registered, reusing.
Registering priority: LeastRequestedPriority
Priority type LeastRequestedPriority already registered, reusing.
Registering priority: NodePreferAvoidPodsPriority
Priority type NodePreferAvoidPodsPriority already registered, reusing.
Registering priority: NodeAffinityPriority
Priority type NodeAffinityPriority already registered, reusing.
Creating scheduler with fit predicates 'map[GeneralPredicates:{}]' and priority functions 'map[EqualPriority:{} LeastRequestedPriority:{} NodeAffinityPriority:{} NodePreferAvoidPodsPriority:{} ServiceSpreadingPriority:{}]'
Most már csak azt kell jelezni a CronJob specifikációjában, hogy a pod-ok ütemezésére vonatkozó összes kérést az új kube-ütemezőnknek kell feldolgoznia:
Végül kaptunk egy további kube-ütemezőt egyedi ütemezési házirendekkel, amelyet közvetlenül a kubelet figyel. Emellett a kube-ütemezőnk között beállítottuk az új vezető választását arra az esetre, ha a régi vezető valamilyen okból elérhetetlenné válik.
A normál alkalmazások és szolgáltatások továbbra is az alapértelmezett kube-ütemezőn keresztül ütemezhetők, és az összes cron-feladat teljesen átkerült az újba. A cron feladatok által létrehozott terhelés most egyenletesen oszlik el az összes csomópont között. Figyelembe véve, hogy a cron feladatok többsége ugyanazokon a csomópontokon történik, mint a projekt fő alkalmazásai, ez jelentősen csökkentette a pod-ok erőforráshiány miatti elmozdulásának kockázatát. A további kube-ütemező bevezetése után nem volt többé probléma a cron feladatok egyenetlen ütemezésével.