Les DaemonSets Kubernetes garantissent qu'une copie d'un Pod s'exécute sur tous les nœuds du cluster ou sur certains d'entre eux. Ce guide va au-delà des bases pour couvrir les mécanismes internes avancés des DaemonSets, les subtilités de planification, les stratégies de mise à jour, la gestion des ressources et le dépannage opérationnel. Nous allons suivre un exemple réaliste d'exécution d'un agent de surveillance de nœud en tant que DaemonSet, avec des commandes concrètes, des configurations et des étapes de vérification.

## Introduction

Les DaemonSets sont un contrôleur de charge de travail essentiel dans Kubernetes. Bien que de nombreuses équipes les utilisent pour la collecte de journaux, la surveillance ou les plugins de stockage, peu exploitent pleinement leurs capacités avancées. Comprendre les mécanismes internes des DaemonSets aide les opérateurs à éviter les pièges courants tels que des Pods s'exécutant sur des nœuds non prévus, des tempêtes de mises à jour ou des conflits de ressources. Ce guide propose une analyse approfondie des concepts avancés des DaemonSets, notamment l'affinité de nœuds, les stratégies de déploiement, la gestion des ressources et les diagnostics. À la fin, vous serez en mesure de concevoir des DaemonSets robustes, efficaces et faciles à exploiter en production.

Nous utiliserons un scénario pratique : le déploiement d'un agent de surveillance de nœud (comme l'exportateur de nœuds Prometheus) en tant que DaemonSet. Les principes s'appliquent à tout service au niveau du nœud.

## Inventaire de la version et de l'environnement

Avant de mettre en œuvre les fonctionnalités avancées des DaemonSets, vérifiez la version de votre cluster et votre environnement. Certaines fonctionnalités, comme `rollingUpdate` avec `maxSurge` et `maxUnavailable`, nécessitent Kubernetes 1.12 ou plus. D'autres, comme `podAffinity` ou `topologySpreadConstraints`, ont évolué au fil des versions. Vérifiez la version de votre serveur d'API :

```bash
kubectl version --short
```

Sortie attendue (exemple) :

```
Client Version: v1.24.3
Server Version: v1.25.2
```

Assurez-vous que `kubectl` est configuré avec les autorisations RBAC appropriées pour les DaemonSets (cluster-admin ou équivalent). Testez la connectivité :

```bash
kubectl get nodes
```

Exemple de sortie :

```
NAME     STATUS   ROLES                  AGE   VERSION
node-1   Ready    control-plane,master   10d   v1.25.2
node-2   Ready    <none>                 10d   v1.25.2
node-3   Ready    <none>                 10d   v1.25.2
```

Si votre cluster a des rôles de nœuds mixtes ou des teintes (taints), identifiez-les car les DaemonSets doivent tolérer ces teintes pour planifier des Pods sur certains nœuds. Par exemple, les nœuds du plan de contrôle ont souvent une teinte `node-role.kubernetes.io/control-plane`. Vous pouvez lister les teintes :

```bash
kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints
```

Exemple de sortie :

```
NAME     TAINTS
node-1   [map[effect: NoSchedule key: node-role.kubernetes.io/control-plane]]
node-2   <none>
node-3   <none>
```

Enfin, notez le runtime de conteneur et toutes les ressources au niveau du nœud auxquelles vous devez accéder. Pour notre agent de surveillance, nous monterons des chemins du système de fichiers hôte, donc les paramètres de sécurité du Pod doivent le permettre.

## Chemin de configuration sûr

La configuration avancée d'un DaemonSet implique plusieurs domaines clés : la planification sur les nœuds, la stratégie de mise à jour, la gestion des ressources et la sécurité. Nous mettrons en œuvre chaque étape de manière incrémentale, en vérifiant au fur et à mesure.

### Étape 1 : DaemonSet de base avec sélecteur de nœud et tolérances

Commencez par un DaemonSet minimal qui cible tous les nœuds Linux. Nous utiliserons un sélecteur de nœud basé sur `kubernetes.io/os: linux` et ajouterons des tolérances pour les teintes courantes afin de garantir que l'agent s'exécute partout. Enregistrez le manifeste suivant sous `daemonset-basic.yaml` :

```yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-agent
  namespace: monitoring
spec:
  selector:
    matchLabels:
      app: node-agent
  template:
    metadata:
      labels:
        app: node-agent
    spec:
      nodeSelector:
        kubernetes.io/os: linux
      tolerations:
        - key: node-role.kubernetes.io/control-plane
          operator: Exists
          effect: NoSchedule
        - key: node-role.kubernetes.io/master
          operator: Exists
          effect: NoSchedule
      containers:
        - name: agent
          image: quay.io/prometheus/node-exporter:v1.3.1
          ports:
            - containerPort: 9100
              name: metrics
          resources:
            requests:
              cpu: 100m
              memory: 64Mi
            limits:
              cpu: 200m
              memory: 128Mi
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            runAsNonRoot: true
            runAsUser: 65534
```

Appliquez et vérifiez :

```bash
kubectl create namespace monitoring
kubectl apply -f daemonset-basic.yaml
kubectl get daemonset node-agent -n monitoring
```

Sortie attendue :

```
NAME          DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
node-agent    3         3         3       3            3           <none>          10s
```

### Étape 2 : Planification avancée avec affinité de nœud

Les sélecteurs de nœud sont simples mais limités. Utilisez `nodeAffinity` pour des règles plus expressives, comme la planification sur des nœuds avec des étiquettes spécifiques ou l'exclusion d'autres. Par exemple, supposons que nous voulions éviter les nœuds dans un pool préemptible. Étiquetez d'abord les nœuds en conséquence (si ce n'est pas déjà fait) :

```bash
kubectl label node node-2 node-pool=regular
kubectl label node node-3 node-pool=regular
kubectl label node node-1 node-pool=control-plane
```

Mettez ensuite à jour la spécification du DaemonSet pour inclure `affinity.nodeAffinity` :

```yaml
spec:
  template:
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: node-pool
                    operator: In
                    values: [regular, control-plane]
      nodeSelector: {}
      tolerations: ...  # comme avant
```

Appliquez le changement :

```bash
kubectl apply -f daemonset-basic.yaml
```

Vérifiez l'état du déploiement :

```bash
kubectl rollout status daemonset node-agent -n monitoring
```

Sortie attendue :

```
daemon set "node-agent" successfully rolled out
```

### Étape 3 : Stratégie de mise à jour progressive

Par défaut, les DaemonSets utilisent `RollingUpdate` avec `maxUnavailable: 1` et `maxSurge: 0`. Cela signifie qu'un nœud à la fois est mis à jour, garantissant la disponibilité. Pour les grands clusters, vous pouvez vouloir des mises à jour plus rapides. Configurez explicitement la stratégie de mise à jour. Dans notre DaemonSet, ajoutez :

```yaml
spec:
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 0
```

C'est la valeur par défaut, mais être explicite documente l'intention. Si votre DaemonSet n'est pas critique et que vous avez de la capacité de réserve, vous pourriez définir `maxUnavailable: 2` ou `maxSurge: 1` (nécessite K8s 1.22+ pour le surge). Pour l'instant, gardez un à la fois. Appliquez et observez le déploiement :

```bash
kubectl apply -f daemonset-basic.yaml
kubectl get pods -n monitoring -o wide --watch
```

Observez le remplacement contrôlé : les anciens Pods sont terminés après que les nouveaux Pods deviennent prêts.

### Étape 4 : Gestion des ressources et priorité

Les Pods de DaemonSet peuvent être gourmands en ressources. Définissez soigneusement les requêtes et les limites. Dans notre exemple, les requêtes sont modestes. Envisagez également de définir un `priorityClassName` pour garantir que les Pods de DaemonSet soient planifiés même sous pression de ressources. Créez une PriorityClass si elle n'existe pas :

```yaml
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: node-agent-priority
value: 1000000
globalDefault: false
description: "Priorité pour les agents de nœud"
```

Ajoutez ensuite `priorityClassName: node-agent-priority` au modèle de Pod. Appliquez les deux.

### Étape 5 : Sécurité du Pod et accès à l'hôte

Pour accéder aux métriques de l'hôte, notre conteneur peut avoir besoin de lire `/proc` et `/sys`. L'exportateur de nœuds s'exécute généralement en tant que root ou avec des capacités. Nous avons utilisé un utilisateur non root, ce qui peut limiter les fonctionnalités. Si vous avez besoin de montages `hostPath`, ajoutez-les et ajustez `securityContext`. Pour la démonstration, nous restons simples, mais soyez conscient des normes de sécurité des Pods. Dans les clusters avec Pod Security Admission, vous devrez peut-être définir des étiquettes de namespace sur `pod-security.kubernetes.io/enforce=privileged` ou utiliser une politique personnalisée.

## Vérification et diagnostics

Après le déploiement, vérifiez que les Pods de DaemonSet s'exécutent correctement sur chaque nœud attendu.

- Vérifiez l'état du DaemonSet :

```bash
kubectl get ds node-agent -n monitoring -o wide
```

La sortie attendue montre DESIRED, CURRENT, READY, etc., et NODE SELECTOR/AFFINITY si défini.

- Assurez-vous que chaque nœud a un Pod :

```bash
kubectl get pods -n monitoring -o wide --selector=app=node-agent
```

Exemple :

```
NAME              READY   STATUS    RESTARTS   AGE    IP          NODE
node-agent-abcde   1/1     Running   0          2m     10.0.1.10   node-1
node-agent-fghij   1/1     Running   0          2m     10.0.2.10   node-2
node-agent-klmno   1/1     Running   0          2m     10.0.3.10   node-3
```

Comparez avec la liste des nœuds : chaque nœud prêt doit avoir un Pod (sauf exclusion par planification).

- Vérifiez les journaux de l'agent :

```bash
kubectl logs daemonset/node-agent -n monitoring --tail=10
```

Cela renvoie les journaux d'un Pod aléatoire. Pour vérifier un Pod spécifique :

```bash
kubectl logs node-agent-abcde -n monitoring
```

- Vérifiez le point de terminaison des métriques :

Redirigez le port vers un Pod :

```bash
kubectl port-forward pod/node-agent-abcde 9100:9100 -n monitoring
```

Puis dans un autre terminal :

```bash
curl localhost:9100/metrics | head
```

La sortie attendue inclut des lignes comme :

```
# HELP node_cpu_seconds_total Seconds the CPUs spent in each mode.
# TYPE node_cpu_seconds_total counter
node_cpu_seconds_total{cpu="0",mode="idle"} 12345.67
```

- Décrivez le DaemonSet pour vérifier les événements :

```bash
kubectl describe ds node-agent -n monitoring
```

Recherchez des événements indiquant des échecs de planification ou d'autres problèmes.

## Modes de défaillance et récupération

Les DaemonSets peuvent échouer de plusieurs manières. Les problèmes courants incluent :

- **Échecs de planification** : Un nœud peut ne pas recevoir de Pod s'il a des teintes non tolérées, ou si les règles de sélecteur de nœud/affinité l'excluent. Vérifiez l'état du DaemonSet : DESIRED > CURRENT indique des Pods manquants.
- **Erreurs d'extraction d'image** : Si l'image est incorrecte ou le registre inaccessible, les Pods seront en `ImagePullBackOff`. Vérifiez `kubectl describe pod` pour plus de détails.
- **CrashLoopBackOff** : Le conteneur démarre mais se termine à plusieurs reprises. Inspectez les journaux et ajustez la commande ou les ressources.
- **Mise à jour bloquée** : Une mise à jour progressive peut se bloquer si un nouveau Pod ne devient jamais prêt. Cela peut se produire en raison d'un échec de sonde de préparation ou de contraintes de ressources. Vérifiez `kubectl rollout status` et les états des Pods.
- **Cordon/drain de nœud** : Si un nœud est cordonné, les Pods de DaemonSet peuvent quand même être planifiés (sauf si la teinte `node.kubernetes.io/unschedulable` est ajoutée). Comprenez le cycle de vie des nœuds.

### Retour en arrière

Pour annuler une mise à jour de DaemonSet, revenez au manifeste précédent ou utilisez `kubectl rollout undo` :

```bash
kubectl rollout undo daemonset node-agent -n monitoring
```

Cela fonctionne si vous avez l'historique des déploiements activé (les DaemonSets prennent en charge l'historique depuis 1.15). Vérifiez l'historique des déploiements :

```bash
kubectl rollout history daemonset node-agent -n monitoring
```

Puis annulez vers une révision spécifique si nécessaire :

```bash
kubectl rollout undo daemonset node-agent -n monitoring --to-revision=2
```

### Suppression forcée

Si un DaemonSet est bloqué, vous devrez peut-être le supprimer et le réappliquer. Utilisez `--cascade=orphan` pour garder les Pods en cours d'exécution temporairement, puis supprimez les Pods manuellement :

```bash
kubectl delete ds node-agent -n monitoring --cascade=orphan
kubectl delete pods -n monitoring -l app=node-agent
```

Ensuite, réappliquez le manifeste corrigé.

### Vérification de la récupération

Après le retour en arrière, vérifiez que le DaemonSet est de nouveau sain :

```bash
kubectl rollout status daemonset node-agent -n monitoring
kubectl get ds node-agent -n monitoring
```

La sortie attendue montre READY égal à DESIRED.

## Liste de contrôle des opérations

Utilisez cette liste de contrôle pour les opérations et revues régulières des DaemonSets.

| Tâche | Commande/Action | Résultat attendu |
|------|----------------|-----------------|
| Vérifier l'état du DaemonSet | `kubectl get ds -n monitoring` | DESIRED égal à CURRENT et READY |
| Vérifier la distribution des Pods | `kubectl get pods -n monitoring -o wide` | Un Pod par nœud éligible |
| Vérifier les Pods en attente | `kubectl get pods -n monitoring --field-selector=status.phase=Pending` | Aucun Pod listé |
| Examiner les événements récents | `kubectl describe ds node-agent -n monitoring` | Aucun événement d'avertissement |
| Inspecter les journaux pour les erreurs | `kubectl logs daemonset/node-agent -n monitoring --tail=50` | Aucun motif d'erreur répété |
| Valider l'utilisation des ressources | `kubectl top pods -n monitoring -l app=node-agent` | CPU/Mémoire dans les limites |
| Vérifier l'historique des déploiements | `kubectl rollout history daemonset node-agent -n monitoring` | Plusieurs révisions disponibles |
| Tester la mise à jour avec un canari | Ajouter temporairement un sélecteur de nœud pour cibler un seul nœud, mettre à jour, puis étendre | Déploiement réussi sur le nœud canari |

Effectuez cette liste de contrôle après tout changement de cluster, mise à niveau ou incident.

## Pièges courants et comment les éviter

La gestion des DaemonSets fait souvent trébucher les équipes de manière prévisible. Voici les erreurs les plus fréquentes et comment les éviter.

### Ignorer les teintes sur les nouveaux nœuds

**Ce qui se passe mal** : Un nouveau nœud est ajouté avec une teinte que le DaemonSet ne tolère pas. Le DaemonSet n'y planifie jamais de Pod, ce qui conduit à une infrastructure non surveillée. Cela se produit généralement lorsque les équipes ajoutent des groupes de mise à l'échelle automatique ou remplacent des nœuds sans examiner les teintes.

**Comment l'éviter** : Incluez des tolérances larges pour les teintes courantes de rôle de nœud et de matériel dans votre spécification DaemonSet. Auditez régulièrement les nœuds et les teintes avec `kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints`. Configurez une alerte si DESIRED dépasse CURRENT dans `kubectl get ds`.

### Stratégies de mise à jour trop agressives

**Ce qui se passe mal** : Définir `maxUnavailable: 100%` ou `maxSurge: 100%` pour accélérer les mises à jour peut entraîner le remplacement simultané de tous les Pods, provoquant une brève panne du service au niveau du nœud. Pour les agents critiques comme les expéditeurs de journaux ou les plugins CNI, cela perturbe l'observabilité ou le réseau.

**Comment l'éviter** : Utilisez la valeur par défaut `maxUnavailable: 1` pour les DaemonSets critiques. Si vous avez besoin de mises à jour plus rapides, testez d'abord avec un nœud canari (voir liste de contrôle) et surveillez l'état du déploiement.

### Négliger les requêtes et limites de ressources

**Ce qui se passe mal** : Sans requêtes de ressources, le planificateur peut placer les Pods de DaemonSet sur des nœuds déjà sous pression. Sans limites, un agent incontrôlable peut consommer le CPU ou la mémoire du nœud, affectant d'autres charges de travail.

**Comment l'éviter** : Définissez toujours `resources.requests` et `limits` appropriés pour l'agent. Utilisez régulièrement `kubectl top pods` pour vérifier l'utilisation réelle. Envisagez `priorityClassName` pour garantir la planification.

### Oublier de mettre à jour les politiques de sécurité des Pods

**Ce qui se passe mal** : Dans les clusters avec Pod Security Admission (PSA) ou les anciennes PodSecurityPolicy (PSP), les Pods de DaemonSet qui nécessitent un accès privilégié (par exemple, monter `/proc` de l'hôte) ne démarrent pas lorsque les politiques de namespace sont appliquées. Cela se manifeste par `CreateContainerConfigError` ou un refus d'admission.

**Comment l'éviter** : Examinez les capacités requises pour votre agent. Utilisez le moindre privilège possible, mais si l'accès à l'hôte est nécessaire, étiquetez le namespace avec le niveau PSA approprié (par exemple, `pod-security.kubernetes.io/enforce=privileged` pour le namespace de surveillance). Documentez clairement les exceptions.

### Oublier les effets du cordon ou du drain de nœud

**Ce qui se passe mal** : Lorsqu'un nœud est cordonné, beaucoup supposent que les Pods de DaemonSet n'y seront pas planifiés, mais les DaemonSets contournent le drapeau non planifiable à moins que le nœud n'ait la teinte `node.kubernetes.io/unschedulable`. Inversement, lors d'un drain, les Pods de DaemonSet sont souvent ignorés par `kubectl drain` à moins que `--ignore-daemonsets` ne soit défini, ce qui crée une confusion sur les Pods restants.

**Comment l'éviter** : Comprenez les sémantiques de cordon par rapport aux teintes. Utilisez `kubectl drain --ignore-daemonsets` lors de l'évacuation des charges de travail régulières, et supprimez manuellement les Pods de DaemonSet si nécessaire. Documentez les procédures de maintenance des nœuds dans les runbooks.

## Planification avancée avec contraintes de répartition topologique

Au-delà de l'affinité de nœud, considérez `topologySpreadConstraints` pour répartir uniformément les Pods de DaemonSet entre les domaines de défaillance comme les zones. Bien qu'un DaemonSet place déjà un Pod par nœud, les contraintes de répartition deviennent utiles lorsque vous avez plusieurs DaemonSets qui ne devraient pas cohabiter, ou lorsque les nœuds sont regroupés en zones logiques. Par exemple, pour répartir notre agent de surveillance entre les zones de disponibilité :

```yaml
spec:
  template:
    spec:
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: topology.kubernetes.io/zone
          whenUnsatisfiable: DoNotSchedule
          labelSelector:
            matchLabels:
              app: node-agent
```

Cela garantit que si les zones ont un nombre inégal de nœuds, le planificateur essaie de maintenir le nombre de Pods dans chaque zone à un près les uns des autres. Appliquez et vérifiez avec `kubectl get pods -n monitoring -o wide --sort-by=.spec.nodeName`.

## Surveillance et alertes pour les DaemonSets

Pour garder les DaemonSets sains, intégrez-les dans votre pile de surveillance. Voici les métriques clés à suivre (en utilisant Prometheus comme exemple) :

- `kube_daemonset_status_desired_number_scheduled` vs `kube_daemonset_status_current_number_scheduled` : Alertez si desired > current pendant plus de 5 minutes.
- `kube_daemonset_status_number_ready` : Doit être égal à desired.
- `kube_daemonset_status_updated_number_scheduled` : Pendant les mises à jour, cela diffère temporairement ; surveillez les déploiements bloqués.
- Taux de redémarrage des Pods : `rate(kube_pod_container_status_restarts_total{namespace="monitoring"}[5m]) > 0` indique une instabilité.

Configurez des alertes avec ces expressions et routez-les vers votre rotation d'astreinte. Révisez les seuils d'alerte trimestriellement pour éviter le bruit.

## Durcissement de la sécurité

La sécurité des DaemonSets va au-delà de Pod Security Admission. Considérez ces pratiques :

- **Exécuter en non-root** : Définissez toujours `runAsNonRoot: true` sauf si absolument nécessaire. Notre exemple d'exportateur de nœuds utilise UID 65534 (nobody), ce qui fonctionne pour les métriques de base mais peut nécessiter root pour certains collecteurs.
- **Utiliser un système de fichiers racine en lecture seule** : Définissez `readOnlyRootFilesystem: true` pour empêcher toute altération.
- **Supprimer les capacités** : Utilisez `capabilities.drop: ["ALL"]` et ajoutez uniquement celles nécessaires. Pour l'exportateur de nœuds, vous pouvez avoir besoin de `CAP_SYS_TIME` pour certains collecteurs ; testez soigneusement.
- **Limiter l'utilisation de hostPath** : Préférez `hostPath` avec `type` spécifié (par exemple, `Directory` ou `File`) et évitez de monter des chemins sensibles comme `/etc/kubernetes` sauf si nécessaire.
- **Politiques réseau** : Appliquez des NetworkPolicies au namespace pour restreindre le trafic vers et depuis les Pods de DaemonSet.

Par exemple, un contexte de sécurité durci pour notre agent :

```yaml
securityContext:
  allowPrivilegeEscalation: false
  readOnlyRootFilesystem: true
  runAsNonRoot: true
  runAsUser: 65534
  capabilities:
    drop: ["ALL"]
    add: ["CAP_SYS_TIME"]
```

Testez minutieusement dans un cluster de staging avant d'appliquer en production.

## Conclusion

Les concepts avancés des DaemonSets permettent un contrôle précis des charges de travail au niveau des nœuds dans Kubernetes. En utilisant l'affinité de nœud, les tolérances, des stratégies de mise à jour explicites et la gestion des ressources, vous pouvez garantir une opération fiable et efficace. Ce guide a démontré une mise en œuvre pratique avec un agent de surveillance, couvrant la configuration, la vérification, la récupération en cas d'échec et une liste de contrôle opérationnelle. Utilisez ces modèles comme base pour vos propres DaemonSets, en les adaptant à votre environnement et à vos exigences.

Pour en savoir plus, consultez la documentation officielle des DaemonSets Kubernetes et la référence API pour plus de détails sur tous les champs.

## Ressources supplémentaires

- [Documentation des DaemonSets Kubernetes](https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/)
- [Référence API Kubernetes : DaemonSetSpec](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#daemonsetspec-v1-apps)
- [Priorité et préemption des Pods](https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/)
- [Contraintes de répartition topologique des Pods](https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/)
- [Pod Security Admission](https://kubernetes.io/docs/concepts/security/pod-security-admission/)