Análisis de PodNominator de kube-planificador

Prefacio

El autor mencionó PodNominator en el artículo "Análisis de SchedulingQueue de kube-Scheduler" , porque la cola de programación hereda PodNominator, pero en ese momento, para evitar una expansión de contenido innecesaria, no hay explicación para PodNominator. Este artículo hará un análisis más completo de PodNominator, desde su definición e implementación hasta su aplicación en kube-planificador.

La rama release-1.20 del código fuente de Kubenetes utilizada en este artículo, el enlace del documento de la última versión de Kubernetes: https://github.com/jindezgm/k8s-src-analysis/blob/master/kube-scheduler/PodNominator.md

Definición de PodNominator

En primer lugar, debe comprender lo que hace PodNominator. La traducción al inglés es Pod Nominator, entonces, ¿qué es la nominación? Esto se puede obtener de la parte de la anotación definida en la API de pod. Enlace fuente: https://github.com/kubernetes/kubernetes/blob/release-1.20/staging/src/k8s.io/api/core/v1/types.go#L3594

    // nominatedNodeName is set only when this pod preempts other pods on the node, but it cannot be
	// scheduled right away as preemption victims receive their graceful termination periods.
	// This field does not guarantee that the pod will be scheduled on this node. Scheduler may decide
	// to place the pod elsewhere if other nodes become available sooner. Scheduler may also decide to
	// give the resources on this node to a higher priority pod that is created after preemption.
	// As a result, this field may be different than PodSpec.nodeName when the pod is
	// scheduled.
	// +optional
	NominatedNodeName string `json:"nominatedNodeName,omitempty" protobuf:"bytes,11,opt,name=nominatedNodeName"`

Un breve resumen del comentario del código fuente es que cuando un Pod se adelanta a otros Pods en Node, establecerá Pod.Status.NominatedNodeName con el nombre del Node. El programador no programará el Pod para el Nodo inmediatamente, porque necesita esperar hasta que el Pod interrumpido salga correctamente. Eso es todo. El otro contenido está relacionado con la programación. Solo necesitamos saber para qué es la nominación.

Ahora sabemos que la nominación significa que el Pod se puede programar para el Nodo nominado, pero es necesario esperar a que el Pod prioritario desocupe los recursos antes de que se pueda realizar la programación. Y PodNominator registra qué Pods son nominados por Node. Entonces, la pregunta es, ¿No está Pod.Status.NominatedNodeName ya registrado, y qué hace PodNominator? El autor no responderá a esta pregunta primero y dará la respuesta en el capítulo de resumen.

Echemos un vistazo a la definición de PodNominator, el enlace del código fuente: https://github.com/kubernetes/kubernetes/blob/release-1.20/pkg/scheduler/framework/interface.go#L562

type PodNominator interface {
	// 提名pod调度到nodeName的Node上
	AddNominatedPod(pod *v1.Pod, nodeName string)
	// 删除pod提名的nodeName 
	DeleteNominatedPodIfExists(pod *v1.Pod)
	// PodNominator处理pod更新事件
	UpdateNominatedPod(oldPod, newPod *v1.Pod)
	// 获取提名到nodeName上的所有Pod,这个接口是不是感觉到PodNominator存在的意义了?
    // 毕竟PodNominator有统计功能,否则就需要遍历所有的Pod.Status.NominatedNodeName才能统计出来
	NominatedPodsForNode(nodeName string) []*v1.Pod
}

Desde el punto de vista de la definición de PodNominator, es muy simple, parece que se puede lograr con el mapa, que es el caso.

Implementación de PodNominator

La implementación de PodNominator está en la cola de programación y vale la pena estudiar su propósito. Veamos primero la implementación del código y estudiemos por qué se implementa en la cola de despacho. Enlace fuente: https://github.com/kubernetes/kubernetes/blob/release-1.20/pkg/scheduler/internal/queue/scheduling_queue.go#L723

// 确实非常简单,用map加读写锁实现的
type nominatedPodMap struct {
	// nominatedPods的key是提名的Node的名字,value是所有的Pod,这就是为NominatedPodsForNode()接口专门设计的
	nominatedPods map[string][]*v1.Pod
	// nominatedPodToNode的key是Pod的UID,value是提名Node的名字,这是为增、删、改接口设计的
    // 为什么调度队列用NS+NAME,而这里用UID,其中可能有版本更新造成的历史原因,关键要看是否有用名字访问的需求。
    // 当然,当前版本调度队列的接口没有直接用NS+NAME访问Pod,但是不排除以前是有这个设计考虑的。
    // 以上只是笔者猜测,仅供参考,有哪位小伙伴有更靠谱的答案欢迎在留言区回复。
	nominatedPodToNode map[ktypes.UID]string

	sync.RWMutex
}

// nominatedPodMap一共就三个核心函数,add、delete、和UpdateNominatedPod
func (npm *nominatedPodMap) add(p *v1.Pod, nodeName string) {
	// 避免Pod已经存在先执行删除,确保同一个Pod不会存在两个实例,这个主要是为了nominatedPods考虑的,毕竟它的value是slice,没有去重能力。
	npm.delete(p)

    // NominatedNodeName()函数就是获取p.Status.NominatedNodeName,
    // 那么下面的代码的意思就是nodeName和p.Status.NominatedNodeName优先用nodeName,如果二者都没有指定则返回
    // 这里需要知道的是nodeName代表着最新的状态,所以需要优先,这一点在PodNominator应用章节会进一步说明
	nnn := nodeName
	if len(nnn) == 0 {
		nnn = NominatedNodeName(p)
		if len(nnn) == 0 {
			return
		}
	}
    // 下面的代码没什么难度,就是把Pod放到两个map中
	npm.nominatedPodToNode[p.UID] = nnn
	for _, np := range npm.nominatedPods[nnn] {
		if np.UID == p.UID {
			klog.V(4).Infof("Pod %v/%v already exists in the nominated map!", p.Namespace, p.Name)
			return
		}
	}
	npm.nominatedPods[nnn] = append(npm.nominatedPods[nnn], p)
}

func (npm *nominatedPodMap) delete(p *v1.Pod) {
    // 如果Pod不存在就返回
	nnn, ok := npm.nominatedPodToNode[p.UID]
	if !ok {
		return
	}
    // 然后从两个map中删除
	for i, np := range npm.nominatedPods[nnn] {
		if np.UID == p.UID {
			npm.nominatedPods[nnn] = append(npm.nominatedPods[nnn][:i], npm.nominatedPods[nnn][i+1:]...)
			if len(npm.nominatedPods[nnn]) == 0 {
				delete(npm.nominatedPods, nnn)
			}
			break
		}
	}
	delete(npm.nominatedPodToNode, p.UID)
}

func (npm *nominatedPodMap) UpdateNominatedPod(oldPod, newPod *v1.Pod) {
	npm.Lock()
	defer npm.Unlock()
    // 首选需要知道一个知识点,kube-scheduler什么时候会调用UpdateNominatedPod()?这个问题貌似应该是PodNominator应用章节的内容。
    // 为了便于理解下面的代码,笔者需要提前剧透一下,答案是在调度队列的更新接口中,感兴趣的同学可以回看《kube-scheduler的SchedulingQueue解析》的源码注释
    // 而调度队列的更新是kube-scheduler在watch apiserver的Pod的时候触发调用的,所以此时默认是没有提名Node的
	nodeName := ""
    // 有一些情况,在Pod刚好提名了Node之后收到了Pod的更新事件并且新Pod.Status.NominatedNodeName="",此时需要保留提名的Node。
    // 以下几种情况更新事件是不会保留提名的Node
    // 1.设置Status.NominatedNodeName:表现为NominatedNodeName(oldPod) == "" && NominatedNodeName(newPod) != ""
    // 2.更新Status.NominatedNodeName:表现为NominatedNodeName(oldPod) != "" && NominatedNodeName(newPod) != ""
    // 3.删除Status.NominatedNodeName:表现为NominatedNodeName(oldPod) != "" && NominatedNodeName(newPod) == ""
	if NominatedNodeName(oldPod) == "" && NominatedNodeName(newPod) == "" {
		if nnn, ok := npm.nominatedPodToNode[oldPod.UID]; ok {
			// 这是唯一一种情况保留提名
			nodeName = nnn
		}
	}
    // 无论提名的Node名字是否修改都需要更新,因为需要确保Pod的指针也被更新
	npm.delete(oldPod)
	npm.add(newPod, nodeName)
}

Además de actualizar y retener la parte de nodo nominada original, es un poco más complicado. Se puede decir que la implementación de todo el PodNominator es demasiado simple para ser más simple. Ni siquiera es exagerado decir que cuando ves la definición de PodNominator, puedes imaginar su implementación. Aunque PodNominator parece muy simple, muchas funciones complejas se construyen a partir de estos módulos simples. PodNominator es la clave para la realización de la programación preventiva por parte del programador de kube. ¿Sientes que no es tan simple en este momento? Ahora lo llevaré a ver cómo se aplica PodNominator en kube-Scheduler para realizar la programación preventiva.

SubNominador 应用

En kube-Scheduler, hay dos tipos que heredan directamente PodNominator, son SchedulingQueue y PreemptHandle. El autor del primero ha sido mencionado en el artículo "Análisis de SchedulingQueue de kube-planificador" , pero se ignora directamente, por lo que este artículo es una continuación de la cola de programación; PreemptHandle es un identificador de preferencia, este artículo no presentará demasiado Queda mucho, o una vieja rutina, para el seguimiento El artículo se analiza, pero por el nombre del tipo, se puede saber que se usa para lograr la preferencia. Entonces, PodNominator está relacionado con la programación preventiva.

Aunque tanto SchedulingQueue como PreemptHandle heredan PodNominator, ambos apuntan al mismo objeto (esto también es una característica del lenguaje golang, herencia de punteros), por lo que solo hay un objeto PodNominator en kube-Scheduler. Esta es también una de las razones por las que nominatedPodMap está bloqueado, porque existe una demanda de acceso simultáneo por parte de múltiples corrutinas.

Aplicación de PodNominator en la cola de despacho

Hay tres situaciones en la cola de despacho que llamarán a PodNominator.AddNominatedPod ():

  1. PriorityQueue.Add (): enlace de origen https://github.com/kubernetes/kubernetes/blob/release-1.20/pkg/scheduler/internal/queue/scheduling_queue.go#L265 , el propósito aquí es agregar Pod si nominar nodo , luego agréguelo a PodNominator. El autor piensa que esta situación puede no suceder con la lógica normal, porque ¿cómo se puede nominar un nuevo Pod? ¿Recuerda el ResyncPeriod de SharedInformer? SharedInformer sincronizará la cantidad total a la hora habitual. El evento en este momento es Agregar. Lo anterior es solo mi suposición.
  2. PriorityQueue.AddUnschedulableIfNotPresent (): Enlace de origen https://github.com/kubernetes/kubernetes/blob/release-1.20/pkg/scheduler/internal/queue/scheduling_queue.go#L326 , el propósito aquí es restaurar el estado de nominación de Pod, porque esta función agregará Pod a unschedulableQ o backoffQ, los cuales no son programables. La nominación original debe eliminarse y restaurarse a Pod.Status.NominatedNodeName.
  3. PriorityQueue.Update (): enlace de origen https://github.com/kubernetes/kubernetes/blob/release-1.20/pkg/scheduler/internal/queue/scheduling_queue.go#L460 , el propósito aquí es el mismo que PriorityQueue.Add () Lo mismo, porque al actualizar, si el Pod no está en ninguna subcola, será tratado como un Add.

La cola de programación está en la función PriorityQueue.Update (). Si el Pod ya existe en la cola, se llamará a PodNominator.UpdateNominatedPod (). El autor ya no copia el código correspondiente y los lectores pueden encontrarlo a través de lo anterior. conexión. Después de todo, el Pod se ha actualizado y el estado correspondiente debe ser más detallado. En PodNominator, es para actualizar el puntero del Pod. De la misma manera, se llama a PodNominator.DeleteNominatedPodIfExists () en la función PriorityQueue.Delete (), y el autor no lo explicará más.

Aplicación de PodNominator en el planificador

Cuando el programador de kube necesita nominar un nodo para un pod a través de la preferencia, el pod en este momento todavía se encuentra en un estado no programado, porque el pod debe esperar hasta que salga el pod interrumpido en el nodo. Entonces, para Pod en este momento, la programación falla, razón por la cual PodNominator.AddNominatedPod () aparece en la función recordSchedulingFailure (). Enlace de código: https://github.com/kubernetes/kubernetes/blob/release-1.20/pkg/scheduler/scheduler.go#L328

func (sched *Scheduler) recordSchedulingFailure(fwk framework.Framework, podInfo *framework.QueuedPodInfo, err error, reason string, nominatedNode string) {
	// sched.Error是Pod调度出错后的处理,即便是抢占成功并提名,依然是资源不满足错误,因为等待被抢占Pod退出
    // 这个函数会调用PriorityQueue.AddUnschedulableIfNoPresent()函数将Pod放入不可调度子队列
    sched.Error(podInfo, err)

	// Update the scheduling queue with the nominated pod information. Without
	// this, there would be a race condition between the next scheduling cycle
	// and the time the scheduler receives a Pod Update for the nominated pod.
	// Here we check for nil only for tests.
	if sched.SchedulingQueue != nil {
		sched.SchedulingQueue.AddNominatedPod(podInfo.Pod, nominatedNode)
	}

	......
}

El único lugar para llamar a la función recordSchedulingFailure () en el planificador para pasar un nombre de nodo válido (no "") es https://github.com/kubernetes/kubernetes/blob/release-1.20/pkg/scheduler/scheduler. go # L500 , indica que la preferencia es exitosa En otros lugares, se pasa una cadena vacía, lo que indica que la programación ha fallado. Este artículo no analiza demasiado el planificador, porque habrá un análisis especial del artículo, los estudiantes interesados ​​pueden analizar el código fuente por sí mismos, por supuesto, también pueden esperar a que se publiquen los artículos relacionados con el autor. El autor describe brevemente el principio de apropiación por parte del programador: cuando ningún nodo satisface las necesidades del Pod, el programador comienza a apropiarse del Pod con baja prioridad de Pod. Si la preferencia es exitosa, designe el Nodo donde se encuentra el Pod preferencial. y luego el Pod se coloca en la cola no programable y espera a que salga el Pod interrumpido.

Por supuesto, el Pod que se coloca en la cola no programable se volverá a poner en activeQ después de un período de tiempo. En este momento, si hay un Nodo que satisface las necesidades del Pod, el programador de kube programará el Pod en el Nodo y, a continuación, borre el nodo designado por el Pod. Enlace de código: https://github.com/kubernetes/kubernetes/blob/release-1.20/pkg/scheduler/scheduler.go#L384

// 还记得《kube-scheduler的Cache解析》对于assume的解释么?如果忘记了请复习一遍
func (sched *Scheduler) assume(assumed *v1.Pod, host string) error {
	// 设置Pod调度的Node
	assumed.Spec.NodeName = host
    // Cache中假定Pod已经调度到了Node上
	if err := sched.SchedulerCache.AssumePod(assumed); err != nil {
		klog.Errorf("scheduler cache AssumePod failed: %v", err)
		return err
	}
	// 已经假定Pod调度到了Node上,应该移除以前提名的Node了(如果有提名的Node)
	if sched.SchedulingQueue != nil {
		sched.SchedulingQueue.DeleteNominatedPodIfExists(assumed)
	}

	return nil
}

¿Puede Pod nominar Node a través de PodNominator? ¿Cómo asegurarse de que el nuevo Pod no sea reemplazado repetidamente en la siguiente ronda de programación (Pod1 no se adelantará a Pod0 si la prioridad de Pod2 no es mayor que Pod1 pero mayor que Pod0 mientras Pod0 está esperando)? Este es el propósito de PodNominator.NominatedPodsForNode (). Cuando el planificador atraviesa el Nodo, agregará esta parte de los recursos al Nodo, para evitar el problema de la apropiación repetida Este es también un término común "Reserva" en kube-planificador. Debido a que esta parte del código es relativamente complicada, no comentaré aquí.

Lo anterior es la aplicación de PodNominator en kube-planificador. Si cree que los puntos de conocimiento están un poco dispersos y no son sistemáticos, consulte el resumen a continuación.

para resumir

  1. PodNominator es un administrador de "nominación" definido por kube-Scheduler para lograr la programación de preferencia. Registra todos los pods que han tenido éxito en la preferencia, pero necesitan esperar a que salga el Pod interrumpido;

  2. A través de PodNominator.NominatedPodsForNode (), kube-Scheduler obtiene todos los Pods nominados al Nodo especificado. Al calcular la programación, los recursos aplicados por esta parte de los Pods se consideran los recursos reservados por Node para apropiarse de los Pods;

  3. Cuando ningún nodo satisface las necesidades del Pod, el programador de kube comienza a realizar la programación preventiva. Si la preferencia es exitosa, use PodNominator para designar el Nodo donde se encuentra el Pod interrumpido como el Nodo que ejecuta el Pod;

  4. La nominación exitosa no significa que se haya programado, por lo que el Pod todavía se encuentra en un estado no programable en este momento y se coloca en la subcola de Q no programable de la cola de programación;

  5. Si el Pod migra de unschedulableQ a activeQ, y hay un Nodo que satisface las necesidades del Pod, el Pod se programa para el Nodo y el Nodo previamente nominado se elimina;

En este momento, regrese y pruebe la "nominación", es decir, el programador reserva una parte de los recursos que actualmente están ocupados por otros Pods en el Nodo para el Pod por adelantado. Ahora veamos las preguntas planteadas al principio de este artículo. ¿Cuál es la función de PodNominator? Creo que el autor no debería tener que hacer explicaciones redundantes.

Finalmente, hay otra pregunta, ¿por qué se implementa PodNominator en la cola de despacho? Si el autor también haría esto, la razón del autor es muy simple: aunque Pod nomina a Nodo, todavía no está programado, por lo que es más apropiado ponerlo en la cola de programación. En cuanto a si el autor lo consideró así, no lo sé.

Supongo que te gusta

Origin blog.csdn.net/weixin_42663840/article/details/113941224
Recomendado
Clasificación