A la lliçó anterior vam resoldre el problema de les càrregues amb estat: el StatefulSet dona a cada rèplica el seu nom, el seu disc i el seu torn. Però hi ha una família de processos per als quals la pregunta "quantes rèpliques vull?" no té sentit. Un recol·lector de logs no necessita tres còpies ni deu: necessita exactament una a cada node, perquè els fitxers de log que ha de llegir són al disc de cada node. Si demà el clúster creix fins a dotze nodes, calen dotze còpies, sense que ningú s'hagi de recordar d'editar un replicas.
Aquesta és la feina del DaemonSet: garantir que hi ha un pod —un, exactament un— per cada node que compleixi uns criteris, i mantenir aquesta invariant quan s'afegeixen o es treuen nodes.
És un objecte que, curiosament, fas servir des de la primera lliçó sense haver-lo mirat: el kube-proxy que vam estudiar a 04-01 i el connector de xarxa del CNI són DaemonSets. En aquesta lliçó els entendrem per dins i desplegarem el primer agent d'infraestructura propi de Rutas Norte: un recol·lector de logs que llegeix els fitxers dels contenidors de tots els nodes.
Contingut
- Una càrrega de treball per node: què és i què la distingeix
- Els quatre casos d'ús canònics
- Anatomia del manifest: per què no hi ha
replicas - On corre:
nodeSelector,affinityitolerations - Accés privilegiat:
hostPath,hostNetwork,hostPIDi el seu cost - Estratègies d'actualització
- Pràctica: recol·lector de logs per a Rutas Norte
- Comprovació: què passa en afegir un node
- Recursos: per què un DaemonSet mal dimensionat és car
- Una càrrega de treball per node: què és i què la distingeix
Un DaemonSet sembla un Deployment en què algú hagués posat replicas igual al nombre de nodes. La diferència és substancial, i està en qui decideix on va cada pod.
Amb un Deployment, el kube-scheduler reparteix les rèpliques segons recursos disponibles, afinitat i equilibri. Res no garanteix que n'hi hagi una per node: en podrien caure tres al mateix node si hi ha lloc. Amb un DaemonSet, el controlador crea un pod amb el node ja assignat, un per cada node elegible, i vigila permanentment aquesta correspondència.
| Aspecte | Deployment | DaemonSet |
|---|---|---|
| Nombre de pods | El que indica replicas |
Un per node elegible; no hi ha replicas |
| Col·locació | La decideix el scheduler | Un per node, per construcció |
| En afegir un node | No canvia res | Apareix un pod nou automàticament |
| En treure un node | El scheduler recol·loca les rèpliques | El pod desapareix amb el node, sense recol·locar |
| Escalat horitzontal | kubectl scale o HPA |
No aplica: escala amb el clúster |
| Cas d'ús | Aplicacions que atenen peticions | Agents d'infraestructura del node |
La conseqüència pràctica més important és l'elasticitat automàtica davant del clúster. Si actives l'autoescalat de nodes (que veurem a 09-03) i el clúster passa de 3 a 20 nodes en un pic d'un pont festiu, els 17 nodes nous reben el seu recol·lector de logs i el seu agent de mètriques sense intervenció humana. Aquesta propietat és la raó de ser de l'objecte.
graph TB
subgraph Node1[node-1]
D1[pod recol·lector] --- L1[/var/log/containers/]
A1[api-reserves]
T1[botiga-web]
end
subgraph Node2[node-2]
D2[pod recol·lector] --- L2[/var/log/containers/]
A2[api-reserves]
end
subgraph Node3[node-3 nou]
D3[pod recol·lector<br/>creat automàticament] --- L3[/var/log/containers/]
end
DS[DaemonSet<br/>recollector-logs] --> D1
DS --> D2
DS --> D3
- Els quatre casos d'ús canònics
Pràcticament tot el que es desplega com a DaemonSet cau en una d'aquestes quatre categories. La regla que les uneix: el pod necessita alguna cosa que només existeix al node on corre.
Recol·lectors de logs
Fluent Bit, Fluentd, Vector, Promtail. Llegeixen els fitxers de /var/log/containers/ del node —on el runtime de contenidors escriu la sortida estàndard de tots els pods d'aquell node—, els enriqueixen amb metadades de Kubernetes i els envien a un backend central.
No podrien ser un Deployment: un pod només pot llegir els fitxers del node on està. Per llegir els logs de tots els nodes cal un pod a cada node.
Agents de mètriques
Node Exporter de Prometheus, agents de Datadog o New Relic. Llegeixen /proc i /sys del node per exposar CPU, memòria, disc, xarxa i temperatura del maquinari. També aquí, la informació és estrictament local al node.
El metrics-server que vam activar com a addon de minikube a 01-04 és un cas diferent: no és un DaemonSet, sinó un Deployment que consulta l'API de cada kubelet. El veurem a 07-02.
Connectors de xarxa del CNI
Calico, Cilium, Flannel, Weave. A 04-01 vam dir que "el CNI programa les rutes del node". Qui ho fa és un pod d'un DaemonSet que escriu configuració a /etc/cni/net.d, instal·la binaris a /opt/cni/bin i manipula les taules de rutes i les regles d'iptables/eBPF del node.
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
kube-proxy 3 3 3 3 3 kubernetes.io/os=linux 14dAquí el tens: kube-proxy, el component que vam estudiar a 04-01 i que tradueix els Services a regles de xarxa, és un DaemonSet. Té tot el sentit: cada node necessita el seu propi conjunt de regles.
Controladors d'emmagatzematge
Els drivers CSI que vam veure a 05-05 es despleguen en dues peces: un controlador (Deployment o StatefulSet, un per clúster, que parla amb l'API del proveïdor per crear volums) i un node plugin (DaemonSet, que munta i desmunta els volums al sistema de fitxers del node). El muntatge només el pot fer algú que estigui en aquell node.
| Categoria | Què necessita del node | Exemples |
|---|---|---|
| Logs | Fitxers a /var/log |
Fluent Bit, Vector, Promtail |
| Mètriques | /proc, /sys, xarxa del host |
Node Exporter, agents APM |
| Xarxa (CNI) | Rutes, iptables, /etc/cni/net.d |
Calico, Cilium, Flannel |
| Emmagatzematge (CSI node) | Muntar al sistema de fitxers | Drivers CSI d'EBS, Ceph, etc. |
| Seguretat | Crides al sistema, auditoria | Falco, agents de detecció |
- Anatomia del manifest: per què no hi ha
replicas
replicasUn DaemonSet mínim:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: recollector-logs
namespace: rutas-norte-pro
spec:
selector:
matchLabels:
app: recollector-logs
entorn: pro
template:
metadata:
labels:
app: recollector-logs
app.kubernetes.io/part-of: rutas-norte
entorn: pro
spec:
containers:
- name: recollector
image: busybox:1.36Comparat amb un Deployment hi falta una sola cosa: spec.replicas. I la seva absència no és un oblit de l'API, és la definició de l'objecte. El nombre de pods no és un valor que tu declaris, és una conseqüència de l'estat del clúster: tants com nodes elegibles hi hagi. Si intentes afegir-lo, l'API el rebutja:
error: error validating "ds.yaml": ValidationError(DaemonSet.spec):
unknown field "replicas" in io.k8s.api.apps.v1.DaemonSetSpecPer la resta, selector i template funcionen igual que en Deployments i StatefulSets, i spec.selector és immutable igual que allà. La template segueix les mateixes convencions de Rutas Norte: només app i entorn al selector, app.kubernetes.io/part-of com a etiqueta informativa.
L'estat es llegeix d'una manera característica:
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
recollector-logs 3 3 3 3 3 <none> 2m| Columna | Significat |
|---|---|
DESIRED |
Nodes elegibles segons selectors, afinitat i tolerations |
CURRENT |
Pods creats |
READY |
Pods que superen les seves sondes de disponibilitat |
UP-TO-DATE |
Pods amb la plantilla actual (no l'anterior) |
AVAILABLE |
Pods llestos durant almenys minReadySeconds |
Si DESIRED és menor del que esperes, el problema està a l'apartat següent: hi ha nodes que el DaemonSet no considera elegibles.
- On corre:
nodeSelector, affinity i tolerations
nodeSelector, affinity i tolerationsPer defecte, "un pod per node" significa tots els nodes. Hi ha tres mecanismes per restringir o ampliar aquest conjunt.
nodeSelector: el filtre simple
Només es creen pods en nodes que tinguin aquesta etiqueta. És el que fa kube-proxy a la sortida d'abans: en un clúster mixt amb nodes Windows, el binari de Linux no serviria.
Un cas propi de Rutas Norte: si a producció hi ha un grup de nodes amb SSD etiquetat disc: ssd, un agent que només interessi allà es restringeix així:
affinity: el filtre expressiu
Quan el filtre no és una igualtat simple, es fa servir afinitat de node. Aquí només el just perquè funcioni; el mecanisme complet, amb els seus operadors i les seves variants preferides, és la lliçó 06-05.
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role.kubernetes.io/analitica
operator: DoesNotExistAquest exemple exclou del recol·lector els nodes dedicats a analítica.
tolerations: el permís per entrar on es rebutja
Aquest és el mecanisme clau dels DaemonSets i val la pena aturar-s'hi.
Alguns nodes porten un taint, una marca que diu "no col·loquis pods aquí llevat que estiguin expressament autoritzats". El cas més habitual és el pla de control, que en un clúster estàndard ve amb:
Aquest taint impedeix que les aplicacions normals acabin competint per CPU amb l'apiserver i etcd. Però un recol·lector de logs sí que hi ha de córrer: els logs de l'apiserver són justament els que més falta fan en una incidència.
La manera de dir "jo sí que puc" és una toleration:
Llegeix-ho així: "tolero el taint la clau del qual existeix amb aquest efecte". No és una preferència per anar al pla de control; és un permís per no ser rebutjat si el DaemonSet m'hi porta.
Els agents d'infraestructura que han de córrer absolutament a tots els nodes, passi el que passi fan servir una toleration universal:
És el que fa el CNI: si el connector de xarxa no corre en un node, aquest node no té xarxa, així que cap condició no ho ha d'impedir. Fes-la servir amb criteri: per al teu recol·lector de logs és probablement excessiva, perquè també el col·locaria en nodes marcats com a no llestos o sota pressió de memòria.
Hi ha a més una cortesia que Kubernetes concedeix automàticament als DaemonSets: el controlador afegeix pel seu compte tolerations a certs taints del sistema (node.kubernetes.io/not-ready, unreachable, disk-pressure, memory-pressure, pid-pressure, unschedulable) perquè els agents no siguin desallotjats justament quan el node té problemes i més falta fa la telemetria. Ho pots veure si inspecciones un pod de DaemonSet:
kubectl get pod -n kube-system -l k8s-app=kube-proxy -o jsonpath='{.items[0].spec.tolerations}' | tr ',' '\n'La mecànica completa de taints, efectes i tolerationSeconds és el contingut de 06-05.
- Accés privilegiat:
hostPath, hostNetwork, hostPID i el seu cost
hostPath, hostNetwork, hostPID i el seu costEls agents de node necessiten veure coses que un pod normal no veu. Kubernetes ho permet, i cada permís amplia la superfície d'atac d'una manera que convé entendre abans d'escriure-la en un manifest.
hostPath
Munta un directori del sistema de fitxers del node dins del contenidor. El vam veure a 05-01 com a volum efímer perillós; aquí és imprescindible.
volumes:
- name: logs-contenidors
hostPath:
path: /var/log/containers
type: Directory
- name: logs-pods
hostPath:
path: /var/log/pods
type: DirectoryDetall tècnic que sorprèn molta gent: els fitxers de /var/log/containers són enllaços simbòlics a /var/log/pods, que al seu torn solen apuntar al directori de dades del runtime (/var/lib/docker/containers o l'equivalent de containerd). Si només muntes el primer, el contenidor veu enllaços trencats. Per això els recol·lectors munten els dos o tres directoris.
Risc: un hostPath amb permís d'escriptura sobre un directori sensible del node (/etc, /var/lib/kubelet, el socket del runtime) equival a control del node. Munta sempre en només lectura (readOnly: true) allò que només necessites llegir.
hostNetwork
El pod comparteix la pila de xarxa del node: els seus ports són ports del node i veu les interfícies reals. És obligatori per als connectors CNI i per a agents que capturen trànsit. Fixa't en el dnsPolicy: sense ell, un pod amb hostNetwork faria servir el DNS del node i perdria la resolució de noms de Kubernetes que vam estudiar a 04-03.
Risc: se salta l'aïllament de xarxa, i amb ell les NetworkPolicies de 04-06, que operen sobre les IP de pod. Un pod amb hostNetwork a rutas-norte-pro no està subjecte al deny-all.
hostPID
El contenidor veu tots els processos del node. Necessari per a agents de perfilat o de seguretat que inspeccionen processos.
Risc: alt. Veure l'arbre de processos del host permet llegir línies d'ordres (que de vegades contenen credencials) i, combinat amb privilegis, entrar a l'espai de noms d'altres contenidors.
| Permís | Per a què es fa servir | Què exposa | Mitigació |
|---|---|---|---|
hostPath (lectura) |
Llegir logs, /proc, /sys |
Contingut del disc del node | readOnly: true, ruta com més específica millor |
hostPath (escriptura) |
Instal·lar binaris CNI, muntar volums | Control efectiu del node | Evitar llevat de drivers d'infraestructura |
hostNetwork |
CNI, captura de trànsit | Tota la xarxa del node; salta NetworkPolicies | Només quan no hi ha alternativa |
hostPID |
Perfilat, seguretat | Processos i els seus arguments | Només agents de seguretat auditats |
privileged: true |
Manipular el kernel | Tot | Preferir capacitats concretes |
La regla d'or: cadascun d'aquests camps converteix el teu DaemonSet en part del pla de confiança del clúster. Un atacant que comprometi la imatge del teu recol·lector de logs amb hostPath d'escriptura té els mateixos poders que si comprometés el kubelet. I com que corre a tots els nodes, el compromís és total, no parcial. A 08-02 endurirem aquests contenidors amb securityContext, capacitats i sistemes de fitxers de només lectura.
- Estratègies d'actualització
Igual que els altres controladors, el DaemonSet defineix com propaga un canvi de plantilla.
RollingUpdate (per defecte)
Actualitza node a node. maxUnavailable limita quants pods poden estar simultàniament no disponibles; amb 1 en un clúster de 3 nodes, l'actualització triga tres passos però mai no deixa més d'un node sense agent.
maxSurge (estable des de 1.25) permet el contrari: crear el pod nou abans de treure el vell, per no deixar cap forat de cobertura. Com que en un node hi caben els dos temporalment, exigeix que tots dos puguin coexistir: si l'agent fa servir hostNetwork amb un port fix, maxSurge: 1 provoca conflicte de ports. Regles:
| Combinació | Comportament | Quan |
|---|---|---|
maxUnavailable: 1, maxSurge: 0 |
Un node sense agent durant l'actualització | Per defecte; agents que poden tenir forats |
maxUnavailable: 0, maxSurge: 1 |
Mai no hi ha forat; solapament temporal | Agents crítics sense ports de host |
maxUnavailable: 25%, maxSurge: 0 |
Més ràpid en clústers grans | Clústers de desenes de nodes |
No poden ser tots dos zero, i no poden ser tots dos diferents de zero.
OnDelete
Els pods s'actualitzen només quan els esborres a mà o quan el node es recrea. És el que és habitual per als connectors CNI: actualitzar la xarxa d'un node en producció és una operació que es vol fer amb supervisió, node a node i amb un pla de marxa enrere.
L'historial i la marxa enrere funcionen com als Deployments de 02-04:
kubectl rollout status daemonset/recollector-logs -n rutas-norte-pro
kubectl rollout history daemonset/recollector-logs -n rutas-norte-pro
kubectl rollout undo daemonset/recollector-logs -n rutas-norte-pro --to-revision=2
- Pràctica: recol·lector de logs per a Rutas Norte
Desplegarem un agent que llegeixi els logs dels contenidors de tots els nodes i —de moment— els mostri per la seva pròpia sortida estàndard. La pila completa de registre centralitzat (Elasticsearch, Fluentd, Kibana) es munta a 07-05; aquí l'objectiu és el DaemonSet, no el destí de les dades.
Fem servir Fluent Bit, lleuger i estàndard a la indústria.
Configuració de l'agent
# k8s/base/recollector-logs-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: recollector-logs-config
namespace: rutas-norte-pro
labels:
app: recollector-logs
app.kubernetes.io/part-of: rutas-norte
entorn: pro
data:
fluent-bit.conf: |
[SERVICE]
Flush 5
Daemon Off
Log_Level info
Parsers_File parsers.conf
[INPUT]
Name tail
Tag rutasnorte.*
Path /var/log/containers/*rutas-norte-pro*.log
Parser cri
DB /var/log/flb-rutasnorte.db
Mem_Buf_Limit 5MB
Skip_Long_Lines On
Refresh_Interval 10
[FILTER]
Name kubernetes
Match rutasnorte.*
Kube_Tag_Prefix rutasnorte.var.log.containers.
Merge_Log On
Keep_Log Off
Labels On
Annotations Off
[OUTPUT]
Name stdout
Match rutasnorte.*
Format json_lines
parsers.conf: |
[PARSER]
Name cri
Format regex
Regex ^(?<time>[^ ]+) (?<stream>stdout|stderr) (?<logtag>[^ ]*) (?<message>.*)$
Time_Key time
Time_Format %Y-%m-%dT%H:%M:%S.%L%zQuè fa cada bloc:
[INPUT] tailsegueix els fitxers de/var/log/containers/el nom dels quals continguirutas-norte-pro. El nom d'aquests fitxers té la forma<pod>_<namespace>_<contenidor>-<id>.log, així que aquest patró filtra per namespace.DBdesa la posició llegida de cada fitxer per no reenviar-ho tot després d'un reinici de l'agent: és un fitxer d'estat, i per això el col·locarem en unhostPath.[FILTER] kubernetesconsulta l'API per afegir a cada línia el pod, el namespace, les etiquetes i el node. Això és el que permetrà filtrar perapp: api-reservesa 07-05, i requereix permisos de lectura sobre pods.[OUTPUT] stdoutbolca a la sortida estàndard del mateix agent. A 07-05 se substituirà per un output cap a Elasticsearch.
Permisos
El filtre de Kubernetes necessita llegir pods i namespaces del clúster sencer. Seguint la pràctica de 03-06, una ServiceAccount dedicada:
# k8s/base/recollector-logs-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: recollector-logs
namespace: rutas-norte-pro
labels:
app: recollector-logs
app.kubernetes.io/part-of: rutas-norte
entorn: pro
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: recollector-logs
rules:
- apiGroups: [""]
resources: ["pods", "namespaces"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: recollector-logs
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: recollector-logs
subjects:
- kind: ServiceAccount
name: recollector-logs
namespace: rutas-norte-proNomés verbs de lectura i només sobre dos recursos: el mínim necessari. El detall d'RBAC —què és un ClusterRole, com s'acota, com s'audita— és la lliçó 08-01; aquí el donem fet perquè l'exemple funcioni.
Fixa't que aquest pod sí que necessita muntar el seu token de ServiceAccount, al contrari que gairebé tots els components de Rutas Norte.
El DaemonSet
# k8s/base/recollector-logs-daemonset.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: recollector-logs
namespace: rutas-norte-pro
labels:
app: recollector-logs
app.kubernetes.io/part-of: rutas-norte
entorn: pro
spec:
selector:
matchLabels:
app: recollector-logs
entorn: pro
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 0
minReadySeconds: 10
template:
metadata:
labels:
app: recollector-logs
app.kubernetes.io/part-of: rutas-norte
entorn: pro
spec:
serviceAccountName: recollector-logs
terminationGracePeriodSeconds: 30
priorityClassName: system-node-critical
tolerations:
# Córrer també als nodes del pla de control
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
containers:
- name: fluent-bit
image: fluent/fluent-bit:3.1.9
resources:
requests:
cpu: 50m
memory: 96Mi
limits:
cpu: 200m
memory: 192Mi
securityContext:
runAsNonRoot: false # necessita llegir fitxers del node propietat de root
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
env:
- name: NODE
valueFrom:
fieldRef:
fieldPath: spec.nodeName
volumeMounts:
- name: config
mountPath: /fluent-bit/etc/
readOnly: true
- name: logs-contenidors
mountPath: /var/log/containers
readOnly: true
- name: logs-pods
mountPath: /var/log/pods
readOnly: true
- name: estat
mountPath: /var/log/flb-estat
volumes:
- name: config
configMap:
name: recollector-logs-config
- name: logs-contenidors
hostPath:
path: /var/log/containers
type: Directory
- name: logs-pods
hostPath:
path: /var/log/pods
type: Directory
- name: estat
hostPath:
path: /var/lib/rutasnorte/fluent-bit
type: DirectoryOrCreateDecisions que val la pena explicar:
priorityClassName: system-node-critical: si el node es queda sense memòria, aquest pod no ha de ser el primer a caure. La mecànica de prioritats i desallotjament és contingut de 06-05.- Les tolerations: només la del pla de control. No fem servir
operator: Existssense clau perquè no volem que el recol·lector es col·loqui als nodes d'analítica que taintarem a 06-05. readOnly: trueals doshostPathde logs: l'agent llegeix, no escriu. L'únic amb escriptura és el directori d'estat, i apunta a una ruta pròpia sota/var/lib/rutasnorte, no a un directori del sistema.readOnlyRootFilesystem: trueamb les capacitats a zero: enduriment bàsic que ampliarem a 08-02.- La variable
NODEve de la Downward API de 03-03 i serveix perquè el mateix agent sàpiga on és sense consultar res.
Desplegar i verificar
kubectl apply -f k8s/base/recollector-logs-rbac.yaml
kubectl apply -f k8s/base/recollector-logs-configmap.yaml
kubectl apply -f k8s/base/recollector-logs-daemonset.yaml
kubectl rollout status daemonset/recollector-logs -n rutas-norte-pro
kubectl get pods -n rutas-norte-pro -l app=recollector-logs -o wideNAME READY STATUS RESTARTS AGE IP NODE
recollector-logs-4wq7n 1/1 Running 0 52s 10.244.0.31 rutas-norte
recollector-logs-hk2zp 1/1 Running 0 52s 10.244.1.44 rutas-norte-m02
recollector-logs-t8xrb 1/1 Running 0 52s 10.244.2.22 rutas-norte-m03Un pod per node, i la columna NODE ho confirma: no n'hi ha dos al mateix lloc.
{"date":1754380522.4,"log":"GET /api/reserves/4471 200 12ms","kubernetes":{"pod_name":"api-reserves-6c8f9d4b7-jm2xq","namespace_name":"rutas-norte-pro","container_name":"api","labels":{"app":"api-reserves","entorn":"pro"},"host":"rutas-norte-m02"}}Aquí tens el resultat: una línia de log d'api-reserves enriquida amb el pod, el namespace, les etiquetes i el node. Aquest enriquiment és el que farà possible a 07-05 buscar "tots els errors d'api-reserves en producció de les últimes dues hores".
- Comprovació: què passa en afegir un node
La propietat central del DaemonSet es demostra en trenta segons amb minikube:
NAME STATUS ROLES AGE VERSION
rutas-norte Ready control-plane 9d v1.30.3
rutas-norte-m02 Ready <none> 9d v1.30.3
rutas-norte-m03 Ready <none> 9d v1.30.3
rutas-norte-m04 Ready <none> 38s v1.30.3
NAME READY STATUS RESTARTS AGE NODE
recollector-logs-4wq7n 1/1 Running 0 11m rutas-norte
recollector-logs-hk2zp 1/1 Running 0 11m rutas-norte-m02
recollector-logs-t8xrb 1/1 Running 0 11m rutas-norte-m03
recollector-logs-x9k2d 1/1 Running 0 22s rutas-norte-m04Ningú no ha tocat el manifest. El controlador de DaemonSet vigila els nodes, n'ha vist un de nou elegible i ha creat el seu pod. És exactament el mateix bucle de reconciliació que vam descriure a 01-02, aplicat a la relació "un pod per node".
A l'inrevés funciona igual:
DESIRED ha tornat a 3 tot sol. Fixa't en la diferència amb un Deployment: quan desapareix un node, el Deployment recol·loca les seves rèpliques als altres nodes per mantenir el nombre; el DaemonSet no recol·loca res, perquè el pod tenia sentit únicament en aquell node.
- Recursos: per què un DaemonSet mal dimensionat és car
Aquí hi ha la conseqüència econòmica que s'oblida amb més freqüència.
Quan demanes 200 MiB a un Deployment de tres rèpliques, demanes 600 MiB. Quan demanes 200 MiB a un DaemonSet, demanes 200 MiB × nombre de nodes, i aquest nombre creix amb el clúster.
Un exemple amb números realistes d'un clúster de 50 nodes i quatre DaemonSets habituals:
| Agent | requests.cpu |
requests.memory |
× 50 nodes (CPU) | × 50 nodes (memòria) |
|---|---|---|---|---|
| Recol·lector de logs | 50m | 96Mi | 2,5 CPU | 4,7 GiB |
| Exportador de mètriques | 30m | 64Mi | 1,5 CPU | 3,1 GiB |
| Connector CNI | 100m | 128Mi | 5 CPU | 6,3 GiB |
| Node plugin CSI | 20m | 64Mi | 1 CPU | 3,1 GiB |
| Total | 10 CPU | 17,2 GiB |
Deu CPU i disset gigues reservats abans de desplegar una sola línia de codi de Rutas Norte. I són requests: capacitat apartada pel scheduler, s'estigui fent servir o no, que redueix el que queda per a api-reserves i botiga-web.
Pitjor encara si t'equivoques per excés. Posar requests.memory: 512Mi en un recol·lector que consumeix 60 MiB "per si de cas" malbarata 22 GiB en aquell clúster: més d'un node sencer pagat per no res.
Consells concrets:
- Mesura abans de fixar. Desplega amb requests baixos a
rutas-norte-dev, observa ambkubectl top pod(07-02) durant uns dies de trànsit real i ajusta. - Posa límits de memòria sempre. Un recol·lector amb una fuita o amb un pic de logs pot consumir tota la memòria del node i provocar desallotjaments de les aplicacions.
Mem_Buf_Limita la configuració de Fluent Bit és la primera línia de defensa; ellimits.memorydel contenidor és la segona. - Compte amb els límits de CPU. Un recol·lector estrangulat per CPU acumula retard i perd línies quan el node està més carregat, que és justament quan més falta fan els logs. Un límit generós o cap és defensable aquí.
- Revisa les quotes. Els
ResourceQuotaper namespace de 03-04 s'apliquen també als pods del DaemonSet. Si la quota derutas-norte-pros'esgota, els pods del DaemonSet en nodes nous no es crearan, i ho descobriràs quan faltin logs. - Pregunta't si de debò en cal un per node. És el consell més rendible. Un agent que consulta l'API de Kubernetes no necessita ser a cada node: en té prou amb un Deployment d'una rèplica.
Errors Comuns i Consells
Intentar posar replicas al manifest. L'API ho rebutja. Si véns de Deployments és la primera ensopegada, i és sa: t'obliga a interioritzar que el nombre de pods el dicta el clúster.
No posar tolerations i perdre els nodes del pla de control. El símptoma és subtil: el DaemonSet apareix 3/3 i tot sembla bé, però falten els logs de l'apiserver. Compara sempre DESIRED amb kubectl get nodes | wc -l.
Muntar només /var/log/containers. Com que són enllaços simbòlics a /var/log/pods, l'agent veu noms però no contingut, i falla amb errors de fitxer no trobat que despisten molt. Munta'ls tots dos.
hostPath amb type incorrecte. Amb type: Directory, si la ruta no existeix al node el pod es queda en ContainerCreating amb un esdeveniment MountVolume.SetUp failed. Per a directoris d'estat propis fes servir DirectoryOrCreate.
Oblidar dnsPolicy: ClusterFirstWithHostNet amb hostNetwork: true. El pod hereta el /etc/resolv.conf del node, deixa de resoldre noms de Services i falla en parlar amb qualsevol component intern. És un error que costa hores de diagnòstic.
maxSurge: 1 amb hostNetwork i port fix. El pod nou no pot arrencar perquè el vell té el port ocupat, i l'actualització es queda bloquejada. Amb hostNetwork fes servir sempre maxSurge: 0.
Creure que un DaemonSet garanteix cobertura durant l'actualització. Amb maxUnavailable: 1 i maxSurge: 0 hi ha sempre un node sense agent durant uns segons. Per a logs d'auditoria això pot ser inacceptable: fes servir maxUnavailable: 0 i maxSurge: 1 si l'agent ho permet.
Consell: kubectl get pods -o wide és la teva verificació. La columna NODE és l'única manera directa de confirmar la invariant "un per node". Afegeix-hi --sort-by=.spec.nodeName per llegir-ho còmode.
Consell: un DaemonSet és un vector d'atac d'abast total. Corre a tots els nodes, sol tenir hostPath i de vegades hostNetwork. Fixa la imatge per etiqueta immutable —millor encara, per digest—, revisa quin RBAC demana i aplica l'enduriment de 08-02 i l'escaneig d'imatges de 08-05.
Exercicis
Exercici 1: DaemonSet mínim i verificació de cobertura
A rutas-norte-dev del teu minikube (perfil rutas-norte, almenys dos nodes), desplega un DaemonSet anomenat sonda-nodes amb busybox:1.36 que cada 30 segons escrigui a la seva sortida estàndard el nom del node (obtingut per Downward API) i l'espai lliure de / del node, llegint /host-arrel muntat en només lectura des de hostPath: /.
Verifica que hi ha un pod per node i consulta'n la sortida.
Exercici 2: tolerations i nodes del pla de control
Comprova si el pod de sonda-nodes s'ha creat al node del pla de control. Si no, esbrina per què i incorpora-hi la toleration necessària. Verifica que DESIRED augmenta.
Exercici 3: actualització sense forats de cobertura
Canvia la imatge de sonda-nodes a busybox:1.37 amb una estratègia que no deixi cap node sense agent en cap moment. Comprova el comportament observant els pods durant l'actualització i explica quina diferència hauries vist amb l'estratègia per defecte.
Solucions
Solució 1
# /tmp/sonda-nodes.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: sonda-nodes
namespace: rutas-norte-dev
labels:
app: sonda-nodes
app.kubernetes.io/part-of: rutas-norte
entorn: dev
spec:
selector:
matchLabels:
app: sonda-nodes
entorn: dev
template:
metadata:
labels:
app: sonda-nodes
app.kubernetes.io/part-of: rutas-norte
entorn: dev
spec:
automountServiceAccountToken: false
containers:
- name: sonda
image: busybox:1.36
command:
- sh
- -c
- 'while true; do echo "$(date +%H:%M:%S) node=$NODE lliure=$(df -h /host-arrel | tail -1 | awk "{print \$4}")"; sleep 30; done'
env:
- name: NODE
valueFrom:
fieldRef:
fieldPath: spec.nodeName
resources:
requests:
cpu: 10m
memory: 16Mi
limits:
cpu: 50m
memory: 32Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
volumeMounts:
- name: arrel-host
mountPath: /host-arrel
readOnly: true
volumes:
- name: arrel-host
hostPath:
path: /
type: Directorykubectl apply -f /tmp/sonda-nodes.yaml
kubectl rollout status daemonset/sonda-nodes -n rutas-norte-dev
kubectl get pods -n rutas-norte-dev -l app=sonda-nodes -o wide
kubectl logs -n rutas-norte-dev daemonset/sonda-nodes --tail=2NAME READY STATUS RESTARTS AGE NODE
sonda-nodes-c4m8p 1/1 Running 0 25s rutas-norte-m02
sonda-nodes-q7t2v 1/1 Running 0 25s rutas-norte-m03
18:22:41 node=rutas-norte-m02 lliure=12.4GEls recursos són mínims a propòsit: és un DaemonSet i tot el que demani es multiplica pel nombre de nodes.
Solució 2
NAME STATUS ROLES AGE VERSION
rutas-norte Ready control-plane 9d v1.30.3
rutas-norte-m02 Ready <none> 9d v1.30.3
rutas-norte-m03 Ready <none> 9d v1.30.3
NAME DESIRED CURRENT READY AGE
sonda-nodes 2 2 2 3mTres nodes però DESIRED és 2: falta el pla de control. La causa:
(En un minikube d'un sol node aquest taint no hi és posat, precisament perquè s'hi puguin desplegar aplicacions; en un clúster multinode o real sí que apareix.)
La solució és tolerar aquest taint:
kubectl apply -f /tmp/sonda-nodes.yaml
kubectl get daemonset sonda-nodes -n rutas-norte-dev
kubectl get pods -n rutas-norte-dev -l app=sonda-nodes -o wideNAME DESIRED CURRENT READY AGE
sonda-nodes 3 3 3 5m
NAME READY STATUS RESTARTS AGE NODE
sonda-nodes-c4m8p 1/1 Running 0 5m rutas-norte-m02
sonda-nodes-q7t2v 1/1 Running 0 5m rutas-norte-m03
sonda-nodes-zr9kd 1/1 Running 0 14s rutas-norteObserva que la toleration no atreu el pod cap al pla de control: simplement deixa d'excloure'l. Qui l'hi col·loca és la lògica d'"un per node" del DaemonSet.
Solució 3
Per no deixar forats cal crear abans de destruir, és a dir maxUnavailable: 0 i maxSurge: 1. És viable perquè sonda-nodes no fa servir hostNetwork ni ports de host, així que dos pods poden coexistir un instant al mateix node.
kubectl patch daemonset sonda-nodes -n rutas-norte-dev -p '{
"spec": {
"updateStrategy": {
"type": "RollingUpdate",
"rollingUpdate": {"maxUnavailable": 0, "maxSurge": 1}
}
}
}'
# Observar en un altre terminal
kubectl get pods -n rutas-norte-dev -l app=sonda-nodes -o wide --watchkubectl set image daemonset/sonda-nodes -n rutas-norte-dev sonda=busybox:1.37
kubectl rollout status daemonset/sonda-nodes -n rutas-norte-devDurant l'actualització s'observa aquest patró a cada node:
sonda-nodes-c4m8p 1/1 Running 0 8m rutas-norte-m02 (vell)
sonda-nodes-n5j3w 0/1 Pending 0 0s rutas-norte-m02 (nou)
sonda-nodes-n5j3w 1/1 Running 0 4s rutas-norte-m02 (nou, llest)
sonda-nodes-c4m8p 1/1 Terminating 0 8m rutas-norte-m02 (vell, es retira)El pod nou arriba a Running abans que el vell comenci a terminar: en cap moment el node m02 no es queda sense agent.
Amb l'estratègia per defecte (maxUnavailable: 1, maxSurge: 0) l'ordre seria l'invers: primer Terminating el vell, després la descàrrega de la imatge nova i l'arrencada. Entre tots dos moments —que amb una imatge no cauada poden ser desenes de segons— aquell node no té recol·lector i els seus logs es perden.
Conclusió
El DaemonSet és el controlador de les càrregues de treball que pertanyen al node, no a l'aplicació. Es distingeix d'un Deployment en què no té replicas: el nombre de pods és una conseqüència de quants nodes elegibles hi ha, i s'ajusta sol quan el clúster creix o minva. Els quatre usos canònics —logs, mètriques, connectors CNI i node plugins CSI— comparteixen un tret: necessiten alguna cosa que només existeix al node on corren, i per això kube-proxy i el mateix CNI del teu clúster són DaemonSets.
Hem vist com s'acota el seu abast amb nodeSelector i affinity, i sobretot com s'amplia amb tolerations per arribar als nodes del pla de control. Hem revisat el preu dels permisos privilegiats que aquests agents solen demanar (hostPath, hostNetwork, hostPID), que converteixen el DaemonSet en part del pla de confiança del clúster. Hem desplegat el recol·lector de logs de Rutas Norte —el destí final del qual muntarem a 07-05—, comprovat que un node nou rep el seu pod sense intervenció, i calculat per què uns requests mal ajustats es multipliquen pel nombre de nodes fins a costar servidors sencers.
Ens queda tota una família de càrregues de treball per cobrir. Deployments, StatefulSets i DaemonSets tenen una cosa en comú: els seus pods estan pensats per córrer indefinidament, i si un contenidor acaba, el sistema ho considera una fallada i el reinicia. Però hi ha feina que consisteix precisament a acabar: generar un informe, migrar un esquema, processar un lot. Per a això Kubernetes té dos altres objectes, i amb ells desplegarem per fi el component que falta a Rutas Norte, informes-ocupacio. És el tema de la lliçó següent: Treballs i CronJobs.
Curs de Kubernetes
Mòdul 1: Introducció a Kubernetes
- Què és Kubernetes?
- Arquitectura de Kubernetes
- Conceptes i Terminologia Clau
- Configuració d'un Clúster de Kubernetes
- La CLI de Kubernetes: kubectl
- Objectes, Manifests YAML i el Model Declaratiu
- El Projecte del Curs: la Plataforma Rutas Norte
Mòdul 2: Components Principals de Kubernetes
- Pods
- ReplicaSets
- Deployments
- Actualitzacions, Rollbacks i Estratègies de Desplegament
- Serveis
- Namespaces
- Etiquetes, Selectors i Anotacions
Mòdul 3: Gestió de Configuració i Secrets
- ConfigMaps
- Secrets
- Variables d'Entorn
- Quotes i Límits de Recursos
- LimitRanges i Classes de Qualitat de Servei (QoS)
- ServiceAccounts i Accés a l'API des dels Pods
Mòdul 4: Xarxes a Kubernetes
- Xarxes de Clúster
- Tipus de Serveis
- DNS Intern i Descobriment de Serveis
- Controladors d'Ingress
- TLS i Gestió de Certificats amb cert-manager
- Polítiques de Xarxa
Mòdul 5: Emmagatzematge a Kubernetes
- Volums
- Volums Persistents
- Reclamacions de Volums Persistents
- Classes d'Emmagatzematge
- Aprovisionament Dinàmic, Expansió i Snapshots
- Còpies de Seguretat i Restauració de Dades
Mòdul 6: Conceptes Avançats de Kubernetes
- StatefulSets
- DaemonSets
- Treballs i CronJobs
- Init Containers, Sidecars i Patrons Multicontenidor
- Planificació: Afinitat, Taints i Toleracions
- Definicions de Recursos Personalitzats (CRDs)
- Operadors i el Patró Controlador
Mòdul 7: Monitoratge i Registre
- Verificacions de Salut i Sondes
- Servidor de Mètriques i kubectl top
- Monitoratge amb Prometheus
- Visualització i Alertes amb Grafana i Alertmanager
- Registre Centralitzat amb Elasticsearch, Fluentd i Kibana (EFK)
- Depuració d'Aplicacions i Esdeveniments del Clúster
Mòdul 8: Seguretat a Kubernetes
- Control d'Accés Basat en Rols (RBAC)
- Contextos de Seguretat i Enduriment del Contenidor
- Polítiques de Seguretat de Pods i Pod Security Standards
- Seguretat de Xarxa
- Seguretat d'Imatges
- Auditoria, Escaneig i Gestió de Vulnerabilitats
Mòdul 9: Escalat i Rendiment
- Autoescalat Horitzontal de Pods
- Autoescalat Vertical de Pods
- Autoescalat de Clúster
- Escalat per Esdeveniments i Mètriques Personalitzades amb KEDA
- Alta Disponibilitat: PodDisruptionBudgets i Topologia
- Ajust de Rendiment
Mòdul 10: Ecosistema i Eines de Kubernetes
- Minikube i Entorns Locals amb kind
- Kubeadm
- Helm
- Kustomize
- GitOps amb Argo CD i Flux
- Kubernetes Gestionat: EKS, AKS i GKE
Mòdul 11: Estudis de Cas i Aplicacions del Món Real
- Desplegament d'una Aplicació Web
- Execució d'Aplicacions amb Estat
- CI/CD amb Kubernetes
- Estratègies de Desplegament: Blue-Green i Canary
- Gestió Multi-Clúster
- Operació en Producció: Incidències, Runbooks i Costos
