Cuestionario de Custom Scheduler (Parte 3)
Este cuestionario evalúa tu comprensión avanzada de la implementación y el uso de Custom Schedulers en Kubernetes.
Preguntas del cuestionario
1. ¿Cuál de los siguientes NO es un problema que puede ocurrir al ejecutar múltiples schedulers simultáneamente en Kubernetes?
A. Contención de recursos B. Conflictos en decisiones de scheduling C. Mayor ancho de banda de red D. Conflictos de leader election
Mostrar respuesta
Respuesta: C. Mayor ancho de banda de red
Explicación: "Mayor ancho de banda de red" NO es un problema que pueda ocurrir al ejecutar múltiples schedulers simultáneamente en Kubernetes. Aunque los schedulers se comunican con el API server, el uso de ancho de banda de red suele ser mínimo y no representa una preocupación.
Problemas reales que pueden ocurrir al ejecutar múltiples schedulers:
Contención de recursos:
- La contención de recursos puede ocurrir cuando múltiples schedulers intentan programar Pods en el mismo pool de Nodes.
- Como cada scheduler opera de forma independiente sin conocer las decisiones de otros schedulers, existe el riesgo de sobreasignar recursos de los Nodes.
- Ejemplo: dos schedulers pueden programar Pods simultáneamente en el mismo Node, excediendo la capacidad del Node.
Conflictos en decisiones de scheduling:
- Los conflictos pueden ocurrir cuando múltiples schedulers intentan programar el mismo Pod.
- Esto puede suceder cuando los Pods no especifican explícitamente un
schedulerName, o cuando múltiples schedulers usan el mismo nombre. - Ejemplo: ocurren race conditions cuando dos schedulers intentan vincular el mismo Pod a diferentes Nodes.
Conflictos de leader election:
- Si múltiples instancias de scheduler con el mismo nombre se ejecutan con leader election habilitado, pueden ocurrir conflictos en el mecanismo de leader election.
- Ejemplo: múltiples instancias de scheduler con el mismo nombre compitiendo por el liderazgo pueden causar transiciones de liderazgo inestables.
Mejores prácticas al ejecutar múltiples schedulers:
Separación clara de responsabilidades:
yaml# Pod for default scheduler apiVersion: v1 kind: Pod metadata: name: default-pod spec: # Uses default scheduler when schedulerName is not specified containers: - name: nginx image: nginx # Pod for custom scheduler apiVersion: v1 kind: Pod metadata: name: custom-pod spec: schedulerName: my-custom-scheduler # Specify custom scheduler containers: - name: nginx image: nginxUsar nombres de scheduler únicos:
yaml# Custom scheduler deployment apiVersion: apps/v1 kind: Deployment metadata: name: my-custom-scheduler namespace: kube-system spec: replicas: 1 selector: matchLabels: component: my-custom-scheduler template: metadata: labels: component: my-custom-scheduler spec: containers: - name: scheduler image: my-custom-scheduler:v1.0 args: - --scheduler-name=my-custom-scheduler # Use unique name - --leader-elect=true - --leader-elect-resource-name=my-custom-scheduler # Use unique resource nameSeparar pools de Nodes usando etiquetas de Node y taints:
yaml# Apply node labels and taints kubectl label node node1 scheduler=default kubectl label node node2 scheduler=custom kubectl taint nodes node2 dedicated=custom-scheduler:NoSchedule # Custom scheduler configuration apiVersion: kubescheduler.config.k8s.io/v1 kind: KubeSchedulerConfiguration profiles: - schedulerName: my-custom-scheduler plugins: filter: enabled: - name: NodeSelector pluginConfig: - name: NodeSelector args: nodeSelector: scheduler: customEstablecer resource quotas:
yamlapiVersion: v1 kind: ResourceQuota metadata: name: default-scheduler-quota namespace: default-workloads spec: hard: pods: "10" cpu: "20" memory: 40Gi --- apiVersion: v1 kind: ResourceQuota metadata: name: custom-scheduler-quota namespace: custom-workloads spec: hard: pods: "10" cpu: "20" memory: 40Gi
Monitoreo de múltiples schedulers:
# Check scheduler pods
kubectl get pods -n kube-system -l component=kube-scheduler
kubectl get pods -n kube-system -l component=my-custom-scheduler
# Check scheduler logs
kubectl logs -n kube-system -l component=kube-scheduler
kubectl logs -n kube-system -l component=my-custom-scheduler
# Check scheduling events
kubectl get events | grep -i "Successfully assigned"Explicación de las otras opciones:
- A. Contención de recursos: un problema real que puede ocurrir cuando múltiples schedulers programan Pods en el mismo pool de Nodes.
- B. Conflictos en decisiones de scheduling: un problema real que puede ocurrir cuando múltiples schedulers intentan programar el mismo Pod.
- D. Conflictos de leader election: un problema real que puede ocurrir cuando múltiples instancias de scheduler con el mismo nombre compiten por el liderazgo.
2. ¿Cuál es el rol del punto de extensión "Permit" en el scheduler de Kubernetes?
A. Vincular Pods a Nodes B. Permitir, denegar o retrasar el scheduling de Pods C. Excluir Nodes donde los Pods no pueden ejecutarse D. Asignar puntuaciones a Nodes
Mostrar respuesta
Respuesta: B. Permitir, denegar o retrasar el scheduling de Pods
Explicación: El rol del punto de extensión "Permit" en el framework de scheduling de Kubernetes es permitir, denegar o retrasar el scheduling de Pods. Los plugins Permit se ejecutan después de que se selecciona un Node, pero antes de la fase de binding, proporcionando aprobación o rechazo final para las decisiones de scheduling de Pods.
Funciones clave del punto de extensión Permit:
- Allow: Permite que el scheduling del Pod continúe hacia la fase de binding.
- Deny: Rechaza el scheduling del Pod para que se pueda seleccionar otro Node.
- Wait: Retrasa temporalmente el scheduling del Pod y espera hasta que se cumplan condiciones específicas.
Interfaz del plugin Permit:
type PermitPlugin interface {
Plugin
// Permit allows, denies, or delays pod scheduling.
// Return values:
// - Success: Allows pod scheduling.
// - Deny: Rejects pod scheduling.
// - Wait: Delays pod scheduling and waits until timeout or allowed.
Permit(ctx context.Context, state *CycleState, pod *v1.Pod, nodeName string) (*Status, time.Duration)
}Tipos de resultado de Permit:
- Success: Permite el scheduling del Pod.
- Deny: Rechaza el scheduling del Pod.
- Wait: Retrasa el scheduling del Pod y espera durante el tiempo especificado.
Plugins Permit predeterminados: Kubernetes proporciona los siguientes plugins Permit predeterminados:
- TaintToleration: Comprueba los taints de Node y las tolerations de Pod.
- PodTopologySpread: Comprueba las restricciones de distribución topológica de Pods.
Ejemplo de plugin Permit personalizado:
// CustomPermit implements custom permit logic.
type CustomPermit struct {
handle framework.Handle
// Map to track waiting pods
waitingPods map[string]waitingPod
// Mutex to synchronize map access
mu sync.RWMutex
}
// waitingPod stores information about waiting pods.
type waitingPod struct {
pod *v1.Pod
nodeName string
status chan bool // true: allow, false: deny
}
// Name returns the plugin name.
func (pl *CustomPermit) Name() string {
return "CustomPermit"
}
// Permit allows, denies, or delays pod scheduling.
func (pl *CustomPermit) Permit(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (*framework.Status, time.Duration) {
// Example: Allow, deny, or delay pod scheduling based on specific conditions
if shouldWait(pod, nodeName) {
// Add pod to waiting list
key := fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)
pl.mu.Lock()
if pl.waitingPods == nil {
pl.waitingPods = make(map[string]waitingPod)
}
pl.waitingPods[key] = waitingPod{
pod: pod,
nodeName: nodeName,
status: make(chan bool),
}
pl.mu.Unlock()
// Wait for up to 10 minutes
return framework.NewStatus(framework.Wait, "waiting for condition"), 10 * time.Minute
}
if shouldDeny(pod, nodeName) {
return framework.NewStatus(framework.Unschedulable, "denied by custom permit plugin"), 0
}
// Allow pod scheduling
return nil, 0
}
// Allow waiting pod
func (pl *CustomPermit) Allow(pod *v1.Pod) {
key := fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)
pl.mu.RLock()
waitingPod, ok := pl.waitingPods[key]
pl.mu.RUnlock()
if ok {
// Allow pod
waitingPod.status <- true
pl.mu.Lock()
delete(pl.waitingPods, key)
pl.mu.Unlock()
}
}
// Reject waiting pod
func (pl *CustomPermit) Reject(pod *v1.Pod) {
key := fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)
pl.mu.RLock()
waitingPod, ok := pl.waitingPods[key]
pl.mu.RUnlock()
if ok {
// Reject pod
waitingPod.status <- false
pl.mu.Lock()
delete(pl.waitingPods, key)
pl.mu.Unlock()
}
}
// Function to check if pod should wait
func shouldWait(pod *v1.Pod, nodeName string) bool {
// Implement custom logic
return false
}
// Function to check if pod should be denied
func shouldDeny(pod *v1.Pod, nodeName string) bool {
// Implement custom logic
return false
}Habilitación del plugin Permit en la configuración del scheduler:
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: custom-scheduler
plugins:
permit:
enabled:
- name: CustomPermit
disabled:
- name: TaintToleration # Disable default pluginCasos de uso de Permit:
- Gang scheduling: Retrasar el scheduling de un grupo de Pods hasta que todos los Pods relacionados estén listos para programarse.
- Reserva de recursos: Reservar recursos externos antes de que los Pods se programen.
- Validación de políticas: Asegurar que el scheduling de Pods cumpla con las políticas organizacionales.
- Flujos de aprobación: Solicitar aprobación externa para el scheduling de Pods.
Ejemplo de Gang scheduling: Gang scheduling es una técnica que asegura que todos los Pods relacionados se programen juntos. Esto es útil para cargas de trabajo como trabajos de entrenamiento distribuido, donde todos los componentes deben ejecutarse simultáneamente.
// GangPermit implements Gang scheduling.
type GangPermit struct {
handle framework.Handle
// Map to track waiting pods by group
waitingGroups map[string]gangGroup
mu sync.RWMutex
}
// gangGroup stores Gang information.
type gangGroup struct {
pods map[string]*v1.Pod
nodeName map[string]string
minCount int
readyPods int
}
// Permit allows, denies, or delays pod scheduling.
func (pl *GangPermit) Permit(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (*framework.Status, time.Duration) {
// Get Gang ID
gangID, ok := pod.Labels["gang-id"]
if !ok {
// Process as regular pod if no Gang ID
return nil, 0
}
pl.mu.Lock()
defer pl.mu.Unlock()
// Create group if not exists
if _, ok := pl.waitingGroups[gangID]; !ok {
minCount, _ := strconv.Atoi(pod.Labels["gang-min-count"])
if minCount <= 0 {
minCount = 1
}
pl.waitingGroups[gangID] = gangGroup{
pods: make(map[string]*v1.Pod),
nodeName: make(map[string]string),
minCount: minCount,
readyPods: 0,
}
}
// Add pod
group := pl.waitingGroups[gangID]
key := fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)
group.pods[key] = pod
group.nodeName[key] = nodeName
group.readyPods++
// Check if minimum count reached
if group.readyPods >= group.minCount {
// Allow all pods
for _, p := range group.pods {
pl.handle.PermitPlugin().Allow(p)
}
// Delete group
delete(pl.waitingGroups, gangID)
return nil, 0
}
// Wait until minimum count is reached
return framework.NewStatus(framework.Wait, "waiting for gang members"), 10 * time.Minute
}Problemas con las otras opciones:
- A. Vincular Pods a Nodes: este es el rol del punto de extensión "Bind".
- C. Excluir Nodes donde los Pods no pueden ejecutarse: este es el rol del punto de extensión "Filter".
- D. Asignar puntuaciones a Nodes: este es el rol del punto de extensión "Score".
A. Colocar Pods solo en Nodes específicos B. Asegurar que todos los Pods relacionados se programen juntos C. Distribuir Pods de manera uniforme entre varios Nodes D. Programar Pods según prioridad
Mostrar respuesta
Respuesta: B. Asegurar que todos los Pods relacionados se programen juntos
Explicación: El propósito principal de Gang Scheduling en Kubernetes es asegurar que todos los Pods relacionados se programen juntos. Esto es importante para cargas de trabajo como trabajos de entrenamiento distribuido y trabajos de procesamiento de datos distribuido, donde todos los componentes deben ejecutarse simultáneamente.
Por qué se necesita Gang scheduling:
- Requisito de todo o nada: Algunas cargas de trabajo requieren que todos los componentes se ejecuten simultáneamente; si solo algunos se ejecutan, el trabajo no avanza.
- Prevención del desperdicio de recursos: Si solo algunos Pods se programan mientras otros esperan, los recursos utilizados por los Pods ya programados pueden desperdiciarse.
- Prevención de deadlock: Puede ocurrir deadlock cuando Pods interdependientes se programan en momentos diferentes.
Métodos de implementación de Gang scheduling: Kubernetes no admite Gang scheduling de forma nativa, pero puede implementarse mediante:
- Custom scheduler: Implementar Gang scheduling usando el punto de extensión Permit.
- Controlador externo: Implementar un controlador que administre Gang scheduling fuera de Kubernetes.
- Soluciones open source: Usar schedulers open source como Volcano o Kube-batch.
Ejemplo de Gang scheduling (Volcano):
# PodGroup definition for Gang scheduling
apiVersion: scheduling.volcano.sh/v1beta1
kind: PodGroup
metadata:
name: tf-training
namespace: default
spec:
minMember: 4 # At least 4 pods must be scheduled together
minResources:
cpu: 8
memory: 16Gi
queue: default
---
# Pod belonging to Gang
apiVersion: v1
kind: Pod
metadata:
name: tf-worker-0
namespace: default
labels:
app: tf-training
annotations:
scheduling.volcano.sh/pod-group: tf-training # Reference PodGroup
spec:
schedulerName: volcano # Use Volcano scheduler
containers:
- name: tensorflow
image: tensorflow/tensorflow:latest-gpu
resources:
requests:
cpu: 2
memory: 4Gi
nvidia.com/gpu: 1Implementación de Gang scheduling usando un plugin Permit personalizado:
// GangSchedulingPlugin implements Gang scheduling.
type GangSchedulingPlugin struct {
handle framework.Handle
// Pod tracking per Gang
gangs map[string]*Gang
mu sync.RWMutex
}
// Gang represents a group of related pods.
type Gang struct {
MinRequired int
Scheduled map[string]string // pod name -> node name
Waiting map[string]*framework.WaitingPod
}
// Name returns the plugin name.
func (pl *GangSchedulingPlugin) Name() string {
return "GangSchedulingPlugin"
}
// PreFilter initializes Gang information.
func (pl *GangSchedulingPlugin) PreFilter(ctx context.Context, state *framework.CycleState, pod *v1.Pod) *framework.Status {
gangID, ok := pod.Labels["gang-id"]
if !ok {
return nil // Process as regular pod if no Gang ID
}
pl.mu.Lock()
defer pl.mu.Unlock()
if _, ok := pl.gangs[gangID]; !ok {
minRequired, _ := strconv.Atoi(pod.Labels["gang-min-required"])
if minRequired <= 0 {
minRequired = 1
}
pl.gangs[gangID] = &Gang{
MinRequired: minRequired,
Scheduled: make(map[string]string),
Waiting: make(map[string]*framework.WaitingPod),
}
}
return nil
}
// Permit implements Gang scheduling logic.
func (pl *GangSchedulingPlugin) Permit(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (*framework.Status, time.Duration) {
gangID, ok := pod.Labels["gang-id"]
if !ok {
return nil, 0 // Process as regular pod if no Gang ID
}
pl.mu.Lock()
defer pl.mu.Unlock()
gang, ok := pl.gangs[gangID]
if !ok {
return framework.NewStatus(framework.Error, "gang not found"), 0
}
podKey := fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)
gang.Scheduled[podKey] = nodeName
// Check if enough pods are scheduled
if len(gang.Scheduled) >= gang.MinRequired {
// Allow all waiting pods
for _, waitingPod := range gang.Waiting {
waitingPod.Allow(pl.Name())
}
gang.Waiting = make(map[string]*framework.WaitingPod)
return nil, 0
}
// Wait until enough pods are scheduled
waitingPod := framework.NewWaitingPod(pod)
gang.Waiting[podKey] = waitingPod
return framework.NewStatus(framework.Wait, "waiting for gang members"), 10 * time.Minute
}Ventajas y desventajas de Gang scheduling: Ventajas:
- Asegura que todos los Pods relacionados se programen juntos
- Previene el desperdicio de recursos
- Previene deadlock y starvation
Desventajas:
- Mayor complejidad de implementación
- Posibles retrasos de scheduling
- Posible disminución en la utilización de recursos del clúster
Cargas de trabajo que necesitan Gang scheduling:
- Trabajos de entrenamiento distribuido: Frameworks de entrenamiento distribuido como TensorFlow, PyTorch
- Procesamiento de datos distribuido: Frameworks de procesamiento de datos distribuido como Spark, Flink
- Trabajos MPI: Cargas de trabajo de computación de alto rendimiento (HPC)
- Service mesh: Service meshes donde múltiples componentes deben trabajar juntos
Problemas con las otras opciones:
- A. Colocar Pods solo en Nodes específicos: este es el rol de node selectors o node affinity.
- C. Distribuir Pods de manera uniforme entre varios Nodes: este es el rol de las restricciones de pod topology spread.
- D. Programar Pods según prioridad: este es el rol de pod priority y preemption.
4. ¿Cuál de los siguientes NO es un endpoint de API requerido al implementar un Scheduler Extender en Kubernetes?
A. /filter B. /prioritize C. /bind D. /validate
Mostrar respuesta
Respuesta: D. /validate
Explicación: "/validate" NO es un endpoint de API requerido al implementar un Scheduler Extender en Kubernetes. Los scheduler extenders suelen implementar endpoints como "/filter", "/prioritize", "/bind", "/preempt", pero "/validate" no es una API estándar para scheduler extenders.
Endpoints de API de Scheduler Extender:
- filter: Recibe una lista de Nodes y devuelve una lista filtrada de Nodes.
- prioritize: Recibe una lista de Nodes y asigna puntuaciones a cada Node.
- bind: Vincula un Pod a un Node.
- preempt: Devuelve Nodes y Pods para preemption.
Ejemplo de configuración de Scheduler Extender:
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
extenders:
- urlPrefix: "http://extender-service:8080"
filterVerb: "filter"
prioritizeVerb: "prioritize"
bindVerb: "bind"
enableHTTPS: false
nodeCacheCapable: false
ignorable: true
managedResources:
- name: example.com/foo
ignoredByScheduler: trueFormatos de solicitud y respuesta de la API de Scheduler Extender:
API filter:
- Solicitud:json
{ "pod": <pod>, "nodes": <nodes>, "nodenames": <node-names> } - Respuesta:json
{ "nodes": <filtered-nodes>, "nodenames": <filtered-node-names>, "failedNodes": <failed-nodes>, "error": <error-message> }
- Solicitud:
API prioritize:
- Solicitud:json
{ "pod": <pod>, "nodes": <nodes>, "nodenames": <node-names> } - Respuesta:json
{ "hostPriorities": [ { "host": <node-name>, "score": <score> }, ... ], "error": <error-message> }
- Solicitud:
API bind:
- Solicitud:json
{ "pod": <pod>, "node": <node-name> } - Respuesta:json
{ "error": <error-message> }
- Solicitud:
API preempt:
- Solicitud:json
{ "pod": <pod>, "nodenames": <node-names>, "nodes": <nodes> } - Respuesta:json
{ "nodenames": <node-names>, "nodes": <nodes>, "podsToPreempt": { <node-name>: [<pod>, ...], ... }, "error": <error-message> }
- Solicitud:
Ejemplo de implementación de Scheduler Extender (Go):
package main
import (
"encoding/json"
"log"
"net/http"
v1 "k8s.io/api/core/v1"
extender "k8s.io/kube-scheduler/extender/v1"
)
func main() {
http.HandleFunc("/filter", filterHandler)
http.HandleFunc("/prioritize", prioritizeHandler)
http.HandleFunc("/bind", bindHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
// Filter handler
func filterHandler(w http.ResponseWriter, r *http.Request) {
var args extender.ExtenderArgs
var result extender.ExtenderFilterResult
// Decode request body
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Implement filtering logic
filteredNodes := make([]v1.Node, 0, len(args.Nodes.Items))
failedNodes := make(map[string]string)
for _, node := range args.Nodes.Items {
// Custom filtering logic
if customFilter(&args.Pod, &node) {
filteredNodes = append(filteredNodes, node)
} else {
failedNodes[node.Name] = "Node failed custom filter"
}
}
// Set result
result.Nodes = &v1.NodeList{Items: filteredNodes}
result.FailedNodes = failedNodes
// Send response
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(result); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
// Prioritize handler
func prioritizeHandler(w http.ResponseWriter, r *http.Request) {
var args extender.ExtenderArgs
var result extender.HostPriorityList
// Decode request body
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Implement prioritization logic
result = make(extender.HostPriorityList, 0, len(args.Nodes.Items))
for _, node := range args.Nodes.Items {
// Custom score calculation
score := customScore(&args.Pod, &node)
result = append(result, extender.HostPriority{
Host: node.Name,
Score: score,
})
}
// Send response
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(result); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
// Bind handler
func bindHandler(w http.ResponseWriter, r *http.Request) {
var args extender.ExtenderBindingArgs
// Decode request body
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Implement binding logic
err := customBind(&args.Pod, args.Node)
// Send response
w.Header().Set("Content-Type", "application/json")
if err != nil {
json.NewEncoder(w).Encode(extender.ExtenderBindingResult{
Error: err.Error(),
})
} else {
json.NewEncoder(w).Encode(extender.ExtenderBindingResult{})
}
}
// Custom filtering function
func customFilter(pod *v1.Pod, node *v1.Node) bool {
// Implement custom filtering logic
return true
}
// Custom score calculation function
func customScore(pod *v1.Pod, node *v1.Node) int64 {
// Implement custom score calculation logic
return 100
}
// Custom binding function
func customBind(pod *v1.Pod, nodeName string) error {
// Implement custom binding logic
return nil
}Ventajas y desventajas de Scheduler Extenders: Ventajas:
- Pueden desarrollarse de forma independiente del codebase del scheduler
- Pueden implementarse en varios lenguajes de programación
- Se ven menos afectados por las actualizaciones del scheduler
Desventajas:
- Degradación del rendimiento debido a la sobrecarga de comunicación HTTP
- Solo pueden extender algunas etapas del ciclo de scheduling
- Posibilidad de fallo de comunicación entre el scheduler y el extender
Scheduler Extender vs plugins del Scheduling Framework:
- Scheduler Extender: Se ejecuta como un proceso externo mediante webhooks HTTP.
- Plugins del Scheduling Framework: Se ejecutan directamente integrados con el codebase del scheduler.
Explicación de las otras opciones:
- A. /filter: Un endpoint de API válido de scheduler extender que filtra la lista de Nodes.
- B. /prioritize: Un endpoint de API válido de scheduler extender que asigna puntuaciones a Nodes.
- C. /bind: Un endpoint de API válido de scheduler extender que vincula Pods a Nodes.
A. Asignar puntuaciones a Nodes después del filtering B. Vincular Pods a Nodes después del filtering C. Ejecutar lógica de preemption cuando falla el filtering D. Actualizar el estado del Pod después del filtering
Mostrar respuesta
Respuesta: C. Ejecutar lógica de preemption cuando falla el filtering
Explicación: El rol del punto de extensión "PostFilter" en el framework de scheduling de Kubernetes es ejecutar lógica de preemption cuando falla el filtering. Cuando todos los Nodes se excluyen durante la fase de filtering y un Pod no puede programarse, los plugins PostFilter encuentran formas de programar el Pod mediante preemption.
Funciones clave del punto de extensión PostFilter:
- Identificar candidatos de preemption: Identifica Pods y Nodes que pueden ser preempted.
- Simulación de preemption: Simula si los Pods pueden programarse después de la preemption.
- Decisión de preemption: Determina la estrategia óptima de preemption.
Interfaz del plugin PostFilter:
type PostFilterPlugin interface {
Plugin
// PostFilter is called when filtering fails.
// Finds ways to schedule pods through preemption.
PostFilter(ctx context.Context, state *CycleState, pod *v1.Pod, filteredNodeStatusMap NodeToStatusMap) (*PostFilterResult, *Status)
}
// PostFilterResult represents the result of PostFilter operation.
type PostFilterResult struct {
// Node where pod will be scheduled after preemption
NominatedNodeName string
}Plugin PostFilter predeterminado: Kubernetes proporciona el siguiente plugin PostFilter predeterminado:
- DefaultPreemption: Implementa la lógica de preemption predeterminada.
Funcionamiento del plugin DefaultPreemption:
- Identifica Nodes donde se puede liberar espacio mediante preemption de Pods de menor prioridad.
- Determina qué Pods preempt en cada Node.
- Verifica que los Pods puedan programarse después de la preemption.
- Selecciona la estrategia óptima de preemption.
- Establece el Node seleccionado como el nominatedNodeName del Pod.
Ejemplo de plugin PostFilter personalizado:
// CustomPostFilter implements custom preemption logic.
type CustomPostFilter struct {
handle framework.Handle
}
// Name returns the plugin name.
func (pl *CustomPostFilter) Name() string {
return "CustomPostFilter"
}
// PostFilter is called when filtering fails.
func (pl *CustomPostFilter) PostFilter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, filteredNodeStatusMap framework.NodeToStatusMap) (*framework.PostFilterResult, *framework.Status) {
// Identify preemptable nodes
preemptableNodes := identifyPreemptableNodes(pl.handle, pod, filteredNodeStatusMap)
if len(preemptableNodes) == 0 {
return nil, framework.NewStatus(framework.Unschedulable, "no preemptable nodes found")
}
// Determine pods to preempt on each node
nodeToVictims := map[string]*framework.Victims{}
for _, node := range preemptableNodes {
victims, err := selectVictimsOnNode(pl.handle, pod, node)
if err != nil {
continue
}
nodeToVictims[node.Name] = victims
}
// Select optimal preemption strategy
nominatedNode, victims := selectBestNodeForPreemption(nodeToVictims)
if nominatedNode == "" {
return nil, framework.NewStatus(framework.Unschedulable, "no node for preemption")
}
// Execute preemption
for _, victim := range victims.Pods {
if err := pl.handle.ClientSet().CoreV1().Pods(victim.Namespace).Delete(ctx, victim.Name, metav1.DeleteOptions{}); err != nil {
return nil, framework.NewStatus(framework.Error, err.Error())
}
}
return &framework.PostFilterResult{
NominatedNodeName: nominatedNode,
}, nil
}
// Identify preemptable nodes
func identifyPreemptableNodes(handle framework.Handle, pod *v1.Pod, filteredNodeStatusMap framework.NodeToStatusMap) []*v1.Node {
// Implementation omitted
return nil
}
// Select pods to preempt on node
func selectVictimsOnNode(handle framework.Handle, pod *v1.Pod, node *v1.Node) (*framework.Victims, error) {
// Implementation omitted
return nil, nil
}
// Select optimal preemption strategy
func selectBestNodeForPreemption(nodeToVictims map[string]*framework.Victims) (string, *framework.Victims) {
// Implementation omitted
return "", nil
}Habilitación del plugin PostFilter en la configuración del scheduler:
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: custom-scheduler
plugins:
postFilter:
enabled:
- name: CustomPostFilter
disabled:
- name: DefaultPreemption # Disable default pluginConfiguraciones relacionadas con preemption:
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: custom-scheduler
pluginConfig:
- name: DefaultPreemption
args:
minCandidateNodesPercentage: 10 # Minimum percentage of preemption candidate nodes
minCandidateNodesAbsolute: 100 # Minimum number of preemption candidate nodesProceso de preemption:
- La fase PostFilter se llama cuando un Pod falla la fase de filtering en todos los Nodes.
- El plugin PostFilter identifica Nodes candidatos para preemption.
- Determina qué Pods preempt en cada Node.
- Verifica que los Pods puedan programarse después de la preemption.
- Selecciona la estrategia óptima de preemption.
- Establece el Node seleccionado como el nominatedNodeName del Pod.
- Los Pods preempted pasan por una terminación graceful.
- Cuando los Pods preempted terminan, los Pods de mayor prioridad se programan.
Monitoreo de métricas relacionadas con preemption:
# Check preemption-related metrics from scheduler metrics
kubectl get --raw /metrics | grep scheduler_preemptionComprobación de eventos de preemption:
# Check preemption events
kubectl get events | grep -i preemptProblemas con las otras opciones:
- A. Asignar puntuaciones a Nodes después del filtering: este es el rol del punto de extensión "Score".
- B. Vincular Pods a Nodes después del filtering: este es el rol del punto de extensión "Bind".
- D. Actualizar el estado del Pod después del filtering: este no es un punto de extensión en el framework del scheduler.
6. ¿Cuál es el propósito principal del plugin "NodeResourcesBalancedAllocation" en el scheduler de Kubernetes?
A. Dar puntuaciones más altas a Nodes con uso equilibrado de CPU y memoria B. Dar puntuaciones más altas a Nodes con menor uso de recursos C. Dar puntuaciones más altas a Nodes con mayor uso de recursos D. Establecer límites de recursos en Nodes
Mostrar respuesta
Respuesta: A. Dar puntuaciones más altas a Nodes con uso equilibrado de CPU y memoria
Explicación: El propósito principal del plugin "NodeResourcesBalancedAllocation" en el scheduler de Kubernetes es dar puntuaciones más altas a Nodes con uso equilibrado de CPU y memoria. Este plugin prefiere Nodes donde la diferencia entre utilización de CPU y memoria es pequeña, mejorando el balance general del uso de recursos en el clúster.
Funcionamiento del plugin NodeResourcesBalancedAllocation:
- Calcula la utilización de CPU y la utilización de memoria para cada Node.
- Calcula la diferencia entre la utilización de CPU y la utilización de memoria.
- Da puntuaciones más altas a Nodes con diferencias más pequeñas.
Método de cálculo de puntuación:
score = 10 - variance(cpuFraction, memoryFraction) * 10Donde:
- cpuFraction = (CPU solicitada + solicitud de CPU del Pod) / CPU asignable
- memoryFraction = (memoria solicitada + solicitud de memoria del Pod) / memoria asignable
- variance(a, b) = |a - b|
Ejemplo:
- Node A: utilización de CPU 80%, utilización de memoria 80% -> diferencia: 0% -> puntuación: 10
- Node B: utilización de CPU 90%, utilización de memoria 50% -> diferencia: 40% -> puntuación: 6
- Node C: utilización de CPU 30%, utilización de memoria 90% -> diferencia: 60% -> puntuación: 4
En este caso, Node A recibe la puntuación más alta y es el que tiene mayor probabilidad de ser seleccionado.
Habilitación del plugin NodeResourcesBalancedAllocation en la configuración del scheduler:
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
plugins:
score:
enabled:
- name: NodeResourcesBalancedAllocation
weight: 2 # Set weightConfiguración del plugin:
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
pluginConfig:
- name: NodeResourcesBalancedAllocation
args:
resources:
- name: cpu
weight: 1
- name: memory
weight: 1NodeResourcesBalancedAllocation vs otros plugins de scoring:
- NodeResourcesBalancedAllocation: Prefiere Nodes con uso equilibrado de CPU y memoria.
- NodeResourcesFit: Prefiere Nodes con más recursos disponibles en comparación con los recursos solicitados.
- NodeResourcesLeastAllocated: Prefiere Nodes con menor uso de recursos.
- NodeResourcesMostAllocated: Prefiere Nodes con mayor uso de recursos.
Casos de uso:
- Balance de recursos: Mejora el balance de uso de CPU y memoria en todo el clúster.
- Prevención de cuellos de botella: Evita que un tipo de recurso (CPU o memoria) se agote antes que el otro.
- Mejora de escalabilidad: Los clústeres con uso equilibrado de recursos pueden escalar con mayor eficiencia.
Ejemplo de plugin de balanced allocation personalizado:
// CustomBalancedAllocation implements custom balanced allocation logic.
type CustomBalancedAllocation struct {
handle framework.Handle
// Resource weights
resourceWeights map[v1.ResourceName]int64
}
// Name returns the plugin name.
func (pl *CustomBalancedAllocation) Name() string {
return "CustomBalancedAllocation"
}
// Score assigns a score to nodes.
func (pl *CustomBalancedAllocation) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) {
nodeInfo, err := pl.handle.SnapshotSharedLister().NodeInfos().Get(nodeName)
if err != nil {
return 0, framework.NewStatus(framework.Error, fmt.Sprintf("getting node %q from Snapshot: %v", nodeName, err))
}
// Node's allocatable resources
allocatable := nodeInfo.Node().Status.Allocatable
// Resources already requested on node
requested := nodeInfo.RequestedResource()
// Pod's resource request
podRequest := calculatePodResourceRequest(pod)
// Calculate resource utilization
fractions := make(map[v1.ResourceName]float64)
for resource, weight := range pl.resourceWeights {
if weight == 0 {
continue
}
allocatableValue := allocatable[resource]
if allocatableValue.IsZero() {
continue
}
requestedValue := requested.ResourceList[resource]
podRequestValue := podRequest[resource]
fraction := float64(requestedValue.Value()+podRequestValue.Value()) / float64(allocatableValue.Value())
fractions[resource] = fraction
}
// Calculate difference between resource utilizations
var variance float64
for _, fraction := range fractions {
for _, otherFraction := range fractions {
diff := fraction - otherFraction
if diff > 0 {
variance += diff
} else {
variance -= diff
}
}
}
// Calculate score
score := int64(100 - variance*100)
if score < 0 {
score = 0
}
return score, nil
}
// ScoreExtensions returns interface for score normalization.
func (pl *CustomBalancedAllocation) ScoreExtensions() framework.ScoreExtensions {
return nil
}
// Calculate pod's resource request
func calculatePodResourceRequest(pod *v1.Pod) v1.ResourceList {
result := v1.ResourceList{}
for _, container := range pod.Spec.Containers {
for resource, value := range container.Resources.Requests {
if currentValue, ok := result[resource]; ok {
currentValue.Add(value)
result[resource] = currentValue
} else {
result[resource] = value.DeepCopy()
}
}
}
return result
}Problemas con las otras opciones:
- B. Dar puntuaciones más altas a Nodes con menor uso de recursos: este es el rol del plugin "NodeResourcesLeastAllocated".
- C. Dar puntuaciones más altas a Nodes con mayor uso de recursos: este es el rol del plugin "NodeResourcesMostAllocated".
- D. Establecer límites de recursos en Nodes: este no es el rol de los plugins de scheduler; los límites de recursos de Node son propiedades de los propios Nodes.
A. Vincular Pods a Nodes B. Realizar operaciones necesarias antes del binding C. Realizar limpieza después del binding D. Realizar operaciones de recuperación cuando falla el binding
Mostrar respuesta
Respuesta: B. Realizar operaciones necesarias antes del binding
Explicación: El rol del punto de extensión "PreBind" en el framework de scheduling de Kubernetes es realizar operaciones necesarias antes de vincular un Pod a un Node. Por ejemplo, pueden realizarse operaciones como aprovisionamiento de volúmenes, configuración de red y reserva de recursos.
Funciones clave del punto de extensión PreBind:
- Aprovisionamiento de volúmenes: Crea y prepara los volúmenes necesarios.
- Configuración de red: Configura los recursos de red necesarios.
- Reserva de recursos: Reserva los recursos necesarios.
- Prevalidación: Verificación final de que el binding es posible.
Interfaz del plugin PreBind:
type PreBindPlugin interface {
Plugin
// PreBind is called before binding a pod to a node.
PreBind(ctx context.Context, state *CycleState, pod *v1.Pod, nodeName string) *Status
}Plugins PreBind predeterminados: Kubernetes proporciona los siguientes plugins PreBind predeterminados:
- VolumeBinding: Realiza operaciones de binding de volúmenes.
- DefaultPreBind: Realiza operaciones básicas previas al binding.
Ejemplo de plugin PreBind personalizado:
// CustomPreBind implements custom pre-binding logic.
type CustomPreBind struct {
handle framework.Handle
}
// Name returns the plugin name.
func (pl *CustomPreBind) Name() string {
return "CustomPreBind"
}
// PreBind is called before binding a pod to a node.
func (pl *CustomPreBind) PreBind(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) *framework.Status {
// 1. Volume provisioning
if err := pl.provisionVolumes(ctx, pod, nodeName); err != nil {
return framework.NewStatus(framework.Error, err.Error())
}
// 2. Network resource setup
if err := pl.setupNetworking(ctx, pod, nodeName); err != nil {
return framework.NewStatus(framework.Error, err.Error())
}
// 3. Resource reservation
if err := pl.reserveResources(ctx, pod, nodeName); err != nil {
return framework.NewStatus(framework.Error, err.Error())
}
// 4. Final validation
if err := pl.validateBinding(ctx, pod, nodeName); err != nil {
return framework.NewStatus(framework.Error, err.Error())
}
return nil
}
// Volume provisioning
func (pl *CustomPreBind) provisionVolumes(ctx context.Context, pod *v1.Pod, nodeName string) error {
// Identify necessary volumes
for _, volume := range pod.Spec.Volumes {
if volume.PersistentVolumeClaim != nil {
// Check PVC status
pvc, err := pl.handle.ClientSet().CoreV1().PersistentVolumeClaims(pod.Namespace).Get(ctx, volume.PersistentVolumeClaim.ClaimName, metav1.GetOptions{})
if err != nil {
return err
}
// If PVC is not bound
if pvc.Status.Phase != v1.ClaimBound {
return fmt.Errorf("PVC %s is not bound", pvc.Name)
}
}
}
return nil
}
// Network resource setup
func (pl *CustomPreBind) setupNetworking(ctx context.Context, pod *v1.Pod, nodeName string) error {
// Example: Network policy setup
if err := pl.setupNetworkPolicies(ctx, pod, nodeName); err != nil {
return err
}
// Example: Service endpoint setup
if err := pl.setupServiceEndpoints(ctx, pod, nodeName); err != nil {
return err
}
return nil
}
// Resource reservation
func (pl *CustomPreBind) reserveResources(ctx context.Context, pod *v1.Pod, nodeName string) error {
// Example: GPU resource reservation
if err := pl.reserveGPUs(ctx, pod, nodeName); err != nil {
return err
}
// Example: Special hardware resource reservation
if err := pl.reserveSpecialHardware(ctx, pod, nodeName); err != nil {
return err
}
return nil
}
// Binding validation
func (pl *CustomPreBind) validateBinding(ctx context.Context, pod *v1.Pod, nodeName string) error {
// Example: Re-verify node status
node, err := pl.handle.ClientSet().CoreV1().Nodes().Get(ctx, nodeName, metav1.GetOptions{})
if err != nil {
return err
}
// Example: Check node resource availability
if !hasEnoughResources(node, pod) {
return fmt.Errorf("node %s does not have enough resources", nodeName)
}
return nil
}Habilitación del plugin PreBind en la configuración del scheduler:
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: custom-scheduler
plugins:
preBind:
enabled:
- name: CustomPreBind
disabled:
- name: VolumeBinding # Disable default pluginCasos de uso de PreBind:
Aprovisionamiento de volúmenes:
- Creación y binding de PersistentVolume
- Preparación de volúmenes efímeros
- Validación de parámetros de StorageClass
Configuración de red:
- Aplicación de NetworkPolicy
- Configuración de endpoints de Service
- Configuración de load balancer
Reserva de recursos:
- Reserva de recursos GPU
- Reserva de recursos FPGA
- Reserva de recursos de hardware especial
Configuración de seguridad:
- Aplicación de políticas de seguridad
- Aprovisionamiento de certificados
- Preparación de montaje de Secrets
Manejo de fallos de PreBind: Cuando un plugin PreBind devuelve un fallo:
- El ciclo de scheduling se aborta.
- El Pod vuelve a la cola de scheduling.
- Los recursos reservados se liberan.
- Se registran eventos de fallo.
Monitoreo de logs y eventos de PreBind:
# Check PreBind-related messages in scheduler logs
kubectl logs -n kube-system <scheduler-pod> | grep -i prebind
# Check pod events
kubectl describe pod <pod-name> | grep -i prebindProblemas con las otras opciones:
- A. Vincular Pods a Nodes: este es el rol del punto de extensión "Bind".
- C. Realizar limpieza después del binding: este es el rol del punto de extensión "PostBind".
- D. Realizar operaciones de recuperación cuando falla el binding: este no es un punto de extensión en el framework del scheduler.
8. ¿Cuál es el propósito principal del plugin "NodeResourcesFit" en el scheduler de Kubernetes?
A. Monitorear el uso de recursos del Node B. Establecer límites de recursos del Node C. Comparar la capacidad de recursos del Node con las solicitudes de recursos del Pod D. Mantener el balance de uso de recursos del Node
Mostrar respuesta
Respuesta: C. Comparar la capacidad de recursos del Node con las solicitudes de recursos del Pod
Explicación: El propósito principal del plugin "NodeResourcesFit" en el scheduler de Kubernetes es comparar la capacidad de recursos del Node con las solicitudes de recursos del Pod para verificar si los Pods pueden ejecutarse en los Nodes. Este plugin considera varios tipos de recursos, incluidos CPU, memoria, almacenamiento efímero y recursos extendidos (como GPUs).
Funciones clave del plugin NodeResourcesFit:
- Validación de solicitudes de recursos: Verifica que las solicitudes de recursos del Pod no excedan los recursos asignables del Node.
- Validación de límites de recursos: Verifica que los límites de recursos del Pod no excedan la capacidad del Node.
- Validación de recursos extendidos: Verifica que las solicitudes de recursos extendidos como GPUs y FPGAs estén disponibles en los Nodes.
Configuración del plugin NodeResourcesFit:
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
plugins:
filter:
enabled:
- name: NodeResourcesFit
score:
enabled:
- name: NodeResourcesFit
weight: 1
pluginConfig:
- name: NodeResourcesFit
args:
scoringStrategy:
type: LeastAllocated
resources:
- name: cpu
weight: 1
- name: memory
weight: 1Estrategias de scoring: El plugin NodeResourcesFit admite las siguientes estrategias de scoring:
LeastAllocated: Da puntuaciones más altas a Nodes con menos recursos en uso.
score = (capacity - requested) / capacityMostAllocated: Da puntuaciones más altas a Nodes con más recursos en uso.
score = requested / capacityRequestedToCapacityRatio: Usa funciones personalizadas para asignar puntuaciones según la proporción de recursos solicitados respecto a la capacidad.
Ejemplo de plugin NodeResourcesFit personalizado:
// CustomNodeResourcesFit implements custom resource fit logic.
type CustomNodeResourcesFit struct {
handle framework.Handle
// Resource weights
resourceWeights map[v1.ResourceName]int64
}
// Name returns the plugin name.
func (pl *CustomNodeResourcesFit) Name() string {
return "CustomNodeResourcesFit"
}
// Filter checks node resource fitness.
func (pl *CustomNodeResourcesFit) Filter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
// Node's allocatable resources
allocatable := nodeInfo.Node().Status.Allocatable
// Resources already requested on node
requested := nodeInfo.RequestedResource()
// Pod's resource request
podRequest := calculatePodResourceRequest(pod)
// Check each resource type
for resourceName := range pl.resourceWeights {
allocatableValue := allocatable[resourceName]
if allocatableValue.IsZero() {
return framework.NewStatus(framework.Unschedulable, fmt.Sprintf("node does not have resource %s", resourceName))
}
requestedValue := requested.ResourceList[resourceName]
podRequestValue := podRequest[resourceName]
if requestedValue.Value()+podRequestValue.Value() > allocatableValue.Value() {
return framework.NewStatus(framework.Unschedulable, fmt.Sprintf("insufficient %s", resourceName))
}
}
return nil
}
// Score assigns scores to nodes.
func (pl *CustomNodeResourcesFit) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) {
nodeInfo, err := pl.handle.SnapshotSharedLister().NodeInfos().Get(nodeName)
if err != nil {
return 0, framework.NewStatus(framework.Error, fmt.Sprintf("getting node %q from Snapshot: %v", nodeName, err))
}
// Node's allocatable resources
allocatable := nodeInfo.Node().Status.Allocatable
// Resources already requested on node
requested := nodeInfo.RequestedResource()
// Pod's resource request
podRequest := calculatePodResourceRequest(pod)
// Calculate score
var score int64 = 0
for resourceName, weight := range pl.resourceWeights {
allocatableValue := allocatable[resourceName]
if allocatableValue.IsZero() {
continue
}
requestedValue := requested.ResourceList[resourceName]
podRequestValue := podRequest[resourceName]
// Use LeastAllocated strategy
resourceScore := (float64(allocatableValue.Value()) - float64(requestedValue.Value()+podRequestValue.Value())) / float64(allocatableValue.Value())
score += int64(resourceScore * float64(weight))
}
return score, nil
}
// ScoreExtensions returns interface for score normalization.
func (pl *CustomNodeResourcesFit) ScoreExtensions() framework.ScoreExtensions {
return pl
}
// NormalizeScore normalizes scores.
func (pl *CustomNodeResourcesFit) NormalizeScore(ctx context.Context, state *framework.CycleState, pod *v1.Pod, scores framework.NodeScoreList) *framework.Status {
var highest int64 = 0
for _, nodeScore := range scores {
if nodeScore.Score > highest {
highest = nodeScore.Score
}
}
if highest == 0 {
return nil
}
for i := range scores {
scores[i].Score = scores[i].Score * framework.MaxNodeScore / highest
}
return nil
}Ejemplo de solicitud y límite de recursos:
apiVersion: v1
kind: Pod
metadata:
name: resource-demo
spec:
containers:
- name: nginx
image: nginx
resources:
requests:
cpu: "500m"
memory: "256Mi"
limits:
cpu: "1"
memory: "512Mi"Problemas con las otras opciones:
- A. Monitorear el uso de recursos del Node: este es el rol de metrics servers o sistemas de monitoreo.
- B. Establecer límites de recursos del Node: este es el rol de la configuración del Node o kubelet.
- D. Mantener el balance de uso de recursos del Node: este es el rol del plugin "NodeResourcesBalancedAllocation".
A. Procesar reglas de affinity entre Pods y Nodes B. Procesar reglas de affinity y anti-affinity entre Pods C. Procesar reglas de affinity entre Pods y volúmenes D. Procesar reglas de affinity entre Pods y Services
Mostrar respuesta
Respuesta: B. Procesar reglas de affinity y anti-affinity entre Pods
Explicación: El propósito principal del plugin "InterPodAffinity" en el scheduler de Kubernetes es procesar reglas de affinity y anti-affinity entre Pods. Este plugin controla si los Pods se colocan en el mismo dominio topológico (Node, zona, región, etc.) que otros Pods (affinity) o en dominios diferentes (anti-affinity).
Funciones clave del plugin InterPodAffinity:
- Procesamiento de reglas de Pod affinity: Asegura que los Pods se coloquen en el mismo dominio topológico que otros Pods con etiquetas específicas.
- Procesamiento de reglas de Pod anti-affinity: Asegura que los Pods se coloquen en dominios topológicos diferentes de otros Pods con etiquetas específicas.
- Consideración del dominio topológico: Considera varios niveles de dominios topológicos, incluidos Nodes, zonas y regiones.
Tipos de Pod affinity y anti-affinity:
- requiredDuringSchedulingIgnoredDuringExecution: Reglas que deben cumplirse para que los Pods se programen (requisito estricto).
- preferredDuringSchedulingIgnoredDuringExecution: Reglas que son preferidas, pero no obligatorias (requisito flexible).
Ejemplo de Pod affinity y anti-affinity:
apiVersion: v1
kind: Pod
metadata:
name: web-server
labels:
app: web
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- cache
topologyKey: kubernetes.io/hostname
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- web
topologyKey: kubernetes.io/hostname
containers:
- name: nginx
image: nginxConfiguración del plugin InterPodAffinity:
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
plugins:
preFilter:
enabled:
- name: InterPodAffinity
filter:
enabled:
- name: InterPodAffinity
score:
enabled:
- name: InterPodAffinity
weight: 2 # Set weight
pluginConfig:
- name: InterPodAffinity
args:
hardPodAffinityWeight: 1 # Hard pod affinity weightEjemplo de plugin InterPodAffinity personalizado:
// CustomInterPodAffinity implements custom inter-pod affinity logic.
type CustomInterPodAffinity struct {
handle framework.Handle
// Hard pod affinity weight
hardPodAffinityWeight int64
}
// Name returns the plugin name.
func (pl *CustomInterPodAffinity) Name() string {
return "CustomInterPodAffinity"
}
// PreFilter initializes inter-pod affinity information.
func (pl *CustomInterPodAffinity) PreFilter(ctx context.Context, state *framework.CycleState, pod *v1.Pod) *framework.Status {
// Initialize pod affinity information
if pod.Spec.Affinity == nil || (pod.Spec.Affinity.PodAffinity == nil && pod.Spec.Affinity.PodAntiAffinity == nil) {
return nil
}
// Store pod affinity information
affinity := pod.Spec.Affinity
state.Write(framework.StateKey("CustomInterPodAffinity"), affinity)
return nil
}
// PreFilterExtensions returns interface providing additional features.
func (pl *CustomInterPodAffinity) PreFilterExtensions() framework.PreFilterExtensions {
return nil
}
// Filter checks inter-pod affinity rules.
func (pl *CustomInterPodAffinity) Filter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
// Get pod affinity information
obj, err := state.Read(framework.StateKey("CustomInterPodAffinity"))
if err != nil {
return nil
}
affinity, ok := obj.(*v1.Affinity)
if !ok || affinity == nil {
return nil
}
// Check required pod affinity rules
if affinity.PodAffinity != nil {
for _, term := range affinity.PodAffinity.RequiredDuringSchedulingIgnoredDuringExecution {
if !satisfiesPodAffinityTerm(pod, term, nodeInfo, pl.handle) {
return framework.NewStatus(framework.Unschedulable, "node does not satisfy pod affinity rules")
}
}
}
// Check required pod anti-affinity rules
if affinity.PodAntiAffinity != nil {
for _, term := range affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution {
if satisfiesPodAffinityTerm(pod, term, nodeInfo, pl.handle) {
return framework.NewStatus(framework.Unschedulable, "node does not satisfy pod anti-affinity rules")
}
}
}
return nil
}
// Score assigns scores to nodes.
func (pl *CustomInterPodAffinity) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) {
// Get pod affinity information
obj, err := state.Read(framework.StateKey("CustomInterPodAffinity"))
if err != nil {
return 0, nil
}
affinity, ok := obj.(*v1.Affinity)
if !ok || affinity == nil {
return 0, nil
}
nodeInfo, err := pl.handle.SnapshotSharedLister().NodeInfos().Get(nodeName)
if err != nil {
return 0, framework.NewStatus(framework.Error, fmt.Sprintf("getting node %q from Snapshot: %v", nodeName, err))
}
var score int64 = 0
// Calculate preferred pod affinity score
if affinity.PodAffinity != nil {
for _, term := range affinity.PodAffinity.PreferredDuringSchedulingIgnoredDuringExecution {
if satisfiesPodAffinityTerm(pod, term.PodAffinityTerm, nodeInfo, pl.handle) {
score += term.Weight
}
}
}
// Calculate preferred pod anti-affinity score
if affinity.PodAntiAffinity != nil {
for _, term := range affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution {
if !satisfiesPodAffinityTerm(pod, term.PodAffinityTerm, nodeInfo, pl.handle) {
score += term.Weight
}
}
}
return score, nil
}
// ScoreExtensions returns interface for score normalization.
func (pl *CustomInterPodAffinity) ScoreExtensions() framework.ScoreExtensions {
return pl
}
// NormalizeScore normalizes scores.
func (pl *CustomInterPodAffinity) NormalizeScore(ctx context.Context, state *framework.CycleState, pod *v1.Pod, scores framework.NodeScoreList) *framework.Status {
var highest int64 = 0
for _, nodeScore := range scores {
if nodeScore.Score > highest {
highest = nodeScore.Score
}
}
if highest == 0 {
return nil
}
for i := range scores {
scores[i].Score = scores[i].Score * framework.MaxNodeScore / highest
}
return nil
}
// Check if pod affinity term is satisfied
func satisfiesPodAffinityTerm(pod *v1.Pod, term v1.PodAffinityTerm, nodeInfo *framework.NodeInfo, handle framework.Handle) bool {
// Implementation omitted
return true
}Casos de uso de Pod affinity y anti-affinity:
- Alta disponibilidad: Distribuir instancias de la misma aplicación entre diferentes Nodes, zonas o regiones
- Optimización de rendimiento: Colocar Pods que se comunican entre sí en el mismo Node para minimizar la latencia
- Aislamiento de recursos: Distribuir Pods con uso intensivo de recursos entre diferentes Nodes
- Restricciones de licencia: Concentrar aplicaciones con restricciones de licencia en Nodes específicos
Impacto en el rendimiento de Pod affinity y anti-affinity: Pod affinity y anti-affinity pueden ser computacionalmente costosas, ya que necesitan considerar todos los Nodes y Pods. En clústeres grandes, esto puede impactar el rendimiento del scheduling, por lo que deben usarse con precaución.
Problemas con las otras opciones:
- A. Procesar reglas de affinity entre Pods y Nodes: este es el rol del plugin "NodeAffinity".
- C. Procesar reglas de affinity entre Pods y volúmenes: este es el rol del plugin "VolumeBinding".
- D. Procesar reglas de affinity entre Pods y Services: este no es un plugin del scheduler de Kubernetes.
10. ¿Cuál es el propósito principal del plugin "NodeName" en el scheduler de Kubernetes?
A. Verificar que el campo spec.nodeName del Pod coincida con el nombre del Node B. Asignar nombres a Nodes C. Asignar nombres de Node a Pods D. Validar el formato del nombre de Node
Mostrar respuesta
Respuesta: A. Verificar que el campo spec.nodeName del Pod coincida con el nombre del Node
Explicación: El propósito principal del plugin "NodeName" en el scheduler de Kubernetes es verificar que el campo spec.nodeName del Pod coincida con el nombre del Node. Este plugin comprueba si un Pod ha sido asignado directamente a un Node específico y solo permite pasar por la fase de filtering a los Nodes con nombres coincidentes.
Funciones clave del plugin NodeName:
- Verificación del nombre del Node: Si el campo
spec.nodeNamedel Pod está configurado, solo se seleccionan Nodes con nombres coincidentes. - Soporte de scheduling directo: Permite a los usuarios asignar Pods directamente a Nodes específicos.
- Omisión del scheduler: Los Pods con
spec.nodeNameconfigurado omiten la lógica normal de scheduling y se asignan directamente al Node especificado.
Implementación del plugin NodeName:
// NodeName plugin implementation example
type NodeName struct{}
// Name returns the plugin name.
func (pl *NodeName) Name() string {
return "NodeName"
}
// Filter verifies that the pod's spec.nodeName field matches the node name.
func (pl *NodeName) Filter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
if pod.Spec.NodeName == "" {
return nil
}
if pod.Spec.NodeName != nodeInfo.Node().Name {
return framework.NewStatus(framework.UnschedulableAndUnresolvable, "node name does not match")
}
return nil
}Ejemplo de Pod con especificación nodeName:
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
nodeName: worker-node-1 # Direct assignment to specific node
containers:
- name: nginx
image: nginxConsideraciones al usar nodeName:
- Omisión del scheduler: Usar
nodeNameomite el filtering, scoring y otra lógica del scheduler. - Comprobación de existencia del Node: Si el Node especificado no existe, el Pod permanece en estado
Pending. - Sin comprobación de recursos: La disponibilidad de recursos del Node no se comprueba, lo que puede provocar fallos por escasez de recursos.
- Ignorar restricciones: Se ignoran taints, affinity y otras restricciones.
nodeName vs nodeSelector vs nodeAffinity:
- nodeName: Asigna directamente a un Node específico. Es la opción más restrictiva y menos flexible.
- nodeSelector: Selecciona Nodes según etiquetas. Simple, pero con expresividad limitada.
- nodeAffinity: Admite reglas complejas de selección de Nodes. Es la opción más flexible y expresiva.
Casos de uso de nodeName:
- Debugging: Ejecutar Pods en Nodes específicos para depurar problemas.
- Pruebas: Ejecutar pruebas en Nodes específicos.
- Hardware especial: Asignar Pods a Nodes con hardware específico.
- Static Pods: Usado para Static Pods administrados directamente por kubelet.
Precauciones al usar nodeName:
- Sin recuperación automática: Si un Node falla, los Pods no se mueven automáticamente a otros Nodes.
- Escalabilidad limitada: Los nombres de Node están codificados de forma fija, lo que limita la escalabilidad.
- Dificultad de mantenimiento: Las definiciones de Pod necesitan actualizaciones si cambian los nombres de Node.
- Sin balanceo de carga: No se pueden aprovechar las funciones de balanceo de carga del scheduler.
Alternativas y recomendaciones:
Usar nodeSelector:
yamlapiVersion: v1 kind: Pod metadata: name: nginx spec: nodeSelector: kubernetes.io/hostname: worker-node-1 containers: - name: nginx image: nginxUsar nodeAffinity:
yamlapiVersion: v1 kind: Pod metadata: name: nginx spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - worker-node-1 containers: - name: nginx image: nginx
Problemas con las otras opciones:
- B. Asignar nombres a Nodes: Los nombres de Node se asignan en la creación del Node y no son el rol de los plugins del scheduler.
- C. Asignar nombres de Node a Pods: Esto se realiza en la fase de binding del scheduler, no por el plugin NodeName.
- D. Validar el formato del nombre de Node: Esto lo realiza la lógica de validación del API server, no los plugins del scheduler.