# Kubernetes FAQ > SdV's FAQ for Kubernetes users. ## CronJobs Kubernetes Les `CronJobs` permettent d'exécuter des tâches planifiées de manière périodique sur un cluster Kubernetes, similairement au `cron` Unix/Linux. Ils sont idéaux pour les traitements batch, les sauvegardes automatiques, les rapports périodiques ou tout job nécessitant une exécution planifiée. ### Introduction Un `CronJob` Kubernetes crée des objets `Job` selon une planification définie au format cron. Chaque `Job` créé déploie un ou plusieurs `Pods` pour exécuter la tâche demandée. #### Architecture et fonctionnement ``` CronJob (schedule: "0 2 * * *") └─> Job-1 (créé à 02:00) └─> Pod-1 (exécution de la tâche) └─> Job-2 (créé à 02:00 le lendemain) └─> Pod-2 (exécution de la tâche) ``` Le contrôleur `CronJob` : * Vérifie [toutes les 10 secondes](https://github.com/kubernetes/kubernetes/blob/392bf0adef478175b9cf0226b02820eb1820f797/pkg/controller/cronjob/cronjob_controller.go#L95-L96) la liste des `CronJobs` du cluster * Calcule si un `Job` doit être créé en fonction du `schedule` configuré * Gère les politiques de concurrence et les délais de démarrage * Conserve l'historique des `Jobs` selon les paramètres de rétention :::info Les `CronJobs` utilisent le fuseau horaire du Control Plane (API Server). Vérifiez ce paramètre si vos exécutions ne correspondent pas à l'horaire attendu. ::: ### Exemple de configuration #### CronJob basique ```yaml showLineNumbers [cronjob.yaml] apiVersion: batch/v1 kind: CronJob metadata: name: backup-database namespace: production labels: app: backup component: database spec: # Exécution tous les jours à 2h du matin schedule: "0 2 * * *" # Politique de concurrence : interdire l'exécution simultanée concurrencyPolicy: Forbid # Nombre de Jobs réussis à conserver dans l'historique successfulJobsHistoryLimit: 3 # Nombre de Jobs échoués à conserver dans l'historique failedJobsHistoryLimit: 5 # Délai maximum après l'heure planifiée pour démarrer le Job startingDeadlineSeconds: 300 # Template du Job à créer jobTemplate: metadata: labels: app: backup component: database spec: # Le Job sera conservé 1 jour après son exécution ttlSecondsAfterFinished: 86400 # Politique de retry : 3 tentatives maximum backoffLimit: 3 # Template du Pod template: metadata: labels: app: backup component: database spec: restartPolicy: OnFailure containers: - name: backup image: postgres:15-alpine command: - /bin/sh - -c - | pg_dump -h $DB_HOST -U $DB_USER -d $DB_NAME | \ gzip > /backup/dump-$(date +%Y%m%d-%H%M%S).sql.gz env: - name: DB_HOST value: "postgresql-service" - name: DB_USER valueFrom: secretKeyRef: name: db-credentials key: username - name: PGPASSWORD valueFrom: secretKeyRef: name: db-credentials key: password - name: DB_NAME value: "production" volumeMounts: - name: backup-volume mountPath: /backup resources: requests: cpu: 100m memory: 256Mi limits: cpu: 500m memory: 512Mi volumes: - name: backup-volume persistentVolumeClaim: claimName: backup-pvc ``` #### Syntaxe du format cron | Champ | Valeurs possibles | Exemple | | ------------------ | ------------------ | ------------------------ | | Minutes | 0-59 | `30` = à la 30ème minute | | Heures | 0-23 | `14` = à 14h | | Jour du mois | 1-31 | `15` = le 15 du mois | | Mois | 1-12 | `6` = juin | | Jour de la semaine | 0-6 (0 = dimanche) | `1` = lundi | **Exemples courants :** ```yaml # Toutes les heures schedule: "0 * * * *" # Tous les jours à 3h30 du matin schedule: "30 3 * * *" # Tous les lundis à 8h schedule: "0 8 * * 1" # Le 1er de chaque mois à minuit schedule: "0 0 1 * *" # Toutes les 15 minutes schedule: "*/15 * * * *" # Du lundi au vendredi à 18h schedule: "0 18 * * 1-5" ``` :::tip Utilisez [crontab.guru](https://crontab.guru/) pour valider et comprendre vos expressions cron. ::: ### Paramètres de configuration clés #### `concurrencyPolicy` Définit le comportement lorsqu'un `Job` est encore en cours d'exécution au moment de la prochaine planification : | Valeur | Comportement | Cas d'usage | | ---------------- | --------------------------------------------------- | -------------------------------------------------- | | `Allow` (défaut) | Autorise l'exécution simultanée de plusieurs `Jobs` | Jobs indépendants, traitement parallèle acceptable | | `Forbid` | Empêche le démarrage si un `Job` est déjà en cours | Traitement exclusif (backup, migration) | | `Replace` | Annule le `Job` en cours et démarre le nouveau | Le dernier Job doit toujours s'exécuter | ```yaml showLineNumbers spec: concurrencyPolicy: Forbid ``` :::warning Avec `concurrencyPolicy: Forbid`, si un `Job` est en cours d'exécution à l'heure planifiée suivante, le nouveau `Job` ne démarrera pas et sera comptabilisé comme **planification manquée** (missed schedule). ::: #### `startingDeadlineSeconds` Délai maximum (en secondes) après l'heure planifiée pendant lequel le `Job` peut encore démarrer. Si ce délai est dépassé, le `Job` ne sera pas créé. ```yaml showLineNumbers spec: # Le Job peut démarrer jusqu'à 5 minutes après l'heure planifiée startingDeadlineSeconds: 300 ``` **Impact :** * Si **défini** : Le contrôleur compte les planifications manquées depuis `now - startingDeadlineSeconds` * Si **non défini** (`nil`) : Le contrôleur compte les planifications manquées depuis `lastScheduleTime`, sans limite de temps **Cas d'usage :** * Job critique qui perd son utilité après un certain délai (ex: traitement temps réel) * Limiter l'accumulation de planifications manquées lors d'une indisponibilité du cluster #### `successfulJobsHistoryLimit` et `failedJobsHistoryLimit` Nombre de `Jobs` terminés (réussis ou échoués) à conserver dans l'historique : ```yaml showLineNumbers spec: successfulJobsHistoryLimit: 3 # Conserver les 3 derniers Jobs réussis failedJobsHistoryLimit: 5 # Conserver les 5 derniers Jobs échoués ``` :::tip Conservez au moins 1-2 Jobs réussis et 3-5 Jobs échoués pour faciliter le diagnostic. Les Jobs trop anciens consomment des ressources etcd inutilement. ::: #### `suspend` Permet de suspendre temporairement l'exécution du `CronJob` sans le supprimer : ```yaml showLineNumbers spec: suspend: true ``` **Utilisation :** ```bash [Terminal] # Suspendre un CronJob kubectl patch cronjob backup-database -n production -p '{"spec":{"suspend":true}}' # Réactiver un CronJob kubectl patch cronjob backup-database -n production -p '{"spec":{"suspend":false}}' ``` ### Commandes kubectl utiles #### Consultation et monitoring ```bash [Terminal] # Lister tous les CronJobs kubectl get cronjobs -A # Détails d'un CronJob spécifique kubectl -n production describe cronjob backup-database # Voir les Jobs créés par un CronJob kubectl -n production get jobs -l app=backup # Voir l'historique des exécutions (status, démarrage, terminaison) kubectl -n production get jobs -l app=backup --sort-by=.status.startTime # Voir les Pods d'un Job spécifique kubectl -n production get pods -l job-name=backup-database-28480640 # Consulter les logs du dernier Job exécuté kubectl -n production logs -l app=backup --tail=100 # Événements liés au CronJob kubectl -n production get events --field-selector involvedObject.kind=CronJob,involvedObject.name=backup-database # Statut détaillé (lastScheduleTime, active Jobs) kubectl -n production get cronjob backup-database -o yaml | grep -A 5 status: ``` #### Opérations courantes ```bash [Terminal] # Créer un CronJob depuis un manifeste kubectl apply -f cronjob-backup.yaml # Déclencher manuellement un Job à partir d'un CronJob kubectl -n production create job backup-manual-$(date +%Y%m%d-%H%M%S) \ --from=cronjob/backup-database # Suspendre un CronJob kubectl -n production patch cronjob backup-database -p '{"spec":{"suspend":true}}' # Réactiver un CronJob kubectl -n production patch cronjob backup-database -p '{"spec":{"suspend":false}}' # Supprimer tous les Jobs terminés d'un CronJob kubectl -n production delete jobs -l app=backup --field-selector status.successful=1 # Supprimer un CronJob et ses Jobs associés kubectl -n production delete cronjob backup-database --cascade=foreground ``` #### Debugging ```bash [Terminal] # Vérifier pourquoi un Job n'a pas démarré kubectl describe cronjob backup-database -n production | grep -A 10 Events # Voir les Pods échoués d'un Job kubectl get pods -n production -l job-name=backup-database-28480640 \ --field-selector status.phase=Failed # Logs d'un Pod échoué kubectl logs -n production --previous # Diagnostiquer les erreurs d'un Job kubectl describe job backup-database-28480640 -n production # Voir la configuration effective d'un CronJob kubectl get cronjob backup-database -n production -o yaml ``` ### Troubleshooting #### Erreur `FailedNeedsStart` : Trop de planifications manquées ##### Symptôme Le `CronJob` n'exécute plus de `Jobs` et affiche l'événement suivant : ``` "FailedNeedsStart", "Cannot determine if job needs to be started. Too many missed start time (> 100). Set or decrease .spec.startingDeadlineSeconds or check clock skew." ``` ##### Analyse du fonctionnement Après analyse de la base de code de [Kubernetes](https://github.com/kubernetes/kubernetes), voici le mécanisme exact du contrôleur `CronJob` : :::steps ##### **Étape 1. Vérification périodique (toutes les 10 secondes)** Le contrôleur vérifie [toutes les 10 secondes](https://github.com/kubernetes/kubernetes/blob/392bf0adef478175b9cf0226b02820eb1820f797/pkg/controller/cronjob/cronjob_controller.go#L95-L96) l'ensemble des `CronJobs` du cluster pour déterminer si un `Job` doit être créé. ##### **Étape 2. Calcul des planifications manquées** Pour chaque `CronJob`, le contrôleur calcule le nombre de planifications manquées entre `lastScheduleTime` et l'instant présent. **Comportement selon `startingDeadlineSeconds` :** * **Si défini** (valeur numérique) : Le calcul des planifications manquées se fait sur la période `[now - startingDeadlineSeconds, now]` *Exemple* : Si `startingDeadlineSeconds: 200`, le contrôleur compte combien de planifications ont été manquées dans les 200 dernières secondes uniquement. * **Si non défini** (`nil`) : Le calcul se fait depuis `lastScheduleTime` jusqu'à maintenant, sans limite de temps. L'implémentation exacte du comptage est disponible [ici](https://github.com/kubernetes/kubernetes/blob/392bf0adef478175b9cf0226b02820eb1820f797/pkg/controller/cronjob/utils.go#L96). ##### **Étape 3. Limite des 100 planifications manquées** Si le nombre de planifications manquées dépasse **100**, le `CronJob` ne créera aucun `Job` et émettra l'événement `FailedNeedsStart`. ##### **Étape 4. Vérification du délai de démarrage** Si le nombre de planifications manquées est inférieur à 100, le contrôleur vérifie si l'heure actuelle dépasse `scheduleTime + startingDeadlineSeconds`. * **Dans les délais** : Le `Job` est créé normalement * **Hors délai** : Le `Job` n'est pas créé et l'événement suivant est enregistré : ``` "Missed starting window for {cronjob-name}. Missed scheduled time to start a job {scheduledTime}" ``` ::: :::info Si `startingDeadlineSeconds` n'est **pas défini**, il n'y a **aucune deadline** : le `Job` sera toujours créé dès que le contrôleur le détecte, quelle que soit l'heure. ::: ##### Causes fréquentes | Cause | Explication | | ---------------------------------------- | ------------------------------------------------------------------------------------ | | **Cluster suspendu/indisponible** | Le contrôleur n'a pas pu traiter les `CronJobs` pendant une longue période | | **`concurrencyPolicy: Forbid`** | Les exécutions successives bloquées s'accumulent comme planifications manquées | | **Job de longue durée** | Un `Job` qui dure plusieurs cycles de planification empêche les suivants de démarrer | | **Schedule trop fréquent** | Planification toutes les minutes avec `Jobs` durant plus d'une minute | | **`startingDeadlineSeconds` non défini** | Accumulation illimitée des planifications manquées lors d'indisponibilités | ##### Solutions :::steps ##### **Étape 1. Définir ou réduire `startingDeadlineSeconds`** Limiter la fenêtre de calcul des planifications manquées : ```yaml showLineNumbers spec: # Autoriser un démarrage jusqu'à 5 minutes après l'heure planifiée startingDeadlineSeconds: 300 schedule: "0 * * * *" # Toutes les heures ``` Avec cette configuration, même après une indisponibilité prolongée du cluster, seules les planifications des 5 dernières minutes seront prises en compte. ##### **Étape 2. Supprimer temporairement le CronJob et le recréer** ```bash [Terminal] # Sauvegarder la configuration kubectl get cronjob backup-database -n production -o yaml > cronjob-backup.yaml # Supprimer le CronJob kubectl delete cronjob backup-database -n production # Attendre quelques secondes puis recréer kubectl apply -f cronjob-backup.yaml ``` Cette action réinitialise le `lastScheduleTime` et efface l'historique des planifications manquées. ##### **Étape 3. Ajuster `concurrencyPolicy`** Si les `Jobs` peuvent s'exécuter en parallèle sans conflit : ```yaml showLineNumbers spec: concurrencyPolicy: Allow # Au lieu de Forbid ``` ##### **Étape 4. Vérifier la synchronisation horaire du cluster** L'erreur mentionne également `check clock skew`. Vérifiez que les nœuds du cluster et le Control Plane sont synchronisés (NTP configuré correctement). ```bash [Terminal] # Sur chaque nœud du cluster timedatectl status ``` ##### **Étape 5. Optimiser la durée d'exécution des Jobs** Si les `Jobs` sont trop longs : * Optimiser le traitement * Réduire les ressources allouées si elles sont excessives * Envisager de découper le traitement en sous-tâches parallèles ::: ##### Questions fréquentes **Q1 : Si `startingDeadlineSeconds: 10` et que le `CronJob` ne peut pas démarrer à l'heure planifiée, peut-il démarrer dans les 10 secondes suivantes ?** **Réponse :** Oui. Le contrôleur tentera de créer le `Job` tant que le délai de 10 secondes après l'heure planifiée n'est pas écoulé. Si ce délai est dépassé, cette exécution ne sera pas faite et sera comptabilisée comme **planification manquée** pour les vérifications ultérieures. **Q2 : Avec `concurrencyPolicy: Forbid`, si un `CronJob` essaie d'être planifié alors qu'un `Job` est déjà en cours, est-ce comptabilisé comme un échec ?** **Réponse :** Oui, cela est considéré comme une **planification manquée**. Le contrôleur enregistre que l'exécution prévue n'a pas eu lieu en raison de la politique de concurrence. Si ce scénario se répète et que `startingDeadlineSeconds` n'est pas défini, cela peut conduire à l'accumulation de plus de 100 planifications manquées et déclencher l'erreur `FailedNeedsStart`. #### Job qui ne démarre jamais ##### Symptôme Le `CronJob` est actif, mais aucun `Job` n'est créé aux heures planifiées. ##### Diagnostic ```bash [Terminal] # Vérifier le statut du CronJob kubectl get cronjob backup-database -n production -o yaml | grep -A 5 status # Consulter les événements kubectl describe cronjob backup-database -n production | grep -A 10 Events # Vérifier si le CronJob est suspendu kubectl get cronjob backup-database -n production -o jsonpath='{.spec.suspend}' ``` ##### Causes et solutions | Cause | Solution | | ------------------------------- | -------------------------------------------------------------------------- | | **CronJob suspendu** | Réactiver : `kubectl patch cronjob -p '{"spec":{"suspend":false}}'` | | **Expression cron invalide** | Valider sur [crontab.guru](https://crontab.guru/) et corriger | | **Problème de fuseau horaire** | Vérifier le fuseau du Control Plane | | **Quota de ressources atteint** | Vérifier les `ResourceQuotas` du namespace | #### Job échoue systématiquement ##### Symptôme Le `CronJob` crée des `Jobs`, mais ils échouent à chaque exécution. ##### Diagnostic ```bash [Terminal] # Lister les Jobs échoués kubectl get jobs -n production -l app=backup --field-selector status.successful=0 # Détails d'un Job échoué kubectl describe job -n production # Logs du Pod échoué kubectl logs -n production # Voir les tentatives de restart kubectl get pods -n production -l job-name= -o wide ``` ##### Causes courantes | Cause | Symptôme | Solution | | ---------------------------- | --------------------------------------- | --------------------------------------------------------- | | **Image inexistante** | `ErrImagePull`, `ImagePullBackOff` | Vérifier le nom et le tag de l'image | | **Secrets manquants** | Erreur au démarrage du conteneur | Vérifier que les `Secrets` existent dans le bon namespace | | **Volumes non disponibles** | `Pod` en état `Pending` | Vérifier les `PVC` et la disponibilité du stockage | | **Ressources insuffisantes** | `Pod` en état `Pending` | Ajuster les `requests`/`limits` ou scaler le cluster | | **Erreur applicative** | Exit code non-zero | Analyser les logs et corriger le script/application | | **Timeout** | `Pod` tué après `activeDeadlineSeconds` | Augmenter la limite ou optimiser le traitement | #### Jobs non nettoyés (historique surchargé) ##### Symptôme Des dizaines/centaines de `Jobs` et `Pods` terminés encombrent le namespace. ##### Solution ```bash [Terminal] # Ajuster les limites d'historique dans le CronJob kubectl patch cronjob backup-database -n production --type=merge -p ' spec: successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 5 ' # Nettoyer manuellement les Jobs terminés kubectl delete jobs -n production -l app=backup --field-selector status.successful=1 # Nettoyer les Pods terminés kubectl delete pods -n production --field-selector status.phase=Succeeded ``` :::tip Configurez également `ttlSecondsAfterFinished` dans le `jobTemplate` pour un nettoyage automatique des `Jobs` : ```yaml showLineNumbers spec: jobTemplate: spec: ttlSecondsAfterFinished: 3600 # Suppression automatique 1h après terminaison ``` ::: #### Logs disparus après expiration du Pod ##### Problème Les `Pods` sont supprimés automatiquement et les logs ne sont plus accessibles. ##### Solutions :::steps ##### **Étape 1. Augmenter la durée de rétention** ```yaml showLineNumbers spec: successfulJobsHistoryLimit: 5 # Conserver plus de Jobs jobTemplate: spec: ttlSecondsAfterFinished: 86400 # Garder les Jobs 24h ``` ##### **Étape 2. Centraliser les logs** Mettre en place une solution de collecte de logs (Loki, ELK, Datadog, etc.) pour conserver les logs au-delà de la durée de vie des `Pods`. ##### **Étape 3. Rediriger les logs vers un volume persistant** ```yaml showLineNumbers spec: jobTemplate: spec: template: spec: containers: - name: job command: - /bin/sh - -c - | # Rediriger stdout et stderr vers un fichier /app/script.sh 2>&1 | tee /logs/job-$(date +%Y%m%d-%H%M%S).log volumeMounts: - name: logs-volume mountPath: /logs volumes: - name: logs-volume persistentVolumeClaim: claimName: logs-pvc ``` ::: ### Bonnes pratiques #### Conception et configuration | Pratique | Justification | | ------------------------------------------- | ------------------------------------------------------------------------------------------ | | **Définir `startingDeadlineSeconds`** | Évite l'accumulation infinie de planifications manquées lors d'indisponibilités | | **Utiliser `concurrencyPolicy: Forbid`** | Prévient les exécutions simultanées pour les traitements exclusifs (backup, migration) | | **Limiter l'historique des Jobs** | `successfulJobsHistoryLimit: 3`, `failedJobsHistoryLimit: 5` pour éviter la surcharge etcd | | **Configurer `ttlSecondsAfterFinished`** | Nettoyage automatique des `Jobs` terminés après un délai défini | | **Définir `activeDeadlineSeconds`** | Évite qu'un `Job` bloqué consomme des ressources indéfiniment | | **Spécifier `resources` (requests/limits)** | Garantit la planification et évite la famine de ressources | | **Utiliser `restartPolicy: OnFailure`** | Permet les tentatives de retry dans le même `Pod` au lieu de créer de nouveaux `Pods` | #### Exemple de configuration optimale ```yaml showLineNumbers apiVersion: batch/v1 kind: CronJob metadata: name: optimized-cronjob namespace: production labels: app.kubernetes.io/name: backup app.kubernetes.io/component: database environment: production spec: schedule: "0 2 * * *" concurrencyPolicy: Forbid startingDeadlineSeconds: 600 successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 5 suspend: false jobTemplate: metadata: labels: app.kubernetes.io/name: backup app.kubernetes.io/component: database spec: # Nettoyage automatique après 24h ttlSecondsAfterFinished: 86400 # 3 tentatives maximum en cas d'échec backoffLimit: 3 # Timeout global du Job : 30 minutes activeDeadlineSeconds: 1800 template: metadata: labels: app.kubernetes.io/name: backup spec: restartPolicy: OnFailure # ServiceAccount dédié avec droits minimaux serviceAccountName: backup-sa containers: - name: backup image: registry.example.com/backup-tool:1.2.3 imagePullPolicy: IfNotPresent command: ["/bin/sh", "-c"] args: - | set -euo pipefail echo "Starting backup at $(date)" /usr/local/bin/backup-script.sh echo "Backup completed at $(date)" env: - name: ENV value: "production" envFrom: - secretRef: name: backup-secrets - configMapRef: name: backup-config resources: requests: cpu: 200m memory: 512Mi limits: cpu: 1000m memory: 1Gi volumeMounts: - name: backup-storage mountPath: /backups # Health check pour détecter les blocages livenessProbe: exec: command: ["/bin/sh", "-c", "pgrep -f backup-script"] initialDelaySeconds: 30 periodSeconds: 60 timeoutSeconds: 5 failureThreshold: 3 volumes: - name: backup-storage persistentVolumeClaim: claimName: backup-pvc # Tolérations et affinité si besoin tolerations: - key: "workload" operator: "Equal" value: "batch" effect: "NoSchedule" ``` #### Monitoring et alerting **Métriques à surveiller :** | Métrique | Alerte si | Action | | --------------------------------- | ------------------------------------ | --------------------------------------------------- | | Nombre de planifications manquées | > 5 consécutives | Investiguer les logs du CronJob | | Taux d'échec des Jobs | > 50% sur 24h | Vérifier les logs des Pods échoués | | Durée d'exécution | > 2x la durée moyenne | Optimiser le traitement ou augmenter les ressources | | Nombre de Jobs en cours | > 1 (si `concurrencyPolicy: Forbid`) | Vérifier pourquoi les Jobs ne se terminent pas | **Exemple de règle Prometheus pour alerter :** ```yaml showLineNumbers - alert: CronJobFailing expr: | kube_cronjob_status_last_schedule_time and on (cronjob) (kube_job_status_failed{job=~".*"} > 0) for: 1h labels: severity: warning annotations: summary: "CronJob {{ $labels.cronjob }} échoue" description: "Le CronJob {{ $labels.cronjob }} a des échecs répétés." - alert: CronJobNotScheduled expr: | time() - kube_cronjob_status_last_schedule_time > 3600 * 2 for: 10m labels: severity: critical annotations: summary: "CronJob {{ $labels.cronjob }} non exécuté" description: "Aucune exécution depuis plus de 2h." ``` #### Sécurité * **ServiceAccount dédié** : Créez un `ServiceAccount` spécifique avec les permissions RBAC minimales nécessaires * **Secrets chiffrés** : Utilisez des `Secrets` pour les credentials, jamais de valeurs en dur * **Image de confiance** : Utilisez des images provenant de registres privés ou validés * **Limites de ressources** : Évitez les abus de ressources avec des `limits` appropriées * **Scan de vulnérabilités** : Scannez les images des `CronJobs` régulièrement #### Cas d'usage courants ##### Sauvegarde de base de données ```yaml showLineNumbers schedule: "0 2 * * *" # Tous les jours à 2h concurrencyPolicy: Forbid ``` ##### Nettoyage de fichiers temporaires ```yaml showLineNumbers schedule: "0 */6 * * *" # Toutes les 6 heures concurrencyPolicy: Replace ttlSecondsAfterFinished: 3600 ``` ##### Génération de rapports hebdomadaires ```yaml showLineNumbers schedule: "0 8 * * 1" # Tous les lundis à 8h concurrencyPolicy: Allow successfulJobsHistoryLimit: 10 ``` ##### Synchronisation de données ```yaml showLineNumbers schedule: "*/30 * * * *" # Toutes les 30 minutes concurrencyPolicy: Forbid startingDeadlineSeconds: 1800 ``` :::warning * Évitez les schedules trop fréquents (\< 1 minute) : privilégiez un `Deployment` avec un worker continu * Ne stockez pas de données sensibles dans les variables d'environnement : utilisez des `Secrets` montés en volumes * Testez vos `CronJobs` avec `kubectl create job --from=cronjob/...` avant de les déployer en production * Documentez la logique métier de chaque `CronJob` dans les annotations ou un README ::: ### Ressources complémentaires * [Documentation officielle Kubernetes - CronJobs](https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/) * [Documentation officielle Kubernetes - Jobs](https://kubernetes.io/docs/concepts/workloads/controllers/job/) * [Code source du contrôleur CronJob](https://github.com/kubernetes/kubernetes/tree/master/pkg/controller/cronjob) * [Crontab Guru - Validateur d'expressions cron](https://crontab.guru/) ## Déploiement d'une application L'objectif de cet exemple est de créer un blog (basé sur [Ghost](https://ghost.org/)) sur le cluster Kubernetes et de conserver les fichiers du blog sur un stockage persistant. :::info Dans cet exemple nous utilisons le service `nip.io` pour la résolution dynamique de noms de domaine. Consultez [la documentation de ce service](https://sslip.io) pour obtenir plus d'informations. ::: ### Commandes kubectl utiles Voici quelques commandes essentielles pour exploiter et diagnostiquer le déploiement Ghost : ```bash [Terminal] # Lister les ressources du namespace blog kubectl -n blog get all # Obtenir les détails d'un pod kubectl -n blog describe pod # Consulter les logs du conteneur Ghost kubectl -n blog logs-l app=ghost # Redémarrer le pod Ghost kubectl -n blog rollout restart deployment/ghost # Vérifier l'état du PVC kubectl -n blog get pvc kubectl -n blog describe pvc claim-nfs-blog # Tester l'accès HTTP depuis le cluster kubectl -n blog run curl --rm -it --image=alpine --restart=Never -- sh -c 'apk add curl; curl http://ghost-svc:80' ``` ### Bonnes pratiques * **Nommage explicite** : Utilisez des noms clairs et cohérents pour les `Namespaces`, `Services`, `PVC`, etc. * **Labels et annotations** : Ajoutez systématiquement des labels pour le suivi, la supervision et la gestion automatisée. * **Sécurité** : Limitez les droits RBAC au strict nécessaire, privilégiez des `ServiceAccounts` dédiés par application. * **Stockage** : Vérifiez la classe de stockage (`storageClassName`) et le mode d'accès (`ReadWriteMany` pour NFS partagé). * **Ressources** : Définissez des `requests` et `limits` pour chaque conteneur afin d'éviter les dérives de consommation. * **Sauvegarde** : Ajoutez les annotations nécessaires pour l'intégration avec Velero ou tout autre outil de sauvegarde. * **Supervision** : Utilisez des labels pour faciliter le monitoring (Prometheus, Grafana, etc.). :::warning - Les images Docker doivent être maintenues à jour pour éviter les failles de sécurité. - L'exposition publique via `Ingress` doit être sécurisée (HTTPS, filtrage IP, authentification si besoin). - Le stockage NFS partagé peut être un point de contention/performance : surveillez l'utilisation. - Les versions d'API obsolètes (`extensions/v1beta1`) ne sont plus supportées sur les clusters récents. ::: ### Labels et annotations recommandés L'utilisation de labels et annotations standardisés facilite l'organisation, la supervision et l'automatisation des déploiements Kubernetes. Voici les plus courants et recommandés. #### Labels standards Les labels suivants sont recommandés par la communauté Kubernetes et largement adoptés : ```yaml showLineNumbers metadata: labels: # Nom de l'application app.kubernetes.io/name: ghost # Instance unique de l'application (utile si plusieurs instances) app.kubernetes.io/instance: ghost-prod # Version de l'application app.kubernetes.io/version: "2.6.1" # Composant dans l'architecture (frontend, backend, database) app.kubernetes.io/component: frontend # Nom du projet/produit de niveau supérieur app.kubernetes.io/part-of: blog-platform # Outil utilisé pour gérer l'application (helm, kustomize, kubectl) app.kubernetes.io/managed-by: kubectl # Environnement de déploiement environment: production # Équipe responsable team: platform # Criticité de l'application criticality: high ``` :::tip Ces labels facilitent le filtrage avec `kubectl` : ```bash kubectl get pods -l app.kubernetes.io/name=ghost kubectl get all -l environment=production ``` ::: #### Labels personnalisés courants ```yaml showLineNumbers metadata: labels: # Identification du projet project: blog # Coût/facturation cost-center: "CC-1234" billing: "departement-it" # Conformité et sécurité data-classification: confidential compliance: pci-dss # Monitoring monitoring: enabled alerting: critical ``` #### Exemple complet Voici un exemple de `Deployment` avec labels et annotations recommandés : ```yaml showLineNumbers [deployment.yaml] apiVersion: apps/v1 kind: Deployment metadata: name: ghost namespace: blog labels: app.kubernetes.io/name: ghost app.kubernetes.io/instance: ghost-prod app.kubernetes.io/version: "2.6.1" app.kubernetes.io/component: frontend app.kubernetes.io/part-of: blog-platform app.kubernetes.io/managed-by: kubectl environment: production team: platform annotations: description: "Blog Ghost pour documentation interne" owner: "platform-team@example.com" documentation: "https://wiki.example.com/ghost" deployed-by: "ci-cd-pipeline" git-commit: "abc123" spec: replicas: 2 selector: matchLabels: app.kubernetes.io/name: ghost app.kubernetes.io/instance: ghost-prod template: metadata: labels: app.kubernetes.io/name: ghost app.kubernetes.io/instance: ghost-prod app.kubernetes.io/version: "2.6.1" environment: production annotations: prometheus.io/scrape: "true" prometheus.io/port: "2368" prometheus.io/path: /metrics backup.velero.io/backup-volumes: nfs-pvc spec: containers: - name: ghost image: ghost:2.6.1-alpine ports: - containerPort: 2368 name: http resources: requests: cpu: "100m" memory: "256Mi" limits: cpu: "500m" memory: "512Mi" volumeMounts: - name: nfs-pvc mountPath: "/var/lib/ghost/content/" volumes: - name: nfs-pvc persistentVolumeClaim: claimName: claim-nfs-blog ``` :::info **Bonnes pratiques :** * Utilisez les labels standards `app.kubernetes.io/*` pour une meilleure interopérabilité * Ajoutez toujours les labels `environment`, `team` et `project` pour faciliter la gestion * Les annotations doivent contenir des informations utiles à l'exploitation (contacts, documentation, configuration d'outils) * Ne stockez jamais de données sensibles dans les labels ou annotations (utilisez des Secrets) ::: ### Création du Namespace Le `Namespace` permet d'isoler les ressources d'un projet, de faciliter la gestion des droits (RBAC), la séparation des environnements (dev, prod, test) et la gestion du cycle de vie applicatif. Il est recommandé d'adopter une convention de nommage explicite (ex : `blog`, `webapp-prod`). ```yaml showLineNumbers [namespace.yaml] apiVersion: v1 kind: Namespace metadata: name: blog labels: project: blog annotations: owner: "noreply@sdv.fr" description: "Namespace dédié au blog Ghost" ``` ### Création du stockage persistant (`PVC`) Le `PersistentVolumeClaim` (`PVC`) permet de demander dynamiquement un volume de stockage auprès d'une `StorageClass` (ici NFS). Il est important de bien choisir le mode d'accès (`ReadWriteMany` pour NFS partagé) et d'ajouter des labels/annotations pour la traçabilité et la sauvegarde. ```yaml showLineNumbers [pvc.yaml] apiVersion: v1 kind: PersistentVolumeClaim metadata: name: claim-nfs-blog namespace: blog labels: app: ghost environment: production spec: storageClassName: managed-nfs-storage accessModes: - ReadWriteMany resources: requests: storage: 1Gi ``` :::tip Adaptez la taille du volume (`storage`) selon vos besoins. Privilégiez le mode `ReadWriteMany` pour les applications nécessitant un accès partagé (NFS). ::: ### Déploiement de l'application Ghost Le `Deployment` permet de gérer la montée en charge, la résilience et la mise à jour de l'application. Utilisez l'`apiVersion: apps/v1` (standard depuis K8s 1.16+). Ajoutez un `selector` explicite et des ressources pour garantir la stabilité. ```yaml showLineNumbers [deployment.yaml] apiVersion: apps/v1 kind: Deployment metadata: name: ghost namespace: blog labels: app: ghost environment: production spec: replicas: 1 selector: matchLabels: app: ghost template: metadata: labels: app: ghost annotations: backup.velero.io/backup-volumes: nfs-pvc spec: containers: - name: ghost image: ghost:2.6.1-alpine resources: requests: cpu: "100m" memory: "256Mi" limits: cpu: "500m" memory: "512Mi" volumeMounts: - name: nfs-pvc mountPath: "/var/lib/ghost/content/" ports: - containerPort: 2368 volumes: - name: nfs-pvc persistentVolumeClaim: claimName: claim-nfs-blog ``` :::note Adaptez les ressources CPU/mémoire selon la charge attendue. Utilisez des labels cohérents pour faciliter la supervision et la sélection par les services. ::: ### Exposition de l'application (`Service`) Le `Service` permet d'exposer le ou les pods Ghost sur le réseau du cluster et de fournir un point d'accès stable. Ici, on expose le port HTTP 80 et on cible le port 2368 du conteneur Ghost. ```yaml showLineNumbers [service.yaml] apiVersion: v1 kind: Service metadata: name: ghost-svc namespace: blog labels: app: ghost environment: production spec: type: ClusterIP selector: app: ghost ports: - name: http protocol: TCP port: 80 targetPort: 2368 ``` :::info Le type `ClusterIP` (par défaut) rend le service accessible uniquement à l'intérieur du cluster. L'accès externe se fait via un Ingress. ::: ### Publication externe (`Ingress`) Le `Ingress` permet d'exposer le service Ghost à l'extérieur du cluster, via une URL et des règles HTTP/S. Depuis Kubernetes 1.19+, utilisez l'`apiVersion: networking.k8s.io/v1` et précisez le champ `pathType`. #### Exemple d'`Ingress` public ```yaml showLineNumbers [ingress.yaml] apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ghost namespace: blog annotations: ingress.kubernetes.io/rewrite-target: / spec: ingressClassName: public rules: - host: ghost.{{publicVIP}}.nip.io http: paths: - path: / pathType: Prefix backend: service: name: ghost-svc port: number: 80 ``` #### Exemple d'`Ingress` privé ```yaml showLineNumbers [ingress-private.yaml] apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ghost-internal namespace: blog annotations: ingress.kubernetes.io/rewrite-target: / spec: ingressClassName: private rules: - host: ghost.{{internalVIP}}.nip.io http: paths: - path: / pathType: Prefix backend: service: name: ghost-svc port: number: 80 ``` #### Exemple d'`Ingress` avec TLS (Let's Encrypt) Dans cet exemple nous présumons que vous avez suivi le [guide d'installation de cert-manager](/guides/cert-manager/challenge-http) et que vous avez à votre disposition le `ClusterIssuer` nommé `cert-manager-clusterissuer-http01-production`. ```yaml showLineNumbers [ingress-with-tls.yaml] apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ghost-tls namespace: blog annotations: ingress.kubernetes.io/rewrite-target: / cert-manager.io/cluster-issuer: cert-manager-clusterissuer-http01-production spec: ingressClassName: public tls: - hosts: - ghost.{{publicVIP}}.nip.io secretName: ghost-tls-cert rules: - host: ghost.{{publicVIP}}.nip.io http: paths: - path: / pathType: Prefix backend: service: name: ghost-svc port: number: 80 ``` :::warning Pensez à bien adapter le champ `ingressClassName` (`public` ou `private`) selon le niveau d'exposition souhaité. Pour la sécurité, privilégiez HTTPS et limitez les accès par IP si besoin (voir annotation `ingress.kubernetes.io/whitelist-source-range`). ::: ### Notes d’exploitation * Pour appliquer les manifestes : ```bash [Terminal] kubectl apply -f namespace.yaml kubectl apply -f pvc.yaml kubectl apply -f deployment.yaml kubectl apply -f service.yaml kubectl apply -f ingress.yaml ``` * Pour vérifier le déploiement : ```bash [Terminal] kubectl -n blog get all kubectl -n blog describe pod kubectl -n blog logs -l app=ghost ``` * Pour supprimer l'ensemble : ```bash [Terminal] kubectl delete namespace blog ``` :::info Vous pouvez lister les namespaces existants avec : ```bash [Terminal] kubectl get namespaces ``` ::: ## Docker in Docker dans Kubernetes ### Introduction Le pattern **Docker-in-Docker (DinD)** consiste à exécuter le daemon Docker à l'intérieur d'un conteneur Kubernetes, permettant ainsi de construire, tester ou manipuler des images Docker depuis des `Pods`. Ce pattern est couramment utilisé dans les pipelines CI/CD (GitLab CI, Tekton, Jenkins) et pour les environnements de développement éphémères. :::warning **Implications de sécurité**\ L'utilisation de Docker-in-Docker nécessite l'exécution en mode **privileged**, ce qui octroie au conteneur des accès étendus au noyau de l'hôte. Cette approche doit être évaluée avec attention du point de vue de la sécurité et n'est pas recommandée pour des workloads de production exposés. ::: :::info Pour une intégration GitLab CI Runner spécifique, consultez [le guide dédié](/guides/gitlab-ci-runner) qui détaille la configuration optimale pour ce cas d'usage particulier. ::: ### Cas d'usage Le Docker-in-Docker est pertinent dans les contextes suivants : | Cas d'usage | Description | Alternative | | -------------------------------- | -------------------------------------------------------- | --------------------------------- | | **Build d'images CI/CD** | Construction d'images Docker dans un pipeline automatisé | Kaniko, Buildah (rootless) | | **Tests d'intégration** | Validation d'images avant publication sur un registry | Docker socket mounting (risqué) | | **Environnements éphémères** | Création de sandboxes temporaires pour développement | Kind, k3d (local), Cloud Build | | **Multi-stage builds complexes** | Builds nécessitant plusieurs couches Docker | BuildKit, Cloud Native Buildpacks | ### Architecture technique Le pattern DinD repose sur deux conteneurs en **sidecar** dans le même `Pod` : 1. **`dind-daemon`** : Daemon Docker autonome, isolé du nœud hôte 2. **`docker-build`** : Client Docker communiquant avec le daemon via TCP/TLS ![Docker in Docker dans un Pod](/guides/dind/dind.png) #### Communication sécurisée La communication entre le client et le daemon s'effectue via **TLS mutuel** automatiquement configuré par l'image `docker:dind` : * Certificats générés au démarrage du daemon dans `/certs` * Volume `emptyDir` partagé entre les deux conteneurs * Variables d'environnement `DOCKER_TLS_CERTDIR` et `DOCKER_CERT_PATH` configurées ### Exemple complet Voici un exemple de `Job` Kubernetes exécutant un build Docker avec DinD : ```yaml showLineNumbers [dind-job.yaml] apiVersion: batch/v1 kind: Job metadata: name: dind-build-example namespace: ci-builds labels: app.kubernetes.io/name: dind-builder app.kubernetes.io/component: build spec: ttlSecondsAfterFinished: 3600 # Nettoyage automatique après 1h backoffLimit: 2 template: metadata: labels: app.kubernetes.io/name: dind-builder spec: restartPolicy: Never serviceAccountName: dind-builder # ServiceAccount dédié containers: # Conteneur client Docker effectuant le build - name: docker-build image: docker:24-cli # Version explicite recommandée command: ['sh', '-c'] args: - | echo "Waiting for Docker daemon to be ready..." timeout 30 sh -c 'until docker info >/dev/null 2>&1; do sleep 1; done' echo "Docker daemon ready" docker build /workspace -t myapp:${CI_COMMIT_SHA:-latest} docker images # Optionnel : push vers registry # docker login -u "${REGISTRY_USER}" -p "${REGISTRY_PASSWORD}" registry.example.com # docker tag myapp:latest registry.example.com/myapp:latest # docker push registry.example.com/myapp:latest env: # Configuration du client Docker pour communiquer via TLS - name: DOCKER_HOST value: tcp://localhost:2376 - name: DOCKER_TLS_CERTDIR value: /certs - name: DOCKER_CERT_PATH value: /certs/client - name: DOCKER_TLS_VERIFY value: "1" # Variables pour authentification registry (exemple) # - name: REGISTRY_USER # valueFrom: # secretKeyRef: # name: registry-credentials # key: username # - name: REGISTRY_PASSWORD # valueFrom: # secretKeyRef: # name: registry-credentials # key: password resources: requests: cpu: 500m memory: 512Mi limits: cpu: 2000m memory: 2Gi volumeMounts: - name: dockerfile-volume mountPath: /workspace/Dockerfile subPath: Dockerfile readOnly: true - name: dind-certs mountPath: /certs readOnly: true # Daemon Docker en sidecar - name: dind-daemon image: docker:24-dind # Version explicite recommandée securityContext: privileged: true # REQUIS pour DinD # Note : privileged=true donne des capacités étendues args: - "--mtu=1400" # MTU adapté aux overlay networks Kubernetes - "--log-level=info" # Ajuster selon besoin (debug, info, warn, error) - "--storage-driver=overlay2" # Driver recommandé env: - name: DOCKER_TLS_CERTDIR value: /certs resources: requests: cpu: 500m memory: 1Gi limits: cpu: 2000m memory: 4Gi volumeMounts: - name: dind-storage mountPath: /var/lib/docker - name: dind-certs mountPath: /certs # Healthcheck pour vérifier que le daemon est prêt readinessProbe: exec: command: - docker - info initialDelaySeconds: 10 periodSeconds: 5 timeoutSeconds: 3 failureThreshold: 3 volumes: # Stockage éphémère pour le graph storage Docker - name: dind-storage emptyDir: sizeLimit: 10Gi # Limite de taille pour éviter saturation du nœud # Partage des certificats TLS entre client et daemon - name: dind-certs emptyDir: medium: Memory # En mémoire pour performances et sécurité # ConfigMap contenant le Dockerfile - name: dockerfile-volume configMap: name: dockerfile-configmap defaultMode: 0644 --- apiVersion: v1 kind: ConfigMap metadata: name: dockerfile-configmap namespace: ci-builds data: Dockerfile: | FROM php:8.2-apache # Installation des dépendances système RUN apt-get update && \ apt-get install --no-install-recommends -y \ git \ libicu-dev \ libxml2-dev \ libmagickwand-dev \ unzip \ vim \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* # Configuration PHP RUN docker-php-ext-install intl xml # Copie des sources (exemple) COPY . /var/www/html/ EXPOSE 80 ``` ### Détails techniques #### MTU de 1400 Le paramètre `--mtu=1400` est crucial dans les environnements Kubernetes utilisant des **overlay networks** (Calico, Flannel, Weave) : * Le MTU par défaut de Docker (1500) peut causer des fragmentations de paquets * Les encapsulations réseau (VXLAN, IPIP) réduisent le MTU effectif * Un MTU de 1400 assure la compatibilité avec la plupart des CNI Kubernetes * **Symptôme sans MTU adapté** : Connexions réseau lentes ou timeouts lors du pull d'images :::tip **Vérification du MTU**\ Pour vérifier le MTU de votre CNI Kubernetes : ```bash kubectl run test-mtu --rm -it --image=alpine --restart=Never -- ip link show eth0 ``` ::: #### Mode privileged Le flag `privileged: true` est **obligatoire** pour DinD car le daemon Docker nécessite : * Accès au socket de contrôle des cgroups * Capacité de monter des filesystems (overlay2) * Gestion des namespaces réseau et PID * Manipulation des devices `/dev` **Risques associés** : * Élévation de privilèges potentielle vers l'hôte * Accès aux périphériques du nœud * Capacités étendues pouvant être exploitées #### Storage driver overlay2 Le driver `overlay2` est recommandé pour : * Meilleures performances en lecture/écriture * Support natif dans les noyaux Linux récents * Partage efficace des couches d'images ### Commandes kubectl utiles ```bash [Terminal] # Créer le Job DinD kubectl apply -f dind-job.yaml # Suivre les logs du build (conteneur docker-build) kubectl logs -f job/dind-build-example -c docker-build -n ci-builds # Suivre les logs du daemon Docker kubectl logs -f job/dind-build-example -c dind-daemon -n ci-builds # Vérifier le statut du Job kubectl get job dind-build-example -n ci-builds kubectl describe job dind-build-example -n ci-builds # Inspecter les événements du Pod kubectl get events --sort-by='.lastTimestamp' -n ci-builds # Accéder au shell du conteneur build pour debug kubectl exec -it job/dind-build-example -c docker-build -n ci-builds -- sh # Nettoyer le Job et ses Pods kubectl delete job dind-build-example -n ci-builds ``` ### Bonnes pratiques #### Sécurité * **Limiter le scope** : Utiliser des `Namespaces` dédiés pour les builds DinD * **ServiceAccount dédié** : Créer un `ServiceAccount` avec droits RBAC minimaux * **PodSecurityPolicy/PodSecurityStandards** : Autoriser explicitement `privileged: true` uniquement pour les `Namespaces` de build * **Network Policies** : Restreindre les communications réseau sortantes si possible * **Secrets** : Ne jamais exposer de credentials en clair, utiliser des `Secrets` Kubernetes * **Image scanning** : Analyser les images construites avec Trivy, Clair ou Anchore avant publication #### Performances * **Resource limits** : Définir des `requests` et `limits` pour éviter la saturation des nœuds * **emptyDir avec limit** : Limiter la taille de `dind-storage` avec `sizeLimit` * **Node affinity** : Dédier certains nœuds aux builds avec des taints/tolerations * **Nettoyage automatique** : Utiliser `ttlSecondsAfterFinished` pour supprimer les Jobs terminés * **Cache layers** : Utiliser un volume persistent pour `/var/lib/docker` si builds fréquents (attention aux permissions) #### Opérationnel * **Monitoring** : Exposer des métriques du daemon Docker via Prometheus * **Timeout** : Ajouter des timeouts sur les Jobs (`activeDeadlineSeconds`) * **Retry policy** : Configurer `backoffLimit` pour gérer les échecs transitoires * **Logs centralisés** : Envoyer les logs vers un système centralisé (ELK, Loki) * **Versions explicites** : Toujours spécifier des tags d'images précis (`docker:24-dind` plutôt que `docker:dind`) ### Limitations et contraintes * **Sécurité** : Le mode privileged est un risque majeur dans les environnements multi-tenant * **Performance** : Overhead dû à l'imbrication des couches de virtualisation * **Stockage** : Les layers Docker s'accumulent dans `emptyDir`, saturation possible * **Réseau** : Complexité accrue avec les overlay networks (MTU, routing) * **Compatibilité** : Certains storage drivers peuvent ne pas fonctionner selon le noyau de l'hôte * **Isolation** : Isolation moindre qu'avec Kaniko ou des solutions managed ### Troubleshooting #### Le daemon Docker ne démarre pas **Symptôme** : Logs `dind-daemon` montrant des erreurs de démarrage ```bash # Vérifier les logs du daemon kubectl logs -c dind-daemon # Erreurs courantes : # - permission denied → vérifier privileged: true # - cannot mount overlay → vérifier support overlay2 sur l'hôte # - MTU issues → ajuster --mtu ``` **Solutions** : * Vérifier que `privileged: true` est bien défini * Tester avec `--storage-driver=vfs` (moins performant mais plus compatible) * Vérifier les PodSecurityPolicies/Standards du cluster #### Le client ne peut pas se connecter au daemon **Symptôme** : Erreur `Cannot connect to the Docker daemon at tcp://localhost:2376` ```bash # Vérifier que le daemon est prêt kubectl exec -c dind-daemon -- docker info # Vérifier la présence des certificats kubectl exec -c docker-build -- ls -la /certs/client/ ``` **Solutions** : * Augmenter le `sleep` ou utiliser une boucle de retry (voir exemple) * Vérifier que `DOCKER_TLS_CERTDIR` est identique dans les deux conteneurs * Vérifier le partage du volume `dind-certs` #### Builds lents ou timeouts réseau **Symptôme** : Pull d'images très lent, timeouts lors du `apt-get update`, etc. **Solutions** : * Ajuster le MTU : `--mtu=1400` ou `--mtu=1350` * Vérifier la connectivité réseau du Pod : ```bash kubectl exec -c docker-build -- ping -c 3 registry-1.docker.io ``` * Configurer un registry mirror/cache local * Vérifier les Network Policies #### Saturation du stockage **Symptôme** : Erreur `no space left on device` dans le daemon **Solutions** : * Augmenter `sizeLimit` de `dind-storage` * Nettoyer régulièrement les images/layers : ```bash docker system prune -af # Dans le conteneur ``` * Utiliser un volume persistent avec lifecycle management * Surveiller l'utilisation avec `docker system df` #### Échecs d'authentification registry **Symptôme** : `unauthorized: authentication required` lors du push **Solutions** : * Vérifier la présence et validité du `Secret` credentials * Tester manuellement le `docker login` dans le conteneur * Vérifier les permissions du ServiceAccount si utilisation de registries privés K8s ### Notes d'exploitation * **Audit** : Logguer tous les builds DinD pour traçabilité (qui, quoi, quand) * **Quotas** : Implémenter des `ResourceQuotas` sur les `Namespaces` de build * **Alerting** : Alerter sur les Jobs en échec répété ou en timeout * **Backup** : Les artifacts buildés doivent être pushés vers un registry, pas stockés localement * **Rotation** : Nettoyer automatiquement les anciens `Jobs` avec des `CronJobs` * **Conformité** : Documenter l'utilisation de DinD pour les audits de sécurité ### Références * [Docker-in-Docker officiel](https://hub.docker.com/_/docker) * [Best practices Docker in Kubernetes](https://kubernetes.io/docs/concepts/containers/) * [Guide Kaniko](https://github.com/GoogleContainerTools/kaniko) * [Documentation Buildah](https://buildah.io/) * [Security context Kubernetes](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) ## Déploiement d'un Runner GitLab dans Kubernetes avec Docker in Docker GitLab permet l'automatisation de procédures sur vos dépôts Git grâce à sa solution d'intégration et de déploiement continue (CI/CD). Ces traitements sont exécutés sur des GitLab Runners, que vous pouvez déployer au sein de clusters Kubernetes chez SdV. Le paramétrage décrit permettra de disposer d'un service `Docker in Docker` (`dind`) afin de builder des images Docker depuis vos pipelines. Cette procédure est valable pour une instance GitLab hébergée par SdV ou pour l'instance SaaS sur [gitlab.com](https://gitlab.com). :::info Ce guide vous accompagne dans l'installation et la configuration d'un runner GitLab en utilisant `helm` dans sa version 3. Si vous n'êtes pas familier avec cet outil de déploiement, référez-vous à la [documentation officielle du projet](https://helm.sh/docs/). ::: ### Prérequis Avant de débuter l'installation, assurez-vous de disposer des éléments suivants : | Ressource | Description | | ----------------- | ------------------------------------------------------------------------------------------- | | **Helm 3** | Disponible sur votre poste de travail ([Installation](https://helm.sh/docs/intro/install/)) | | **kubeconfig** | Fichier de configuration valide pour accéder à votre cluster Kubernetes | | **Droits GitLab** | Rôle `Maintainer` ou `Owner` dans votre projet GitLab | | **kubectl** | CLI Kubernetes configurée et fonctionnelle | | **Namespace** | Espace de noms dédié au runner (création possible lors du déploiement) | ### Versions La procédure d'installation d'un runner GitLab peut évoluer dans le temps selon les releases du projet. À l'heure de la rédaction de ce guide, les versions utilisées sont : | Composant | Version | | ----------------- | ---------------------- | | **Chart Helm** | `0.86.0` | | **GitLab Runner** | `18.9.0` | | **Image dind** | `docker:dind` (latest) | | **Kubernetes** | `1.18+` minimum | :::warning En cas de doute ou pour les versions plus récentes, référez-vous à la [procédure d'installation officielle sur le site de GitLab](https://docs.gitlab.com/runner/install/kubernetes.html). ::: ### Installation #### Préparation du dépôt GitLab Avant tout, rendez-vous dans votre projet GitLab, dans le menu `Settings > CI/CD`, section `Runners`. Vous devriez visualiser un écran similaire à la capture ci-après. ![Runners](/guides/gitlab-ci-runner/gitlab-runners.png) Deux actions sont à réaliser : 1. **Désactiver les runners mutualisés** (encadré orange) : permet d'utiliser exclusivement votre runner dédié 2. **Noter l'URL et le Token** d'enregistrement de votre projet (encadré cyan) : nécessaires pour la configuration Helm :::tip Le token d'enregistrement est sensible et ne doit pas être versé dans Git. Il sera utilisé uniquement lors du déploiement Helm et stocké ensuite dans un `Secret` Kubernetes. ::: #### Préparation du chart Helm Ajoutez le repository Helm officiel de GitLab : ```bash [Terminal] helm repo add gitlab https://charts.gitlab.io helm repo update ``` Afin de connaître toutes les options de configuration disponibles, vous pouvez télécharger le chart dans son ensemble pour visualiser le fichier `values.yaml` : ```bash [Terminal] helm fetch gitlab/gitlab-runner --untar cat gitlab-runner/values.yaml ``` #### Stratégies de déploiement Deux approches sont possibles pour l'installation du service `Docker in Docker` : | Méthode | Portée | Avantages | Inconvénients | | ------------------------------------ | --------- | -------------------------------------------------------- | --------------------------------------------------------------------------- | | **1. Service dans `.gitlab-ci.yml`** | Par dépôt | Service lancé uniquement pour les jobs qui en ont besoin | Configuration dupliquée dans chaque dépôt, faible évolutivité | | **2. Service dans le Runner** | Globale | Configuration centralisée, `.gitlab-ci.yml` simplifiés | Service démarré pour tous les jobs, modification nécessite un redéploiement | :::info **Recommandation SdV** : La méthode 2 (déclaration dans le Runner) est recommandée pour simplifier la gestion et garantir l'homogénéité des configurations. ::: ##### 1. Déclaration du service `dind` dans les `.gitlab-ci.yml` ##### 1.a. Déploiement Dans ce cas de figure, le `values.yaml` que nous suggérons d'utiliser est le suivant : ```yaml showLineNumbers [values.yaml] # URL de votre instance GitLab gitlabUrl: [URL_GITLAB] # Token d'enregistrement du runner (depuis Settings > CI/CD > Runners) runnerRegistrationToken: [TOKEN_GITLAB] # Configuration RBAC pour le runner rbac: create: true # Ressources Kubernetes accessibles par le runner resources: ["pods", "pods/exec", "secrets"] # Actions autorisées verbs: ["get", "list", "watch", "create", "patch", "delete"] # Limité au namespace de déploiement clusterWideAccess: false # Configuration du runner runners: executor: kubernetes config: | [[runners]] [runners.kubernetes] # Image par défaut pour les jobs image = "ubuntu:20.04" # Requis pour Docker in Docker privileged = true [runners.feature_flags] # Active l'helper pour le GitLab Container Registry FF_GITLAB_REGISTRY_HELPER_IMAGE = true ``` :::warning **Paramètres à personnaliser** : * Remplacez `[URL_GITLAB]` par l'URL de votre instance (ex: `https://gitlab.com` ou `https://gitlab.entreprise.fr`) * Remplacez `[TOKEN_GITLAB]` par le token d'enregistrement relevé précédemment * Adaptez l'image `ubuntu:20.04` selon vos besoins ::: **Explication des paramètres RBAC** : | Paramètre | Description | Impact | | -------------------------- | ------------------------------------ | --------------------------------------------- | | `create: true` | Crée automatiquement les objets RBAC | Le runner dispose des permissions nécessaires | | `clusterWideAccess: false` | Limite les droits au namespace | Isolation et sécurité renforcée | | `resources` | Types de ressources accessibles | Permet de gérer les pods, secrets, etc. | | `verbs` | Actions autorisées | CRUD complet sur les ressources | Déployez désormais votre runner à l'aide de la commande suivante : ```bash [Terminal] # Installer le runner avec Helm 3 helm upgrade --install \ --namespace gitlab-runner \ --create-namespace \ -f values.yaml \ gitlab-runner \ gitlab/gitlab-runner ``` :::tip Avec Helm 3, l'option `--name` n'existe plus. Le premier argument après `install` est le nom de la release. ::: **Vérifier le déploiement** : ```bash [Terminal] # Vérifier le statut du runner kubectl -n gitlab-runner get pods kubectl -n gitlab-runner logs -l app=gitlab-runner # Vérifier les ressources RBAC créées kubectl -n gitlab-runner get role,rolebinding,serviceaccount ``` ##### 1.b. Utilisation Une fois déployé, patientez quelques instants et retournez sur l'écran `Runners` de votre projet GitLab. Le runner devrait apparaître avec un indicateur vert : ![Runners](gitlab-runners-2.png) Désormais, vous pouvez déclarer un service `Docker in Docker` dans vos dépôts GitLab. Voici un exemple de fichier `.gitlab-ci.yml` : ```yaml showLineNumbers [.gitlab-ci.yml] # Image de base pour tous les jobs image: docker:latest # Service Docker in Docker services: - name: docker:dind # MTU réduit pour compatibilité réseau cluster command: ["--mtu=1400"] # Variables d'environnement globales variables: REGISTRY_IMAGE_PATH: registry.gitlab.com/group/project:latest # Désactive TLS pour communication avec dind DOCKER_TLS_CERTDIR: "" # Adresse du daemon Docker DOCKER_HOST: tcp://docker:2375 # Script exécuté avant chaque job before_script: # Attente démarrage du service dind - sleep 30 - docker info stages: - build - test build_image: stage: build script: - echo "Building image ${REGISTRY_IMAGE_PATH}" - docker build --pull -t ${REGISTRY_IMAGE_PATH} . - docker push ${REGISTRY_IMAGE_PATH} only: - main - develop test_image: stage: test script: - docker pull ${REGISTRY_IMAGE_PATH} - docker run --rm ${REGISTRY_IMAGE_PATH} /bin/sh -c "echo Test OK" dependencies: - build_image ``` **Explication des paramètres critiques** : | Paramètre | Valeur | Raison | | ----------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------ | | `--mtu=1400` | MTU réduit | L'image Docker officielle utilise MTU 1500 par défaut, incompatible avec certains réseaux overlay Kubernetes | | `DOCKER_TLS_CERTDIR=""` | TLS désactivé | Force la communication HTTP non chiffrée avec le service dind (communication intra-pod) | | `DOCKER_HOST` | `tcp://docker:2375` | Nom DNS du service dind (correspond au `name` dans `services`) | | `sleep 30` | Délai d'attente | L'image dind ne fournit pas de healthcheck, ce délai garantit que le daemon est prêt | :::warning **Limitations de cette approche** : * Le service dind démarre à chaque job, ce qui augmente la durée d'exécution * La configuration doit être dupliquée dans tous les dépôts * Le `sleep 30` est une solution de contournement (pas de healthcheck natif) ::: ##### 2. Déclaration du service `dind` dans le Runner ##### 2.a. Déploiement Voici le fichier `values.yaml` recommandé qui intègre le service dind directement dans la configuration du runner : ```yaml showLineNumbers [values.yaml] # URL de votre instance GitLab gitlabUrl: [URL_GITLAB] # Token d'enregistrement du runner runnerRegistrationToken: [TOKEN_GITLAB] # Configuration RBAC rbac: create: true resources: ["pods", "pods/exec", "secrets"] verbs: ["get", "list", "watch", "create", "patch", "delete"] clusterWideAccess: false # Configuration avancée du runner runners: executor: kubernetes config: | [[runners]] # Variables d'environnement injectées dans tous les jobs environment = [ "DOCKER_TLS_CERTDIR=", "DOCKER_HOST=tcp://127.0.0.1:2375" ] [runners.kubernetes] # Image par défaut image = "ubuntu:20.04" # Mode privileged requis pour Docker in Docker privileged = true # Service dind démarré automatiquement avec chaque job [[runners.kubernetes.services]] name = "docker:dind" command = [ "--mtu=1400" ] [runners.feature_flags] # Support GitLab Container Registry FF_GITLAB_REGISTRY_HELPER_IMAGE = true ``` :::tip **Avantage de cette méthode** : Les variables `DOCKER_TLS_CERTDIR` et `DOCKER_HOST` sont définies au niveau du runner. Vos fichiers `.gitlab-ci.yml` n'ont plus besoin de les déclarer. ::: **Différences clés avec la méthode 1** : | Élément | Méthode 1 (service dans CI) | Méthode 2 (service dans runner) | | ------------------------- | ---------------------------- | --------------------------------- | | Localisation config dind | `.gitlab-ci.yml` | `values.yaml` du runner | | Variables d'environnement | À déclarer dans chaque dépôt | Injectées automatiquement | | Maintenance | Difficile (multiples dépôts) | Centralisée | | Flexibilité | Haute (par dépôt) | Moyenne (nécessite redéploiement) | Déployez le runner avec la commande suivante : ```bash [Terminal] # Installer le runner avec Helm 3 helm update --install \ --namespace gitlab-runner \ --create-namespace \ -f values.yaml \ gitlab-runner \ gitlab/gitlab-runner ``` **Vérifier le déploiement** : ```bash [Terminal] # Statut du runner kubectl -n gitlab-runner get pods # Logs du runner kubectl -n gitlab-runner logs -l app=gitlab-runner # Inspecter la configuration kubectl -n gitlab-runner get cm kubectl -n gitlab-runner describe cm gitlab-runner # Tester un job kubectl -n gitlab-runner logs -l app=gitlab-runner --tail=100 -f ``` ##### 2.b. Utilisation Une fois déployé, patientez quelques instants et retournez sur l'écran `Runners` de votre projet GitLab. Le runner devrait apparaître avec un indicateur vert : ![Runners](/guides/gitlab-ci-runner/gitlab-runners-2.png) Désormais, vous pouvez utiliser le service `Docker in Docker` démarré automatiquement par votre runner. Voici un exemple simplifié de fichier `.gitlab-ci.yml` : ```yaml showLineNumbers [.gitlab-ci.yml] # Image de base image: docker:latest # Variables personnalisées variables: REGISTRY_IMAGE_PATH: registry.gitlab.com/group/project:latest # Les variables DOCKER_TLS_CERTDIR et DOCKER_HOST # sont déjà définies par le runner # Attente démarrage du service dind before_script: - sleep 30 - docker info stages: - build - push build_image: stage: build script: - echo "Building ${REGISTRY_IMAGE_PATH}" - docker build --pull -t ${REGISTRY_IMAGE_PATH} . only: - main - develop push_image: stage: push before_script: - sleep 30 - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY script: - docker push ${REGISTRY_IMAGE_PATH} dependencies: - build_image only: - main ``` **Avantages de cette configuration** : * Pas besoin de déclarer le service dind dans `.gitlab-ci.yml` * Variables d'environnement Docker automatiquement disponibles * Configuration homogène sur tous les dépôts * Maintenance centralisée sur le runner :::info Le `sleep 30` reste nécessaire car l'image Docker officielle ne fournit pas de healthcheck. Pour un timing plus robuste, vous pouvez utiliser : ```yaml before_script: - | for i in $(seq 1 30); do docker info && break echo "Waiting for Docker daemon... ($i/30)" sleep 1 done ``` ::: ### Commandes kubectl utiles Voici les commandes essentielles pour exploiter et diagnostiquer votre runner GitLab : ```bash [Terminal] # Lister les ressources du namespace kubectl -n gitlab-runner get all # Détails du pod runner kubectl -n gitlab-runner describe pod -l app=gitlab-runner # Logs en temps réel kubectl -n gitlab-runner logs -l app=gitlab-runner -f # Logs des jobs (pods temporaires) kubectl -n gitlab-runner get pods --watch kubectl -n gitlab-runner logs runner-xxxxx-project-xxxxx-concurrent-x # Vérifier les secrets kubectl -n gitlab-runner get secrets kubectl -n gitlab-runner describe secret # Redémarrer le runner kubectl -n gitlab-runner rollout restart deployment gitlab-runner # Inspecter la configuration RBAC kubectl -n gitlab-runner get role,rolebinding,serviceaccount kubectl -n gitlab-runner describe role # Vérifier les ressources consommées kubectl -n gitlab-runner top pod ``` ### Configuration avancée #### Limitation des ressources Pour éviter la surconsommation de ressources, ajoutez des limites dans `values.yaml` : ```yaml showLineNumbers [values.yaml] runners: config: | [[runners]] [runners.kubernetes] # Ressources pour le build container cpu_request = "100m" cpu_limit = "2" memory_request = "256Mi" memory_limit = "2Gi" # Ressources pour le service dind service_cpu_request = "100m" service_cpu_limit = "1" service_memory_request = "256Mi" service_memory_limit = "1Gi" # Helpers helper_cpu_request = "50m" helper_cpu_limit = "500m" helper_memory_request = "64Mi" helper_memory_limit = "256Mi" ``` #### Node selection Pour déployer le runner sur des nodes spécifiques : ```yaml showLineNumbers [values.yaml] runners: config: | [[runners]] [runners.kubernetes] # NodeSelector [runners.kubernetes.node_selector] "workload" = "ci-cd" "environment" = "production" # Tolerations pour taints [[runners.kubernetes.node_tolerations]] key = "ci-workload" operator = "Equal" value = "true" effect = "NoSchedule" ``` #### Cache et artifacts Pour améliorer les performances avec un cache persistant : ```yaml showLineNumbers [values.yaml] runners: config: | [[runners]] [runners.cache] Type = "s3" Shared = true [runners.cache.s3] ServerAddress = "s3.amazonaws.com" BucketName = "gitlab-runner-cache" BucketLocation = "eu-west-1" [runners.kubernetes] # Volume pour cache local [[runners.kubernetes.volumes.empty_dir]] name = "docker-cache" mount_path = "/cache" medium = "Memory" ``` ### Bonnes pratiques #### Sécurité * **Isolation** : Utilisez un namespace dédié pour chaque runner ou équipe * **RBAC minimal** : Ne donnez que les permissions strictement nécessaires (`clusterWideAccess: false`) * **Secrets** : Stockez les credentials dans des `Secret` Kubernetes ou GitLab CI/CD Variables * **Images** : Utilisez des images de base maîtrisées et scannées (pas `latest`) * **Registry privé** : Configurez l'accès aux registries privés via `imagePullSecrets` * **Network Policies** : Limitez le trafic réseau des pods runner si possible ```yaml showLineNumbers [values.yaml] # Image pull secret pour registry privé imagePullSecrets: - name: gitlab-registry-credentials # Configuration pour registry privé runners: config: | [[runners]] [runners.kubernetes] image_pull_secrets = ["gitlab-registry-credentials"] ``` #### Performance * **Ressources** : Définissez des limites adaptées à vos workloads * **Cache** : Activez le cache S3 ou `PVC` pour réutiliser les dépendances * **Concurrent jobs** : Ajustez le nombre de jobs simultanés selon votre cluster ```yaml showLineNumbers [values.yaml] runners: config: | concurrent = 10 # Nombre max de jobs simultanés [[runners]] limit = 5 # Limite pour ce runner spécifique ``` #### Maintenance * **Logs** : Configurez la rotation et l'archivage des logs * **Monitoring** : Intégrez Prometheus pour suivre les métriques du runner * **Mises à jour** : Planifiez les mises à jour du chart Helm et de l'image runner * **Nettoyage** : Surveillez les pods terminés et les volumes orphelins ```bash [Terminal] # Nettoyer les pods terminés kubectl -n gitlab-runner delete pods --field-selector=status.phase=Succeeded kubectl -n gitlab-runner delete pods --field-selector=status.phase=Failed ``` #### Observabilité Activez les métriques Prometheus du runner : ```yaml showLineNumbers [values.yaml] metrics: enabled: true portName: metrics port: 9252 serviceMonitor: enabled: true ``` ### Dépannage #### Le runner n'apparaît pas dans GitLab **Symptômes** : Le pod runner est en cours d'exécution mais n'apparaît pas dans l'interface GitLab. **Diagnostic** : ```bash [Terminal] kubectl -n gitlab-runner logs -l app=gitlab-runner ``` **Causes possibles** : | Problème | Solution | | --------------- | ------------------------------------------------------------------------- | | Token invalide | Vérifiez le token dans GitLab (`Settings > CI/CD > Runners`) | | URL incorrecte | Vérifiez `gitlabUrl` dans `values.yaml` (avec `https://`) | | Problème réseau | Testez la connectivité : `kubectl exec -it -- curl -I ` | | Certificat SSL | Ajoutez `certsSecretName` si certificat auto-signé | #### Les jobs restent en statut "pending" **Symptômes** : Les jobs CI/CD ne démarrent jamais. **Diagnostic** : ```bash [Terminal] # Vérifier les événements du namespace kubectl -n gitlab-runner get events --sort-by='.lastTimestamp' # Vérifier les quotas et limites kubectl -n gitlab-runner describe resourcequota kubectl -n gitlab-runner describe limitrange ``` **Causes possibles** : | Problème | Solution | | ------------------------ | ------------------------------------------------------------------------------------------------------------ | | Quota dépassé | Augmentez les quotas du namespace ou réduisez les `requests` | | Pas de nodes disponibles | Vérifiez `kubectl get nodes` et les ressources disponibles | | Image inaccessible | Vérifiez `imagePullSecrets` et les credentials registry | | RBAC insuffisant | Vérifiez les permissions avec `kubectl auth can-i --list --as=system:serviceaccount:gitlab-runner:` | #### Erreur "Cannot connect to the Docker daemon" **Symptômes** : Les jobs échouent avec l'erreur de connexion au daemon Docker. **Diagnostic** : ```bash [Terminal] # Vérifier les logs du service dind kubectl -n gitlab-runner logs -c svc-0 # Vérifier les variables d'environnement kubectl -n gitlab-runner exec -c build -- env | grep DOCKER ``` **Solutions** : 1. Vérifiez que `DOCKER_HOST=tcp://localhost:2375` (méthode 2) ou `tcp://docker:2375` (méthode 1) 2. Vérifiez que `DOCKER_TLS_CERTDIR=""` est bien défini 3. Augmentez le `sleep` dans `before_script` (essayez 60 secondes) 4. Vérifiez que `privileged: true` est activé #### Le build échoue avec des erreurs réseau **Symptômes** : Timeouts lors du pull d'images ou de dépendances. **Solutions** : 1. **MTU** : Vérifiez que `--mtu=1400` est bien configuré 2. **DNS** : Testez la résolution DNS : ```yaml showLineNumbers [.gitlab-ci.yml] before_script: - nslookup google.com - ping -c 3 8.8.8.8 ``` 3. **Proxy** : Si un proxy est nécessaire, configurez-le : ```yaml showLineNumbers [values.yaml] runners: config: | [[runners]] environment = [ "HTTP_PROXY=http://proxy.entreprise.fr:8080", "HTTPS_PROXY=http://proxy.entreprise.fr:8080", "NO_PROXY=localhost,127.0.0.1,.cluster.local" ] ``` #### Les jobs sont très lents **Diagnostic** : ```bash [Terminal] # Vérifier les ressources CPU/Mémoire kubectl -n gitlab-runner top pod # Vérifier les I/O disque kubectl -n gitlab-runner exec -- df -h ``` **Optimisations** : 1. **Cache** : Activez le cache S3 ou PVC 2. **Image layers** : Optimisez vos Dockerfiles (multi-stage builds) 3. **Ressources** : Augmentez les `cpu_limit` et `memory_limit` 4. **Node local storage** : Utilisez des nodes avec SSD ### Maintenance et mise à jour #### Mise à jour du runner ```bash [Terminal] # Lister les versions disponibles helm search repo gitlab/gitlab-runner --versions # Mettre à jour le repository helm repo update # Upgrader le runner helm upgrade --install \ --namespace gitlab-runner \ --create-namespace \ -f values.yaml \ gitlab-runner \ gitlab/gitlab-runner # Vérifier la nouvelle version kubectl -n gitlab-runner get pods kubectl -n gitlab-runner logs -l app=gitlab-runner | grep "version" ``` #### Sauvegarde de la configuration ```bash [Terminal] # Exporter la configuration Helm helm -n gitlab-runner get values gitlab-runner > gitlab-runner-values-backup.yaml # Exporter tous les manifestes Kubernetes kubectl -n gitlab-runner get all,secrets,cm,role,rolebinding,sa -o yaml > gitlab-runner-backup.yaml ``` #### Désinstallation ```bash [Terminal] # Désinstaller le runner helm -n gitlab-runner uninstall gitlab-runner # Nettoyer les ressources résiduelles kubectl delete namespace gitlab-runner # Vérifier qu'il ne reste rien kubectl -n gitlab-runner get all ``` ### Considérations de sécurité #### Mode privileged :::warning Le mode `privileged: true` est requis pour Docker in Docker mais présente des risques de sécurité : * Accès complet aux ressources du node hôte * Possibilité d'échapper du conteneur * Accès aux devices et capabilities élevés ::: #### Isolation des workloads Pour renforcer la sécurité, isolez les runners par namespace et appliquez des `NetworkPolicies` : ```yaml showLineNumbers [network-policy.yaml] apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: gitlab-runner-isolation namespace: gitlab-runner spec: podSelector: matchLabels: app: gitlab-runner policyTypes: - Ingress - Egress egress: # Autoriser vers API Kubernetes - to: - namespaceSelector: {} ports: - protocol: TCP port: 443 # Autoriser vers GitLab - to: - podSelector: {} ports: - protocol: TCP port: 443 # Autoriser DNS - to: - namespaceSelector: matchLabels: name: kube-system ports: - protocol: UDP port: 53 ``` ### Références * [Documentation officielle GitLab Runner](https://docs.gitlab.com/runner/) * [GitLab Runner Helm Chart](https://gitlab.com/gitlab-org/charts/gitlab-runner) * [Kubernetes Executor](https://docs.gitlab.com/runner/executors/kubernetes.html) * [Advanced Configuration](https://docs.gitlab.com/runner/configuration/advanced-configuration.html) * [Docker in Docker (dind)](https://hub.docker.com/_/docker) ## Autoscaling Horizontal avec HPA et KEDA L'autoscaling horizontal permet d'ajuster automatiquement le nombre de réplicas d'un `Deployment`, `StatefulSet` ou `ReplicaSet` en fonction de métriques observées. Ce guide présente deux approches complémentaires : **`HPA`** (natif Kubernetes) et **KEDA** (événementiel avancé). ### Introduction L'**`HorizontalPodAutoscaler`** (`HPA`) est un contrôleur natif Kubernetes qui ajuste le nombre de `Pods` en fonction de métriques : * **Métriques CPU/Memory** : via Metrics Server (intégré) * **Métriques custom** : via Prometheus Adapter ou tout adaptateur compatible Metrics API * **Métriques externes** : via External Metrics API #### Approches de scaling : du plus simple au plus précis Le scaling peut être mis en œuvre selon trois niveaux de complexité et de précision : **1. Scaling sur CPU/Memory (Premier niveau - Recommandé pour démarrer)** * **Simple** : Metrics Server généralement préinstallé, configuration minimale * **Pas d'infrastructure supplémentaire** : Fonctionne out-of-the-box * **Universel** : Applicable à toutes les applications * **Imprécis** : CPU/Memory ne reflètent pas toujours la charge réelle de l'application * **Réactif** : Scale après saturation des ressources, pas de manière prédictive **2. Scaling sur métriques applicatives (Niveau avancé - Plus précis)** * **Précision** : Basé sur des indicateurs métiers (requêtes/sec, latence, taux d'erreur, pool de connexions, etc.) * **Proactif** : Scale avant saturation des ressources système * **Adapté au contexte** : Reflète réellement la charge applicative * **Infrastructure requise** : Nécessite Prometheus + Prometheus Adapter ou équivalent * **Configuration** : Requiert instrumentation de l'application et configuration des règles **3. Scaling événementiel (KEDA - Pour workloads asynchrones)** * **Scale-to-zero** : Économies maximales en l'absence de charge * **Sources multiples** : 60+ scalers intégrés (files, bases, cloud services) * **Événementiel** : Idéal pour traitement batch et workers * **Cold start** : Latence au premier événement si scaled à zéro :::tip **Stratégie recommandée :** 1. **Démarrez** avec CPU/Memory pour une mise en place rapide 2. **Affinez** avec des métriques applicatives une fois l'application en production et les patterns de charge identifiés 3. **Complétez** avec KEDA pour les composants asynchrones (workers, batch) **Exemples de progression :** * API REST : CPU 70% → Latence p95 \< 200ms → Requêtes/sec par Pod * Worker RabbitMQ : Memory 80% → Messages en queue → KEDA RabbitMQ scaler * Traitement batch : CPU 70% → KEDA Cron + Queue depth ::: **KEDA** (Kubernetes Event-Driven Autoscaling) étend le `HPA` standard en permettant le scaling basé sur des événements externes (files de messages, bases de données, métriques cloud, etc.) et supporte le scale-to-zero. :::info Ce guide couvre : 1. Autoscaling basé sur CPU et Memory (cas standard avec Metrics Server) 2. Configuration `HPA` avec métriques personnalisées (exemple PHP-FPM) 3. Architecture Prometheus + Prometheus Adapter 4. Introduction à KEDA pour l'autoscaling événementiel 5. Bonnes pratiques opérationnelles et troubleshooting ::: ### Architecture et composants #### Stack `HPA` avec métriques personnalisées | Composant | Rôle | Déploiement | | ---------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | **Metrics Server** | Métriques CPU/Memory des `Pods` | Généralement préinstallé sur le cluster | | **Prometheus** | Collecte et stockage des métriques applicatives | Helm Chart `prometheus-community/prometheus`. Ce composant est préinstallé dans votre cluster SdV. | | **Prometheus Adapter** | Exposition des métriques Prometheus dans l'API Kubernetes | Helm Chart `prometheus-community/prometheus-adapter`. Ce composant est préinstallé dans votre cluster SdV. | | **HPA Controller** | Contrôleur natif Kubernetes | Intégré au Control Plane | #### Flux de données ![Flux](/guides/hpa/hpa-global.png) ### Prérequis * Fichier `kubeconfig` valide avec les droits nécessaires (création de `Namespace`, `Deployment`, `HPA`) * Accès réseau pour la résolution DNS dynamique (pour l'exemple avec `nip.io`) * Métriques CPU/Memory : `Metrics Server` opérationnel (vérifier avec `kubectl top nodes`) :::tip Pour les métriques custom, le cluster SdV dispose de **Prometheus** et **Prometheus Adapter** préinstallés. Il suffit d'annoter vos Pods pour que Prometheus collecte leurs métriques automatiquement. ::: ### Autoscaling basé sur `CPU` et `Memory` Le cas d'usage le plus simple et le plus courant est le scaling basé sur les métriques **CPU** et **Memory** des `Pods`. Ces métriques sont fournies nativement par le **Metrics Server**. #### Vérification de Metrics Server Avant de créer un `HPA`, vérifiez que le Metrics Server fonctionne correctement : ```bash [Terminal] # Vérifier que l'API metrics.k8s.io est disponible kubectl get apiservices | grep metrics # Tester la récupération des métriques des nodes kubectl top nodes # Tester la récupération des métriques des pods kubectl top pods -A ``` Si ces commandes échouent, le Metrics Server n'est pas installé ou non fonctionnel. Consultez la documentation SdV ou contactez l'équipe d'administration du cluster. #### Exemple 1 : HPA basé sur CPU uniquement Configuration minimale pour scaler un `Deployment` en fonction de l'utilisation CPU : ```yaml showLineNumbers [hpa-cpu.yaml] apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: myapp-cpu-hpa namespace: myapp labels: app: myapp spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: myapp minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 # Scale up si CPU moyen > 70% ``` :::info **Calcul de l'utilisation CPU :** L'utilisation CPU est calculée en pourcentage des `requests.cpu` définis dans le `Deployment`. **Exemple :** * `requests.cpu: 100m` (100 millicores) * Consommation actuelle : 70m * Utilisation : 70m / 100m = **70%** Si l'utilisation dépasse 70%, le HPA ajoutera des `Pods`. Si elle redescend sous 70%, il en retirera (avec stabilisation). ::: #### Exemple 2 : HPA basé sur Memory uniquement Configuration pour scaler en fonction de l'utilisation mémoire : ```yaml showLineNumbers [hpa-memory.yaml] apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: myapp-memory-hpa namespace: myapp labels: app: myapp spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: myapp minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: memory target: type: Utilization averageUtilization: 80 # Scale up si Memory moyenne > 80% ``` :::warning **Attention au scaling sur Memory :** * La mémoire n'est généralement **pas compressible** (contrairement au CPU) * Un Pod dépassant ses `limits.memory` sera tué (OOMKilled) sans possibilité de scale-up préventif * Privilégiez un seuil conservateur (75-80%) pour laisser une marge de manœuvre * Combinez toujours avec des alertes sur l'utilisation mémoire proche des limites ::: #### Exemple 3 : HPA combinant CPU et Memory Configuration recommandée combinant les deux métriques (le HPA scale si **l'une des deux** dépasse son seuil) : ```yaml showLineNumbers [hpa-cpu-memory.yaml] apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: myapp-hpa namespace: myapp labels: app: myapp monitoring: enabled spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: myapp minReplicas: 2 maxReplicas: 20 # Le HPA scale si CPU > 70% OU Memory > 80% metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 - type: Resource resource: name: memory target: type: Utilization averageUtilization: 80 # Comportements de scaling pour éviter le flapping behavior: scaleUp: stabilizationWindowSeconds: 60 policies: - type: Pods value: 4 periodSeconds: 60 - type: Percent value: 100 # Doubler le nombre de Pods maximum periodSeconds: 60 selectPolicy: Max scaleDown: stabilizationWindowSeconds: 300 # 5 minutes policies: - type: Pods value: 1 periodSeconds: 180 # Retirer 1 Pod toutes les 3 minutes selectPolicy: Min ``` #### Exemple 4 : HPA avec valeurs absolues Au lieu de pourcentages (`Utilization`), vous pouvez utiliser des valeurs absolues (`AverageValue`) : ```yaml showLineNumbers [hpa-absolute.yaml] apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: myapp-absolute-hpa namespace: myapp spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: myapp minReplicas: 2 maxReplicas: 10 metrics: # Scale si CPU moyen > 500 millicores (0.5 core) - type: Resource resource: name: cpu target: type: AverageValue averageValue: 500m # Scale si Memory moyenne > 1Gi - type: Resource resource: name: memory target: type: AverageValue averageValue: 1Gi ``` :::tip **Quand utiliser `Utilization` vs `AverageValue` ?** * **`Utilization`** (recommandé) : Exprimé en % des `requests`. Plus flexible car adaptatif aux changements de `requests`. * **`AverageValue`** : Valeur absolue indépendante des `requests`. Utile si vous connaissez précisément les seuils critiques de votre application. **En général, privilégiez `Utilization`** pour sa simplicité et sa cohérence avec les `requests` définis. ::: #### Dimensionnement des requests et limits Pour que le HPA fonctionne correctement avec CPU/Memory, les `requests` et `limits` doivent être correctement dimensionnés dans votre `Deployment` : ```yaml showLineNumbers [deployment-resources.yaml] apiVersion: apps/v1 kind: Deployment metadata: name: myapp namespace: myapp spec: replicas: 2 selector: matchLabels: app: myapp template: metadata: labels: app: myapp spec: containers: - name: app image: myregistry/myapp:latest ports: - containerPort: 8080 resources: requests: cpu: 200m # CPU demandé au scheduler memory: 256Mi # Memory demandée au scheduler limits: cpu: 1000m # CPU max (throttling au-delà) memory: 512Mi # Memory max (OOMKill au-delà) ``` :::warning **Règles critiques pour le dimensionnement :** 1. **Toujours définir `requests`** : Le HPA calcule l'utilisation en % de `requests.cpu` et `requests.memory` 2. **`requests` réalistes** : Basés sur la consommation réelle observée (utiliser `kubectl top pods`) 3. **Choisir la QoS Class adaptée** : Voir le tableau ci-dessous pour comprendre l'impact de `requests` vs `limits` **Impact sur le scaling :** * **Si `requests` trop bas :** Le HPA verra une utilisation artificiellement élevée et sur-scalera * **Si `requests` trop haut :** Le HPA verra une utilisation artificiellement basse et sous-scalera ::: #### Quality of Service (QoS) Classes Kubernetes assigne automatiquement une QoS Class à chaque Pod selon la configuration `requests` et `limits`. Cette classe détermine la priorité d'éviction en cas de pression sur les ressources du nœud. | QoS Class | Configuration | Priorité éviction | Cas d'usage typique | | -------------- | ------------------------------------------- | ----------------------------- | ------------------------------------- | | **Guaranteed** | `limits = requests` pour CPU ET Memory | 🛡️ Haute (évincé en dernier) | Bases de données, services critiques | | **Burstable** | `requests < limits` OU seulement `requests` | ⚠️ Moyenne (évincé en second) | Applications web/API, charge variable | | **BestEffort** | Ni `requests` ni `limits` | ❌ Basse (évincé en premier) | Dev/test uniquement | **Détails des QoS Classes :** **Guaranteed** : * ✅ Ressources garanties et réservées * ✅ Prévisibilité maximale * ✅ Priorité d'exécution maximale * ❌ Pas de burst possible * ❌ Peut gaspiller des ressources inutilisées **Burstable** : * ✅ Burst autorisé lors des pics * ✅ Optimisation de l'utilisation des ressources * ✅ Flexible et économique * ❌ Peut être évincé en cas de pression * ❌ Moins prévisible **BestEffort** : * ✅ Utilise toutes les ressources disponibles * ✅ Pas de contrainte * ❌ Aucune garantie de ressources * ❌ Évincé en premier en cas de pression * ❌ À éviter en production **Exemples de configuration :** ```yaml showLineNumbers # Guaranteed - Workload critique resources: requests: cpu: 500m memory: 512Mi limits: cpu: 500m # = requests memory: 512Mi # = requests # Burstable - Workload standard (recommandé pour la plupart des cas) resources: requests: cpu: 200m memory: 256Mi limits: cpu: 1000m # 5x requests (burst possible) memory: 512Mi # 2x requests # BestEffort - À éviter en production resources: {} # Aucune limite ``` :::tip **Recommandations selon le type d'application :** **Utilisez Guaranteed (`limits = requests`) pour :** * Bases de données (PostgreSQL, MySQL, Redis, etc.) * Services critiques (authentification, paiement) * Applications sensibles à la latence (temps réel) * Workloads avec consommation stable et prévisible **Utilisez Burstable (`limits > requests`) pour :** * APIs REST / Services web classiques * Applications avec charge variable * Workers de traitement asynchrone * La plupart des applications stateless **N'utilisez jamais BestEffort en production** (sauf environnements de dev/test) ::: #### Calibration des requests avec des métriques réelles Processus recommandé pour calibrer les `requests` : ```bash [Terminal] # 1. Déployer l'application avec des requests initiaux (estimation) kubectl apply -f deployment.yaml # 2. Observer la consommation réelle sous charge normale kubectl top pods -n myapp --containers # Exemple de sortie : # POD NAME CPU(cores) MEMORY(bytes) # myapp-abc123 app 150m 180Mi # myapp-def456 app 170m 200Mi # 3. Calculer la moyenne et ajouter une marge de 20-30% # Moyenne CPU : 160m -> requests: 200m (160m * 1.25) # Moyenne Memory : 190Mi -> requests: 250Mi (190Mi * 1.3) # 4a. Définir les limits pour une QoS Burstable (recommandé pour la plupart des cas) # Permet le burst lors des pics de charge # CPU limits: 1000m (5x requests pour gérer les pics) # Memory limits: 500Mi (2x requests) # 4b. Ou définir les limits pour une QoS Guaranteed (workloads critiques) # Ressources garanties, priorité d'éviction maximale # CPU limits: 200m (= requests) # Memory limits: 250Mi (= requests) # 5. Mettre à jour le Deployment avec les valeurs calibrées (exemple Burstable) kubectl set resources deployment myapp -n myapp \ --requests=cpu=200m,memory=250Mi \ --limits=cpu=1000m,memory=500Mi # 6. Tester le scaling sous charge (load test) # Utiliser des outils comme k6, Apache Bench, Gatling, etc. ``` #### Tester le HPA Une fois le HPA créé, testez son fonctionnement : ```bash [Terminal] # Créer le HPA kubectl apply -f hpa-cpu-memory.yaml # Observer l'état initial kubectl get hpa -n myapp # Générer de la charge CPU (test simple) kubectl run load-generator --image=busybox --restart=Never -n myapp -- /bin/sh -c "while true; do wget -q -O- http://myapp-service:8080; done" # Observer le scaling en temps réel kubectl get hpa -n myapp -w # Consulter les événements de scaling kubectl describe hpa myapp-hpa -n myapp # Supprimer le générateur de charge kubectl delete pod load-generator -n myapp # Observer le scale down (après stabilizationWindowSeconds) kubectl get hpa -n myapp -w ``` #### Commandes de diagnostic ```bash [Terminal] # Vérifier que le HPA récupère correctement les métriques kubectl get hpa myapp-hpa -n myapp -o yaml # Afficher les métriques actuelles via l'API kubectl get --raw "/apis/metrics.k8s.io/v1beta1/namespaces/myapp/pods" | jq . # Consulter les logs du Metrics Server (si problème) kubectl logs -n kube-system -l k8s-app=metrics-server # Vérifier les événements du HPA kubectl get events -n myapp --sort-by='.lastTimestamp' | grep HorizontalPodAutoscaler ``` #### Tableau récapitulatif : Recommandations de seuils | Métrique | Seuil HPA recommandé | `requests` conseillés | | ---------------- | --------------------- | -------------------------- | | **CPU** | 70-80% | Consommation moyenne + 20% | | **Memory** | 75-85% | Consommation moyenne + 30% | | **CPU + Memory** | CPU: 70%, Memory: 80% | Calibrés séparément | **Recommandations pour `limits` selon la QoS Class :** | QoS Class | CPU limits | Memory limits | Quand utiliser | | -------------- | ---------------- | ------------------ | ---------------------------------------- | | **Burstable** | 2x à 5x requests | 1.5x à 2x requests | Applications web/API (défaut recommandé) | | **Guaranteed** | = requests | = requests | Workloads critiques uniquement | :::info **Pourquoi ces seuils ?** * **70-80% CPU** : Laisse une marge pour les pics temporaires avant le scale-up * **75-85% Memory** : Plus conservateur car la mémoire n'est pas compressible * **Marge sur `requests`** : Évite le sous-dimensionnement et laisse de la place pour la croissance **Choix de la stratégie :** * **Burstable (défaut)** : Permet le burst lors des pics, optimise l'utilisation des ressources du cluster * **Guaranteed (critique)** : Réserve les ressources, priorité d'éviction maximale, pour bases de données et services essentiels uniquement ::: ### Cas d'usage : Autoscaling PHP-FPM (métriques custom) Dans cet exemple, nous allons scaler un applicatif PHP basé sur le taux d'utilisation des workers **php-fpm**. L'approche est transposable à d'autres applicatifs exposant des métriques custom (pools de connexions, files d'attente, latences, etc.). #### Objectif * Collecter les métriques `phpfpm_active_processes` et `phpfpm_total_processes` * Calculer un ratio d'utilisation : `phpfpm_process_utilization_rate` * Scaler automatiquement lorsque ce ratio dépasse 50% #### Prérequis pour métriques custom Le cluster SdV dispose déjà de l'infrastructure nécessaire pour les métriques custom : * **Prometheus** : Préinstallé pour la collecte et le stockage des métriques * **Prometheus Adapter** : Préinstallé pour exposer les métriques dans l'API `custom.metrics.k8s.io` * **Prometheus Operator** : Préinstallé pour gérer la configuration de scraping via des CRD (ServiceMonitor, PodMonitor) #### Méthodes de collecte de métriques Il existe trois approches pour faire collecter vos métriques applicatives par Prometheus. Choisissez celle qui correspond le mieux à votre cas d'usage. ##### 1. Annotations Prometheus (Approche simple) **Principe :** Prometheus découvre automatiquement les `Pods` via leurs annotations. Cette méthode fonctionne si Prometheus est configuré avec Service Discovery Kubernetes. **Annotations nécessaires :** ```yaml showLineNumbers metadata: annotations: prometheus.io/scrape: "true" # Active le scraping prometheus.io/port: "9253" # Port d'exposition des métriques prometheus.io/path: "/metrics" # Path de l'endpoint (défaut: /metrics) ``` **Avantages :** * ✅ Simple et rapide à mettre en place * ✅ Pas de ressource supplémentaire à créer * ✅ Fonctionne out-of-the-box si Prometheus est configuré pour **Inconvénients :** * ❌ Moins déclaratif (configuration mélangée aux annotations) * ❌ Difficile à gérer à grande échelle * ❌ Dépend de la configuration Prometheus (peut ne pas fonctionner) **Exemple complet :** ```yaml showLineNumbers [deployment-annotations.yaml] apiVersion: apps/v1 kind: Deployment metadata: name: myapp namespace: myapp spec: replicas: 2 selector: matchLabels: app: myapp template: metadata: labels: app: myapp annotations: prometheus.io/scrape: "true" prometheus.io/port: "9253" prometheus.io/path: "/metrics" spec: containers: - name: app image: myapp:latest ports: - containerPort: 8080 name: http - containerPort: 9253 name: metrics ``` ##### 2. `ServiceMonitor` (Approche recommandée) **Principe :** Avec Prometheus Operator, vous créez un objet `ServiceMonitor` qui décrit comment scraper un `Service` Kubernetes. C'est l'approche déclarative et la plus maintainable. **Ressources nécessaires :** 1. Un `Service` qui expose vos Pods 2. Un `ServiceMonitor` qui référence ce Service **Avantages :** * ✅ Déclaratif et Infrastructure as Code * ✅ Géré par Prometheus Operator (auto-reloading) * ✅ Facile à gérer à grande échelle * ✅ Support avancé (authentification, TLS, relabeling) * ✅ Isolé de la configuration Prometheus **Inconvénients :** * ❌ Ressource supplémentaire à maintenir (`Service` + `ServiceMonitor`) **Exemple complet :** :::code-group ```yaml showLineNumbers [service.yaml] # Service exposant les métriques apiVersion: v1 kind: Service metadata: name: myapp-metrics namespace: myapp labels: app: myapp metrics: enabled # Label important pour le ServiceMonitor spec: selector: app: myapp ports: - name: metrics # Le nom du port est important port: 9253 targetPort: 9253 ``` ```yaml showLineNumbers [servicemonitor.yaml] # ServiceMonitor pour la collecte Prometheus apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: myapp-monitor namespace: myapp labels: app: myapp release: sdv-monitoring # Label pour que Prometheus le découvre spec: # Sélecteur du Service à surveiller selector: matchLabels: app: myapp metrics: enabled # Namespace(s) à surveiller (par défaut: même namespace que le ServiceMonitor) namespaceSelector: matchNames: - myapp # Configuration du scraping endpoints: - port: metrics # Nom du port défini dans le Service interval: 30s # Fréquence de scraping path: /metrics # Path de l'endpoint scheme: http # http ou https # Relabeling optionnel (ajout de labels personnalisés) relabelings: - sourceLabels: [__meta_kubernetes_pod_name] targetLabel: pod - sourceLabels: [__meta_kubernetes_namespace] targetLabel: namespace ``` ::: **Vérification du `ServiceMonitor` :** ```bash [Terminal] # Lister les ServiceMonitors kubectl get servicemonitor -n myapp # Détails d'un ServiceMonitor kubectl describe servicemonitor myapp-monitor -n myapp # Vérifier que Prometheus a chargé le ServiceMonitor # (accéder à Prometheus UI et aller dans Status > Targets) ``` ##### 3. `PodMonitor` (Alternative aux `ServiceMonitor`) **Principe :** Similaire au `ServiceMonitor`, mais scrape directement les `Pods` sans passer par un `Service`. Utile pour les `Pods` sans `Service` ou avec plusieurs ports de métriques. **✅ Avantages :** * Scraping direct des `Pods` (pas de `Service` requis) * Utile pour les `DaemonSets`, `StatefulSets` * Peut scraper plusieurs ports par Pod **❌ Inconvénients :** * Moins stable que `ServiceMonitor` (`Pods` éphémères) * Pas de load balancing **Exemple complet :** ```yaml showLineNumbers [podmonitor-example.yaml] apiVersion: monitoring.coreos.com/v1 kind: PodMonitor metadata: name: myapp-pod-monitor namespace: myapp labels: app: myapp release: sdv-monitoring spec: # Sélecteur des Pods à surveiller selector: matchLabels: app: myapp # Namespace(s) à surveiller namespaceSelector: matchNames: - myapp # Configuration du scraping podMetricsEndpoints: - port: metrics # Nom du port défini dans le Pod interval: 30s path: /metrics scheme: http # Relabeling optionnel relabelings: - sourceLabels: [__meta_kubernetes_pod_name] targetLabel: instance ``` #### Tableau comparatif des méthodes | Critère | Annotations | ServiceMonitor | PodMonitor | | ---------------------------- | ------------------------ | ----------------------- | ---------------------------- | | **Simplicité** | ⭐⭐⭐ Très simple | ⭐⭐ Moyen | ⭐⭐ Moyen | | **Déclaratif** | ❌ Non | ✅ Oui | ✅ Oui | | **Prérequis** | Service Discovery activé | Aucun (préinstallé) | Aucun (préinstallé) | | **Scalabilité** | ⭐ Limitée | ⭐⭐⭐ Excellente | ⭐⭐ Bonne | | **Maintenance** | ⭐ Difficile | ⭐⭐⭐ Facile | ⭐⭐ Moyenne | | **Fonctionnalités avancées** | ❌ Limitées | ✅ Complètes | ✅ Complètes | | **Service requis** | ❌ Non | ✅ Oui | ❌ Non | | **Cas d'usage** | Prototypage, dev | Production (recommandé) | `DaemonSets`, `StatefulSets` | #### Recommandations :::tip **Choix de la méthode selon le contexte :** **Utilisez les annotations si :** * Vous êtes en phase de prototypage/développement * Vous avez peu d'applications à monitorer * Prometheus est configuré pour découvrir les annotations **Utilisez `ServiceMonitor` (recommandé par SdV)** : * Vous êtes en production * Vous gérez plusieurs applications * Vous voulez une configuration maintenable et évolutive * Infrastructure as Code et GitOps **Utilisez `PodMonitor` si :** * Vous n'avez pas de `Service` pour vos `Pods` * Vous utilisez des `DaemonSets` ou `StatefulSets` * Vous devez scraper plusieurs ports par `Pod` ::: :::info **Pour le cluster SdV :** Prometheus Operator est **préinstallé** sur votre cluster. Privilégiez les **`ServiceMonitor`** pour une gestion déclarative et maintenable. Vérifiez la disponibilité des CRD : ```bash [Terminal] # Vérifier les CRD Prometheus Operator kubectl api-resources | grep monitoring.coreos.com # Vous devriez voir : # servicemonitors monitoring.coreos.com/v1 true ServiceMonitor # podmonitors monitoring.coreos.com/v1 true PodMonitor # prometheusrules monitoring.coreos.com/v1 true PrometheusRule ``` **Approche recommandée :** Créez un `ServiceMonitor` pour chaque application exposant des métriques. Les annotations Prometheus restent utilisables mais sont moins maintenables à grande échelle. ::: #### Vérification de l'API custom metrics Vérifiez que l'API `custom.metrics.k8s.io` est disponible : ```bash [Terminal] # Vérifier l'API custom metrics kubectl get apiservices | grep custom.metrics # Lister les métriques custom disponibles (exemple) kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1" | jq . ``` :::warning **Ajout de métriques custom dans Prometheus Adapter** Les métriques collectées par Prometheus ne sont pas automatiquement disponibles pour le HPA. Il faut les exposer via **Prometheus Adapter** en créant une règle de transformation. Sur le cluster SdV, Prometheus Adapter est géré par l'équipe d'administration. Pour ajouter une métrique custom : 1. **Collectez d'abord vos métriques** (via annotations ou `ServiceMonitor`) 2. **Vérifiez dans Prometheus** que les métriques sont bien scrapées 3. **Contactez l'équipe SdV** avec les informations suivantes : * Nom de la métrique dans Prometheus (ex: `phpfpm_active_processes`) * Nom souhaité dans l'API custom.metrics (ex: `phpfpm_process_utilization_rate`) * Requête PromQL pour calculer la métrique (ex: `max((100 / phpfpm_total_processes) * phpfpm_active_processes) by (namespace, pod)`) * Namespaces concernés L'équipe ajoutera la règle dans la configuration de Prometheus Adapter. Les métriques seront disponibles sous quelques minutes après le redémarrage de l'Adapter. ::: ### Configuration de l'applicatif #### Exposition des métriques PHP-FPM Les images officielles PHP-FPM n'exposent pas nativement de métriques Prometheus. Nous allons déployer un **sidecar container** (`php-fpm_exporter`) qui scrape le endpoint `/fpm-status` et expose les métriques au format Prometheus. :::warning Cette approche nécessite que `pm.status_path` soit configuré dans votre pool PHP-FPM (par défaut `/fpm-status`). Vérifiez votre configuration `www.conf` ou équivalent. ::: **Exemple de `Deployment` avec sidecar exporter :** ```yaml showLineNumbers [deployment.yaml] apiVersion: apps/v1 kind: Deployment metadata: name: myapp-php namespace: myapp labels: app: myapp tier: backend spec: replicas: 2 selector: matchLabels: app: myapp tier: backend template: metadata: labels: app: myapp tier: backend annotations: # Annotations pour Prometheus Service Discovery prometheus.io/scrape: "true" prometheus.io/port: "9253" prometheus.io/path: "/metrics" spec: containers: # Conteneur applicatif principal (PHP-FPM) - name: php-fpm image: php:8.2-fpm-alpine ports: - containerPort: 9000 name: fastcgi resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 512Mi # ... autres configurations (volumes, env, etc.) # Sidecar pour exposer les métriques PHP-FPM - name: fpm-metrics image: hipages/php-fpm_exporter:latest ports: - containerPort: 9253 name: metrics env: # Adresse FastCGI du pool PHP-FPM (local via 127.0.0.1) - name: PHP_FPM_SCRAPE_URI value: tcp://127.0.0.1:9000/fpm-status # Port d'écoute de l'exporter - name: PHP_FPM_WEB_LISTEN_ADDRESS value: :9253 # Path des métriques - name: PHP_FPM_WEB_TELEMETRY_PATH value: /metrics # Correction des compteurs de process (recommandé) - name: PHP_FPM_FIX_PROCESS_COUNT value: "true" # Niveau de log - name: PHP_FPM_LOG_LEVEL value: info # Health checks readinessProbe: httpGet: path: /metrics port: 9253 initialDelaySeconds: 5 periodSeconds: 5 livenessProbe: httpGet: path: /metrics port: 9253 initialDelaySeconds: 10 periodSeconds: 10 startupProbe: httpGet: path: /metrics port: 9253 failureThreshold: 30 periodSeconds: 2 resources: requests: cpu: 20m memory: 32Mi limits: cpu: 50m memory: 64Mi ``` :::tip **Bonnes pratiques pour le sidecar exporter :** * Utilisez toujours des `startupProbe` pour éviter les redémarrages prématurés au démarrage * Dimensionnez les ressources du sidecar au strict minimum (overhead négligeable) * Activez `PHP_FPM_FIX_PROCESS_COUNT` pour éviter les incohérences de compteurs PHP-FPM ::: #### Configuration de la collecte Prometheus Vous avez deux options pour faire collecter vos métriques par Prometheus : ##### Option 1 : Annotations (déjà configurées) Les annotations ajoutées dans le Deployment ci-dessus suffisent si Prometheus est configuré avec Service Discovery : ```yaml showLineNumbers annotations: prometheus.io/scrape: "true" prometheus.io/port: "9253" prometheus.io/path: "/metrics" ``` ##### Option 2 : `ServiceMonitor` (recommandé) Créez un Service et un `ServiceMonitor` pour une approche déclarative : :::code-group ```yaml showLineNumbers [service.yaml] # Service pour exposer les métriques PHP-FPM apiVersion: v1 kind: Service metadata: name: myapp-php-metrics namespace: myapp labels: app: myapp tier: backend metrics: phpfpm spec: selector: app: myapp tier: backend ports: - name: metrics port: 9253 targetPort: 9253 protocol: TCP ``` ```yaml showLineNumbers [servicemonitor.yaml] apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: myapp-php-metrics namespace: myapp labels: app: myapp release: sdv-monitoring # Label requis pour découverte par Prometheus spec: selector: matchLabels: app: myapp metrics: phpfpm endpoints: - port: metrics interval: 30s path: /metrics ``` ::: Déployez les ressources : ```bash [Terminal] # Déployer l'application kubectl apply -f deployment.yaml # Déployer le ServiceMonitor (recommandé sur cluster SdV) kubectl apply -f servicemonitor-phpfpm.yaml # Vérifier le ServiceMonitor kubectl get servicemonitor -n myapp kubectl describe servicemonitor myapp-php-metrics -n myapp ``` #### Vérification de la collecte Vérifiez que Prometheus scrape correctement les métriques : ```bash [Terminal] # Accéder à l'interface Prometheus et exécuter cette requête PromQL phpfpm_active_processes # Ou via port-forward (adapter le namespace et le service selon votre cluster) kubectl port-forward -n svc/ 9090:80 # Puis ouvrir http://localhost:9090 ``` :::tip Le namespace et le nom du service Prometheus peuvent varier selon la configuration de votre cluster SdV. Contactez votre administrateur pour obtenir ces informations. Exemples courants : `monitoring`, `observability`, `prometheus`. ::: ### Configuration du HPA #### Création du HPA avec métrique custom L'objet HPA doit être déployé dans le **même `Namespace`** que l'application à scaler. Il surveille périodiquement la métrique et ajuste le nombre de réplicas selon les seuils définis. **Exemple de HPA basé sur `phpfpm_process_utilization_rate` :** ```yaml showLineNumbers [hpa.yaml] apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: myapp-php-hpa namespace: myapp labels: app: myapp spec: # Cible du scaling scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: myapp-php # Limites de scaling minReplicas: 2 maxReplicas: 10 # Métriques de scaling metrics: - type: Pods pods: metric: name: phpfpm_process_utilization_rate target: type: AverageValue averageValue: "50" # Scale up si le ratio dépasse 50% # Comportements de scaling (optionnel mais recommandé) behavior: scaleUp: stabilizationWindowSeconds: 60 # Attendre 60s avant un scale up policies: - type: Pods value: 2 # Ajouter max 2 pods periodSeconds: 60 # Par période de 60s - type: Percent value: 50 # Ou augmenter de 50% periodSeconds: 60 selectPolicy: Max # Choisir la politique la plus agressive scaleDown: stabilizationWindowSeconds: 300 # Attendre 5min avant un scale down policies: - type: Pods value: 1 # Retirer max 1 pod periodSeconds: 120 # Par période de 2min selectPolicy: Min # Choisir la politique la plus conservatrice ``` :::info **Explication des paramètres :** | Paramètre | Description | | ---------------------------- | ------------------------------------------------------ | | `minReplicas` | Nombre minimum de réplicas (toujours maintenu) | | `maxReplicas` | Nombre maximum de réplicas (plafond de scaling) | | `target.averageValue` | Seuil moyen par `Pod` déclenchant le scaling | | `behavior.scaleUp` | Contrôle la vitesse et l'agressivité du scale up | | `behavior.scaleDown` | Contrôle la prudence du scale down (évite le flapping) | | `stabilizationWindowSeconds` | Fenêtre d'observation avant décision de scaling | ::: Appliquez le HPA : ```bash [Terminal] kubectl apply -f hpa.yaml ``` #### Vérification et monitoring du HPA ```bash [Terminal] # Vérifier l'état du HPA kubectl get hpa -n myapp # Affichage détaillé avec métriques actuelles kubectl describe hpa myapp-php-hpa -n myapp # Surveiller en temps réel kubectl get hpa -n myapp -w # Consulter les événements de scaling kubectl get events -n myapp --sort-by='.lastTimestamp' | grep HPA ``` Sortie exemple : ``` NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE myapp-php-hpa Deployment/myapp-php 42/50 2 10 2 5m ``` :::tip **Interprétation :** * `TARGETS` : Valeur actuelle / Seuil cible (ici 42/50 = 84% du seuil) * `REPLICAS` : Nombre actuel de Pods (scaling automatique entre 2 et 10) * Si la valeur actuelle dépasse durablement 50, le HPA ajoutera des Pods ::: ### Commandes kubectl utiles ```bash [Terminal] # Lister tous les HPA du cluster kubectl get hpa --all-namespaces # Tester le scaling manuellement (pour debug) kubectl scale deployment myapp-php --replicas=5 -n myapp # Supprimer temporairement le HPA (revenir au scaling manuel) kubectl delete hpa myapp-php-hpa -n myapp # Consulter les métriques via l'API custom.metrics kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1/namespaces/myapp/pods/*/phpfpm_process_utilization_rate" | jq . # Diagnostiquer les erreurs de récupération de métriques (adapter le namespace Prometheus) kubectl logs -n -l app.kubernetes.io/name=prometheus-adapter # Vérifier que Metrics Server fonctionne (pour CPU/Memory) kubectl top nodes kubectl top pods -n myapp ``` ### Bonnes pratiques HPA #### Dimensionnement des seuils | Métrique | Seuil recommandé | Justification | | ---------------- | ---------------- | --------------------------------------------------------------- | | CPU | 70-80% | Laisse une marge pour les pics temporaires | | Memory | 80-85% | Évite les OOMKill tout en optimisant l'utilisation | | Métriques custom | Variable | Dépend de la nature de la métrique (pools, queues, latences...) | #### Éviter le flapping Le **flapping** (oscillation rapide du nombre de `Pods`) peut survenir si : * Les seuils sont trop proches de la charge moyenne * La fenêtre de stabilisation (`stabilizationWindowSeconds`) est trop courte * Les métriques sont bruitées (pics aléatoires) **Solutions :** * Augmenter `stabilizationWindowSeconds` (5-10 minutes pour scale down) * Utiliser des métriques lissées (moyenne sur 5-10min) * Définir des politiques `scaleDown` prudentes (1 Pod toutes les 2-5 minutes) #### Sizing initial Avant d'activer l'autoscaling : 1. **Load test** votre application pour connaître les seuils réalistes 2. Définissez `minReplicas` en fonction de la charge de base 3. Calibrez les `requests` CPU/Memory pour que les métriques soient représentatives :::warning Si `requests` est trop bas, le HPA verra une utilisation CPU/Memory artificiellement élevée et sur-scalera. Si trop haut, il sous-scalera. ::: #### Monitoring et alerting Mettez en place des alertes sur : * HPA atteignant `maxReplicas` (capacité saturée) * HPA ne pouvant pas récupérer les métriques (erreur adaptateur) * Scaling trop fréquent (flapping détecté) * Temps de réponse ou erreurs applicatives pendant le scaling ### Autoscaling événementiel avec KEDA **KEDA** (Kubernetes Event-Driven Autoscaling) est un projet CNCF qui étend les capacités de l'HPA standard pour supporter : * Le **scale-to-zero** (réduire à 0 réplica en l'absence de charge) * Plus de **60 scalers** prêts à l'emploi (RabbitMQ, Kafka, Azure Queue, AWS SQS, Redis, PostgreSQL, etc.) * Des sources de métriques externes sans adapter personnalisé * L'agrégation de plusieurs métriques avec des stratégies complexes #### Différences HPA vs KEDA | Critère | HPA standard | KEDA | | ------------------------ | ------------------------------------------- | ------------------------------------- | | **Scale-to-zero** | Non (minReplicas ≥ 1) | Oui (0 réplica possible) | | **Sources de métriques** | CPU, Memory, Custom, External | 60+ scalers intégrés | | **Configuration** | Objet HPA natif | Objet `ScaledObject` (CRD) | | **Complexité** | Nécessite adaptateurs pour métriques custom | Scalers clé en main | | **Cas d'usage** | Applications web, API REST | Traitement asynchrone, batch, workers | :::info KEDA crée et gère automatiquement des objets HPA en arrière-plan. Vous ne manipulez que des objets `ScaledObject` ou `ScaledJob`. ::: #### Installation de KEDA KEDA se déploie via Helm dans un namespace dédié : ```bash [Terminal] # Ajouter le repo Helm KEDA helm repo add kedacore https://kedacore.github.io/charts helm repo update # Installer KEDA kubectl create namespace keda helm install keda kedacore/keda --namespace keda ``` Vérifiez l'installation : ```bash [Terminal] kubectl get pods -n keda kubectl api-resources | grep keda ``` Vous devriez voir les CRD suivantes : * `ScaledObject` : Pour scaler des `Deployments`/`StatefulSets` * `ScaledJob` : Pour créer des `Jobs` à la demande * `TriggerAuthentication` : Pour gérer les authentifications externes #### Exemple 1 : Scaling basé sur RabbitMQ **Cas d'usage :** Une application consomme des messages d'une queue RabbitMQ. On souhaite scaler en fonction de la profondeur de la queue. ```yaml showLineNumbers [scaledobject-rabbitmq.yaml] apiVersion: keda.sh/v1alpha1 kind: ScaledObject metadata: name: rabbitmq-consumer-scaler namespace: myapp spec: # Cible du scaling scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: message-consumer # Scale-to-zero activé minReplicaCount: 0 maxReplicaCount: 30 # Période d'inactivité avant scale-to-zero cooldownPeriod: 300 # 5 minutes # Période de polling des métriques pollingInterval: 30 # 30 secondes # Scalers (sources de métriques) triggers: - type: rabbitmq metadata: # Connection string (ou via TriggerAuthentication) host: amqp://user:password@rabbitmq.myapp.svc.cluster.local:5672/vhost queueName: tasks queueLength: "5" # Scale up si plus de 5 messages par Pod protocol: auto ``` :::warning **Sécurité :** Ne pas mettre les credentials en clair dans le manifest. Utilisez un objet `TriggerAuthentication` avec un `Secret` Kubernetes. ::: **Avec `TriggerAuthentication` (recommandé) :** :::code-group ```yaml showLineNumbers [secret.yaml] apiVersion: v1 kind: Secret metadata: name: rabbitmq-secret namespace: myapp type: Opaque stringData: host: amqp://user:password@rabbitmq.myapp.svc.cluster.local:5672/vhost ``` ```yaml showLineNumbers [triggerauthentication.yaml] apiVersion: keda.sh/v1alpha1 kind: TriggerAuthentication metadata: name: rabbitmq-trigger-auth namespace: myapp spec: secretTargetRef: - parameter: host name: rabbitmq-secret key: host ``` ```yaml showLineNumbers [scaledobject.yaml] apiVersion: keda.sh/v1alpha1 kind: ScaledObject metadata: name: rabbitmq-consumer-scaler namespace: myapp spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: message-consumer minReplicaCount: 0 maxReplicaCount: 30 cooldownPeriod: 300 pollingInterval: 30 triggers: - type: rabbitmq authenticationRef: name: rabbitmq-trigger-auth metadata: queueName: tasks queueLength: "5" protocol: auto ``` ::: #### Exemple 2 : Scaling basé sur Prometheus KEDA peut aussi consommer des métriques Prometheus (alternative à Prometheus Adapter) : ```yaml showLineNumbers [scaledobject-prometheus.yaml] apiVersion: keda.sh/v1alpha1 kind: ScaledObject metadata: name: prometheus-scaler namespace: myapp spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: myapp minReplicaCount: 1 maxReplicaCount: 10 triggers: - type: prometheus metadata: # Adapter l'URL selon votre cluster SdV serverAddress: http://..svc.cluster.local:80 # Requête PromQL query: sum(rate(http_requests_total{job="myapp"}[2m])) # Seuil : 1 réplica pour 100 requêtes/sec threshold: "100" ``` #### Exemple 3 : Scaling basé sur un Cron Scaling préventif basé sur un calendrier (montée en charge anticipée) : ```yaml showLineNumbers [scaledobject-cron.yaml] apiVersion: keda.sh/v1alpha1 kind: ScaledObject metadata: name: cron-scaler namespace: myapp spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: myapp minReplicaCount: 1 maxReplicaCount: 20 triggers: # Scale up à 10 réplicas entre 8h et 18h en semaine - type: cron metadata: timezone: Europe/Paris start: 0 8 * * 1-5 # Lundi-Vendredi à 8h end: 0 18 * * 1-5 # Lundi-Vendredi à 18h desiredReplicas: "10" ``` #### Exemple 4 : `ScaledJob` pour traitement batch Créer des `Jobs` Kubernetes à la demande (au lieu de scaler un `Deployment`) : ```yaml showLineNumbers [scaledjob.yaml] apiVersion: keda.sh/v1alpha1 kind: ScaledJob metadata: name: batch-processor namespace: myapp spec: jobTargetRef: template: metadata: labels: app: batch-job spec: containers: - name: processor image: myregistry/batch-processor:latest env: - name: TASK_ID value: "{{ .Task }}" restartPolicy: Never # Nombre max de jobs simultanés maxReplicaCount: 50 # Stratégie de rollout (default, gradual, accurate) rollout: strategy: default propagationPolicy: foreground triggers: - type: rabbitmq metadata: queueName: batch-tasks queueLength: "10" # 1 Job pour 10 messages ``` #### Commandes kubectl pour KEDA ```bash [Terminal] # Lister les ScaledObjects kubectl get scaledobject -A kubectl get so -A # Alias court # Détails d'un ScaledObject kubectl describe scaledobject rabbitmq-consumer-scaler -n myapp # Lister les HPA créés automatiquement par KEDA kubectl get hpa -n myapp # Consulter les logs KEDA (debugging) kubectl logs -n keda -l app=keda-operator # Désactiver temporairement un ScaledObject (sans le supprimer) kubectl annotate scaledobject rabbitmq-consumer-scaler autoscaling.keda.sh/paused=true -n myapp # Réactiver kubectl annotate scaledobject rabbitmq-consumer-scaler autoscaling.keda.sh/paused- -n myapp ``` #### Liste des scalers populaires | Scaler | Source de métrique | Cas d'usage | | --------------- | -------------------------- | ------------------------------- | | **rabbitmq** | Profondeur queue RabbitMQ | Traitement messages asynchrones | | **kafka** | Consumer lag Kafka | Streaming events, CDC | | **azure-queue** | Azure Queue Storage | Batch processing sur Azure | | **aws-sqs** | AWS SQS queue depth | Batch processing sur AWS | | **prometheus** | Métriques Prometheus | Application métrique custom | | **postgresql** | Nombre de lignes query SQL | Traitement base de données | | **redis** | Length de liste Redis | Queues Redis | | **cron** | Calendrier | Scaling préventif | | **cpu** | CPU usage (comme HPA) | Fallback simple | | **memory** | Memory usage | Fallback simple | Consultez la [liste complète des scalers](https://keda.sh/docs/scalers/) dans la documentation officielle KEDA. #### Bonnes pratiques KEDA ##### Scale-to-zero Le scale-to-zero est adapté pour : * Workers de traitement asynchrone (queues, batch) * Applications pouvant tolérer un cold start (démarrage de Pod) * Environnements non-production (dev, staging) :::warning **Attention au scale-to-zero pour :** * Applications critiques temps-réel (latence de démarrage inacceptable) * Services REST/API synchrones (le premier appel échoue pendant le scale-up) * Bases de données (pertes de connexions, états internes) **Solution :** Utilisez `minReplicaCount: 1` pour ces cas. ::: ##### Sécurité des credentials Toujours utiliser `TriggerAuthentication` + `Secret` pour les credentials : ```yaml showLineNumbers [triggerauthentication.yaml] apiVersion: keda.sh/v1alpha1 kind: TriggerAuthentication metadata: name: aws-sqs-auth namespace: myapp spec: secretTargetRef: - parameter: awsAccessKeyID name: aws-credentials key: AWS_ACCESS_KEY_ID - parameter: awsSecretAccessKey name: aws-credentials key: AWS_SECRET_ACCESS_KEY ``` ##### Monitoring KEDA Intégrez KEDA avec votre stack de monitoring : ```bash [Terminal] # Prometheus ServiceMonitor pour KEDA (préinstallé sur cluster SdV) kubectl apply -f https://raw.githubusercontent.com/kedacore/keda/main/config/prometheus/monitor.yaml # Métriques exposées par KEDA # - keda_scaler_errors_total # - keda_scaler_metrics_value # - keda_scaled_object_paused ``` ##### Performance et limites | Paramètre | Valeur par défaut | Recommandation | | ----------------- | ----------------- | --------------------------------------------------------- | | `pollingInterval` | 30s | 10-30s pour charges stables, 5-10s pour charges variables | | `cooldownPeriod` | 300s (5min) | 300-600s pour éviter le flapping | | `maxReplicaCount` | - | Définir une limite pour éviter l'explosion de coûts | ### Comparaison : Quand utiliser HPA ou KEDA ? #### Utilisez HPA standard si : * Vous scalez sur CPU/Memory uniquement * Vous avez déjà un Prometheus Adapter configuré * Vous ne nécessitez pas de scale-to-zero * Architecture simple (API REST, applications stateless classiques) #### Utilisez KEDA si : * Vous avec des workers consommant des queues (RabbitMQ, Kafka, SQS, etc.) * Le scale-to-zero est nécessaire (économies, environnements éphémères) * Vous utilisez des sources externes (bases, cloud services) * Vous voulez combiner plusieurs métriques (cron + prometheus + queue) * Vous préférez une configuration unifiée (pas d'adapter à maintenir) #### Utilisez les deux (hybride) si : * HPA pour les services web/API (CPU/Memory) * KEDA pour les workers/batch asynchrones (queues) :::tip Dans un cluster SdV, vous pouvez installer KEDA en parallèle de votre stack Prometheus existante. Les deux cohabitent sans conflit. KEDA peut même utiliser Prometheus comme source de métriques. ::: ### Troubleshooting #### HPA n'obtient pas les métriques ```bash [Terminal] # Vérifier l'état de l'API custom metrics kubectl get apiservices v1beta1.custom.metrics.k8s.io # Vérifier les logs de Prometheus Adapter (adapter le namespace selon votre cluster) kubectl logs -n -l app.kubernetes.io/name=prometheus-adapter # Tester manuellement l'API kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1" | jq . ``` **Causes fréquentes :** * Prometheus Adapter mal configuré (mauvaise règle `seriesQuery`) * Prometheus ne scrape pas les Pods (annotations manquantes) * Métrique Prometheus inexistante ou mal nommée #### KEDA ne scale pas ```bash [Terminal] # Vérifier l'état du ScaledObject kubectl get scaledobject -n myapp kubectl describe scaledobject -n myapp # Consulter les logs KEDA kubectl logs -n keda -l app=keda-operator --tail=100 # Vérifier le HPA créé par KEDA kubectl get hpa -n myapp kubectl describe hpa -n myapp ``` **Causes fréquentes :** * Credentials incorrects (vérifier `TriggerAuthentication`) * Connectivité réseau vers la source externe (RabbitMQ, AWS, etc.) * Requête métrique invalide (PromQL mal formé, queue inexistante) * MinReplicaCount = MaxReplicaCount (scaling désactivé) #### Flapping (oscillations) Symptômes : Le nombre de `Pods` oscille rapidement entre 2 valeurs. **Solutions :** * Augmenter `stabilizationWindowSeconds` dans HPA * Augmenter `cooldownPeriod` dans KEDA * Lisser les métriques (moyenne sur fenêtre glissante) * Ajuster les seuils pour avoir une zone tampon ### Ressources et documentation * [Documentation officielle HPA Kubernetes](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) * [Prometheus Adapter GitHub](https://github.com/kubernetes-sigs/prometheus-adapter) * [Documentation KEDA](https://keda.sh/) * [Liste complète des KEDA scalers](https://keda.sh/docs/scalers/) * [Blog : HPA deep dive](https://kubernetes.io/blog/2016/07/autoscaling-in-kubernetes/) ### Conclusion L'autoscaling horizontal est essentiel pour garantir la disponibilité et l'efficacité des applications Kubernetes. Le **HPA natif** couvre les besoins standards (CPU, Memory, métriques custom via adaptateurs), tandis que **KEDA** apporte des fonctionnalités avancées pour l'autoscaling événementiel et le scale-to-zero. Sur un cluster SdV : * **Deployez HPA + Prometheus Adapter** pour vos API / applications web classiques * **Ajoutez KEDA** pour vos workers asynchrones et traitements batch * Combinez les deux approches selon vos cas d'usage L'investissement initial dans l'infrastructure de métriques et d'autoscaling est rapidement rentabilisé par l'amélioration de la disponibilité et l'optimisation des coûts d'exploitation. ## Utilisation avancée des Ingress HAProxy Ce guide présente les usages avancés des objets `Ingress` sur Kubernetes avec le contrôleur HAProxy, adaptés à l'environnement SdV. Il s'adresse aux équipes DevOps/Ops/SRE souhaitant maîtriser l'exposition, la sécurité et la personnalisation du trafic HTTP(S). ### Introduction L'objet `Ingress` permet d'exposer des services internes Kubernetes à l'extérieur du cluster via des règles HTTP/S. Le contrôleur HAProxy offre de nombreuses options de personnalisation via annotations et ConfigMap. Consultez la [documentation officielle HAProxy Ingress](https://haproxy-ingress.github.io/v0.14/docs/configuration/keys/) pour la liste complète des clés. :::info Depuis Kubernetes 1.18+, privilégiez le champ `ingressClassName` dans vos manifestes Ingress. L'annotation `kubernetes.io/ingress.class` est obsolète. ::: :::tip Pour certain besoin il peut être nécessaire d'avoir un autre ingress controler que celui proposé par SdV. C'est possible mais uniquement en parallèle de celui de SdV sur une VIP supplémentaire.\ Les contraintes de routage interne et de configuration automatique de notre load-balancer externe font que le déploiement d'un autre ingress doit se faire en **`DaemonSet`** sur l'ensemble des worker nodes Kubernetes. Pour ce genre de configuration spécifique, merci de contacter SdV. ::: ### Configuration générale Selon le scope, la configuration peut se faire : * via des **annotations** sur l'objet Ingress (recommandé pour la plupart des cas) * via une **`ConfigMap`** globale (réservé à l'administration du cluster) :::warning Les clés de scope "Global" nécessitent une intervention de SdV (`ConfigMap` déployée via Helm). ::: ### Restrictions d'accès par IP Pour limiter l'accès à un service selon l'adresse IP source, utilisez l'annotation suivante : ```yaml showLineNumbers [ingress.yaml] apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ghost annotations: ingress.kubernetes.io/whitelist-source-range: 212.95.64.0/19,1.2.3.4 spec: ingressClassName: public rules: - host: ghost.{{publicVIP}}.nip.io http: paths: - path: / pathType: ImplementationSpecific backend: service: name: ghost-svc port: number: 80 ``` Pour des besoins avancés, utilisez `ingress.kubernetes.io/config-backend` pour injecter des ACL HAProxy personnalisées : ```yaml showLineNumbers annotations: ingress.kubernetes.io/config-backend: | acl whitelist src 1.2.3.4 acl protected_host hdr(host) -i ghost.{{publicVIP}}.nip.io http-request deny if protected_host !whitelist ``` ### Protection par mot de passe (basic auth) La première étape est de créer le hash de votre mot de passe au format `sha512crypt`, puis l'encoder au format `base64` avec le prefix `` (attention à l'échappement des charactères spéciaux): ```shell [Terminal] $ mkpasswd -m sha-512 -s mypassword $6$p5yZF8jSo5dEcxJx$n49C3xodRTuWkPXIqt6kd6Lra0nJCxO9yK1HU2eX7Mt7EP3Fg4RRcaKk.IvpMSuBet.zuFQk1UeAqVO6tjG8w0 $ echo -e "myuser:\$6\$p5yZF8jSo5dEcxJx\$n49C3xodRTuWkPXIqt6kd6Lra0nJCxO9yK1HU2eX7Mt7EP3Fg4RRcaKk.IvpMSuBet.zuFQk1UeAqVO6tjG8w0" | base64 bXl1c2VyOiQ2JHA1eVpGOGpTbzVkRWN4SngkbjQ5QzN4b2RSVHVXa1BYSXF0NmtkNkxyYTBuSkN4Tzl5SzFIVTJlWDdNdDdFUDNGZzRSUmNhS2suSXZwTVN1QmV0Lnp1RlFrMVVlQXFWTzZ0akc4dzAK ``` Nous allons ensuite stocker le résultat dans un `Secret`: ```yaml showLineNumbers [secret.yaml] apiVersion: v1 kind: Secret metadata: name: basic-auth data: auth: bXl1c2VyOiQ2JHA1eVpGOGpTbzVkRWN4SngkbjQ5QzN4b2RSVHVXa1BYSXF0NmtkNkxyYTBuSkN4Tzl5SzFIVTJlWDdNdDdFUDNGZzRSUmNhS2suSXZwTVN1QmV0Lnp1RlFrMVVlQXFWTzZ0akc4dzAK type: Opaque ``` Enfin, ajouter les annotations suivantes au niveau de votre objet `Ingress` pour activer l'authentification: ```yaml annotations: ingress.kubernetes.io/auth-type: basic ingress.kubernetes.io/auth-secret: basic-auth ingress.kubernetes.io/auth-realm: "Authentification Required" ``` ### Certificats ACME / Let's Encrypt Par défaut, l'ingress public SdV supporte ACME v2 (Let's Encrypt) en mode `http01`. Exemple : ```yaml showLineNumbers [ingress.yaml] apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ghost annotations: ingress.kubernetes.io/cert-signer: acme spec: ingressClassName: public tls: - hosts: - ghost.{{publicVIP}}.nip.io secretName: ghost-cert rules: - host: ghost.{{publicVIP}}.nip.io http: paths: - path: / pathType: ImplementationSpecific backend: service: name: ghost-svc port: number: 80 ``` Le certificat Let's Encrypt sera stocké dans le Secret `ghost-cert`. :::warning Pour utiliser CertManager à la place, demandez la désactivation ACME à SdV car elle peut entrer en conflit avec CertManager et entraîner l'impossiblité d'émettre des certificats TLS pendant une période définie par les règles de Lets Encrypt. ::: ### Gestion des entêtes `X-Forwarded-For` Les ingress HAProxy ajoutent automatiquement l'entête HTTP `X-Forwarded-For` avec l'IP source réelle. Si un entête existe déjà, il est déplacé en `X-Original-Forwarded-For`. **Exemple 1 :** ```bash [Terminal] $ curl ghost.212.95.76.25.nip.io ... X-Forwarded-For: 212.95.95.248 ... ``` **Exemple 2 :** ```bash [Terminal] curl -H 'X-Forwarded-For: 1.2.3.4' ghost.212.95.76.25.nip.io ... X-Forwarded-For: 212.95.95.248 ... ... X-Original-Forwarded-For: 1.2.3.4 ... ``` :::info Si un reverse-proxy (Varnish, etc.) précède l'ingress, vérifiez la gestion de ces entêtes dans votre application. ::: ### ServerAliases et multi-domaines L'annotation `ingress.kubernetes.io/server-alias` permet d'associer plusieurs FQDN à la même règle d'Ingress, sans dupliquer les blocs `rules`. Cela simplifie la gestion des alias DNS (par exemple, www, sans-www, etc.). Exemple : pour que `ghost.{{publicVIP}}.nip.io` et `blog.{{publicVIP}}.nip.io` pointent vers le même backend : ```yaml showLineNumbers [ingress.yaml] apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ghost annotations: ingress.kubernetes.io/server-alias: "blog.{{publicVIP}}.nip.io,www.{{publicVIP}}.nip.io" spec: ingressClassName: public rules: - host: ghost.{{publicVIP}}.nip.io http: paths: - path: / pathType: Prefix backend: service: name: ghost-svc port: number: 80 ``` :::info L'annotation accepte une liste de FQDN séparés par des virgules. Tous pointeront vers le même backend que le host principal. ::: :::tip Privilégiez toujours la déclaration de plusieurs hosts directement dans l'`Ingress` afin de permettre la génération de certificats TLS adaptés. ::: ### SSL Pass Through (TLS pass-through) Le mode SSL Pass Through permet de transmettre le trafic TLS (HTTPS) directement au backend sans terminaison SSL côté Ingress. Cela peut être utile pour des applications nécessitant la gestion de certificats côté service (SNI, mutual TLS, etc.). **Limitations :** * Ce mode désactive la plupart des fonctionnalités HTTP de l'Ingress (annotations, filtrage, réécriture, etc.). Pour activer le SSL Pass Through sur un backend, ajoutez l'annotation suivante : ```yaml annotations: ingress.kubernetes.io/secure-backends: "true" ``` Exemple d'Ingress avec SSL Pass Through : ```yaml showLineNumbers [ingress.yaml] apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: my-app-ssl-passthrough annotations: ingress.kubernetes.io/secure-backends: "true" spec: ingressClassName: public rules: - host: myapp.{{publicVIP}}.nip.io http: paths: - path: / pathType: ImplementationSpecific backend: service: name: myapp-svc port: number: 443 ``` ### Bonnes pratiques et notes d'exploitation * Privilégiez les annotations pour la configuration fine. * Utilisez `ingressClassName` (`public` ou `private`) selon le niveau d'exposition souhaité. * Sécurisez vos Ingress (HTTPS, filtrage IP, authentification). * Documentez chaque `Ingress` (labels, annotations, description). * Vérifiez la génération automatique des certificats et leur renouvellement. * Surveillez les logs et métriques HAProxy pour le diagnostic. ### Commandes utiles ```bash # Lister les ingress kubectl get ingress -A # Voir les détails d'un ingress kubectl describe ingress -n # Voir les annotations d'un ingress kubectl get ingress -n -o yaml ``` ## Authentification OIDC avec Keycloak pour Kubernetes La gestion des accès aux clusters Kubernetes représente un défi récurrent pour les équipes de développement. Le partage de fichiers `kubeconfig` avec des privilèges inadaptés (trop larges ou trop restrictifs) génère des risques de sécurité et des difficultés opérationnelles. Ce guide présente l'implémentation du protocole [OpenID Connect (OIDC)](https://openid.net/connect/) avec Keycloak pour centraliser l'authentification des utilisateurs sur un cluster Kubernetes. Cette approche permet de : * Utiliser un **fichier `kubeconfig` unique** partageable entre tous les utilisateurs * Hériter automatiquement des **droits RBAC** basés sur les groupes Keycloak * Centraliser la gestion des identités et des accès * Révoquer instantanément les droits sans redistribuer de fichiers * Tracer précisément les actions par utilisateur :::info OpenID Connect (OIDC) est un protocole d'authentification standardisé construit au-dessus d'OAuth 2.0. Il permet de déléguer l'authentification à un fournisseur d'identité externe (IdP) tout en préservant la gestion des autorisations (RBAC) au niveau Kubernetes. ::: ### Prérequis Avant de débuter, assurez-vous de disposer de : * **Une instance Keycloak** accessible pour vos utilisateurs avec accès administrateur * **Un cluster Kubernetes chez SdV** avec le `kubeconfig` administrateur * **kubectl** installé localement sur votre poste (version compatible avec le cluster) * **Droits suffisants** pour créer des ressources RBAC dans le cluster :::warning Ce guide ne couvre pas l'installation d'une instance Keycloak dans Kubernetes. Consultez la [documentation officielle Keycloak](https://www.keycloak.org/documentation) pour le déploiement. ::: :::info Versions utilisées lors de la rédaction de ce guide : * Cluster Kubernetes 1.20.14+ (compatible avec toute version ≥ 1.19) * kubectl 1.23.3+ * Keycloak 11.0.2+ (les versions ultérieures peuvent présenter des différences d'interface) * kubelogin (oidc-login) : dernière version disponible ::: :::warning **Changement important dans Keycloak 17+ :** Les versions récentes de Keycloak (`≥ 17`) ont modifié la structure des URLs : * **Anciennes versions (`< 17`)** : `https://keycloak.example.com/auth/realms/kubernetes` * **Nouvelles versions (`≥ 17`)** : `https://keycloak.example.com/realms/kubernetes` (suppression de `/auth`) Adaptez les URLs dans votre configuration en fonction de votre version de Keycloak. Vous pouvez vérifier en accédant à l'endpoint discovery : ```bash [Terminal] # Keycloak ancienne version (< 17) curl https://[KEYCLOAK-URL]/auth/realms/[REALM]/.well-known/openid-configuration # Keycloak nouvelle version (≥ 17) curl https://[KEYCLOAK-URL]/realms/[REALM]/.well-known/openid-configuration ``` ::: ### Architecture et flux d'authentification #### Schéma du flux OIDC Voici le déroulement complet d'une authentification OIDC avec Kubernetes : ![OIDC Flow](/guides/oidc/oidc-flow.png) #### Composants de l'architecture | Composant | Rôle | Responsabilité | | ----------------------------- | ------------------------- | ------------------------------------------------------------------------------- | | **Keycloak** | Identity Provider (IdP) | Authentification des utilisateurs, gestion des groupes, émission des tokens JWT | | **API Server Kubernetes** | Autorisation | Validation des tokens JWT, application des règles RBAC | | **kubectl** | Client CLI | Exécution des commandes, appel du plugin kubelogin | | **kubelogin** | Plugin d'authentification | Orchestration du flux OAuth2/OIDC, mise en cache des tokens | | **RBAC (`Roles`/`Bindings`)** | Politique d'autorisation | Définition des permissions par utilisateur/groupe | | **kubeconfig** | Configuration | Définition du cluster, du mécanisme exec, des paramètres OIDC | #### Sécurité du flux Le flux utilise plusieurs mécanismes de sécurité : * **PKCE (Proof Key for Code Exchange)** : Protection contre l'interception du code d'autorisation * **State parameter** : Protection contre les attaques CSRF * **Signature JWT** : Garantie de l'intégrité et de l'authenticité du token (RS256/ES256) * **HTTPS obligatoire** : Chiffrement de tous les échanges avec Keycloak * **Token courte durée** : Limitation de la fenêtre d'exploitation en cas de compromission * **Redirect URI restreinte** : Seul localhost:8000 est autorisé (pas de redirection externe) ### Paramétrage de Keycloak #### Création du client Keycloak La première étape pour nous va être de déclarer une nouvelle application Keycloak dans votre `realm`.\ C'est grâce à celle-ci que nous serons en mesure de configurer l'OIDC sur votre cluster Kubernetes. Rendez-vous dans le panneau d'administration de votre instance Keycloak et identifiez-vous avec un compte administrateur. Sélectionnez le `realm` avec lequel vous souhaitez faire le lien avec votre cluster Kubernetes. Dans notre cas, ce sera `kubernetes`. Dans le menu de gauche, cliquez sur `Configure > Clients`. Cliquez sur le bouton `Create` en haut à droite de la liste des clients existants.\ Vous voyez apparaître le formulaire suivant. ![Ecran de création d'un client](keycloak-oidc-01.png) Remplissez le formulaire de création de client avec les informations suivantes : | Paramètre | Valeur | Description | | ------------------- | ---------------- | ------------------------------------------------ | | **Client ID** | `kube-oidc` | Identifiant unique du client (personnalisable) | | **Client Protocol** | `openid-connect` | Protocole d'authentification à utiliser | | **Root URL** | *(vide)* | Aucune URL racine nécessaire pour ce cas d'usage | Cliquez sur `Save`, vous êtes redirigé vers la page d'édition du client Keycloak que vous venez de créer. ![Ecran d'édition d'un client](keycloak-oidc-02.png) Sur cette page, modifiez les éléments suivants : | Paramètre | Valeur | Raison | | ----------------------- | ----------------------- | ------------------------------------------------------ | | **Access Type** | `Confidential` | Génère un `Client Secret` pour authentifier les appels | | **Valid Redirect URIs** | `http://localhost:8000` | URL de callback locale pour `kubectl` (via kubelogin) | :::info **Fonctionnement de la redirection locale :** * L'Access Type `Confidential` génère un secret client nécessaire pour l'authentification OAuth 2.0 * L'URL `http://localhost:8000` est utilisée par le plugin `kubelogin` après authentification * `kubelogin` ouvre automatiquement un serveur HTTP temporaire sur le port 8000 pour recevoir le token * Cette URL **ne nécessite aucune configuration réseau** particulière (uniquement localhost) ::: Une fois ces paramètres changés, un nouvel onglet `Credentials` apparaît dans la console Keycloak. Cliquez dessus. ![Gestion des Credentials du client](keycloak-oidc-03.png) Vous aurez besoin de ces informations pour les dernières étapes de configuration : * `Client ID` (selon le nommage réalisé lors de la création du client) * `Client Secret` (depuis l'onglet `Credentials`) #### Création du mapping de groupes Par défaut, lorsque l'utilisateur validera son authentification au travers du client que vous venez de créer, les informations sur les groupes auxquels l'utilisateur appartient ne remontent pas dans le token JWT renvoyé à `kubectl`. Nous devons activer cela en faisant un mapping de groupes. Rendez-vous sur l'onglet `Mappers`, puis cliquez sur le bouton `Create` en haut à droite de la liste des mappings déjà existants (qui devrait être vide à ce stade). ![Gestion des mappings de groupes](keycloak-oidc-04.png) Sur cette page, ajoutez les informations comme sur la capture : | Paramètre | Valeur | Description | | ----------------------- | ------------------ | ------------------------------------------------------------ | | **Name** | `Groups` | Nom du mapper à créer | | **Mapper Type** | `Group Membership` | Type de mapper pour inclure les groupes | | **Token Claim Name** | `groups` | Nom du claim dans le token JWT | | **Full group path** | `Off` | Utiliser uniquement le nom du groupe (pas le chemin complet) | | **Add to ID token** | `On` | Inclure dans le token d'identification | | **Add to access token** | `On` | Inclure dans le token d'accès | | **Add to userinfo** | `On` | Inclure dans la réponse userinfo | :::info **Rôle du mapper de groupes :** Par défaut, les tokens JWT OIDC ne contiennent pas les informations de groupes. Le mapper permet d'injecter la liste des groupes Keycloak dans le claim `groups` du token JWT. Kubernetes utilisera cette information pour mapper les autorisations RBAC sur les groupes. ::: Validez en cliquant sur `Save`. ### Activer l'OIDC sur votre Cluster [Contactez l'équipe Système de SdV](https://requete.sdv.fr/) et demandez l'activation de l'OIDC sur votre cluster Kubernetes. Les élements de configuration à communiquer sont les suivants : | Paramètre |  Description | Valeur à fournir | | ---------------------- | --------------------------------------------------------------- | ----------------------------------------------------------------- | | `oidc-issuer-url` | URL vers votre instance Keycloak en intégrant le nom du `realm` | `https://URL-DE-VOTRE-INSTANCE-KEYCLOAK/auth/realms/NOM-DU-REALM` | | `oidc-client-id` | Client ID que vous avez créé | `kube-oidc` | | `oidc-username-claim` | Noeud du token JWT où est stocké le UserID | `sub` | | `oidc-username-prefix` | Préfixe à ajouter aux UserID issus de l'OIDC | `oidcuser:` | | `oidc-groups-claim` | Noeud du token JWT où est stockée la liste des groupes | `groups` | | `oidc-gorups-prefix` | Préfixe à ajouter aux Groupes issus de l'OIDC | `oidcgroup:` | Lorsqu'un utilisateur se voit identifié dans Keycloak, Kubernetes va vérifier ses droits dans les RBAC du cluster. L'identifiant de l'utilisateur sera `oidcuser:XXXX-XXXX-XXXX-XXXX` pour un utilisateur et `oidcgroup:YYYY-YYYY-YYYY-YYYY` pour un groupe. Cela permet d'utiliser ces notations pour la création des `ClusterRoleBindings` et `RoleBindings`. ### Création du fichier `kubeconfig` générique Le fichier `kubeconfig` suivant utilise le mécanisme `exec` pour déléguer l'authentification au plugin `kubelogin`. Ce fichier peut être **partagé avec tous les utilisateurs** sans compromettre la sécurité. ```yaml showLineNumbers [kubeconfig-oidc.yaml] apiVersion: v1 kind: Config preferences: {} # Définition du cluster Kubernetes clusters: - cluster: # URL de l'API Server Kubernetes (fournie par SdV) server: [CLUSTER-API-ENDPOINT] # Certificat racine du cluster (fourni par SdV) certificate-authority-data: [CLUSTER-CA-CRT] name: kubernetes-oidc # Définition des utilisateurs (authentification déléguée à OIDC) users: - name: oidc-user user: exec: # Version de l'API d'authentification client apiVersion: client.authentication.k8s.io/v1beta1 # Commande kubectl (qui délègue à kubelogin via plugin) command: kubectl # Arguments passés au plugin kubelogin args: - oidc-login - get-token - --oidc-issuer-url=https://[KEYCLOAK-URL]/auth/realms/[KEYCLOAK-REALM] - --oidc-client-id=[KEYCLOAK-CLIENT-ID] - --oidc-client-secret=[KEYCLOAK-CLIENT-SECRET] - --oidc-extra-scope=email - --grant-type=authcode env: null # Définition des contextes (association cluster + utilisateur + namespace par défaut) contexts: - context: cluster: kubernetes-oidc user: oidc-user namespace: default # Namespace par défaut (modifiable selon vos besoins) name: oidc-context # Contexte actif par défaut current-context: oidc-context ``` :::info **Fonctionnement du mécanisme exec :** * Lorsque vous exécutez une commande `kubectl`, celui-ci appelle le plugin `kubelogin` via le mécanisme `exec` * `kubelogin` ouvre votre navigateur sur Keycloak pour authentification * Après validation, `kubelogin` reçoit le token JWT et le renvoie à `kubectl` * `kubectl` utilise ce token pour s'authentifier auprès de l'API Server * Le token est **mis en cache localement** (`~/.kube/cache`) pour éviter de redemander l'authentification à chaque commande ::: Remplacez les valeurs entourées de crochets dans le yaml ci-dessus, selon le tableau suivant : | Clé | Description | Valeur par défaut | | -------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------- | | `[CLUSTER-API-ENDPOINT]` | API Kubernetes de votre cluster | Réutilisez la valeur définie dans le `kubeconfig` fourni par SdV au même niveau de yaml | | `[CLUSTER-CA-CRT]` | Certificat d'Autorité de votre cluster | Réutilisez la valeur définie dans le `kubeconfig` fourni par SdV au même niveau de yaml | | `[KEYCLOAK-URL]` | URL de votre instance Keycloak | Selon votre installation | | `[KEYCLOAK-REALM]` | Nom du `realm` Keycloak à utiliser |  Selon votre installation | | `[KEYCLOAK-CLIENT-ID]` | Client ID du Client créé dans ce guide | `kube-oidc` | | `[KEYCLOAK-CLIENT-SECRET]` | Client Secret du Client créé dans ce guide | Contextuel à votre paramétrage | ### Valider le fonctionnement du `kubeconfig` Maintenant que vous disposez d'un fichier `kubeconfig`, il faut valider le bon fonctionnement lors de son utilisation. En premier lieu, nous créerons un utilisateur Keycloak, ensuite nous créerons un groupe, ainsi que les droits associés dans le cluster Kubernetes au travers des RBAC. #### Création des utilisateurs Connectez-vous à la console d'administration de votre instance Keycloak et sélectionnez le `realm` adéquat. Dans le menu de gauche, rendez-vous dans `Manage > Users` puis cliquez sur le bouton `Add user` en haut à droite de la liste des utilisateurs (vide par défaut). ![Création d'un utilisateur Keycloak](keycloak-oidc-05.png) Remplissez le formulaire en veillant bien à ce que : * `User Enabled` soit à `On` * `Email Verified` soit à `On` Validez la création en cliquant sur `Save`. Vous êtes redirigé sur le formulaire de gestion de l'utilisateur nouvellement créé. ![Définition du mot de passe utilisateur](keycloak-oidc-06.png) Cliquez sur l'onglet `Credentials`, et définissez un mot de passe pour l'utilisateur en remplissant les champs `Password` et `Password Confirmation`. Décochez `Temporary` pour ne pas forcer la redéfinition du mot de passe de l'utilisateur à sa première connexion. Validez en cliquant sur `Set Password`. Répétez l'opération pour disposer d'un second utilisateur. Pour notre guide, nous avons créé : * `oidc-admin-user` qui sera associé aux droits d'administration sur le cluster Kubernetes * `oidc-regular-user` qui n'aura de droits que dans l'un des Namespaces du cluster Kubernetes #### Création des groupes Nous disposons des utilisateurs, nous allons désormais créer les groupes et les y associer. Rendez-vous dans la console d'administration Keycloak, dans le menu de gauche, cliquez sur `Manage > Groups` puis cliquez sur le bouton `New` en haut à droite de la liste des groupes. ![Création de groupe Keycloak](keycloak-oidc-07.png) Dans le formulaire qui suit, vous n'avez qu'à saisir le nom du groupe souhaité. Nous créons deux groupes, l'un pour les administrateurs du cluster, l'autre pour les utilisateurs verrouillés dans un Namespace : * `ClusterAdmins` * `DefaultNamespace` Nous devons désormais associer nos utilisateurs à chaque groupe. Rendez-vous dans la console d'administration Keycloak, dans le menu de gauche, cliquez sur `Manage > Users`. ![Choix des utilisateurs](keycloak-oidc-08.png) Dans la liste des utilisateurs, sélectionnez `oidc-admin-user`, puis rendez-vous dans l'onglet `Groups`. ![Choix d'un groupe pour un utilisateur](keycloak-oidc-09.png) Sélectionnez le groupe `ClusterAdmins` et cliquez sur le bouton `Join`. Le groupe apparaît désormais dans la liste `Group Membership`. Retournez sur la liste des utilisateurs via `Manage > Users`, et répétez l'opération pour associer `oidc-regular-user` au groupe `DefaultNamespace`. #### Création des RBAC Au niveau du cluster Kubernetes, l'authentification se fera au travers du groupe auquel l'utilisateur identifié est associé. Pour se faire, nous aurons besoin de créer des `ClusterRoleBinding` et des `ClusterRole`. ##### Groupe des Administrateurs de Cluster Kubernetes embarque nativement déjà un `ClusterRole` nommé `cluster-admin` qui donne les droits complets sur le cluster. Nous n'avons donc qu'à créer un `ClusterRoleBinding` pour associer le groupe `ClusterAdmins` à `cluster-admin`. Créez un fichier comme qui suit : ```yaml showLineNumbers [oidc-cluster-admins.yml] apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: oidc-cluster-admins labels: app.kubernetes.io/component: rbac app.kubernetes.io/managed-by: kubectl app.kubernetes.io/part-of: oidc-auth roleRef: # Référence au ClusterRole natif cluster-admin (droits complets) apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cluster-admin subjects: - apiGroup: rbac.authorization.k8s.io kind: Group # Groupe Keycloak préfixé selon configuration OIDC name: oidcgroup:ClusterAdmins ``` Appliquez cette configuration à votre cluster en utilisant le fichier `kubeconfig` fourni par SdV (avec droits admin) : ```bash [Terminal] kubectl apply -f oidc-cluster-admins.yml # Output: # clusterrolebinding.rbac.authorization.k8s.io/oidc-cluster-admins created # Vérification kubectl get clusterrolebinding oidc-cluster-admins -o yaml ``` ##### Groupe des utilisateurs verrouillés Pour notre exemple, nous allons verrouiller nos utilisateurs appartenant au groupe `DefaultNamespace` dans le Namespace `default`. Pour pouvoir faire cela, nous aurons à créer un `Role` ainsi qu'un `RoleBinding`. A la différence du `ClusterRoleBinding` créé pour le groupe d'administration, le `RoleBinding` est namespacée dans l'espace de nom où l'utilisateur est verrouillé. Créez les fichiers comme qui suit : :::code-group ```yaml showLineNumbers [oidc-default-namespace-clusterrole.yml] apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: oidc-default-namespace-clusterrole labels: app.kubernetes.io/component: rbac app.kubernetes.io/managed-by: kubectl app.kubernetes.io/part-of: oidc-auth rules: # Permissions complètes sur toutes les ressources # (dans le cadre d'un RoleBinding, limité au namespace ciblé) - apiGroups: - '*' # Tous les API groups resources: - '*' # Toutes les ressources verbs: - '*' # Toutes les actions (get, list, create, update, delete, etc.) ``` ```yaml showLineNumbers [oidc-default-namespace.yml] apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: oidc-default-namespace namespace: default # Limitation au namespace default labels: app.kubernetes.io/component: rbac app.kubernetes.io/managed-by: kubectl app.kubernetes.io/part-of: oidc-auth roleRef: # Référence au ClusterRole défini ci-dessus apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: oidc-default-namespace-clusterrole subjects: - apiGroup: rbac.authorization.k8s.io kind: Group # Groupe Keycloak préfixé selon configuration OIDC name: oidcgroup:DefaultNamespace ``` ::: :::warning **Bonnes pratiques RBAC :** * L'exemple ci-dessus accorde **tous les droits** (`*`) dans le namespace. En production, réduisez les permissions au strict nécessaire. * Préférez créer des `ClusterRole` granulaires (ex: lecture seule, déploiement uniquement) plutôt que des permissions totales. * Documentez chaque `ClusterRole` et `RoleBinding` avec des labels et annotations explicites. ::: Appliquez ces RBAC dans votre cluster Kubernetes (avec le `kubeconfig` admin SdV) : ```bash [Terminal] # Application du ClusterRole kubectl apply -f oidc-default-namespace-clusterrole.yml # Output: # clusterrole.rbac.authorization.k8s.io/oidc-default-namespace-clusterrole created # Application du RoleBinding dans le namespace default kubectl apply -f oidc-default-namespace.yml # Output: # rolebinding.rbac.authorization.k8s.io/oidc-default-namespace created # Vérification kubectl get clusterrole oidc-default-namespace-clusterrole kubectl get rolebinding -n default oidc-default-namespace ``` ### Utilisation de l'OIDC sur votre cluster Nous disposons désormais de : * Un cluster Kubernetes configuré pour accepter l'authentification OIDC * Un fichier `kubeconfig` qui redirige l'utilisateur de `kubectl` vers le serveur OIDC Keycloak * Un utilisateur Keycloak `oidc-admin-user` associé au groupe `ClusterAdmins` * Un `ClusterRoleBinding` associant le groupe `oidcgroup:ClusterAdmins` au `ClusterRole` nommé `cluster-admin` * Un utilisateur Keyclaok `oidc-regular-user` associé au groupe `DefaultNamespace` * Un `RoleBinding` associant le groupe `oidcgroup:DefaultNamespace` au `ClusterRole` nommé `oidc-default-namespace-clusterrole` dans le Namespace `default` * Un `ClusterRole` nommé `oidc-default-namespace-clusterrole` définissant des droits spécifiques sur le cluster Kubernetes Pour tester cette mise en œuvre, les utilisateurs devront installer le plugin `kubectl` nommé `kubelogin` (aussi appelé `kubectl-oidc_login`). #### Installation de kubelogin Les instructions complètes sont disponibles sur [la page du projet kubelogin](https://github.com/int128/kubelogin#setup). **Installation rapide selon l'OS :** :::code-group ```bash [macOS] # macOS via Homebrew brew install int128/kubelogin/kubelogin ``` ```bash [linux] # Linux via script curl -LO https://github.com/int128/kubelogin/releases/latest/download/kubelogin_linux_amd64.zip unzip kubelogin_linux_amd64.zip sudo install kubelogin /usr/local/bin/kubectl-oidc_login ``` ```bash [windows] # Windows via Chocolatey choco install kubelogin ``` ::: ```bash [Terminal] # Vérification de l'installation kubectl oidc-login --version ``` :::info Le plugin `kubelogin` doit être installé sur **chaque poste utilisateur** qui souhaite s'authentifier via OIDC. Il n'y a aucune installation côté serveur. ::: #### Tester l'utilisateur `oidc-admin-user` Définissez la variable `KUBECONFIG` pour pointer vers le fichier `kubeconfig` que vous avez créé dans ce guide : ```bash [Terminal] export KUBECONFIG=/path/to/your/custom/kubeconfig-oidc.yaml # Vérification du contexte actif kubectl config current-context # Output: oidc-context ``` Puis demandez le listing des Namespaces du cluster Kubernetes : ```bash [Terminal] kubectl get namespaces ``` Votre navigateur ouvre un onglet sur le serveur Keycloak et invite l'utilisateur à s'authentifier.\ Utilisez le compte `oidc-admin-user`. Une fois identifié, l'onglet affiche une page sur `localhost:8000` avec la confirmation de la connexion. Vous pouvez fermer cet onglet. ![Confirmation de connexion OIDC](keycloak-oidc-10.png) Retournez sur votre terminal, vous devriez voir la liste des Namespaces de votre cluster, puisque vous disposez des droits complets sur le cluster. #### Tester l'utilisateur `oidc-regular-user` Vous êtes déjà identifié avec le compte `oidc-admin-user`, donc pour continuer vous aurez besoin de vous déconnecter de Keycloak. Si vous ne connaissez pas l'URL de déconnexion, vous pouvez la reconstituer : `https://[KEYCLOAK-URL]/auth/realms/[KEYCLOAK-REALM]/account` puis cliquer sur le bouton `Sign Out` situé en haut à droite de la page. ![Déconnectez-vous de Keycloak](keycloak-oidc-11.png) Vous pouvez également supprimer le cache `kubectl` sur votre poste pour forcer une nouvelle authentification : ```bash [Terminal] rm -rf ~/.kube/cache # Alternative : supprimer uniquement le cache OIDC rm -rf ~/.kube/cache/oidc-login ``` En utilisant le même fichier `kubeconfig` qu'à l'étape précédente, demandez le listing des Namespaces du cluster Kubernetes : ```bash [Terminal] export KUBECONFIG=/path/to/your/custom/kubeconfig-oidc.yaml kubectl get namespaces ``` Votre navigateur ouvre à nouveau un onglet sur le serveur Keyclaok et vous invite à vous identifier.\ Utilisez le compte `oidc-regular-user`. Une fois identifié, l'onglet affiche une page sur `localhost:8000` avec la confirmation de connexion. Vous pouvez fermer cet onglet. Retournez sur votre terminal, vous devriez avoir une erreur comme qui suit : ```shell $ kubectl get namespaces Error from server (Forbidden): namespaces is forbidden: User "oidcuser:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" cannot list resource "namespaces" in API group "" at the cluster scope ``` Vous n'avez en effet pas le droit de visualiser la liste des Namespaces, puisque vous êtes limité au Namespace `default` par le `RoleBinding`. Listez les ressources déployées dans le Namespace `default` : ```bash [Terminal] kubectl -n default get all # Output: # NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE # service/kubernetes ClusterIP 10.43.0.1 443/TCP 385d ``` Vous voyez bien le service `kubernetes` qui est situé dans ce Namespace. Vous disposez donc bien du droit uniquement sur ce Namespace. Si vous tentez de visualiser d'autres objets ailleurs dans le cluster Kubernetes, vous aurez d'autres types d'erreurs : ```shell $ kubectl -n probes get all Error from server (Forbidden): pods is forbidden: User "oidcuser:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" cannot list resource "pods" in API group "" in the namespace "probes" Error from server (Forbidden): replicationcontrollers is forbidden: User "oidcuser:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" cannot list resource "replicationcontrollers" in API group "" in the namespace "probes" Error from server (Forbidden): services is forbidden: User "oidcuser:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" cannot list resource "services" in API group "" in the namespace "probes" Error from server (Forbidden): daemonsets.apps is forbidden: User "oidcuser:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" cannot list resource "daemonsets" in API group "apps" in the namespace "probes" Error from server (Forbidden): deployments.apps is forbidden: User "oidcuser:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" cannot list resource "deployments" in API group "apps" in the namespace "probes" Error from server (Forbidden): replicasets.apps is forbidden: User "oidcuser:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" cannot list resource "replicasets" in API group "apps" in the namespace "probes" Error from server (Forbidden): statefulsets.apps is forbidden: User "oidcuser:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" cannot list resource "statefulsets" in API group "apps" in the namespace "probes" Error from server (Forbidden): horizontalpodautoscalers.autoscaling is forbidden: User "oidcuser:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" cannot list resource "horizontalpodautoscalers" in API group "autoscaling" in the namespace "probes" Error from server (Forbidden): jobs.batch is forbidden: User "oidcuser:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" cannot list resource "jobs" in API group "batch" in the namespace "probes" Error from server (Forbidden): cronjobs.batch is forbidden: User "oidcuser:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" cannot list resource "cronjobs" in API group "batch" in the namespace "probes" ``` Félicitations, vous venez d'activer avec succès une authentification OIDC sur votre cluster Kubernetes à l'aide de Keycloak. ### Bonnes pratiques et recommandations #### Sécurité * **Durée de vie des tokens** : Configurez une durée courte (5-10 minutes) pour minimiser la fenêtre d'exploitation en cas de compromission * **Rotation du Client Secret** : Changez périodiquement le `Client Secret` Keycloak (tous les 90 jours minimum) * **HTTPS obligatoire** : Assurez-vous que Keycloak est accessible uniquement en HTTPS pour éviter l'interception des tokens * **Multi-Factor Authentication (MFA)** : Activez l'authentification multi-facteurs dans Keycloak pour renforcer la sécurité * **Audit des sessions** : Surveillez régulièrement les sessions actives dans Keycloak (`Sessions` tab) #### Gestion des groupes et RBAC * **Principe du moindre privilège** : Accordez uniquement les permissions strictement nécessaires * **Groupes granulaires** : Créez des groupes Keycloak par rôle fonctionnel (ex: `dev-readers`, `prod-deployers`, `monitoring-viewers`) * **Séparation des environnements** : Utilisez des groupes distincts pour dev, staging et production * **Documentation des rôles** : Documentez clairement les responsabilités et permissions de chaque groupe * **Revue périodique** : Auditez régulièrement les membres des groupes et les RBAC associés #### Opérations * **Fichier kubeconfig versionné** : Stockez le fichier kubeconfig générique dans un dépôt Git (sans secrets sensibles) * **Distribution sécurisée** : Partagez le kubeconfig via des canaux sécurisés (pas par email non chiffré) * **Monitoring** : Surveillez les échecs d'authentification dans les logs de l'API Server Kubernetes * **Sauvegarde Keycloak** : Sauvegardez régulièrement la configuration Keycloak (realm export) * **Plan de repli** : Conservez toujours un kubeconfig admin traditionnel en cas de défaillance de Keycloak #### Performance * **Mise en cache des tokens** : Le cache local (`~/.kube/cache/oidc-login`) évite les ré-authentifications répétées * **Connexions réseau** : Assurez-vous d'une latence faible entre les utilisateurs et Keycloak * **Resource limits** : Dimensionnez correctement votre instance Keycloak selon le nombre d'utilisateurs #### Cas d'usage avancés | Besoin | Solution | | ---------------------------------------------------------- | ---------------------------------------------------------------------------------- | | Authentification avec plusieurs IdP (Google, GitHub, etc.) | Configurez des Identity Providers dans Keycloak | | Groupes imbriqués | Activez `Full group path: On` dans le mapper si nécessaire | | Permissions temporaires | Utilisez des groupes Keycloak avec date d'expiration | | Audit détaillé | Activez les Event Listeners dans Keycloak pour tracer toutes les authentifications | | Automatisation de la gestion des groupes | Utilisez l'API Keycloak pour synchroniser depuis LDAP/AD | :::warning **Limitations à connaître :** * Un seul provider OIDC par cluster Kubernetes (limitation technique de l'API Server) * Les tokens OIDC ne supportent pas l'impersonation (`kubectl --as`) * La révocation immédiate nécessite de vider le cache local utilisateur * Les `ServiceAccounts` ne peuvent pas utiliser OIDC (utilisez des tokens Kubernetes classiques) ::: ### FAQ #### Est-il possible de révoquer l'accès à un utilisateur ? Oui, l'un des principaux avantages de l'authentification OIDC centralisée est la possibilité de révoquer instantanément les accès. Plusieurs méthodes existent : **Méthode 1 : Désactivation du compte utilisateur** * Rendez-vous dans `Manage > Users` dans Keycloak * Sélectionnez l'utilisateur concerné * Basculez `User Enabled` sur `Off` * L'utilisateur ne pourra plus s'authentifier dès la prochaine tentative **Méthode 2 : Retrait des groupes** * Accédez à l'onglet `Groups` de l'utilisateur * Retirez-le de tous les groupes ayant des droits Kubernetes * Les permissions RBAC ne s'appliqueront plus **Méthode 3 : Déconnexion forcée des sessions actives** * Allez dans `Manage > Users > [Utilisateur] > Sessions` * Cliquez sur `Logout` pour invalider toutes les sessions actives * Les tokens JWT actuels seront révoqués immédiatement :::info **Délai de prise en compte :** La durée de vie du token JWT définit le délai maximum pendant lequel un utilisateur révoqué peut encore effectuer des actions. C'est pourquoi il est recommandé de configurer une durée courte (5-10 minutes). Pour une révocation immédiate, demandez à l'utilisateur de vider son cache local : ```bash [Terminal] rm -rf ~/.kube/cache/oidc-login ``` ::: #### Comment régler la durée de vie du token JWT ? **Procédure :** 1. Connectez-vous à la console d'administration Keycloak 2. Accédez à `Configure > Clients` 3. Sélectionnez le client OIDC créé pour Kubernetes (ex: `kube-oidc`) 4. Descendez dans la section `Advanced Settings` 5. Modifiez le paramètre `Access Token Lifespan` ![Paramétrage de la durée de vie du token JWT](keycloak-oidc-12.png) **Recommandations :** | Environnement | Durée recommandée | Raison | | ------------- | ----------------- | ------------------------------------ | | Production | 5-10 minutes | Sécurité maximale, révocation rapide | | Staging | 15-30 minutes | Équilibre sécurité/confort | | Développement | 1 heure | Confort d'utilisation | **Autres paramètres importants :** * `SSO Session Idle` : Durée avant expiration d'une session inactive (recommandé : 30 minutes) * `SSO Session Max` : Durée maximale d'une session (recommandé : 8-10 heures) * `Client Session Idle` : Durée d'inactivité avant reconnexion (recommandé : 15 minutes) :::warning Une durée trop courte (\< 5 minutes) peut être gênante car l'utilisateur devra se ré-authentifier fréquemment. Une durée trop longue (> 1 heure) augmente les risques de sécurité en cas de compromission. ::: #### Est-il possible d'avoir deux sources de connexion OIDC pour un cluster Kubernetes ? **Non**, Kubernetes ne supporte qu'**un seul provider OIDC par cluster**. C'est une limitation technique de l'API Server Kubernetes. **Solutions de contournement :** 1. **Utiliser les Identity Providers de Keycloak**\ Keycloak peut fédérer plusieurs sources d'authentification externes (Google, GitHub, Azure AD, LDAP, etc.) * Rendez-vous dans `Configure > Identity Providers` dans Keycloak * Ajoutez les providers souhaités (GitHub, Google, StackOverflow, Microsoft, etc.) * Les utilisateurs choisiront leur méthode d'authentification lors du login * Tous passeront par le même endpoint OIDC Keycloak côté Kubernetes 2. **Synchronisation LDAP/Active Directory** * Configurez Keycloak pour synchroniser les utilisateurs depuis votre annuaire d'entreprise * Les utilisateurs s'authentifient avec leurs credentials habituels * Les groupes peuvent être synchronisés automatiquement 3. **Fédération SAML** * Si votre organisation utilise SAML, Keycloak peut agir comme pont SAML ↔ OIDC :::info **Avantage de Keycloak comme IdP centralisé :** En utilisant Keycloak comme point d'entrée unique, vous bénéficiez : * D'une gestion centralisée des accès multi-sources * De politiques d'authentification unifiées (MFA, durée de session, etc.) * D'un audit cohérent de toutes les authentifications * D'une flexibilité pour ajouter/retirer des providers sans reconfigurer Kubernetes ::: #### Est-ce que l'OIDC empêche de continuer à utiliser des `kubeconfig` originels ? **Non**, l'OIDC est une **méthode d'authentification complémentaire**. Kubernetes supporte simultanément plusieurs mécanismes : | Méthode d'authentification | Cas d'usage | Compatibilité OIDC | | ---------------------------------------- | ------------------------------------ | ------------------------------ | | Certificats X.509 (kubeconfig classique) | Accès admin, outils d'infrastructure | Cohabite avec OIDC | | `ServiceAccounts` (tokens) | Automatisation, CI/CD, Pods | Indépendant de OIDC | | OIDC (via Keycloak) | Utilisateurs humains, développeurs | Peut être utilisé en parallèle | | Bearer tokens statiques | Déprécié, ne pas utiliser | - | **Exemple d'architecture hybride :** * **Utilisateurs interactifs** → OIDC (via Keycloak) * **Pipelines CI/CD** → `ServiceAccounts` avec RBAC dédiés * **Break-glass admin** → Certificat client classique (stocké en coffre-fort) * **Outils d'exploitation** → `ServiceAccounts` avec permissions restreintes :::tip **Bonne pratique :** Conservez toujours au moins un `kubeconfig` avec certificat admin dans un coffre-fort sécurisé (Vault, 1Password, etc.) comme solution de secours en cas de défaillance de Keycloak. ::: #### Comment procéder pour authentifier un automate, comme le CI/CD ? **Les automates ne doivent PAS utiliser OIDC**. OIDC est conçu pour l'authentification interactive (navigateur). Pour les automatisations, utilisez des **ServiceAccounts Kubernetes**. **Procédure recommandée :** :::steps ##### Étape 1. **Créer un `ServiceAccount` dédié** ```yaml showLineNumbers [sa-cicd.yaml] apiVersion: v1 kind: ServiceAccount metadata: name: cicd-deployer namespace: default labels: app.kubernetes.io/component: automation app.kubernetes.io/managed-by: kubectl ``` ##### Étape 2. **Créer un `Role`/`ClusterRole` adapté** ```yaml showLineNumbers [role-cicd.yaml] apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: cicd-deployer-role namespace: default rules: - apiGroups: ["apps"] resources: ["deployments", "replicasets"] verbs: ["get", "list", "create", "update", "patch"] - apiGroups: [""] resources: ["services", "configmaps", "secrets"] verbs: ["get", "list", "create", "update", "patch"] ``` ##### Étape 3. **Lier le `ServiceAccount` au `Role`** ```yaml showLineNumbers [rolebinding-cicd.yaml] apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: cicd-deployer-binding namespace: default roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: cicd-deployer-role subjects: - kind: ServiceAccount name: cicd-deployer namespace: default ``` ##### Étape 4. **Générer le `kubeconfig` pour le `ServiceAccount`** ```bash [Terminal] # Récupérer le token du ServiceAccount (Kubernetes 1.24+) kubectl -n default create token cicd-deployer --duration=87600h > sa-token.txt # Construire le kubeconfig kubectl config set-cluster my-cluster \ --server=https://[CLUSTER-API-ENDPOINT] \ --certificate-authority=/path/to/ca.crt \ --embed-certs=true \ --kubeconfig=kubeconfig-cicd kubectl config set-credentials cicd-deployer \ --token=$(cat sa-token.txt) \ --kubeconfig=kubeconfig-cicd kubectl config set-context cicd-context \ --cluster=my-cluster \ --user=cicd-deployer \ --namespace=default \ --kubeconfig=kubeconfig-cicd kubectl config use-context cicd-context --kubeconfig=kubeconfig-cicd ``` ##### Étape 5. **Utiliser le kubeconfig dans votre pipeline CI/CD** ```yaml [.gitlab-ci.yml] deploy: stage: deploy script: - export KUBECONFIG=./kubeconfig-cicd - kubectl apply -f deployment.yaml only: - main ``` ::: :::warning **Sécurité des `ServiceAccounts` :** * Stockez les tokens de `ServiceAccount` dans un gestionnaire de secrets (GitLab Variables, GitHub Secrets, Vault) * N'accordez que les permissions strictement nécessaires (principe du moindre privilège) * Créez des `ServiceAccounts` distincts par environnement (dev, staging, prod) * Auditez régulièrement les permissions et l'utilisation des `ServiceAccounts` * Utilisez des tokens avec durée de vie limitée (paramètre `--duration` de `kubectl create token`) ::: Pour plus de détails sur la gestion des RBAC et `ServiceAccounts`, consultez le guide [RBAC](/guides/rbac). #### Comment déboguer les problèmes d'authentification OIDC ? **Symptômes courants et solutions :** | Problème | Cause probable | Solution | | ------------------------------------------------------------- | ----------------------------- | ----------------------------------------------------- | | "Unable to connect to the server: oauth2: cannot fetch token" | Client Secret incorrect | Vérifiez le secret dans Keycloak (onglet Credentials) | | "Error from server (Forbidden): ..." | RBAC mal configuré | Vérifiez les `RoleBinding`/`ClusterRoleBinding` | | Le navigateur ne s'ouvre pas | Plugin kubelogin non installé | Installez `kubectl-oidc_login` | | "invalid\_redirect\_uri" | Redirect URI non configuré | Ajoutez `http://localhost:8000` dans Keycloak | | Authentification en boucle | Token expiré dans le cache | Supprimez `~/.kube/cache/oidc-login` | | "oidc: issuer did not match" | Issuer URL incorrecte | Vérifiez `oidc-issuer-url` dans le kubeconfig | **Commandes de débogage :** ```bash [Terminal] # Afficher le token JWT décodé kubectl oidc-login get-token \ --oidc-issuer-url=https://[KEYCLOAK-URL]/auth/realms/[REALM] \ --oidc-client-id=[CLIENT-ID] \ --oidc-client-secret=[SECRET] \ | jq -R 'split(".") | .[1] | @base64d | fromjson' # Tester l'authentification sans cache rm -rf ~/.kube/cache/oidc-login kubectl get nodes -v=8 # Mode verbose pour voir les détails # Vérifier les logs de l'API Server (côté SdV) # Contactez l'équipe SdV pour analyser les logs si nécessaire ``` **Vérification de la configuration Keycloak :** ```bash [Terminal] # Tester l'endpoint OIDC discovery curl https://[KEYCLOAK-URL]/auth/realms/[REALM]/.well-known/openid-configuration | jq # Vérifier que l'issuer correspond exactement # Comparer avec la valeur dans votre kubeconfig ``` :::tip Activez le mode verbeux de kubectl (`-v=8`) pour voir les détails des échanges OIDC et identifier précisément où se situe le problème. ::: ### Commandes kubectl utiles Une fois l'authentification OIDC configurée, voici les commandes essentielles pour l'exploitation quotidienne : ```bash [Terminal] # Vérifier l'identité actuelle (utilisateur OIDC) kubectl auth whoami # Lister les permissions de l'utilisateur courant kubectl auth can-i --list # Tester une permission spécifique kubectl auth can-i create deployments -n default kubectl auth can-i get pods --all-namespaces # Afficher le contexte actif et le kubeconfig utilisé kubectl config current-context kubectl config view # Forcer une ré-authentification (vider le cache) rm -rf ~/.kube/cache/oidc-login kubectl get nodes # Déclenchera une nouvelle authentification # Consulter les informations du token JWT (debug) kubectl oidc-login get-token \ --oidc-issuer-url=https://[KEYCLOAK-URL]/auth/realms/[REALM] \ --oidc-client-id=[CLIENT-ID] \ --oidc-client-secret=[SECRET] # Vérifier les RoleBindings appliqués à un groupe OIDC kubectl get rolebinding,clusterrolebinding -A -o json \ | jq '.items[] | select(.subjects[]?.name | contains("oidcgroup:"))' # Lister les ClusterRoles disponibles kubectl get clusterrole | grep -i admin # Voir les détails d'un ClusterRoleBinding kubectl describe clusterrolebinding oidc-cluster-admins ``` ### Récapitulatif Vous avez maintenant configuré une authentification OIDC complète pour votre cluster Kubernetes. Cette mise en place permet : * **Gestion centralisée** : Un seul point de gestion des identités (Keycloak) * **Sécurité renforcée** : Révocation instantanée, MFA, audit centralisé * **Expérience utilisateur** : Un fichier kubeconfig partageable, authentification transparente * **Conformité** : Traçabilité précise des actions par utilisateur * **Scalabilité** : Ajout/suppression d'utilisateurs sans redistribuer de secrets **Prochaines étapes recommandées :** 1. Configurez des groupes Keycloak selon vos équipes et projets 2. Créez des `ClusterRoles` granulaires adaptés à vos besoins 3. Activez la MFA dans Keycloak pour renforcer la sécurité 4. Mettez en place un processus d'onboarding/offboarding documenté 5. Configurez la surveillance des événements d'authentification (Keycloak Events) 6. Planifiez la rotation du Client Secret Keycloak 7. Documentez votre politique RBAC pour les nouveaux arrivants Pour aller plus loin : * Consultez le guide [RBAC](/guides/rbac) pour des exemples de politiques d'autorisation avancées * Explorez la [documentation Keycloak](https://www.keycloak.org/documentation) pour les fonctionnalités avancées (Identity Brokering, User Federation, Custom Authenticators) * Lisez la [documentation Kubernetes sur l'authentification](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens) pour comprendre les options de configuration de l'API Server ## Gestion des droits avec RBAC RBAC signifie **Role-Based Access Control**. Il s'agit d'un mécanisme de contrôle d'accès utilisé dans Kubernetes pour octroyer des autorisations granulaires à des utilisateurs, groupes ou applications (ServiceAccounts) au sein d'un cluster. :::info RBAC est le modèle de sécurité recommandé par Kubernetes pour gérer les autorisations. Il remplace les anciens mécanismes ABAC (Attribute-Based Access Control) désormais dépréciés. ::: Les RBAC ont été introduits à partir de la version 1.8 de Kubernetes et utilisent l'API Group `rbac.authorization.k8s.io` pour la création des politiques d'accès. Ce mécanisme est **activé par défaut** sur tous les clusters Kubernetes modernes. L'implémentation de RBAC se fait au travers de quatre types d'entités Kubernetes : | Entité | Scope | Rôle | | -------------------- | --------- | -------------------------------------------------------------- | | `Role` | Namespace | Définit des autorisations dans un namespace spécifique | | `ClusterRole` | Cluster | Définit des autorisations au niveau cluster ou réutilisables | | `RoleBinding` | Namespace | Attribue un Role ou ClusterRole à des sujets dans un namespace | | `ClusterRoleBinding` | Cluster | Attribue un ClusterRole à des sujets au niveau cluster | :::warning RBAC fonctionne selon un principe **additif uniquement** : il n'existe pas de règles de refus explicites. Toutes les autorisations sont accordées par addition de règles. ::: Le schéma ci-après montre trois façons d'aborder l'octroi d'accès : 1. **Utilisation d'un `Role` en association avec un `RoleBinding`**\ Application d'autorisations restreintes au `Namespace` car les entités RBAC utilisées sont namespacées ![Schéma RBAC 1](/guides/rbac/rbacs-namespaced.png) 2. **Utilisation d'un `ClusterRole` en association avec un `RoleBinding`**\ Application d'autorisations globales (`ClusterRole`) limitées à un `Namespace` grâce au `RoleBinding` namespacé ![Schéma RBAC 2](/guides/rbac/rbacs-clusterwide-namespaced.png) 3. **Utilisation d'un `ClusterRole` en association avec un `ClusterRoleBinding`**\ Application d'autorisations globales aux ressources du Cluster ![Schéma RBAC 2](/guides/rbac/rbacs-clusterwide.png) ### `Roles` et `ClusterRoles` Avant d'attribuer des autorisations aux utilisateurs avec RBAC, vous devez tout d'abord définir ces autorisations en tant que rôle. Les `Roles` et `ClusterRoles` accordent des autorisations. Le concept d'autorisation refusée n'existe pas. Les `Role` sont utilisés pour accorder des autorisations dans un espace de noms. Si vous devez accorder des autoriations sur l'ensemble du cluster ou sur des ressources de cluster en dehors d'un espace de noms donné, vous pouvez utiliser les `ClusterRoles` à la place. #### Role Les `Roles` définissent ce que vous pouvez réaliser avec quelle ressource, au sein d'un `Namespace` particulier, car l'entité `Role` est namespacée. Ils sont composés de règles (`rules`) définissant chacune : * **apiGroups** : Liste des groupes d'API concernés (obtenez la liste complète avec `kubectl api-resources`) * `""` pour le groupe Core (`Pods`, `Services`, `ConfigMaps`, `Secrets`...) * `apps` pour les `Deployments`, `StatefulSets`, `DaemonSets` * `rbac.authorization.k8s.io` pour les RBAC * `batch` pour les `Jobs` et `CronJobs` * `networking.k8s.io` pour les `Ingress` et `NetworkPolicies` * **resources** : Types d'objets Kubernetes concernés (`Pods`, `Services`, `Deployments`, `Secrets`...) * **verbs** : Actions autorisées sur ces ressources ##### Verbes disponibles | Verbe | Description | Cas d'usage | | ------------------ | ------------------------------------------------- | ------------------------------------- | | `get` | Lire un objet spécifique | Consulter un `Pod` particulier | | `list` | Lister les objets d'un type | Voir tous les `Pods` d'un `Namespace` | | `watch` | Surveiller les changements | Suivre les événements en temps réel | | `create` | Créer un nouvel objet | Déployer une application | | `update` | Modifier un objet existant (remplacement complet) | Mettre à jour un `Deployment` | | `patch` | Modifier partiellement un objet | Changer une annotation | | `delete` | Supprimer un objet | Retirer un `Pod` | | `deletecollection` | Supprimer plusieurs objets | Nettoyer tous les `Pods` d'un label | | `*` | Tous les verbes ci-dessus | Accès administrateur complet | :::tip Pour lister tous les API Groups disponibles sur votre cluster : ```bash [Terminal] kubectl api-resources -o wide ``` ::: Voici un exemple de `Role` mis en place pour le déploiement de l'opérateur [Kopf](https://github.com/nolar/kopf) : ```yaml showLineNumbers [role.yaml] apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: namespace: [NAMESPACE] name: [NAME] rules: - apiGroups: [zalando.org] resources: [kopfpeerings] verbs: [list, watch, patch, get] - apiGroups: [events.k8s.io] resources: [events] verbs: [create] - apiGroups: [batch] resources: [jobs] verbs: [create] - apiGroups: [""] resources: [services, events, namespaces] verbs: [get, list, watch, create, update, patch, delete] - apiGroups: [""] resources: [pods, persistentvolumeclaims] verbs: [create] ``` Ce `Role` donnera donc droit aux ressources : * `KopfPeerings` qui appartient à l'ApiGroup `zalando.org` avec les verbes `list`, `watch`, `patch` et `get` * `Events` qui appartient à l'ApiGroup `events.k8s.io` avec le verbe `create` * `Services`, `Events` et `Namespaces` de l'ApiGroup Core (`""`) avec les verbes `get`, `list`, `watch`, `create`, `update`, `patch` et `delete` * `Jobs` dans l'API Group `batch` avec le verbe `create` * `Pods` et `PersistentVolumeClaims` dans l'ApiGroup Core (`""`) avec le verbe `create` :::note Vous pouvez gérer finement les règles d'accès en répétant les API Groups avec des ressources et verbes différents. Cette approche permet de suivre le principe du **moindre privilège**. ::: ##### `Roles` par défaut Kubernetes est livré avec un certain nombre de `ClusterRoles` prédéfinis que vous pouvez réutiliser via des `RoleBindings` : | Role | Description | | --------------- | -------------------------------------------------------------- | | `view` | Accès en lecture seule (`Pods`, `Services`, `ConfigMaps`...) | | `edit` | Accès en lecture/écriture, mais pas aux `Roles`/`RoleBindings` | | `admin` | Accès administrateur sur un `Namespace` (gestion RBAC incluse) | | `cluster-admin` | Accès super-administrateur sur tout le cluster | Consultez [la liste complète dans la documentation officielle](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#default-roles-and-role-bindings). #### `ClusterRole` Les `ClusterRoles` se définissent comme les `Roles` mais s'appliquent à l'ensemble du cluster Kubernetes. Ils peuvent être utilisés de deux façons : 1. **Avec un `ClusterRoleBinding`** : Autorisations appliquées sur tout le cluster 2. **Avec un `RoleBinding`** : Autorisations applicables globalement mais restreintes à un `Namespace` spécifique :::tip Utilisez les `ClusterRoles` pour créer des modèles d'autorisations réutilisables dans plusieurs namespaces. Cela évite de dupliquer les définitions de `Roles`. ::: ##### Cas d'usage des `ClusterRoles` * **Ressources non-namespacées** : `Nodes`, `PersistentVolumes`, `Namespaces`, `ClusterRoles`... * **Modèles réutilisables** : Définir une politique d'accès commune à appliquer dans plusieurs `Namespaces` * **Accès cluster-wide** : Opérateurs, observabilité, controllers nécessitant une vue globale ### `RoleBindings` et `ClusterRoleBindings` Une fois les rôles définis pour accorder les autorisations aux ressources, vous affectez ces autorisations RBAC au moyen d'un `RoleBinding` ou `ClusterRoleBinding`. #### `RoleBinding` Les `RoleBinding` associent un `Role` ou un `ClusterRole` à un ou plusieurs **sujets** (subjects) dans un namespace spécifique. Cette approche permet de séparer logiquement un cluster avec des utilisateurs uniquement capables d'accéder aux ressources de leur `Namespace` attribué. ##### Sujets (`Subjects`) Un `RoleBinding` peut lier des autorisations à trois types de sujets : | Type | Description | Exemple | | ---------------- | ------------------------------ | ---------------------------------- | | `User` | Utilisateur humain authentifié | `user@example.com` | | `Group` | Groupe d'utilisateurs | `system:authenticated`, `dev-team` | | `ServiceAccount` | Identité pour les Pods | `my-service-account` | :::tip Privilégiez l'utilisation de **`ServiceAccounts`** pour les applications et **`Groups`** pour les utilisateurs humains afin de simplifier la gestion des accès. ::: Voici un exemple de `RoleBinding` utilisé dans la mise en place de l'opérateur Kopf : ```yaml showLineNumbers [rolebinding.yaml] apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: kopf-operator-binding namespace: my-namespace roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: kopf-operator-role subjects: - kind: ServiceAccount name: kopf-operator-sa namespace: my-namespace ``` :::note Le champ `roleRef` est **immuable** après création. Pour changer de `Role`, vous devez supprimer et recréer le `RoleBinding`. ::: #### `ClusterRoleBinding` Un `ClusterRoleBinding` fonctionne de la même façon qu'un `RoleBinding` pour lier des rôles aux sujets, mais il s'applique à **toutes les ressources du cluster**, sans restriction de namespace. :::warning Utilisez les `ClusterRoleBindings` avec précaution : ils accordent des autorisations sur **l'ensemble du cluster**. Préférez les `RoleBindings` pour limiter la portée des accès. ::: ##### Cas d'usage * **Administrateurs cluster** : Accès complet pour la gestion du cluster * **Opérateurs Kubernetes** : Controllers nécessitant une vue globale (cert-manager, ingress-controller...) * **Monitoring** : Systèmes d'observabilité (Prometheus, Grafana...) * **Accès multi-namespaces** : Utilisateurs devant gérer plusieurs namespaces Voici un exemple de `ClusterRoleBinding` : ```yaml showLineNumbers [clusterrolebinding.yaml] apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: kopf-operator-cluster-binding roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: kopf-operator-cluster-role subjects: - kind: ServiceAccount name: kopf-operator-sa namespace: operators ``` :::note Même dans un `ClusterRoleBinding`, les `ServiceAccounts` doivent spécifier leur namespace d'origine. ::: ### `ServiceAccount` Les `ServiceAccounts` sont des identités utilisées par les `Pods` pour s'authentifier auprès de l'API Kubernetes. Ils permettent aux applications de communiquer avec le cluster de manière sécurisée. #### Comportement par défaut À la création d'un `Namespace`, Kubernetes crée automatiquement un `ServiceAccount` nommé `default`. Ce compte dispose de permissions très limitées : :::warning Le ServiceAccount `default` est associé au groupe `system:serviceaccounts:` qui n'offre **aucun droit** par défaut, même en lecture. Pour toute application nécessitant un accès à l'API Kubernetes, créez un `ServiceAccount` dédié avec les autorisations appropriées. ::: #### Utilisation dans un Pod Vous pouvez spécifier un `ServiceAccount` personnalisé dans vos `Deployments` ou `Pods` avec le champ `serviceAccountName` : ```yaml showLineNumbers [deployment.yaml] apiVersion: apps/v1 kind: Deployment metadata: name: my-app namespace: my-namespace spec: replicas: 1 selector: matchLabels: app: my-app template: metadata: labels: app: my-app spec: serviceAccountName: my-service-account containers: - name: app image: my-app:latest ``` :::note Si `serviceAccountName` n'est pas spécifié, le Pod utilise automatiquement le `ServiceAccount` `default` du namespace. ::: #### Token et authentification Chaque `ServiceAccount` est associé à un `Secret` contenant un token JWT permettant l'authentification auprès de l'API Kubernetes : ```bash [Terminal] # Lister les secrets associés à un ServiceAccount kubectl get secrets -n my-namespace # Extraire le token kubectl get secret -n my-namespace -o jsonpath='{.data.token}' | base64 --decode ``` :::warning À partir de Kubernetes 1.24, les tokens ne sont plus automatiquement créés. Utilisez la commande suivante pour créer un token : ```bash [Terminal] kubectl create token my-service-account -n my-namespace ``` ::: ### `Groups` Les `Groups` sont des ensembles d'utilisateurs ou de `ServiceAccounts` partageant des autorisations communes. Kubernetes dispose de plusieurs groupes système prédéfinis, identifiables par le préfixe `system:`. #### Groupes système courants | Groupe | Description | | ------------------------------------ | --------------------------------------------- | | `system:authenticated` | Tous les utilisateurs authentifiés | | `system:unauthenticated` | Utilisateurs non authentifiés | | `system:serviceaccounts` | Tous les `ServiceAccounts` du cluster | | `system:serviceaccounts:` | `ServiceAccounts` d'un `Namespace` spécifique | | `system:masters` | Super-administrateurs cluster | #### Lister les `ClusterRoles` système Pour visualiser tous les `ClusterRoles` système disponibles : ```bash [Terminal] kubectl get clusterroles | grep ^system: ``` Exemple de sortie : ``` system:aggregate-to-admin 363d system:aggregate-to-edit 363d system:aggregate-to-view 363d system:auth-delegator 363d system:basic-user 363d system:certificates.k8s.io:certificatesigningrequests:nodeclient 363d system:node 363d ``` :::tip Utilisez les groupes `system:serviceaccounts:` dans vos `RoleBindings` pour accorder rapidement des autorisations à tous les `ServiceAccounts` d'un namespace. ::: ### Cas pratiques Ces exemples illustrent des scénarios fréquents de création de `ServiceAccounts` avec des autorisations limitées. \:::warning Sécurité Appliquez toujours le principe du **moindre privilège** : n'accordez que les autorisations strictement nécessaires. Évitez d'utiliser `verbs: ["*"]` en production. \::: #### Cas 1 : `ServiceAccount` limité à un `Namespace` unique **Contexte** : Vous souhaitez créer un accès pour une application ou un utilisateur devant gérer **un seul `Namespace`**. **Avantages** : * Isolation stricte par `Namespace` * Sécurité renforcée (accès impossible aux autres `Namespaces`) * Idéal pour les environnements multi-tenants **Prérequis** : Accès administrateur au cluster Kubernetes ::::steps ##### Étape 1 : Définir les variables Définissons nos variables pour le contexte : ```bash [Terminal] # Le Namespace dans lequel vérouiller l'utilisateur NAMESPACE="my-namespace" # Le nom du ServiceAccount à créer SERVICE_ACCOUNT="my-service-account" # Le nom du Role à créer ROLE="${SERVICE_ACCOUNT}-role" # Le nom du RoleBinding à créer ROLEBINDING="${ROLE}-binding" # Le nom du Context pour le fichier kubeconfig CLUSTER_NAME=$(kubectl config view --minify -o jsonpath='{.clusters[0].name}') CONTEXT="${SERVICE_ACCOUNT}-${NAMESPACE}-${CLUSTER_NAME}" ``` ##### Étape 2 : Créer le `Namespace` Si le `Namespace` n'existe pas encore : ```bash [Terminal] kubectl create namespace ${NAMESPACE} ``` ##### Étape 3 : Créer le `ServiceAccount` Si le `ServiceAccount` n'existe pas encore : ```bash [Terminal] kubectl -n ${NAMESPACE} create serviceaccount ${SERVICE_ACCOUNT} ``` ##### Étape 4 : Créer le `Role` Créons le `Role` donnant tous les droits sur le `Namespace` : :::warning Cet exemple utilise `verbs: ["*"]` pour simplifier. En production, spécifiez uniquement les verbes nécessaires (`get`, `list`, `watch`, `create`, `update`, `delete`). ::: ```bash [Terminal] cat < payload.yml apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: ${ROLE} namespace: ${NAMESPACE} rules: - apiGroups: ["*"] resources: ["*"] verbs: ["*"] EOF kubectl -n ${NAMESPACE} apply -f payload.yml ``` ##### Étape 5 : Créer le `RoleBinding` Associons le `ServiceAccount` au `Role` : ```bash [Terminal] cat < payload.yml apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: ${ROLEBINDING} namespace: ${NAMESPACE} subjects: - kind: ServiceAccount name: ${SERVICE_ACCOUNT} namespace: ${NAMESPACE} roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: ${ROLE} EOF kubectl -n ${NAMESPACE} apply -f payload.yml ``` A ce stade, le RBAC est paramétré, il nous faut désormais extraire le kubeconfig pour l'utilisateur. Commençons par récupérer les informations de base du Cluster : le certificat SSL, le nom du Cluster et l'URL de l'API. ```bash [Terminal] # Le Certificat du Cluster CLUSTER_CA_CRT=$(kubectl config view --minify --flatten -o=jsonpath='{.clusters[*].cluster.certificate-authority-data}' 2> /dev/null) # Le Nom du Cluster CLUSTER_NAME=$(kubectl config view --minify --flatten -o=jsonpath='{.clusters[*].name}') # Le Endpoint API du Cluster CLUSTER_ENDPOINT=$(kubectl config view --minify --flatten -o=jsonpath='{.clusters[*].cluster.server}') ``` ##### Étape 6 : Extraire le token du `ServiceAccount` Le token est stocké dans le `Secret` associé au `ServiceAccount` : :::warning Kubernetes 1.24+ À partir de Kubernetes 1.24, les tokens ne sont plus automatiquement créés. Utilisez plutôt : ```bash [Terminal] SERVICE_ACCOUNT_TOKEN=$(kubectl create token ${SERVICE_ACCOUNT} -n ${NAMESPACE} --duration=8760h) ``` ::: Pour les versions antérieures : ```bash [Terminal] # Le Secret associé au ServiceAccount SERVICE_ACCOUNT_SECRET=$(kubectl -n ${NAMESPACE} get secrets -o jsonpath="{.items[?(@.metadata.annotations['kubernetes\.io/service-account\.name']=='${SERVICE_ACCOUNT}')].metadata.name}" 2> /dev/null) # Le Token caché dans le Secret SERVICE_ACCOUNT_TOKEN=$(kubectl -n ${NAMESPACE} get secret ${SERVICE_ACCOUNT_SECRET} -o jsonpath="{.data['token']}" 2> /dev/null | base64 --decode) ``` ##### Étape 7 : Générer le fichier kubeconfig Reconstituons toutes les informations dans un nouveau fichier `kubeconfig` : ```bash [Terminal] cat < kubeconfig apiVersion: v1 kind: Config users: - name: ${SERVICE_ACCOUNT} user: token: ${SERVICE_ACCOUNT_TOKEN} clusters: - name: ${CLUSTER_NAME} cluster: certificate-authority-data: ${CLUSTER_CA_CRT} server: ${CLUSTER_ENDPOINT} contexts: - name: ${CONTEXT} context: cluster: ${CLUSTER_NAME} namespace: ${NAMESPACE} user: ${SERVICE_ACCOUNT} current-context: ${CONTEXT} EOF ``` ##### Étape 8 : Tester l'accès Vous pouvez tester l'accès avec le kubeconfig généré : ```bash [Terminal] # Commande en erreur : vous n'avez pas accès au NS kube-system kubectl --kubeconfig=./kubeconfig -n kube-system get all # Commande Ok : accès au namespace autorisé kubectl --kubeconfig=./kubeconfig -n ${NAMESPACE} get all ``` :::tip Distribuez ce fichier `kubeconfig` de manière sécurisée (chiffré, vault, secret manager). Ne le commitez jamais dans Git ! ::: :::: #### Cas 2 : `ServiceAccount` limité à plusieurs namespaces **Contexte** : Vous souhaitez créer un accès pour une application ou un utilisateur devant gérer **plusieurs namespaces** (par exemple : dev, staging). **Avantages** : * Réutilisation d'un seul `ClusterRole` pour plusieurs namespaces * Gestion centralisée des permissions * Évite la duplication de `Roles` **Prérequis** : Accès administrateur au cluster Kubernetes ::::steps ##### Étape 1 : Définir les variables Définissons nos variables pour le contexte : ```bash [Terminal] # Les Namespaces dans lesquels vérouiller l'utilisateur NAMESPACE1="my-namespace" NAMESPACE2="my-namespace-bis" # Le nom du ServiceAccount à créer SERVICE_ACCOUNT="my-service-account" # Le nom du ClusterRole à créer CLUSTERROLE="${SERVICE_ACCOUNT}-role" # Le nom du RoleBinding à créer ROLEBINDING="${CLUSTERROLE}-binding" # Le nom du Context pour le fichier kubeconfig CLUSTER_NAME=$(kubectl config view --minify -o jsonpath='{.clusters[0].name}') CONTEXT="${SERVICE_ACCOUNT}-multi-ns-${CLUSTER_NAME}" ``` ##### Étape 2 : Créer les `Namespaces` Si les `Namespaces` n'existent pas encore : ```bash [Terminal] kubectl create namespace ${NAMESPACE1} kubectl create namespace ${NAMESPACE2} ``` ##### Étape 3 : Créer le `ServiceAccount` Créez le `ServiceAccount` dans le namespace `default` : ```bash [Terminal] kubectl -n default create serviceaccount ${SERVICE_ACCOUNT} ``` :::note Le `ServiceAccount` est créé dans `default` car il sera référencé par plusieurs `RoleBindings` dans différents `Namespaces`. ::: ##### Étape 4 : Créer le `ClusterRole` Créons le `ClusterRole` donnant tous les droits : :::warning Cet exemple utilise `verbs: ["*"]` pour simplifier. En production, spécifiez uniquement les verbes nécessaires. ::: ```bash [Terminal] cat < payload.yml apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: ${CLUSTERROLE} rules: - apiGroups: ["*"] resources: ["*"] verbs: ["*"] EOF kubectl apply -f payload.yml ``` :::note Un ClusterRole n'a pas de namespace, ne spécifiez pas `namespace: default` dans les metadata. ::: ##### Étape 5 : Créer les `RoleBindings` Associons le `ServiceAccount` au `ClusterRole` via un `RoleBinding` par `Namespace` : ```bash [Terminal] cat < payload.yml apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: ${ROLEBINDING}-${NAMESPACE1} namespace: ${NAMESPACE1} subjects: - kind: ServiceAccount name: ${SERVICE_ACCOUNT} namespace: default roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: ${CLUSTERROLE} --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: ${ROLEBINDING}-${NAMESPACE2} namespace: ${NAMESPACE2} subjects: - kind: ServiceAccount name: ${SERVICE_ACCOUNT} namespace: default roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: ${CLUSTERROLE} EOF kubectl apply -f payload.yml ``` ##### Étape 6 : Extraire les informations du cluster À ce stade, le RBAC est paramétré. Extrayons les informations nécessaires pour générer le kubeconfig : ```bash [Terminal] # Le Certificat du Cluster CLUSTER_CA_CRT=$(kubectl config view --minify --flatten -o=jsonpath='{.clusters[*].cluster.certificate-authority-data}' 2> /dev/null) # Le Nom du Cluster CLUSTER_NAME=$(kubectl config view --minify --flatten -o=jsonpath='{.clusters[*].name}') # Le Endpoint API du Cluster CLUSTER_ENDPOINT=$(kubectl config view --minify --flatten -o=jsonpath='{.clusters[*].cluster.server}') ``` ##### Étape 7 : Extraire le token du `ServiceAccount` Le token est stocké dans le `Secret` associé au `ServiceAccount` : :::warning Kubernetes 1.24+ À partir de Kubernetes 1.24, utilisez plutôt : ```bash [Terminal] SERVICE_ACCOUNT_TOKEN=$(kubectl create token ${SERVICE_ACCOUNT} -n default --duration=8760h) ``` ::: Pour les versions antérieures : ```bash [Terminal] # Le Secret associé au ServiceAccount SERVICE_ACCOUNT_SECRET=$(kubectl -n default get secrets -o jsonpath="{.items[?(@.metadata.annotations['kubernetes\.io/service-account\.name']=='${SERVICE_ACCOUNT}')].metadata.name}" 2> /dev/null) # Le Token caché dans le Secret SERVICE_ACCOUNT_TOKEN=$(kubectl -n default get secret ${SERVICE_ACCOUNT_SECRET} -o jsonpath="{.data['token']}" 2> /dev/null | base64 --decode) ``` ##### Étape 8 : Générer le fichier kubeconfig Reconstituons toutes les informations dans un nouveau fichier `kubeconfig` : ```bash [Terminal] cat < kubeconfig apiVersion: v1 kind: Config users: - name: ${SERVICE_ACCOUNT} user: token: ${SERVICE_ACCOUNT_TOKEN} clusters: - name: ${CLUSTER_NAME} cluster: certificate-authority-data: ${CLUSTER_CA_CRT} server: ${CLUSTER_ENDPOINT} contexts: - name: ${CONTEXT} context: cluster: ${CLUSTER_NAME} namespace: ${NAMESPACE1} user: ${SERVICE_ACCOUNT} current-context: ${CONTEXT} EOF ``` ##### Étape 9 : Tester l'accès Vous pouvez tester l'accès avec le kubeconfig généré : ```bash [Terminal] # Commande en erreur : vous n'avez pas accès au NS kube-system kubectl --kubeconfig=./kubeconfig -n kube-system get all # Commandes Ok : accès aux deux namespaces autorisés kubectl --kubeconfig=./kubeconfig -n ${NAMESPACE1} get all kubectl --kubeconfig=./kubeconfig -n ${NAMESPACE2} get all ``` :::tip Ce `ServiceAccount` peut accéder aux deux `Namespaces` spécifiés. Pour ajouter d'autres `Namespaces`, créez simplement de nouveaux `RoleBindings` référençant le même `ClusterRole`. ::: :::: ### Bonnes pratiques #### Principe du moindre privilège :::warning N'accordez **que les permissions strictement nécessaires**. Évitez les configurations trop permissives comme `verbs: ["*"]` ou `resources: ["*"]` en production. ::: **Exemple de `Role` restrictif** : ```yaml showLineNumbers [role.yaml] apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: pod-reader namespace: production rules: - apiGroups: [""] resources: ["pods"] verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["pods/log"] verbs: ["get"] ``` #### Séparation des responsabilités * **Développeurs** : Accès en lecture/écriture sur leurs namespaces (`edit` role) * **Ops/SRE** : Accès administrateur multi-namespaces avec `RoleBindings` ciblés * **Administrateurs** : Seul le strict minimum devrait avoir `cluster-admin` #### Utiliser les `Roles` prédéfinis Privilégiez les `ClusterRoles` prédéfinis quand ils correspondent à vos besoins : ```yaml showLineNumbers [rolebinding.yaml] apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: developers-edit namespace: dev subjects: - kind: Group name: dev-team apiGroup: rbac.authorization.k8s.io roleRef: kind: ClusterRole name: edit # Role prédéfini Kubernetes apiGroup: rbac.authorization.k8s.io ``` #### `ServiceAccounts` dédiés Créez un `ServiceAccount` par application avec des permissions spécifiques : :::code-group ```yaml showLineNumbers [serviceaccount.yaml] apiVersion: v1 kind: ServiceAccount metadata: name: app-backend namespace: production ``` ```yaml showLineNumbers [role.yaml] apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: app-backend-role namespace: production rules: - apiGroups: [""] resources: ["configmaps", "secrets"] resourceNames: ["app-config", "app-secrets"] # Limiter aux ressources spécifiques verbs: ["get"] ``` ::: #### Audit et révision * **Lister les `RoleBindings`** : ```bash [Terminal] kubectl get rolebindings --all-namespaces ``` * **Vérifier les permissions d'un `ServiceAccount`** : ```bash [Terminal] kubectl auth can-i --list --as=system:serviceaccount:my-namespace:my-sa ``` * **Tester une action spécifique** : ```bash [Terminal] kubectl auth can-i create pods --as=system:serviceaccount:my-namespace:my-sa -n my-namespace ``` #### Gestion des tokens :::warning Sécurité des tokens * Ne commitez **jamais** de kubeconfig ou tokens dans Git * Utilisez des tokens avec durée de vie limitée (Kubernetes 1.24+) * Stockez les tokens dans un gestionnaire de secrets (Vault, AWS Secrets Manager...) * Révoquez les tokens compromis en supprimant le `Secret` ou `ServiceAccount` ::: ```bash [Terminal] # Créer un token avec expiration (1 heure) kubectl create token my-service-account -n my-namespace --duration=1h # Créer un token avec expiration longue (1 an) kubectl create token my-service-account -n my-namespace --duration=8760h ``` #### Éviter les `ClusterRoleBindings` inutiles Préférez les `RoleBindings` même avec des `ClusterRoles` pour limiter la portée : ```yaml showLineNumbers [rolebinding.yaml] # ✅ Bon : ClusterRole appliqué à un namespace apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: dev-team-binding namespace: dev roleRef: kind: ClusterRole name: edit apiGroup: rbac.authorization.k8s.io subjects: - kind: Group name: dev-team apiGroup: rbac.authorization.k8s.io ``` ### Troubleshooting #### Erreur : "User cannot list resource" **Symptôme** : ``` Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:my-ns:my-sa" cannot list resource "pods" ``` **Causes possibles** : 1. Le `ServiceAccount` n'a pas les permissions nécessaires 2. Le `RoleBinding` n'est pas dans le bon namespace 3. Le verb "list" n'est pas accordé **Solution** : ```bash [Terminal] # Vérifier les permissions actuelles kubectl auth can-i list pods --as=system:serviceaccount:my-ns:my-sa -n my-ns # Lister les RoleBindings du namespace kubectl get rolebindings -n my-ns # Décrire le RoleBinding pour voir les permissions kubectl describe rolebinding -n my-ns ``` #### Erreur : "Unable to connect to the server" **Symptôme** : ``` Unable to connect to the server: x509: certificate signed by unknown authority ``` **Causes** : * Le certificat CA du cluster est manquant ou invalide dans le kubeconfig **Solution** : ```bash [Terminal] # Régénérer le certificat CA CLUSTER_CA_CRT=$(kubectl config view --minify --flatten -o=jsonpath='{.clusters[*].cluster.certificate-authority-data}') # Mettre à jour le kubeconfig avec le bon certificat ``` #### `ServiceAccount` non trouvé dans le `Pod` **Symptôme** : ``` Error: secrets "default-token-xxxxx" not found ``` **Causes** : * Le `ServiceAccount` spécifié n'existe pas dans le `Namespace` * À partir de K8s 1.24, les tokens ne sont plus créés automatiquement **Solution** : ```bash [Terminal] # Vérifier que le ServiceAccount existe kubectl get sa -n my-namespace # Créer le ServiceAccount si nécessaire kubectl create sa my-service-account -n my-namespace # Pour K8s 1.24+, créer le token manuellement kubectl create token my-service-account -n my-namespace ``` #### Permissions insuffisantes malgré un `ClusterRole` **Symptôme** : Un `ClusterRole` est défini mais l'utilisateur n'a pas les permissions. **Causes** : * Manque de `RoleBinding` ou `ClusterRoleBinding` pour lier le `ClusterRole` au sujet **Solution** : ```bash [Terminal] # Vérifier les bindings existants kubectl get clusterrolebindings | grep kubectl get rolebindings --all-namespaces | grep # Créer le binding manquant kubectl create rolebinding my-binding --clusterrole= --serviceaccount=: -n ``` #### Impossible de modifier un `RoleBinding` **Symptôme** : ``` Error: roleRef is immutable ``` **Causes** : * Le champ `roleRef` d'un `RoleBinding` ne peut pas être modifié après création **Solution** : ```bash [Terminal] # Supprimer et recréer le RoleBinding kubectl delete rolebinding -n kubectl create rolebinding --role= --serviceaccount=: -n ``` ### Commandes utiles récapitulatives ```bash [Terminal] # Lister tous les Roles d'un namespace kubectl get roles -n # Lister tous les ClusterRoles kubectl get clusterroles # Lister tous les RoleBindings kubectl get rolebindings --all-namespaces # Lister tous les ClusterRoleBindings kubectl get clusterrolebindings # Décrire les permissions d'un Role kubectl describe role -n # Vérifier si un utilisateur peut effectuer une action kubectl auth can-i --as= -n # Voir toutes les permissions d'un utilisateur kubectl auth can-i --list --as= -n # Créer un Role rapidement kubectl create role pod-reader --verb=get,list,watch --resource=pods -n # Créer un RoleBinding rapidement kubectl create rolebinding read-pods --role=pod-reader --serviceaccount=: -n # Créer un ServiceAccount kubectl create serviceaccount -n # Créer un token pour un ServiceAccount (K8s 1.24+) kubectl create token -n --duration= # Supprimer un RoleBinding kubectl delete rolebinding -n ``` ## Guide d'utilisation de Velero Ce guide détaille l'utilisation de Velero pour la sauvegarde et la restauration de ressources Kubernetes sur un cluster SdV. Il s'adresse aux équipes DevOps/Ops/SRE responsables de la gestion des sauvegardes et de la continuité d'activité. ### Introduction Velero est un outil open-source permettant de sauvegarder et restaurer les ressources Kubernetes ainsi que les volumes persistants. Sur l'infrastructure SdV, Velero est pré-installé et configuré pour effectuer des sauvegardes automatiques. #### Prérequis Pour interagir avec Velero, vous devez disposer du `velero-cli`, que vous pouvez installer en suivant [les instructions sur le site officiel](https://velero.io/docs/latest/basic-install/#install-the-cli). :::warning Il est impératif de disposer d'une version du client équivalente à la version du serveur pour garantir la compatibilité. ::: Pour connaître la version de Velero installée sur votre cluster : ```bash [Terminal] kubectl -n velero get deploy velero -o=jsonpath='{.spec.template.spec.containers[0].image}' | cut -d: -f2 ``` ### Configuration et paramètres #### Namespace Velero Chez SdV, Velero est installé par défaut dans le namespace `velero`. Il est donc inutile de préciser l'argument `--namespace` à `velero-cli` sauf si vous avez décidé de déplacer `velero` dans un autre namespace. #### Configuration KUBECONFIG Par défaut, `velero-cli` utilise la configuration `KUBECONFIG` courante de votre terminal, identique à votre commande `kubectl`. Pour forcer l'utilisation d'un fichier `KUBECONFIG` particulier : ```bash [Terminal] velero --kubeconfig=/path/to/kubeconfig ``` Pour forcer le contexte à utiliser : ```bash [Terminal] velero --kubecontext= ``` #### Inclusion et exclusion de namespaces Sans précision de votre part, `velero-cli` utilise `--include-namespaces '*'`, ce qui inclut tous les namespaces dans le scope de votre demande de backup. **Pour limiter le backup à un ou plusieurs namespaces :** ```bash [Terminal] velero --include-namespaces namespace1,namespace2 ``` **Pour exclure un ou plusieurs namespaces :** ```bash [Terminal] velero --exclude-namespaces namespace1,namespace2 ``` #### Durée de rétention des sauvegardes Pour préciser la durée de rétention lors de la création d'une sauvegarde ponctuelle ou d'une programmation, utilisez l'argument `--ttl`. La valeur est au format `XXXhYYYmZZZs`. **Exemples :** | Durée | Format | Description | | -------- | ----------- | ------------------ | | 30 jours | `720h0m0s` | Rétention standard | | 7 jours | `168h0m0s` | Rétention courte | | 90 jours | `2160h0m0s` | Rétention longue | :::info La durée de rétention par défaut dépend de la configuration de votre cluster SdV. ::: ### Cas d'usage et exemples #### Programmer une sauvegarde récurrente Exemple avec une sauvegarde quotidienne en gardant 30 jours de rétention : ```bash [Terminal] velero create schedule dailybackup-name \ --schedule="@every 24h" \ --ttl 720h0m0s ``` | Argument | Description | | ---------------------------------- | ------------------------------------------------------------------ | | `create schedule dailybackup-name` | Créer une tâche de sauvegarde programmée nommée *dailybackup-name* | | `--schedule="@every 24h"` | Exécution toutes les 24 heures | | `--ttl 720h0m0s` | Durée de conservation des sauvegardes : 720h (30 jours) | **Autres syntaxes de planification :** ```bash [Terminal] # Tous les jours à 2h du matin velero create schedule daily-2am --schedule="0 2 * * *" --ttl 720h0m0s # Toutes les heures velero create schedule hourly --schedule="@every 1h" --ttl 96h0m0s # Tous les lundis à 3h velero create schedule weekly --schedule="0 3 * * 1" --ttl 2160h0m0s ``` #### Créer une sauvegarde ponctuelle Pour effectuer immédiatement une sauvegarde du namespace *backup-test* : ```bash [Terminal] velero create backup backup-test --include-namespaces backup-test ``` **Exemples avancés :** ```bash [Terminal] # Backup complet du cluster (tous les namespaces) velero create backup full-cluster-backup --ttl 720h0m0s # Backup d'un namespace avec TTL personnalisé velero create backup my-app-backup \ --include-namespaces my-app \ --ttl 168h0m0s # Backup de plusieurs namespaces velero create backup multi-ns-backup \ --include-namespaces app1,app2,app3 \ --ttl 720h0m0s ``` #### Sauvegarde des `PersistentVolumes` Lorsque vous souhaitez que `velero` sauvegarde le contenu stocké sur les `PersistentVolumeClaims`, vous devez ajouter une annotation sur les `Pods` qui utilisent ces `PersistentVolumeClaims`. Vos Pods étant pilotés par des `Deployments`, `DaemonSets`, `StatefulSets`, `CronJobs` et autres, vous devez prévoir l'annotation dans ces entités englobantes pour que les `Pods` qui en découlent disposent de la bonne annotation. Exemple pour un `Deployment` : ```yaml showLineNumbers [deployment.yaml] apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment namespace: my-namespace spec: replicas: 1 selector: matchLabels: app: my-deployment template: metadata: labels: app: my-deployment annotations: backup.velero.io/backup-volumes: my-service-storage spec: containers: - name: my-service image: myservice:tag volumeMounts: - name: my-service-storage mountPath: /data volumes: - name: my-service-storage persistentVolumeClaim: claimName: my-persistent-volume-claim ``` Ici, le `Deployment` démarre un `Pod` qui va utiliser le `PersistentVolumeClaim` nommé `my-persistent-volume-claim`.\ C'est ce `PersistentVolumeClaim` que nous souhaitons sauvegarder. L'annotation est donc ajoutée au niveau `.spec.template.metadata.annotations` pour que le `Pod` porte bien l'annotation `backup.velero.io.backup-volumes` donnant pour instruction de sauvegarder les volumes nommés en valeur, dans notre cas : `my-service-storage`. Ce volume `my-service-storage` fait référence à un `persistentVolumeClaim` nommé `my-persistent-volume-claim`. C'est bien celui-ci qui sera sauvegardé. ```shell [Terminal] velero create schedule my-deployment-backup \ --include-namespaces my-namespace \ --schedule='0 5 * * *' \ --ttl 720h0m0s ``` | Argument | Détail | | -------------------------------------- | ------------------------------------------------------------------------------------- | | `create schedule my-deployment-backup` | Créer une tâche de sauvegarde programmée se nommant *my-deployment-backup* | | `--include-namespaces my-namespace` | La sauvegarde ne concernera que les entités annotées dans le Namespace `my-namespace` | | `--schedule='0 5 * * *'` | La plannificiation se fait tous les jours à 05h00 | | `–ttl 720h0m0s` | Définis la durée de conservation des sauvegardes à 720h soit 30 jours | ### Visualisation et monitoring #### Visualiser les sauvegardes programmées ```bash [Terminal] velero schedule get ``` **Exemple de sortie :** ``` NAME STATUS CREATED SCHEDULE BACKUP TTL LAST BACKUP SELECTOR my-deployment-backup Enabled 2022-02-24 10:48:22 +0100 CET 0 5 * * * 720h0m0s 28m ago sdv-velero-hourly Enabled 2021-03-31 11:33:26 +0200 CEST @every 1h 96h0m0s 21m ago ``` #### Visualiser les sauvegardes réalisées ```bash [Terminal] velero backup get ``` **Exemple de sortie :** ``` NAME STATUS ERRORS WARNINGS CREATED EXPIRES STORAGE LOCATION SELECTOR my-deployment-backup-20220224094824 Completed 0 0 2022-02-24 10:48:24 +0100 CET 29d default sdv-velero-hourly-20220224095616 Completed 0 0 2022-02-24 10:56:16 +0100 CET 3d default sdv-velero-hourly-20220224085616 Completed 0 0 2022-02-24 09:56:16 +0100 CET 3d default ``` :::info La colonne `EXPIRES` indique quand la sauvegarde sera automatiquement supprimée selon le TTL configuré. ::: #### Voir les détails d'une sauvegarde ```bash [Terminal] velero describe backup my-deployment-backup-20220224094824 ``` **Exemple de sortie :** ``` Name: my-deployment-backup-20220224094824 Namespace: velero Labels: velero.io/schedule-name=my-deployment-backup velero.io/storage-location=default Annotations: velero.io/source-cluster-k8s-gitversion=v1.20.14 Phase: Completed Errors: 0 Warnings: 0 Namespaces: Included: my-namespace Excluded: Resources: Included: * Excluded: Cluster-scoped: auto Label selector: Storage Location: default Velero-Native Snapshot PVs: auto Snapshot Move Data: auto Data Mover: velero TTL: 720h0m0s CSISnapshotTimeout: 10m0s ItemOperationTimeout: 0s Hooks: Backup Format Version: 1.1.0 Started: 2022-02-24 10:48:24 +0100 CET Completed: 2022-02-24 10:49:15 +0100 CET Expiration: 2022-03-26 10:48:24 +0100 CET Total items to be backed up: 42 Items backed up: 42 Backup Volumes: Velero-Native Snapshots: CSI Snapshots: Pod Volume Backups - (specify --details for more information): Completed: 6 ``` Ajoutez l'argument `--details` pour voir le détail des entités et volumes sauvegardés : ```bash [Terminal] velero describe backup my-deployment-backup-20220224094824 --details ``` #### Télécharger une sauvegarde réalisée (manifestes YAML) ```bash [Terminal] velero backup download my-deployment-backup-20220224094824 ``` **Sortie attendue :** ``` Backup my-deployment-backup-20220224094824 has been successfully downloaded to ./my-deployment-backup-20220224094824-data.tar.gz ``` :::note Cette archive contient l'ensemble des manifestes YAML des ressources sauvegardées, mais **pas** le contenu des `PersistentVolumeClaims`. ::: #### Visualiser les sauvegardes de volumes ```bash [Terminal] velero repo get ``` **Exemple de sortie :** ``` NAME STATUS LAST MAINTENANCE test-default-kopia Ready 2026-03-02 13:57:31 +0100 CET ``` ### Restauration de sauvegardes #### Principe de restauration :::warning Une restauration **n'écrase jamais** une ressource déjà existante. Si vous souhaitez écraser une ressource, vous devez d'abord la supprimer manuellement. ::: **Commande de base :** ```bash [Terminal] velero create restore --from-backup ``` #### Exemples de restauration ##### Restauration complète d'un namespace ```bash [Terminal] # Supprimer le namespace existant (si nécessaire) kubectl delete namespace blog # Restaurer depuis un backup velero create restore --from-backup backup-blog ``` ##### Restauration d'un namespace vers un nouveau nom ```bash [Terminal] velero create restore \ --from-backup backup-blog \ --namespace-mappings blog:blog-restored ``` ##### Restauration sélective de ressources ```bash [Terminal] # Restaurer uniquement les Deployments et Services velero create restore \ --from-backup backup-blog \ --include-resources deployments,services # Restaurer en excluant certaines ressources velero create restore \ --from-backup backup-blog \ --exclude-resources secrets,configmaps ``` ##### Vérifier l'état d'une restauration ```bash [Terminal] velero restore get # Voir les détails d'une restauration velero restore describe ``` ### Gestion avancée des `PersistentVolumes` #### Récupération d'un `PV` orphelin Dans Kubernetes, lorsqu'un `Namespace` ou un `PersistentVolumeClaim` est supprimé, le `PersistentVolume` associé se retrouve sans attribution et sera supprimé ou conservé selon la `ReclaimPolicy` configurée. :::info Il est possible de récupérer un `PV` orphelin si la `ReclaimPolicy` vaut `Retain`. Kubernetes ne permet pas cette récupération de manière automatique. ::: Consultez la [documentation officielle des PersistentVolumes](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) pour plus de détails. #### Procédure manuelle de récupération ::::steps ##### Étape 1 : Passer le `PV` en mode `Retain` Si le `PersistentVolume` est en `ReclaimPolicy: Delete`, passez-le en `Retain` : ```bash [Terminal] kubectl patch pv -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}' ``` :::tip Le nom du PV est trouvable dans le champ `volumeName` du `PersistentVolumeClaim`. ::: ##### Étape 2 : Retirer le finalizer du `PVC` ```bash [Terminal] kubectl patch pvc --type=json --patch='[{"op": "remove", "path": "/metadata/finalizers"}]' ``` ##### Étape 3 : Supprimer le `PVC` ```bash [Terminal] kubectl delete pvc ``` Le `PV` reste disponible grâce à la `ReclaimPolicy: Retain`. ##### Étape 4 : Retirer la référence au `PVC` dans le `PV` ```bash [Terminal] kubectl patch pv --type=json --patch='[{"op": "remove", "path": "/spec/claimRef"}]' ``` ##### Étape 5 : Créer un nouveau `PVC` lié au `PV` existant Créez un nouveau `PVC` en spécifiant le champ `volumeName` : ```yaml showLineNumbers [pvc.yaml] apiVersion: v1 kind: PersistentVolumeClaim metadata: name: your-pvc-name namespace: your-namespace spec: storageClassName: managed-nfs-storage accessModes: - ReadWriteMany resources: requests: storage: 1Gi volumeName: pv0001 ``` ##### Étape 6 : Appliquer le manifeste ```bash [Terminal] kubectl apply -f pvc.yaml ``` :::: #### Exemple complet ```yaml showLineNumbers [pvc.yaml] apiVersion: v1 kind: PersistentVolumeClaim metadata: name: webapp-data namespace: production spec: storageClassName: managed-nfs-storage accessModes: - ReadWriteMany resources: requests: storage: 10Gi volumeName: pvc-z11fe0a2-919c-4f58-b2e9-f9bd1cf925dd ``` ### Bonnes pratiques * **Planification** : Configurez des sauvegardes automatiques avec des schedules adaptés (quotidien, hebdomadaire) * **TTL** : Définissez des durées de rétention cohérentes avec votre politique de sauvegarde (30j minimum recommandé) * **Volumes** : N'oubliez pas l'annotation `backup.velero.io/backup-volumes` pour sauvegarder les volumes persistants * **Tests de restauration** : Testez régulièrement vos restaurations sur un namespace de test * **Documentation** : Documentez vos schedules et stratégies de sauvegarde * **Monitoring** : Surveillez l'état des sauvegardes avec `velero backup get` et alertez en cas d'erreur ### Commandes utiles récapitulatives ```bash [Terminal] # Lister toutes les sauvegardes velero backup get # Lister tous les schedules velero schedule get # Créer une sauvegarde immédiate d'un namespace velero backup create --include-namespaces # Restaurer une sauvegarde velero restore create --from-backup # Voir les logs d'une sauvegarde velero backup logs # Voir les logs d'une restauration velero restore logs # Supprimer une sauvegarde velero backup delete # Supprimer un schedule velero schedule delete ``` ## cert-manager : Let's Encrypt depuis K8S - DNS01 Ce guide détaille la mise en œuvre de certificats TLS automatisés via **Let's Encrypt** et **cert-manager** sur un cluster Kubernetes SdV, en utilisant la méthode de validation **DNS01**. Il s'adresse aux équipes DevOps/Ops/SRE souhaitant sécuriser leurs applications web avec HTTPS, en particulier pour les **Ingress privés** et les **certificats wildcard**. :::warning **Spécificité DNS01**\ La méthode `DNS01` nécessite un **Token d'API SdV** pour automatiser la création d'enregistrements DNS de validation. Ce guide utilise le webhook SdV fourni par la plateforme. ::: :::info **Portée de ce guide**\ Ce guide couvre **uniquement** la validation `DNS01` sur un `Ingress` public **ou** privé, avec support des certificats wildcard.\ Pour un `Ingress` public uniquement sans wildcard, consultez le guide [validation HTTP01](/guides/cert-manager/challenge-http). ::: ### Introduction **Let's Encrypt** est une autorité de certification (CA) gratuite, automatisée et ouverte, permettant de générer des certificats TLS pour sécuriser les communications HTTPS. Sur Kubernetes, [cert-manager](https://cert-manager.io/) automatise l'ensemble du cycle de vie des certificats : demande, validation, renouvellement et révocation. #### Concepts clés ##### Méthode de validation DNS01 La validation `DNS01` repose sur la création temporaire d'un enregistrement DNS TXT pour prouver le contrôle du domaine : ``` _acme-challenge.[votre-domaine] IN TXT "" ``` Let's Encrypt vérifie que vous contrôlez le domaine en interrogeant le DNS. Cette méthode nécessite : * Un compte avec accès API au fournisseur DNS (ici : API SdV) * Un Token d'API SdV avec les droits de gestion DNS * Le webhook `cert-manager-webhook-sdv` pour automatiser les opérations DNS :::tip **Quand utiliser DNS01 ?** * Ingress privés (non accessibles depuis Internet) * Certificats wildcard (`*.example.com`) * Domaines avec restrictions d'accès HTTP * Validation sans exposer de service public ::: ##### `Issuer` vs `ClusterIssuer` * **`Issuer`** : Ressource namespacée, utilisable uniquement dans son namespace * **`ClusterIssuer`** : Ressource cluster-wide, utilisable depuis tous les namespaces :::tip **Recommandation**\ Pour DNS01 avec le webhook SdV, utilisez des `Issuers` namespacés si vous souhaitez isoler les configurations par projet, ou des `ClusterIssuers` pour mutualiser. ::: ##### Staging vs Production Let's Encrypt propose deux environnements : | Environnement | Usage | Limites | Validité certificat | | -------------- | -------------------- | ------------------------------ | --------------------------------- | | **Staging** | Tests, développement | Aucune | ❌ Non reconnu par les navigateurs | | **Production** | Production | 50 certificats/domaine/semaine | ✅ Valide et reconnu | Le mode **staging** permet de valider votre configuration sans risquer d'atteindre les limites de production. :::warning **Limites de production**\ Let's Encrypt impose des [rate limits](https://letsencrypt.org/docs/rate-limits/) stricts en production. Testez toujours en staging avant de passer en production. ::: ##### Certificats wildcard Un certificat wildcard (`*.example.com`) couvre tous les sous-domaines de premier niveau : * ✅ `app.example.com`, `api.example.com`, `test.example.com` * ❌ `sub.app.example.com` (sous-domaine de niveau 2) :::info Pour couvrir le domaine racine **et** les sous-domaines, incluez les deux dans `dnsNames` : ```yaml dnsNames: - example.com - "*.example.com" ``` ::: ### Prérequis Avant de démarrer, assurez-vous de disposer de : * **Helm 3.x** ([guide d'installation](https://helm.sh/docs/intro/install/)) * **kubectl** configuré avec un `kubeconfig` valide pointant vers votre cluster SdV * **Une entrée DNS** (A ou CNAME) pointant vers l'IP de l'`Ingress` privé du cluster (pour ingress privés) ou public * **Un Token d'API SdV** avec les droits de gestion DNS pour votre zone * **Droits d'administration** sur le cluster (création de namespaces, CRDs, ClusterRoles) :::tip **Obtention du Token SdV**\ Contactez votre administrateur SdV ou consultez le portail SdV pour générer un Token d'API avec les permissions DNS. ::: ### Installation de cert-manager cert-manager est un composant central pour la gestion automatisée des certificats TLS sur Kubernetes. Il s'installe via Helm et déploie plusieurs contrôleurs, CRDs (Custom Resource Definitions) et webhooks. :::info **Version recommandée**\ À la date de rédaction (février 2026), la version stable recommandée est **v1.14.2**.\ Consultez les [releases officielles](https://github.com/jetstack/cert-manager/releases) pour les versions plus récentes. ::: :::steps #### Étape 1 : Ajout du chart Helm Ajoutez le repository Jetstack à votre configuration Helm : ```bash [Terminal] helm repo add jetstack https://charts.jetstack.io --force-update helm repo update ``` #### Étape 2 : Déploiement de cert-manager Nous déployons cert-manager dans un namespace dédié `cert-manager` avec les CRDs incluses. ```bash [Terminal] cat < values.yaml # Installation des CRDs (Custom Resource Definitions) # Obligatoire pour utiliser les ressources Certificate, Issuer, ClusterIssuer installCRDs: true # Désactivation de Prometheus (optionnel) # Activez si vous utilisez Prometheus pour la supervision prometheus: enabled: false # Ressources recommandées pour la production resources: requests: cpu: 10m memory: 32Mi limits: cpu: 100m memory: 128Mi EOF helm upgrade --install \ --namespace cert-manager \ --create-namespace \ --version "v1.14.2" \ --values values.yaml \ cert-manager \ jetstack/cert-manager ``` #### Étape 3 : Vérification de l'installation Après quelques instants, vérifiez que tous les `Pods` cert-manager sont opérationnels : ```bash [Terminal] kubectl get pods -n cert-manager ``` Vous devriez voir 3 pods en état `Running` : * `cert-manager-` : Contrôleur principal * `cert-manager-webhook-` : Webhook de validation * `cert-manager-cainjector-` : Injection de CA bundles Vérifiez également les CRDs installées : ```bash [Terminal] kubectl get crd | grep cert-manager ``` Vous devriez voir plusieurs CRDs dont `certificates.cert-manager.io`, `issuers.cert-manager.io`, `clusterissuers.cert-manager.io`. ::: :::tip **Personnalisation avancée**\ Le fichier `values.yaml` par défaut propose de nombreuses options (resource limits, replicas, webhooks, annotations). Consultez le [values.yaml complet](https://github.com/jetstack/cert-manager/blob/master/deploy/charts/cert-manager/values.yaml) pour personnaliser selon vos besoins. ::: ### Installation du webhook SdV Le webhook `cert-manager-webhook-sdv` est un composant spécifique SdV qui permet à cert-manager d'interagir avec l'API DNS de SdV pour créer automatiquement les enregistrements TXT nécessaires à la validation DNS01. :::warning **Token SdV requis**\ Vous devez disposer d'un Token d'API SdV avec les droits de gestion DNS. Conservez ce token de manière sécurisée. ::: #### Déploiement du webhook ```bash [Terminal] cat < values.webhook.yaml # Email de contact pour Let's Encrypt contactEmail: admin@example.com # Token d'API SdV pour la gestion DNS applicationToken: "VOTRE_TOKEN_SDP_ICI" EOF helm upgrade --install \ --namespace cert-manager \ --create-namespace \ --version "0.8.0" \ --values values.webhook.yaml \ cert-manager-webhook-sdv \ oci://sdv-registry-ext.sdv.fr/devops/cert-manager-webhook-sdv-chart ``` #### Vérification du webhook Vérifiez que le pod webhook SdV est opérationnel : ```bash [Terminal] kubectl get pods -n cert-manager -l app=cert-manager-webhook-sdv ``` #### Création des Issuers DNS01 Par défaut, le webhook SdV crée automatiquement deux `ClusterIssuers` : ```bash [Terminal] kubectl get clusterissuer ``` Sortie attendue : ``` NAME READY AGE cert-manager-clusterissuer-staging True 5m cert-manager-clusterissuer-production True 5m ``` Si vous préférez créer des `Issuers` namespacés, voici les manifestes : :::code-group ```yaml showLineNumbers [issuer-dns01-staging.yaml] apiVersion: cert-manager.io/v1 kind: Issuer metadata: name: letsencrypt-dns-staging namespace: letsencrypt-example spec: acme: # Serveur ACME de staging Let's Encrypt server: https://acme-staging-v02.api.letsencrypt.org/directory # Email de contact pour les notifications email: admin@example.com # Secret pour stocker la clé privée du compte ACME privateKeySecretRef: name: letsencrypt-dns-staging # Solver DNS01 avec webhook SdV solvers: - dns01: webhook: groupName: sdv solverName: sdv config: apiKeySecretRef: name: sdv-webhook-credentials key: applicationToken ``` ```yaml showLineNumbers [issuer-dns01-production.yaml] apiVersion: cert-manager.io/v1 kind: Issuer metadata: name: letsencrypt-dns-production namespace: letsencrypt-example spec: acme: # Serveur ACME de production Let's Encrypt server: https://acme-v02.api.letsencrypt.org/directory # Email de contact pour les notifications email: admin@example.com # Secret pour stocker la clé privée du compte ACME privateKeySecretRef: name: letsencrypt-dns-production # Solver DNS01 avec webhook SdV solvers: - dns01: webhook: groupName: sdv solverName: sdv config: apiKeySecretRef: name: sdv-webhook-credentials key: applicationToken ``` ::: ```bash [Terminal] # Créer d'abord le secret avec le token SdV kubectl create secret generic sdv-webhook-credentials \ --namespace letsencrypt-example \ --from-literal=applicationToken='VOTRE_TOKEN_SDP_ICI' # Déployer les Issuers kubectl apply -f issuer-dns01-staging.yaml kubectl apply -f issuer-dns01-production.yaml ``` :::info **`ClusterIssuers` vs `Issuers`**\ Si vous utilisez les `ClusterIssuers` créés automatiquement par le webhook, vous n'avez pas besoin de créer ces `Issuers` namespacés. Les `ClusterIssuers` sont utilisables depuis n'importe quel namespace. ::: ### Mise en œuvre dans un projet Le processus se déroule en trois étapes : 1. Déploiement de l'exemple 2. Génération d'un certificat TLS provenant d'un `Issuer` de staging 3. Génération d'un certificat TLS provenant d'un `Issuer` de production #### Déploiement de l'exemple À titre d'exemple, nous allons déployer un serveur nginx qui sert une simple page statique. Les ressources à déployer sont : * Un **`Namespace`** * Un **`ConfigMap`** contenant notre page HTML * Un **`Deployment`** de nginx qui utilise le `ConfigMap` * Un **`Service`** pour permettre d'atteindre le serveur nginx * Un **`Ingress`** pour nous permettre d'accéder à nginx depuis le FQDN choisi :::warning Modifiez `[FQDN]` par le FQDN que vous aurez fait pointer vers l'`Ingress` privé du Cluster. ::: :::code-group ```yaml showLineNumbers [namespace.yaml] apiVersion: v1 kind: Namespace metadata: name: letsencrypt-example ``` ```yaml showLineNumbers [service.yaml] apiVersion: v1 kind: Service metadata: name: nginx-svc namespace: letsencrypt-example spec: selector: app: nginx ports: - port: 80 protocol: TCP targetPort: 80 ``` ```yaml showLineNumbers [configmap.yaml] apiVersion: v1 kind: ConfigMap metadata: name: nginx-html namespace: letsencrypt-example data: index.html: | Letsencrypt ``` ```yaml showLineNumbers [deployment.yaml] apiVersion: apps/v1 kind: Deployment metadata: name: nginx namespace: letsencrypt-example spec: replicas: 1 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - image: nginx:1.19 name: nginx volumeMounts: - name: nginx-html mountPath: /usr/share/nginx/html/index.html subPath: index.html readOnly: true resources: requests: cpu: "0.5" memory: 64Mi limits: cpu: "1.0" memory: 128Mi volumes: - name: nginx-html configMap: defaultMode: 0644 name: nginx-html ``` ```yaml showLineNumbers [ingress.yaml] apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: nginx-ingress namespace: letsencrypt-example annotations: ingress.kubernetes.io/rewrite-target: / spec: ingressClassName: private rules: - host: [FQDN] http: paths: - path: / pathType: Prefix backend: service: name: nginx-svc port: number: 80 ``` ::: ```bash [Terminal] kubectl apply -f namespace.yaml kubectl apply -f service.yaml kubectl apply -f configmap.yaml kubectl apply -f deployment.yaml kubectl apply -f ingress.yaml ``` Sortie attendue : ``` namespace/letsencrypt-example created service/nginx-svc created configmap/nginx-html created deployment.apps/nginx created ingress.networking.k8s.io/nginx-ingress created ``` :::info Notez que contrairement au guide pour la version HTTP-01, l'`Ingress` utilise la classe `private` et ne dispose pas de l'annotation qui le rend public. ::: Vérifiez que vous accédez bien à nginx de façon non sécurisée : ```bash [Terminal] curl http://[FQDN]/ ``` Sortie : ``` Letsencrypt ``` #### Génération d'un certificat TLS provenant d'un `Issuer` de staging Pour que `cert-manager` puisse traiter vos demandes de génération de certificats TLS, il faut créer des `Issuers` ou `ClusterIssuers`. :::info **`Issuers` vs `ClusterIssuers`** * **`Issuer`** : ressource namespacée * **`ClusterIssuer`** : ressource globale au cluster ::: Let's Encrypt permet la génération de deux catégories de certificats TLS : * **Certificats de production** : chaîne de signature complète, avec restrictions sur le nombre de certificats générés par jour * **Certificats de staging** : chaîne de signature incomplète, sans restriction sur le nombre de certificats générés par jour :::tip Par défaut, si vous déployez le webhook SdV, deux ClusterIssuers sont créés automatiquement : * `cert-manager-clusterissuer-staging` * `cert-manager-clusterissuer-production` ::: Contrairement à la méthode de validation HTTP-01, la méthode **DNS-01** permet la génération de certificats TLS pour des domaines wildcards. Pour pouvoir les générer, nous devrons créer un `Certificate` plutôt que d'utiliser l'annotation dans l'`Ingress`. ::::steps ##### Créez un manifeste `Certificate` ```yaml showLineNumbers [certificate.yaml] apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: wildcard-certificate namespace: letsencrypt-example spec: commonName: "*.[FQDN]" dnsNames: - [FQDN] - "*.[FQDN]" issuerRef: kind: Issuer name: letsencrypt-dns-staging secretName: nginx-tls ``` ##### Déployez le certificat ```bash [Terminal] kubectl apply -f certificate.yaml ``` ```text certificate.cert-manager.io/wildcard-certificate created ``` :::warning **Important** * Le nom du Secret dans `spec.secretName` sera à renseigner dans l'Ingress par la suite * Vous choisissez l'Issuer à utiliser via `spec.issuerRef` * Nous utilisons celui de staging le temps de valider l'exemple ::: ##### Modifier l'`Ingress` Il faut maintenant modifier l'`Ingress` pour ajouter le nœud `tls` qui précise : * Le(s) FQDN concerné(s) par la génération du certificat TLS * Le nom du Secret Kubernetes qui sera créé et qui contiendra les clés privée/publique Éditez l'`Ingress` : ```bash [Terminal] kubectl -n letsencrypt-example edit ingress nginx-ingress ``` Modifiez l'`Ingress` pour ajouter la section TLS : ```yaml showLineNumbers [ingress.yaml] apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: nginx-ingress namespace: letsencrypt-example annotations: ingress.kubernetes.io/rewrite-target: / spec: ingressClassName: public tls: - secretName: nginx-tls hosts: - [FQDN] rules: - host: [FQDN] http: paths: - path: / pathType: Prefix backend: service: name: nginx-svc port: number: 80 ``` :::info `spec.tls[].secretName` doit correspondre au Secret déclaré dans l'objet Certificate. `[FQDN]` est le domaine réellement servi par l'Ingress. ::: ##### Vérifier la génération du certificat ```bash [Terminal] kubectl -n letsencrypt-example describe certificate ``` Sortie complète ```text Name: wildcard-certificate Namespace: letsencrypt-example Labels: Annotations: API Version: cert-manager.io/v1 Kind: Certificate Metadata: Creation Timestamp: 2022-04-28T08:44:35Z Generation: 1 Resource Version: 230554098 Self Link: /apis/cert-manager.io/v1/namespaces/letsencrypt-example/certificates/wildcard-certificate UID: b8b35833-0c1d-4246-9cea-404c80748b38 Spec: Common Name: *.[FQDN] Dns Names: [FQDN] *.[FQDN] Issuer Ref: Kind: Issuer Name: letsencrypt-dns-staging Secret Name: nginx-tls Status: Conditions: Last Transition Time: 2022-04-28T08:58:38Z Message: Certificate is up to date and has not expired Observed Generation: 2 Reason: Ready Status: True Type: Ready Not After: 2022-07-27T07:58:36Z Not Before: 2022-04-28T07:58:37Z Renewal Time: 2022-06-27T07:58:36Z Revision: 1 Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Issuing 2m18s cert-manager Issuing certificate as Secret does not exist Normal Generated 2m17s cert-manager Stored new private key in temporary Secret resource "wildcard-certificate-vq2ms" Normal Requested 2m17s cert-manager Created new CertificateRequest resource "wildcard-certificate-tt25h" Normal Issuing 2m15s cert-manager The certificate has been successfully issued ``` La dernière ligne confirme que le certificat TLS a bien été créé. On peut vérifier le Secret créé : ```bash [Terminal] kubectl -n letsencrypt-example get secret nginx-tls ``` Sortie : ``` nginx-tls kubernetes.io/tls 2 2m23s ``` ##### Validation dans le navigateur Pour finir de valider le processus de génération, testez avec un navigateur en accédant au FQDN déclaré. :::info **Certificat de staging** Vous devriez avoir une erreur de validation du certificat, car celui-ci n'a pas été signé par une Autorité de Certification connue, puisque nous l'avons généré grâce à l'Issuer de staging : ```text Erreur Chrome : NET::ERR_CERT_AUTHORITY_INVALID Chaîne de Validation : Fake LE Root X1 ↳ Fake LE Intermediate X1 ↳ [FQDN] ``` ::: :::: #### Génération d'un certificat TLS provenant d'un `Issuer` de production Générons désormais le certificat par notre `Issuer` de production en éditant notre Certificate et modifiant le nœud `spec.issuerRef.name` pour pointer vers `letsencrypt-dns-production`. ::::steps ##### Éditez le `Certificate` ```bash [Terminal] kubectl -n letsencrypt-example edit certificate wildcard-certificate ``` Modifiez la référence de l'`Issuer` pour utiliser la production : ```yaml apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: wildcard-certificate namespace: letsencrypt-example spec: commonName: "*.[FQDN]" dnsNames: - [FQDN] - "*.[FQDN]" issuerRef: kind: Issuer name: letsencrypt-dns-production secretName: nginx-tls ``` Sauvegardez et patientez quelques secondes. ##### Vérifier le nouveau certificat ```bash [Terminal] kubectl -n letsencrypt-example describe certificate ``` Sortie complète ```text Name: wildcard-certificate Namespace: letsencrypt-example Labels: Annotations: API Version: cert-manager.io/v1 Kind: Certificate Metadata: Creation Timestamp: 2022-04-28T08:44:35Z Generation: 2 Resource Version: 230556933 Self Link: /apis/cert-manager.io/v1/namespaces/letsencrypt-example/certificates/wildcard-certificate UID: b8b35833-0c1d-4246-9cea-404c80748b38 Spec: Common Name: *.k8s-priv2.gretel.com Dns Names: k8s-priv2.gretel.com *.k8s-priv2.gretel.com Issuer Ref: Kind: Issuer Name: letsencrypt-dns-production Secret Name: nginx-tls Status: Conditions: Last Transition Time: 2022-04-28T08:58:38Z Message: Certificate is up to date and has not expired Observed Generation: 2 Reason: Ready Status: True Type: Ready Not After: 2022-07-27T07:58:36Z Not Before: 2022-04-28T07:58:37Z Renewal Time: 2022-06-27T07:58:36Z Revision: 2 Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Issuing 16m cert-manager-certificates-trigger Issuing certificate as Secret does not exist Normal Generated 16m cert-manager-certificates-key-manager Stored new private key in temporary Secret resource "wildcard-certificate-b8mnt" Normal Requested 16m cert-manager-certificates-request-manager Created new CertificateRequest resource "wildcard-certificate-lp7ph" Normal Issuing 4m37s cert-manager-certificates-trigger Issuing certificate as Secret was previously issued by Issuer.cert-manager.io/letsencrypt-dns-staging Normal Reused 4m37s cert-manager-certificates-key-manager Reusing private key stored in existing Secret resource "nginx-tls" Normal Requested 4m37s cert-manager-certificates-request-manager Created new CertificateRequest resource "wildcard-certificate-zmp5b" Normal Issuing 2m1s (x2 over 13m) cert-manager-certificates-issuing The certificate has been successfully issued ``` :::tip **Certificat mis à jour**\ La dernière ligne indique que le certificat a bien été mis à jour avec l'Issuer de production. ::: Il n'y a plus qu'à vérifier dans le navigateur : tout est désormais valide ! :::: ## cert-manager : Let's Encrypt depuis K8S - HTTP01 Ce guide détaille la mise en œuvre de certificats TLS automatisés via **Let's Encrypt** et **cert-manager** sur un cluster Kubernetes SdV, en utilisant la méthode de validation **HTTP01**. Il s'adresse aux équipes DevOps/Ops/SRE souhaitant sécuriser leurs applications web avec HTTPS. :::warning **Configuration préalable requise**\ Par défaut, les certificats Let's Encrypt avec validation `HTTP01` sont directement pris en charge par l'[ingress controller HAProxy](guides/ingress.md). Si vous souhaitez utiliser cert-manager au lieu du mécanisme natif, vous devez **demander à SdV de modifier la configuration de l'ingress controller** pour désactiver la génération automatique ACME. ::: :::info **Portée de ce guide**\ Ce guide couvre **uniquement** la validation `HTTP01` sur un `Ingress` public.\ Pour un `Ingress` privé ou des certificats wildcard, consultez le guide [validation DNS01](/guides/cert-manager/challenge-dns.md). ::: ### Introduction **Let's Encrypt** est une autorité de certification (CA) gratuite, automatisée et ouverte, permettant de générer des certificats TLS pour sécuriser les communications HTTPS. Sur Kubernetes, [cert-manager](https://cert-manager.io/) automatise l'ensemble du cycle de vie des certificats : demande, validation, renouvellement et révocation. #### Concepts clés ##### Méthode de validation HTTP01 La validation `HTTP01` repose sur la création temporaire d'un fichier accessible publiquement via HTTP à l'URL : ``` http:///.well-known/acme-challenge/ ``` Let's Encrypt vérifie que vous contrôlez le domaine en accédant à cette URL. Cette méthode nécessite : * Un domaine public pointant vers l'IP de l'Ingress public du cluster * Un accès HTTP (port 80) ouvert depuis Internet * Un Ingress Controller fonctionnel :::tip **Quand utiliser HTTP01 ?** * Domaines publics accessibles via HTTP * Certificats pour des FQDN spécifiques (pas de wildcard) * Pas d'accès API au fournisseur DNS ::: ##### `Issuer` vs `ClusterIssuer` * **`Issuer`** : Ressource namespacée, utilisable uniquement dans son namespace * **`ClusterIssuer`** : Ressource cluster-wide, utilisable depuis tous les namespaces :::tip **Recommandation**\ Privilégiez `ClusterIssuer` pour mutualiser la configuration ACME entre projets. ::: ##### Staging vs Production Let's Encrypt propose deux environnements : | Environnement | Usage | Limites | Validité certificat | | -------------- | -------------------- | ------------------------------ | --------------------------------- | | **Staging** | Tests, développement | Aucune | ❌ Non reconnu par les navigateurs | | **Production** | Production | 50 certificats/domaine/semaine | ✅ Valide et reconnu | Le mode **staging** permet de valider votre configuration sans risquer d'atteindre les limites de production. :::warning **Limites de production**\ Let's Encrypt impose des [rate limits](https://letsencrypt.org/docs/rate-limits/) stricts en production. Testez toujours en staging avant de passer en production. ::: ### Prérequis Avant de démarrer, assurez-vous de disposer de : * **Helm 3.x** ([guide d'installation](https://helm.sh/docs/intro/install/)) * **kubectl** configuré avec un `kubeconfig` valide pointant vers votre cluster SdV * **Une entrée DNS publique** (enregistrement A ou CNAME) pointant vers l'IP de l'`Ingress` public du cluster * **Droits d'administration** sur le cluster (création de namespaces, CRDs, ClusterRoles) * **Accès HTTP (port 80)** depuis Internet vers l'Ingress public (pour la validation ACME) :::tip **Vérification DNS**\ Validez la résolution DNS avant de continuer : ```bash [Terminal] nslookup votre-domaine.example.com dig votre-domaine.example.com +short ``` L'IP retournée doit correspondre à l'IP publique de votre Ingress. ::: ### Installation de cert-manager cert-manager est un composant central pour la gestion automatisée des certificats TLS sur Kubernetes. Il s'installe via Helm et déploie plusieurs contrôleurs, CRDs (Custom Resource Definitions) et webhooks. :::info **Version recommandée**\ À la date de rédaction (février 2026), la version stable recommandée est **v1.14.2**.\ Consultez les [releases officielles](https://github.com/jetstack/cert-manager/releases) pour les versions plus récentes. ::: ::::steps #### Étape 1 : Ajout du chart Helm Ajoutez le repository Jetstack à votre configuration Helm : ```bash [Terminal] helm repo add jetstack https://charts.jetstack.io --force-update helm repo update ``` #### Étape 2 : Déploiement de cert-manager Nous déployons cert-manager dans un namespace dédié `cert-manager` avec les CRDs incluses. ```bash [Terminal] cat < values.yaml # Installation des CRDs (Custom Resource Definitions) # Obligatoire pour utiliser les ressources Certificate, Issuer, ClusterIssuer installCRDs: true # Désactivation de Prometheus (optionnel) # Activez si vous utilisez Prometheus pour la supervision prometheus: enabled: false serviceMonitor: enabled: true # Ressources recommandées pour la production resources: requests: cpu: 10m memory: 32Mi limits: cpu: 100m memory: 128Mi EOF helm upgrade --install \ --namespace cert-manager \ --create-namespace \ --version "v1.14.2" \ --values values.yaml \ cert-manager \ jetstack/cert-manager ``` #### Étape 3 : Création des `ClusterIssuers` Créez les deux `ClusterIssuers` `http01` : :::code-group ```yaml showLineNumbers [clusterissuer-http01-staging.yaml] apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: cert-manager-clusterissuer-http01-staging spec: acme: # Serveur ACME de staging Let's Encrypt server: https://acme-staging-v02.api.letsencrypt.org/directory # Email de contact pour les notifications Let's Encrypt # (expiration, renouvellement, sécurité) email: admin@example.com # Secret Kubernetes pour stocker la clé privée du compte ACME privateKeySecretRef: name: cert-manager-clusterissuer-http01-staging # Méthode de validation : HTTP01 via Ingress solvers: - http01: ingress: ingressClassName: public ``` ```yaml showLineNumbers [clusterissuer-http01-production.yaml] apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: cert-manager-clusterissuer-http01-production spec: acme: # Serveur ACME de staging Let's Encrypt server: https://acme-v02.api.letsencrypt.org/directory # Email de contact pour les notifications Let's Encrypt # (expiration, renouvellement, sécurité) email: admin@example.com # Secret Kubernetes pour stocker la clé privée du compte ACME privateKeySecretRef: name: cert-manager-clusterissuer-http01-production # Méthode de validation : HTTP01 via Ingress solvers: - http01: ingress: ingressClassName: public ``` ::: ```bash [Terminal] kubectl apply -f cert-manager-clusterissuer-http01-staging.yaml kubectl apply -f cert-manager-clusterissuer-http01-production.yaml ``` Vérifiez que les `ClusterIssuers` sont prêts : ```bash [Terminal] kubectl get clusterissuer cert-manager-clusterissuer-http01-staging -o wide kubectl describe clusterissuer cert-manager-clusterissuer-http01-staging kubectl get clusterissuer cert-manager-clusterissuer-http01-production -o wide kubectl describe clusterissuer cert-manager-clusterissuer-http01-production ``` Le statut doit afficher `Ready: True`. #### Étape 4 : Vérification de l'installation Après quelques instants, vérifiez que tous les `Pods` cert-manager sont opérationnels : ```bash [Terminal] kubectl get pods -n cert-manager ``` Vous devriez voir 3 pods en état `Running` : * `cert-manager-` : Contrôleur principal * `cert-manager-webhook-` : Webhook de validation * `cert-manager-cainjector-` : Injection de CA bundles Vérifiez également les CRDs installées : ```bash [Terminal] kubectl get crd | grep cert-manager ``` Vous devriez voir plusieurs CRDs dont `certificates.cert-manager.io`, `issuers.cert-manager.io`, `clusterissuers.cert-manager.io`. :::: :::tip **Personnalisation avancée**\ Le fichier `values.yaml` par défaut propose de nombreuses options (resource limits, replicas, webhooks, annotations). Consultez le [values.yaml complet](https://github.com/jetstack/cert-manager/blob/master/deploy/charts/cert-manager/values.yaml) pour personnaliser selon vos besoins. ::: ### Mise en œuvre dans un projet Cette section illustre le déploiement complet d'une application avec génération automatique de certificats Let's Encrypt. Le processus se décompose en trois étapes : 1. **Déploiement d'une application exemple** (serveur nginx) 2. **Génération d'un certificat de staging** (validation de la configuration) 3. **Génération d'un certificat de production** (certificat valide et reconnu) :::tip **Approche progressive**\ Testez toujours en staging avant de passer en production pour éviter d'atteindre les rate limits de Let's Encrypt. ::: #### Déploiement de l'application exemple Nous utilisons un serveur **nginx** simple servant une page HTML statique. Cette application démontre l'intégration complète : application → service → ingress → certificat TLS. ##### Ressources Kubernetes déployées * **`Namespace`** : Isolation logique du projet * **`ConfigMap`** : Contenu HTML de la page * **`Deployment`** : Contrôleur de pods nginx * **`Service`** : Exposition interne du serveur nginx * **`Ingress`** : Exposition externe via FQDN ##### Manifeste complet :::warning **Personnalisation requise**\ Remplacez `[FQDN]` par votre propre nom de domaine pointant vers l'IP publique de l'`Ingress` du cluster. Exemple : `letsencrypt.example.com` ou `demo.k8s.votreentreprise.fr` ::: :::code-group ```yaml showLineNumbers [namespace.yaml] apiVersion: v1 kind: Namespace metadata: name: letsencrypt-example labels: project: letsencrypt-demo annotations: description: "Démonstration cert-manager avec Let's Encrypt HTTP01" ``` ```yaml showLineNumbers [configmap.yaml] apiVersion: v1 kind: ConfigMap metadata: name: nginx-html namespace: letsencrypt-example labels: app: nginx data: index.html: | Let's Encrypt Demo

✅ Certificat TLS Let's Encrypt opérationnel !

Généré automatiquement via cert-manager et validation HTTP01

``` ```yaml showLineNumbers [deployment.yaml] apiVersion: apps/v1 kind: Deployment metadata: name: nginx namespace: letsencrypt-example labels: app: nginx spec: replicas: 1 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.25-alpine ports: - containerPort: 80 protocol: TCP volumeMounts: - name: nginx-html mountPath: /usr/share/nginx/html/index.html subPath: index.html readOnly: true resources: requests: cpu: 50m memory: 64Mi limits: cpu: 100m memory: 128Mi livenessProbe: httpGet: path: / port: 80 initialDelaySeconds: 5 periodSeconds: 10 readinessProbe: httpGet: path: / port: 80 initialDelaySeconds: 3 periodSeconds: 5 volumes: - name: nginx-html configMap: name: nginx-html defaultMode: 0644 ``` ```yaml showLineNumbers [service.yaml] apiVersion: v1 kind: Service metadata: name: nginx-svc namespace: letsencrypt-example labels: app: nginx spec: type: ClusterIP selector: app: nginx ports: - port: 80 protocol: TCP targetPort: 80 name: http ``` ```yaml showLineNumbers [ingress.yaml] apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: nginx-ingress namespace: letsencrypt-example labels: app: nginx annotations: ingress.kubernetes.io/rewrite-target: / spec: ingressClassName: public rules: - host: [FQDN] # Remplacez par votre domaine, ex: demo.example.com http: paths: - path: / pathType: Prefix backend: service: name: nginx-svc port: number: 80 ``` ::: ##### Déploiement Une fois le manifeste personnalisé avec votre FQDN, appliquez-le : ```bash [Terminal] kubectl apply -f namespace.yaml kubectl apply -f configmap.yaml kubectl apply -f deployment.yaml kubectl apply -f service.yaml kubectl apply -f ingress.yaml ``` Sortie attendue : ``` namespace/letsencrypt-example created configmap/nginx-html created deployment.apps/nginx created service/nginx-svc created ingress.networking.k8s.io/nginx-ingress created ``` ##### Vérification du déploiement Vérifiez que tous les composants sont opérationnels : ```bash [Terminal] # Vérifier les pods kubectl get pods -n letsencrypt-example # Vérifier le service kubectl get svc -n letsencrypt-example # Vérifier l'Ingress kubectl get ingress -n letsencrypt-example ``` Testez l'accès HTTP (non sécurisé) : ```bash [Terminal] curl -I http://[FQDN]/ ``` Vous devriez obtenir un code `HTTP/1.1 200 OK`. :::tip **Résolution de problèmes**\ Si le service n'est pas accessible : 1. Vérifiez que le `Pod` nginx est en `Running` : `kubectl get pods -n letsencrypt-example` 2. Vérifiez les logs : `kubectl logs -n letsencrypt-example -l app=nginx` 3. Vérifiez la résolution DNS : `nslookup [FQDN]` 4. Vérifiez l'`Ingress` : `kubectl describe ingress nginx-ingress -n letsencrypt-example` ::: #### Génération d'un certificat TLS en staging ##### Comprendre les `Issuers` et `ClusterIssuers` Pour que cert-manager puisse traiter vos demandes de certificats TLS, il faut déclarer un `Issuer` (namespacé) ou un `ClusterIssuer` (cluster-wide). Let's Encrypt propose deux environnements ACME : | Environnement | URL API | Usage | Certificat valide | | -------------- | -------------------------------------------------------- | ------------------------ | ----------------------- | | **Staging** | `https://acme-staging-v02.api.letsencrypt.org/directory` | Tests, validation config | ❌ Non (CA non reconnue) | | **Production** | `https://acme-v02.api.letsencrypt.org/directory` | Production | ✅ Oui (CA reconnue) | :::info Suivez le [guide d'installation](#installation-de-cert-manager) pour vous assurer de disposer des `ClusterIssuers`. Vérifiez leur présence : ```bash [Terminal] kubectl get clusterissuer ``` ::: ##### Configuration de l'Ingress pour cert-manager Pour déclencher la génération automatique du certificat, modifiez l'`Ingress` en ajoutant : 1. **Annotation `cert-manager.io/cluster-issuer`** : Indique quel ClusterIssuer utiliser 2. **Section `tls`** : Définit les domaines concernés et le nom du Secret qui contiendra le certificat ```yaml showLineNumbers [ingress-tls-staging.yaml] apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: nginx-ingress namespace: letsencrypt-example labels: app: nginx annotations: ingress.kubernetes.io/rewrite-target: / # Annotation clé pour cert-manager cert-manager.io/cluster-issuer: cert-manager-clusterissuer-http01-staging spec: ingressClassName: public # Section TLS tls: - secretName: nginx-tls # Nom du Secret à créer automatiquement hosts: - [FQDN] # Domaine à sécuriser rules: - host: [FQDN] http: paths: - path: / pathType: Prefix backend: service: name: nginx-svc port: number: 80 ``` Appliquez la modification : ```bash [Terminal] kubectl apply -f ingress-tls-staging.yaml ``` Ou éditez directement l'Ingress existant : ```bash [Terminal] kubectl edit ingress nginx-ingress -n letsencrypt-example ``` ##### Suivi de la génération du certificat Dès que l'Ingress est modifié, cert-manager détecte l'annotation et déclenche automatiquement : 1. Création d'une ressource `Certificate` 2. Création d'une `CertificateRequest` 3. Création d'un `Order` (demande ACME) 4. Création d'un `Challenge` (validation HTTP01) 5. Création temporaire d'un pod solver et modification de l'Ingress 6. Validation par Let's Encrypt via `http://[FQDN]/.well-known/acme-challenge/` 7. Emission du certificat et stockage dans le Secret Suivez la progression : ```bash [Terminal] # Vérifier les événements de l'Ingress kubectl describe ingress nginx-ingress -n letsencrypt-example # Vérifier la ressource Certificate kubectl get certificate -n letsencrypt-example kubectl describe certificate nginx-tls -n letsencrypt-example # Suivre les challenges HTTP01 kubectl get challenge -n letsencrypt-example kubectl describe challenge -n letsencrypt-example # Vérifier les logs cert-manager kubectl logs -n cert-manager -l app=cert-manager -f ``` Lorsque le certificat est généré, vous verrez dans les événements : ``` Normal CreateCertificate Successfully created Certificate "nginx-tls" Normal Issuing Issuing certificate as Secret does not exist Normal Generated Stored new private key in temporary Secret Normal Requested Created new CertificateRequest Normal Issued Certificate issued successfully ``` ##### Vérification du Secret et du certificat Vérifiez que le Secret TLS a bien été créé : ```bash [Terminal] kubectl get secret nginx-tls -n letsencrypt-example kubectl describe secret nginx-tls -n letsencrypt-example ``` Le Secret de type `kubernetes.io/tls` contient deux clés : `tls.crt` (certificat) et `tls.key` (clé privée). Inspectez le certificat généré : ```bash [Terminal] kubectl get secret nginx-tls -n letsencrypt-example -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -text -noout ``` Points à vérifier : * **Issuer** : `Fake LE Intermediate X1` (staging) * **Subject** : `CN=[FQDN]` * **Validity** : Date d'expiration (90 jours par défaut) * **Subject Alternative Name** : `DNS:[FQDN]` ##### Test dans le navigateur Accédez à `https://[FQDN]/` dans votre navigateur. Vous obtiendrez une erreur de certificat : ``` Chrome : NET::ERR_CERT_AUTHORITY_INVALID Firefox : SEC_ERROR_UNKNOWN_ISSUER Chaîne de certification : Fake LE Root X1 └─ Fake LE Intermediate X1 └─ [FQDN] ``` C'est **normal** : le certificat de staging n'est pas signé par une CA reconnue. Il sert uniquement à valider la configuration. :::tip **Accéder malgré l'erreur**\ Pour tester quand même : * Chrome : Cliquez sur "Avancé" puis "Continuer vers le site" * Firefox : "Avancé" puis "Accepter le risque et continuer" * Curl : `curl -k https://[FQDN]/` ::: #### Génération d'un certificat TLS en production Une fois la configuration validée en staging, basculez vers le `ClusterIssuer` de production `cert-manager-clusterissuer-http01-production` pour obtenir un certificat reconnu par les navigateurs. ##### Modification de l'`Ingress` Il suffit de changer l'annotation `cert-manager.io/cluster-issuer` pour pointer vers le `ClusterIssuer` de production : ```bash [Terminal] kubectl edit ingress nginx-ingress -n letsencrypt-example ``` Modifiez uniquement l'annotation : ```yaml showLineNumbers [ingress-tls-production.yaml] apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: nginx-ingress namespace: letsencrypt-example labels: app: nginx annotations: ingress.kubernetes.io/rewrite-target: / # Basculement vers production cert-manager.io/cluster-issuer: cert-manager-clusterissuer-http01-production spec: ingressClassName: public tls: - secretName: nginx-tls hosts: - [FQDN] rules: - host: [FQDN] http: paths: - path: / pathType: Prefix backend: service: name: nginx-svc port: number: 80 ``` :::info **Renouvellement automatique**\ cert-manager détecte automatiquement le changement d'`Issuer` grâce à l'annotation modifiée. Il va : 1. Déclencher une nouvelle `CertificateRequest` 2. Supprimer l'ancien certificat staging 3. Générer un nouveau certificat production 4. Mettre à jour le Secret `nginx-tls` ::: ##### Suivi de la génération Suivez le processus comme pour le staging : ```bash [Terminal] # Surveiller la mise à jour du certificat kubectl get certificate nginx-tls -n letsencrypt-example -w # Vérifier les événements kubectl describe certificate nginx-tls -n letsencrypt-example # Voir les challenges kubectl get challenge -n letsencrypt-example ``` Événement attendu dans l'\`Ingress@ : ``` Normal UpdateCertificate Successfully updated Certificate "nginx-tls" ``` ##### Vérification du certificat production Inspectez le nouveau certificat : ```bash [Terminal] kubectl get secret nginx-tls -n letsencrypt-example -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -text -noout | grep -A2 "Issuer\|Subject\|Validity" ``` Points à vérifier : * **Issuer** : `CN=R3, O=Let's Encrypt, C=US` (production) * **Subject** : `CN=[FQDN]` * **Validity** : Expire dans 90 jours Vérifiez la chaîne complète : ```bash [Terminal] echo | openssl s_client -servername [FQDN] -connect [FQDN]:443 2>/dev/null | openssl x509 -noout -issuer -subject -dates ``` ##### Test final dans le navigateur Accédez à `https://[FQDN]/` dans votre navigateur. Le certificat doit être **valide et reconnu** : * Cadenas vert/icône de sécurité * Aucun avertissement de sécurité * Chaîne de certification complète et valide ``` Chaîne de certification : ISSRG Root X1 (DST Root CA X3) └─ R3 (Let's Encrypt Authority X3) └─ [FQDN] ``` Consultez les détails du certificat (clic sur le cadenas) pour vérifier : * Validité : 90 jours * Emis par : Let's Encrypt (R3 ou R11 selon la période) * Algorithme : RSA 2048 bits ou ECDSA :::tip **Renouvellement automatique**\ cert-manager renouvelle automatiquement le certificat **30 jours avant expiration**. Vous n'avez aucune action à effectuer. Surveillez les logs cert-manager pour être alerté en cas d'échec de renouvellement. ::: ##### Validation SSL/TLS avec des outils externes Pour une analyse complète de la configuration SSL/TLS : ```bash [Terminal] # Test avec curl curl -vI https://[FQDN]/ # Test avec testssl.sh (outil d'audit SSL) docker run --rm drwetter/testssl.sh [FQDN] # Analyse avec SSL Labs (en ligne) # https://www.ssllabs.com/ssltest/analyze.html?d=[FQDN] ``` ### Troubleshooting Cette section recense les problèmes courants rencontrés lors de la mise en œuvre de cert-manager avec Let's Encrypt. #### Le certificat n'est pas généré ##### Symptômes * Le `Secret` TLS n'existe pas après plusieurs minutes * Aucun événement `CreateCertificate` dans l'`Ingress` * Le `Pod` cert-manager redémarre en boucle ##### Diagnostic ```bash [Terminal] # Vérifier l'état du Certificate kubectl get certificate -n letsencrypt-example kubectl describe certificate nginx-tls -n letsencrypt-example # Vérifier les CertificateRequest kubectl get certificaterequest -n letsencrypt-example kubectl describe certificaterequest -n letsencrypt-example # Vérifier les logs cert-manager kubectl logs -n cert-manager -l app=cert-manager --tail=100 # Vérifier que les CRDs sont installées kubectl get crd | grep cert-manager ``` ##### Solutions 1. **CRDs manquantes** : Réinstallez cert-manager avec `installCRDs: true` 2. **Annotation incorrecte** : Vérifiez `cert-manager.io/cluster-issuer` (et non `cert-manager.io/issuer` pour un `ClusterIssuer`) 3. **ClusterIssuer non prêt** : `kubectl get clusterissuer` doit afficher `Ready: True` 4. **Problème RBAC** : cert-manager n'a peut-être pas les droits nécessaires #### Challenge HTTP01 échoue ##### Symptômes * Le challenge reste en état `Pending` ou `Processing` * Erreur dans les événements : `Waiting for HTTP-01 challenge propagation` * Let's Encrypt ne peut pas valider le domaine ##### Diagnostic ```bash [Terminal] # Voir l'état des challenges kubectl get challenge -n letsencrypt-example kubectl describe challenge -n letsencrypt-example # Vérifier que le pod solver est créé kubectl get pods -n letsencrypt-example # Tester l'accès au challenge depuis l'extérieur curl -I http://[FQDN]/.well-known/acme-challenge/test # Vérifier les logs du webhook kubectl logs -n cert-manager -l app=cert-manager-webhook ``` ##### Solutions 1. **DNS mal configuré** : Vérifiez que le FQDN pointe bien vers l'IP publique de l'Ingress ```bash [Terminal] nslookup [FQDN] dig [FQDN] +short ``` 2. **Port 80 non accessible** : Assurez-vous que le port 80 est ouvert depuis Internet ```bash [Terminal] curl -I http://[FQDN] telnet [FQDN] 80 ``` 3. **Ingress Controller ne route pas le trafic** : Vérifiez la configuration de l'Ingress Controller ```bash [Terminal] kubectl get ingress -A kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx ``` 4. **Firewall/WAF bloque Let's Encrypt** : Let's Encrypt utilise des IPs publiques variables. Vérifiez les règles de pare-feu. #### Rate limit atteint (production) ##### Symptômes * Erreur : `too many certificates already issued` * Impossible de générer un nouveau certificat * Erreur `429 Too Many Requests` dans les logs ##### Diagnostic ```bash [Terminal] kubectl describe certificaterequest -n letsencrypt-example | grep -i "rate limit" kubectl logs -n cert-manager -l app=cert-manager | grep -i "rate limit" ``` ##### Solutions 1. **Attendre** : Les limites Let's Encrypt sont temporaires (1 semaine) 2. **Utiliser staging** : Basculez temporairement vers le `ClusterIssuer` staging 3. **Consulter les limites** : [Documentation Let's Encrypt Rate Limits](https://letsencrypt.org/docs/rate-limits/) 4. **Réutiliser les certificats** : Ne supprimez pas les `Secrets` TLS inutilement Les limites principales : * 50 certificats/domaine enregistré/semaine * 5 certificats/FQDN/semaine (renouvellements inclus) * 300 comptes/IP/3 heures #### Certificat expiré ou non renouvelé ##### Symptômes * Erreur SSL dans le navigateur : `NET::ERR_CERT_DATE_INVALID` * Le certificat a dépassé sa date d'expiration * Aucun événement de renouvellement dans les logs ##### Diagnostic ```bash [Terminal] # Vérifier la date d'expiration kubectl get secret nginx-tls -n letsencrypt-example -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -dates # Vérifier l'état du Certificate kubectl get certificate nginx-tls -n letsencrypt-example -o yaml | grep -A5 "status:" # Vérifier les logs de renouvellement kubectl logs -n cert-manager -l app=cert-manager | grep -i "renew" ``` ##### Solutions 1. **Forcer le renouvellement** : Supprimez le Secret pour déclencher une nouvelle génération ```bash [Terminal] kubectl delete secret nginx-tls -n letsencrypt-example ``` cert-manager recréera automatiquement le certificat. 2. **Vérifier la configuration du Certificate** : Le renouvellement automatique se fait 30 jours avant expiration ```bash [Terminal] kubectl get certificate nginx-tls -n letsencrypt-example -o yaml ``` 3. **Problème de webhook** : Redémarrez le webhook cert-manager ```bash [Terminal] kubectl rollout restart deployment cert-manager-webhook -n cert-manager ``` #### Erreur "self-signed certificate in certificate chain" ##### Symptômes * Erreur lors d'opérations kubectl ou curl : `x509: certificate signed by unknown authority` * Le certificat staging est utilisé alors que production était demandé ##### Solutions 1. **Vérifier l'annotation de l'Ingress** : Assurez-vous d'utiliser `cert-manager-clusterissuer-http01-production` 2. **Vider le cache du Secret** : Supprimez et recréez le certificat 3. **Vérifier la chaîne du certificat** : ```bash [Terminal] kubectl get secret nginx-tls -n letsencrypt-example -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -text -noout | grep "Issuer" ``` ### Bonnes pratiques #### Gestion des certificats ##### Utiliser des `ClusterIssuers` Privilégiez `ClusterIssuer` plutôt que `Issuer` pour une configuration centralisée et réutilisable dans tous les namespaces. ```yaml showLineNumbers # ✅ Recommandé cert-manager.io/cluster-issuer: cert-manager-clusterissuer-http01-production # ⚠️ Moins flexible cert-manager.io/issuer: letsencrypt-production ``` ##### Toujours tester en staging Validez votre configuration avec le `ClusterIssuer` de staging avant de passer en production pour éviter d'atteindre les rate limits. ```yaml showLineNumbers # Phase 1 : Validation cert-manager.io/cluster-issuer: cert-manager-clusterissuer-http01-staging # Phase 2 : Production cert-manager.io/cluster-issuer: cert-manager-clusterissuer-http01-production ``` ##### Sauvegarder la clé privée ACME La clé privée du compte ACME (stockée dans le Secret référencé par `privateKeySecretRef`) est critique. Incluez-la dans vos sauvegardes Velero : ```yaml showLineNumbers [clusterissuer.yaml] spec: acme: privateKeySecretRef: name: letsencrypt-production-account-key # À sauvegarder ! ``` #### Sécurité ##### Limiter l'accès aux Secrets TLS Les Secrets TLS contiennent des clés privées sensibles. Appliquez des RBAC stricts : ```yaml showLineNumbers [rbac.yaml] apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: cert-viewer namespace: letsencrypt-example rules: - apiGroups: [""] resources: ["secrets"] resourceNames: ["nginx-tls"] verbs: ["get", "list"] ``` ##### Restreindre les domaines Dans un environnement multi-tenant, limitez les domaines pouvant être demandés via cert-manager pour éviter les abus. ##### Activer les notifications Configurez un email valide dans le `ClusterIssuer` pour recevoir les alertes Let's Encrypt (expiration, problèmes de renouvellement). ```yaml showLineNumbers spec: acme: email: devops@votreentreprise.fr # Email d'alerte ``` #### Performance et fiabilité ##### Définir des ressources pour cert-manager Assurez des `requests` et `limits` appropriées pour éviter l'éviction des pods cert-manager : ```yaml showLineNumbers [values.yaml] resources: requests: cpu: 20m memory: 64Mi limits: cpu: 200m memory: 256Mi ``` ##### Superviser cert-manager Activez Prometheus pour monitorer cert-manager : ```yaml showLineNumbers [values.yaml] prometheus: enabled: true servicemonitor: enabled: true ``` Métriques clés à surveiller : * `certmanager_certificate_expiration_timestamp_seconds` : Date d'expiration des certificats * `certmanager_certificate_ready_status` : État des certificats * `certmanager_http_acme_client_request_count` : Nombre de requêtes ACME ##### Configurer des alertes Créez des alertes Prometheus pour anticiper les problèmes : ```yaml showLineNumbers [prometheus-rule.yaml] - alert: CertificateExpirationSoon expr: (certmanager_certificate_expiration_timestamp_seconds - time()) < (7 * 24 * 3600) annotations: summary: "Certificat expire dans moins de 7 jours" ``` #### Architecture ##### Séparer les environnements Utilisez des `Namespaces` distincts pour dev, staging et production : ```yaml showLineNumbers # dev : certificats staging cert-manager.io/cluster-issuer: cert-manager-clusterissuer-http01-staging # prod : certificats production cert-manager.io/cluster-issuer: cert-manager-clusterissuer-http01-production ``` ##### Documenter les domaines Maintenez un inventaire des certificats et domaines gérés : ```yaml showLineNumbers metadata: annotations: description: "Certificat TLS pour l'application Ghost" owner: "equipe-devops" expires: "Auto-renew 30j avant expiration" ``` ### Renouvellement automatique cert-manager gère automatiquement le renouvellement des certificats Let's Encrypt. Voici comment fonctionne ce mécanisme. #### Principe * Les certificats Let's Encrypt ont une **validité de 90 jours** * cert-manager les renouvelle automatiquement **30 jours avant expiration** * Le renouvellement est transparent : aucune interruption de service #### Vérifier l'échéance de renouvellement ```bash [Terminal] # Afficher la date d'expiration kubectl get certificate nginx-tls -n letsencrypt-example -o jsonpath='{.status.notAfter}' # Calculer les jours restants kubectl get secret nginx-tls -n letsencrypt-example -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -enddate ``` #### Forcer un renouvellement manuel Si nécessaire, déclenchez manuellement un renouvellement : ```bash [Terminal] # Méthode 1 : Supprimer le Secret (cert-manager le recréera) kubectl delete secret nginx-tls -n letsencrypt-example # Méthode 2 : Annoter le Certificate pour forcer le renouvellement kubectl annotate certificate nginx-tls -n letsencrypt-example cert-manager.io/issue-temporary-certificate="true" --overwrite ``` #### Surveiller les renouvellements Consultez les logs pour vérifier les renouvellements : ```bash [Terminal] kubectl logs -n cert-manager -l app=cert-manager | grep -i "renew\|issued" ``` Événements attendus : ``` Issuing certificate as Secret does not contain a certificate Certificate issued successfully ``` ### Nettoyage et désinstallation #### Supprimer l'application exemple ```bash [Terminal] # Supprimer toutes les ressources du namespace kubectl delete namespace letsencrypt-example # Ou supprimer ressource par ressource kubectl delete ingress nginx-ingress -n letsencrypt-example kubectl delete service nginx-svc -n letsencrypt-example kubectl delete deployment nginx -n letsencrypt-example kubectl delete configmap nginx-html -n letsencrypt-example kubectl delete secret nginx-tls -n letsencrypt-example ``` #### Supprimer les `ClusterIssuers` (optionnel) :::warning **Attention**\ Ne supprimez les ClusterIssuers que si vous êtes certain qu'aucun autre projet ne les utilise. ::: ```bash [Terminal] kubectl delete clusterissuer cert-manager-clusterissuer-http01-staging kubectl delete clusterissuer cert-manager-clusterissuer-http01-production ``` #### Désinstaller cert-manager ```bash [Terminal] # Désinstaller via Helm helm uninstall cert-manager -n cert-manager # Supprimer les CRDs (attention : supprime toutes les ressources Certificate/Issuer) kubectl delete crd certificates.cert-manager.io kubectl delete crd certificaterequests.cert-manager.io kubectl delete crd challenges.cert-manager.io kubectl delete crd clusterissuers.cert-manager.io kubectl delete crd issuers.cert-manager.io kubectl delete crd orders.cert-manager.io # Supprimer le namespace kubectl delete namespace cert-manager ``` :::danger **Sauvegardez avant de désinstaller**\ Avant de supprimer cert-manager, sauvegardez vos Secrets TLS et la clé privée ACME pour éviter de perdre l'historique de vos certificats. ::: ### Ressources complémentaires * [Documentation officielle cert-manager](https://cert-manager.io/docs/) * [Let's Encrypt - Documentation](https://letsencrypt.org/docs/) * [Rate Limits Let's Encrypt](https://letsencrypt.org/docs/rate-limits/) * [Chart Helm cert-manager](https://artifacthub.io/packages/helm/cert-manager/cert-manager) * [Guide Ingress HAProxy sur SdV](/guides/ingress) * [Guide cert-manager DNS01](/guides/cert-manager/challenge-dns) ### Conclusion Vous disposez désormais d'une configuration complète pour générer automatiquement des certificats TLS Let's Encrypt sur votre cluster Kubernetes SdV via cert-manager et la méthode de validation HTTP01. Points clés à retenir : * Testez toujours en **staging** avant la production * Utilisez des **`ClusterIssuers`** pour mutualiser la configuration * Surveillez les **renouvellements automatiques** (30 jours avant expiration) * Attention aux **rate limits** Let's Encrypt en production * Sauvegardez les **`Secrets` TLS** et la **clé ACME** Pour des besoins avancés (certificats wildcard, Ingress privé), consultez le [guide DNS01](/guides/cert-manager/challenge-dns.md). ## Gestion des `Kubeconfigs` À la livraison de votre cluster Kubernetes, SdV fournit un fichier `kubeconfig` d'administration. Ce dernier dispose de **toutes les permissions sur le cluster** (droits `cluster-admin`) et présente un risque de sécurité majeur s'il est intégré directement dans vos pipelines CI/CD ou utilisé par vos applications. :::warning **Risques du kubeconfig admin** * **Privilèges excessifs** : accès total au cluster, y compris suppression de ressources critiques * **Surface d'attaque** : compromission d'un pipeline = compromission du cluster entier * **Traçabilité limitée** : opérations difficiles à attribuer à un workload spécifique * **Non-conformité** : violation du principe du moindre privilège (least privilege) ::: SdV recommande vivement de **générer des fichiers `kubeconfig` dédiés** pour chacun de vos workloads, en appliquant le principe du moindre privilège pour sécuriser vos déploiements. ### Validité des credentials Le `kubeconfig` d'administration fourni par SdV dispose d'une **validité limitée**. Selon le type d'authentification configuré : | Méthode d'authentification | Durée de validité typique | Rotation requise | | -------------------------- | --------------------------------------------- | ---------------------------------- | | Certificat client x509 | 365 jours | Manuelle | | Token ServiceAccount | Illimitée (legacy) ou 1 an (TokenRequest API) | Automatique avec projected volumes | | OIDC (Keycloak, etc.) | Selon le provider (ex: 24h) | Automatique | :::tip Pour vérifier la date d'expiration d'un certificat client dans votre kubeconfig : ```bash [Terminal] # Extraire le certificat client kubectl config view --raw -o jsonpath='{.users[0].user.client-certificate-data}' | base64 -d > /tmp/client.crt # Vérifier la validité openssl x509 -in /tmp/client.crt -noout -dates ``` ::: Pour plus de détails sur la gestion des permissions au sein de votre cluster Kubernetes, référez-vous au [guide dédié à la gestion des droits RBAC](/guides/rbac). ### Anatomie d'un fichier kubeconfig Un fichier `kubeconfig` est un fichier YAML structuré contenant trois sections principales : ```yaml showLineNumbers [kubeconfig-example.yaml] apiVersion: v1 kind: Config preferences: {} # 1. Clusters : endpoints API server clusters: - name: sdv-cluster-prod cluster: server: https://api.k8s.example.com:6443 certificate-authority-data: LS0tLS1CRUdJTi... # CA du cluster # 2. Users : credentials d'authentification users: - name: gitlab-deployer user: token: eyJhbGciOiJSUzI1NiIsImtpZCI6... # Token ServiceAccount # OU client-certificate-data: LS0tLS1CRUdJTi... client-key-data: LS0tLS1CRUdJTi... # 3. Contexts : association cluster + user + namespace contexts: - name: sdv-prod-deployer context: cluster: sdv-cluster-prod user: gitlab-deployer namespace: production # namespace par défaut # Contexte actif par défaut current-context: sdv-prod-deployer ``` :::info **Stockage du kubeconfig**\ Par défaut, `kubectl` recherche le fichier dans `~/.kube/config`. Pour utiliser un fichier alternatif : ```bash export KUBECONFIG=/path/to/custom-kubeconfig.yaml # ou kubectl --kubeconfig=/path/to/custom-kubeconfig.yaml get pods ``` ::: ### Générer un kubeconfig pour un workload La génération d'un kubeconfig dédié repose sur la création d'un `ServiceAccount` associé à un `Role` ou `ClusterRole` via un `RoleBinding` ou `ClusterRoleBinding`. ::::steps #### Créer un `Namespace` dédié Isolez vos applications dans des `Namespaces` dédiés pour faciliter la gestion RBAC et la segmentation des ressources : ```bash [Terminal] kubectl create namespace production ``` #### Créer un `ServiceAccount` Le `ServiceAccount` représente l'identité technique de votre workload (pipeline CI/CD, application, opérateur...) : ```yaml showLineNumbers [serviceaccount.yaml] apiVersion: v1 kind: ServiceAccount metadata: name: gitlab-deployer namespace: production labels: app.kubernetes.io/name: gitlab-deployer app.kubernetes.io/managed-by: kubectl ``` ```bash [Terminal] kubectl apply -f serviceaccount.yaml ``` #### Définir les permissions RBAC Créez un `Role` (namespacé) ou `ClusterRole` (cluster-wide) avec uniquement les permissions nécessaires au workload. **Exemple 1 : Permissions de déploiement dans un namespace** ```yaml showLineNumbers [role-deployer.yaml] apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: deployer namespace: production rules: # Gestion des Deployments, StatefulSets, DaemonSets - apiGroups: ["apps"] resources: ["deployments", "statefulsets", "daemonsets"] verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] # Gestion des ReplicaSets (créés par les Deployments) - apiGroups: ["apps"] resources: ["replicasets"] verbs: ["get", "list", "watch"] # Gestion des Pods (lecture seule pour debug) - apiGroups: [""] resources: ["pods", "pods/log"] verbs: ["get", "list", "watch"] # Gestion des Services - apiGroups: [""] resources: ["services"] verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] # Gestion des ConfigMaps et Secrets - apiGroups: [""] resources: ["configmaps", "secrets"] verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] # Gestion des Ingress - apiGroups: ["networking.k8s.io"] resources: ["ingresses"] verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] ``` **Exemple 2 : Permissions lecture seule sur plusieurs namespaces** ```yaml showLineNumbers [clusterrole-readonly.yaml] apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: monitoring-reader rules: - apiGroups: [""] resources: ["pods", "services", "endpoints", "nodes"] verbs: ["get", "list", "watch"] - apiGroups: ["apps"] resources: ["deployments", "statefulsets", "daemonsets"] verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["pods/log"] verbs: ["get", "list"] ``` ```bash [Terminal] kubectl apply -f role-deployer.yaml # ou pour le ClusterRole kubectl apply -f clusterrole-readonly.yaml ``` :::tip **Permissions minimales recommandées**\ Commencez toujours avec le minimum de permissions, puis ajoutez au besoin. Pour un déploiement standard : * `deployments` : `get`, `list`, `watch`, `create`, `update`, `patch` (évitez `delete` si possible) * `pods` : `get`, `list`, `watch` uniquement (lecture seule pour debug) * `pods/log` : `get`, `list` (lecture des logs) * `services`, `configmaps`, `ingresses` : selon les besoins réels ::: #### Lier le `ServiceAccount` au `Role` Utilisez un `RoleBinding` (pour un `Role` namespacé) ou `ClusterRoleBinding` (pour un `ClusterRole`) : **Binding namespacé :** ```yaml showLineNumbers [rolebinding.yaml] apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: gitlab-deployer-binding namespace: production subjects: - kind: ServiceAccount name: gitlab-deployer namespace: production roleRef: kind: Role name: deployer apiGroup: rbac.authorization.k8s.io ``` **Binding cluster-wide :** ```yaml showLineNumbers [clusterrolebinding.yaml] apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: monitoring-reader-binding subjects: - kind: ServiceAccount name: prometheus namespace: monitoring roleRef: kind: ClusterRole name: monitoring-reader apiGroup: rbac.authorization.k8s.io ``` ```bash [Terminal] kubectl apply -f rolebinding.yaml ``` #### Récupérer le token du ServiceAccount Depuis Kubernetes 1.24, les tokens de `ServiceAccount` ne sont plus automatiquement créés. Deux méthodes sont disponibles : **Méthode 1 : Créer un `Secret` manuellement (token longue durée)** ```yaml showLineNumbers [serviceaccount-token.yaml] apiVersion: v1 kind: Secret metadata: name: gitlab-deployer-token namespace: production annotations: kubernetes.io/service-account.name: gitlab-deployer type: kubernetes.io/service-account-token ``` ```bash [Terminal] kubectl apply -f serviceaccount-token.yaml # Récupérer le token TOKEN=$(kubectl get secret gitlab-deployer-token -n production -o jsonpath='{.data.token}' | base64 -d) echo $TOKEN ``` :::warning **Tokens longue durée**\ Les tokens créés via `Secret` sont **permanents** et ne tournent pas automatiquement. Ils constituent un risque de sécurité si compromis. Privilégiez la méthode 2 pour les environnements de production. ::: **Méthode 2 : Générer un token temporaire (recommandé)** ```bash [Terminal] # Token avec expiration (par défaut 1h, max 24h selon la config du cluster) TOKEN=$(kubectl create token gitlab-deployer -n production --duration=8760h) echo $TOKEN ``` :::tip **Méthode recommandée**\ Pour les pipelines CI/CD, privilégiez des tokens temporaires avec durée limitée (ex: 24h-168h) et régénérez-les automatiquement via des secrets managers (Vault, GitLab CI Variables avec rotation (premium), etc.). ::: #### Récupérer les informations du cluster ```bash [Terminal] # URL de l'API Server API_SERVER=$(kubectl config view --raw -o jsonpath='{.clusters[0].cluster.server}') # Certificat de l'autorité de certification du cluster CA_CERT=$(kubectl config view --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}') echo "API Server: $API_SERVER" ``` #### Générer le fichier `kubeconfig` Créez le fichier `kubeconfig` complet en combinant les informations récupérées : ```bash [Terminal] cat > kubeconfig-gitlab-deployer.yaml < ~/.kube/config-merged ``` ### Bonnes pratiques de sécurité #### Stockage des kubeconfigs :::warning **Ne committez JAMAIS** un `kubeconfig` dans un dépôt Git, même privé. Utilisez : * **GitLab CI/CD Variables** (type `File`, masked) * **GitHub Secrets** * **HashiCorp Vault** * **AWS Secrets Manager / Azure Key Vault / GCP Secret Manager** * **Sealed Secrets** pour les kubeconfigs chiffrés dans le cluster ::: #### Permissions minimales Appliquez le principe du moindre privilège : | Use Case | Ressources | Verbs recommandés | | ----------------------- | ---------------------------------------------------- | ----------------------------------------------- | | Pipeline de déploiement | `deployments`, `services`, `configmaps`, `ingresses` | `get`, `list`, `create`, `update`, `patch` | | Monitoring (Prometheus) | `nodes`, `pods`, `services`, `endpoints` | `get`, `list`, `watch` | | Backup (Velero) | Toutes ressources | `get`, `list`, `watch`, `create` (pour restore) | | Debug ponctuel | `pods`, `pods/log` | `get`, `list` | :::tip Évitez `delete` et `deletecollection` sauf si absolument nécessaire. Privilégiez des processus contrôlés (GitOps) pour les suppressions. ::: #### Rotation des credentials Automatisez la rotation des tokens : ```yaml showLineNumbers [gitlab-ci.yml] generate_token: stage: setup script: # Générer un token avec durée limitée - TOKEN=$(kubectl create token gitlab-deployer -n production --duration=168h) - echo "KUBE_TOKEN=$TOKEN" >> deploy.env artifacts: reports: dotenv: deploy.env deploy: stage: deploy script: # Utiliser le token fraîchement généré - kubectl --token=$KUBE_TOKEN apply -f manifests/ ``` #### Audit et traçabilité Activez l'audit des API calls dans le cluster pour tracer les actions effectuées par chaque `ServiceAccount` : ```yaml showLineNumbers [audit-policy.yaml] apiVersion: audit.k8s.io/v1 kind: Policy rules: # Log toutes les modifications (create, update, patch, delete) - level: RequestResponse verbs: ["create", "update", "patch", "delete"] # Log les accès en lecture aux secrets - level: Metadata resources: - group: "" resources: ["secrets"] verbs: ["get", "list", "watch"] ``` :::info L'activation de l'audit nécessite une configuration au niveau de l'API Server. Contactez SdV si vous souhaitez activer cette fonctionnalité. ::: #### `Namespaces` dédiés Isolez les `ServiceAccounts` par `Namespace` selon l'environnement ou l'application : ``` production/ ├── gitlab-deployer (droits d'écriture) └── prometheus (lecture seule) staging/ ├── gitlab-deployer (droits d'écriture) └── prometheus (lecture seule) monitoring/ └── prometheus (lecture cluster-wide) ``` ### Intégration CI/CD #### GitLab CI/CD Stockez le kubeconfig en tant que **File Variable** : ```yaml showLineNumbers [.gitlab-ci.yml] deploy: stage: deploy image: bitnami/kubectl:latest script: # La variable KUBECONFIG_FILE est injectée comme fichier - export KUBECONFIG=$KUBECONFIG_FILE - kubectl apply -f k8s/ - kubectl rollout status deployment/app -n production only: - main ``` #### GitHub Actions ```yaml showLineNumbers [deploy.yml] name: Deploy to Kubernetes on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup kubectl uses: azure/setup-kubectl@v3 - name: Deploy env: KUBECONFIG_CONTENT: ${{ secrets.KUBECONFIG }} run: | echo "$KUBECONFIG_CONTENT" > /tmp/kubeconfig export KUBECONFIG=/tmp/kubeconfig kubectl apply -f k8s/ kubectl rollout status deployment/app -n production ``` #### Jenkins ```groovy [Jenkinsfile] pipeline { agent any environment { KUBECONFIG = credentials('kubeconfig-prod') } stages { stage('Deploy') { steps { sh 'kubectl apply -f k8s/' sh 'kubectl rollout status deployment/app -n production' } } } } ``` ### Dépannage #### Erreur "The connection to the server was refused" Vérifiez l'URL de l'API Server : ```bash [Terminal] kubectl config view -o jsonpath='{.clusters[0].cluster.server}' # Testez la connectivité curl -k https://api.k8s.example.com:6443/version ``` #### Erreur "x509: certificate signed by unknown authority" Le certificat CA du cluster est manquant ou invalide : ```bash [Terminal] # Vérifier la présence du CA kubectl config view --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}' | base64 -d | openssl x509 -noout -text # Alternative : désactiver la vérification TLS (NON RECOMMANDÉ en production) kubectl --insecure-skip-tls-verify=true get nodes ``` #### Erreur "User 'system\:serviceaccount:...' cannot get resource" Le ServiceAccount ne dispose pas des permissions RBAC nécessaires : ```bash [Terminal] # Vérifier les permissions actuelles kubectl auth can-i get pods --as=system:serviceaccount:production:gitlab-deployer -n production # Lister toutes les permissions kubectl auth can-i --list --as=system:serviceaccount:production:gitlab-deployer -n production # Vérifier les RoleBindings kubectl get rolebinding -n production kubectl describe rolebinding gitlab-deployer-binding -n production ``` #### Token expiré Si vous utilisez un token temporaire (méthode 2), régénérez-le : ```bash [Terminal] # Vérifier l'expiration du token (décode le JWT) echo $TOKEN | cut -d'.' -f2 | base64 -d 2>/dev/null | jq .exp # Régénérer un nouveau token kubectl create token gitlab-deployer -n production --duration=168h ``` ### Alternatives et outils complémentaires #### `kubelogin` (OIDC) Pour les clusters configurés avec un provider OIDC (Keycloak, Azure AD, Okta) : ```bash [Terminal] kubectl oidc-login setup \ --oidc-issuer-url=https://keycloak.example.com/auth/realms/kubernetes \ --oidc-client-id=kubernetes \ --oidc-client-secret=xxx ``` Le kubeconfig utilisera alors l'authentification OIDC avec refresh automatique des tokens. Référez-vous au [guide dédié à l'OIDC dans Kubernetes](/guides/oidc) pour paramétrer votre cluster. #### kubie Outil pour gérer facilement plusieurs contextes kubeconfig : ```bash [Terminal] # Installation brew install kubie # Lancer un shell avec un contexte isolé kubie ctx sdv-prod-deployer # Dans ce shell, toutes les commandes kubectl utilisent le bon contexte kubectl get pods ``` #### k9s Interface TUI (Text User Interface) pour naviguer dans Kubernetes : ```bash [Terminal] # Installation brew install k9s # Lancer avec un kubeconfig spécifique k9s --kubeconfig=/path/to/kubeconfig.yaml ``` ### Checklist finale Avant de déployer un kubeconfig en production : * [ ] `ServiceAccount` créé dans le namespace approprié * [ ] `Role`/`ClusterRole` avec permissions minimales strictes * [ ] `RoleBinding`/`ClusterRoleBinding` configurés correctement * [ ] Token généré avec durée de validité adaptée * [ ] Kubeconfig testé avec `kubectl auth can-i` * [ ] Kubeconfig stocké de manière sécurisée (secret manager) * [ ] Jamais committé dans Git * [ ] Process de rotation documenté * [ ] Permissions auditées et validées par l'équipe sécurité * [ ] `Namespace` par défaut configuré si nécessaire ### Documentation complémentaire * [Guide RBAC](/guides/rbac) : gestion avancée des permissions * [Documentation Kubernetes officielle sur les `ServiceAccounts`](https://kubernetes.io/docs/concepts/security/service-accounts/) * [Documentation kubectl config](https://kubernetes.io/docs/reference/kubectl/cheatsheet/#kubectl-context-and-configuration) ## Gestion des logs Kubernetes La gestion des logs est un enjeu central pour l'exploitation, le support et la sécurité des applications déployées sur Kubernetes. Cette page détaille les mécanismes de collecte, de centralisation, d'archivage et d'accès aux logs dans un cluster Kubernetes SdV. ### Tableau récapitulatif des destinations de logs | Destination | Annotation | Cas d’usage principaux | Accès / Visualisation | | ------------- | ------------------------- | ---------------------------------- | -------------------------- | | ElasticSearch | `sdv/logging: elastic` | Recherche, corrélation, alerting | Kibana, Grafana, API | | S3 | `sdv/logging: s3` | Archivage long terme, conformité | Accès S3, outils d’audit | | TCP | `sdv/logging: tcp` | Intégration SIEM, logstash, syslog | Stack externe (SIEM, etc.) | | Elastic+S3 | `sdv/logging: elastic,s3` | Mix temps réel + archivage | Kibana + S3 | ### Accès direct aux logs Kubernetes permet d'accéder aux logs des pods en temps réel via la commande suivante : ```shell [Terminal] kubectl -n logs [-c ] ``` Exemple : ```shell [Terminal] kubectl -n mon-namespace logs mon-container-8xdp9 ``` :::tip **Limite :** Cette méthode devient rapidement fastidieuse lorsque le nombre de pods ou de namespaces augmente, ou pour des analyses transverses. ::: ### Centralisation vers ElasticSearch #### Description fonctionnelle La centralisation des logs dans ElasticSearch permet : * d’effectuer des recherches avancées et des corrélations sur l’ensemble des applications, * de visualiser les logs via Kibana ou Grafana, * de mettre en place des alertes sur des patterns d’erreur ou de sécurité. #### Description technique * Le serveur ElasticSearch peut être interne ou externe au cluster. * Le paramétrage de la destination ElasticSearch est réalisé par SdV au niveau du cluster (non modifiable par l’utilisateur). * Une seule destination ElasticSearch est possible par cluster. * ElasticSearch 7.x+ est recommandé (gestion native de la rétention via les Lifecycle Policies). #### Cas d’usage * Analyse d’incidents multi-applications * Recherche de logs sur une période donnée * Détection d’anomalies ou de comportements suspects #### Impacts opérationnels * Les logs sont accessibles même après la suppression des pods * Possibilité de requêtes complexes (full-text, agrégations) #### Activation (annotation) L’activation se fait via une annotation sur le pod ou le template de déploiement : ```yaml showLineNumbers annotations: sdv/logging: elastic # log vers ElasticSearch ``` Par exemple pour envoyer les logs `nginx` vers ElasticSearch : ```yaml showLineNumbers [deployment.yaml] apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment namespace: mon-namespace spec: replicas: 2 selector: matchLabels: app: nginx template: metadata: labels: app: nginx annotations: sdv/logging: elastic spec: containers: - name: nginx image: nginx:stable ports: - containerPort: 80 ``` ### Archivage dans S3 #### Description fonctionnelle L’archivage des logs dans un stockage S3 permet : * de conserver les logs sur une durée longue (compliance, audits, forensics), * de réduire la charge sur ElasticSearch, * de restaurer des historiques en cas d’incident. #### Description technique * Le stockage S3 peut être interne (SdV) ou externe (compatible API S3). * Le paramétrage de la destination S3 est réalisé par SdV au niveau du cluster. * Une seule destination S3 est possible par cluster. * La rétention peut être personnalisée par annotation (en jours). #### Cas d’usage * Archivage légal ou réglementaire * Analyse post-mortem d’incidents * Conservation de logs applicatifs critiques #### Impacts opérationnels * Les logs archivés sont compressés et organisés par rétention, date, namespace. * L’accès nécessite des droits sur le bucket S3 cible. #### Activation (annotation) L’activation se fait via une annotation sur le pod ou le template de déploiement : ```yaml showLineNumbers annotations: sdv/logging: s3 # demande l'archivage sur le S3 sdv/s3-logging-retention: "14" # rétention spécifique de 14 jours ``` :::tip Une rétention par défaut est définie au niveau du cluster par SdV. ::: Par exemple pour garder les logs `nginx` sur 1 an : ```yaml showLineNumbers [deployment.yaml] apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment namespace: mon-namespace spec: replicas: 2 selector: matchLabels: app: nginx template: metadata: labels: app: nginx annotations: sdv/logging: s3 sdv/s3-logging-retention: "365" spec: containers: - name: nginx image: nginx:stable ports: - containerPort: 80 ``` Ou via `kubectl` : ```shell [Terminal] kubectl annotate pods mon-pod sdv/logging='s3' sdv/s3-logging-retention='365' ``` :::tip Pour que les changements dans les annotations soit pris en compte, il faut redémarrer le ou les pods modifiés. ```bash [Terminal] kubectl rollout restart deploy/mon-deploiement ``` ::: #### A propos de la rétention :::note La rétention n'est qu'indicative. Le système va déposer les logs dans un sous-dossier du bucket S3. Le nom du sous-dossier est la rétention. Demandez à votre fournisseur de stockage S3 d'ajouter un traitement d'épuration automatisé selon cette règle. ::: L'organisation des logs dans le bucket S3 est de la forme suivante : ```raw ///-.log.gz ``` Par exemple : ```raw 3/2020-01-14/emojivoto/1579014033-416693c4-3b06-4599-88dc-6af8a5c6b549.log.gz 3/2020-01-14/emojivoto/1579017634-ec2b7dbf-6dc5-4949-8e87-2b5d20b3c802.log.gz 3/2020-01-14/emojivoto/1579021235-1c39facf-8452-456c-b691-7bb6624edf71.log.gz 7/2020-01-14/sdv-logging/1579014043-c17c10fb-4ada-40f5-b5c7-74436cd5115b.log.gz 7/2020-01-14/sdv-logging/1579017675-8a0c8669-a2d4-4969-9866-727a0c06adc1.log.gz 7/2020-01-14/sdv-logging/1579021288-45efb2ee-ec42-4e99-aab7-bfbb2f314d72.log.gz ``` ### Externalisation en TCP #### Description fonctionnelle L’externalisation des logs en TCP permet : * d’envoyer les flux de logs vers une stack de traitement externe (ex : Logstash, SIEM, syslog-ng), * d’intégrer Kubernetes dans une infrastructure de supervision/logging existante. #### Description technique * Le paramétrage de la destination TCP est réalisé par SdV au niveau du cluster. * Une seule destination TCP est possible par cluster. * Le format d’envoi est généralement du JSON ou du texte brut. #### Cas d’usage * Agrégation de logs multi-clusters * Intégration avec des outils de sécurité (SIEM) * Traitement temps réel (alerting custom, parsing avancé) #### Impacts opérationnels * Les logs sont transmis en temps réel à la destination TCP * Dépendance à la disponibilité du collecteur externe #### Activation (annotation) L’activation se fait via une annotation sur le pod ou le template de déploiement : ```yaml showLineNumbers annotations: sdv/logging: tcp # log vers destination TCP ``` Par exemple pour envoyer les logs `nginx` vers ElasticSearch : ```yaml showLineNumbers [deployment.yaml] apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment namespace: mon-namespace spec: replicas: 2 selector: matchLabels: app: nginx template: metadata: labels: app: nginx annotations: sdv/logging: tcp spec: containers: - name: nginx image: nginx:stable ports: - containerPort: 80 ``` ### Destinations multiples #### Description fonctionnelle Il est possible d’envoyer les logs d’un même pod vers plusieurs destinations simultanément (ElasticSearch, S3, TCP). #### Cas d’usage * Archivage long terme + recherche temps réel * Migration progressive d’une solution de logs à une autre #### Impacts opérationnels * Attention à la volumétrie générée (coût, bande passante) * Les destinations sont indépendantes (une panne n’affecte pas les autres) #### Activation (annotation) Lister les destinations dans l’annotation `sdv/logging`, séparées par une virgule : ```yaml showLineNumbers [deployment.yaml] apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment namespace: mon-namespace spec: replicas: 2 selector: matchLabels: app: nginx template: metadata: labels: app: nginx annotations: sdv/logging: elastic,s3 spec: containers: - name: nginx image: nginx:stable ports: - containerPort: 80 ``` ### Limitations techniques En raison de la représentation interne des logs dans [vector](https://vector.dev/), les `.` dans les clés des metadonnées Kubernetes sont remplacés par des `_`. Exemple : ```json "annotations": { "cni_projectcalico_org/podIP": "10.42.3.38/32" } ``` ### Bonnes pratiques * Toujours définir explicitement la rétention souhaitée pour l’archivage S3. * Utiliser des labels et annotations cohérents pour faciliter la recherche et la corrélation. * Documenter les destinations de logs dans vos chartes d’exploitation. * Surveiller la volumétrie générée par la centralisation et l’archivage. * Tester la restauration/l’accès aux logs archivés régulièrement. ### Attention * Les modifications d’annotations nécessitent un redémarrage des pods pour être prises en compte. * Une mauvaise configuration des destinations peut entraîner une perte de logs. * La rétention S3 n’est pas une suppression automatique : il faut configurer un lifecycle policy côté bucket. ### Notes d’exploitation * Pour des besoins spécifiques (compliance, sécurité), rapprochez-vous du support SdV pour valider la stratégie de logs. * En cas de volumétrie importante, privilégier l’archivage S3 et limiter la rétention ElasticSearch. ## Observabilité et Alertes ### Vue d'ensemble Chaque cluster Kubernetes SdV est livré avec une stack de monitoring et d'alerting complète, opérationnelle dès la mise en service. Cette infrastructure repose sur des outils standards de l'écosystème Cloud Native et permet une supervision continue de la santé du cluster et de vos applications. #### Architecture de la stack de monitoring La solution de monitoring déployée sur les clusters SdV intègre les composants suivants : | Composant | Rôle | Fonctionnalités clés | | ----------------------- | --------------------------------- | -------------------------------------------------------------------------------- | | **Prometheus** | Collecte et stockage de métriques | Scraping automatique, time-series database, PromQL, règles d'alerte | | **Prometheus Operator** | Gestion déclarative de Prometheus | Déploiement et configuration via CRDs (`ServiceMonitor`, `PrometheusRule`, etc.) | | **Grafana** | Visualisation des données | Dashboards interactifs, requêtes PromQL, alerting visuel | | **Alertmanager** | Gestion centralisée des alertes | Routage, groupement, déduplication, silencing, notifications multi-canaux | | **Node Exporter** | Export de métriques système | Métriques CPU, RAM, disque, réseau des nœuds | | **Kube State Metrics** | Export de métriques Kubernetes | État des ressources K8s (`Pods`, `Deployments`, `Services`, etc.) | :::info **Accès aux interfaces web**\ Toutes les interfaces de monitoring sont accessibles uniquement via le **VPN** et utilisent le domaine privé `*.internal..sdv.fr`.\ Aucune authentification n'est requise par défaut, la sécurité reposant sur l'accès réseau restreint. ::: #### Points d'accès | Interface | URL | Description | | ---------------- | ------------------------------------------------------- | ------------------------------------------------------ | | **Grafana** | `http://grafana.internal..sdv.fr/` | Tableaux de bord et visualisations | | **Prometheus** | `http://prometheus.internal..sdv.fr/` | Exploration des métriques (PromQL) et état du scraping | | **Alertmanager** | `http://alertmanager.internal..sdv.fr/` | Gestion des alertes actives, silencing, statut | :::tip Remplacez `` par l'identifiant réel de votre cluster (exemple : `sdv-k8n42c1`). ::: #### Métriques disponibles La stack de monitoring vous offre une visibilité complète sur : **Infrastructure physique :** * Consommation CPU, mémoire, disque, réseau par nœud * Charge système et disponibilité des nœuds (masters et workers) * Métriques matérielles détaillées (températures, erreurs hardware si supporté) **Ressources Kubernetes :** * État et santé des `Pods`, `Deployments`, `StatefulSets`, `DaemonSets` * Utilisation des ressources (requests/limits vs consommation réelle) * Événements Kubernetes (crashs, evictions, scheduling failures) * Quotas et limites par `Namespace` **Métriques applicatives :** * Exposition de métriques custom au format Prometheus via `/metrics` * Scraping automatique avec `ServiceMonitor` (voir section [Monitoring applicatif](#monitoring-applicatif)) * Métriques HTTP (latence, codes de réponse, throughput) si instrumentées ### Grafana **Grafana** est la plateforme de visualisation utilisée pour explorer les métriques collectées par Prometheus. Elle propose une interface web intuitive permettant de créer, consulter et partager des tableaux de bord personnalisés. #### Accès à Grafana | Paramètre | Valeur | | --------------------- | -------------------------------------------------- | | **URL** | `http://grafana.internal..sdv.fr/` | | **Authentification** | Aucune (accès VPN requis) | | **Source de données** | Prometheus (préconfigurée) | :::info Grafana est configuré en **mode lecture seule** pour les utilisateurs standards. Les modifications de dashboards nécessitent des privilèges administrateur.\ Pour toute demande de personnalisation de tableaux de bord, contactez l'équipe SdV. ::: #### Dashboards préconfigurés Les clusters SdV sont livrés avec un ensemble de dashboards couvrant les principaux aspects de supervision : | Dashboard | Description | Cas d'usage | | ----------------------- | -------------------------------------------------------- | ------------------------------------------------------------------- | | **Cluster Overview** | Vue synthétique du cluster (nœuds, pods, CPU, RAM) | Supervision globale, vérification rapide de l'état de santé | | **Node Details** | Métriques détaillées par nœud (CPU, RAM, disque, réseau) | Investigation sur un nœud spécifique, diagnostic de performance | | **Namespace Resources** | Consommation de ressources par `Namespace` | Suivi de la consommation par application ou environnement | | **Pod Resources** | Métriques détaillées par `Pod` | Debugging, analyse des performances applicatives | | **Ingress Traffic** | Statistiques de trafic HTTP/HTTPS via les `Ingress` | Analyse du trafic entrant, identification des pics de charge | | **Persistent Volumes** | État et utilisation du stockage persistant | Surveillance de la consommation de disque, prévention de saturation | :::tip Pour naviguer entre les dashboards, utilisez le menu latéral de Grafana ou la barre de recherche (`Ctrl+K`). ::: #### Configuration des dashboards Les configurations des dashboards Grafana sont stockées sous forme de `ConfigMaps` dans le `Namespace` `kube-system`. Cela permet un déploiement déclaratif et versionné. ##### Lister les ConfigMaps de dashboards ```shell [Terminal] kubectl -n kube-system get configmaps | grep grafana-dashboard ``` ##### Consulter un dashboard spécifique ```shell [Terminal] kubectl -n kube-system get cm sdv-monitoring-kube-promet-k8s-resources-cluster -o yaml ``` Exemple de contenu : ```yaml showLineNumbers [configmap.yaml] apiVersion: v1 kind: ConfigMap metadata: name: sdv-monitoring-kube-promet-k8s-resources-cluster namespace: kube-system labels: grafana_dashboard: "1" data: k8s-resources-cluster.json: | { "dashboard": { "title": "Kubernetes / Compute Resources / Cluster", "panels": [ ... ] } } ``` :::warning **Modification des dashboards**\ Toute modification directe des `ConfigMaps` sera écrasée lors des mises à jour du cluster. Pour des personnalisations persistantes, contactez l'équipe SdV, ou déployez votre instance de Grafana dédiée. ::: #### Bonnes pratiques Grafana ✅ **Recommandations :** * Utilisez les dashboards fournis comme point de départ avant de demander des personnalisations * Créez des vues personnalisées via les filtres de variables (namespace, pod, etc.) * Exploitez les timeranges dynamiques pour analyser des périodes spécifiques * Partagez des liens directs vers des graphiques spécifiques pour faciliter la communication avec les équipes ❌ **À éviter :** * Créer des dashboards manuellement (non persistants) * Multiplier les requêtes lourdes sur de longues périodes (impact performance Prometheus) * Ignorer les dashboards existants et recréer des vues redondantes ### Prometheus **Prometheus** est le cœur du système de monitoring. C'est une base de données de séries temporelles (time series database) qui collecte et stocke les métriques du cluster et de vos applications. #### Accès à Prometheus | Paramètre | Valeur | | -------------------- | ----------------------------------------------------------- | | **URL principale** | `http://prometheus.internal..sdv.fr/` | | **URL des règles** | `http://prometheus.internal..sdv.fr/rules` | | **URL des alertes** | `http://prometheus.internal..sdv.fr/alerts` | | **Authentification** | Aucune (accès VPN requis) | #### Fonctionnalités principales ##### 1. Exploration de métriques (PromQL) L'interface Prometheus permet d'exécuter des requêtes **PromQL** (Prometheus Query Language) pour explorer les métriques en temps réel. Exemples de requêtes utiles : ```promql # Utilisation CPU par pod sum(rate(container_cpu_usage_seconds_total[5m])) by (pod, namespace) # Mémoire utilisée par namespace sum(container_memory_usage_bytes{image!=""}) by (namespace) # Nombre de pods en état non-running count(kube_pod_status_phase{phase!="Running"}) by (namespace) # Requêtes HTTP par seconde sur un Ingress sum(rate(haproxy_frontend_http_requests_total[5m])) by (frontend) # Latence p95 d'une application histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) ``` :::tip L'interface Prometheus propose l'autocomplétion des métriques. Commencez à taper le nom d'une métrique et appuyez sur `Ctrl+Espace` pour voir les suggestions. ::: ##### 2. Visualisation des targets La page **Status > Targets** (`/targets`) affiche tous les endpoints scrapés par Prometheus, leur état de santé et la dernière collecte réussie. Cela permet de vérifier : * Que vos `ServiceMonitors` sont correctement détectés * Que les endpoints de métriques répondent correctement * Les éventuelles erreurs de scraping ##### 3. Règles d'alerte Prometheus permet de définir des **règles d'alerte** qui évaluent en continu des conditions PromQL. Lorsqu'une condition est remplie, une alerte est déclenchée et transmise à Alertmanager. ##### Consulter les règles actives Rendez-vous à l'adresse suivante pour voir toutes les règles configurées : ``` http://prometheus.internal..sdv.fr/rules ``` Cette page affiche : * **Recording rules** : règles qui pré-calculent des métriques coûteuses * **Alerting rules** : règles qui déclenchent des alertes ##### Stockage des règles Les règles Prometheus sont stockées dans des `ConfigMaps` dans le `Namespace` `kube-system` : ```shell [Terminal] kubectl -n kube-system get configmaps | grep prometheus-rulefiles ``` Exemple de consultation : ```shell [Terminal] kubectl -n kube-system get cm prometheus-sdv-monitoring-kube-promet-prometheus-rulefiles-0 -o yaml ``` Extrait d'une règle typique : ```yaml showLineNumbers [configmap.yaml] apiVersion: v1 kind: ConfigMap metadata: name: prometheus-sdv-monitoring-kube-promet-prometheus-rulefiles-0 namespace: kube-system data: rules.yaml: | groups: - name: kubernetes-resources interval: 30s rules: - alert: KubePodCrashLooping annotations: description: "Pod {{ $labels.namespace }}/{{ $labels.pod }} is crash looping" summary: "Pod is crash looping" expr: | rate(kube_pod_container_status_restarts_total[15m]) > 0 for: 15m labels: severity: warning - alert: KubeNodeNotReady annotations: description: "Node {{ $labels.node }} is not ready" summary: "Node is not ready" expr: | kube_node_status_condition{condition="Ready",status="true"} == 0 for: 5m labels: severity: critical ``` :::info **Personnalisation des règles**\ Il est possible de créer des règles d'alerte custom pour vos applications en utilisant l'objet CRD `PrometheusRule`. Voir la section [Alertes custom](#alertes-custom) pour plus de détails. ::: ##### Consulter les alertes actives La page **Alerts** affiche toutes les alertes en cours, leur état (pending, firing), et les labels associés : ``` http://prometheus.internal..sdv.fr/alerts ``` | État | Description | | ------------ | ------------------------------------------------------------- | | **Inactive** | La condition de l'alerte n'est pas remplie | | **Pending** | La condition est remplie mais le délai `for` n'est pas écoulé | | **Firing** | L'alerte est active et envoyée à Alertmanager | :::warning Les alertes visibles dans Prometheus ne sont **pas encore notifiées**. C'est Alertmanager qui gère le routage et l'envoi des notifications. ::: ### Alertmanager **Alertmanager** gère la distribution des alertes générées par Prometheus. Il assure le routage intelligent, le groupement, la déduplication, le silencing et l'envoi vers les différents canaux de notification (email, Slack, webhooks, PagerDuty, etc.). #### Accès à Alertmanager | Paramètre | Valeur | | | | ------------------ | --------------------------------------------------------------- | -------------------- | ------------------------- | | **URL principale** | `http://alertmanager.internal..sdv.fr/` | | | | **URL de statut** | `http://alertmanager.internal..sdv.fr/#/status` | **Authentification** | Aucune (accès VPN requis) | #### Fonctionnalités principales ##### 1. Vue des alertes actives L'interface Alertmanager affiche toutes les alertes en cours de traitement, avec leurs labels, annotations, et état de groupement. Vous pouvez : * Filtrer les alertes par labels (`namespace`, `severity`, etc.) * Voir l'historique de déclenchement * Appliquer des **silences** temporaires pour suspendre les notifications ##### 2. Silencing (mise en silence) Le **silencing** permet de suspendre temporairement les notifications pour une alerte spécifique ou un groupe d'alertes. Cas d'usage : * Maintenance planifiée d'un service * Investigation en cours d'un incident connu * Désactivation temporaire d'une alerte bruyante :::tip Créez un silence depuis l'interface Alertmanager en cliquant sur une alerte puis **Silence**.\ Spécifiez la durée, les labels à matcher, et un commentaire explicatif. ::: ##### 3. Routage intelligent Alertmanager route les alertes vers différents destinataires selon des règles configurables (severity, namespace, labels custom, etc.). Par exemple : * Alertes `severity=critical` → email équipe SRE + PagerDuty * Alertes `severity=warning` + `namespace=dev` → blackhole (ignorées) * Alertes `severity=warning` + `namespace=production` → email équipe produit #### Architecture de la configuration La configuration d'Alertmanager est stockée dans un `Secret` Kubernetes : ``` Namespace: kube-system Secret: alertmanager-sdv-monitoring-kube-promet-alertmanager Clé: alertmanager.yaml ``` ##### Visualiser la configuration actuelle ```shell [Terminal] kubectl get secret -n kube-system alertmanager-sdv-monitoring-kube-promet-alertmanager \ -o go-template='{{index .data "alertmanager.yaml"}}' | base64 -d ``` Exemple de configuration type : ```yaml showLineNumbers [alertmanager.yaml] global: resolve_timeout: 5m smtp_from: alerting@sdv.fr smtp_require_tls: false smtp_smarthost: relix.sdv.fr:25 # Règles d'inhibition : évite les alertes redondantes inhibit_rules: - equal: - alertname - namespace source_matchers: - severity="critical" target_matchers: - severity="warning" # Définition des destinataires receivers: - name: "null" # Blackhole : ignore les alertes - name: email-admins-sdv email_configs: - to: admins@sdv.fr headers: Subject: "[{{ .GroupLabels.severity | toUpper }}] {{ .GroupLabels.alertname }}" - name: watchdog webhook_configs: - url: "https://alertmanagerwatchdog.sdv.fr/watchdog.php" send_resolved: true # Configuration du routage route: group_by: - alertname - namespace group_wait: 30s # Attente avant le premier envoi (groupement) group_interval: 5m # Intervalle entre envois d'alertes groupées repeat_interval: 3h # Délai avant renvoi d'une même alerte receiver: email-admins-sdv # Destinataire par défaut routes: # Ignore les alertes InfoInhibitor - matchers: - alertname = InfoInhibitor receiver: "null" continue: false # Watchdog vers webhook dédié - matchers: - alertname = Watchdog receiver: watchdog continue: false # Toutes les alertes warning/critical vont aux admins - matchers: - severity =~ critical|warning receiver: email-admins-sdv continue: true templates: - /etc/alertmanager/config/*.tmpl ``` #### Structure de la configuration La configuration Alertmanager se compose de quatre sections principales : ##### 1. Section `global` Paramètres globaux appliqués à tous les receivers. ```yaml showLineNumbers [alertmanager-header.yaml] global: resolve_timeout: 5m # Délai avant de considérer une alerte comme résolue smtp_from: alerting@sdv.fr # Adresse email expéditeur smtp_smarthost: relix.sdv.fr:25 # Serveur SMTP smtp_require_tls: false # TLS non requis (SMTP interne) ``` ##### 2. Section `receivers` Définit les **destinataires** des alertes. Chaque receiver a un nom unique et une configuration spécifique (email, Slack, webhook, etc.). ##### Exemple : Receiver email ```yaml showLineNumbers [alertmanager-receivers.yaml] receivers: - name: team-backend email_configs: - to: backend@example.com from: alerting@sdv.fr smarthost: relix.sdv.fr:25 require_tls: false send_resolved: true # Envoie aussi les résolutions d'alertes headers: Subject: "[K8S] {{ .GroupLabels.alertname }} - {{ .GroupLabels.namespace }}" ``` ##### Exemple : Receiver Slack ```yaml showLineNumbers [alertmanager-receivers.yaml] receivers: - name: team-frontend-slack slack_configs: - api_url: "https://hooks.slack.com/services/YOUR/WEBHOOK/URL" channel: "#alerts-prod" title: "{{ .GroupLabels.alertname }}" text: "{{ range .Alerts }}{{ .Annotations.description }}\n{{ end }}" send_resolved: true ``` ##### Exemple : Receiver webhook ```yaml showLineNumbers [alertmanager-receivers-webhook.yaml] receivers: - name: custom-webhook webhook_configs: - url: "https://monitoring.example.com/webhook" send_resolved: true http_config: basic_auth: username: user password: pass ``` ##### Exemple : Receiver blackhole (ignorer des alertes) ```yaml showLineNumbers [alertmanager-receivers-blackhole.yaml] receivers: - name: "null" # Toutes les alertes routées ici sont ignorées ``` ##### 3. Section `route` Définit le **routage** des alertes vers les receivers. Fonctionne comme un arbre de décision : ```yaml showLineNumbers [alertmanager-routes.yaml] route: receiver: default # Receiver par défaut si aucune route ne matche group_by: [alertname, namespace] # Critères de groupement group_wait: 30s # Attente initiale avant envoi (permet groupement) group_interval: 5m # Intervalle entre envois groupés repeat_interval: 12h # Délai avant renvoi d'une alerte identique routes: # Route 1 : Développement → Blackhole - matchers: - namespace =~ .*-dev receiver: "null" continue: false # Stop le routage ici # Route 2 : Alertes critiques → Email + PagerDuty - matchers: - severity = critical receiver: oncall-pagerduty continue: true # Continue vers les routes suivantes routes: # Sous-route : Critiques production → Email équipe prod - matchers: - namespace =~ .*-prod receiver: team-prod-email # Route 3 : Warnings production → Email - matchers: - severity = warning - namespace =~ .*-prod receiver: team-prod-email ``` ##### Matchers Les matchers permettent de filtrer les alertes selon leurs labels : | Opérateur | Description | Exemple | | --------- | --------------- | -------------------------- | | `=` | Égalité stricte | `severity = critical` | | `!=` | Différence | `namespace != kube-system` | | `=~` | Regex match | `env =~ .*-prod` | | `!~` | Regex non-match | `alertname !~ Info.*` | ##### Paramètres de temporisation | Paramètre | Description | Valeur recommandée | | ----------------- | -------------------------------------------------- | ------------------ | | `group_wait` | Délai avant le premier envoi d'un groupe d'alertes | `30s` - `1m` | | `group_interval` | Intervalle entre envois d'alertes déjà groupées | `5m` - `10m` | | `repeat_interval` | Délai avant renvoi d'une alerte identique | `3h` - `24h` | :::tip **Optimiser le groupement**\ Un `group_wait` trop court entraîne de multiples notifications. Un délai de 30s-1m permet de grouper efficacement les alertes d'un même incident. ::: ##### 4. Section `inhibit_rules` Les règles d'inhibition permettent de **supprimer des alertes redondantes** lorsqu'une alerte plus sévère est active. Exemple : Si une alerte `critical` est active sur un service, toutes les alertes `warning` concernant ce même service sont inhibées. ```yaml showLineNumbers [alertmanager-inhibit.yaml] inhibit_rules: - source_matchers: - severity = critical target_matchers: - severity = warning equal: - alertname - namespace ``` Explication : * **Source** : Alerte critique active * **Target** : Alertes warning qui seront inhibées * **Equal** : Labels devant être identiques entre source et target :::info Les alertes inhibées restent visibles dans Prometheus mais ne déclenchent **aucune notification** dans Alertmanager. ::: #### Exemples de configurations avancées ##### Routage par environnement et sévérité ```yaml showLineNumbers [alertmanager-route.yaml] route: receiver: default group_by: [alertname, namespace, severity] group_wait: 30s group_interval: 5m repeat_interval: 12h routes: # Ignorer les alertes de dev (tous niveaux) - matchers: - namespace =~ .*-dev receiver: "null" continue: false # Alertes critiques production - matchers: - severity = critical - namespace =~ .*-prod receiver: oncall-critical repeat_interval: 1h # Renvoi toutes les heures si non résolu continue: true routes: # Spécialisations par namespace - matchers: - namespace = payment-prod receiver: team-payment-critical - matchers: - namespace = api-prod receiver: team-api-critical # Alertes warning production - matchers: - severity = warning - namespace =~ .*-prod receiver: team-prod-warnings repeat_interval: 24h # Alertes staging (severity important uniquement) - matchers: - namespace =~ .*-stage - severity =~ critical|warning receiver: team-staging repeat_interval: 6h ``` ##### Routage par type d'alerte ```yaml showLineNumbers [alertmanager-routes.yaml] route: receiver: default routes: # Alertes infrastructure → Équipe SRE - matchers: - alertname =~ Kube.*|Node.* receiver: sre-team # Alertes applicatives → Équipe Dev - matchers: - alertname =~ App.*|HTTP.* receiver: dev-team # Alertes stockage → Équipe Storage - matchers: - alertname =~ PVC.*|PV.*|Disk.* receiver: storage-team ``` #### Modifier la configuration Alertmanager :::warning **Prudence lors des modifications**\ Une erreur de syntaxe dans la configuration empêchera Alertmanager de démarrer, interrompant toutes les notifications d'alertes.\ Testez toujours vos modifications dans un environnement de test avant de les appliquer en production. ::: ##### Procédure de modification :::steps ##### Étape 1. **Extraire la configuration actuelle** ```shell [Terminal] kubectl get secret -n kube-system alertmanager-sdv-monitoring-kube-promet-alertmanager \ -o go-template='{{index .data "alertmanager.yaml"}}' | base64 -d > alertmanager.yaml ``` ##### Étape 2. **Editer le fichier `alertmanager.yaml`** Modifiez la configuration selon vos besoins. Validez la syntaxe YAML avec : ```shell [Terminal] # Validation syntaxe YAML yamllint alertmanager.yaml # Validation configuration Alertmanager (si amtool est installé) amtool check-config alertmanager.yaml ``` ##### Étape 3. **Créer le nouveau Secret** ```shell [Terminal] kubectl create secret generic alertmanager-sdv-monitoring-kube-promet-alertmanager \ --from-file=alertmanager.yaml \ --namespace kube-system \ --dry-run=client -o yaml | kubectl apply -f - ``` L'option `--dry-run=client` génère le manifeste YAML sans l'appliquer, puis le pipe vers `kubectl apply` l'applique réellement. ##### Étape 4. **Vérifier le rechargement** Alertmanager recharge automatiquement sa configuration. Vérifiez l'absence d'erreurs dans les logs : ```shell [Terminal] kubectl -n kube-system logs -l app.kubernetes.io/name=alertmanager -f ``` Exemple de logs de succès : ``` level=info ts=2026-02-19T10:15:32.123Z caller=coordinator.go:119 component=configuration msg="Loading configuration file" file=/etc/alertmanager/config/alertmanager.yaml level=info ts=2026-02-19T10:15:32.124Z caller=coordinator.go:131 component=configuration msg="Completed loading of configuration file" file=/etc/alertmanager/config/alertmanager.yaml ``` Exemple de logs d'erreur : ``` level=error ts=2026-02-19T10:15:32.123Z caller=coordinator.go:125 component=configuration msg="Loading configuration file failed" file=/etc/alertmanager/config/alertmanager.yaml err="yaml: line 12: could not find expected ':'" ``` ##### Étape 5. **Vérifier la configuration appliquée** Consultez la configuration interprétée par Alertmanager depuis l'interface web : ``` http://alertmanager.internal..sdv.fr/#/status ``` Cette page affiche la configuration en cours, les receivers définis, et l'arbre de routage. ::: ##### Ajouter plusieurs fichiers de configuration Il est possible d'enrichir la configuration avec des fichiers supplémentaires (templates, configurations externes, etc.) : ```shell [Terminal] kubectl create secret generic alertmanager-sdv-monitoring-kube-promet-alertmanager \ --from-file=alertmanager.yaml \ --from-file=templates/email.tmpl \ --from-file=templates/slack.tmpl \ --namespace kube-system \ --dry-run=client -o yaml | kubectl apply -f - ``` Les templates doivent être référencés dans la section `templates` de la configuration principale : ```yaml showLineNumbers [alertmanager-templates.yaml] templates: - /etc/alertmanager/config/email.tmpl - /etc/alertmanager/config/slack.tmpl ``` ### Monitoring applicatif #### Exposer des métriques custom Vos applications peuvent exposer leurs propres métriques au format Prometheus pour un monitoring spécifique à votre domaine métier. ##### Prérequis 1. **Instrumenter l'application** avec une bibliothèque Prometheus client (disponible pour la plupart des langages : Go, Python, Java, Node.js, etc.) 2. **Exposer un endpoint `/metrics`** retournant les métriques au format Prometheus 3. **Créer un ServiceMonitor** pour configurer le scraping automatique ##### Exemple d'instrumentation (Go) ```go showLineNumbers [main.go] package main import ( "net/http" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" ) var ( httpRequestsTotal = prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "http_requests_total", Help: "Total number of HTTP requests", }, []string{"method", "endpoint", "status"}, ) httpRequestDuration = prometheus.NewHistogramVec( prometheus.HistogramOpts{ Name: "http_request_duration_seconds", Help: "HTTP request duration in seconds", Buckets: prometheus.DefBuckets, }, []string{"method", "endpoint"}, ) ) func init() { prometheus.MustRegister(httpRequestsTotal) prometheus.MustRegister(httpRequestDuration) } func main() { http.Handle("/metrics", promhttp.Handler()) http.ListenAndServe(":8080", nil) } ``` ##### ServiceMonitor pour scraping automatique Un `ServiceMonitor` est une CRD (Custom Resource Definition) du Prometheus Operator qui configure automatiquement le scraping d'un `Service`. :::code-group ```yaml showLineNumbers [servicemonitor.yaml] apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: mon-application namespace: production labels: app: mon-application release: sdv-monitoring # Label requis pour être détecté par Prometheus spec: selector: matchLabels: app: mon-application # Selector du Service à scraper endpoints: - port: metrics # Nom du port défini dans le Service path: /metrics interval: 30s # Fréquence de scraping scrapeTimeout: 10s ``` ```yaml showLineNumbers [service.yaml] apiVersion: v1 kind: Service metadata: name: mon-application namespace: production labels: app: mon-application spec: selector: app: mon-application ports: - name: metrics # Nom référencé dans le ServiceMonitor port: 8080 targetPort: 8080 protocol: TCP ``` ::: :::tip Vérifiez que votre `ServiceMonitor` est correctement détecté en consultant les **targets** dans Prometheus :\ `http://prometheus.internal..sdv.fr/targets` ::: #### Alertes custom Vous pouvez définir des alertes personnalisées pour vos applications en utilisant la CRD `PrometheusRule`. ##### Exemple de PrometheusRule ```yaml showLineNumbers [prometheusrule.yaml] apiVersion: monitoring.coreos.com/v1 kind: PrometheusRule metadata: name: mon-application-alerts namespace: production labels: release: sdv-monitoring # Label requis pour être détecté par Prometheus spec: groups: - name: mon-application interval: 30s rules: # Alerte : Trop de requêtes en erreur - alert: HighErrorRate expr: | sum(rate(http_requests_total{status=~"5.."}[5m])) by (namespace, pod) / sum(rate(http_requests_total[5m])) by (namespace, pod) > 0.05 for: 5m labels: severity: warning component: backend annotations: summary: "Taux d'erreur élevé sur {{ $labels.namespace }}/{{ $labels.pod }}" description: "Le pod {{ $labels.pod }} a un taux d'erreur de {{ $value | humanizePercentage }} sur les 5 dernières minutes." # Alerte : Latence élevée - alert: HighLatency expr: | histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le, namespace, pod) ) > 2 for: 10m labels: severity: critical component: backend annotations: summary: "Latence p95 élevée sur {{ $labels.namespace }}/{{ $labels.pod }}" description: "La latence p95 est de {{ $value }}s, au-dessus du seuil de 2s." # Alerte : Pods en crash loop - alert: PodCrashLooping expr: | rate(kube_pod_container_status_restarts_total{namespace="production"}[15m]) > 0 for: 15m labels: severity: critical component: infrastructure annotations: summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} en crash loop" description: "Le pod redémarre fréquemment, {{ $value }} restarts sur 15min." ``` :::info Les alertes définies dans un `PrometheusRule` apparaissent automatiquement dans l'interface Prometheus et sont transmises à Alertmanager lorsqu'elles se déclenchent. ::: ### Bonnes pratiques #### Général ✅ **Recommandations :** * Consultez régulièrement les dashboards Grafana pour anticiper les problèmes * Configurez des alertes proactives (saturation disque, mémoire, etc.) avant d'atteindre les limites * Documentez vos seuils d'alerte et ajustez-les au fil du temps (réduction du bruit) * Utilisez les **silences** Alertmanager pendant les maintenances planifiées * Instrumentez vos applications avec des métriques métier (commandes traitées, transactions, etc.) * Créez des **runbooks** (procédures de résolution) pour chaque alerte critique ❌ **À éviter :** * Ignorer les alertes répétées (alerte fatigue) : ajustez les seuils ou corrigez le problème * Supprimer des alertes sans analyse : comprendre la cause racine avant de désactiver * Utiliser uniquement Grafana sans configurer d'alertes (approche réactive vs proactive) * Exposer les interfaces de monitoring publiquement (risque de sécurité) #### Alerting ✅ **Recommandations :** * Limitez les alertes aux événements nécessitant une action humaine * Classifiez la sévérité de manière cohérente : * `critical` : incident impactant les utilisateurs, intervention immédiate requise * `warning` : dégradation partielle, investigation nécessaire mais pas urgente * `info` : informationnel, aucune action requise * Utilisez le groupement (`group_by`) pour éviter les tempêtes d'alertes * Configurez des `repeat_interval` adaptés (3h-12h pour les warnings, 1h pour les critiques) * Ajoutez toujours des annotations `description` et `summary` explicites dans vos alertes * Testez le routage avec des alertes fictives avant de déployer en production ❌ **À éviter :** * Alertes trop sensibles déclenchant des faux positifs fréquents * Absence de `for` dans les alertes (déclenchement instantané sur des pics transitoires) * Envoyer toutes les alertes au même destinataire sans routage intelligent * Oublier de configurer `send_resolved: true` (vous ne saurez pas quand l'incident est résolu) #### Métriques applicatives ✅ **Recommandations :** * Exposez des métriques alignées sur vos objectifs métier (SLI/SLO) * Utilisez les types de métriques appropriés : * **Counter** : événements cumulatifs (requêtes HTTP, erreurs, etc.) * **Gauge** : valeurs instantanées (CPU, mémoire, connexions actives, etc.) * **Histogram** : distribution de valeurs (latences, tailles de payload etc.) * **Summary** : similaire à histogram mais avec quantiles précalculés * Limitez la cardinalité des labels (évitez les labels avec trop de valeurs uniques : user\_id, request\_id, etc.) * Préfixez les noms de métriques par le nom de votre application (`myapp_http_requests_total`) * Documentez vos métriques custom dans un README applicatif ❌ **À éviter :** * Créer des labels à haute cardinalité (explosion de séries temporelles, impact performance Prometheus) * Exposer des informations sensibles dans les labels ou noms de métriques * Changer les noms de métriques sans versioning (casse l'historique) * Ne pas tester l'endpoint `/metrics` avant le déploiement (risque de scraping en erreur) #### Performance et dimensionnement ✅ **Recommandations :** * Utilisez des **recording rules** pour pré-calculer des requêtes PromQL coûteuses * Limitez la rétention de métriques aux besoins réels (par défaut : 15 jours) * Archivez les métriques long terme dans un système externe si nécessaire (Thanos, Cortex, etc.) * Réduisez l'intervalle de scraping pour les métriques peu critiques (`interval: 60s` au lieu de `30s`) ❌ **À éviter :** * Requêtes PromQL sur de très longues périodes dans Grafana (impact performance) * Scraping trop fréquent de métriques peu volatiles (gaspillage de ressources) * Stocker indéfiniment toutes les métriques (saturation du stockage Prometheus) ### Troubleshooting #### Prometheus ne scrape pas mes métriques **Symptômes :** * Votre application n'apparaît pas dans la page **Targets** de Prometheus * Les métriques custom ne sont pas disponibles dans PromQL **Causes possibles :** 1. **ServiceMonitor mal configuré** * Vérifier le label `release: sdv-monitoring` est présent * Vérifier le selector `matchLabels` correspond au `Service` * Vérifier le nom du port dans `endpoints[].port` correspond au nom dans le `Service` 2. **Service inexistant ou mal configuré** * Vérifier que le `Service` existe : `kubectl get svc -n ` * Vérifier que le selector du Service matche les labels des `Pods` 3. **Port non accessible** * Tester manuellement l'endpoint metrics : ```shell [Terminal] kubectl port-forward -n pod/ 8080:8080 curl http://localhost:8080/metrics ``` 4. **Namespace non monitoré** * Vérifier que le `Namespace` n'est pas exclu de la configuration Prometheus **Solution :** ```shell [Terminal] # Vérifier que le ServiceMonitor est créé kubectl get servicemonitor -n # Vérifier la configuration du ServiceMonitor kubectl describe servicemonitor -n # Consulter les logs Prometheus kubectl -n kube-system logs -l app.kubernetes.io/name=prometheus -f ``` #### Alertes non reçues **Symptômes :** * Les alertes sont visibles dans Prometheus mais aucune notification n'est reçue **Causes possibles :** 1. **Alerte en état `pending`** * Le délai `for` n'est pas encore écoulé 2. **Alerte inhibée** * Une alerte plus sévère est active sur le même service * Vérifier les règles d'inhibition dans Alertmanager 3. **Routage incorrect** * L'alerte est routée vers le receiver `"null"` (blackhole) * Vérifier la configuration `route` dans Alertmanager 4. **Problème de configuration du receiver** * Serveur SMTP inaccessible * Webhook endpoint en erreur * Token Slack invalide **Solution :** ```shell [Terminal] # Vérifier l'état des alertes dans Prometheus # http://prometheus.internal..sdv.fr/alerts # Vérifier les alertes actives dans Alertmanager # http://alertmanager.internal..sdv.fr/ # Consulter les logs Alertmanager kubectl -n kube-system logs -l app.kubernetes.io/name=alertmanager -f # Tester le routage avec amtool (si installé) amtool alert query # Liste les alertes actives amtool config routes test --config.file=alertmanager.yaml \ = = # Teste le routage ``` #### Grafana affiche "No data" **Symptômes :** * Les dashboards Grafana affichent "No data" alors que des métriques existent **Causes possibles :** 1. **Filtre de dashboard trop restrictif** * Vérifier les variables de filtrage (namespace, pod, etc.) 2. **Période de temps incorrecte** * Ajuster le time range dans Grafana 3. **Requête PromQL invalide** * Tester la requête directement dans Prometheus 4. **Prometheus inaccessible depuis Grafana** * Vérifier la datasource dans Grafana : **Configuration > Data Sources > Prometheus** **Solution :** ```shell [Terminal] # Tester la requête PromQL directement # http://prometheus.internal..sdv.fr/graph # Vérifier la connectivité Grafana → Prometheus kubectl -n kube-system logs -l app.kubernetes.io/name=grafana -f ``` #### Saturation du stockage Prometheus **Symptômes :** * Prometheus crashloop ou redémarre fréquemment * Logs indiquant "out of disk space" **Causes possibles :** 1. **Trop de séries temporelles collectées** * Métriques avec labels à haute cardinalité 2. **Rétention trop longue** * Paramètre `retention` trop élevé **Solution :** ```shell [Terminal] # Vérifier la consommation de stockage kubectl -n kube-system exec -it prometheus- -- df -h /prometheus # Lister le nombre de séries temporelles # http://prometheus.internal..sdv.fr/api/v1/status/tsdb # Identifier les métriques volumineuses # http://prometheus.internal..sdv.fr/api/v1/label/__name__/values # Réduire la rétention (contact SdV requis) # Ou supprimer des ServiceMonitors inutiles ``` ### Ressources complémentaires #### Documentation officielle * [Prometheus Documentation](https://prometheus.io/docs/introduction/overview/) * [Prometheus Operator](https://prometheus-operator.dev/) * [Grafana Documentation](https://grafana.com/docs/grafana/latest/) * [Alertmanager Documentation](https://prometheus.io/docs/alerting/latest/alertmanager/) * [PromQL Basics](https://prometheus.io/docs/prometheus/latest/querying/basics/) #### Configuration Alertmanager * [Alertmanager Configuration](https://prometheus.io/docs/prometheus/latest/configuration/configuration/) * [Notification Templates](https://prometheus.io/docs/alerting/latest/notification_examples/) #### Instrumentation applicative * [Prometheus Client Libraries](https://prometheus.io/docs/instrumenting/clientlibs/) * [Instrumenting a Go application](https://prometheus.io/docs/guides/go-application/) * [Node.js client (prom-client)](https://github.com/siimon/prom-client) * [Java client (Micrometer)](https://micrometer.io/docs/registry/prometheus) #### Exemples de métriques et alertes * [Awesome Prometheus alerts](https://awesome-prometheus-alerts.grep.to/rules.html) * [Kubernetes Monitoring with Prometheus](https://github.com/prometheus-operator/kube-prometheus) ## Prise en main et premiers pas ### Nœuds Kubernetes Les nœuds du cluster sont répartis en deux catégories : * Les **masters** : au nombre de trois pour assurer une haute disponibilité, ils pilotent le cluster. Ils sont exclusivement dédiés à la gestion du cluster et n'exécutent que les conteneurs nécessaires à son fonctionnement. * Les **workers** : leur nombre est adapté aux besoins de vos applications. Ils exécutent vos conteneurs ainsi que quelques conteneurs système (fournisseurs de stockage, `Ingress`, monitoring, etc.). Au-delà des nœuds, le cluster propose également des services de stockage et d'accessibilité réseau. ### Services de stockage Le cluster met à votre disposition plusieurs `StorageClass` permettant d'assurer un stockage persistant avec différentes politiques de rétention. Ces classes de stockage vous permettent de provisionner à la demande des `PersistentVolumes` pour vos applications. | `StorageClass` | Type | Rétention | Cas d'usage | | ---------------------------- | ----- | --------- | ------------------------------------------------------------------------------------------------ | | `managed-nfs-storage` | `NFS` | `Delete` | Stockage temporaire supprimé automatiquement avec le PVC (bases de données de test, cache, etc.) | | `managed-nfs-storage-retain` | `NFS` | `Retain` | Stockage persistant conservé après suppression du PVC (données de production, sauvegardes, etc.) | :::tip Utilisez la politique `Retain` pour les données critiques nécessitant une conservation manuelle, et `Delete` pour les environnements de développement ou les données temporaires. ::: Pour plus de détails, consultez la documentation [Stockage](cluster/stockage). ### Exposition des applications Le cluster expose vos applications au monde extérieur grâce à des contrôleurs d'`Ingress` basés sur **HAProxy** qui routent le trafic HTTP et HTTPS vers vos conteneurs. #### Accès HTTP/HTTPS Deux points d'entrée réseau sont disponibles selon vos besoins : | Type | Accessibilité | Usage recommandé | | ---------- | -------------- | -------------------------------------------------------------------------------- | | **Public** | Internet | Applications destinées au grand public, sites web de production, APIs publiques | | **Privé** | VPN uniquement | Environnements de test, back-offices, consoles d'administration, outils internes | :::info L'accès privé nécessite une connexion VPN active. Consultez la section [Mise en place du VPN](#mise-en-place-du-vpn) pour plus d'informations. ::: #### Routage TCP/UDP Il est également possible, sur demande, de mettre en place un routage TCP/UDP direct vers vos workloads pour des protocoles non-HTTP (bases de données, services custom, etc.), sous la forme de services de type `LoadBalancer`. :::warning Le routage TCP/UDP nécessite une configuration réseau spécifique. Rapprochez-vous des équipes techniques ou commerciales SdV pour sa mise en œuvre. ::: Pour plus de détails, consultez la documentation [Réseau](/cluster/reseaux). ### Services annexes #### Registry OCI SdV vous met à disposition une registry OCI lors de la livraison de votre cluster Kubernetes. Référez-vous [à la documentation dédiée](/cluster/registry). #### Collecte et centralisation des logs SdV propose plusieurs options pour collecter et centraliser les logs de vos applications déployées dans ses clusters Kubernetes : | Destination | Description | Cas d'usage | | --------------------- | ------------------------------------------------------------ | ----------------------------------------- | | **Buckets S3** | Stockage objet pour archivage long terme | Conformité, audit, analyse différée | | **Elasticsearch** | Indexation et recherche en temps réel | Debugging, monitoring, dashboards Kibana | | **Services externes** | Routage TCP vers des services tiers (Logstash, Splunk, etc.) | Intégration avec infrastructure existante | :::tip La configuration du routage des logs est personnalisable selon vos besoins. Contactez les équipes techniques SdV pour définir la stratégie adaptée à votre contexte. ::: Pour plus de détails, consultez la documentation [Logs](cluster/logs). #### Monitoring / Alerting Chaque cluster Kubernetes SdV est livré avec une solution de monitoring complète et opérationnelle dès sa mise en service. Cette infrastructure de supervision repose sur des outils standards de l'écosystème Kubernetes : | Composant | Rôle et fonctionnalités | | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Prometheus Operator** | Système de collecte, d'agrégation et de stockage des métriques issues du cluster et de vos applications. Supporte les ServiceMonitors pour un scraping automatique. | | **Grafana** | Plateforme de visualisation offrant une interface web intuitive pour explorer vos métriques via des graphiques interactifs et des tableaux de bord personnalisables. | | **Dashboards préconfigurés** | Ensemble de tableaux de bord prêts à l'emploi couvrant les aspects essentiels du cluster : vue d'ensemble, détail par namespace, par nœud, et par workload. | | **AlertManager** | Gestionnaire d'alertes centralisant les notifications issues de Prometheus. Supporte le routage intelligent, le groupement, la déduplication et l'envoi vers multiples canaux (email, Slack, webhook, PagerDuty, etc.). | ##### Indicateurs disponibles Cette stack de monitoring vous donne une visibilité complète sur : * **Ressources matérielles** : consommation CPU, mémoire RAM, espace disque et I/O réseau * **Infrastructure** : état de santé des nœuds (masters et workers), disponibilité et charge système * **Workloads** : statut des pods, nombre de restarts, temps de réponse, erreurs applicatives * **Métriques applicatives** : vous pouvez exposer vos propres métriques custom via le format Prometheus pour un monitoring spécifique à votre domaine métier ##### Système d'alerting AlertManager gère de manière intelligente les alertes déclenchées par Prometheus selon plusieurs mécanismes : * **Routage intelligent** : direction des alertes vers différents destinataires selon leur sévérité, leur source ou tout autre critère configurable * **Groupement** : regroupement d'alertes similaires en une seule notification pour éviter la saturation * **Déduplication** : suppression des alertes redondantes déjà notifiées * **Silencing** : désactivation temporaire d'alertes pendant des maintenances programmées * **Canaux multiples** : envoi simultané vers plusieurs systèmes (email, Slack, Microsoft Teams, webhook HTTP, PagerDuty, Opsgenie, etc.) :::warning La configuration des règles d'alerting et des routes de notification nécessite une bonne compréhension de votre infrastructure. Les équipes SdV peuvent vous accompagner dans la définition d'une stratégie d'alerting adaptée à vos besoins. ::: Pour plus de détails, consultez la documentation [Monitoring](cluster/monitoring). ### Connexion au cluster L'accès à l'API Kubernetes est sécurisé et s'effectue selon deux modes possibles, définis lors de la mise en place initiale du cluster. #### Modes d'accès | Mode | Description | Sécurité | | ---------------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------ | | **Privé** (défaut) | L'API Kubernetes est hébergée dans un réseau privé, accessible uniquement via un tunnel VPN IPsec | Isolation réseau complète + authentification K8s | | **Public** (optionnel) | L'API Kubernetes est exposée sur une adresse IP publique avec filtrage par liste blanche d'adresses IP | Filtrage IP + authentification K8s | :::tip Le mode d'accès privé via VPN est recommandé pour une sécurité maximale. Le mode public peut être envisagé pour faciliter l'intégration avec des systèmes CI/CD hébergés en externe. ::: #### Configuration du VPN Pour plus d'informations, référez-vous [à la documentation dédiée](/cluster/vpn). ##### Prérequis Lors de la livraison du cluster, SdV vous fournit un dossier contenant : * Les paramètres de connexion VPN (adresse du serveur, certificats, clés) * Les identifiants d'authentification * La documentation spécifique à votre environnement ##### Installation du client Un guide complet est disponible sur [vpn.sdv.fr](https://vpn.sdv.fr) pour configurer votre client VPN selon votre système d'exploitation : * **Windows** : configuration via le client VPN natif ou des solutions tierces (OpenVPN, etc.) * **macOS** : configuration via les préférences système ou Tunnelblick * **Linux** : configuration via NetworkManager ou en ligne de commande * **iOS/Android** : applications mobiles compatibles ##### Validation de la connexion Une fois le VPN actif, vous pouvez vérifier la connexion en testant l'accès à l'API Kubernetes : ```bash [Terminal] # Test de connectivité vers l'API Kubernetes kubectl cluster-info # Affichage des nœuds du cluster kubectl get nodes ``` #### Utilisation de kubectl ##### Configuration initiale Le fichier `kubeconfig` fourni à la livraison contient tous les éléments nécessaires pour authentifier vos requêtes : * L'URL de l'API Kubernetes * Les certificats d'authentification client * Les contextes pré-configurés (production, staging, etc.) Pour l'utiliser, placez-le dans l'emplacement standard ou référencez-le explicitement : ```bash [Terminal] # Option 1 : Emplacement standard (recommandé) mkdir -p ~/.kube cp kube_config_rke_config.yml ~/.kube/config # Option 2 : Variable d'environnement export KUBECONFIG=/chemin/vers/kube_config_rke_config.yml # Option 3 : Paramètre en ligne de commande kubectl --kubeconfig=/chemin/vers/kube_config_rke_config.yml get pods ``` ##### Gestion des permissions et sécurité :::warning Le fichier `kubeconfig` fourni à la livraison dispose de **permissions d'administration complètes** sur l'ensemble du cluster (droits `cluster-admin`). Il permet de créer, modifier et supprimer toutes les ressources dans tous les namespaces. ::: ##### Bonnes pratiques de sécurité Pour respecter le principe du moindre privilège, il est **fortement recommandé** de créer des identités dédiées avec des permissions limitées selon les besoins : | Type d'identité | Usage recommandé | | -------------------- | --------------------------------------------------------------------------------------------------- | | **ServiceAccount** | Pour les applications et pipelines CI/CD automatisés nécessitant un accès programmatique au cluster | | **Utilisateur RBAC** | Pour les membres de l'équipe nécessitant un accès via kubectl avec des permissions spécifiques | ##### Exemple de configuration à portée limitée Voici un cas d'usage typique : créer un `kubeconfig` pour l'application A qui : * A des **permissions en écriture** uniquement sur les namespaces correspondant au pattern `app-a-*` * A des **permissions en lecture seule** sur les autres namespaces ::::steps ###### Étape 1 : Créer un ServiceAccount ```yaml showLineNumbers [serviceaccount.yaml] apiVersion: v1 kind: ServiceAccount metadata: name: app-a-deployer namespace: app-a-prod ``` ###### Étape 2 : Définir les rôles et permissions :::code-group ```yaml showLineNumbers [role.yaml] # Permissions en écriture sur les namespaces app-a-* apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: app-a-writer namespace: app-a-prod rules: - apiGroups: ["", "apps", "batch"] resources: ["pods", "deployments", "services", "configmaps", "secrets", "jobs"] verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] ``` ```yaml showLineNumbers [clusterrole.yaml] # Permissions en lecture seule cluster-wide apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: app-a-reader rules: - apiGroups: ["", "apps"] resources: ["pods", "deployments", "services"] verbs: ["get", "list", "watch"] ``` ::: ###### Étape 3 : Lier les rôles au ServiceAccount :::code-group ```yaml showLineNumbers [rolebinding.yaml] apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: app-a-writer-binding namespace: app-a-prod subjects: - kind: ServiceAccount name: app-a-deployer namespace: app-a-prod roleRef: kind: Role name: app-a-writer apiGroup: rbac.authorization.k8s.io ``` ```yaml showLineNumbers [clusterrolebinding.yaml] apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: app-a-reader-binding subjects: - kind: ServiceAccount name: app-a-deployer namespace: app-a-prod roleRef: kind: ClusterRole name: app-a-reader apiGroup: rbac.authorization.k8s.io ``` ::: ###### Étape 4 : Générer le `kubeconfig` dédié Vous trouverez plus de détails ainsi qu'un script vous facilitant la génération du `kubeconfig` dédié dans la section [RBACs](/guides/rbac). ```bash [Terminal] # Récupérer le token du ServiceAccount SECRET_NAME=$(kubectl get serviceaccount app-a-deployer -n app-a-prod -o jsonpath='{.secrets[0].name}') TOKEN=$(kubectl get secret $SECRET_NAME -n app-a-prod -o jsonpath='{.data.token}' | base64 -d) # Créer le fichier kubeconfig kubectl config set-cluster mon-cluster --server=https://api.cluster.example.com:6443 --kubeconfig=kubeconfig-app-a kubectl config set-credentials app-a-deployer --token=$TOKEN --kubeconfig=kubeconfig-app-a kubectl config set-context app-a --cluster=mon-cluster --user=app-a-deployer --kubeconfig=kubeconfig-app-a kubectl config use-context app-a --kubeconfig=kubeconfig-app-a ``` :::: :::info Pour des configurations RBAC plus complexes ou pour mettre en place une authentification via OIDC/LDAP, consultez la documentation [RBAC](guides/rbac) ou rapprochez-vous des équipes SdV. ::: ##### Commandes essentielles Voici quelques commandes de base pour débuter avec votre cluster : ```bash [Terminal] # Lister les namespaces kubectl get namespaces # Lister les pods d'un namespace kubectl get pods -n # Obtenir les détails d'un pod kubectl describe pod -n # Consulter les logs d'un conteneur kubectl logs -n # Exécuter une commande dans un pod kubectl exec -it -n -- /bin/bash ``` :::info Pour une documentation complète de `kubectl`, consultez la [référence officielle Kubernetes](https://kubernetes.io/fr/docs/reference/kubectl/). ::: ### Documentation utile * [Kubernetes : les concepts de base](https://kubernetes.io/fr/docs/concepts/) * [Kubernetes Spec Explorer : accédez aux définitions yaml des entités Kubernetes](https://kubespec.dev/) ## Registry OCI ### Vue d'ensemble À la livraison de votre cluster Kubernetes, SdV vous met à disposition une instance de **registry OCI (Open Container Initiative)**, reposant sur le produit **Harbor**. Harbor est une solution de registry d'entreprise open-source qui permet de stocker, signer et scanner les images de conteneurs Docker et OCI. Elle offre des fonctionnalités avancées de gestion, de sécurité et de conformité pour vos artefacts conteneurisés. #### Fonctionnalités principales | Fonctionnalité | Description | Avantages | | -------------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------- | | **Stockage d'images** | Hébergement privé d'images Docker et artefacts OCI | Contrôle total sur vos artefacts, pas de dépendance à des registries publics | | **Gestion des projets** | Organisation hiérarchique par projets et repositories | Isolation et contrôle d'accès granulaire | | **Scan de vulnérabilités** | Analyse automatique des CVE dans les images | Identification proactive des failles de sécurité | | **Signature d'images** | Signature cryptographique des artefacts (Content Trust) | Garantie d'intégrité et de provenance | | **Contrôle d'accès** | RBAC intégré avec support LDAP/OIDC | Authentification et autorisation centralisées | | **Réplication** | Synchronisation entre registries | Distribution multi-sites, backup | | **Webhooks** | Notifications événementielles | Intégration CI/CD, automatisation | | **Garbage Collection** | Nettoyage automatique des artefacts non référencés | Optimisation de l'espace de stockage | ### Accès au registry Harbor #### URL et authentification Lors de la livraison du cluster, SdV vous fournit : * **URL du registry** : fournie à la livraison du cluster * **Interface Web** : accessible via un navigateur pour la gestion visuelle * **API REST** : pour les intégrations automatisées * **Identifiants d'un compte administrateur** initial :::info **Modes d'authentification supportés :** * **Local** : gestion des utilisateurs directement dans Harbor (par défaut) * **LDAP/AD** : intégration avec votre annuaire d'entreprise (sur demande) * **OIDC** : authentification via un fournisseur d'identité compatible (Keycloak, Okta, Auth0, etc.) Si vous souhaitez activer LDAP ou OIDC, il faudra réaliser la configuration de votre instance Harbor dès votre première connexion. ::: #### Connexion à l'interface web 1. Accédez à l'URL de votre registry Harbor via un navigateur 2. Authentifiez-vous avec vos identifiants 3. Vous accédez au tableau de bord listant vos projets et statistiques #### Authentification Docker CLI Pour pousser ou récupérer des images via la ligne de commande : ```bash [Terminal] # Connexion au registry Harbor docker login {URL_HARBOR} # Saisir votre nom d'utilisateur et mot de passe # Les credentials sont stockés dans ~/.docker/config.json ``` :::warning **Sécurité des credentials :** * Ne jamais commiter le fichier `~/.docker/config.json` dans un dépôt Git * Utiliser des **Robot Accounts** au lieu de comptes utilisateurs pour les pipelines CI/CD * Activer l'authentification multi-facteurs (MFA) si disponible dans votre configuration ::: ### Gestion des projets #### Structure organisationnelle Harbor organise les images en **projets**, qui regroupent un ou plusieurs **repositories**. Cette hiérarchie facilite la gestion des droits et la séparation des environnements. ``` harbor-votre-cluster.sdv.fr/ ├── production/ │ ├── frontend:1.0.0 │ ├── backend:2.3.1 │ └── api:latest ├── staging/ │ ├── frontend:develop │ └── backend:feature-xyz └── shared-libs/ ├── base-nginx:1.21 └── python-base:3.11-alpine ``` #### Création d'un projet Via l'interface web : 1. Cliquez sur **"New Project"** 2. Définissez le nom (ex : `production`, `staging`, `team-backend`) 3. Choisissez la visibilité : * **Private** : accessible seulement aux membres autorisés (recommandé) * **Public** : accessible en lecture à tous les utilisateurs authentifiés 4. Activez les options selon vos besoins : * **Scan automatique** : scan de vulnérabilités au push * **Prévention des images vulnérables** : blocage du pull si CVE critiques détectées * **Content Trust** : signature obligatoire des images :::tip **Conventions de nommage recommandées :** * Utiliser des noms descriptifs et cohérents : `production`, `staging`, `development` * Organiser par environnement ou par équipe selon votre contexte * Éviter les caractères spéciaux, préférer les minuscules et tirets ::: #### Robot Accounts Les **Robot Accounts** sont des comptes de service dédiés aux automatisations (CI/CD, Kubernetes, scripts). Ils peuvent être limités à un projet spécifique avec des droits restreints. ##### Création d'un Robot Account 1. Dans votre projet, allez dans **"Robot Accounts"** 2. Cliquez sur **"New Robot Account"** 3. Définissez un nom explicite (ex : `gitlab-ci-pusher`, `k8s-puller`) 4. Sélectionnez les permissions : * **Push artifact** : droit de pousser des images * **Pull artifact** : droit de récupérer des images * **Delete artifact** : droit de supprimer des images (attention !) 5. Récupérez le **token** généré et conservez-le en sécurité :::warning Le token du Robot Account n'est affiché qu'une seule fois. Conservez-le dans un gestionnaire de secrets (Vault, GitLab CI Variables, Kubernetes Secrets, etc.). ::: ##### Utilisation dans un pipeline CI/CD ```yaml showLineNumbers [.gitlab-ci.yml] stages: - build - push variables: HARBOR_REGISTRY: "harbor.k8s-prod.sdv.fr" HARBOR_PROJECT: "production" IMAGE_NAME: "backend" IMAGE_TAG: "${CI_COMMIT_SHORT_SHA}" build: stage: build image: docker:24-dind services: - docker:24-dind before_script: - echo "${HARBOR_ROBOT_TOKEN}" | docker login -u "${HARBOR_ROBOT_USER}" --password-stdin ${HARBOR_REGISTRY} script: - docker build -t ${HARBOR_REGISTRY}/${HARBOR_PROJECT}/${IMAGE_NAME}:${IMAGE_TAG} . - docker tag ${HARBOR_REGISTRY}/${HARBOR_PROJECT}/${IMAGE_NAME}:${IMAGE_TAG} ${HARBOR_REGISTRY}/${HARBOR_PROJECT}/${IMAGE_NAME}:latest - docker push ${HARBOR_REGISTRY}/${HARBOR_PROJECT}/${IMAGE_NAME}:${IMAGE_TAG} - docker push ${HARBOR_REGISTRY}/${HARBOR_PROJECT}/${IMAGE_NAME}:latest only: - main ``` :::tip Définissez `HARBOR_ROBOT_USER` et `HARBOR_ROBOT_TOKEN` comme variables protégées dans votre CI/CD pour sécuriser les credentials. ::: ### Utilisation du registry #### Push d'une image ```bash [Terminal] # Construire une image localement docker build -t mon-app:1.0.0 . # Tagger l'image avec le chemin complet du registry docker tag mon-app:1.0.0 harbor-votre-cluster.sdv.fr/production/mon-app:1.0.0 # Pousser l'image vers Harbor docker push harbor-votre-cluster.sdv.fr/production/mon-app:1.0.0 ``` #### Pull d'une image ```bash [Terminal] # Récupérer une image depuis Harbor docker pull harbor-votre-cluster.sdv.fr/production/mon-app:1.0.0 # Lancer un conteneur avec cette image docker run -d harbor-votre-cluster.sdv.fr/production/mon-app:1.0.0 ``` #### Stratégies de tagging | Stratégie | Exemple | Cas d'usage | | ------------------------- | ----------------------------------- | ----------------------------------------- | | **Semantique versionnée** | `1.0.0`, `1.0.1`, `2.0.0` | Production, releases stables | | **Git commit SHA** | `a3f5c2d`, `${CI_COMMIT_SHORT_SHA}` | Traçabilité exacte, rollback précis | | **Branches Git** | `main`, `develop`, `feature-auth` | Environnements de développement | | **Environnement** | `prod`, `staging`, `dev` | Déploiements multi-environnements | | **Date** | `2026-02-25`, `20260225-143000` | Builds quotidiens, audits temporels | | **Latest** | `latest` | Développement uniquement (éviter en prod) | :::warning **Évitez le tag `latest` en production** car il ne garantit pas la reproductibilité des déploiements. Utilisez toujours des tags versionnés explicites. ::: ### Scan de vulnérabilités Harbor intègre **Trivy** (scanner de vulnérabilités open-source) pour analyser automatiquement les images et détecter les CVE (Common Vulnerabilities and Exposures). #### Fonctionnement * **Scan automatique au push** : configurable au niveau du projet * **Scan manuel** : déclenchable via l'interface web ou l'API * **Base de données CVE** : mise à jour régulièrement pour détecter les dernières vulnérabilités #### Niveaux de sévérité | Sévérité | Description | Action recommandée | | -------------- | --------------------------------------------- | ------------------------------------------------ | | **Critical** | Vulnérabilité critique exploitable facilement | Correction immédiate, bloquer le déploiement | | **High** | Vulnérabilité majeure | Correction prioritaire sous 7 jours | | **Medium** | Vulnérabilité modérée | Correction planifiée sous 30 jours | | **Low** | Vulnérabilité mineure | Correction lors du prochain cycle de maintenance | | **Negligible** | Impact très faible ou non applicable | Suivi informatif | #### Politique de blocage Vous pouvez configurer Harbor pour **empêcher le pull d'images vulnérables** : 1. Dans les paramètres du projet, activez **"Prevent vulnerable images from running"** 2. Définissez le seuil : `Critical`, `Critical+High`, etc. 3. Les images ne respectant pas le seuil ne pourront pas être déployées :::tip **Bonnes pratiques scan de vulnérabilités :** * Activer le scan automatique sur tous les projets de production * Mettre en place une politique de blocage pour les CVE critiques * Scanner régulièrement les images anciennes (des CVE peuvent être découvertes après le push) * Mettre à jour régulièrement les images de base (alpine, debian, ubuntu, etc.) * Utiliser des images minimales (distroless, scratch, alpine) pour réduire la surface d'attaque ::: #### Visualisation des résultats Dans l'interface Harbor : 1. Accédez à votre repository 2. Cliquez sur une image/tag spécifique 3. Consultez l'onglet **"Vulnerabilities"** 4. Vous verrez la liste des CVE détectées avec leur sévérité, description et solution ### Intégration avec Kubernetes #### Authentification via Secret Pour permettre à Kubernetes de récupérer des images depuis Harbor, créez un **Secret de type `docker-registry`** : ```bash [Terminal] kubectl create secret docker-registry harbor-credentials \ --docker-server=harbor-votre-cluster.sdv.fr \ --docker-username='robot$production+k8s-puller' \ --docker-password=VOTRE_TOKEN_ROBOT \ --namespace=production ``` :::info Les Robot Accounts ont des noms au format `robot$+` (ex : `robot$production+k8s-puller`).\ Attention lorsque vous enregistrez la valeur sous forme de variable dans GitLab, n'oubliez pas de décocher la case `Expand Variables` pour éviter que le `$` ne soit interprété. ::: #### Utilisation dans un `Pod` Référencez le `Secret` dans votre `Deployment` pour autoriser le pull depuis Harbor : ```yaml showLineNumbers [deployment.yaml] apiVersion: apps/v1 kind: Deployment metadata: name: mon-application namespace: production labels: app: mon-application spec: replicas: 3 selector: matchLabels: app: mon-application template: metadata: labels: app: mon-application spec: # Référence au Secret pour l'authentification Harbor imagePullSecrets: - name: harbor-credentials containers: - name: backend image: harbor.k8s-prod.sdv.fr/production/backend:1.2.3 ports: - containerPort: 8080 name: http resources: requests: memory: "256Mi" cpu: "100m" limits: memory: "512Mi" cpu: "500m" ``` #### `ServiceAccount` avec `imagePullSecrets` Pour éviter de répéter `imagePullSecrets` dans chaque `Deployment`, attachez-le au `ServiceAccount` : ```yaml showLineNumbers [serviceaccount.yaml] apiVersion: v1 kind: ServiceAccount metadata: name: app-serviceaccount namespace: production imagePullSecrets: - name: harbor-credentials ``` Puis référencez ce `ServiceAccount` dans vos `Pods` : ```yaml showLineNumbers [deployment-with-sa.yaml] apiVersion: apps/v1 kind: Deployment metadata: name: mon-application namespace: production spec: replicas: 3 selector: matchLabels: app: mon-application template: metadata: labels: app: mon-application spec: serviceAccountName: app-serviceaccount containers: - name: backend image: harbor.k8s-prod.sdv.fr/production/backend:1.2.3 ports: - containerPort: 8080 ``` ### Gestion du stockage #### Quotas de stockage Les projets Harbor peuvent être soumis à des **quotas de stockage** pour limiter l'espace disque consommé. * **Quota par défaut** : défini lors de la création du projet ou configuré globalement * **Quota personnalisé** : ajustable par projet selon vos besoins * **Notification** : alertes lorsque le quota approche de sa limite :::info SdV vous facture ce service au stockage. Pour toute information, contactez le service commercial. ::: #### Garbage Collection Le **Garbage Collection** supprime les artefacts orphelins (layers non référencés par des images) pour libérer de l'espace. ##### Déclenchement * **Manuel** : via l'interface web (Administration → Garbage Collection → "GC Now") * **Planifié** : configuration d'un cron pour exécution automatique (ex : tous les dimanches à 2h) :::warning Le Garbage Collection peut prendre du temps et consommer des ressources. Il est recommandé de le planifier durant les heures creuses. ::: ##### Bonnes pratiques de nettoyage ✅ **Recommandé :** * Planifier un GC hebdomadaire automatique (nuit du samedi au dimanche) * Supprimer régulièrement les tags obsolètes ou de développement * Définir une politique de rétention (ex : garder les 10 dernières versions uniquement) * Utiliser des tags explicites plutôt que `latest` pour faciliter le nettoyage ❌ **À éviter :** * GC trop fréquents (impact sur les performances) * Suppression d'images encore utilisées en production * Absence de politique de rétention (croissance incontrôlée) ### Réplication La **réplication** permet de synchroniser des images entre plusieurs registries Harbor (ou vers d'autres registries OCI compatibles). #### Cas d'usage | Scénario | Description | | ------------------------------ | ------------------------------------------------------------------------------ | | **Backup** | Réplication vers un registry de secours pour la continuité d'activité | | **Distribution multi-sites** | Réplication vers des clusters dans différentes régions géographiques | | **Promotion d'environnements** | Réplication automatique de `staging` vers `production` après validation | | **Migration** | Synchronisation vers un nouveau registry lors d'une migration d'infrastructure | #### Configuration 1. Dans **Administration → Replications**, créez une règle de réplication 2. Définissez la source (local ou distant) et la destination 3. Configurez les filtres : * Projets spécifiques * Tags matchant un pattern (ex : `v*`, `prod-*`) 4. Définissez le déclencheur : * **Manuel** : déclenché manuellement * **Scheduled** : planification cron * **Event Based** : dès qu'une nouvelle image est poussée :::info La réplication vers des registries externes nécessite une configuration réseau et des credentials appropriés. Contactez les équipes SdV pour assistance. ::: ### Webhooks et automatisation Harbor peut envoyer des **webhooks** lors d'événements spécifiques pour déclencher des actions automatisées. #### Événements supportés | Événement | Description | Cas d'usage | | ------------------- | ----------------------------- | ---------------------------------------------- | | **Push artifact** | Une image a été poussée | Déclencher un build CI/CD, notifier une équipe | | **Pull artifact** | Une image a été téléchargée | Audit d'utilisation | | **Delete artifact** | Une image a été supprimée | Notification de suppression, logging | | **Scan completed** | Scan de vulnérabilité terminé | Notification de CVE détectées, blocage CD | | **Quota exceeded** | Quota de stockage atteint | Alerte aux administrateurs | #### Configuration 1. Dans les paramètres du projet, allez dans **"Webhooks"** 2. Créez un webhook avec l'URL de destination 3. Sélectionnez les événements à surveiller 4. Harbor enverra une requête POST JSON à chaque événement ##### Exemple de payload webhook (push artifact) ```json showLineNumbers [webhook-payload.json] { "type": "pushArtifact", "occur_at": 1708881234, "operator": "admin", "event_data": { "resources": [ { "resource_url": "harbor.k8s-prod.sdv.fr/production/backend:1.2.3", "tag": "1.2.3" } ], "repository": { "name": "backend", "namespace": "production", "repo_full_name": "production/backend", "repo_type": "private" } } } ``` ### Bonnes pratiques #### Sécurité ✅ **Recommandations :** * Utiliser des **Robot Accounts** pour les pipelines CI/CD plutôt que des comptes utilisateurs * Activer le **scan automatique** de vulnérabilités sur tous les projets * Mettre en place une politique de **blocage des images critiquement vulnérables** * Ne jamais commiter de credentials Docker dans Git (utiliser `.gitignore`) * Renouveler régulièrement les tokens des Robot Accounts * Activer **Content Trust** (signature d'images) pour les environnements de production * Limiter les droits des utilisateurs au strict nécessaire (principe du moindre privilège) #### Gestion des images ✅ **Recommandations :** * Utiliser des **tags versionnés** explicites (semantic versioning, git SHA) * Éviter le tag `latest` en production * Documenter les tags dans vos repositories (README, changelog) * Implémenter une **politique de rétention** pour nettoyer les anciennes images * Organiser les projets par environnement ou par équipe * Utiliser des **images de base minimales** (alpine, distroless, scratch) * Scanner régulièrement les anciennes images (nouvelles CVE) #### Performance ✅ **Recommandations :** * Planifier le **Garbage Collection** durant les heures creuses * Planifier le **Scan des Vulnérabilités** durant les heures creuses ou au push d'un contenu * Optimiser la taille des images (multi-stage builds, .dockerignore) * Utiliser le **cache de layers Docker** dans les pipelines CI/CD * Configurer la **réplication** pour distribuer la charge sur plusieurs registries * Monitorer l'utilisation des quotas de stockage #### Organisation ✅ **Recommandations :** * Établir des **conventions de nommage** claires (projets, repositories, tags) * Documenter l'architecture des projets Harbor dans votre documentation interne * Former les équipes aux bonnes pratiques Docker et Harbor * Mettre en place des **notifications** pour les événements critiques (quota atteint, CVE critique) ### Cas d'usage courants #### Déploiement d'une application microservices ```bash [Terminal] # 1. Construire chaque service docker build -t harbor.k8s-prod.sdv.fr/production/frontend:1.0.0 ./frontend docker build -t harbor.k8s-prod.sdv.fr/production/backend:1.0.0 ./backend docker build -t harbor.k8s-prod.sdv.fr/production/api:1.0.0 ./api # 2. Pousser les images vers Harbor docker push harbor.k8s-prod.sdv.fr/production/frontend:1.0.0 docker push harbor.k8s-prod.sdv.fr/production/backend:1.0.0 docker push harbor.k8s-prod.sdv.fr/production/api:1.0.0 # 3. Déployer sur Kubernetes kubectl apply -f k8s/frontend-deployment.yaml kubectl apply -f k8s/backend-deployment.yaml kubectl apply -f k8s/api-deployment.yaml ``` #### Migration d'images depuis Docker Hub ```bash [Terminal] # Pull depuis Docker Hub docker pull nginx:1.24-alpine # Retag pour Harbor docker tag nginx:1.24-alpine harbor.k8s-prod.sdv.fr/shared-libs/nginx:1.24-alpine # Push vers Harbor docker push harbor.k8s-prod.sdv.fr/shared-libs/nginx:1.24-alpine ``` #### Promotion staging → production ```bash [Terminal] # Pull depuis staging docker pull harbor.k8s-prod.sdv.fr/staging/backend:2.0.0-rc1 # Retag pour production docker tag harbor.k8s-prod.sdv.fr/staging/backend:2.0.0-rc1 \ harbor.k8s-prod.sdv.fr/production/backend:2.0.0 # Push en production docker push harbor.k8s-prod.sdv.fr/production/backend:2.0.0 ``` ### Support et assistance Pour toute question ou problème lié au registry Harbor : * **Documentation officielle Harbor** : [goharbor.io/docs](https://goharbor.io/docs/) * **Support technique SdV** : contactez vos interlocuteurs habituels ou ouvrez un ticket * **Demande d'évolution** : augmentation de quotas, activation LDAP/OIDC, configuration avancée :::tip Les équipes SdV peuvent vous accompagner dans la configuration initiale de Harbor, la définition de vos stratégies de scanning et de rétention, ainsi que l'intégration avec vos pipelines CI/CD. ::: ## Ingress, Egress, LoadBalancers et TLS ### Introduction Ce document décrit l'architecture réseau des clusters Kubernetes managés par SdV, les différentes méthodes d'exposition des services (HTTP/HTTPS via Ingress, TCP/UDP via LoadBalancer), ainsi que les bonnes pratiques associées. #### Architecture réseau simplifiée ![Diagramme Réseau Simplifié](/cluster/reseau-simplifie.png) **Points clés :** * Les **VIP (Virtual IP)** sont des adresses IP publiques ou privées configurées sur l'infrastructure réseau SdV * Le trafic HTTP/HTTPS passe par l'**Ingress Controller** (HAProxy) * Le trafic TCP/UDP custom nécessite des **Services de type LoadBalancer** avec MetalLB * Deux types d'accès : **public** (Internet) et **privé** (VPN) ### Routage `http` et `https` L'accès réseau depuis Internet à destination des `Pods` se fait par le biais de **VIP (Virtual IP)** configurées sur les loadbalancers de l'infrastructure SdV. Ces derniers dirigent ensuite le trafic vers l'[Ingress Controller][Ingress] Kubernetes (HAProxy). #### Ingress Controller : HAProxy Les clusters SdV utilisent **HAProxy Ingress Controller** comme contrôleur d'Ingress. C'est un composant critique qui : * Réceptionne le trafic HTTP/HTTPS depuis les VIP * Route les requêtes vers les `Services` Kubernetes appropriés selon les règles d'`Ingress` * Prend en charge la terminaison TLS/SSL * Offre des fonctionnalités avancées (rate limiting, redirections, authentification, etc.) :::info **L'[Ingress][Ingress] ne permet de router que les flux HTTP et HTTPS** (ports 80 et 443).\ Pour exposer d'autres protocoles (TCP/UDP custom), référez-vous à la section [Routage TCP/UDP](#routage-tcpudp). ::: #### VIP : Adresses IP publiques et privées Chaque cluster dispose de **deux VIP** avec leurs alias DNS dynamiques pour les tests : | Type d'Ingress | `ingressClassName` | VIP | Alias DNS wildcard (tests uniquement) | Accessibilité | | -------------- | ------------------ | ---------------------- | ------------------------------------- | ---------------- | | **Public** | `public` | Fournie à la livraison | `*.public.{XXXX}.sdv.fr` | Internet complet | | **Privé** | `private` | Fournie à la livraison | `*.internal.{XXXX}.sdv.fr` | VPN uniquement | :::info Remplacez `{XXXX}` par le nom de votre cluster (exemple : `sdv-k8n42c1.sdv.fr`).\ Les alias DNS wildcard sont **réservés aux environnements de test**.\ Pour la production et la préproduction, créez des enregistrements DNS pointant vers les VIP fournies.\ Préférez toujours la création d'enregistrements `CNAME` à des enregistrements `A`. ::: :::warning En l'absence du champ `ingressClassName`, la VIP **privée** est utilisée par défaut. Ce fonctionnement implicite n'est pas recommandé : spécifiez toujours explicitement `ingressClassName: private` ou `ingressClassName: public` pour éviter toute ambiguïté. ::: #### Configurations avancées :::tip La configuration de l'`Ingress` s'effectue par le biais d'annotations. La documentation officielle des annotations disponibles : [https://haproxy-ingress.github.io/v0.15/docs/configuration/keys/](https://haproxy-ingress.github.io/v0.15/docs/configuration/keys/) ::: #### Exemples Lors de la configuration de vos `Ingress` vous devez préciser si vous comptez utiliser la VIP privée ou la VIP publique. Cela se fait avec le champ `ingressClassName` dans l'objet Ingress. ##### Ingress Public et Backend http Vous trouverez ci-dessous un exemple de configuration d’un `Service` associé à un [`Ingress`](https://kubernetes.io/fr/docs/concepts/services-networking/ingress/) public : :::code-group ```yaml showLineNumbers [service.yaml] apiVersion: v1 kind: Service metadata: name: mon-application namespace: production labels: app: mon-application tier: frontend spec: type: ClusterIP selector: app: mon-application ports: - name: http port: 80 targetPort: 8080 # Port sur lequel l'application écoute dans le Pod protocol: TCP ``` ```yaml showLineNumbers [ingress-public.yaml] apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: mon-application-public namespace: production labels: app: mon-application annotations: # Force la redirection HTTP vers HTTPS (recommandé en production) ingress.kubernetes.io/ssl-redirect: "true" spec: ingressClassName: public # Exposition sur la VIP publique rules: - host: www.votre-domaine.com # DNS avec enregistrement CNAME vers la VIP publique http: paths: - path: / pathType: Prefix backend: service: name: mon-application port: number: 80 ``` ::: **Validation :** ```bash [Terminal] # Vérifier le Service kubectl get svc mon-application -n production # Vérifier l'Ingress et son adresse IP kubectl get ingress mon-application-public -n production # Tester l'accès (remplacer par votre domaine) curl -I http://www.votre-domaine.com ``` ##### Exemple 2 : Ingress Public avec Backend HTTPS Lorsque votre application backend communique en HTTPS (certificat applicatif), configurez l'annotation `secure-backends` : ```yaml showLineNumbers [ingress-public-secure-backends.yaml] apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: mon-application-secure-backend namespace: production annotations: # Active la communication HTTPS entre l'Ingress et le backend ingress.kubernetes.io/secure-backends: "true" # Optionnel : désactiver la vérification du certificat backend (non recommandé en prod) # ingress.kubernetes.io/secure-verify-ca-secret: "false" spec: ingressClassName: public rules: - host: secure-backend.votre-domaine.com http: paths: - path: / pathType: Prefix backend: service: name: mon-application port: number: 443 # Le service écoute en HTTPS ``` :::info **Combinaisons possibles :** * **Client → Ingress** : HTTP ou HTTPS * **Ingress → Backend** : HTTP ou HTTPS Toutes les combinaisons sont supportées. Privilégiez HTTPS de bout en bout pour les données sensibles. ::: ##### Exemple 3 : Ingress Public avec TLS/HTTPS (Let's Encrypt) Configuration d'un Ingress avec terminaison TLS au niveau de l'Ingress Controller : ```yaml showLineNumbers [ingress-public-tls.yaml] apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: mon-application-tls namespace: production annotations: # Force HTTPS (redirection 301) ingress.kubernetes.io/ssl-redirect: "true" # Cert-Manager générera automatiquement le certificat (si le cluster issuer est configuré) cert-manager.io/cluster-issuer: letsencrypt-prod spec: ingressClassName: public tls: - hosts: - api.votre-domaine.com secretName: api-votre-domaine-tls # Cert-Manager créera ce Secret automatiquement rules: - host: api.votre-domaine.com http: paths: - path: / pathType: Prefix backend: service: name: mon-application port: number: 80 # Backend en HTTP, TLS terminé au niveau Ingress ``` :::tip **Gestion des certificats TLS :** * Vous devez fournir à l'`Ingress` un certificat TLS valide stocké dans un `Secret` de type `tls` * Si aucun certificat n'est fourni, c'est le certificat TLS **par défaut** de l'Ingress Controller (auto-signé) qui sera présenté, provoquant des avertissements de sécurité dans les navigateurs * **Recommandation :** Utilisez **Cert-Manager** pour automatiser la génération et le renouvellement des certificats via Let's Encrypt * Référez-vous à la [section TLS](#gestion-des-certificats-tls) pour plus de détails ::: ##### Exemple 4 : Ingress Privé (VPN) Pour exposer un service uniquement sur le réseau privé (VPN) : ```yaml showLineNumbers [ingress-private.yaml] apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: mon-application-private namespace: production annotations: ingress.kubernetes.io/whitelist-source-range: "10.0.0.0/8,172.16.0.0/12" # Restriction IP spec: ingressClassName: private # Exposition sur la VIP privée uniquement rules: - host: internal-api.internal.{XXXX}.sdv.fr http: paths: - path: / pathType: Prefix backend: service: name: mon-application-backend port: number: 8080 ``` ### Gestion des Certificats TLS Cette section détaille la configuration et la gestion des certificats TLS/SSL pour sécuriser vos services exposés via Ingress. #### Pourquoi utiliser TLS ? * **Chiffrement des données** en transit entre le client et le serveur * **Authentification** du serveur (vérification de l'identité) * **Confiance** des utilisateurs (pas d'avertissement de sécurité dans le navigateur) * **Conformité** réglementaire (RGPD, PCI-DSS, etc.) * **SEO** : Google favorise les sites en HTTPS #### Méthodes de gestion des certificats ##### Méthode 1 : Certificats manuels Créer un Secret TLS à partir de vos certificats existants : :::code-group ```bash [Terminal] # Créer un Secret TLS à partir d'un certificat et d'une clé privée kubectl create secret tls mon-certificat \ --cert=chemin/vers/cert.crt \ --key=chemin/vers/cert.key \ -n production ``` ```yaml showLineNumbers [secret.yaml] apiVersion: v1 kind: Secret metadata: name: mon-certificat-tls namespace: production type: kubernetes.io/tls data: tls.crt: tls.key: ``` ::: ##### Méthode 2 : Cert-Manager (recommandé) **Cert-Manager** automatise la génération, le renouvellement et la gestion des certificats TLS via Let's Encrypt ou d'autres autorités de certification. :::tip Cert-Manager est la solution recommandée pour la production. Elle permet : * **Automatisation** complète de l'obtention et du renouvellement des certificats * **Multi-provider** : Let's Encrypt, Venafi, HashiCorp Vault, auto-signé, etc. * **Gestion centralisée** avec `ClusterIssuer` et `Issuer` * **Intégration native** avec les Ingress Kubernetes **Pour l'installation et la configuration complète de Cert-Manager, référez-vous aux guides dédiés :** * [Guide Cert-Manager avec challenge HTTP-01](/guides/cert-manager/challenge-http) - Validation via HTTP (recommandé pour la plupart des cas) * [Guide Cert-Manager avec challenge DNS-01](/guides/cert-manager/challenge-dns) - Validation via DNS (requis pour les certificats wildcard) Ces guides incluent les prérequis, l'installation pas-à-pas, et des exemples complets de configuration. ::: **Types de challenges ACME :** Let's Encrypt utilise le protocole **ACME** pour valider que vous contrôlez le nom de domaine avant d'émettre un certificat. Deux méthodes principales : | Challenge | Description | Cas d'usage | Prérequis | | ----------- | --------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ----------------------------------------------------- | | **HTTP-01** | Validation via fichier HTTP accessible sur `http://domaine/.well-known/acme-challenge/` | Domaines individuels avec Ingress accessible publiquement | Port 80 accessible, Ingress fonctionnel | | **DNS-01** | Validation via enregistrement TXT DNS | Certificats **wildcard** (`*.domaine.com`), domaines non exposés publiquement | Accès API du provider DNS (Cloudflare, Route53, etc.) | :::info **Recommandation :** * Utilisez **HTTP-01** pour les cas standards (simple à mettre en œuvre) * Utilisez **DNS-01** pour les certificats wildcard ou si le port 80 n'est pas accessible depuis internet ::: **Exemple simplifié de ClusterIssuer Let's Encrypt (HTTP-01) :** ```yaml showLineNumbers [cluster-issuer-prod.yaml] apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-prod spec: acme: # URL du serveur Let's Encrypt (production) server: https://acme-v02.api.letsencrypt.org/directory email: admin@votre-domaine.com privateKeySecretRef: name: letsencrypt-prod-account-key solvers: # Challenge HTTP-01 via Ingress - http01: ingress: class: public ``` **Utilisation avec un `Ingress` :** ```yaml showLineNumbers [ingress-with-cluster-issuer.yaml] apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: mon-app-auto-tls namespace: production annotations: cert-manager.io/cluster-issuer: letsencrypt-prod spec: ingressClassName: public tls: - hosts: - www.votre-domaine.com - api.votre-domaine.com secretName: votre-domaine-tls # Cert-Manager créera automatiquement ce Secret rules: - host: www.votre-domaine.com http: paths: - path: / pathType: Prefix backend: service: name: frontend port: number: 80 - host: api.votre-domaine.com http: paths: - path: / pathType: Prefix backend: service: name: backend-api port: number: 8080 ``` ##### Méthode 3 : ACME via l'`Ingress Controller` HAProxy SdV active par défaut la prise en charge du protocole ACME v2 directement au niveau de l'`Ingress Controller` HAProxy. La prise en charge est limitée à l'`Ingress` public car la validation du certificat n'est possible que en mode http01 qui nécessite une adresse IP publique. La configuration déployée permet de faire cohabiter ACME dans l'`Ingress Controller` et Cert-Manager. Le choix se fait dans les annotations de l'object `Ingress` (voir dans les exemples fournis). L'utilisation de acme dans l'ingress est très simple, voici un exemple : ```yaml showLineNumbers [ingress-with-cert-signer.yaml] apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: mon-app-acme-tls annotations: ingress.kubernetes.io/cert-signer: acme spec: ingressClassName: public tls: - hosts: - www.votre-domaine.com - api.votre-domaine.com secretName: votre-domaine-tls # HAProxy créera automatiquement ce Secret rules: - host: www.votre-domaine.com http: paths: - path: / pathType: ImplementationSpecific backend: service: name: frontend port: number: 80 - host: api.votre-domaine.com http: paths: - path: / pathType: Prefix backend: service: name: backend-api port: number: 8080 ``` #### Vérification des certificats ```bash [Terminal] # Vérifier l'état du certificat géré par Cert-Manager kubectl get certificate -n production kubectl describe certificate votre-domaine-tls -n production # Vérifier le Secret TLS créé kubectl get secret votre-domaine-tls -n production # Tester le certificat via OpenSSL openssl s_client -connect www.votre-domaine.com:443 -servername www.votre-domaine.com # Vérifier l'expiration du certificat echo | openssl s_client -connect www.votre-domaine.com:443 -servername www.votre-domaine.com 2>/dev/null | openssl x509 -noout -dates ``` #### Renouvellement automatique Cert-Manager renouvelle automatiquement les certificats **30 jours avant leur expiration**. Vous pouvez forcer un renouvellement : ```bash [Terminal] # Forcer le renouvellement d'un certificat kubectl delete certificaterequest -n production -l cert-manager.io/certificate-name=votre-domaine-tls # Ou supprimer le Secret pour forcer une régénération kubectl delete secret votre-domaine-tls -n production ``` #### Bonnes pratiques TLS :::tip **Recommandations de sécurité :** * **Activez TLS sur tous les Ingress publics** en production * Utilisez **Cert-Manager** pour automatiser la gestion des certificats * Configurez `ssl-redirect: "true"` pour forcer HTTPS * Utilisez des certificats **wildcard** (`*.votre-domaine.com`) pour simplifier la gestion multi-services * **Testez** régulièrement vos certificats avec [SSL Labs](https://www.ssllabs.com/ssltest/) * **Surveillez** les dates d'expiration des certificats (idéalement avec Prometheus/Grafana) * N'exposez **jamais** les clés privées dans des `ConfigMaps` ou logs * Utilisez des **certificats séparés** par environnement (dev, staging, prod) ::: ### Routage TCP/UDP Les contrôleurs `Ingress` HAProxy sont limités aux protocoles **HTTP et HTTPS** sur les ports 80 et 443.\ Pour exposer des services utilisant **d'autres protocoles TCP ou UDP** (bases de données, services RPC, protocoles custom), il est nécessaire d'utiliser des **Services de type LoadBalancer** avec MetalLB. :::warning **Prérequis :** Le routage TCP/UDP nécessite une **configuration réseau spécifique** au niveau de l'infrastructure. Cette fonctionnalité n'est **pas activée par défaut** sur les clusters. **Contactez les équipes techniques ou commerciales SdV** pour : * Valider la faisabilité technique de votre besoin * Obtenir le pool d'adresses IP publiques allouées (MetalLB) * Configurer les règles de routage et firewall appropriées * Mettre en place la supervision réseau associée ::: #### MetalLB : LoadBalancer pour Kubernetes **MetalLB** est un load-balancer réseau pour Kubernetes qui permet d'allouer des adresses IP externes aux Services de type `LoadBalancer` dans des environnements bare-metal ou sur des infrastructures qui ne fournissent pas nativement ce service. **Fonctionnement :** 1. Vous créez un Service de type `LoadBalancer` 2. MetalLB alloue une adresse IP depuis le pool configuré par SdV 3. MetalLB annonce cette adresse IP sur le réseau via BGP 4. Le trafic vers cette adresse IP est routé directement vers les `Nodes` kubernetes qui font suivre vers les `Pods` du `Service` #### Cas d'usage des `Services` `LoadBalancer` **Exemples d'applications nécessitant TCP/UDP :** * **Bases de données** : PostgreSQL (5432), MySQL (3306), MongoDB (27017), Redis (6379) * **Messagerie** : RabbitMQ (5672, 15672), Kafka (9092), MQTT (1883) * **Protocoles personnalisés** : Applications legacy, protocoles métiers * **Transfert de fichiers** : FTP (20, 21), SFTP (22) * **RPC** : gRPC sur TCP (sans HTTP/2), Thrift * **Gaming** : Serveurs de jeux (ports UDP variables) * **VoIP et Streaming** : SIP, RTP, RTSP * **DNS custom** : Serveurs DNS internes (53/UDP et TCP) #### Configuration des `Services` `LoadBalancer` Suite à l'activation de MetalLB par SdV, vous recevrez un **pool d'adresses IP** dédié à votre cluster. ##### Exemple 1 : Base de données PostgreSQL (TCP) ```yaml showLineNumbers [service-tcp.yaml] apiVersion: v1 kind: Service metadata: name: postgres-external namespace: production labels: app: postgres spec: type: LoadBalancer # Adresse IP fournie par SdV dans le pool MetalLB loadBalancerIP: "203.0.113.10" selector: app: postgres tier: database ports: - name: postgres protocol: TCP port: 5432 targetPort: 5432 ``` **Validation :** ```bash [Terminal] # Vérifier l'allocation de l'IP externe kubectl get svc postgres-external -n production # Attendez que EXTERNAL-IP affiche l'IP configurée # Tester la connexion depuis l'extérieur psql -h 203.0.113.10 -U username -d database ``` ##### Exemple 2 : Serveur de jeu (UDP) ```yaml showLineNumbers [service-udp.yaml] apiVersion: v1 kind: Service metadata: name: gameserver-udp namespace: gaming spec: type: LoadBalancer loadBalancerIP: "203.0.113.20" selector: app: gameserver ports: - name: game-udp protocol: UDP port: 7777 targetPort: 7777 - name: game-query protocol: UDP port: 27015 targetPort: 27015 ``` ##### Exemple 3 : Partage d'IP entre plusieurs services Vous pouvez **partager une même IP externe** entre plusieurs Services si : * Vous utilisez la **même clé** `metallb.io/allow-shared-ip` * Les Services déclarent des **ports différents** (pas de conflit TCP/UDP sur le même port) * Les Services déclarent la **même IP** dans `loadBalancerIP` :::code-group ```yaml showLineNumbers [service-loadbalancer-pg.yaml] # Service 1 : PostgreSQL sur 5432/TCP apiVersion: v1 kind: Service metadata: name: postgres namespace: production annotations: metallb.io/allow-shared-ip: "shared-db-ip" spec: type: LoadBalancer loadBalancerIP: "203.0.113.30" selector: app: postgres ports: - port: 5432 protocol: TCP ``` ```yaml showLineNumbers [service-loadbalancer-redis.yaml] # Service 2 : Redis sur 6379/TCP (même IP, port différent) apiVersion: v1 kind: Service metadata: name: redis namespace: production annotations: metallb.io/allow-shared-ip: "shared-db-ip" # Même clé // [!code hl] spec: type: LoadBalancer loadBalancerIP: "203.0.113.30" # Même IP // [!code hl] selector: app: redis ports: - port: 6379 protocol: TCP ``` ::: :::warning **Attention au partage d'IP :** * Le partage d'IP nécessite une **gestion rigoureuse** des ports pour éviter les conflits * Documentez clairement les ports utilisés sur chaque IP partagée * Privilégiez le partage pour des services liés (ex: plusieurs bases de données) * Ne partagez **jamais** d'IP entre environnements (dev, staging, prod) Pour plus d'informations, consultez la [documentation MetalLB](https://metallb.universe.tf/usage/). ::: #### `ExternalTrafficPolicy` : conservation de l'IP source Le champ `externalTrafficPolicy` contrôle comment le trafic externe est routé vers les `Pods` : | Valeur | Comportement | Cas d'usage | | -------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------ | | **Cluster** (défaut) | Le trafic peut être routé vers n'importe quel nœud, puis SNAT vers le `Pod` | Load-balancing global, pas besoin de l'IP source | | **Local** | Le trafic est routé uniquement vers les `Pods` sur le nœud qui reçoit la requête | Logs avec IP source réelle, audit, sécurité | :::warning **Limitation :** * en mode `externalTrafficPolicy: Local`, il n'y a pas de load-balancing mais uniquement de la haute disponibilité si vous avez des `Pods` sur plusieurs nœuds. Cette limitation est propre à l'implémentation chez SdV. Elle pourrait être disparaître dans le futur avec l'évolution de notre infrastructure. ::: :::tip **Recommandation :** * Utilisez `externalTrafficPolicy: Local` si vous avez besoin de l'**IP source réelle** du client (logs, audit, restrictions par IP) * Utilisez `Cluster` (défaut) pour un meilleur load-balancing au détriment de la conservation de l'IP source * Avec `Local`, assurez-vous d'avoir des `Pods` sur plusieurs nœuds pour la haute disponibilité ::: **Exemple avec conservation de l'IP source :** ```yaml showLineNumbers [service.yaml] apiVersion: v1 kind: Service metadata: name: api-external namespace: production spec: type: LoadBalancer loadBalancerIP: "203.0.113.40" externalTrafficPolicy: Local # L'IP source du client est préservée selector: app: api-gateway ports: - port: 8080 protocol: TCP ``` #### Sécurisation des Services LoadBalancer :::tip **Bonnes pratiques de sécurité :** **1. Privilégiez le réseau privé** * Utilisez la VIP **privée** (accessible uniquement via VPN) pour les services sensibles * Exposez en **public** uniquement si absolument nécessaire **2. Restriction par IP source** * Utilisez `loadBalancerSourceRanges` pour limiter l'accès par plages IP : ```yaml spec: type: LoadBalancer loadBalancerSourceRanges: - "203.0.113.0/24" # Votre réseau d'entreprise - "198.51.100.50/32" # IP spécifique autorisée ``` **3. Authentification et chiffrement** * Implémentez l'authentification applicative (TLS client, OAuth, API keys) * Activez le chiffrement au niveau application (TLS/SSL) * Pour PostgreSQL/MySQL : utilisez SSL/TLS obligatoire **4. NetworkPolicies** * Limitez les flux réseau entrants/sortants au niveau `Pod` avec des [`NetworkPolicies`](#networkpolicies) **5. Monitoring et alerting** * Surveillez les connexions suspectes (nombre, origine, patterns) * Configurez des alertes sur les tentatives de connexion échouées * Utilisez des outils comme Falco pour la détection d'intrusion ::: **Exemple de Service sécurisé :** ```yaml showLineNumbers [service-lb.yaml] apiVersion: v1 kind: Service metadata: name: postgres-secure namespace: production spec: type: LoadBalancer loadBalancerIP: "10.0.1.50" # IP privée externalTrafficPolicy: Local # Restriction par IP source loadBalancerSourceRanges: - "10.0.0.0/8" # Réseau interne - "172.16.0.0/12" # Réseau VPN selector: app: postgres ports: - port: 5432 protocol: TCP ``` ### Egress réseau (sortie du cluster) #### Passerelle de sortie commune Tous les clusters Kubernetes installés dans le réseau d’un client partagent la **même passerelle de sortie** (egress gateway) pour accéder à Internet ou à des ressources externes. Cela signifie que, quelle que soit la source (`Pod`, `Node`, `Namespace`), le trafic sortant du cluster passera par cette passerelle unique, ce qui facilite la gestion des flux, la traçabilité et l’application de politiques de sécurité côté client. :::info **Conséquence :** L’adresse IP publique vue par les services externes pour tout trafic sortant du cluster sera la même pour tous les clusters du client. ::: #### Trouver l’IP de la passerelle de sortie Pour identifier l’IP de la passerelle utilisée par votre cluster, vous pouvez lancer un Pod temporaire et observer la route par défaut : ```bash [Terminal] # Lancer un pod temporaire kubectl run -it --rm debug \ --image=curlimages/curl \ --restart=Never \ -- curl -s 'https://api.ipify.org?format=json' ``` La sortie affichera une ligne du type : ```json { "ip": "213.225.160.2" } ``` ### `NetworkPolicies` Les **`NetworkPolicies`** Kubernetes permettent de contrôler les flux réseau au niveau des `Pods`, agissant comme un firewall applicatif distribué. #### Concepts de base Par défaut, Kubernetes autorise **toutes les communications** entre `Pods`. Les `NetworkPolicies` permettent de : * **Restreindre** les flux entrants (Ingress) et sortants (Egress) * Implémenter une **micro-segmentation** réseau * Renforcer la **sécurité** en limitant la surface d'attaque * Respecter le principe du **moindre privilège** réseau :::info Les `NetworkPolicies` nécessitent un **plugin CNI compatible** (Calico, Cilium, Weave Net).\ Sur les clusters SdV, le plugin réseau configuré supporte les `NetworkPolicies`. ::: #### Comportement par défaut * Si **aucune `NetworkPolicy`** ne sélectionne un Pod, tout le trafic est autorisé * Dès qu'une `NetworkPolicy` sélectionne un Pod, **seul le trafic explicitement autorisé** est permis (deny-by-default) * Les règles sont **additives** : si plusieurs NetworkPolicies sélectionnent le même Pod, l'union des règles s'applique #### Exemple 1 : Isolation complète d'un namespace Bloquer tout le trafic entrant et sortant pour tous les `Pods` d'un namespace : ```yaml showLineNumbers [networkpolicy.yaml] apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: deny-all namespace: production spec: podSelector: {} # Sélectionne tous les Pods du namespace policyTypes: - Ingress - Egress # Pas de règles ingress/egress = tout est bloqué ``` #### Exemple 2 : Autoriser uniquement le trafic depuis l'`Ingress` Autoriser un frontend à recevoir du trafic uniquement depuis l'Ingress Controller : ```yaml showLineNumbers [networkpolicy-from-ingress.yaml] apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-from-ingress namespace: production spec: podSelector: matchLabels: app: frontend policyTypes: - Ingress ingress: - from: # Autoriser le trafic depuis les Pods de l'Ingress Controller - namespaceSelector: matchLabels: name: ingress-controller podSelector: matchLabels: app: haproxy-ingress ports: - protocol: TCP port: 8080 ``` #### Exemple 3 : Communication inter-namespaces contrôlée Autoriser un backend à communiquer avec une base de données dans un autre namespace : ```yaml showLineNumbers [networkpolicy-ns-controlled.yaml] apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: postgres-allow-backend namespace: databases spec: podSelector: matchLabels: app: postgres policyTypes: - Ingress ingress: - from: # Autoriser uniquement depuis le namespace "production" - namespaceSelector: matchLabels: name: production podSelector: matchLabels: app: backend-api ports: - protocol: TCP port: 5432 ``` #### Exemple 4 : Règles Egress (trafic sortant) Autoriser un `Pod` à communiquer uniquement avec des services spécifiques : ```yaml showLineNumbers [networkpolicy-egress.yaml] apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: backend-egress namespace: production spec: podSelector: matchLabels: app: backend-api policyTypes: - Egress egress: # Autoriser DNS (nécessaire pour la résolution de noms) - to: - namespaceSelector: matchLabels: name: kube-system podSelector: matchLabels: k8s-app: kube-dns ports: - protocol: UDP port: 53 # Autoriser PostgreSQL - to: - namespaceSelector: matchLabels: name: databases podSelector: matchLabels: app: postgres ports: - protocol: TCP port: 5432 # Autoriser accès HTTPS externe (API tierces) - to: - ipBlock: cidr: 0.0.0.0/0 except: - 10.0.0.0/8 - 172.16.0.0/12 - 192.168.0.0/16 ports: - protocol: TCP port: 443 ``` #### Bonnes pratiques `NetworkPolicies` :::tip **Recommandations d'implémentation :** **1. Approche progressive** * Commencez par **observer** le trafic (logs, monitoring) * Documentez les flux nécessaires * Implémentez les `NetworkPolicies` en **mode test** d'abord * Validez l'absence d'impact sur les applications **2. Stratégie deny-by-default** * Créez une `NetworkPolicy` `deny-all` par namespace * Ajoutez ensuite des règles pour autoriser explicitement les flux requis **3. Labels et sélecteurs** * Utilisez des **labels cohérents** pour identifier les `Pods` * Créez des labels dédiés aux `NetworkPolicies` (ex: `network-policy: allow-ingress`) * Documentez votre stratégie de labelling **4. N'oubliez pas le DNS** * Autorisez toujours le trafic vers `kube-dns` (port 53/UDP) pour la résolution de noms * Sans DNS, les communications par nom de `Service` échoueront **5. Monitoring et audit** * Surveillez les logs réseau pour identifier les communications bloquées * Utilisez des outils comme Cilium Hubble pour visualiser les flux réseau * Testez régulièrement les `NetworkPolicies` après des déploiements ::: #### Validation et troubleshooting ```bash [Terminal] # Lister les NetworkPolicies d'un namespace kubectl get networkpolicy -n production # Afficher les détails d'une NetworkPolicy kubectl describe networkpolicy allow-from-ingress -n production # Tester la connectivité depuis un Pod kubectl exec -it mon-pod -n production -- curl http://service-cible:8080 # Tester la résolution DNS kubectl exec -it mon-pod -n production -- nslookup kubernetes.default # Vérifier les règles appliquées sur un nœud (si Calico) kubectl exec -n kube-system calico-node-xxxxx -- iptables-save | grep production ``` ### Troubleshooting réseau Cette section détaille les commandes et méthodes pour diagnostiquer les problèmes réseau courants. #### Problèmes d'`Ingress` ##### Symptôme : Site inaccessible via nom de domaine ```bash [Terminal] # 1. Vérifier la résolution DNS dig www.votre-domaine.com nslookup www.votre-domaine.com # 2. Vérifier l'Ingress kubectl get ingress -n production kubectl describe ingress mon-ingress -n production # 3. Vérifier le Service kubectl get svc mon-application -n production # 4. Vérifier les Pods backend kubectl get pods -n production -l app=mon-application # 5. Vérifier les logs de l'Ingress Controller kubectl logs -n ingress-controller -l app=haproxy-ingress --tail=100 # 6. Tester depuis un Pod dans le cluster kubectl run -it --rm debug --image=curlimages/curl --restart=Never -- \ curl -v -H "Host: www.votre-domaine.com" http://mon-application.production.svc.cluster.local ``` ##### Symptôme : Erreur 502 Bad Gateway ```bash [Terminal] # Vérifier que les `Pods` backend sont en Running et Ready kubectl get pods -n production -l app=mon-application -o wide # Vérifier les logs applicatifs kubectl logs -n production mon-application-xxxxx --tail=50 # Vérifier que le port targetPort correspond bien au port d'écoute de l'application kubectl get svc mon-application -n production -o yaml | grep -A 5 ports # Tester la connectivité directe au Pod kubectl exec -it debug-pod -n production -- curl http://:8080 ``` ##### Symptôme : Certificat TLS invalide ou auto-signé ```bash [Terminal] # Vérifier le Secret TLS kubectl get secret mon-certificat-tls -n production # Vérifier le contenu du certificat kubectl get secret mon-certificat-tls -n production -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -text -noout # Vérifier l'état du Certificate (si Cert-Manager) kubectl get certificate -n production kubectl describe certificate mon-certificat -n production # Vérifier les logs de Cert-Manager kubectl logs -n cert-manager -l app=cert-manager --tail=100 # Forcer un renouvellement kubectl delete certificaterequest -n production -l cert-manager.io/certificate-name=mon-certificat ``` #### Problèmes de Service LoadBalancer ##### Symptôme : EXTERNAL-IP reste en `` ```bash # Vérifier les événements du Service kubectl describe svc mon-service-lb -n production # Contacter les équipes SdV si le pool IP n'est pas configuré ``` ##### Symptôme : Impossible de se connecter à l'IP externe ```bash # Vérifier que le Service a bien une EXTERNAL-IP kubectl get svc mon-service-lb -n production # Vérifier les endpoints du Service kubectl get endpoints mon-service-lb -n production # Vérifier que les `Pods` backend sont en Running kubectl get pods -n production -l app=mon-app # Tester la connectivité réseau depuis l'extérieur telnet nc -zv # Vérifier les règles firewall et loadBalancerSourceRanges kubectl get svc mon-service-lb -n production -o yaml | grep -A 10 loadBalancerSourceRanges ``` #### Problèmes de `NetworkPolicy` ##### Symptôme : Communication bloquée entre Pods ```bash [Terminal] # Lister les NetworkPolicies appliquées au Pod source kubectl get networkpolicy -n production # Vérifier les sélecteurs de la NetworkPolicy kubectl describe networkpolicy ma-policy -n production # Vérifier les labels du Pod kubectl get pod mon-pod -n production --show-labels # Tester la connectivité avec netcat/curl depuis le Pod kubectl exec -it mon-pod -n production -- curl -v http://service-cible:8080 # Désactiver temporairement une NetworkPolicy pour tester kubectl delete networkpolicy ma-policy -n production # (N'oubliez pas de la recréer ensuite !) ``` #### Problèmes DNS ##### Symptôme : Échec de résolution de noms ```bash # Vérifier le Service kube-dns kubectl get svc -n kube-system kube-dns # Vérifier les `Pods` CoreDNS kubectl get pods -n kube-system -l k8s-app=kube-dns # Tester la résolution DNS depuis un Pod kubectl run -it --rm debug --image=busybox --restart=Never -- nslookup kubernetes.default # Vérifier la configuration CoreDNS kubectl get configmap coredns -n kube-system -o yaml # Logs de CoreDNS kubectl logs -n kube-system -l k8s-app=kube-dns --tail=50 ``` #### Outils de diagnostic réseau ```bash # Déployer un `Pod` de debug avec outils réseau kubectl run netshoot --rm -it --image=nicolaka/netshoot -- /bin/bash # Une fois dans le Pod, vous avez accès à : # - curl, wget, http # - ping, traceroute, mtr # - nslookup, dig, host # - netcat (nc) # - tcpdump # - iperf3 # Exemple : analyser le trafic réseau tcpdump -i any -n port 8080 # Exemple : tester la bande passante iperf3 -s # Sur le serveur iperf3 -c # Sur le client ``` #### Commandes utiles ```bash # Afficher tous les Services avec leurs EXTERNAL-IP kubectl get svc --all-namespaces -o wide # Afficher tous les Ingress avec leurs hôtes kubectl get ingress --all-namespaces # Afficher les Endpoints pour vérifier la connectivité Service→Pods kubectl get endpoints -n production # Suivre les événements en temps réel kubectl get events -n production --watch # Vérifier les quotas réseau (si configurés) kubectl describe resourcequota -n production # Afficher la configuration réseau d'un Pod kubectl get pod mon-pod -n production -o jsonpath='{.status.podIP}' kubectl get pod mon-pod -n production -o jsonpath='{.spec.containers[0].ports}' ``` ### Limites et considérations de performance #### Limites d'`Ingress` HAProxy | Ressource | Limite recommandée | Notes | | ------------------------ | ------------------------ | -------------------------------------- | | Règles par Ingress | `< 100` hosts/paths | Au-delà, privilégier plusieurs Ingress | | Certificats TLS | `< 50` par Ingress | Utiliser des certificats wildcard | | Connexions simultanées | Selon infrastructure SdV | Contactez SdV pour dimensionnement | | Timeout par défaut | 50s (configurable) | Ajustable via annotations | | Taille max body (upload) | Configurable | Annotation `proxy-body-size` | #### Optimisation de performance :::tip **Bonnes pratiques de performance :** **1. Ingress** * Utilisez des certificats **wildcard** pour réduire le nombre de certificats TLS * Activez le **HTTP/2** (activé par défaut sur HAProxy Ingress) * Configurez le **keepalive** pour réutiliser les connexions **2. Services LoadBalancer** * Dimensionnez correctement le nombre de replicas de votre application * Utilisez des **health checks** pour retirer automatiquement les `Pods` défaillants **3. NetworkPolicies** * Les NetworkPolicies ont un **impact limité** sur les performances réseau * Privilégiez des sélecteurs simples pour faciliter le traitement par le CNI ::: ### Ressources et références #### Documentation officielle * [Kubernetes Networking](https://kubernetes.io/docs/concepts/services-networking/) * [Kubernetes Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) * [Kubernetes NetworkPolicies](https://kubernetes.io/docs/concepts/services-networking/network-policies/) * [Kubernetes Services](https://kubernetes.io/docs/concepts/services-networking/service/) #### Documentation des composants SdV * [HAProxy Ingress - Documentation complète](https://haproxy-ingress.github.io/) * [HAProxy Ingress - Annotations](https://haproxy-ingress.github.io/v0.12/docs/configuration/keys/) * [MetalLB - Documentation officielle](https://metallb.universe.tf/) * [Cert-Manager - Documentation](https://cert-manager.io/docs/) #### Outils et utilitaires * **Testing SSL/TLS** : [SSL Labs](https://www.ssllabs.com/ssltest/) * **Testing DNS** : [DNS Checker](https://dnschecker.org/) * **Debugging réseau** : [netshoot Docker image](https://github.com/nicolaka/netshoot) [Ingress]: https://kubernetes.io/fr/docs/concepts/services-networking/ingress/ ## Gestion des sauvegardes ### Vue d'ensemble La sauvegarde des ressources Kubernetes est un élément critique de la stratégie de résilience et de continuité d'activité. SdV intègre **Velero** comme solution de backup pour l'ensemble des objets du cluster, permettant la sauvegarde et la restauration à la fois des ressources Kubernetes et des volumes persistants. #### Objectifs de la sauvegarde La stratégie de sauvegarde mise en place répond à plusieurs besoins opérationnels : | Objectif | Description | Exemple d'usage | | --------------------------- | ------------------------------------------------ | -------------------------------------------- | | **Disaster Recovery** | Restauration complète après incident majeur | Corruption cluster, panne datacenter | | **Migration de cluster** | Transfert d'applications entre environnements | Migration dev → staging → prod | | **Rollback applicatif** | Retour arrière après déploiement défaillant | Annulation d'un release problématique | | **Clonage d'environnement** | Duplication d'une configuration complète | Reproduction d'un bug en environnement isolé | | **Conformité et audit** | Conservation de l'état du cluster à un instant T | Obligation réglementaire, forensic | :::info Velero est préinstallé et préconfigré sur tous les clusters Kubernetes managés par SdV. L'infrastructure de stockage backend (S3-compatible) est fournie et gérée par SdV. ::: ### Architecture de sauvegarde #### Composants Velero Velero s'appuie sur plusieurs composants pour assurer la sauvegarde et la restauration : | Composant | Type | Rôle | | ------------------------ | --------------------------------------- | ------------------------------------------------- | | **Velero Server** | `Deployment` dans le namespace `velero` | Orchestrateur des opérations de backup/restore | | **Node Agent DaemonSet** | `DaemonSet` sur chaque nœud worker | Sauvegarde des données des `PersistentVolumes` | | **Backend S3** | Stockage objet externe | Stockage distant et résilient des backups | | **Velero CLI** | Outil en ligne de commande | Interface d'administration depuis un poste client | #### Flux de sauvegarde ![Diagramme Velero Simplifié](/cluster/velero-simplifie.png) :::tip Les sauvegardes sont **incrémentales** pour les volumes et **complètes** pour les ressources Kubernetes. Cela optimise l'utilisation du stockage tout en garantissant la cohérence des backups. ::: ### Périmètre de sauvegarde #### Ressources sauvegardées Velero peut sauvegarder l'ensemble des ressources Kubernetes ainsi que leurs données persistantes : ##### Ressources Kubernetes | Type de ressource | Sauvegardé par défaut | Notes | | ------------------------------------------- | --------------------- | -------------------------------------------------------------------- | | `Namespaces` | Oui | Structure et métadonnées | | `Deployments`, `StatefulSets`, `DaemonSets` | Oui | Définitions complètes | | `Services`, `Ingress` | Oui | Configuration réseau | | `ConfigMaps`, `Secrets` | Oui | Configuration applicative | | `PersistentVolumeClaims` | Non | Métadonnées + données si `annotation` spécifique présente sur le pod | | `CustomResourceDefinitions` (CRDs) | Oui | Définitions et instances | | `ServiceAccounts`, `Roles`, `RoleBindings` | Oui | Configuration RBAC | | `NetworkPolicies` | Oui | Politiques réseau | :::warning Les ressources **système** des namespaces `kube-system`, `kube-public`, `kube-node-lease` et `velero` sont **exclues par défaut** pour éviter les conflits lors d'une restauration. Cette configuration peut être ajustée selon les besoins. ::: ##### Volumes persistants | Type de volume | Méthode de sauvegarde | Performances | | ------------------------------------- | ----------------------------------- | ------------------------------------- | | Volumes `NFS` (`managed-nfs-storage`) | **kopia** (snapshot niveau fichier) | Moyennes (scan complet au 1er backup) | | `PersistentVolumes` avec `hostPath` | Non supporté par défaut | ⚠️ Non recommandé en production | | Volumes éphémères (`emptyDir`) | ❌ Non sauvegardé | Nature temporaire | :::info **Optimisation kopia :** Le premier backup d'un volume est complet, les suivants sont incrémentaux (seules les modifications sont sauvegardées). Cela réduit significativement le temps de backup et l'espace de stockage utilisé. ::: #### Filtrage et sélection Velero offre des mécanismes de filtrage pour cibler précisément le périmètre de sauvegarde : | Critère de filtrage | Exemple | Cas d'usage | | ------------------------- | ------------------------------------------ | ---------------------------------------------- | | Par **namespace** | `--include-namespaces production,staging` | Sauvegarder uniquement certains environnements | | Par **label** | `--selector app=webapp,tier=frontend` | Sauvegarder un sous-ensemble d'objets | | Par **type de ressource** | `--include-resources deployments,services` | Sauvegarder uniquement certaines ressources | | Par **exclusion** | `--exclude-namespaces dev,test` | Exclure des environnements non-critiques | ### Installation et configuration du CLI #### Prérequis Pour interagir avec Velero, vous devez disposer de : 1. **Accès au cluster Kubernetes** : fichier `kubeconfig` fourni par SdV (généralement nommé `kube_config_rke_config.yml`) 2. **Connexion réseau** : accès à l'API Kubernetes (via VPN si cluster en mode privé) 3. **Droits suffisants** : permissions RBAC pour lire/créer des ressources Velero #### Téléchargement du CLI Velero CLI est disponible pour les principaux systèmes d'exploitation : | OS | Architecture | Lien de téléchargement | | ----------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------- | | **Linux** | `amd64` | [velero-v1.17.1-linux-amd64.tar.gz](https://github.com/vmware-tanzu/velero/releases/download/v1.17.1/velero-v1.17.1-linux-amd64.tar.gz) | | **macOS** | `amd64` | [velero-v1.17.1-darwin-amd64.tar.gz](https://github.com/vmware-tanzu/velero/releases/download/v1.17.1/velero-v1.17.1-darwin-amd64.tar.gz) | | **Windows** | `amd64` | [velero-v1.17.1-windows-amd64.tar.gz](https://github.com/vmware-tanzu/velero/releases/download/v1.17.1/velero-v1.17.1-windows-amd64.tar.gz) | :::tip **Version recommandée :** v1.17.1 (version préinstallée sur les clusters SdV) **Dernière version stable :** Consultez [la page des releases GitHub](https://github.com/vmware-tanzu/velero/releases) pour les versions ultérieures. ::: #### Installation :::code-group ```bash [Linux] # Télécharger et extraire wget https://github.com/vmware-tanzu/velero/releases/download/v1.17.1/velero-v1.17.1-linux-amd64.tar.gz tar -xvzf velero-v1.17.1-linux-amd64.tar.gz # Copier le binaire dans le PATH sudo mv velero-v1.17.1-linux-amd64/velero /usr/local/bin/ # Vérifier l'installation velero version --client-only ``` ```bash [macOS] # Installation via Homebrew brew install velero # Vérifier l'installation velero version --client-only ``` ```powershell [Windows] # Télécharger l'archive Invoke-WebRequest -Uri "https://github.com/vmware-tanzu/velero/releases/download/v1.17.1/velero-v1.17.1-windows-amd64.tar.gz" -OutFile "velero.tar.gz" # Extraire et copier dans C:\Program Files\velero\ # Ajouter C:\Program Files\velero\ au PATH système # Vérifier l'installation velero version --client-only ``` ::: #### Configuration du kubeconfig Pour que le CLI Velero puisse communiquer avec votre cluster, vous devez spécifier le fichier `kubeconfig` fourni par SdV : ##### Méthode 1 : Variable d'environnement (recommandée) ```bash [Terminal] # Définir la variable d'environnement (à ajouter dans ~/.bashrc ou ~/.zshrc) export KUBECONFIG=/chemin/vers/kube_config_rke_config.yml # Vérifier la connexion kubectl cluster-info velero version ``` ##### Méthode 2 : Option en ligne de commande ```bash [Terminal] # Spécifier le kubeconfig à chaque commande velero backup get --kubeconfig=/chemin/vers/kube_config_rke_config.yml ``` :::tip **Bonne pratique :** Définissez la variable `KUBECONFIG` de manière permanente dans votre profil shell pour éviter de la spécifier à chaque commande. ::: #### Validation de l'installation Une fois le CLI installé et configuré, vérifiez la communication avec le cluster : ```bash [Terminal] # Vérifier la version du CLI et du serveur Velero velero version # Lister les backups existants velero backup get # Vérifier la configuration du backend de stockage velero backup-location get # Vérifier le statut de la sauvegarde des volumes persistants velero repo get ``` **Sortie attendue :** ``` Client: Version: v1.17.1 Git commit: - Server: Version: v1.17.1 ``` :::warning Si la version du serveur n'apparaît pas, vérifiez : * Votre connexion au cluster (VPN actif si cluster privé) * Les permissions RBAC de votre utilisateur Kubernetes * La présence du namespace `velero` dans le cluster : `kubectl get ns velero` ::: ### Opérations de sauvegarde #### Sauvegarde ponctuelle ##### Sauvegarde complète d'un namespace ```bash [Terminal] # Sauvegarder tous les objets d'un namespace velero backup create backup-production-$(date +%Y%m%d-%H%M%S) \ --include-namespaces production \ --wait # Surveiller la progression velero backup describe backup-production-20260219-143000 --details ``` **Paramètres :** * `--include-namespaces` : liste des namespaces à sauvegarder * `--wait` : attend la fin du backup avant de rendre la main ##### Sauvegarde sélective par labels ```bash [Terminal] # Sauvegarder uniquement les ressources avec un label spécifique velero backup create backup-webapp-$(date +%Y%m%d-%H%M%S) \ --selector app=webapp,environment=production \ ``` ##### Sauvegarde multi-namespaces ```bash [Terminal] # Sauvegarder plusieurs namespaces velero backup create backup-multi-ns-$(date +%Y%m%d-%H%M%S) \ --include-namespaces production,staging,integration \ ``` ##### Sauvegarde complète du cluster ```bash [Terminal] # Sauvegarder l'intégralité du cluster (hors namespaces système) velero backup create backup-cluster-complet-$(date +%Y%m%d-%H%M%S) \ --exclude-namespaces kube-system,kube-public,kube-node-lease,velero \ ``` :::warning Une sauvegarde complète du cluster peut prendre **plusieurs heures** selon le nombre de ressources et la taille des volumes. Planifiez ces opérations en dehors des heures de pointe. ::: #### Sauvegarde planifiée (Schedule) Les sauvegardes planifiées garantissent une protection continue sans intervention manuelle. ##### Créer une sauvegarde quotidienne ```bash [Terminal] # Sauvegarde quotidienne à 2h00 du matin velero schedule create daily-production \ --schedule="0 2 * * *" \ --include-namespaces production \ --ttl 720h0m0s ``` **Paramètres :** * `--schedule` : expression cron (format standard Unix) * `--ttl` : durée de rétention (ici 30 jours = 720 heures) ##### Exemples d'expressions cron | Expression | Description | Heure d'exécution | | ------------- | ----------------------------- | ----------------- | | `0 2 * * *` | Tous les jours à 2h00 | Quotidien | | `0 3 * * 0` | Tous les dimanches à 3h00 | Hebdomadaire | | `0 4 1 * *` | Le 1er de chaque mois à 4h00 | Mensuel | | `0 */6 * * *` | Toutes les 6 heures | Toutes les 6h | | `0 0 * * 1-5` | Du lundi au vendredi à minuit | Jours ouvrés | ##### Lister les sauvegardes planifiées ```bash [Terminal] # Afficher toutes les schedules velero schedule get # Détails d'une schedule velero schedule describe daily-production ``` ##### Modifier ou supprimer une schedule ```bash [Terminal] # Supprimer une schedule velero schedule delete daily-production # Mettre en pause temporairement (la schedule reste définie mais n'est plus exécutée) kubectl patch schedule daily-production -n velero --type merge -p '{"spec":{"paused":true}}' # Réactiver une schedule en pause kubectl patch schedule daily-production -n velero --type merge -p '{"spec":{"paused":false}}' ``` #### Surveiller les sauvegardes ##### Lister les backups ```bash [Terminal] # Lister tous les backups velero backup get # Filtrer par statut velero backup get --selector velero.io/schedule-name=daily-production # Afficher les backups des 7 derniers jours velero backup get --selector 'velero.io/backup.expiration-date>'$(date -d '7 days ago' +%Y-%m-%d) ``` ##### Vérifier le statut d'un backup ```bash [Terminal] # Statut résumé velero backup describe backup-production-20260219-143000 # Statut détaillé avec liste des erreurs éventuelles velero backup describe backup-production-20260219-143000 --details # Consulter les logs velero backup logs backup-production-20260219-143000 ``` **États possibles :** * `New` : backup créé mais pas encore démarré * `InProgress` : backup en cours * `Completed` : backup terminé avec succès * `PartiallyFailed` : backup terminé mais avec des erreurs non-bloquantes * `Failed` : backup échoué ##### Télécharger un backup localement ```bash [Terminal] # Télécharger les métadonnées et les ressources d'un backup velero backup download backup-production-20260219-143000 \ --output-dir ./backup-export/ # Cette commande télécharge : # - backup-production-20260219-143000.tar.gz (ressources Kubernetes) # - backup-production-20260219-143000-logs.gz (logs du backup) ``` :::info Le téléchargement local d'un backup est utile pour : * Audit et conformité * Transfert vers un autre cluster Velero * Analyse forensic après incident ::: #### Suppression de backups ```bash [Terminal] # Supprimer un backup unique velero backup delete backup-production-20260219-143000 # Supprimer tous les backups expirés velero backup delete --expired # Supprimer tous les backups d'une schedule velero backup delete --selector velero.io/schedule-name=daily-production --confirm ``` :::warning La suppression d'un backup supprime également les données associées dans le backend S3. Cette opération est **irréversible**. ::: ### Opérations de restauration #### Restauration complète ##### Restaurer un namespace complet ```bash [Terminal] # Restaurer un namespace depuis un backup velero restore create restore-production-$(date +%Y%m%d-%H%M%S) \ --from-backup backup-production-20260219-143000 \ --wait # Surveiller la progression velero restore describe restore-production-20260219-143000 --details ``` ##### Restaurer plusieurs namespaces ```bash [Terminal] # Restaurer plusieurs namespaces spécifiques velero restore create restore-multi-ns-$(date +%Y%m%d-%H%M%S) \ --from-backup backup-multi-ns-20260219-143000 \ --include-namespaces production,staging ``` #### Restauration sélective ##### Restaurer uniquement certaines ressources ```bash [Terminal] # Restaurer uniquement les Deployments et Services velero restore create restore-partial-$(date +%Y%m%d-%H%M%S) \ --from-backup backup-production-20260219-143000 \ --include-resources deployments,services \ --include-namespaces production ``` ##### Restaurer avec mapping de namespace ```bash [Terminal] # Restaurer dans un namespace différent (utile pour tests) velero restore create restore-production-to-test-$(date +%Y%m%d-%H%M%S) \ --from-backup backup-production-20260219-143000 \ --namespace-mappings production:test-production ``` **Cas d'usage :** * Cloner un environnement de production vers staging * Tester une restauration sans impacter la production * Debugger un problème dans un environnement isolé ##### Restaurer par labels ```bash [Terminal] # Restaurer uniquement les ressources avec un label spécifique velero restore create restore-webapp-$(date +%Y%m%d-%H%M%S) \ --from-backup backup-production-20260219-143000 \ --selector app=webapp,tier=frontend ``` #### Gestion des conflits Par défaut, Velero **ne restaure pas** les ressources déjà existantes dans le cluster pour éviter les écrasements accidentels. ##### Forcer la restauration (écraser les ressources existantes) ```bash [Terminal] # Restaurer en écrasant les ressources existantes velero restore create restore-force-$(date +%Y%m%d-%H%M%S) \ --from-backup backup-production-20260219-143000 \ --existing-resource-policy=update ``` **Politiques disponibles :** * `none` (défaut) : ignore les ressources existantes * `update` : met à jour les ressources existantes avec les données du backup :::warning L'option `--existing-resource-policy=update` peut **écraser des ressources en production**. Utilisez-la avec précaution et testez d'abord dans un namespace dédié. ::: #### Surveiller les restaurations ```bash [Terminal] # Lister toutes les restaurations velero restore get # Détails d'une restauration velero restore describe restore-production-20260219-143000 # Logs de restauration velero restore logs restore-production-20260219-143000 ``` **États possibles :** * `New` : restauration créée mais pas encore démarrée * `InProgress` : restauration en cours * `Completed` : restauration terminée avec succès * `PartiallyFailed` : restauration terminée mais avec des erreurs non-bloquantes * `Failed` : restauration échouée #### Validation post-restauration Après une restauration, vérifiez systématiquement l'état du cluster : ```bash [Terminal] # Vérifier les Pods du namespace restauré kubectl get pods -n production # Vérifier les événements récents kubectl get events -n production --sort-by='.lastTimestamp' # Vérifier les PVC et leur montage kubectl get pvc -n production kubectl describe pvc -n production # Tester la connectivité applicative kubectl run test-curl --image=curlimages/curl:latest -it --rm -- sh # Dans le pod : curl http://mon-service.production.svc.cluster.local ``` :::tip **Check-list post-restauration :** 1. Tous les `Pods` sont en état `Running` 2. Les `PersistentVolumeClaims` sont en état `Bound` 3. Les `Services` répondent correctement (test de connectivité) 4. Les `Ingress` sont accessibles depuis l'extérieur 5. Les données applicatives sont cohérentes (vérification métier) ::: ### Bonnes pratiques #### Stratégie de sauvegarde ##### Fréquence de sauvegarde recommandée | Environnement | Fréquence | Rétention | Justification | | ----------------- | ------------------ | --------- | -------------------------------------------- | | **Production** | Quotidienne (2h00) | 30 jours | Balance entre protection et coût de stockage | | **Staging** | Hebdomadaire | 14 jours | Environnement moins critique | | **Développement** | Optionnel | 7 jours | Données facilement reconstituables | ##### Sauvegardes avant maintenance Effectuez **toujours** une sauvegarde immédiatement avant : * Mise à jour majeure d'une application * Modification de la configuration réseau ou stockage * Intervention sur l'infrastructure du cluster * Opération de migration ou de réorganisation ```bash [Terminal] # Backup pré-maintenance avec annotation velero backup create pre-maintenance-$(date +%Y%m%d-%H%M%S) \ --include-namespaces production \ --labels maintenance=true,date=$(date +%Y%m%d) ``` #### Sécurité et confidentialité ##### Protection des Secrets Les objets `Secret` Kubernetes sont sauvegardés par Velero. Pour une sécurité renforcée : ```bash [Terminal] # Exclure les Secrets de la sauvegarde (si géré par un vault externe) velero backup create backup-no-secrets-$(date +%Y%m%d-%H%M%S) \ --include-namespaces production \ --exclude-resources secrets ``` Alternativement, utilisez une solution de gestion de secrets externe (HashiCorp Vault, Sealed Secrets, etc.) pour ne pas persister les secrets sensibles dans les backups. ##### Contrôle d'accès RBAC Limitez l'accès aux opérations Velero via des `RoleBindings` Kubernetes : ```yaml showLineNumbers [rolebinding.yaml] apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: velero-operator namespace: production subjects: - kind: User name: ops-team@sdv.fr apiGroup: rbac.authorization.k8s.io roleRef: kind: ClusterRole name: velero-operator apiGroup: rbac.authorization.k8s.io ``` #### Tests de restauration :::warning **Un backup non testé est un backup potentiellement inutilisable.** ::: ##### Fréquence de test recommandée | Fréquence | Type de test | Environnement cible | | ------------------- | --------------------------------------- | -------------------------- | | **Mensuel** | Restauration complète d'un namespace | Cluster de test dédié | | **Trimestriel** | Disaster Recovery complet | Nouveau cluster temporaire | | **Avant migration** | Restauration dans l'environnement cible | Cluster destination | ##### Procédure de test type ```bash [Terminal] # 1. Créer un namespace de test kubectl create namespace test-restore-$(date +%Y%m%d) # 2. Restaurer le backup dans ce namespace velero restore create test-restore-$(date +%Y%m%d-%H%M%S) \ --from-backup backup-production-20260219-143000 \ --namespace-mappings production:test-restore-20260219 # 3. Valider le fonctionnement kubectl get all -n test-restore-20260219 # 4. Nettoyer après validation kubectl delete namespace test-restore-20260219 ``` #### Monitoring et alerting ##### Vérification automatique des backups Mettez en place un monitoring automatique pour détecter les échecs de backup : ```bash [Terminal] # Lister les backups en erreur velero backup get | grep -E "Failed|PartiallyFailed" # Vérifier qu'au moins un backup récent existe velero backup get | grep "Completed" | head -1 ``` **Métriques à surveiller :** * Statut du dernier backup (succès/échec) * Temps écoulé depuis le dernier backup réussi * Taille des backups (détection de dérives) * Durée d'exécution des backups (dégradation des performances) ##### Intégration avec Prometheus Velero expose des métriques Prometheus accessibles via un `ServiceMonitor` : ```yaml showLineNumbers [servicemonitor.yaml] apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: velero namespace: velero spec: selector: matchLabels: app.kubernetes.io/name: velero endpoints: - port: monitoring path: /metrics ``` **Métriques clés :** * `velero_backup_total` : nombre total de backups créés * `velero_backup_success_total` : nombre de backups réussis * `velero_backup_failure_total` : nombre de backups échoués * `velero_backup_duration_seconds` : durée des backups #### Optimisation des performances ##### Accélérer les sauvegardes ```bash [Terminal] # Exclure les namespaces non-critiques velero backup create backup-optimized-$(date +%Y%m%d-%H%M%S) \ --include-namespaces production \ --exclude-resources events,events.events.k8s.io # Exclure les volumes temporaires velero backup create backup-no-tmpfs-$(date +%Y%m%d-%H%M%S) \ --include-namespaces production \ --snapshot-volumes=false ``` ##### Utiliser les annotations pour contrôler la sauvegarde des volumes persistants ```yaml showLineNumbers [pod.yaml] apiVersion: v1 kind: Pod metadata: name: webapp namespace: production annotations: # Ignorer ce Pod dans les backups de volume persistant backup.velero.io/backup-volumes-excludes: cache-volume,tmp-volume spec: containers: - name: webapp image: webapp:1.0 volumeMounts: - name: data-volume mountPath: /data - name: cache-volume mountPath: /cache volumes: - name: data-volume persistentVolumeClaim: claimName: webapp-data - name: cache-volume emptyDir: {} ``` #### Rétention et nettoyage ##### Définir une politique de rétention ```bash [Terminal] # Schedule avec rétention de 30 jours velero schedule create production-30d \ --schedule="0 2 * * *" \ --include-namespaces production \ --ttl 720h0m0s # Schedule avec rétention de 90 jours (backups mensuels) velero schedule create production-monthly \ --schedule="0 3 1 * *" \ --include-namespaces production \ --ttl 2160h0m0s ``` ##### Nettoyage manuel ```bash [Terminal] # Supprimer les backups de plus de 60 jours velero backup get -o json | jq -r '.items[] | select(.status.expiration < now) | .metadata.name' | xargs -I {} velero backup delete {} # Supprimer tous les backups d'un namespace supprimé velero backup delete --selector velero.io/namespace=ancien-namespace --confirm ``` ### Dépannage et résolution de problèmes #### Problèmes courants ##### Backup bloqué en état `InProgress` **Symptômes :** ```bash [Terminal] velero backup get # NAME STATUS ... # backup-prod InProgress ... ``` **Diagnostic :** ```bash [Terminal] # Vérifier les logs du backup velero backup logs backup-prod # Vérifier les Pods Velero kubectl get pods -n velero # Vérifier les logs du serveur Velero kubectl logs -n velero deployment/velero -f ``` **Solutions :** * Vérifier la connectivité au backend S3 * Vérifier les quotas de stockage * Redémarrer le serveur Velero si nécessaire : `kubectl rollout restart deployment/velero -n velero` ##### Échec de sauvegarde des volumes **Symptômes :** ``` PartiallyFailed: some volumes failed to backup ``` **Diagnostic :** ```bash [Terminal] # Vérifier le status velero repo get # Vérifier les logs du DaemonSet kubectl logs -n velero daemonset/node-agent -f ``` **Solutions possibles :** * Volume non compatible (ex: `hostPath`) * Permissions insuffisantes sur le volume * Ajouter l'annotation `backup.velero.io/backup-volumes` sur les `Pods` ##### Restauration partielle **Symptômes :** ``` PartiallyFailed: some resources failed to restore ``` **Diagnostic :** ```bash [Terminal] # Afficher les erreurs détaillées velero restore describe --details # Vérifier les événements Kubernetes kubectl get events -A --sort-by='.lastTimestamp' | grep -i error ``` **Causes fréquentes :** * `StorageClass` non disponible sur le cluster cible * `CustomResourceDefinitions` manquantes * Conflits de noms avec des ressources existantes * Quotas de ressources dépassés **Solutions :** * Installer les `CRDs` manquantes avant la restauration * Utiliser `--existing-resource-policy=update` si approprié * Augmenter les quotas du namespace #### Collecte d'informations pour le support En cas de problème persistant, collectez les informations suivantes avant de contacter le support SdV : ```bash [Terminal] # Dump complet de la configuration Velero kubectl get all -n velero -o yaml > velero-resources.yaml # Logs du serveur Velero kubectl logs -n velero deployment/velero --tail=500 > velero-server.log # Logs node-agent kubectl logs -n velero daemonset/node-agent --tail=500 --all-containers=true > node-agent.log # Configuration du backend de stockage velero backup-location get -o yaml > backup-locations.yaml # Liste des backups et restaurations récentes velero backup get > backups-list.txt velero restore get > restores-list.txt ``` ### Ressources complémentaires #### Documentation officielle * **Documentation complète Velero v1.17.1** : [velero.io/docs/v1.17](https://velero.io/docs/v1.17/) * **Guide de dépannage** : [troubleshooting](https://velero.io/docs/v1.17/troubleshooting/) * **API Reference** : [api-types](https://velero.io/docs/v1.17/api-types/) #### Pages liées * **Guide pratique Velero** : [Exemples d'utilisation détaillés](/guides/velero) * **Stockage persistant** : [Configuration des StorageClass](stockage) * **Démarrage rapide** : [Premiers pas avec le cluster](demarrer) #### Support SdV Pour toute question ou assistance sur la configuration avancée de Velero : * **Email support technique** : [support@sdv.fr](mailto\:support@sdv.fr) * **Outil de requêtes** : [requete.sdv.fr](https://requete.sdv.fr) * **Hotline** : Consultez votre documentation de livraison pour les coordonnées :::tip Les équipes SdV peuvent vous accompagner pour : * Configuration de schedules personnalisées * Migration de données entre clusters * Audit et optimisation de votre stratégie de backup * Formation à l'utilisation avancée de Velero ::: ## Gestion du stockage ### Vue d'ensemble Le stockage dans Kubernetes est géré via un système de ressources découplant les `Pods` de l'infrastructure de stockage sous-jacente. Ce document décrit les options de stockage disponibles sur le cluster Kubernetes SdV, leurs cas d'usage, leurs limites et les bonnes pratiques associées. #### Concepts clés | Concept | Description | Rôle | | ----------------------------------- | ---------------------------------------------- | -------------------------------------------- | | **`PersistentVolume` (`PV`)** | Ressource de stockage physique dans le cluster | Représente un espace de stockage disponible | | **`PersistentVolumeClaim` (`PVC`)** | Demande de stockage par un utilisateur | Consomme un `PV` et l'attache à un `Pod` | | **`StorageClass`** | Définit les classes de stockage disponibles | Permet le provisionnement dynamique des `PV` | | **`VolumeSnapshot`** | Point de sauvegarde d'un volume | Permet la restauration et la duplication | ### Stockage Persistant SdV propose dans son catalogue une classe de stockage NFS pour le stockage persistant des données des workloads Kubernetes. Cette solution est adaptée au stockage de fichiers nécessitant un accès concurrent depuis plusieurs Pods. #### StorageClasses disponibles | `StorageClass` | Type | Politique de rétention | Provisionnement | Cas d'usage recommandé | | ---------------------------- | --------- | ---------------------- | --------------- | ------------------------------------------------------------------- | | `managed-nfs-storage` | `fichier` | `Delete` | Dynamique | Développement, staging, données temporaires | | `managed-nfs-storage-retain` | `fichier` | `Retain` | Dynamique | Production, données critiques nécessitant une conservation manuelle | ##### Politique de rétention * **`Delete`** : Le `PersistentVolume` et les données associées sont automatiquement supprimés lorsque le `PVC` est détruit. ⚠️ **Risque de perte de données**. * **`Retain`** : Le `PersistentVolume` est conservé même après la suppression du `PVC`. Les données restent disponibles et doivent être supprimées manuellement par un administrateur. :::info **Infrastructure de stockage**\ Les volumes **NFS** sont localisés sur une baie de stockage professionnelle équipée de **snapshot** et **mirroring** pour garantir la disponibilité et la résilience des données. **Politique de sauvegarde :** * **Snapshot** : toutes les **6 heures** * **Rétention** : **4 semaines** * **Mirroring** : actualisé toutes les **30 minutes** Le quota pour cet espace de stockage est ajustable et défini dans notre offre commerciale.\ Pour augmenter votre quota, contactez le service commercial de SdV. ::: #### Stockage en mode fichier (NFS) Cette `StorageClass` est basée sur le protocole **NFS (Network File System)**. Elle permet de partager un système de fichiers entre plusieurs `Pods` simultanément. ##### Caractéristiques | Critère | Valeur | Commentaire | | -------------------- | --------------------- | -------------------------------------------------- | | **Protocole** | NFSv3/NFSv4 | Protocole réseau pour partage de fichiers | | **Performance** | Moyenne (réseau) | Latence dépendante du réseau (\~1-5ms) | | **IOPS** | Faibles à moyennes | Non adapté aux charges intensives en I/O | | **Débit** | \~100-500 MB/s | Dépendant du réseau et de la charge | | **Accès concurrent** | Oui (`ReadWriteMany`) | Plusieurs `Pods` peuvent lire/écrire simultanément | ##### Cas d'usage recommandés ✅ **Adapté pour :** * Applications web stateless (assets, uploads utilisateurs) * Partage de fichiers de configuration entre Pods * Stockage de fichiers médias (images, vidéos, documents) * Logs d'applications à centraliser * Dossiers partagés pour traitement batch * CMS (WordPress, Drupal, etc.) * Applications nécessitant `ReadWriteMany` ❌ **Déconseillé pour :** * Bases de données relationnelles (PostgreSQL, MySQL, MariaDB) * Bases de données NoSQL nécessitant des IOPS élevées (Cassandra, MongoDB) * Applications nécessitant des performances disque élevées * Stockage de données nécessitant une forte cohérence transactionnelle * Workloads avec des milliers d'opérations I/O par seconde ##### Modes d'accès supportés | Mode | Abréviation | Description | Support NFS | | ------------------- | ----------- | ------------------------------------ | ---------------------------- | | **`ReadWriteOnce`** | `RWO` | Lecture-écriture par un seul nœud | Oui | | **`ReadOnlyMany`** | `ROX` | Lecture seule par plusieurs nœuds | Oui | | **`ReadWriteMany`** | `RWX` | Lecture-écriture par plusieurs nœuds | Oui (principal avantage NFS) | ##### Exemple de PersistentVolumeClaim ```yaml showLineNumbersb [pvc.yaml] apiVersion: v1 kind: PersistentVolumeClaim metadata: name: webapp-storage namespace: production labels: app: webapp environment: production annotations: description: "Stockage partagé pour les uploads utilisateurs" spec: storageClassName: managed-nfs-storage-retain accessModes: - ReadWriteMany resources: requests: storage: 10Gi ``` ##### Utilisation dans un Pod ```yaml showLineNumbers [pod.yaml] apiVersion: v1 kind: Pod metadata: name: webapp-pod namespace: production labels: app: webapp spec: containers: - name: webapp image: myapp:1.0.0 volumeMounts: - name: persistent-storage mountPath: /var/www/html/uploads subPath: uploads # Utilise un sous-répertoire du PVC volumes: - name: persistent-storage persistentVolumeClaim: claimName: webapp-storage ``` ##### Exemple avec Deployment ```yaml showLineNumbers [deployment.yaml] apiVersion: apps/v1 kind: Deployment metadata: name: webapp namespace: production spec: replicas: 3 selector: matchLabels: app: webapp template: metadata: labels: app: webapp spec: containers: - name: webapp image: myapp:1.0.0 volumeMounts: - name: shared-data mountPath: /data/shared readOnly: false volumes: - name: shared-data persistentVolumeClaim: claimName: webapp-storage ``` ### Autres types de stockage Kubernetes propose par défaut plusieurs types de volumes pour le stockage. Tous ne sont pas persistants et présentent des caractéristiques différentes. Voir la [documentation officielle](https://kubernetes.io/fr/docs/concepts/storage/volumes) pour une liste exhaustive. #### Stockage objet S3 SdV propose offres de stockage objet compatible S3 pour les besoins de stockage massif, d'archivage ou de distribution de contenu statique. ##### Caractéristiques | Critère | Valeur | | --------------- | ------------------------------------------------ | | **Type** | Stockage objet (Object Storage) | | **Protocole** | API REST S3 (compatible AWS S3) | | **Durabilité** | Très élevée (réplication multi-zones) | | **Scalabilité** | Illimitée | | **Performance** | Optimisé pour le throughput, pas pour la latence | ##### Cas d'usage ✅ **Adapté pour :** * Stockage de backups et archives * Distribution de contenu statique (CDN) * Data lakes et analytics * Stockage d'images et vidéos à grande échelle * Logs applicatifs (via Fluentd, Loki, etc.) * Artefacts de build (images Docker, binaires) ##### Intégration avec Kubernetes Le stockage S3 n'est pas directement monté comme un volume Kubernetes. L'accès se fait via : 1. **SDK applicatif** : Boto3 (Python), AWS SDK (Java, Node.js, Go...) 2. **S3 FUSE** : Monte un bucket S3 comme système de fichiers (performances limitées) ##### Exemple d'utilisation avec des secrets :::code-group ```yaml showLineNumbers [secret.yaml] apiVersion: v1 kind: Secret metadata: name: s3-credentials namespace: production type: Opaque stringData: access-key: "AKIAIOSFODNN7EXAMPLE" secret-key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" ``` ```yaml showLineNumbers [pod.yaml] apiVersion: v1 kind: Pod metadata: name: s3-backup-job namespace: production spec: containers: - name: backup image: amazon/aws-cli:latest command: - sh - -c - | aws s3 cp /data/backup.tar.gz s3://my-bucket/backups/$(date +%Y%m%d)/ env: - name: AWS_ACCESS_KEY_ID valueFrom: secretKeyRef: name: s3-credentials key: access-key - name: AWS_SECRET_ACCESS_KEY valueFrom: secretKeyRef: name: s3-credentials key: secret-key - name: AWS_DEFAULT_REGION value: "eu-west-1" volumeMounts: - name: data mountPath: /data volumes: - name: data emptyDir: {} ``` ::: :::info **Tarification S3**\ SdV propose des offres de stockage S3. Rapprochez-vous du Service Commercial afin d'obtenir la tarification selon vos besoins (volumétrie, bande passante, nombre de requêtes). ::: #### `emptyDir` Un volume `emptyDir` est créé lorsqu'un `Pod` est assigné à un nœud et existe aussi longtemps que le `Pod` s'exécute sur ce nœud. Comme son nom l'indique, le volume est initialement vide. Tous les conteneurs du `Pod` peuvent lire et écrire dans ce volume. ##### Caractéristiques | Critère | Valeur | | ---------------- | ------------------------------------------- | | **Persistance** | Non persistant (supprimé avec le `Pod`) | | **Localisation** | Disque local du nœud ou RAM | | **Partage** | Entre conteneurs d'un même `Pod` uniquement | | **Taille** | Limitée par l'espace disque/RAM du nœud | ##### Cas d'usage ✅ **Adapté pour :** * Cache temporaire d'une application * Échange de données entre conteneurs d'un même `Pod` (sidecars) * Espace de travail pour traitements temporaires * Stockage de fichiers de session éphémères ❌ **Déconseillé pour :** * Données devant survivre au redémarrage du `Pod` * Volumétries importantes (>100 Mo) * Données critiques nécessitant une persistance :::warning **Limitations importantes**\ Les données d'un volume `emptyDir` sont stockées directement sur le **nœud d'exécution du `Pod`**. L'espace disponible est donc très limité et dépend de la catégorie de nœuds du cluster. ⚠️ **Ce type de volume est à proscrire pour :** * Vos données persistantes * Les volumétries dépassant quelques dizaines de Mo * Les logs d'applications (préférer une solution centralisée) **Impact d'un `Pod` en erreur** : Un `Pod` remplissant l'espace disque d'un `emptyDir` peut provoquer l'éviction d'autres `Pods` sur le même nœud. ::: ##### Exemple avec stockage disque ```yaml showLineNumbers [pod.yaml] apiVersion: v1 kind: Pod metadata: name: webapp-cache namespace: production labels: app: webapp spec: containers: - name: webapp image: nginx:1.25 volumeMounts: - name: cache-volume mountPath: /var/cache/nginx - name: cache-warmer image: busybox:1.36 command: ['sh', '-c', 'while true; do date > /cache/timestamp; sleep 60; done'] volumeMounts: - name: cache-volume mountPath: /cache volumes: - name: cache-volume emptyDir: sizeLimit: 100Mi # Limite la taille du volume ``` ##### Exemple avec stockage en RAM (`tmpfs`) Pour optimiser les temps d'accès, il est possible d'utiliser un stockage en **RAM** (`tmpfs`) en définissant le champ `emptyDir.medium` à `Memory`. :::warning Le volume consommera de la RAM du nœud et comptera dans la limite mémoire du `Pod`. ::: ```yaml showLineNumbers [pod.yaml] apiVersion: v1 kind: Pod metadata: name: high-speed-cache namespace: production spec: containers: - name: app image: myapp:1.0.0 resources: requests: memory: "256Mi" # Inclut l'emptyDir en mémoire limits: memory: "512Mi" volumeMounts: - name: tmp-cache mountPath: /tmp/cache volumes: - name: tmp-cache emptyDir: medium: Memory sizeLimit: 128Mi # Taille max en RAM ``` #### `ConfigMap` et `Secret` (volumes projetés) Les `ConfigMap` et `Secret` peuvent être montés comme volumes dans les Pods pour injecter des fichiers de configuration ou des informations sensibles. ##### Exemple avec `ConfigMap` :::code-group ```yaml showLineNumbers [configmap.yaml] apiVersion: v1 kind: ConfigMap metadata: name: app-config namespace: production data: app.conf: | server { listen 80; server_name example.com; } settings.json: | { "debug": false, "max_connections": 100 } ``` ```yaml showLineNumbers [pod.yaml] apiVersion: v1 kind: Pod metadata: name: app-with-config namespace: production spec: containers: - name: app image: nginx:1.25 volumeMounts: - name: config mountPath: /etc/config readOnly: true volumes: - name: config configMap: name: app-config ``` ::: ##### Exemple avec `Secret` :::code-group ```yaml showLineNumbers [secret.yaml] apiVersion: v1 kind: Secret metadata: name: db-credentials namespace: production type: Opaque stringData: username: admin password: "P@ssw0rd!Secure" ``` ```yaml showLineNumbers [pod.yaml] apiVersion: v1 kind: Pod metadata: name: app-with-secret namespace: production spec: containers: - name: app image: myapp:1.0.0 volumeMounts: - name: credentials mountPath: /etc/secrets readOnly: true volumes: - name: credentials secret: secretName: db-credentials defaultMode: 0400 # Permissions strictes ``` ::: ### Commandes utiles #### Gestion des `PersistentVolumeClaims` ```bash [Terminal] # Lister les PVC dans le namespace courant kubectl get pvc # Lister les PVC dans tous les namespaces kubectl get pvc --all-namespaces # Détails d'un PVC kubectl describe pvc -n # Voir les événements liés à un PVC kubectl get events --field-selector involvedObject.name= -n # Supprimer un PVC kubectl delete pvc -n ``` #### Gestion des `PersistentVolumes` ```bash [Terminal] # Lister tous les PV du cluster kubectl get pv # Détails d'un PV kubectl describe pv # Voir les PV avec leur statut et leur claim kubectl get pv -o wide # Filtrer les PV disponibles kubectl get pv --field-selector status.phase=Available ``` #### Gestion des `StorageClasses` ```bash [Terminal] # Lister les StorageClasses disponibles kubectl get storageclass kubectl get sc # Alias # Détails d'une StorageClass kubectl describe sc managed-nfs-storage ``` #### Vérification de l'utilisation du stockage ```bash [Terminal] # Voir l'utilisation disque dans un Pod kubectl exec -it -n -- df -h # Taille d'un répertoire monté kubectl exec -it -n -- du -sh /path/to/mount # Lister les fichiers d'un volume kubectl exec -it -n -- ls -lah /path/to/mount ``` ### Bonnes pratiques #### Choix de la politique de rétention | Environnement | StorageClass recommandée | Justification | | ----------------- | ---------------------------- | --------------------------------------------- | | **Production** | `managed-nfs-storage-retain` | Évite la suppression accidentelle des données | | **Staging** | `managed-nfs-storage-retain` | Facilite les tests de restauration | | **Développement** | `managed-nfs-storage` | Simplifie le nettoyage automatique | | **CI/CD** | `managed-nfs-storage` | Volumes éphémères pour les tests | #### Dimensionnement des volumes * **Anticiper la croissance** : Prévoir 30-50% de marge par rapport aux besoins initiaux * **Surveiller l'utilisation** : Mettre en place des alertes sur l'espace disque (ex: >80%) * **Tester l'extension** : Vérifier si la `StorageClass` supporte l'expansion de volume (`allowVolumeExpansion`) :::info **Extension de volumes**\ L'extension de volumes (augmentation de la taille d'un PVC existant) dépend de la StorageClass utilisée. Contactez SdV pour connaître les possibilités d'extension des volumes NFS. ::: #### Sécurité ```yaml showLineNumbers [pod.yaml] # Utiliser des permissions restrictives apiVersion: v1 kind: Pod metadata: name: secure-pod spec: containers: - name: app image: myapp:1.0.0 volumeMounts: - name: data mountPath: /data readOnly: false # false pour écriture, true pour lecture seule securityContext: runAsNonRoot: true runAsUser: 1000 fsGroup: 2000 # Group ID pour les permissions des fichiers allowPrivilegeEscalation: false volumes: - name: data persistentVolumeClaim: claimName: app-data ``` #### Nommage et labels Adopter une convention de nommage claire : ```yaml showLineNumbers [pvc.yaml] apiVersion: v1 kind: PersistentVolumeClaim metadata: name: -- # Ex: webapp-uploads-prod namespace: production labels: app: webapp component: storage environment: production storage-type: nfs annotations: owner: "team-platform@example.com" description: "Stockage des uploads utilisateurs" backup: "enabled" ``` #### Gestion du cycle de vie 1. **Création** : Toujours définir `storageClassName` explicitement 2. **Utilisation** : Monitorer l'espace disque et les performances 3. **Mise à jour** : Tester l'extension de volume en pré-production 4. **Suppression** : * Pour `Retain` : Supprimer manuellement le `PV` après vérification * Documenter les `PV` orphelins et leur contenu * Archiver les données avant suppression définitive #### Monitoring et alerting Métriques à surveiller : * **Utilisation de l'espace disque** : Pourcentage d'utilisation du `PVC` * **Latence I/O** : Temps de réponse des opérations disque * **Erreurs de montage** : Échecs de binding `PVC`/`PV` * **PV orphelins** : PV en statut `Released` après suppression du `PVC` Exemple d'alerte Prometheus : ```yaml showLineNumbers [alertmanager.yaml] # Alerte si un PVC est rempli à plus de 85% - alert: PVCAlmostFull expr: | kubelet_volume_stats_used_bytes / kubelet_volume_stats_capacity_bytes > 0.85 for: 10m labels: severity: warning annotations: summary: "PVC {{ $labels.persistentvolumeclaim }} presque plein" description: "Le PVC est rempli à {{ $value | humanizePercentage }}" ``` ### Dépannage #### Problèmes courants ##### PVC en état `Pending` **Causes possibles :** * Aucun `PV` disponible correspondant aux critères * `StorageClass` inexistante ou mal configurée * Quota de stockage dépassé * Provisioner NFS indisponible **Diagnostic :** ```bash [Terminal] # Vérifier l'état du PVC kubectl describe pvc -n # Vérifier les événements kubectl get events -n --sort-by='.lastTimestamp' # Vérifier les PV disponibles kubectl get pv --field-selector status.phase=Available ``` ##### Erreur de montage dans le Pod **Causes possibles :** * Serveur NFS inaccessible * Permissions insuffisantes * Volume déjà monté en exclusif (`RWO`) sur un autre nœud **Diagnostic :** ```bash [Terminal] # Voir les événements du Pod kubectl describe pod -n # Vérifier les logs du CSI driver (si applicable) kubectl logs -n kube-system -l app=nfs-provisioner ``` ##### Performance dégradée **Actions :** * Vérifier la charge réseau vers le serveur NFS * Analyser les I/O du Pod : `kubectl exec -- iostat -x 5` * Contacter le support SdV pour vérifier l'état de la baie de stockage ### Références * [Documentation Kubernetes - Volumes](https://kubernetes.io/docs/concepts/storage/volumes/) * [Documentation Kubernetes - Persistent Volumes](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) * [Documentation Kubernetes - Storage Classes](https://kubernetes.io/docs/concepts/storage/storage-classes/) * [NFS et Kubernetes - Best Practices](https://kubernetes.io/docs/concepts/storage/volumes/#nfs) ## VPN et accès sécurisé au cluster Kubernetes ### Description fonctionnelle Le VPN (Virtual Private Network) permet d’établir une connexion sécurisée entre votre poste de travail et le réseau privé du cluster Kubernetes SdV. Il garantit la confidentialité des échanges, l’authentification des utilisateurs et l’accès aux ressources internes (API, dashboards, etc.) depuis un environnement externe. ### Description technique Les paramètres de connexion au VPN sont fournis lors de la livraison du cluster. Le VPN peut reposer sur des solutions standards telles qu’IPSec ou WireGuard (BETA en cours en 2026). :::tip Une aide en ligne permettant de configurer un client VPN sous Windows, macOS, Linux, iOS et Android est disponible sur [vpn.sdv.fr](http://vpn.sdv.fr). ::: #### Bonnes pratiques * Stocker les fichiers de configuration VPN dans un emplacement sécurisé. * Utiliser des certificats ou clés à usage individuel. * Mettre à jour régulièrement le client VPN. * Vérifier la date d’expiration des certificats. * Tester la connexion VPN avant toute opération critique. #### Attention * Ne jamais partager vos fichiers de configuration ou clés VPN. * En cas de perte ou de compromission, prévenir immédiatement le service Système. * Les accès VPN sont soumis à des politiques de sécurité strictes.