Ordonner un déploiement : vagues, hooks et options
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 :
| Hook | Quand |
|---|---|
PreSync | avant l'application des ressources |
Sync | pendant, dans l'ordre des vagues, avec les autres ressources |
PostSync | après que toutes les ressources sont appliquées et saines |
SyncFail | si la synchronisation échoue |
PreDelete, PostDelete | avant ou après la suppression de l'application (PreDelete depuis la 3.3) |
Skip | jamais 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 hookSyncs'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. BeforeHookCreationgarde 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,
OutOfSyncmais 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
SyncFails'est exécuté, et le test de fuméePostSyncnon. - 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) :
| Option | Effet | Usage typique |
|---|---|---|
CreateNamespace=true | crée le namespace de destination | presque toujours |
ServerSideApply=true | applique côté serveur, avec gestion des propriétaires de champs | ressources volumineuses (définitions de ressources), cohabitation avec d'autres contrôleurs |
PruneLast=true | élague après toutes les vagues | supprimer l'ancien seulement quand le nouveau fonctionne |
Prune=false | exclut une ressource de l'élagage | volumes persistants |
Replace=true | kubectl replace au lieu d'un apply | ressources dont certains champs ne sont pas modifiables |
ApplyOutOfSyncOnly=true | n'applique que ce qui diffère | applications de milliers de ressources |
RespectIgnoreDifferences=true | respecte ignoreDifferences à la synchronisation | leçon 3 |
FailOnSharedResource=true | échoue si une ressource appartient à une autre application | éviter les disputes |
Validate=false | désactive la validation de schéma | ressources 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: 1mPar 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
SyncFails'exécute. - Une synchronisation automatique est réessayée cinq fois par défaut ; déclarez
retryexplicitement, avecrefresh: 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=truefait 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
- Argo CD, Sync Phases and Waves : l'ordre complet et ses règles.
- Argo CD, Resource Hooks : tous les hooks et les politiques de suppression.
- Leçon suivante : ApplicationSet : des applications en série.
Sources
- Argo CD, Sync Phases and Waves
- Argo CD, Resource Hooks
- Argo CD, Sync Options
- Argo CD, Automated Sync Policy : réessais
- argoproj/argo-cd v3.5.3, controller/appcontroller.go (réessai par défaut des synchronisations automatiques)
- Argo CD, Helm : correspondance des hooks Helm
- Kubernetes, Server-Side Apply
- PostgreSQL 18, ALTER TABLE ... ADD COLUMN IF NOT EXISTS
- Docker Hub, image officielle postgres (répertoire de données de la version 18)