Un DaemonSet en Kubernetes asegura que una copia de un Pod específico se ejecute en todos los nodos del clúster, o en un subconjunto de ellos. Cuando un nuevo nodo se une al clúster, automáticamente se le asigna una instancia del Pod gestionado por el DaemonSet. De manera similar, si un nodo es eliminado, los Pods asociados a él se desvinculan. La eliminación de un DaemonSet resulta en la supresión de todos los Pods que ha creado.
Los casos de uso típicos para DaemonSet incluyen:
- Implementación de servicios de almacenamiento distribuidos, como
glusterdoceph, en cada nodo. - Despliegue de agantes de recolección de logs, como
fluentdologstash, en todos los nodos. - Ejecución de agentes de monitorización en cada nodo, tales como Prometheus Node Exporter,
collectd, el agente de Datadog o el agente de New Relic.
Es posible configurar múltiples DaemonSets para diferentes propósitos o con configuraciones variadas (por ejemplo, distintos requisitos de CPU/memoria para distintas arquitecturas de hardware), permitiendo una gestión granular de los servicios de fondo.
Definición de un DaemonSet
Al igual que con otros recursos de Kubernetes, un DaemonSet requiere los campos apiVersion, kind y metadata. El campo spec define el comportamiento deseado del DaemonSet.
El componente esencial dentro de spec es template, que es una plantilla de Pod. Esta plantilla comparte el esquema de un objeto Pod, pero carece de los campos apiVersion y kind. Los Pods definidos en la plantilla de un DaemonSet deben incluir etiquetas (labels) que permitan la selección por parte del DaemonSet. Además, la política de reinicio (RestartPolicy) para los Pods en la plantilla de un DaemonSet debe ser Always o no especificarse, asumiendo por defecto Always.
El selector de Pods (spec.selector) especifica qué Pods son gestionados por el DaemonSet. Este selector puede basarse en matchLabels o matchExpressions, permitiendo la definición de criterios de selección más complejos. Es imperativo que el selector definido en spec.selector coincida con las etiquetas especificadas en spec.template.metadata.labels. Si no se proporciona explícitamente, se asume que coinciden por defecto. Kubernetes previene la creación de Pods si sus etiquetas ya coinciden con el selector de un DaemonSet existente o son creados manualmente con el objetivo de ser gestionados por el DaemonSet.
Comunicación entre Pods de DaemonSet
Existen varias estrategias para la comunicación con los Pods gestionados por un DaemonSet:
- Push: Los Pods del
DaemonSetpueden enviar datos a otros servicios o bases de datos de métricas. - NodeIP y Puerto Fijo: Utilizando
hostPort, los Pods pueden ser accesibles directamente a través de la IP del nodo y un puerto conocido. Esto requiere que los clientes tengan conocimiento de la lista de IPs de los nodos. - DNS y Headless Service: La creación de un Service sin clúster (Headless Service) con el mismo selector de Pods que el
DaemonSetpermite la discovery de los Pods delDaemonSeta través de registros DNS o del recursoendpoints. - Service: Un Service estándar, con el mismo selector de Pods, puede enrutar el tráfico a una instancia aleatoria del
DaemonSet. Sin embargo, este método no permite acceder a un nodo específico.
Ejemplo de Despliegue: Redis y Filebeat con DaemonSet
A continuación, se muestra un ejemplo de cómo desplegar un servicio Redis y un agente Filebeat utilizando un DaemonSet. Ambos recursos pueden definirse en el mismo archivo YAML.
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis-deployment
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: redis-cache
tier: backend
template:
metadata:
labels:
app: redis-cache
tier: backend
spec:
containers:
- name: redis-container
image: redis:6.2-alpine
ports:
- containerPort: 6379
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: filebeat-logging
namespace: default
spec:
selector:
matchLabels:
app: filebeat-agent
environment: production
template:
metadata:
labels:
app: filebeat-agent
environment: production
spec:
containers:
- name: filebeat-container
image: elastic/filebeat:7.17.0
env:
- name: REDIS_HOST_ENV
value: "redis-service.default.svc.cluster.local"
- name: LOG_LEVEL_ENV
value: "info"
volumeMounts:
- name: varlog
mountPath: /var/log
- name: containerlogs
mountPath: /var/lib/docker/containers
volumes:
- name: varlog
hostPath:
path: /var/log
- name: containerlogs
hostPath:
path: /var/lib/docker/containers
Para aplicar esta configuración:
kubectl apply -f deploy-redis-filebeat.yaml
Exponer el servicio Redis:
kubectl expose deployment redis-deployment --port=6379 --name=redis-service
Verificar el estado de los Pods:
kubectl get pods -o wide
Comprobar la configuración de Filebeat y la conexión a Redis:
kubectl exec -it <filebeat-pod-name> -- cat /etc/filebeat/filebeat.yml
Dentro del Pod de Redis, se puede verificar la conexión y la recepción de datos:
kubectl exec -it <redis-pod-name> -- redis-cli
127.0.0.1:6379> KEYS *
Actualización de DaemonSet
Los DaemonSets soportan dos estrategias de actualización:
- OnDelete: La estrategia predeterminada por compatibilidad. Con esta opción, las nuevas versiones de los Pods solo se crean manualmente tras eliminar las instancias antiguas.
- RollingUpdate: Con esta estrategia, al actualizar la plantilla del
DaemonSet, los Pods antiguos se terminan y se crean nuevos Pods de manera controlada y automática.
Para habilitar la actualización continua, se debe configurar .spec.updateStrategy.type a RollingUpdate.
Para verificar la estrategia de actualización actual:
kubectl get daemonset <daemonset-name> -o go-template='{{.spec.updateStrategy.type}}{{"\n"}}'
Cualquier modificación en .spec.template de un DaemonSet con estrategia RollingUpdate iniciará el proceso de actualización. Esto se puede realizar mediante comandos declarativos (kubectl apply) o imperativos (kubectl edit, kubectl patch, o kubectl set image).
Ejemplo de actualización de la imagen del contenedor de Filebeat:
kubectl set image daemonset/filebeat-logging filebeat-container=elastic/filebeat:7.17.1
Observar el progreso de la actualización de los Pods:
kubectl get pods -w -l app=filebeat-agent
Durante una actualización RollingUpdate, se observa que los Pods antiguos se van terminando gradualmente a medida que se crean las nuevas instancias, asegurando una transición fluida del servicio.