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.
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.
¿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.
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:
- 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.
- 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.
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
schedulerNameen 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.
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).
kubectlconfigurado 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)
}
}
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
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.
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.Handleproporciona 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 tugo.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.Printfson buenos para el desarrollo, pero insuficientes para producción. - Leader Election: Si despliegas múltiples réplicas de tu scheduler personalizado, necesitarás implementar
leaderElectionpara asegurar que solo una instancia esté activa programando pods en un momento dado y evitar conflictos. El Kube-schedulerapp.NewSchedulerCommandlo 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)
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
- Optimización de Recursos en Kubernetes: Limitando y Solicitando CPU/Memoria para tus Contenedores 🚀intermediate15 min
- Gestión de Configuración en Kubernetes: ConfigMaps y Secrets para Aplicaciones Robustas ⚙️intermediate18 min
- Gestión de Redes en Kubernetes: Servicios, Ingress y Network Policies 🌐intermediate15 min
- Gestión de Estado Persistente en Kubernetes: Almacenamiento Duradero con PVC y StorageClasses 💾intermediate20 min
- Observabilidad Integral en Kubernetes: Monitorización, Logs y Tracing con Prometheus, Grafana y Jaeger 📊intermediate20 min
Comentarios (0)
Aún no hay comentarios. ¡Sé el primero!