DaemonSet
Un Deployment responde a la pregunta "¿cuántas copias quiero?". Hay una familia de cargas de trabajo para las que esa pregunta no tiene sentido: el agent de logs, el exportador de métricas, el plugin de red, el driver de almacenamiento. De todos ellos quieres exactamente uno por nodo, ni más ni menos, y quieres que aparezca solo cuando entre un nodo nuevo al clúster.
Ese es el trabajo del DaemonSet. Ya te has cruzado con uno sin saberlo: el kube-proxy de la lección de componentes.
Trabaja desde la pestaña dev-machine.
Paso 1: El DaemonSet
Un agent de logs no sirve de nada si no puede leer los logs, y los logs de los
contenedores están en el disco del nodo. Así que este DaemonSet monta un
directorio de la máquina anfitriona. Crea daemonset.yaml:
cat << 'EOF' > daemonset.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: agent
labels:
app: agent
spec:
selector:
matchLabels:
app: agent
template:
metadata:
labels:
app: agent
spec:
tolerations:
- operator: Exists
volumes:
- name: logs-del-nodo
hostPath:
path: /var/log
containers:
- name: agent
image: ghcr.io/iximiuz/labs/nginx:alpine
command: ["sh", "-c", "while true; do sleep 3600; done"]
volumeMounts:
- name: logs-del-nodo
mountPath: /var/log/host
readOnly: true
resources:
requests:
cpu: 10m
memory: 32Mi
EOF
El YAML, explicado con preguntas y respuestas
¿Dónde está el campo replicas?
No existe, y esa ausencia es toda la lección. El número de Pods no lo decides tú: lo decide el número de nodos. Añade un nodo al clúster y aparecerá un Pod; retira un nodo y desaparecerá. Por eso kubectl scale no funciona sobre un DaemonSet.
Entonces, ¿quién elige el nodo de cada Pod?
El controlador del DaemonSet crea cada Pod con una nodeAffinity que lo ata a un nodo concreto, y el scheduler se limita a confirmar la decisión. Esto importa cuando depuras: un Pod de DaemonSet en Pending no está esperando a que aparezca sitio en cualquier nodo, está esperando a que quepa en su nodo.
¿Por qué esa toleration con operator: Exists?
Porque tolera cualquier taint, y eso es justo lo que quiere un agent de infraestructura: correr también en los nodos reservados o acordonados, incluido el control plane. Recuerda el challenge de scheduling: un taint rechaza a todo el que no lo tolere, y un agent de logs que no vigile el control plane es un agent de logs con un punto ciego. Es el patrón habitual en los DaemonSets de sistema, aunque conviene usarlo a conciencia y no por costumbre.
¿Cómo se actualiza un DaemonSet?
Con updateStrategy, que por defecto es RollingUpdate con maxUnavailable: 1: los Pods se reemplazan nodo a nodo. La alternativa, OnDelete, no toca nada hasta que borras los Pods a mano, y se usa en agentes tan delicados que el reemplazo debe ser manual.
¿Por qué separar el agent en otro Namespace, y no relajar la política de tienda?
Porque la excepción se concede a una pieza, no a un vecindario. Si bajas el nivel
de tienda para que quepa el agent, se lo bajas también al frontend, al backend y
a la base de datos, que no lo necesitan y que son justo los que exponen tráfico al
exterior. Meter la pieza privilegiada en su propio Namespace mantiene la excepción
acotada y visible: quien mire plataforma sabe que ahí dentro hay permisos que en
el resto del clúster no existen.
¿Y si solo quiero el agent en algunos nodos?
Se combina con lo que ya sabes: un nodeSelector o una nodeAffinity en la plantilla. Es la forma habitual de desplegar, por ejemplo, un driver de GPU solo en los nodos que la tienen.
Paso 2: Dónde NO puede vivir
Aplícalo donde estás, que es el Namespace tienda:
kubectl apply -f daemonset.yaml
kubectl get daemonset agent
kubectl get pods -l app=agent
El DaemonSet se crea, pero no aparece ni un Pod. Pregunta por qué:
kubectl get events --field-selector involvedObject.name=agent --sort-by=.lastTimestamp
Ahí está: violates PodSecurity "baseline:latest": hostPath volumes. El
Namespace tienda lleva una etiqueta que obliga a cumplir un nivel mínimo de
seguridad, y montar un directorio del nodo no lo cumple. El objeto existe, el
controlador lo intenta, y la API rechaza cada Pod que crea.
¿Y por qué el error sale en el DaemonSet y no al aplicar el YAML?
Porque quien viola la política no es el DaemonSet, son los Pods, y esos los crea
el controlador después. Es el patrón de todos los controladores: el objeto padre
se admite y los hijos se rechazan de uno en uno. Por eso hay que mirar los
eventos del controlador y no solo la salida del apply.
Ese mecanismo es Pod Security Admission y tiene su propia lección más adelante.
Por ahora quédate con la consecuencia: el agent no cabe en tienda.
Paso 3: Dónde sí
Bórralo y llévalo a plataforma, que es el Namespace que el equipo de
infraestructura reserva para las piezas que necesitan tocar la máquina:
kubectl delete -f daemonset.yaml
kubectl apply -f daemonset.yaml -n plataforma
kubectl get daemonset agent -n plataforma
kubectl get pods -l app=agent -n plataforma -o wide
Un Pod por nodo, cada uno en el suyo. Fíjate en las columnas del DaemonSet: DESIRED, CURRENT, READY y NODE SELECTOR cuentan la misma historia desde el punto de vista del controlador.
Prueba a escalarlo, para que la API te lo explique mejor que yo:
kubectl scale daemonset agent -n plataforma --replicas=1
Resumen
- Un DaemonSet cubre nodos, no réplicas: no existe el campo
replicasni tiene sentido escalarlo. - Un agent que monta
hostPathno cabe en un Namespace conbaseline: por eso vive enplataformay no entienda. La excepción se acota a una pieza en vez de relajarla para todo el vecindario. - El controlador ata cada Pod a su nodo, así que un Pod de DaemonSet en
Pendingespera sitio en ese nodo, no en cualquiera. - Las tolerations le abren la puerta de los nodos reservados; el
nodeSelectorlo limita a los nodos que quieras. - Se actualiza nodo a nodo con
updateStrategy: RollingUpdate, o a mano conOnDelete.
En la siguiente lección, la otra forma de salirse del molde: varios contenedores dentro de un mismo Pod.
- Previous lesson
- StatefulSet
- Next lesson
- Init containers y sidecars