Argo pour Kubernetes : d'Argo CD à Workflows et Image Updater | Un guide complet

J'ai récemment migré tous mes services de GitHub Actions vers un écosystème Argo complet. Cela inclut l'utilisation d'Argo CD pour le déploiement des manifestes Kubernetes, d'Argo Workflows pour la construction des images, et d'Argo Image Updater pour le déploiement continu. Dans ce guide, je vais vous accompagner pas à pas dans la mise en place du même workflow.
Attention : Dans cet article, nous allons apprendre les bases d'Argo et prendrons donc quelques raccourcis par souci de simplicité. Ainsi, certains choix de conception pourraient ne pas être appropriés pour une installation en production.
Vous pouvez suivre ce guide de manière pratique avec le code source complet et les manifestes dans le repos : Argo-tutorial
Écosystème Argo vs GitHub Actions
La première question qui se posent: Pourquoi utiliser Argo au lieu de Github Actions?
Sécurité & Architecture Étant une solution basée sur le modèle pull, Argo est intrinsèquement plus sécurisé. GitHub Actions nécessite un accès externe à votre cluster pour déployer les manifestes. À l'inverse, Argo réside à l'intérieur de votre cluster. Il n'a besoin que de lire votre repos Git ; les identifiants du cluster ne quittent jamais votre réseau.
Nettoyage (Pruning) natif
Le pruning (nettoyage) est natif à Argo. Sans cela, supprimer un fichier YAML de votre repos Git vous obligerait à exécuter manuellement kubectl delete pour retirer la même ressource du cluster. A l'inverse, Argo surveille le repos git, ce qui signifie que si un manifeste de ressource est retiré de Git, Argo va automatiquement le retirer du cluster (on peux désactiver cette option). Avec Argo, votre repos est la source de vérité, et votre cluster garde le contrôle.
Visibilité Je trouve également Argo plus facile à déboguer et à visualiser. Son interface web (UI) montre clairement ce qui est déployé et ce qui a mal tourné.

Indépendance vis-à-vis de GitHub Pour moi, ces fonctionnalités n'étaient pas les principales raisons de ce changement. La curiosité a joué un rôle, mais le principal déclencheur a été que mon organisation GitHub à été signalé comme frauduleux par erreur pendant plusieurs mois. Cela à désactivé plusieurs composants comme les Github Actions.
Pourquoi ? L'une de mes Actions consistait à vérifier tous les liens de mon blog. Elle cherchait les liens morts et effectuait une analyse interne. L'un de ces liens externes avait changé de propriétaire pour quelqu'un sur la liste noire de GitHub. Un workflow qui fonctionnait depuis des mois m'a soudainement valu un signalement, sans préavis, sans e-mail, ni avertissement.
Après des mois de discussions avec le support de GitHub qui refusait simplement d'expliquer le problème, j'ai finalement trouver la solution et récupéré mon compte. Entre-temps, j'avais besoin d'une alternative qui ne dépende pas des caprices d'une organisation externe. J'ai donc tout basculé sur Argo, en ne gardant que mon code sur GitHub (pour l'instant).
La beauté d'Argo, c'est que je pourrais passer à GitLab ou à un Gitea auto-hébergé sans pratiquement aucun changement ; Argo se fiche de l'endroit où les repos sont hébergés. Je suis maintenant totalement indépendant de GitHub. La seule raison pour laquelle je continue d'utiliser GitHub est de m'assurer que mon code est sauvegardé en dehors de mon cluster.
Prérequis
Evidemment, avant de commencer, vous aurez besoin d'un cluster Kubernetes fonctionnel et l'outil CLI kubectl configurée pour y accéder. Lisez cet article et celui-ci si vous voulez en savoir plus à ce sujet.
Vous aurez également besoin de Helm. Helm va nous aider à installer Argo. L'installer est aussi simple que d'exécuter l'une de ces commandes :
#Linux
sudo apt-get install helm
#Windows
choco install kubernetes-helm
#Mac OS
brew install helmFor more package manager see the official documentation.
Étape 1 : Installer Argo CD
Argo CD est le personnage principal de cette histoire. Son rôle est de surveiller votre repos Git et de maintenir votre cluster Kubernetes synchronisé avec les manifestes qui y sont stockés. Cela signifie que chaque fois que vous commitez une modification dans un manifeste, Argo le verra et l'appliquera directement.
Pour être clair, Argo CD ne gère que vos fichiers yaml, c'est tout. Il ne construit ni ne deploie d'images. Pour cela, nous utiliserons Argo Workflow et Argo Image Updater. Nous verrons comment faire cela plus tard, mais une chose à la fois.
Installer Argo CD avec des valeurs personnalisées
Pour déployer Argo, nous devons d'abord ajouter le repos Helm d'Argo :
helm repo add argo https://argoproj.github.io/argo-helm
helm repo updateEnsuite, il est de bonne pratique de créer un fichier values.yaml pour stocker les paramètres que nous utiliserons pour configurer Argo. Voici un exemple :
global:
domain: argo.home.arpa
configs:
params:
server.insecure: true
server.redirect.https: "false"
server:
ingress:
enabled: true
hostname: argo.home.arpa
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
paths:
- /
pathType: PrefixCette configuration indique que l'interface web (UI) d'Argo sera déployée sur argo.home.arpa de manière non sécurisée, ce qui est acceptable pour une configuration locale. Bien sûr, vous pouvez choisir n'importe quelle autre URL pour le hostname, c'est à vous de décider.
Si vous souhaitez un accès à distance avec HTTPS, je vous suggère de lire mon article précédent sur le sujet. Notez que cela est risqué car cela ajoute un point d'attaque potentielle.
Vous pouvez enregistrer ce manifeste où vous le souhaitez. Pour ma part, comme il s'agit d'une infrastructure essentielle, je l'enregistre dans mes repos d'infrastructure, dans un dossier nommé argocd/. Je ferai de même pour le reste des fichiers nécessaires à la configuration d'Argo.
Ensuite, vous pouvez installer Argo via Helm avec cette commande, qui créera le namespace argocd et déploiera Argo CD :
helm upgrade --install argocd argo/argo-cd --namespace argocd --create-namespace -f values.yamlUne fois terminé, vous devriez pouvoir vous connecter via l'URL configurée, pour moi argo.home.arpa. Pour votre première connexion, votre nom d'utilisateur est "admin" et votre mot de passe est un secret Kubernetes nommé argocd-initial-admin-secret. Vous pouvez le récupérer en ligne de commande comme ceci :
#Linux
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
#Windows
$encoded = kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}"
[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($encoded))Vous pouvez également envisager d'utiliser une interface web comme Portainer pour accéder plus facilement aux secrets.
Vous devriez maintenant pouvoir vous connecter et trouver une interface Argo vide, prête à l'emploi. C'est là que vous trouverez vos applications et leur statut. C'est très utile pour le débogage.

Configurer les identifiants du repos
Argo CD a besoin d'un PAT (Personal Access Token) GitHub pour obtenir un accès en lecture à vos repos Git. Afin de le stocker de manière sécurisée dans votre cluster, créons un secret via un manifeste :
apiVersion: v1
kind: Secret
metadata:
name: github-org-pat-creds
namespace: argocd
labels:
argocd.argoproj.io/secret-type: repo-creds
type: Opaque
stringData:
url: https://github.com/Local-pie/
username: JudicaelPoumay
password: ghp_xxxxxxxxxxxxNotez le label secret-type. Argo recherche les secrets avec ce label dans son namespace. Vous pouvez définir plusieurs secrets de ce type si vous avez besoin d'accéder à plusieurs repos ou organisations. Pour ma part, chaque repos lié à mon cluster se trouve dans une seule organisation; ce qui simplifie les choses car je n'ai pas à créer un secret pour chaque nouveau repos, mais c'est à vous de voir.
N'oubliez pas de remplacer ghp_xxxxxxxxxxxx par votre PAT. Vous pouvez en générer un en allant dans GitHub Settings > Developer Settings > Personal Access Tokens (Documentation GitHub). Pour une configuration simple, créez un jeton classique avec les accès en lecture appropriés.
Pour un déploiement en production, préférez un jeton à granularité fine (fine-grained token) avec des permissions de lecture limitées au repos plutôt qu'un PAT classique.
Ensuite, appliquez-le :
kubectl apply -f secret.yamlConseil pour les débutants : Ne commitez jamais de secrets, même sur un repos GitHub privé ; les fuites de données arrivent. Si vous souhaitez stocker des secrets dans GitHub tout en restant indépendant de leurs services, je vous suggère d'utiliser des solution comme KubeSeal.
Comment utiliser Argo CD
C'est tout pour l'installation; voyons maintenant comment tout cela fonctionne.
Une application simple
Argo CD garantit que l'état de votre cluster reflète strictement votre repos Git. Ainsi, vous définissez l'état souhaité dans Git à travers un ensemble de fichiers yaml, et Argo CD réconcilie continuellement l'état réel du cluster pour qu'il corresponde.
Pour ce faire, nous devons définir un fichier YAML séparé pour Argo. Ensuite, on l'applique comme n'importe quel autre YAML au cluster Kubernetes. Par exemple, voici la définition de mon application Argo pour mon homepage :
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: homepage
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/Local-pie/homepage.git
targetRevision: main
path: k8s
destination:
server: "https://kubernetes.default.svc"
namespace: utils-dev
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=trueLa plupart des champs sont explicites, alors concentrons-nous sur quelques parties intéressantes :
- project: Les projets sont comme les organisations GitHub, ils fournissent un regroupement logique, des contrôles de sécurité et plus encore. Nous utiliserons
defaultpour nous simplifier la vie. - targetRevision: C'est la branche qu'Argo va récupérer et utiliser comme source de vérité.
- server: Pointe vers votre cluster. Si Argo est auto-hébergé sur le même cluster, c'est
https://kubernetes.default.svc. - namespaces: Notez que le manifeste de l'Application a un namespace, le même qu'Argo. Mais l'application
homepageelle-même a son propre namespace qui est différent. - syncPolicy: Définit comment nous voulons synchroniser.
- Prune: Si un manifeste est retiré de votre repos Git, Argo CD le nettoie immédiatement du cluster.
- selfHeal: Argo corrige toute dérive de configuration, ce qui signifie que si vous supprimez manuellement quelque chose du cluster, Argo le redéploiera immédiatement.
- CreateNamespace: Signifie qu'Argo est autorisé à créer le namespace de l'application s'il n'existe pas.
L'argument path est particulièrement intéressant car il contient le chemin vers le dossier de vos manifestes Kubernetes. Il peut s'agir d'un dossier avec un simple ensemble de fichiers YAML, mais aussi d'un dossier avec une configuration de type Helm, avec un fichier values.yaml, Chart.yaml et un dossier templates/. Argo détectera automatiquement lequel vous utilisez.
Note : Le chemin est relatif à la racine Git, et non à l'emplacement de ce fichier d'Application Argo. Personnellement, j'aime conserver la définition de mon Application Argo dans un dossier
Argo/séparé à la racine.
Voici a quoi peut ressembler votre structure de fichiers :
# Configuration Kubernetes simple
Argo/
└── app.yaml
k8s/
├── namespace.yaml
├── deployment.yaml
├── service.yaml
└── ingress.yaml
# Configuration Helm Kubernetes
Argo/
└── app.yaml
k8s/
├── Chart.yaml
├── values.yaml
└── templates/
├── namespace.yaml
├── ingress.yaml
├── deployment.yaml
└── service.yaml
Une fois configuré, appliquez le fichier Argo avec kubectl apply comme tout le reste jusqu'à présent. Il devrait alors apparaître dans votre interface Argo, et vous devriez voir Argo lire votre repos Git et déployer les manifestes présent. (N'oubliez pas de les commiter avant ;) )
Pour des projets basiques, on peux s'arreter la. Si tout ce que vous avez besoin c'est de deployer des manifests, c'est peut-être sufisant. Evidemment, on peux faire plus avec Argo.
Déployer sur plusieurs environnements
À un moment donné, vous aurez probablement besoin de mettre en place un environnement de pré-production séparé, distinct de la production réelle. Avec Argo, il vous suffit de créer un nouveau fichier d'Application.
Soit vous le faites pointer vers un dossier séparé, soit, si vous utilisez Helm, vous pouvez indiquer à Argo quel values.yaml utiliser et dans quel ordre avec les arguments Helm. Par exemple, voici un configuration pre-prod :
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: homepage
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/Local-pie/homepage.git
targetRevision: develop
path: k8s
helm:
valueFiles:
- values.yaml
- values-preprod.yaml
destination:
server: "https://kubernetes.default.svc"
namespace: utils-dev
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=trueLe dossier k8s ressemblerait alors à ceci :
k8s/
├── Chart.yaml
├── values.yaml
├── values-prd.yaml
├── values-preprod.yaml
└── templates/
├── namespace.yaml
├── ingress.yaml
├── deployment.yaml
└── service.yaml
Étape 2 : Installer Argo Workflows
Passer à Argo CD est étrange car ce n'est pas vraiment un remplacement pour GitHub Actions tel quel. Mettre à jour des fichiers YAML, c'est bien, mais qu'en est-il si nous avons besoin de workflows pour construire des images ou faire autre chose ? La solution est Argo Workflows !
Argo Workflows est essentiellement un orchestrateur capable de créer et de gérer des Jobs Kubernetes pour faire tout ce que vous voulez. Dans notre cas, nous allons construire et déployer des images à partir de repos GitHub.
Installer Argo Workflows
Commençons par créer un fichier values.yaml.
server:
# Disables HTTPS inside the pod
secure: false
# Bypasses the UI login
extraArgs:
- --auth-mode=server
# Configures the Ingress
ingress:
enabled: true
ingressClassName: "traefik"
hosts:
- workflow.home.arpa
paths:
- /
pathType: PrefixNotez que, similairement à Argo CD, je désactive également HTTPS car je n'ai ni le besoin ni l'envie de gérer des certificats ici. Dans ce YAML, je définis également l'Ingress pour l'interface web.
kubectl create namespace argo
helm install argo argo/argo-workflows -n argo --create-namespace -f values.yamlPourquoi créer un nouvel namespace et ne pas utiliser celui d'Argo CD ? Les séparer prévient les risques de sécurité en empêchant les workflows d'accéder aux secrets de déploiement d'Argo CD. Cela vous permet également de mettre à jour, gérer ou supprimer chaque outil indépendamment sans affecter l'autre.
Une fois terminé, vous devriez pouvoir accéder à l'interface en une minute ou deux. Dans ce cas, aucun mot de passe n'est nécessaire car, dans cet exemple, j'ai décidé de définir --auth-mode=server, ce qui désactive l'identification. Cela peut être acceptable tant que vous avez un réseau local sécurisé et que vous savez ce que vous faites. C'est à vous de décider.

Configurer un namespace séparé pour les Workflows
Vous ne voulez pas que vos workflows se trouvent dans le même namespace que le contrôleur pour deux raisons.
- Sécurité : Des espaces de noms séparés signifient un rayon d'impact plus petit en cas de problème.
- Ressources : Les workflows peuvent être gourmands en ressources et pourraient priver le contrôleur de ressources. Il est plus facile de définir des quotas de ressources dans un namespace séparé.
Créons donc un troisième namespace pour les workflows :
kubectl create namespace argo-builds
Lorsque nous créerons des workflows plus tard, nous les assignerons à cet namespace.
Configurer les identifiants du repos
Argo Workflows a également besoin d'un PAT GitHub pour lire vos repos Git. Similairement à Argo CD, créons un secret via un manifeste :
apiVersion: v1
kind: Secret
metadata:
name: github-org-pat-creds
namespace: argo-builds
type: Opaque
stringData:
url: https://github.com/Local-pie/
username: JudicaelPoumay
password: ghp_xxxxxxxxxxxxNotez qu'il n'y a pas de label ici, c'est parce que nous utiliserons ce secret explicitement plus tard. Argo Workflow ne le cherchera pas. Notez aussi que le namespace est celui où les workflows s'exécuteront.
N'oubliez pas de l'enregistrer de manière sécurisée et de l'appliquer.
Configurer le RBAC pour les workflows
Nos workflows auront besoin de permissions pour gérer les résultats des tâches et les déploiements. Voici un autre manifeste YAML à appliquer pour définir l'ensemble des permissions nécessaires :
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: workflow-taskresult-manager
namespace: argo-builds
rules:
- apiGroups: ["argoproj.io"]
resources: ["workflowtaskresults", "workflows", "cronworkflows"]
verbs: ["create", "get", "list", "watch", "patch", "update"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "patch", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: workflow-taskresult-binding
namespace: argo-builds
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: workflow-taskresult-manager
subjects:
- kind: ServiceAccount
name: default
namespace: argo-buildsDéfinir les quotas de ressources
Enfin, les workflows peuvent devenir très lourds, surtout sur de petits clusters. Vous pouvez limiter les ressources CPU pour les workflows dans le namespace argo-builds pour éviter l'épuisement des ressources. Par exemple, voici un manifeste ResourceQuota qui plafonne le CPU à 2 cœurs et les jobs concurrents à 5 :
apiVersion: v1
kind: ResourceQuota
metadata:
name: job-and-cpu-quota
namespace: argo-builds
spec:
hard:
requests.cpu: "1"
limits.cpu: "2"
count/jobs.batch: "5"Soyez particulièrement prudent lorsque vous limitez les jobs concurrents. Les jobs terminés comptent toujours dans la limite jusqu'à ce que Kubernetes les supprime. Cela peut empêcher de nouveaux jobs de s'exécuter jusqu'à ce que les anciens soient nettoyés. Kubernetes n'est pas très flexible sur ce point, et il n'y a pas de moyen précis de distinguer les jobs actifs des jobs terminés dans la configuration de la limite.
Comment configurer un Argo Workflow
Un Workflow de base
Commençons par les bases : un simple Argo Workflow pour afficher "Hello World" en deux étapes.
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: two-stage-
namespace: argo-builds
spec:
entrypoint: main
templates:
# The main entrypoint now defines a sequence of steps
- name: main
steps:
- - name: stage-one
template: echo-message
arguments:
parameters:
- name: message
value: "Starting stage one... Hello!"
- - name: stage-two
template: echo-message
arguments:
parameters:
- name: message
value: "Stage one complete. Hello from stage two!"
# A reusable template that accepts a parameter
- name: echo-message
inputs:
parameters:
- name: message
container:
image: alpine:latest
command: ["sh", "-c"]
args: ["echo '{{inputs.parameters.message}}'"]Voici ce qu'il se passe :
generateName: Définit le préfixe du workflow. Argo ajoute un suffixe aléatoire lors de l'instanciation, permettant plusieurs instances sans conflit de nom. Si vous utilisez unnamefixe à la place, vous ne pouvez pas le ré-exécuter tant que le précédent n'est pas nettoyé.namespace: Comme mentionné, nous utilisons le namespace dédiéargo-buildspour nos jobs.templates: Comme des fonctions dans un langage de programmation, ce sont des blocs de code réutilisables.echo-message: Utilise l'image Alpine pour afficher un message paramétré.stage-one&stage-two: Réutilisentecho-messagepour afficher des textes différents.
entrypoint: Spécifie le template principal à exécuter (ici,main).steps: Définit le workflow comme une séquence d'étapes. Dans ce cas, nos deux étapes.
Il y a beaucoup plus de paramètres pour personnaliser, mais vous avez l'idée. Je vous montrerai un exemple plus complexe plus tard.
Alors, comment l'exécuter ? Vous pourriez le déclencher manuellement via l'interface web d'Argo, la CLI, ou kubectl, mais ce n'est pas ce que nous cherchons. Pour automatiser cela, nous pouvons configurer des déclencheurs pour les workflows via Argo Events ou l'API REST d'Argo, ce qui ouvre des configurations intéressantes.
Un CronWorkflow de base
Cependant, dans notre quête pour remplacer GitHub Actions, il existe une autre méthode : le CronWorkflow. Comme son nom l'indique, c'est un workflow qui s'exécute selon un calendrier. Je préfère cette option car elle me permet d'éviter une source d'événements externe.
J'ai configuré le mien pour qu'il s'exécute toutes les 5 minutes pour construire et pousser les nouvelles images. J'ai choisi cette approche plutôt qu'Argo Events car elle me permet de retirer les GitHub Actions/webhooks du chemin critique et de garder la configuration autonome. Ce n'est pas nécessairement la solution la plus efficace.
Naturellement, je ne veux pas construire toutes les 5 minutes, car cela créerait une charge très lourde sur mon cluster. Cependant, il y a une solution simple : vérifier simplement si vous avez besoin de construire. Nous explorerons comment implémenter cette vérification dans les sections suivantes.
Notez que vous pouvez aussi les déclencher manuellement en plus de leurs exécutions planifiées via l'interface web ou la CLI.
Voici un exemple de CronWorkflow :
apiVersion: argoproj.io/v1alpha1
kind: CronWorkflow
metadata:
name: hello-world-cron
namespace: argo-builds
spec:
schedules:
- "* * * * *"
concurrencyPolicy: Forbid
workflowSpec:
entrypoint: hello
templates:
- name: hello
container:
image: alpine:latest
command: [sh, -c]
args: ["echo 'Hello World'"]C'est presque la même chose que ce que nous avions auparavant. Seules quelques choses changent :
- name: change en
CronWorkflow - concurrencyPolicy: Que se passe-t-il si un nouveau job veut démarrer alors qu'un autre n'est pas terminé ? Vous pouvez
Allow(autoriser),Forbid(attendre), ouReplace(tuer l'ancien). - schedules: définit une liste de planifications via des expressions Cron, vous pouvez donc avoir un workflow complexe multi-planifié si nécessaire.
Si vous ne le savez pas, les expressions Cron fonctionnent comme ceci :
* * * * *
│ │ │ │ │
│ │ │ │ └─── Jour de la semaine (0 - 6, où 0 est Dimanche)
│ │ │ └───── Mois (1 - 12)
│ │ └─────── Jour du mois (1 - 31)
│ └───────── Heure (0 - 23)
└─────────── Minute (0 - 59)Exemples :
"0 12 * * *"# S'exécute tous les jours à 12:00"0 0 * * 1"# S'exécute tous les lundis à 00:00"*/5 * * * *"# S'exécute toutes les 5 minutes
L'architecture du pipeline de build
Maintenant, construisons un vrai pipeline de CI avec cela. Mon pipeline de CI de base a trois étapes :
- Récupérer le Hash : Je récupère le dernier hash de commit de la branche cible sans télécharger les fichiers du repos. C'est rapide et ça évite du travail inutile.
- Vérifier le Cache : J'interroge mon registre de conteneurs pour vérifier si une image avec le tag du hash de commit existe déjà. Si l'image existe, le pipeline saute la construction. De cette façon, je ne construis pas constamment des images, mais seulement lorsque le repos Git est mis à jour.
- Construire : Si le hash a changé, je construis l'image Docker.
À des fins de débogage, je définis également une stratégie TTL pour nettoyer automatiquement les workflows terminés :
- Les workflows qui réussissent sont supprimés après 180 secondes (3 minutes).
- Les workflows qui échouent sont conservés pendant 3600 secondes (1 heure) pour le débogage.
- Rappel sur les Quotas de Ressources : Comme je l'ai expliqué plus haut, si vous avez défini une limite de jobs dans vos quotas de ressources, cela peut entraîner une pénurie dans le namespace. Un job en échec qui ne fait rien compte toujours comme un job, ce qui peut empêcher de nouveaux jobs de s'exécuter.
Créer des Cluster Workflow Templates
Comme je réutilise le même pipeline de CI partout, faire des copier-coller serait un gaspillage. La solution d'Argo ici est le WorkflowTemplate : définissez le pipeline une fois, réutilisez-le partout. Vous pouvez utiliser ClusterWorkflowTemplate pour une utilisation entre différents espaces de noms, mais comme tous nos workflows sont dans argo-builds, nous n'en avons pas besoin.
Un WorkflowTemplate ressemble à un workflow normal, sauf qu'il n'est pas destiné à être exécuté seul. La principale différence ? Ils n'utilisent pas generateName car ils doivent être référencés par leur nom.
Voici le workflow que j'ai décrit plus tôt. Il est complexe (lisez-le si vous osez), mais c'est un parfait exemple de la puissance d'Argo.
Les éléments notables sont :
- arguments: Ce sont les variables passées dans le template (nous y reviendrons).
- volumes et containerSet: Nous montons des volumes
emptyDirsur/workspaceet/tmp. Dans les workflows Argo standards, les étapes s'exécutent dans des pods séparés, ce qui signifie que vous devez jongler avec des sidecars ou pousser des données vers un stockage juste pour partager des fichiers. Avec moncontainerSet, plusieurs conteneurs s'exécutent dans le même Pod. Cela leur permet de partager un état localement — comme écrire legit shadans/tmp/shaou partager le repos dans/workspace/srcsans toute cette surcharge. - Exécution Conditionnelle: L'étape 2 (
check-registry) écrittrueoufalsedans/tmp/build_needed. L'étape 3 (buildkit-build) vérifie ce fichier à l'exécution :if [ "$(cat /tmp/build_needed)" = "false" ]; then exit 0; fi.
Ce n'est pas le plus beau workflow que vous verrez, mais il fonctionne. Le voici :
apiVersion: argoproj.io/v1alpha1
kind: WorkflowTemplate
metadata:
name: build-template
spec:
# Define default values that can be overridden by the calling Workflow / CronWorkflow
arguments:
parameters:
- name: repo-url
value: "github.com/Local-pie/homepage.git"
- name: git-branch
value: "main"
- name: image-name
value: "zot.home.arpa/homepage"
- name: image-tag-prefix
value: "preprod"
- name: target-platform
value: "linux/arm64"
ttlStrategy:
secondsAfterSuccess: 180
secondsAfterFailure: 3600
entrypoint: build-pipeline
volumes:
- name: workspace
emptyDir: {}
- name: tmp
emptyDir: {}
templates:
- name: build-pipeline
containerSet:
volumeMounts:
- mountPath: /workspace
name: workspace
- mountPath: /tmp
name: tmp
containers:
# 1. Instantly fetch the latest commit hash without downloading files
- name: get-sha
image: docker.io/alpine/git:v2.54.0
resources:
requests:
cpu: "10m"
limits:
cpu: "50m"
env:
- name: GIT_USERNAME
valueFrom:
secretKeyRef:
name: "github-org-pat-creds"
key: username
- name: GIT_PASSWORD
valueFrom:
secretKeyRef:
name: "github-org-pat-creds"
key: password
command: [sh, -c]
args:
- |
git ls-remote https://${GIT_USERNAME}:${GIT_PASSWORD}@{{workflow.parameters.repo-url}} {{workflow.parameters.git-branch}} | awk '{print $1}' > /tmp/sha
echo "Target Hash: $(cat /tmp/sha)"
# 2. Check if this hash already exists in the registry
- name: check-registry
image: quay.io/skopeo/stable:latest
resources:
requests:
cpu: "10m"
limits:
cpu: "50m"
dependencies: [get-sha]
command: [sh, -c]
args:
- |
COMMIT_HASH=$(cat /tmp/sha)
TAG_NAME="{{workflow.parameters.image-tag-prefix}}-$COMMIT_HASH"
echo "Checking if tag $TAG_NAME exists..."
if skopeo inspect --tls-verify=false docker://{{workflow.parameters.image-name}}:$TAG_NAME > /dev/null 2>&1; then
echo "Tag exists. Skipping build."
echo "false" > /tmp/build_needed
else
echo "Tag missing. Triggering build."
echo "true" > /tmp/build_needed
fi
# 3. Build and push using BuildKit
- name: buildkit-build
image: moby/buildkit:v0.19.0
securityContext:
privileged: true
resources:
requests:
cpu: "200m"
limits:
cpu: "1"
dependencies: [check-registry]
env:
- name: GIT_USERNAME
valueFrom:
secretKeyRef:
name: "github-org-pat-creds"
key: username
- name: GIT_PASSWORD
valueFrom:
secretKeyRef:
name: "github-org-pat-creds"
key: password
command: [sh, -c]
args:
- |
if [ "$(cat /tmp/build_needed)" = "false" ]; then
echo "Skipping buildkit build."
exit 0
fi
# Extract registry domain dynamically for insecure configuration
REGISTRY_DOMAIN=$(echo "{{workflow.parameters.image-name}}" | cut -d'/' -f1)
# Create buildkitd config for insecure registry access
mkdir -p /etc/buildkit
cat > /etc/buildkit/buildkitd.toml <<EOF
[registry."$REGISTRY_DOMAIN"]
http = true
insecure = true
EOF
# Start buildkitd in background with config
nohup buildkitd --oci-worker-snapshotter=native --config /etc/buildkit/buildkitd.toml > /tmp/buildkitd.log 2>&1 &
BUILDKITD_PID=$!
echo "Started buildkitd (PID: $BUILDKITD_PID), waiting for readiness..."
# Wait up to 60s for daemon socket to appear and be ready
READY=false
for i in $(seq 1 60); do
if [ -S /run/buildkit/buildkitd.sock ] && buildctl connections 2>/dev/null | head -n1 >/dev/null; then
echo "BuildKit daemon is ready after ${i}s."
READY=true
break
fi
sleep 1
done
if [ "$READY" = false ]; then
echo "ERROR: BuildKit daemon failed to start!"
cat /tmp/buildkitd.log
exit 1
fi
# Clone the repo into a local directory
git clone https://${GIT_USERNAME}:${GIT_PASSWORD}@{{workflow.parameters.repo-url}} /workspace/src --depth 1 --branch {{workflow.parameters.git-branch}} \
2>&1 || { echo "ERROR: git clone failed"; exit 1; }
COMMIT_HASH=$(cd /workspace/src && git rev-parse HEAD)
echo "$COMMIT_HASH" > /tmp/sha
# Write build command using the parameters (Using standard string concatenation instead of printf %% to avoid shell parsing bugs)
echo "buildctl build --frontend=dockerfile.v0 --local context=/workspace/src --local dockerfile=/workspace/src --opt filename=Dockerfile --opt platform={{workflow.parameters.target-platform}} --opt provenance=false --output \"type=image,name={{workflow.parameters.image-name}}:{{workflow.parameters.image-tag-prefix}}-$COMMIT_HASH,push=true,compression=gzip,oci-mediatypes=true\" --import-cache type=registry,ref={{workflow.parameters.image-name}}/cache --export-cache type=registry,mode=max,ref={{workflow.parameters.image-name}}/cache,oci-mediatypes=true" > /tmp/build.sh
# Run build for parameterized tag
sh /tmp/build.sh 2>&1 || exit 1
# Retag and push prefix-latest using the cached build
sed "s/{{workflow.parameters.image-tag-prefix}}-.\*/{{workflow.parameters.image-tag-prefix}}-latest/g" /tmp/build.sh > /tmp/build_latest.sh
sh /tmp/build_latest.sh 2>&1 || exit 1Exemple de CronWorkflow pour le CI/CD
Maintenant que nous avons un template, nous pouvons le réutiliser dans plusieurs projets. Il accepte des paramètres pour l'URL du repos, la branche git, le nom de l'image, le préfixe du tag de l'image et la plateforme cible. C'est ainsi que je l'utilise en pratique dans tous mes projets où je dois construire des images.
Comparé au bazar ci-dessus, cela semble vraiment propre et clair :
apiVersion: argoproj.io/v1alpha1
kind: CronWorkflow
metadata:
name: {{ .Values.app.namespace }}-build
namespace: argo-builds
spec:
schedule: "*/5 * * * *"
concurrencyPolicy: Forbid
workflowSpec:
workflowTemplateRef:
name: build-template
arguments:
parameters:
- name: git-branch
value: "{{ .Values.git.branch }}"
- name: image-tag-prefix
value: "{{ .Values.image.prefix }}"
- name: repo-url
value: "github.com/Local-pie/the-thought-process.git"
- name: image-name
value: "zot.home.arpa/thethoughtprocess"Vous pouvez surcharger n'importe quel paramètre lorsque vous créez un CronWorkflow qui référence ce template. Si vous omettez un paramètre, il utilisera la valeur par défaut.
J'aime enregistrer mes workflows dans le même dossier k8s qui est surveillé par Argo CD. Ainsi, si je mets à jour le workflow, Argo CD le met à jour. Il apparaît également dans l'interface d'Argo CD :

Vous devriez également le voir dans l'interface d'Argo Workflow, listé sous Cron Workflow :

Vous pouvez aussi voir comment il s'exécute :

Étape 3 : Installer Argo CD Image Updater
Nous approchons de la fin, mais il nous manque une dernière étape.
Nous pourrions forcer la mise à jour de l'image déployée en utilisant des workflows post-build, mais cela brise le modèle GitOps en contournant Git. À la place, ArgoCD Image Updater automatise ce processus nativement. Il interroge notre registre de conteneurs pour de nouvelles images et réécrit les nouveaux tags directement dans Git, restaurant ainsi le flux GitOps.
Son fonctionnement est simple : l'updater surveille votre registre pour de nouveaux tags et met à jour automatiquement les images déployées. Mettons cela en place.
Installer l'Image Updater
D'abord, nous installons Argo Image Updater
helm install argocd-image-updater argo/argocd-image-updater --namespace argocdSi vous avez suivi ma configuration de Zot auto-hébergé, vous devrez également surcharger la ConfigMap pour autoriser le pulling depuis votre Zot non sécurisé. Par exemple :
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-image-updater-config
namespace: argocd
data:
registries.conf: |
registries:
- name: Zot
api_url: http://zot.home.arpa
prefix: zot.home.arpa
insecure: trueComment ça marche
Annotations de l'Image Updater
Argo Image Updater fait le pont entre votre processus de build et Argo CD. Il surveille votre registre de conteneurs pour de nouvelles images et met automatiquement à jour les tags des images dans vos manifestes d'Application Argo CD.
Pour activer cela, il vous suffit d'ajouter une annotation à votre manifeste d'Application Argo CD spécifiant la stratégie de mise à jour :
metadata:
annotations:
argocd-image-updater.argoproj.io/image-list: my-app-registry=zot.home.arpa/my-app-registry
argocd-image-updater.argoproj.io/my-app-registry.update-strategy: newest-build
argocd-image-updater.argoproj.io/my-app-registry.allow-tags: regexp:^prd-[a-f0-9]+$
argocd-image-updater.argoproj.io/write-back-method: argocdLaissez-moi expliquer les parties clés :
image-list: Définit quel est le repos de votre registre d'images d'application.update-strategy: Définit quelle image utiliser. Ici,newest-buildindique l'image la plus récente.allow-tags: Regex pour filtrer les images avec des noms commeprd-a1b2c3....- Note : L'expression régulière
allow-tagsexige que le tag dans votre manifeste de déploiement commence parprd-. Si votre manifeste utiliselatest, l'updater l'ignorera carlatestne correspond pas à votre regex. Vous devez utiliser un placeholder modifiable commeprd-a1b2c3.
- Note : L'expression régulière
write-back-method: Définit comment l'updater commit le changement.git: Le changement est commité dans Git, et Argo CD est censé le récupérer. Git reste la source de vérité, mais cela ajoute des commits automatisés à votre historique. Incompatible avec ma stratégie de CronWorkflow, car un nouveau commit déclencherait un nouveau build, menant ainsi à une boucle infinie.argocd: Nous demandons à Argo de mettre à jour l'image directement. Pas de commits automatisés, mais Git n'est plus exactement la source de vérité.
Ces annotations sont placées dans votre manifeste d'application Argo CD comme ceci :
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
annotations:
argocd-image-updater.argoproj.io/image-list: my-app-registry=zot.home.arpa/my-app-registry
argocd-image-updater.argoproj.io/my-app-registry.update-strategy: newest-build
argocd-image-updater.argoproj.io/my-app-registry.allow-tags: regexp:^prd-[a-f0-9]+$
argocd-image-updater.argoproj.io/write-back-method: argocd
name: homepage
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/Local-pie/homepage.git
targetRevision: main
path: k8s
destination:
server: "https://kubernetes.default.svc"
namespace: utils-dev
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=trueDéploiement multi-images
Si vous avez plusieurs images dans votre déploiement, vous pouvez configurer Argo CD Image Updater pour les gérer séparément. Dans cet exemple, j'utilise digest pour le backend, ce qui indique à l'updater de surveiller les changements dans le digest de l'image plutôt qu'un tag spécifique.
metadata:
annotations:
# 1. Define multiple images using aliases separated by commas
argocd-image-updater.argoproj.io/image-list: fe=zot.home.arpa/my-app-registry/frontend, be=zot.home.arpa/my-app-registry/backend
# --- Frontend Rules (alias: fe) ---
argocd-image-updater.argoproj.io/fe.update-strategy: newest-build
argocd-image-updater.argoproj.io/fe.allow-tags: regexp:^prd-fe-[a-f0-9]+$
# --- Backend Rules (alias: be) ---
argocd-image-updater.argoproj.io/be.update-strategy: digest
argocd-image-updater.argoproj.io/be.allow-tags: regexp: dev
# --- Global Settings ---
argocd-image-updater.argoproj.io/write-back-method: argocdPour être complet, votre déploiement associé ressemblerait à ceci :
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
# Frontend Container
- name: frontend
#Notice the placeholder `prd-fe-1234567`
#It doens't point to any image
#but respect the expected format in our regex
#Otherwise, Image Updater won't be happy
image: zot.home.arpa/my-app-registry/frontend:prd-fe-1234567
ports:
- containerPort: 80
# Backend Container
- name: backend
image: zot.home.arpa/my-app-registry/backend:dev
ports:
- containerPort: 8080Débogage de l'Image Updater
L'Image Updater est le seul composant sans un bon retour visuel car nous n'avons ni interface utilisateur ni intégration dans l'UI d'Argo CD. Il fonctionne simplement en arrière-plan. S'il y a des problèmes, la meilleure façon est de regarder les logs.
La boucle GitOps complète
À ce stade, nous avons les trois pièces du puzzle :
- Argo CD déploie les manifestes Kubernetes définis dans Git.
- Argo Workflows construit et pousse nos images de conteneur.
- Argo CD Image Updater surveille le registre pour de nouvelles images.
Voici comment ca marche en résumé.
1. Un commit est poussé sur Git
Tout commence par une modification du code source de l'application. Par exemple, je modifie mon application et je pousse les changements sur la branche main.
2. Argo CD détecte le changement
Argo CD surveille continuellement le repos et détecte que l'état souhaité a changé. Si les manifestes Kubernetes eux-mêmes ont changé, Argo CD peut immédiatement les synchroniser avec le cluster. Cependant, pour les changements de code applicatif, nous devons encore construire une nouvelle image de conteneur. C'est là qu'intervient Argo Workflows.
3. Argo Workflow construit l'image
Notre CronWorkflow vérifie périodiquement le repos pour de nouveaux commits. Il récupère le dernier hash de commit et vérifie si une image pour ce commit existe déjà dans le registre. Si ce n'est pas le cas, le workflow construit l'image et la pousse sur Zot.
Par exemple :
zot.home.arpa/homepage:preprod-8f31c2aÀ ce stade, la nouvelle image existe dans le registre.
4. La nouvelle image devient la nouvelle version
Argo CD Image Updater surveille le registre pour de nouvelles images et met directement à jour la référence d'image utilisée par l'application.
Et ensuite ?
Nous avons terminé ! Je ne peux pas dire que c'était aussi facile que de configurer un Runner GitHub auto-hébergé, mais ce n'était pas si dur.
L'écosystème Argo est assez modulaire et si vous voulez aller plus loin, vous pouvez explorer :
- Argo Rollouts : Allez au-delà des déploiements simples. Implémentez des stratégies canary ou blue-green pour des lancements sans interruption de service. Voir la doc officielle →
- Argo Events : Passez des CronWorkflows planifiés à des pipelines pilotés par les événements qui réagissent aux webhooks ou aux événements cloud. Documentation Argo Events →
- Argo CD Notifications : Gardez le contrôle en configurant des alertes dans Slack ou Teams. (https://argo-cd.readthedocs.io/en/stable/operator-manual/notifications/)
- Et bien plus encore !



