tutoriales.com

Automatización de Tareas Administrativas en Kubernetes con Kube-scheduler Personalizado 🛠️

Este tutorial te guiará a través del proceso de extender el Kube-scheduler de Kubernetes para automatizar tareas administrativas. Descubrirás cómo crear un scheduler personalizado que puede implementar lógica de negocio específica para tus necesidades, optimizando la asignación de pods y la gestión de recursos en tu clúster. Ideal para operadores de Kubernetes que buscan mayor control y automatización.

Avanzado20 min de lectura10 views
Reportar error

Introducción: Más allá del Scheduler Predeterminado de Kubernetes 🚀

Kubernetes es conocido por su capacidad para automatizar la gestión de contenedores. Una de sus piezas clave es el Kube-scheduler, el componente encargado de decidir en qué nodo se ejecutará cada nuevo pod. Por defecto, el Kube-scheduler utiliza un conjunto de reglas y algoritmos para determinar la mejor ubicación para un pod, considerando factores como los requisitos de recursos, la afinidad, la anti-afinidad y la tolerancia a fallos.

Sin embargo, ¿qué pasa si las reglas predeterminadas no son suficientes para tus necesidades específicas? ¿Y si quieres implementar una lógica de negocio muy particular para la asignación de pods, o automatizar tareas administrativas complejas que dependen de dónde se programan los pods? Aquí es donde entra en juego la capacidad de extender el Kube-scheduler.

Extender el scheduler te permite introducir tu propia lógica de decisión, lo que abre un mundo de posibilidades para la optimización, la automatización y la personalización de tu clúster de Kubernetes. En este tutorial, exploraremos cómo puedes ir más allá del comportamiento predeterminado y construir tu propio scheduler para satisfacer tus requisitos únicos.

📌 Nota: Este tutorial asume que tienes un conocimiento básico de Kubernetes y sus componentes principales. Si eres nuevo en Kubernetes, te recomendamos familiarizarte con conceptos como Pods, Nodos, Deployments y Services antes de profundizar en la personalización del scheduler.

¿Por qué Personalizar el Kube-scheduler? Casos de Uso Comunes ✨

El scheduler predeterminado de Kubernetes hace un excelente trabajo en la mayoría de los escenarios. Sin embargo, hay situaciones específicas donde la personalización ofrece ventajas significativas:

  • Lógica de Negocio Específica: Tu aplicación podría tener requisitos únicos que el scheduler predeterminado no puede satisfacer. Por ejemplo, siempre quieres que un pod de procesamiento de datos se ejecute en nodos con GPUs específicas y con una etiqueta particular para la licencia.
  • Optimización de Costos: Podrías querer que ciertos pods se programen en nodos spot instances o de menor costo, priorizando otros pods en instancias on-demand.
  • Compliance y Restricciones Geográficas: Asegurar que los pods con datos sensibles se ejecuten solo en nodos ubicados en una región geográfica específica.
  • Consolidación de Cargas de Trabajo: En lugar de distribuir pods, podrías querer consolidarlos en el menor número posible de nodos para reducir los costos de infraestructura (menos nodos encendidos).
  • Integración con Sistemas Externos: Decisiones de scheduling basadas en información de sistemas externos, como un inventario de hardware o un sistema de gestión de licencias.
  • Automatización de Tareas Administrativas: Por ejemplo, marcar nodos para mantenimiento o limpieza basándose en la carga de trabajo programada, o reequilibrar cargas automáticamente.
🔥 Importante: Aunque potente, personalizar el scheduler añade complejidad a tu clúster. Asegúrate de que los beneficios superen la sobrecarga de mantenimiento y depuración que conlleva una lógica personalizada.

Entendiendo el Ciclo de Scheduling de Kubernetes 📖

Antes de sumergirnos en la personalización, es fundamental comprender cómo funciona el Kube-scheduler estándar. El proceso de scheduling se divide en dos fases principales:

  1. Fase de Filtrado (Predicates): El scheduler examina todos los nodos disponibles en el clúster y descarta aquellos que no cumplen con los requisitos mínimos del pod. Ejemplos de predicados incluyen verificar si el nodo tiene suficiente CPU y memoria, si las restricciones de afinidad/anti-afinidad se cumplen, o si las tolerancias coinciden con los taints del nodo.
  2. Fase de Puntuación (Priorities): De los nodos que pasaron la fase de filtrado, el scheduler les asigna una puntuación. Los nodos con puntuaciones más altas son considerados más adecuados. Ejemplos de prioridades incluyen preferir nodos con menor utilización, nodos en la misma zona de disponibilidad, o nodos donde ya existen otros pods relacionados.

Después de estas dos fases, el scheduler selecciona el nodo con la puntuación más alta y enlaza (binds) el pod a ese nodo. Si varios nodos tienen la misma puntuación máxima, se elige uno aleatoriamente.

Ciclo de Scheduling de Kubernetes 1. Pod Creado (Estado: Pending) 2. Kube-scheduler detecta el Pod 3. Fase de Filtrado (Predicates) 4. Fase de Puntuación (Priorities) 5. Seleccionar mejor nodo 6. Enlazar (Bind) Pod al Nodo

Extensiones del Kube-scheduler 🛠️

Kubernetes ofrece varias formas de extender el scheduler. Las más comunes son:

  • Multiple Schedulers: Ejecutar múltiples instancias del Kube-scheduler, cada una con su propia configuración o lógica, y especificar cuál debe programar un pod usando el campo schedulerName en la especificación del pod.
  • Scheduler Extenders: Un mecanismo para integrar un servicio externo con el Kube-scheduler predeterminado. Este servicio puede implementar lógica personalizada de filtrado y puntuación que se invoca durante el ciclo de scheduling estándar.
  • Framework de Scheduling (kube-scheduler v1.15+): La opción más moderna y potente. Permite implementar plugins que se integran directamente en el Kube-scheduler y pueden modificar o añadir lógica en diferentes puntos del ciclo de scheduling. Este es el enfoque que usaremos.
💡 Consejo: Para versiones de Kubernetes anteriores a la 1.15, los Scheduler Extenders eran la principal forma de extender el scheduler. Sin embargo, el Framework de Scheduling es más flexible y performante, y es la práctica recomendada para versiones modernas.

Creando un Kube-scheduler Personalizado con el Framework de Scheduling ⚙️

Vamos a crear un scheduler personalizado que implementará una lógica sencilla: priorizar nodos con menos pods ya programados. Esto puede ser útil para escenarios donde quieres una distribución más equitativa de las cargas de trabajo entre los nodos, más allá de solo la utilización de recursos.

1. Entorno de Desarrollo y Requisitos Previos

Necesitarás:

  • Go (versión 1.16 o superior).
  • Docker (para construir la imagen de tu scheduler).
  • Acceso a un clúster de Kubernetes (Minikube o Kind son excelentes para desarrollo).
  • kubectl configurado para interactuar con tu clúster.

2. Estructura del Proyecto y Boilerplate

Comenzaremos con la estructura básica para un plugin de scheduling. Necesitamos un archivo main.go y un módulo Go.

Crea un nuevo directorio para tu proyecto y inicializa un módulo Go:

mkdir custom-scheduler
cd custom-scheduler
go mod init github.com/tu-usuario/custom-scheduler

Ahora, crea el archivo main.go.

package main

import (
	"context"
	"fmt"
	"reflect"
	"sync"

	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/kubernetes/cmd/kube-scheduler/app"
	"k8s.io/kubernetes/pkg/scheduler/framework"
	"k8s.io/kubernetes/pkg/scheduler/framework/plugins/names"
)

const ( 
	Name = "node-less-pods-scheduler"
)

type Args struct {
	// Opcional: Argumentos de configuración para tu plugin
	// Puedes añadir campos aquí si tu plugin necesita configuraciones dinámicas.
}

type NodeLessPods struct {
	args *Args
	handle framework.Handle
	// Puedes añadir un caché o cualquier estado que necesite tu plugin.
	// Por ejemplo, para contar pods en nodos de manera más eficiente.
	mu sync.RWMutex
	nodePodCounts map[string]int
}

// New inicializa un nuevo plugin NodeLessPods.
func New(obj runtime.Object, handle framework.Handle) (framework.Plugin, error) {
	args := &Args{}
	if obj != nil {
		if err := runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), obj, args); err != nil {
			return nil, fmt.Errorf("decoding args into args failed: %w", err)
		}
	}

	return &NodeLessPods{
		args:        args,
		handle:       handle,
		nodePodCounts: make(map[string]int),
	},
		
}

// Name devuelve el nombre del plugin.
func (nlp *NodeLessPods) Name() string {
	return Name
}

// Score implementa la fase de puntuación (Priorities).
func (nlp *NodeLessPods) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) {
	nlp.mu.RLock()
	defer nlp.mu.RUnlock()

	// Obtener el objeto del nodo. Esto es útil para acceder a etiquetas, taints, etc.
	nodeInfo, err := nlp.handle.SnapshotSharedLister().NodeInfos().Get(nodeName)
	if err != nil {
		return 0, framework.As                          Status(fmt.Errorf("getting node %q from snapshot error: %w", nodeName, err))
	}

	// Contar los pods ya programados en este nodo (excluyendo el pod actual si ya estuviera en el nodo)
	// Nota: Este conteo es simplista y solo usa el snapshot actual. Para un conteo preciso
	// durante el scheduling de múltiples pods simultáneamente, se necesitaría un mecanismo más robusto
	// o depender de la información del framework que ya tiene en cuenta pods 'pending'.
	// Para este ejemplo, contamos los pods del snapshot.
	numPods := len(nodeInfo.Pods)

	// Nuestra lógica: priorizar nodos con menos pods.
	// Asignamos una puntuación inversamente proporcional al número de pods.
	// Cuantos menos pods, mayor la puntuación.
	// Max score es 10, min score es 0 por defecto en el framework.
	// Si un nodo tiene 0 pods, score = 10
	// Si un nodo tiene 1 pod, score = 9
	// ...
	// Si un nodo tiene 10 pods o más, score = 0

	score := int64(framework.MaxNodeScore - numPods)

	if score < 0 {
		score = 0
	}

	fmt.Printf("Scheduler %s: Scoring pod %s/%s on node %s. Num pods: %d, Score: %d\n",
		Name, pod.Namespace, pod.Name, nodeName, numPods, score)

	return score, framework.NewStatus(framework.Success)
}

// ScoreExtensions implementa las extensiones de puntuación, si las hubiera.
// Para este plugin simple, no necesitamos una extensión de normalización.
func (nlp *NodeLessPods) ScoreExtensions() framework.ScoreExtensions {
	return nil
}

// Filter implementa la fase de filtrado (Predicates).
// Para este ejemplo, no implementaremos lógica de filtrado personalizada
// y permitiremos que el scheduler predeterminado haga el filtrado básico.
func (nlp *NodeLessPods) Filter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
	// Podemos añadir lógica de filtrado aquí si fuera necesario.
	// Por ejemplo, rechazar nodos que tengan una etiqueta específica.

	// Para este plugin, no aplicamos ningún filtro adicional, así que siempre retornamos Success.
	return framework.NewStatus(framework.Success)
}

// PreFilter implementa la fase PreFilter (opcional).
func (nlp *NodeLessPods) PreFilter(ctx context.Context, state *framework.CycleState, pod *v1.Pod) (*framework.Status) {
	// Podemos realizar inicializaciones o comprobaciones globales aquí antes de los filtros de nodo.
	return framework.NewStatus(framework.Success)
}

// PreFilterExtensions implementa las extensiones de PreFilter (opcional).
func (nlp *NodeLessPods) PreFilterExtensions() framework.PreFilterExtensions {
	return nil
}

// PostFilter implementa la fase PostFilter (opcional).
func (nlp *NodeLessPods) PostFilter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, filteredNodesStatuses framework.NodeStatusMap) (*framework.Status) {
	// Se llama si no se encuentran nodos aptos después del filtrado.
	// Podemos añadir lógica de recuperación o logging aquí.
	return framework.NewStatus(framework.Success)
}

// Reserve implementa la fase Reserve (opcional).
func (nlp *NodeLessPods) Reserve(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) *framework.Status {
	// Se llama después de seleccionar un nodo, pero antes de que el pod sea 'bound'.
	// Útil para reservar recursos externos o hacer un pre-check final.
	return framework.NewStatus(framework.Success)
}

// Unreserve implementa la fase Unreserve (opcional).
func (nlp *NodeLessPods) Unreserve(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) {
	// Se llama si la fase 'Reserve' tuvo éxito, pero el binding falla o se cancela el scheduling.
	// Útil para liberar recursos reservados en 'Reserve'.
}

// PreBind implementa la fase PreBind (opcional).
func (nlp *NodeLessPods) PreBind(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (*framework.Status) {
	// Se llama justo antes de realizar el binding del pod al nodo.
	// Puedes realizar ajustes finales al pod o al nodo antes del binding.
	return framework.NewStatus(framework.Success)
}

// Bind implementa la fase Bind (opcional, generalmente no se implementa en plugins).
// Si un plugin implementa Bind, es el único que lo ejecuta, el scheduler por defecto no lo hará.
// Para este ejemplo, delegamos el Bind al scheduler por defecto.
func (nlp *NodeLessPods) Bind(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (*framework.Status) {
	return framework.NewStatus(framework.Error, "delegated to default bind")
}

// PostBind implementa la fase PostBind (opcional).
func (nlp *NodeLessPods) PostBind(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) {
	// Se llama después de que el pod ha sido exitosamente 'bound' al nodo.
	// Útil para tareas de post-procesamiento o notificaciones.
}

func main() {
	// Registrar el plugin con el kube-scheduler.
	// El plugin será cargado por el kube-scheduler si se especifica en la configuración.
	command := app.New                  SchedulerCommand(
		app.With              Plugin(Name, New),
	)
	
	if err := command.Execute(); err != nil {
		panic(err)
	}
}
⚠️ Advertencia: El ejemplo `main.go` utiliza `k8s.io/kubernetes` directamente, lo cual no es lo ideal para plugins externos debido a su gran tamaño y la posibilidad de conflictos de dependencias. Para producción, se recomienda usar `k8s.io/kube-scheduler/extender/v1` o similar para construir un scheduler como un binario separado que se comunica con la API de Kubernetes, o bien, construirlo como un plugin Go del Framework de Scheduling compilado *dentro* de la imagen del `kube-scheduler` oficial. Para simplificar el ejemplo, aquí usamos el enfoque directo.

Ejecuta go mod tidy para descargar las dependencias:

go mod tidy

3. Construyendo la Imagen Docker de tu Scheduler

Crea un Dockerfile en el mismo directorio:

FROM golang:1.20-alpine AS builder

WORKDIR /app

COPY go.mod go.sum ./ 
RUN go mod download

COPY . .

RUN CGO_ENABLED=0 go build -o /bin/kube-scheduler -ldflags "-s -w" .

FROM alpine:latest

COPY --from=builder /bin/kube-scheduler /usr/local/bin/kube-scheduler

CMD ["kube-scheduler", "--v=4", "--config=/etc/kubernetes/scheduler-config.yaml"]

Ahora, construye la imagen Docker:

docker build -t tu-usuario/custom-scheduler:v1.0.0 .

Empuja la imagen a un registro Docker accesible por tu clúster (Docker Hub, GCR, etc.):

docker push tu-usuario/custom-scheduler:v1.0.0

4. Configurando tu Kube-scheduler Personalizado en Kubernetes

Para que Kubernetes use tu scheduler, necesitas crear un Deployment para tu scheduler y un ServiceAccount, ClusterRole y ClusterRoleBinding para que tenga los permisos necesarios.

Primero, los permisos:

# custom-scheduler-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: custom-scheduler-sa
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: custom-scheduler-role
rules:
- apiGroups:
  - ""
  resources:
  - events
  verbs:
  - create
  - patch
- apiGroups:
  - ""
  resources:
  - pods
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - ""
  resources:
  - pods/status
  verbs:
  - update
- apiGroups:
  - ""
  resources:
  - nodes
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - ""
  resources:
  - bindings
  verbs:
  - create
- apiGroups:
  - ""
  resources:
  - endpoints
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - policy
  resources:
  - poddisruptionbudgets
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - ""
  resources:
  - configmaps
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - scheduling.k8s.io
  resources:
  - priorityclasses
  verbs:
  - get
  - list
  - watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: custom-scheduler-rolebinding
subjects:
- kind: ServiceAccount
  name: custom-scheduler-sa
  namespace: kube-system
roleRef:
  kind: ClusterRole
  name: custom-scheduler-role
  apiGroup: rbac.authorization.k8s.io

Aplica estos recursos:

kubectl apply -f custom-scheduler-rbac.yaml

Ahora, la configuración del scheduler. El Kube-scheduler moderno utiliza un KubeSchedulerConfiguration para definir qué plugins están activos y en qué fases. Este archivo se monta como un ConfigMap.

# custom-scheduler-config.yaml
apiVersion: kubescheduler.config.k8s.io/v1beta3 # Usa la versión adecuada para tu K8s
kind: KubeSchedulerConfiguration
leaderElection:
  leaderElect: false # Para un solo scheduler personalizado, no necesitamos leader election
clientConnection:
  kubeconfig: "/etc/kubernetes/scheduler.kubeconfig"
profiles:
- schedulerName: custom-scheduler # Nombre de tu scheduler
  plugins:
    score:
      enabled:
      - name: "NodeLessPods"
      disabled:
      - name: *
    filter:
      enabled:
      - name: NodeLessPods
      disabled:
      - name: *
    # Si no deshabilitas los plugins por defecto, se ejecutarán junto con el tuyo.
    # Para este ejemplo, deshabilitamos la mayoría para ver el efecto de nuestro plugin.
    # En un escenario real, probablemente querrías mantener algunos plugins por defecto como NodeResourcesFit.
  pluginConfig:
  - name: NodeLessPods
    args: # Argumentos de configuración para tu plugin (si los tuvieras)
      # myArg: "myValue"

Crea el ConfigMap:

kubectl create configmap custom-scheduler-config --from-file=scheduler-config.yaml=custom-scheduler-config.yaml -n kube-system

Finalmente, el Deployment de tu scheduler personalizado:

# custom-scheduler-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: custom-scheduler
  namespace: kube-system
  labels:
    app: custom-scheduler
spec:
  replicas: 1
  selector:
    matchLabels:
      app: custom-scheduler
  template:
    metadata:
      labels:
        app: custom-scheduler
    spec:
      serviceAccountName: custom-scheduler-sa
      containers:
      - name: scheduler
        image: tu-usuario/custom-scheduler:v1.0.0 # Reemplaza con tu imagen
        imagePullPolicy: Always
        args:
        - kube-scheduler
        - --config=/etc/kubernetes/scheduler-config.yaml
        - --kubeconfig=/etc/kubernetes/scheduler.kubeconfig
        volumeMounts:
        - name: kubeconfig
          mountPath: /etc/kubernetes/scheduler.kubeconfig
          subPath: scheduler.kubeconfig
        - name: scheduler-config-volume
          mountPath: /etc/kubernetes/scheduler-config.yaml
          subPath: scheduler-config.yaml
      volumes:
      - name: kubeconfig
        secret:
          secretName: default-scheduler-kubeconfig # Reemplaza con el secret de kubeconfig si es diferente
          items:
          - key: kubeconfig
            path: scheduler.kubeconfig
      - name: scheduler-config-volume
        configMap:
          name: custom-scheduler-config
          items:
          - key: scheduler-config.yaml
            path: scheduler-config.yaml
📌 Nota sobre el Kubeconfig: El `default-scheduler-kubeconfig` es un secret que generalmente utiliza el Kube-scheduler predeterminado. Puede que necesites crear uno nuevo o ajustar el que ya existe para que tu scheduler tenga acceso a la API de Kubernetes. En muchos entornos, el kube-scheduler se ejecuta como un pod y utiliza el ServiceAccount del `kube-system` para comunicarse con la API. Si estás en Minikube o Kind, el kubeconfig por defecto del scheduler suele funcionar.

Aplica el Deployment:

kubectl apply -f custom-scheduler-deployment.yaml

Verifica que tu scheduler esté corriendo:

kubectl get pods -n kube-system -l app=custom-scheduler
kubectl logs -f <nombre-del-pod-de-tu-scheduler> -n kube-system

Deberías ver los logs de tu scheduler, incluyendo las puntuaciones que asigna.


Probando tu Kube-scheduler Personalizado ✅

Ahora que nuestro scheduler personalizado está desplegado, podemos crear pods y especificar que sean programados por él.

Crea un Pod de prueba que especifique tu schedulerName:

# test-pod-1.yaml
apiVersion: v1
kind: Pod
metadata:
  name: test-pod-1
  labels:
    app: test-app
spec:
  schedulerName: custom-scheduler # ¡Aquí especificamos nuestro scheduler!
  containers:
  - name: nginx
    image: nginx
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"

Despliega el primer pod:

kubectl apply -f test-pod-1.yaml

Observa los logs de tu custom-scheduler y el estado del pod:

kubectl logs -f <nombre-del-pod-de-tu-scheduler> -n kube-system
kubectl get pod test-pod-1 -o wide

Ahora, crea un segundo pod con los mismos requisitos, y observa cómo se distribuye:

# test-pod-2.yaml
apiVersion: v1
kind: Pod
metadata:
  name: test-pod-2
  labels:
    app: test-app
spec:
  schedulerName: custom-scheduler
  containers:
  - name: nginx
    image: nginx
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"

Despliega el segundo pod:

kubectl apply -f test-pod-2.yaml

Si tienes al menos dos nodos disponibles, y nuestro scheduler está funcionando correctamente, test-pod-1 y test-pod-2 deberían terminar en diferentes nodos (o al menos, el segundo pod buscará el nodo con menos pods en ese momento, lo que en un clúster de prueba con pocos pods suele significar un nodo diferente). Observa los logs del scheduler para ver las puntuaciones y la decisión de programación.

💡 Consejo: Para una prueba más robusta, puedes crear un `Deployment` con varias réplicas y observar la distribución de los pods. Asegúrate de que el `schedulerName` esté en el `PodTemplateSpec`.
apiVersion: apps/v1
kind: Deployment
metadata:
  name: custom-app-deployment
  labels:
    app: custom-app
spec:
  replicas: 5 # ¡Crea 5 pods!
  selector:
    matchLabels:
      app: custom-app
  template:
    metadata:
      labels:
        app: custom-app
    spec:
      schedulerName: custom-scheduler # ¡Todos programados por nuestro scheduler!
      containers:
      - name: custom-app-container
        image: busybox
        command: ["sh", "-c", "echo Hello Custom Scheduler! && sleep 3600"]
        resources:
          requests:
            memory: "32Mi"
            cpu: "100m"

Limpieza 🗑️

Cuando hayas terminado de probar, elimina los recursos:

k                   ubectl delete deployment custom-scheduler -n kube-system
kubectl delete configmap custom-scheduler-config -n kube-system
k kubectl delete -f custom-scheduler-rbac.yaml
kubectl delete pod test-pod-1 test-pod-2
kubectl delete deployment custom-app-deployment

Consideraciones Avanzadas y Mejores Prácticas 🧠

Crear un scheduler personalizado es una tarea avanzada que requiere una comprensión profunda de Kubernetes y sus implicaciones. Aquí hay algunas consideraciones clave:

  • Rendimiento: Un scheduler personalizado mal optimizado puede ralentizar significativamente el proceso de programación de pods. Minimiza la complejidad computacional de tus plugins de filtrado y puntuación.
  • Estado Consistente: Si tu scheduler necesita mantener un estado (como el conteo de pods en cada nodo), asegúrate de que sea robusto frente a fallos y reinicios. El framework.Handle proporciona acceso a un snapshot consistente del estado del clúster, lo cual es vital.
  • Interacción con el Scheduler Predeterminado: Puedes ejecutar tu scheduler personalizado junto con el Kube-scheduler predeterminado. Algunos pods pueden usar el predeterminado, y otros el tuyo. Esto es útil para migrar o para cargas de trabajo heterogéneas.
  • Actualizaciones de Kubernetes: El Framework de Scheduling puede evolucionar con nuevas versiones de Kubernetes. Asegúrate de que tu plugin sea compatible con las versiones de Kubernetes que utilizas. Esto puede implicar actualizar las dependencias de k8s.io/* en tu go.mod.
  • Observabilidad: Integra métricas, logs y tracing en tu scheduler personalizado para entender su comportamiento, depurar problemas y asegurar su correcto funcionamiento en producción. Los logs de fmt.Printf son buenos para el desarrollo, pero insuficientes para producción.
  • Leader Election: Si despliegas múltiples réplicas de tu scheduler personalizado, necesitarás implementar leaderElection para asegurar que solo una instancia esté activa programando pods en un momento dado y evitar conflictos. El Kube-scheduler app.NewSchedulerCommand lo soporta por defecto, solo asegúrate de habilitarlo en tu configuración (leaderElect: true).
¿Cuándo usar Scheduler Extenders vs. Framework de Scheduling? Los **Scheduler Extenders** son una opción más sencilla si solo necesitas aplicar lógica de filtrado o puntuación a los resultados del scheduler predeterminado y no quieres compilar un binario personalizado. Se comunican vía HTTP con el scheduler existente.

El Framework de Scheduling es más potente y flexible. Te permite controlar cada fase del ciclo de scheduling, añadir tus propias fases, e incluso reemplazar completamente la lógica de binding. Es el enfoque preferido para una personalización profunda o para construir un scheduler completamente nuevo.

Diagrama de Flujo de un Scheduler Personalizado (simplificado)

1. Nuevo Pod schedulerName: custom-scheduler 2. Kube-apiserver Notifica al scheduler personalizado CUSTOM-SCHEDULER 3. Lógica de Filtrado Descarta nodos sin recursos suficientes 4. Lógica de Puntuación Prioriza nodos con menos Pods 5. Binding (Enlace) Asigna el Pod al mejor Nodo elegido 6. Kubelet en el Nodo Detecta asignación e inicia el contenedor del Pod Pod Running

Conclusión ✨

Extender el Kube-scheduler de Kubernetes es una capacidad poderosa que te permite adaptar el comportamiento de programación de pods a las necesidades específicas de tu infraestructura y aplicaciones. Ya sea para optimizar costos, cumplir con requisitos de compliance, o implementar lógicas de negocio complejas, el Framework de Scheduling ofrece la flexibilidad necesaria para construir soluciones robustas.

Recuerda que con gran poder viene gran responsabilidad. Un scheduler personalizado debe ser diseñado y probado cuidadosamente para evitar introducir inestabilidad o bajo rendimiento en tu clúster. Sin embargo, la capacidad de automatizar tareas administrativas complejas y optimizar la utilización de recursos hace que la inversión valga la pena para muchos escenarios avanzados de Kubernetes.

Esperamos que este tutorial te haya proporcionado una base sólida para comenzar a explorar la personalización del Kube-scheduler y te inspire a construir soluciones innovadoras para tus clústeres de Kubernetes.

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!