Aller au contenu
English
CI/CD & Automatisation medium

Kargo : promouvoir un artefact de dev à prod en GitOps

35 min de lecture

logo kubernetes

Une image validée en recette doit arriver en production sans être reconstruite, et sans que personne édite un fichier à la main. C'est ce que Kargo automatise. Vous décrivez une suite d'environnements, test puis prod, et Kargo retient ce qui a été validé où, puis refuse de sauter une étape. Cette page part du problème concret, explique les quatre objets de Kargo avant de l'installer, monte une chaîne à deux étages et promeut une image. Les commandes ont été rejouées sur un cluster, et les sorties affichées sont les vraies.

  • Installer Kargo par Helm depuis un registre OCI, avec une version épinglée
  • Distinguer les quatre objets du modèle : Warehouse, Freight, Stage, Promotion
  • Enchaîner deux environnements et savoir ce qui autorise le second à recevoir
  • Reconnaître les refus de l'API qui coûtent le plus de temps
  • Décider si Kargo vous apporte quelque chose que votre outil GitOps ne fait pas
  • Un cluster Kubernetes fonctionnel, un cluster local suffit
  • Helm 3.13.1 au minimum, pour la raison expliquée plus bas
  • cert-manager installé, dont dépend la configuration par défaut
  • Les bases du GitOps et, idéalement, une pratique d'Argo CD

Prenons une équipe de trois personnes qui exploite un cluster. L'image nginx:1.31.6 est déployée en recette depuis deux jours, les tests passent, la mise en production est décidée. Quelqu'un ouvre le dépôt Git, cherche le fichier de l'environnement de production, remplace 1.31.5 par 1.31.6, ouvre une demande de fusion, attend une relecture, fusionne. Son outil de déploiement voit le changement et applique.

Ce geste fonctionne, et il pose trois problèmes qui grandissent avec l'équipe. Personne ne garantit que la version mise en production est celle qui a été testée : un chiffre se recopie de travers. Rien n'empêche de sauter la recette : éditer directement le fichier de production est tout aussi simple. Et la trace se perd : six mois après, retrouver quelle version est passée par quel environnement demande de relire l'historique Git.

Argo CD sait appliquer ce que Git contient, mais il ne décide pas ce qui a le droit d'y entrer. C'est précisément le vide que Kargo occupe : il surveille les nouvelles versions, les regroupe en candidates, et fait respecter l'ordre des environnements. Le guide déploiement multi-environnements montre la version manuelle de ce geste ; Kargo l'automatise.

Kargo ajoute son propre vocabulaire à Kubernetes, et quatre mots suffisent pour commencer. Ils décrivent le trajet d'une version : d'où elle vient, ce qu'elle contient, où elle va, et qui l'y envoie. Les comprendre maintenant évite d'installer cinq composants sans savoir à quoi ils servent.

ObjetRôleAnalogie
WarehouseSurveille une source, image ou dépôt Git, et détecte les nouvelles versionsLe quai de réception
FreightUn ensemble de versions constaté à un instant donné, immuableLe colis scellé
StageUn environnement, qui déclare d'où il accepte son FreightUne étape du trajet
PromotionL'ordre de faire entrer un Freight donné dans un Stage donnéLe bon de livraison

Le Freight est la pièce centrale. Il ne contient pas qu'un numéro de version : il fige un ensemble cohérent, par exemple une image et le commit Git qui l'accompagne. C'est ce colis entier qui voyage d'un environnement au suivant, ce qui garantit que rien ne se désynchronise en route.

Kargo installe en réalité neuf ressources dans le groupe kargo.akuity.io. Les cinq autres portent la configuration et les tâches réutilisables : vous les croiserez en listant le cluster, sans avoir à les connaître pour suivre cette page.

Kargo ne se distribue pas par un dépôt Helm classique, mais par un registre OCI. Un registre OCI est un entrepôt d'images de conteneurs, comme Docker Hub ou GitHub Packages ; depuis Helm 3, il sait aussi stocker des charts, c'est-à-dire des paquets d'installation Kubernetes. Conséquence pratique : l'adresse oci://ghcr.io/akuity/kargo-charts/kargo ne s'ajoute pas avec helm repo add, elle se passe directement à helm upgrade. Cette différence explique à elle seule deux messages d'erreur déroutants, traités dans le dépannage.

  1. Installer cert-manager

    Kargo réclame des certificats pour ses webhooks d'admission, ces composants que l'API de Kubernetes appelle avant d'écrire une ressource, et qui peuvent la refuser. Comme l'API leur parle en HTTPS, ils ont besoin d'un certificat, que cert-manager fabrique et renouvelle. La documentation amont présente cert-manager comme une dépendance non absolue ; en configuration par défaut, elle l'est bel et bien.

    Fenêtre de terminal
    kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.16.1/cert-manager.yaml
    kubectl -n cert-manager rollout status deploy/cert-manager-webhook --timeout=300s
  2. Générer le mot de passe administrateur et la clé de signature

    Le chart n'a aucune valeur par défaut pour ces deux champs : sans eux, l'installation échoue. Le mot de passe est transmis haché, jamais en clair, et la clé de signature authentifie les jetons de l'API.

    Fenêtre de terminal
    pass=$(openssl rand -base64 48 | tr -d "=+/" | head -c 32)
    echo "Mot de passe administrateur : $pass"
    hashed_pass=$(htpasswd -bnBC 10 "" "$pass" | tr -d ':\n')
    signing_key=$(openssl rand -base64 48 | tr -d "=+/" | head -c 32)

    htpasswd vient du paquet apache2-utils sur Debian et Ubuntu, httpd-tools sur les distributions de la famille Red Hat.

  3. Installer le chart avec une version épinglée

    La documentation amont omet --version, ce qui installe la dernière publiée et rend l'installation non reproductible. On l'épingle.

    Fenêtre de terminal
    helm upgrade --install kargo \
    oci://ghcr.io/akuity/kargo-charts/kargo \
    --version 1.11.4 \
    --namespace kargo \
    --create-namespace \
    --set api.adminAccount.passwordHash="$hashed_pass" \
    --set api.adminAccount.tokenSigningKey="$signing_key" \
    --wait --timeout 10m
  4. Vérifier ce qui tourne

    Fenêtre de terminal
    kubectl -n kargo get deploy
    Résultat
    NAME READY UP-TO-DATE AVAILABLE
    kargo-api 1/1 1 1
    kargo-controller 1/1 1 1
    kargo-external-webhooks-server 1/1 1 1
    kargo-management-controller 1/1 1 1
    kargo-webhooks-server 1/1 1 1

Kargo n'est pas un contrôleur unique, mais cinq déploiements. C'est une dépendance sérieuse ajoutée au cluster, et une raison de mesurer son intérêt avant de l'installer. Le kargo-api sert l'interface web et l'API ; les deux serveurs de webhooks interceptent les écritures ; les deux contrôleurs réconcilient les objets.

La dépendance se vérifie, elle ne se suppose pas. Une fois Kargo installé, trois objets Certificate apparaissent dans son namespace :

Fenêtre de terminal
kubectl -n kargo get certificate
Résultat
NAME READY
kargo-api True
kargo-external-webhooks-server True
kargo-webhooks-server True

Sans cert-manager, aucun contrôleur ne sert ces objets : ils restent sans certificat, et les webhooks n'ont pas de TLS. Le cluster n'échoue pas bruyamment, il reste à moitié installé, ce qui est plus difficile à diagnostiquer qu'une erreur franche.

Un Project Kargo crée son propre namespace. Le Warehouse ci-dessous surveille une image publique et se limite aux versions 1.31.x :

projet.yaml
apiVersion: kargo.akuity.io/v1alpha1
kind: Project
metadata:
name: demo
---
apiVersion: kargo.akuity.io/v1alpha1
kind: Warehouse
metadata:
name: nginx
namespace: demo
spec:
subscriptions:
- image:
repoURL: docker.io/library/nginx
semverConstraint: ^1.31.0 # 1.31.x uniquement, jamais 1.32
discoveryLimit: 3 # ne retient que les 3 versions les plus récentes

Quelques secondes après l'application, le Warehouse a fait son travail :

Fenêtre de terminal
kubectl -n demo get warehouse nginx \
-o jsonpath='{.status.conditions[?(@.type=="Ready")].message}'
Résultat
Successfully discovered artifacts from 1 subscriptions

Chaque Freight reçoit un alias lisible, deux mots séparés d'un tiret. Kargo le génère pour vous éviter de manipuler son identifiant réel, une empreinte hexadécimale illisible à l'œil :

Fenêtre de terminal
kubectl -n demo get freight \
-o jsonpath='{range .items[*]}{.alias} {.images[0].repoURL}:{.images[0].tag}{"\n"}{end}'
Résultat
virtuous-chicken docker.io/library/nginx:1.31.6

Le Stage test accepte son Freight directement du Warehouse. Le Stage prod, lui, ne l'accepte que s'il est passé par test : c'est toute la chaîne, et elle tient dans le seul champ sources.

stages.yaml
apiVersion: kargo.akuity.io/v1alpha1
kind: Stage
metadata:
name: test
namespace: demo
spec:
requestedFreight:
- origin: {kind: Warehouse, name: nginx}
sources: {direct: true} # se sert au quai de réception
promotionTemplate:
spec:
steps:
- uses: file-write
config:
path: promu.txt
contents: test
---
apiVersion: kargo.akuity.io/v1alpha1
kind: Stage
metadata:
name: prod
namespace: demo
spec:
requestedFreight:
- origin: {kind: Warehouse, name: nginx}
sources: {stages: ["test"]} # ne prend que ce qui a passé test
promotionTemplate:
spec:
steps:
- uses: file-write
config:
path: promu.txt
contents: prod

Promouvoir, et voir ce qui débloque l'étape suivante

Section intitulée « Promouvoir, et voir ce qui débloque l'étape suivante »

Une Promotion nomme un Stage et un Freight. Le champ freight attend le nom réel de l'objet, celui que renvoie metadata.name, et non l'alias affiché plus haut, qui ne sert qu'à la lecture humaine :

Fenêtre de terminal
FREIGHT=$(kubectl -n demo get freight -o jsonpath='{.items[0].metadata.name}')
kubectl apply -f - <<EOF
apiVersion: kargo.akuity.io/v1alpha1
kind: Promotion
metadata:
name: promo-test-1
namespace: demo
spec:
stage: test
freight: $FREIGHT
EOF
Résultat
promotion.kargo.akuity.io/test.01m376e8c2bc3gje3sb4qka4hf.e7f0029 created

Le nom demandé n'a pas été retenu. Kargo impose le sien, construit en <stage>.<identifiant unique>.<freight abrégé>. Un script qui référencerait ensuite promo-test-1 ne trouverait rien : on relit toujours le nom rendu par la création.

Une promotion réussie ne se contente pas de changer l'état du Stage : elle marque le Freight.

Fenêtre de terminal
kubectl -n demo get freight "$FREIGHT" -o jsonpath='{.status.verifiedIn}'
Résultat
{"test":{"verifiedAt":"2026-09-23T13:14:20Z"}}

C'est ce champ verifiedIn, et lui seul, qui débloque l'étape suivante. Le Stage prod déclare sources: {stages: ["test"]} : il ne réclamera un Freight que si celui-ci porte la trace de son passage par test. La chaîne n'est donc pas une convention de nommage ni une discipline d'équipe, c'est une contrainte que l'API applique.

La promotion vers la production s'écrit avec le même manifeste, à un mot près : stage: prod remplace stage: test. Le Freight désigné, lui, ne change pas, et c'est tout l'intérêt de la manœuvre : c'est le même colis qui poursuit son trajet.

Fenêtre de terminal
kubectl apply -f - <<EOF
apiVersion: kargo.akuity.io/v1alpha1
kind: Promotion
metadata:
name: promo-prod-1
namespace: demo
spec:
stage: prod
freight: $FREIGHT
EOF

Les deux étages portent désormais leur promotion, et une seule commande donne l'état final de la chaîne :

Fenêtre de terminal
kubectl -n demo get promotion \
-o custom-columns=NOM:.metadata.name,STAGE:.spec.stage,PHASE:.status.phase
Résultat
NOM STAGE PHASE
prod.01m376f9n8f4cjdre86jm3y6kv.e7f0029 prod Succeeded
test.01m376e8c2bc3gje3sb4qka4hf.e7f0029 test Succeeded

Les deux lignes se terminent par le même suffixe, e7f0029, et c'est là que se lit le résultat : un seul et même Freight a franchi les deux étages. La colonne PHASE dit où en est chaque ordre, et Succeeded signifie que toutes les étapes du promotionTemplate ont abouti.

Rien ne se promeut tout seul. C'est le point que l'on suppose souvent à l'envers : autoPromotionEnabled vaut false par défaut, et la documentation du champ le dit sans ambiguïté sur le cluster.

Fenêtre de terminal
kubectl explain projectconfig.spec.promotionPolicies.autoPromotionEnabled
Extrait du résultat
field defaults to false, but is commonly set to true for Stages that
subscribe to Warehouses instead of other, upstream Stages.

Autrement dit, la validation humaine est le comportement par défaut : sans Promotion créée par quelqu'un, en ligne de commande ou depuis l'interface web, un Freight reste au quai. C'est l'automatisation qui s'active explicitement, pas l'inverse. L'usage courant consiste à l'ouvrir sur le premier étage, celui qui s'abonne au Warehouse, et à la laisser fermée ensuite.

autopromouvoir le premier étage seulement
apiVersion: kargo.akuity.io/v1alpha1
kind: ProjectConfig
metadata:
name: demo
namespace: demo
spec:
promotionPolicies:
- stageSelector:
name: test
autoPromotionEnabled: true # test suit le Warehouse
# prod n'apparaît pas : il reste manuel

Approuver n'est pas promouvoir, et la distinction se paie cher si on la manque. L'approbation manuelle sert au correctif urgent : elle autorise un Freight à sauter des étages du pipeline.

Fenêtre de terminal
kargo approve --project demo --freight <identifiant> --stage prod

Cette commande n'est pas kubectl : elle vient de la CLI kargo, un binaire distinct que le projet publie pour Linux, macOS et Windows, et qui s'installe à part. Tout le reste de cette page s'en passe volontairement, pour ne dépendre que de l'API Kubernetes.

Trois limites à connaître, toutes trois documentées par le projet :

  • l'approbation ne crée aucune Promotion : elle lève une condition, elle ne déclenche rien ;
  • elle contourne les vérifications seulement. Le Stage doit toujours réclamer le Freight depuis son origine, sinon l'approbation échoue ;
  • elle exige les mêmes droits qu'une promotion vers ce Stage, ce qui évite qu'une approbation serve de porte dérobée.

Le statut du Freight distingue ces trois situations, et c'est là qu'on lit ce qui s'est réellement passé :

ChampCe qu'il dit
currentlyInLes Stages où ce Freight se trouve maintenant
verifiedInLes Stages qui l'ont validé par le parcours normal
approvedForLes Stages pour lesquels un humain l'a approuvé d'avance, hors parcours

Une entrée dans approvedFor est donc une trace d'exception, et c'est exactement ce qu'on veut auditer après coup : elle nomme le raccourci pris, quand verifiedIn ne décrit que le chemin nominal.

Les quatre messages ci-dessous couvrent l'essentiel des blocages d'une première installation. Les deux premiers n'évoquent en rien leur cause réelle, ce qui les rend coûteux : ils parlent de droits d'accès quand le problème est une version d'outil ou un jeton périmé.

MessageCause réelleCorrection
401 Unauthorized sur le chartHelm antérieur à 3.13.1, qui gère mal les registres OCIMettre Helm à jour, le message ne parle pas de version
403 Forbidden sur le chartJeton ghcr.io expiré dans la configuration DockerSe déconnecter de ghcr.io : le chart est public, l'anonyme fonctionne
cannot re-use a name that is still in usehelm install sur un release déjà présentUtiliser helm upgrade --install, qui vaut pour les deux cas
steps in body should have at least 1 itemspromotionTemplate sans aucune étapeAjouter au moins une étape, même triviale

Le troisième mérite une mention : il ne se rencontre pas à la première installation, mais à la deuxième, quand on rejoue une procédure sur un cluster qui porte déjà Kargo. C'est la raison pour laquelle ce guide écrit helm upgrade --install partout.

Kargo n'a aujourd'hui aucun concurrent open source direct et vivant, ce qui est autant un argument pour que contre : le créneau est peu disputé, mais l'outil est jeune.

SolutionÉtatCe qu'elle couvre
Argo CD Image UpdateractifMet à jour un tag d'image. Une brique, pas une chaîne d'environnements
Flux image-automationactifL'équivalent côté Flux, même périmètre réduit
SpinnakeractifPlateforme de livraison multi-cloud, d'une tout autre ampleur, antérieure au GitOps
KubeVelaactifPlateforme applicative avec workflows multi-environnements
KeptnarchivéOrchestration multi-étages pilotée par SLO
TelefonistkaarchivéPromotion GitOps entre environnements, le plus proche conceptuellement

Trois questions tranchent. Promouvez-vous un ensemble de versions plutôt qu'une image isolée ? Avez-vous plus de deux environnements en file ? Voulez-vous que la règle « rien ne passe en production sans être passé en recette » soit appliquée par l'API plutôt que par une revue humaine ? Trois oui désignent Kargo. Un seul non, et Argo CD seul avec des pull requests de promotion vous coûtera moins cher en exploitation.

  1. Kargo promeut, il ne déploie pas : il décide ce qui passe à l'étape suivante, votre outil GitOps applique
  2. Le chart vient d'un registre OCI, il s'épingle avec --version, et helm upgrade --install évite l'échec au deuxième passage
  3. cert-manager est un vrai prérequis : Kargo lui demande trois certificats pour ses webhooks
  4. L'installation pose cinq déploiements et neuf ressources personnalisées, ce n'est pas un composant d'appoint
  5. Le Freight fige un ensemble de versions, c'est lui qui voyage d'un environnement au suivant
  6. Un Stage exige au moins une étape de promotion, un steps: [] est refusé
  7. Kargo impose son propre nom de Promotion, celui que vous demandez est ignoré
  8. Le champ verifiedIn est ce qui autorise l'environnement suivant à réclamer un Freight
  9. Rien ne se promeut tout seul : autoPromotionEnabled vaut false par défaut, la validation humaine est le comportement natif
  10. Approuver n'est pas promouvoir : kargo approve lève les vérifications pour un correctif urgent, sans créer de Promotion
  • Déploiements progressifs : le canary et le blue-green, qui décident comment un Freight promu atteint ses utilisateurs.
  • Rollback et reprise : revenir en arrière quand la promotion a livré un défaut en production.
  • Admission Controllers : les webhooks qui filtrent les écritures, du même ordre que ceux que Kargo installe.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens ce site gratuitement, sans publicité, sans profilage et sans compte à créer. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn