Aller au contenu
Ordonner un déploiement : vagues, hooks et options

Ordonner un déploiement : vagues, hooks et options

300 Concevoir ⏱ 1 h 15 argocdkuberneteshelmpostgresqlgitops

À la fin, vous saurez

  • Ordonner les ressources d'une application avec les phases et les vagues de synchronisation
  • Écrire une migration de base de données comme hook, et un test de fumée après déploiement
  • Prévoir le comportement d'Argo CD quand un hook échoue, y compris ses réessais par défaut
  • Choisir les options de synchronisation utiles : application côté serveur, élagage en dernier, remplacement
  • Éviter les modèles Helm qui rendent une application perpétuellement désynchronisée

Prérequis

Testé avec argocd 3.5.3 helm 3.16.3 kubernetes 1.36.4 postgresql 18.6 , vérifié le 2 octobre 2026

Pourquoi

Jusqu'ici, Signalements gardait ses données en mémoire, et l'ordre d'application de ses deux objets n'avait aucune importance. Une vraie application a une base de données, et son déploiement a une séquence : la base doit exister avant que la migration s'y connecte, la migration doit avoir ajouté la colonne avant que la nouvelle version la lise, et l'on veut savoir après coup si l'ensemble répond. Appliquer tous les manifestes d'un coup, comme le faisait l'agent de la leçon 1, produit une application qui démarre, échoue à se connecter, redémarre en boucle, et finit peut-être par converger, ou par écrire dans une table qui n'a pas encore la bonne forme.

Argo CD ordonne une synchronisation avec deux outils : les phases (avant, pendant, après, en cas d'échec) et les vagues (des numéros qui fixent l'ordre à l'intérieur d'une phase). Cette leçon les utilise pour ajouter PostgreSQL, une migration et un test de fumée à Signalements, puis casse volontairement la migration pour observer ce qu'Argo CD protège, et ce qu'il fait sans qu'on le lui ait demandé.

Les concepts

Phases et hooks

Une synchronisation se déroule en phases. Une ressource ordinaire est appliquée pendant la phase Sync. Une ressource marquée comme hook (argocd.argoproj.io/hook) est exécutée à un moment précis, et n'est pas considérée comme faisant partie de l'état de l'application :

HookQuand
PreSyncavant l'application des ressources
Syncpendant, dans l'ordre des vagues, avec les autres ressources
PostSyncaprès que toutes les ressources sont appliquées et saines
SyncFailsi la synchronisation échoue
PreDelete, PostDeleteavant ou après la suppression de l'application (PreDelete depuis la 3.3)
Skipjamais appliqué par Argo CD

Un hook est généralement un Job (une migration, un test, une notification), mais toute ressource peut l'être. Sa politique de suppression (argocd.argoproj.io/hook-delete-policy) dit quand Argo CD l'efface : HookSucceeded (après succès), HookFailed (après échec), ou BeforeHookCreation (juste avant d'en recréer un au prochain passage, ce qui laisse le dernier visible pour le diagnostic ; c'est la valeur par défaut).

Les hooks Helm d'un chart (helm.sh/hook: pre-install, post-upgrade...) sont traduits en hooks Argo CD, puisque Argo CD ne fait pas d'installation Helm.

Vagues

À l'intérieur d'une phase, l'annotation argocd.argoproj.io/sync-wave (un entier, éventuellement négatif, 0 par défaut) fixe l'ordre. Argo CD applique toutes les ressources d'une vague, attend qu'elles soient saines, puis passe à la suivante, avec une courte pause entre deux vagues. L'ordre complet est : phase, puis vague, puis type de ressource (les namespaces avant les déploiements, par exemple), puis nom. L'élagage se fait dans l'ordre inverse des vagues.

Pour Signalements :

    flowchart LR
  subgraph Sync["Phase Sync"]
    direction LR
    W1["Vague -1<br/>Secret, Service et StatefulSet<br/>de PostgreSQL"] --> W0["Vague 0<br/>Job de migration (hook Sync)<br/>Service de l'application"]
    W0 --> W2["Vague 1<br/>Deployment de l'application"]
  end
  Sync --> P["Phase PostSync<br/>test de fumée"]
  Sync -.->|"en cas d'échec"| F["Phase SyncFail<br/>alerte"]
  

Pourquoi un hook Sync en vague 0 plutôt qu'un hook PreSync pour la migration ? Parce qu'au premier déploiement, la phase PreSync passe avant la base : la migration s'exécuterait avant que PostgreSQL existe. Placée dans la phase Sync, entre la vague de la base et celle de l'application, elle fonctionne au premier déploiement comme aux suivants.

En pratique

PostgreSQL dans le chart

Le chart reçoit un drapeau postgresql.enabled (faux par défaut, vrai en recette) et un fichier templates/postgresql.yaml :

{{- if .Values.postgresql.enabled }}
# Vague -1 : le secret et la base existent avant tout le reste.
apiVersion: v1
kind: Secret
metadata:
  name: {{ .Release.Name }}-base
  annotations:
    argocd.argoproj.io/sync-wave: "-1"
stringData:
  DATABASE_URL: postgresql://signalements:{{ .Values.postgresql.motDePasse }}@{{ .Release.Name }}-base:5432/signalements
  POSTGRES_PASSWORD: {{ .Values.postgresql.motDePasse | quote }}
---
apiVersion: v1
kind: Service
metadata:
  name: {{ .Release.Name }}-base
  annotations:
    argocd.argoproj.io/sync-wave: "-1"
spec:
  selector:
    app: {{ .Release.Name }}-base
  ports:
    - port: 5432
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: {{ .Release.Name }}-base
  annotations:
    argocd.argoproj.io/sync-wave: "-1"
spec:
  serviceName: {{ .Release.Name }}-base
  replicas: 1
  selector:
    matchLabels:
      app: {{ .Release.Name }}-base
  template:
    metadata:
      labels:
        app: {{ .Release.Name }}-base
    spec:
      containers:
        - name: postgresql
          image: {{ .Values.postgresql.image }}
          env:
            - {name: POSTGRES_USER, value: signalements}
            - {name: POSTGRES_DB, value: signalements}
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef: {name: {{ .Release.Name }}-base, key: POSTGRES_PASSWORD}
          readinessProbe:
            exec:
              command: [pg_isready, -U, signalements]
          volumeMounts:
            - {name: donnees, mountPath: /var/lib/postgresql}
  volumeClaimTemplates:
    - metadata:
        name: donnees
      spec:
        accessModes: [ReadWriteOnce]
        resources:
          requests:
            storage: 1Gi
{{- end }}

La sonde de disponibilité est essentielle : c'est elle qui rend le StatefulSet « sain », et Argo CD ne passe à la vague suivante qu'une fois la vague courante saine. Sans elle, la base serait déclarée saine dès le démarrage du conteneur, avant d'accepter les connexions. Le montage sur /var/lib/postgresql (et non /var/lib/postgresql/data) est celui qu'attend l'image officielle à partir de PostgreSQL 18.

Le mot de passe est écrit dans les valeurs du chart, donc dans Git : c'est une valeur de démonstration, sans autre usage que ce laboratoire, et la leçon 8 la retire de Git. Le Deployment reçoit l'annotation de vague 1 et la variable DATABASE_URL lue dans le secret.

La migration, un hook Sync

{{- if .Values.postgresql.enabled }}
# Hook de la phase Sync, vague 0 : après la base (vague -1), avant l'application (vague 1).
apiVersion: batch/v1
kind: Job
metadata:
  name: {{ .Release.Name }}-migration
  annotations:
    argocd.argoproj.io/hook: Sync
    argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
    argocd.argoproj.io/sync-wave: "0"
spec:
  backoffLimit: 2
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: migration
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          envFrom:
            - secretRef: {name: {{ .Release.Name }}-base}
          command:
            - python
            - -c
            - |
              import os, psycopg
              with psycopg.connect(os.environ["DATABASE_URL"], connect_timeout=5) as cnx:
                  cnx.execute("CREATE TABLE IF NOT EXISTS signalements ("
                              " id serial PRIMARY KEY, lieu text NOT NULL,"
                              " description text NOT NULL, cree_le timestamptz NOT NULL DEFAULT now())")
                  cnx.execute("ALTER TABLE signalements ADD COLUMN IF NOT EXISTS priorite smallint NOT NULL DEFAULT 2")
              print("migration appliquée")
{{- end }}
  • L'image de la migration est celle de l'application, à la même étiquette : la migration et le code qui l'attend voyagent ensemble.
  • La migration est idempotente (IF NOT EXISTS) : un hook Sync s'exécute à chaque synchronisation, pas seulement quand le schéma change. Une migration qui échouerait au second passage bloquerait tous les déploiements suivants. Avec un vrai outil de migration (Alembic, Flyway), c'est l'outil qui tient la liste des migrations déjà passées.
  • BeforeHookCreation garde le Job du dernier passage jusqu'au suivant : ses journaux restent consultables.

Le test de fumée et l'alerte

# Hook PostSync : un test de fumée après chaque synchronisation réussie.
apiVersion: batch/v1
kind: Job
metadata:
  name: {{ .Release.Name }}-fumee
  annotations:
    argocd.argoproj.io/hook: PostSync
    argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
spec:
  backoffLimit: 0
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: fumee
          image: curlimages/curl:8.16.0
          args: ["-sf", "--retry", "5", "--retry-connrefused", "http://{{ .Release.Name }}/sante"]
# Hook SyncFail : exécuté seulement si la synchronisation échoue.
apiVersion: batch/v1
kind: Job
metadata:
  name: {{ .Release.Name }}-echec
  annotations:
    argocd.argoproj.io/hook: SyncFail
    argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
spec:
  backoffLimit: 0
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: alerte
          image: curlimages/curl:8.16.0
          command: ["sh", "-c", "echo 'Synchronisation de {{ .Release.Name }} en échec : prévenir l’équipe'"]

Le hook SyncFail se contente ici d'écrire un message ; en vrai, il appellerait une messagerie ou un outil d'astreinte (les notifications d'Argo CD, leçon 10, le font plus proprement). Le rendu du chart confirme les annotations :

$ helm template signalements chart -f environnements/recette/values.yaml | grep -E "^kind:|sync-wave|hook:"
kind: Secret
    argocd.argoproj.io/sync-wave: "-1"
kind: Service
    argocd.argoproj.io/sync-wave: "-1"
kind: Service
kind: Deployment
    argocd.argoproj.io/sync-wave: "1"
kind: StatefulSet
    argocd.argoproj.io/sync-wave: "-1"
kind: Job
    argocd.argoproj.io/hook: PostSync
kind: Job
    argocd.argoproj.io/hook: Sync
    argocd.argoproj.io/sync-wave: "0"

(Le hook SyncFail a été ajouté au commit suivant.)

Le déploiement, dans l'ordre

$ git add chart environnements && git commit -qm "Recette : base PostgreSQL, migration et test de fumée" && git push -q origin main
$ argocd app wait signalements-recette --operation --timeout 300
$ argocd app get signalements-recette --show-operation
...
Phase:              Succeeded
Start:              2026-10-02 09:36:38 +0200 CEST
Finished:           2026-10-02 09:37:41 +0200 CEST
Duration:           1m3s
Message:            successfully synced (no more tasks)

GROUP  KIND         NAMESPACE             NAME                    STATUS     HEALTH   HOOK      MESSAGE
       Secret       signalements-recette  signalements-base       Synced                        secret/signalements-base created
       Service      signalements-recette  signalements-base       Synced     Healthy            service/signalements-base created
apps   StatefulSet  signalements-recette  signalements-base       Synced     Healthy            partitioned roll out complete: 1 new pods have been updated...
       Service      signalements-recette  signalements            Synced     Healthy            service/signalements unchanged
batch  Job          signalements-recette  signalements-migration  Succeeded  Synced   Sync      Reached expected number of succeeded pods
apps   Deployment   signalements-recette  signalements            Synced     Healthy            deployment.apps/signalements configured
batch  Job          signalements-recette  signalements-fumee      Succeeded  Synced   PostSync  Reached expected number of succeeded pods

L'ordre est celui voulu : la base (vague -1), la migration (vague 0, avec le Service de l'application qui n'a pas d'annotation et vaut donc 0), le Deployment (vague 1), puis le test de fumée. La synchronisation a pris une minute, le temps que le nœud télécharge l'image de PostgreSQL et que la base devienne disponible. La migration a fait son travail, et l'application stocke désormais ses données dans PostgreSQL :

$ kubectl -n signalements-recette logs job/signalements-migration
migration appliquée
$ kubectl -n signalements-recette exec statefulset/signalements-base -- psql -U signalements -d signalements -c '\d signalements' | grep -E "priorite|lieu"
 lieu        | text                     |           | not null |
 priorite    | smallint                 |           | not null | 2
$ ...
{"version":"1.2.0","stockage":"postgresql"}

Une migration qui échoue

Le cas qui justifie tout cet ordonnancement : une nouvelle livraison (ici, l'étiquette 1.1.0 reprise pour l'exemple, la version importe peu : c'est la migration qui compte) livrée avec une migration erronée, qui vise une table signalement au singulier.

$ grep -n "categorie" chart/templates/migration.yaml
31:                  cnx.execute("ALTER TABLE signalement ADD COLUMN categorie text")
$ git commit -qam "Recette 1.1.0 avec une migration erronée" && git push -q origin main
$ argocd app get signalements-recette --show-operation
...
Phase:              Running
Start:              2026-10-02 09:38:07 +0200 CEST
Finished:           2026-10-02 09:42:09 +0200 CEST
Duration:           4m2s
Message:            one or more synchronization tasks completed unsuccessfully. Retrying attempt #4 at 7:42AM.

GROUP  KIND         NAMESPACE             NAME                    STATUS     HEALTH   HOOK      MESSAGE
       Secret       signalements-recette  signalements-base       Synced                        secret/signalements-base configured
       Service      signalements-recette  signalements-base       Synced     Healthy            service/signalements-base unchanged
apps   StatefulSet  signalements-recette  signalements-base       Synced     Healthy            partitioned roll out complete: 1 new pods have been updated...
       Service      signalements-recette  signalements            Synced     Healthy            service/signalements unchanged
batch  Job          signalements-recette  signalements-migration  Failed     Synced   Sync      Job has reached the specified backoff limit
batch  Job          signalements-recette  signalements-echec      Succeeded  Synced   SyncFail  Reached expected number of succeeded pods
apps   Deployment   signalements-recette  signalements            OutOfSync  Healthy
batch  Job          signalements-recette  signalements-fumee                 Healthy
$ kubectl -n signalements-recette get deploy signalements -o jsonpath='image en place : {.spec.template.spec.containers[0].image}{"\n"}'
image en place : registre.lyneko.example/signalements:1.2.0
$ kubectl -n signalements-recette logs job/signalements-echec
Synchronisation de signalements en échec : prévenir l’équipe

Trois constats :

  • La nouvelle version n'a pas été déployée. La migration (vague 0) a échoué, donc la vague 1 n'a jamais été appliquée : le Deployment est resté en 1.2.0, OutOfSync mais sain. L'application continue de servir avec l'ancien code et l'ancien schéma, qui vont ensemble. C'est exactement ce que l'ordonnancement devait garantir.
  • Le hook SyncFail s'est exécuté, et le test de fumée PostSync non.
  • Argo CD réessaie, alors qu'aucune politique de réessai n'a été configurée : « Retrying attempt #4 ». La section Sous le capot explique d'où vient ce réessai.

Revenir en arrière

On annule le commit fautif :

$ git revert --no-edit HEAD && git push -q origin main
$ git log --oneline -1
b7f1a75 Revert "Recette 1.1.0 avec une migration erronée"
$ argocd app get signalements-recette --refresh | grep "^Sync Status"
Sync Status:        Synced to main (b7f1a75)

L'application est immédiatement synchronisée, sans qu'aucune opération ne soit nécessaire : le cluster n'avait jamais quitté l'état décrit par le commit précédent, puisque la version fautive n'avait pas été appliquée, et les hooks ne comptent pas dans l'état de synchronisation. La dernière opération, elle, reste enregistrée comme un échec sur le commit fautif, une fois ses réessais épuisés :

$ argocd app get signalements-recette -o json | jq -r '"\(.status.operationState.phase) \(.status.operationState.syncResult.revision[0:7]) retry=\(.status.operationState.retryCount) msg=\(.status.operationState.message)"'
Failed 3d9f774 retry=5 msg=one or more synchronization tasks completed unsuccessfully (retried 5 times).

C'est utile pour le diagnostic, et trompeur pour une supervision qui ne regarderait que la dernière opération : l'application est saine et conforme, mais son dernier statut d'opération est rouge.

Les options de synchronisation

Les options se posent sur l'application (spec.syncPolicy.syncOptions) ou sur une ressource (annotation argocd.argoproj.io/sync-options) :

OptionEffetUsage typique
CreateNamespace=truecrée le namespace de destinationpresque toujours
ServerSideApply=trueapplique côté serveur, avec gestion des propriétaires de champsressources volumineuses (définitions de ressources), cohabitation avec d'autres contrôleurs
PruneLast=trueélague après toutes les vaguessupprimer l'ancien seulement quand le nouveau fonctionne
Prune=falseexclut une ressource de l'élagagevolumes persistants
Replace=truekubectl replace au lieu d'un applyressources dont certains champs ne sont pas modifiables
ApplyOutOfSyncOnly=truen'applique que ce qui diffèreapplications de milliers de ressources
RespectIgnoreDifferences=truerespecte ignoreDifferences à la synchronisationleçon 3
FailOnSharedResource=trueéchoue si une ressource appartient à une autre applicationéviter les disputes
Validate=falsedésactive la validation de schémaressources dont le schéma n'est pas encore connu

L'application côté serveur change ce que Kubernetes retient de chaque écriture. Avant et après une synchronisation avec --server-side :

$ kubectl -n signalements-recette get deploy signalements --show-managed-fields -o json | jq -r '[.metadata.managedFields[]|"\(.manager) \(.operation)"]|unique|.[]'
argocd-controller Update
kube-controller-manager Update
$ argocd app sync signalements-recette --server-side
$ kubectl -n signalements-recette get deploy signalements --show-managed-fields -o json | jq -r '[.metadata.managedFields[]|"\(.manager) \(.operation)"]|unique|.[]'
argocd-controller Apply
kube-controller-manager Update

Avec l'application côté serveur, Argo CD devient le propriétaire déclaré des champs qu'il écrit (Apply), et un autre outil qui modifierait l'un de ces champs provoquerait un conflit explicite au lieu d'un écrasement silencieux. Depuis la 3.3, l'option ClientSideApplyMigration (active par défaut) fait migrer proprement les anciens propriétaires de champs lors du passage.

Sous le capot

Les réessais par défaut. La documentation présente le réessai comme une option (syncPolicy.retry). Le code du contrôleur d'Argo CD 3.5.3 montre qu'une synchronisation automatique en reçoit un d'office :

	op := appv1.Operation{
		Sync: &appv1.SyncOperation{ ... },
		InitiatedBy: appv1.OperationInitiator{Automated: true},
		Retry:       appv1.RetryStrategy{Limit: 5},
	}
	if app.Spec.SyncPolicy.Retry != nil {
		op.Retry = *app.Spec.SyncPolicy.Retry
	}

Cinq réessais, avec les délais par défaut (5 secondes, doublés à chaque fois, plafonnés à 3 minutes). Pour une migration erronée, ces cinq réessais rejouent cinq fois le même échec, et une nouvelle révision n'est pas prise en compte tant qu'ils ne sont pas épuisés, sauf avec retry.refresh: true, qui fait rafraîchir l'application pendant les réessais, pour qu'une nouvelle révision soit prise en compte sans attendre leur épuisement. Déclarer explicitement retry (limite basse, rafraîchissement activé) rend le comportement prévisible.

La santé entre deux vagues. Après chaque vague, le contrôleur attend que les ressources appliquées soient saines, avec les règles de santé de la leçon 2. Un StatefulSet sans sonde de disponibilité est sain dès que son pod tourne ; un Job hook est terminé quand il réussit ou échoue. C'est pourquoi la qualité des sondes conditionne celle de l'ordonnancement.

Les hooks ne sont pas l'état. Un hook n'entre pas dans la comparaison : il n'est ni Synced ni OutOfSync au sens de l'application. Argo CD le crée, le suit, et l'efface selon sa politique. C'est ce qui explique que le revert ait rendu l'application Synced sans opération.

Pièges courants

Une migration en PreSync qui échoue au premier déploiement. La phase PreSync passe avant la base. Utilisez un hook Sync dans une vague située entre la base et l'application, ou séparez la base dans sa propre application.

Une migration non idempotente. Un hook Sync s'exécute à chaque synchronisation, y compris celles qui ne changent rien au schéma. CREATE TABLE sans IF NOT EXISTS bloque tous les déploiements suivants.

L'application ne passe jamais à la vague suivante. Une ressource de la vague courante ne devient jamais saine : sonde absente ou fausse, image introuvable, volume non provisionné. L'opération reste Running ; argocd app get --show-operation montre la ressource qui bloque.

Une application perpétuellement OutOfSync avec Helm. Un modèle qui produit une valeur différente à chaque rendu (randAlphaNum pour un mot de passe, now pour une date, uuidv4) : Argo CD rend le chart à chaque comparaison et trouve toujours une différence, et l'auto-réparation réécrit l'objet sans fin. Générez ces valeurs une fois, hors du chart (un secret géré à part, leçon 8).

Le dernier statut d'opération est en échec alors que tout va bien. Un échec ancien, suivi d'un revert qui n'a pas nécessité de synchronisation. Supervisez l'état de synchronisation et la santé, pas seulement la dernière opération.

kind load échoue sur une image multi-plateforme. Observé dans le laboratoire avec postgres:18.6 : ctr: content digest ... not found, parce que seules certaines plateformes de l'image sont présentes localement. Laissez le nœud télécharger l'image lui-même.

Sécurité

Un hook s'exécute avec les droits de l'application. Un Job de migration reçoit la chaîne de connexion de la base ; un hook ajouté par une demande de fusion malveillante peut exfiltrer ce qu'il veut. Les hooks se relisent comme le reste du dépôt, et les projets (leçon 9) limitent ce qu'une application peut créer.

Les politiques de suppression effacent des traces. HookSucceeded supprime le Job dès sa réussite, avec ses journaux. Pour une migration, gardez la trace (BeforeHookCreation) ou exportez les journaux.

Le mot de passe de démonstration est dans Git. Ce n'est acceptable que pour ce laboratoire ; la leçon 8 le remplace.

En production

Les migrations longues. Un hook a une durée limitée par celle de l'opération ; une migration de plusieurs heures (une réindexation, un remplissage de colonne) ne doit pas bloquer les déploiements. On la découple : migration compatible en deux temps (ajouter la colonne, déployer le code qui sait vivre avec et sans, remplir, puis contraindre), ou outil dédié hors du cycle de synchronisation.

Les bases gérées. En production chez Lyneko et chez la plupart des clients, PostgreSQL n'est pas un StatefulSet du cluster mais une base gérée par le fournisseur (Scaleway Managed Database, par exemple), provisionnée par Terraform. La vague -1 disparaît, la migration reste un hook qui s'y connecte.

Superviser les échecs. Le hook SyncFail dépanne ; les notifications d'Argo CD (leçon 10) et ses métriques (argocd_app_info, états de synchronisation et de santé) sont la vraie réponse, avec une alerte sur toute application OutOfSync ou Degraded depuis plus de quelques minutes.

Exercices

1. Dans quel ordre Argo CD applique-t-il ces ressources : un Namespace sans annotation, une ConfigMap en vague 2, un Deployment en vague 1, un Job hook PreSync, un Job hook PostSync, un Service sans annotation ?

Solution

Le Job PreSync ; puis, dans la phase Sync, la vague 0 (le Namespace avant le Service, par ordre de type), la vague 1 (le Deployment), la vague 2 (la ConfigMap) ; enfin, une fois tout sain, le Job PostSync.

2. Pourquoi la migration de cette leçon est-elle un hook Sync en vague 0, et pas un hook PreSync ? Que se passerait-il au premier déploiement avec PreSync ?

Solution

La phase PreSync précède l'application de toutes les ressources ordinaires, y compris la base en vague -1. Au premier déploiement, la migration s'exécuterait avant que PostgreSQL existe : elle échouerait, la synchronisation aussi, et la base ne serait jamais créée. Dans la phase Sync, la vague 0 vient après la vague -1 (la base) et avant la vague 1 (l'application).

3. Ajoutez à l'application une politique de réessai explicite : au plus deux réessais, et prise en compte d'une nouvelle révision pendant les réessais. Quel est l'intérêt par rapport au comportement par défaut ?

Solution
  syncPolicy:
    retry:
      limit: 2
      refresh: true
      backoff:
        duration: 10s
        factor: 2
        maxDuration: 1m

Par défaut, une synchronisation automatique est réessayée cinq fois, pendant plusieurs minutes, même si une correction a déjà été poussée. Avec refresh: true, une nouvelle révision est prise en compte pendant les réessais, sans attendre leur fin ; avec une limite basse, un échec reproductible est signalé plus vite.

4. Un modèle du chart contient password: {{ randAlphaNum 24 | quote }}. Décrivez ce qui se passe avec selfHeal: true, et proposez une correction.

Solution

Chaque rendu produit un mot de passe différent : l'application est toujours OutOfSync, et l'auto-réparation réécrit le secret à chaque correction (avec le délai croissant entre deux corrections), changeant le mot de passe sous les pieds de l'application. Correction : générer le mot de passe une fois, hors du chart, et le faire arriver par un secret géré à part (leçon 8), que le chart référence sans le contenir.

5. Écrivez un hook PostSync qui vérifie, en plus de /sante, que la colonne priorite existe. Quels droits doit-il avoir ?

Solution

Un Job avec l'image de l'application, qui lit DATABASE_URL dans le secret et exécute par exemple SELECT priorite FROM signalements LIMIT 0 avec psycopg (une erreur fait échouer le Job, donc signale l'échec du test). Il n'a besoin que de la lecture sur la base : idéalement un rôle PostgreSQL en lecture seule dédié, plutôt que l'utilisateur propriétaire de la base.

Récapitulatif

  • Une synchronisation se déroule en phases (PreSync, Sync, PostSync, SyncFail) ; à l'intérieur, les vagues fixent l'ordre, et chaque vague attend que la précédente soit saine.
  • Une migration se place en hook Sync, dans une vague entre la base et l'application ; elle doit être idempotente.
  • Si un hook échoue, les vagues suivantes ne sont pas appliquées : l'ancienne version continue de tourner, et le hook SyncFail s'exécute.
  • Une synchronisation automatique est réessayée cinq fois par défaut ; déclarez retry explicitement, avec refresh: true.
  • Les hooks ne comptent pas dans l'état de synchronisation ; le dernier statut d'opération peut rester en échec après un revert.
  • ServerSideApply=true fait d'Argo CD le propriétaire déclaré des champs qu'il écrit ; les modèles Helm non déterministes rendent une application perpétuellement désynchronisée.

Pour aller plus loin

Voir ma constellation →

Sources