Весь код в этом разделе основан на версии 1.13.4.
Процесс загрузки
Как упоминалось ранее в анализе контроллер-менеджер, контроллер имеет в основном один и тот же формат управления для каждого контроллера, который имеет видstart***Controller
Способ инкапсулировать его в независимый метод, NodeController не является исключением. В версии 1.13.4 контроллеры Node разделены на два типа (в более ранних версиях был только один), а именноNodeIpamControllerа такжеNodeLifecycleController. Среди них NodeIpamController в основном имеет дело с IPAM-адресом Node, а NodeLifecycleController — со всем жизненным циклом Node.В этой статье в основном анализируется NodeLifecycleController.
startNodeLifecycleController
Метод начинает процесс управления его жизненным циклом. В основном сосредоточьтесь на двух методах:NewNodeLifecycleControllerа такжеRun. NewNodeLifecycleController отвечает за создание объектов ресурсов, а Run отвечает за запуск и завершение выполнения задач.
Анализ NewNodeLifecycleController
NewNodeLifecycleController в основном выполняет следующие задачи:
1. Построить большую структуру контроллера в соответствии с заданной конфигурацией и выполнить задачи по настройке некоторых параметров;
2. ДляpodInformer
,nodeInformer
,leaseInformer
так же какdaemonSetInformer
Настройте соответствующие методы обратного вызова, включаяAddFunc
,UpdateFunc
так же какDeleteFunc
. Таким образом, при изменении соответствующего узла соответствующий контроллер может вовремя отслеживать его и вызывать соответствующий метод обработки;
3. Вернуть построенную структуру.
Есть несколько переменных, на которые необходимо обратить внимание при настройке, которые будут часто использоваться позже.
useTaintBasedEvictions: добавив в узел
TaintNodeNotReady
а такжеTaintNodeUnreachable
Метод taint заменяет предыдущий метод прямого исключения модулей, который удаляет модули посредством управления потоком. Главным образом для предотвращения внезапного выселения подов в большом количестве в определенный момент времени;taintNodeByCondition: добавить соответствующую заразу в узел через состояние узла.
К тому же, в отличие от предыдущей версии, добавлен LeezeInformer, основная функция которого — судить о работоспособности Node.
Выполнить анализ метода
Метод Run в основном включает в себя следующие методы, каждый из которых запускается как отдельная горутина:
1,go nc.taintManager.Run(stopCh)
: TaintManager, который в основном выполняет задачу выселения пода;
2,doNoScheduleTaintingPassWorker
: выполнить задачу обновления taint в NoSchedule;
3.doNoExecuteTaintingPass
,doEvictionPass
: выполнить задачу обновления испорченного кода NoExecute;
4.monitorNodeHealth
: проверка состояния узла и выполнение таких задач, как добавление, удаление, изменение и проверка узла, а также обработка вытеснения пода.
код показывает, как показано ниже
// Run starts an asynchronous loop that monitors the status of cluster nodes.
func (nc *Controller) Run(stopCh <-chan struct{}) {
defer utilruntime.HandleCrash()
klog.Infof("Starting node controller")
defer klog.Infof("Shutting down node controller")
if !controller.WaitForCacheSync("taint", stopCh, nc.leaseInformerSynced, nc.nodeInformerSynced, nc.podInformerSynced, nc.daemonSetInformerSynced) {
return
}
if nc.runTaintManager {
go nc.taintManager.Run(stopCh)
}
if nc.taintNodeByCondition {
// Close node update queue to cleanup go routine.
defer nc.nodeUpdateQueue.ShutDown()
// Start workers to update NoSchedule taint for nodes.
for i := 0; i < scheduler.UpdateWorkerSize; i++ {
// Thanks to "workqueue", each worker just need to get item from queue, because
// the item is flagged when got from queue: if new event come, the new item will
// be re-queued until "Done", so no more than one worker handle the same item and
// no event missed.
go wait.Until(nc.doNoScheduleTaintingPassWorker, time.Second, stopCh)
}
}
if nc.useTaintBasedEvictions {
// Handling taint based evictions. Because we don't want a dedicated logic in TaintManager for NC-originated
// taints and we normally don't rate limit evictions caused by taints, we need to rate limit adding taints.
go wait.Until(nc.doNoExecuteTaintingPass, scheduler.NodeEvictionPeriod, stopCh)
} else {
// Managing eviction of nodes:
// When we delete pods off a node, if the node was not empty at the time we then
// queue an eviction watcher. If we hit an error, retry deletion.
go wait.Until(nc.doEvictionPass, scheduler.NodeEvictionPeriod, stopCh)
}
// Incorporate the results of node health signal pushed from kubelet to master.
go wait.Until(func() {
if err := nc.monitorNodeHealth(); err != nil {
klog.Errorf("Error monitoring node health: %v", err)
}
}, nc.nodeMonitorPeriod, stopCh)
<-stopCh
}
Процесс реализации
Процесс выполнения NodeLifecycleController — это в основном задачи, соответствующие каждой горутине, которые анализируются одна за другой.
TaintManager
TaintManager запускается методом Run. В методе Run в основном выполняются несколько задач:
1. ИнициализацияnodeUpdateChannels
а такжеpodUpdateChannels
, размер 8 каналов, которые потом можно обрабатывать параллельно;
2. Запустите две горутины для мониторинга сообщений nodeUpdateQueue и podUpdateQueue соответственно;
3. Запустить 8 рабочих задач параллельно для обработки отслеживаемых сообщений nodeUpdate и podUpdate.
// Run starts NoExecuteTaintManager which will run in loop until `stopCh` is closed.
func (tc *NoExecuteTaintManager) Run(stopCh <-chan struct{}) {
klog.V(0).Infof("Starting NoExecuteTaintManager")
for i := 0; i < UpdateWorkerSize; i++ {
tc.nodeUpdateChannels = append(tc.nodeUpdateChannels, make(chan nodeUpdateItem, NodeUpdateChannelSize))
tc.podUpdateChannels = append(tc.podUpdateChannels, make(chan podUpdateItem, podUpdateChannelSize))
}
// Functions that are responsible for taking work items out of the workqueues and putting them
// into channels.
go func(stopCh <-chan struct{}) {
for {
item, shutdown := tc.nodeUpdateQueue.Get()
if shutdown {
break
}
nodeUpdate := item.(nodeUpdateItem)
hash := hash(nodeUpdate.nodeName, UpdateWorkerSize)
select {
case <-stopCh:
tc.nodeUpdateQueue.Done(item)
return
case tc.nodeUpdateChannels[hash] <- nodeUpdate:
// tc.nodeUpdateQueue.Done is called by the nodeUpdateChannels worker
}
}
}(stopCh)
go func(stopCh <-chan struct{}) {
for {
item, shutdown := tc.podUpdateQueue.Get()
if shutdown {
break
}
podUpdate := item.(podUpdateItem)
hash := hash(podUpdate.nodeName, UpdateWorkerSize)
select {
case <-stopCh:
tc.podUpdateQueue.Done(item)
return
case tc.podUpdateChannels[hash] <- podUpdate:
// tc.podUpdateQueue.Done is called by the podUpdateChannels worker
}
}
}(stopCh)
wg := sync.WaitGroup{}
wg.Add(UpdateWorkerSize)
for i := 0; i < UpdateWorkerSize; i++ {
go tc.worker(i, wg.Done, stopCh)
}
wg.Wait()
}
В рабочих задачах, запущенных параллельно, событие nodeUpdate обрабатывается первым, а событие podUpdate обрабатывается после завершения обработки nodeUpdate. Соответствие методу обработки nodeUpdatehandleNodeUpdate
, соответствующий podUpdatehandlePodUpdate
.
handleNodeUpdate
Основная функция состоит в том, чтобы получить информацию об узле через отслеживаемое имя узла и получить соответствующие сведения об узле с помощью информации об узле. Затем последовательно выполняются все поды на узле.processPodOnNode
метод. Методы, как показано ниже:
func (tc *NoExecuteTaintManager) handleNodeUpdate(nodeUpdate nodeUpdateItem) {
node, err := tc.getNode(nodeUpdate.nodeName)
if err != nil {
if apierrors.IsNotFound(err) {
// Delete
klog.V(4).Infof("Noticed node deletion: %#v", nodeUpdate.nodeName)
tc.taintedNodesLock.Lock()
defer tc.taintedNodesLock.Unlock()
delete(tc.taintedNodes, nodeUpdate.nodeName)
return
}
utilruntime.HandleError(fmt.Errorf("cannot get node %s: %v", nodeUpdate.nodeName, err))
return
}
// Create or Update
klog.V(4).Infof("Noticed node update: %#v", nodeUpdate)
taints := getNoExecuteTaints(node.Spec.Taints)
func() {
tc.taintedNodesLock.Lock()
defer tc.taintedNodesLock.Unlock()
klog.V(4).Infof("Updating known taints on node %v: %v", node.Name, taints)
if len(taints) == 0 {
delete(tc.taintedNodes, node.Name)
} else {
tc.taintedNodes[node.Name] = taints
}
}()
pods, err := getPodsAssignedToNode(tc.client, node.Name)
if err != nil {
klog.Errorf(err.Error())
return
}
if len(pods) == 0 {
return
}
// Short circuit, to make this controller a bit faster.
if len(taints) == 0 {
klog.V(4).Infof("All taints were removed from the Node %v. Cancelling all evictions...", node.Name)
for i := range pods {
tc.cancelWorkWithEvent(types.NamespacedName{Namespace: pods[i].Namespace, Name: pods[i].Name})
}
return
}
now := time.Now()
for i := range pods {
pod := &pods[i]
podNamespacedName := types.NamespacedName{Namespace: pod.Namespace, Name: pod.Name}
tc.processPodOnNode(podNamespacedName, node.Name, pod.Spec.Tolerations, taints, now)
}
}
handlePodUpdate
Получение информации об одном модуле и информации об узле также является окончательным выполнением.processPodOnNode
метод. Методы, как показано ниже:
func (tc *NoExecuteTaintManager) handlePodUpdate(podUpdate podUpdateItem) {
pod, err := tc.getPod(podUpdate.podName, podUpdate.podNamespace)
if err != nil {
if apierrors.IsNotFound(err) {
// Delete
podNamespacedName := types.NamespacedName{Namespace: podUpdate.podNamespace, Name: podUpdate.podName}
klog.V(4).Infof("Noticed pod deletion: %#v", podNamespacedName)
tc.cancelWorkWithEvent(podNamespacedName)
return
}
utilruntime.HandleError(fmt.Errorf("could not get pod %s/%s: %v", podUpdate.podName, podUpdate.podNamespace, err))
return
}
// We key the workqueue and shard workers by nodeName. If we don't match the current state we should not be the one processing the current object.
if pod.Spec.NodeName != podUpdate.nodeName {
return
}
// Create or Update
podNamespacedName := types.NamespacedName{Namespace: pod.Namespace, Name: pod.Name}
klog.V(4).Infof("Noticed pod update: %#v", podNamespacedName)
nodeName := pod.Spec.NodeName
if nodeName == "" {
return
}
taints, ok := func() ([]v1.Taint, bool) {
tc.taintedNodesLock.Lock()
defer tc.taintedNodesLock.Unlock()
taints, ok := tc.taintedNodes[nodeName]
return taints, ok
}()
// It's possible that Node was deleted, or Taints were removed before, which triggered
// eviction cancelling if it was needed.
if !ok {
return
}
tc.processPodOnNode(podNamespacedName, nodeName, pod.Spec.Tolerations, taints, time.Now())
}
processPodOnNode
Метод в основном добавляет поды, которые необходимо удалить в заранее определенном формате, вtaintEvictionQueue
, задачи в очереди устанавливаются с рассчитанным временем задачи, и они вызываются в течение соответствующего времени.deletePodHandler
метод удаления модуля, этот метод находится вpkg/controller/nodelifecycle/scheduler/taint_manager.go
Вниз. Методы, как показано ниже:
func deletePodHandler(c clientset.Interface, emitEventFunc func(types.NamespacedName)) func(args *WorkArgs) error {
return func(args *WorkArgs) error {
ns := args.NamespacedName.Namespace
name := args.NamespacedName.Name
klog.V(0).Infof("NoExecuteTaintManager is deleting Pod: %v", args.NamespacedName.String())
if emitEventFunc != nil {
emitEventFunc(args.NamespacedName)
}
var err error
for i := 0; i < retries; i++ {
err = c.CoreV1().Pods(ns).Delete(name, &metav1.DeleteOptions{})
if err == nil {
break
}
time.Sleep(10 * time.Millisecond)
}
return err
}
}
Поэтому основная функция TaintManager — настроить задачи удаления по расписанию для подов, которые необходимо исключить, а затем удалить их с соответствующего узла по одному.
doNoScheduleTaintingPassWorker
Когда функция taintNodeByCondition включена, она будет вызыватьсяdoNoScheduleTaintingPassWorker
перейти к узлуNoSchedule.Обновление пятен. называетсяdoNoScheduleTaintingPass
метод. Методы, как показано ниже:
func (nc *Controller) doNoScheduleTaintingPass(nodeName string) error {
node, err := nc.nodeLister.Get(nodeName)
if err != nil {
// If node not found, just ignore it.
if apierrors.IsNotFound(err) {
return nil
}
return err
}
// Map node's condition to Taints.
var taints []v1.Taint
for _, condition := range node.Status.Conditions {
if taintMap, found := nodeConditionToTaintKeyStatusMap[condition.Type]; found {
if taintKey, found := taintMap[condition.Status]; found {
taints = append(taints, v1.Taint{
Key: taintKey,
Effect: v1.TaintEffectNoSchedule,
})
}
}
}
if node.Spec.Unschedulable {
// If unschedulable, append related taint.
taints = append(taints, v1.Taint{
Key: schedulerapi.TaintNodeUnschedulable,
Effect: v1.TaintEffectNoSchedule,
})
}
// Get exist taints of node.
nodeTaints := taintutils.TaintSetFilter(node.Spec.Taints, func(t *v1.Taint) bool {
// only NoSchedule taints are candidates to be compared with "taints" later
if t.Effect != v1.TaintEffectNoSchedule {
return false
}
// Find unschedulable taint of node.
if t.Key == schedulerapi.TaintNodeUnschedulable {
return true
}
// Find node condition taints of node.
_, found := taintKeyToNodeConditionMap[t.Key]
return found
})
taintsToAdd, taintsToDel := taintutils.TaintSetDiff(taints, nodeTaints)
// If nothing to add not delete, return true directly.
if len(taintsToAdd) == 0 && len(taintsToDel) == 0 {
return nil
}
if !nodeutil.SwapNodeControllerTaint(nc.kubeClient, taintsToAdd, taintsToDel, node) {
return fmt.Errorf("failed to swap taints of node %+v", node)
}
return nil
}
doNoScheduleTaintingPass
В основном выполняют следующие работы:
1. Получить информацию об узле в соответствии с nodeName;
2. Судите по полю node.Status.Conditions, нужно ли узлу добавлять пометку NoSchedule. Критерии оценки следующие:
3. Если узел находится в состоянии Unschedulable, также добавьте пометку NoSchedule;
4. По имеющимся пятнам на Node определить, какие пятна, добавленные ранее, нужно добавить, а какие удалить;
5. звонок
SwapNodeControllerTaint
Обновление состояния заражения на Node.
doNoExecuteTaintingPass и doEvictionPass
doNoExecuteTaintingPass
а такжеdoEvictionPass
Оба будут выполнять только одно или другое.
doNoExecuteTaintingPass
добавлен метод для узлаNoExecuteпятно; иdoEvictionPass
Он заключается в том, чтобы напрямую определить, какие поды необходимо удалить, и перейти непосредственно к работе по удалению.doNoExecuteTaintingPass
метод, получивzoneNoExecuteTainterДанные внутри оценивают состояние узла, при необходимости добавляют пометку NoExecute и вызываютSwapNodeControllerTaint
метод для обновления taint на этом узле. Информация zoneNoExecuteTainter передается черезmonitorNodeHealth
Метод получен, и он будет проанализирован позже.doNoExecuteTaintingPass
Метод заключается в следующем:
doEvictionPass
получается непосредственноzonePodEvictorЧтобы определить, какие поды необходимо исключить, вызовите интерфейс DELETE пода, чтобы выполнить задачу вытеснения пода. Информация zonePodEvictor также передается черезmonitorNodeHealth
метод получен.doEvictionPass
Методы, как показано ниже:Разница между двумя методами заключается в том, чтоdoNoExecuteTaintingPass
Просто размазать Node, иdoEvictionPass
Затем завершается окончательное удаление.doEvictionPass
Таким образом, большое количество подов необходимо удалить в течение определенного периода времени, что будет генерировать много трафика; иdoNoExecuteTaintingPass
Размазав узел, пусть TaintManager выполнит окончательное удаление Pod.Задача удаления TaintManager выполняется регулярно по периодам времени, поэтому такой большой проблемы с трафиком не будет. Поэтому рекомендуется включить эту функцию и добавить ее в параметры запуска kube-controller-manager.--feature-gates=TaintBasedEvictions=true
Вот и все.
monitorNodeHealth
Задачи первых нескольких горутин в основном вращаются вокруг Taint, аmonitorNodeHealth
Это регулярно обновлять информацию о узле и генерировать источник данных.
monitorNodeHealth
Основные задачи можно разделить на следующие этапы:
1. Получить всю информацию об узлах, в соответствии с тем, какие из них были добавлены вновь, какие из них необходимо удалить, а какие являются соответствующей информацией о возвращенных узлах, которые необходимо перепланировать;
tryUpdateNodeHealth
метод;4. В соответствии с полученным состоянием узла сравните его с исходным состоянием узла и соответствующим образом отметьте узел. Этот код длинный, основная структура следующая5. Для проблемы прерывания сети узла настройте соответствующую скорость выселения в соответствии с различными состояниями узла и вызовитеhandleDisruption
метод.Затем проанализируйте каждый шаг один за другим.шаг 1
Сначала получите всю информацию об узле через интерфейс List, черезclassifyNodes
Завершите разделение Node.classifyNodes
Правило деления очень простое, сравнениеknownNodeSet
а такжеallNodes
, что можно понимать какknownNodeSet
для последних данных,allNodes
новые данные, то:
1. Если вallNodes
существуют, вknownNodeSet
не существует, это недавно добавленный узел;
2. Если вknownNodeSet
существуют, вallNodes
Не существует, это удаленный узел;
3. Если вknownNodeSet
а такжеallNodes
Все существуют, но нет состояний зоны, которые являются узлами newZoneRepresentatives. Каждый узел должен принадлежать Зоне.
Шаг 2
После завершения разделения узлов на шаге 1 на шаге 2 выполняются соответствующие операции обработки для каждого типа узла.
1. Добавьте Node для добавления в кэш в knownNodeSet и передайтеaddPodEvictorForNewZone
Чтобы он принадлежал Зоне, с помощью переключателя useTaintBasedEvictions решается, следует ли пометить узел как достижимый или отменить исключение модуля. Короче говоря, это означает, что узел можно использовать в обычном режиме;
2. Для удаляемого узла удалите его из набора известных узлов;
3. Узлы, не разделенные на Зоны, добавляются в Кэш Зон.
Шаг 3
Для всех полученных узлов звонитеPollImmediate
метод каждые 20 мс повторяет 5 попыток обновления состояния узла, в основном вызываяtryUpdateNodeHealth
метод.tryUpdateNodeHealth
Среди значений метода основное внимание уделяетсяobservedReadyCondition
а такжеcurrentReadyCondition
. можно понимать какobservedReadyCondition
Указывает последнее состояние узла,currentReadyCondition
Представляет текущее состояние узла. Следующие множественные операторы if-else работают с этими двумя значениями.
Шаг 4
Все большое утверждение начинается сcurrentReadyCondition
Если он не пуст, его можно разделить на следующие ситуации:
1,observedReadyCondition
Значение равно False, то есть узел не готов, пометьте узел с помощью node.kubernetes.io/not-ready:NoExecute taint или напрямую вытесните под на узле;
2,observedReadyCondition
Если значение равно Unknown, пометьте Node.kubernetes.io/unreachable:NoExecute как taint или напрямую изгоните Pod на Node;
3.observedReadyCondition
Значение равно True, что указывает на то, что Node находится в нормальном рабочем состоянии, помечая Node как Reachable или останавливая операцию исключения Pod;
4.currentReadyCondition
не для правдыobservedReadyCondition
Если это правда, это означает, что узел находится в состоянии «Не готов», помечая узел как «Не готов» и обновляя статус пода на узле;
5.currentReadyCondition
Если это не True и настроен облачный провайдер, он удалит Node.
Основная задача всего большого цикла — судить о статусе Node, помечать пометку Node или исключать связанные операции.zoneNoExecuteTainter
а такжеzonePodEvictor
Информация обоих наборов данных соответственно обновляется здесь.
Шаг 5
Последний вызовhandleDisruption
Выполните некоторые связанные операции обработки для сетевых прерываний.
Прерывания в основном имеют следующие состояния:
handleDisruption
в, черезallAreFullyDisrupted
а такжеallWasFullyDisrupted
Отметьте текущее состояние зоны и ранее кэшированное состояние зоны, указав последний результат и информацию о последнем результате соответственно. Затем выполните три операции обработки:1. Если новое состояние
fullDisruption
, то есть полное прерывание, указывающее на то, что все Узлы находятся в состоянии Not Ready.В это время нормальная скорость вытеснения восстанавливается, и операция вытеснения останавливается;2. Если новое состояние
partialDisruption
, то есть частичное прерывание, указывающее на то, что некоторые Узлы находятся в состоянии Not Ready, и в это время установлена определяемая пользователем скорость выселения;3, если новое состояние
normal
, возвращается к коэффициенту выселения по умолчанию.Основная цель состоит в том, чтобы контролировать скорость вытеснения в соответствии с различными состояниями прерывания для поддержания стабильности системы.
На этом задача NodeLifecycleController завершена. Основной процесс заключается в обновлении информации об узле в соответствии с различными состояниями узла, отметке/удалении соответствующего дефекта на узле, чтобы убедиться, что узел можно запланировать, и регулярном выполнении операции выселения пода.