GitOps avec ArgoCD : Setup et Best Practices
GitOps révolutionne la façon dont nous déployons et gérons nos applications Kubernetes. ArgoCD est l'un des outils les plus populaires pour implémenter cette approche, utilisé par des entreprises comme Intuit, Tesla et Red Hat.
Architecture GitOps avec ArgoCD
<svg viewBox="0 0 800 400" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="gitGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#f05033"/>
<stop offset="100%" style="stop-color:#de4c36"/>
</linearGradient>
<linearGradient id="argoGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#ef7b4d"/>
<stop offset="100%" style="stop-color:#e96d3f"/>
</linearGradient>
<linearGradient id="k8sGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#326ce5"/>
<stop offset="100%" style="stop-color:#2756b3"/>
</linearGradient>
</defs>
<!-- Background -->
<rect width="800" height="400" fill="#0f172a"/>
<!-- Git Repository -->
<g transform="translate(80, 180)">
<rect x="-60" y="-60" width="120" height="120" rx="15" fill="url(#gitGrad)" opacity="0.9"/>
<text x="0" y="-20" text-anchor="middle" fill="white" font-size="28" font-weight="bold">Git</text>
<text x="0" y="10" text-anchor="middle" fill="white" font-size="14">Repository</text>
<text x="0" y="35" text-anchor="middle" fill="white" font-size="12" opacity="0.8">Source of Truth</text>
</g>
<!-- ArgoCD -->
<g transform="translate(400, 180)">
<rect x="-80" y="-70" width="160" height="140" rx="15" fill="url(#argoGrad)" opacity="0.9"/>
<text x="0" y="-30" text-anchor="middle" fill="white" font-size="24" font-weight="bold">ArgoCD</text>
<text x="0" y="0" text-anchor="middle" fill="white" font-size="12">Continuous Sync</text>
<text x="0" y="20" text-anchor="middle" fill="white" font-size="12">Self-Healing</text>
<text x="0" y="40" text-anchor="middle" fill="white" font-size="12">Multi-Cluster</text>
</g>
<!-- Kubernetes Clusters -->
<g transform="translate(680, 100)">
<rect x="-50" y="-40" width="100" height="80" rx="10" fill="url(#k8sGrad)" opacity="0.9"/>
<text x="0" y="-5" text-anchor="middle" fill="white" font-size="14" font-weight="bold">Production</text>
<text x="0" y="15" text-anchor="middle" fill="white" font-size="11">Cluster</text>
</g>
<g transform="translate(680, 220)">
<rect x="-50" y="-40" width="100" height="80" rx="10" fill="url(#k8sGrad)" opacity="0.7"/>
<text x="0" y="-5" text-anchor="middle" fill="white" font-size="14" font-weight="bold">Staging</text>
<text x="0" y="15" text-anchor="middle" fill="white" font-size="11">Cluster</text>
</g>
<g transform="translate(680, 340)">
<rect x="-50" y="-40" width="100" height="80" rx="10" fill="url(#k8sGrad)" opacity="0.5"/>
<text x="0" y="-5" text-anchor="middle" fill="white" font-size="14" font-weight="bold">Dev</text>
<text x="0" y="15" text-anchor="middle" fill="white" font-size="11">Cluster</text>
</g>
<!-- Arrows -->
<defs>
<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#22c55e"/>
</marker>
</defs>
<!-- Git to ArgoCD -->
<line x1="145" y1="180" x2="310" y2="180" stroke="#22c55e" stroke-width="3" marker-end="url(#arrowhead)"/>
<text x="227" y="165" text-anchor="middle" fill="#22c55e" font-size="11">Pull</text>
<!-- ArgoCD to Clusters -->
<line x1="490" y1="140" x2="620" y2="100" stroke="#22c55e" stroke-width="2" marker-end="url(#arrowhead)"/>
<line x1="490" y1="180" x2="620" y2="220" stroke="#22c55e" stroke-width="2" marker-end="url(#arrowhead)"/>
<line x1="490" y1="220" x2="620" y2="340" stroke="#22c55e" stroke-width="2" marker-end="url(#arrowhead)"/>
<!-- Legend -->
<text x="400" y="380" text-anchor="middle" fill="#94a3b8" font-size="12">ArgoCD synchronise automatiquement l'etat desire (Git) avec l'etat actuel (Clusters)</text>
</svg>
Qu'est-ce que GitOps ?
GitOps est une méthodologie qui utilise Git comme source de vérité unique pour la configuration de l'infrastructure et des applications. Les principes clés :
| Principe | Description | Avantage |
|---|---|---|
| Déclaratif | Tout est défini dans des manifests YAML | Reproductibilité garantie |
| Versionné | Git comme source de vérité | Rollback instantané |
| Automatique | Réconciliation continue | Zero intervention manuelle |
| Observable | Drift detection | Alerting proactif |
Pourquoi ArgoCD ?
ArgoCD se distingue par :
- UI intuitive : Dashboard web pour visualiser l'état des applications
- Multi-cluster natif : Gestion centralisée de plusieurs clusters
- SSO intégré : OIDC, LDAP, SAML, GitHub, GitLab
- RBAC granulaire : Contrôle fin des permissions
- ApplicationSet : Déploiement à grande échelle
Installation d'ArgoCD
Prérequis
# Vérifier la version de Kubernetes (1.22+)
kubectl version --short
# Vérifier les ressources disponibles
kubectl top nodes
# Ressources minimales recommandées pour ArgoCD :
# - 2 CPU cores
# - 4 GB RAM
# - 20 GB storage (pour les repos git)
Installation via Manifests (Développement)
# Créer le namespace
kubectl create namespace argocd
# Installer ArgoCD (version stable)
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# Attendre que tous les pods soient prêts
kubectl wait --for=condition=available --timeout=300s deployment/argocd-server -n argocd
# Vérifier l'installation
kubectl get pods -n argocd
Installation via Helm (Production)
# Ajouter le repo Helm
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update
# Créer le fichier de values
cat > argocd-values.yaml << 'EOF'
# Configuration Production ArgoCD
global:
image:
tag: "v2.9.3" # Version spécifique pour production
server:
replicas: 2 # Haute disponibilité
# Ingress configuration
ingress:
enabled: true
ingressClassName: nginx
hosts:
- argocd.votredomaine.com
tls:
- secretName: argocd-tls
hosts:
- argocd.votredomaine.com
# Metrics pour Prometheus
metrics:
enabled: true
serviceMonitor:
enabled: true
# Resources
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
controller:
replicas: 1 # Un seul controller
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
# Optimisations
args:
# Limite de reconciliation parallèle
statusProcessors: "20"
operationProcessors: "10"
repoServer:
replicas: 2
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
redis:
enabled: true
resources:
requests:
cpu: 100m
memory: 128Mi
# Haute Disponibilité
redis-ha:
enabled: false # Activer pour HA
applicationSet:
enabled: true
replicas: 2
notifications:
enabled: true
EOF
# Installation
helm install argocd argo/argo-cd \
--namespace argocd \
--create-namespace \
--values argocd-values.yaml \
--wait
Accès Initial
# Récupérer le mot de passe admin initial
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d && echo
# Port-forward pour accès local
kubectl port-forward svc/argocd-server -n argocd 8080:443
# Accéder à https://localhost:8080
# User: admin
# Password: (mot de passe récupéré ci-dessus)
# Installer le CLI ArgoCD
curl -sSL -o argocd https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
chmod +x argocd && sudo mv argocd /usr/local/bin/
# Se connecter via CLI
argocd login localhost:8080 --username admin --password <mot-de-passe> --insecure
Configuration des Applications
Application Simple
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
# Finalizer pour nettoyer les ressources à la suppression
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://github.com/mon-org/mon-app
targetRevision: HEAD
path: k8s/overlays/production
# Pour Helm charts
# helm:
# valueFiles:
# - values-production.yaml
# Pour Kustomize
# kustomize:
# images:
# - my-app=my-registry/my-app:v1.2.3
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true # Supprimer les ressources obsolètes
selfHeal: true # Corriger les drifts automatiquement
allowEmpty: false # Empêcher la suppression totale
syncOptions:
- CreateNamespace=true
- PrunePropagationPolicy=foreground
- PruneLast=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
# Health checks personnalisés
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas # Ignorer si HPA gère les replicas
Application avec Helm
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: nginx-ingress
namespace: argocd
spec:
project: default
source:
repoURL: https://kubernetes.github.io/ingress-nginx
chart: ingress-nginx
targetRevision: 4.8.3
helm:
releaseName: nginx-ingress
values: |
controller:
replicaCount: 2
metrics:
enabled: true
resources:
requests:
cpu: 100m
memory: 128Mi
destination:
server: https://kubernetes.default.svc
namespace: ingress-nginx
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
ApplicationSet pour Multi-Environnement
ApplicationSet permet de gérer plusieurs applications à partir d'un seul template, idéal pour les déploiements multi-cluster ou multi-environnement.
Generator List
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: my-app-environments
namespace: argocd
spec:
generators:
- list:
elements:
- env: production
cluster: https://kubernetes.default.svc
namespace: production
replicas: "3"
resources_cpu: "500m"
resources_memory: "512Mi"
- env: staging
cluster: https://kubernetes.default.svc
namespace: staging
replicas: "2"
resources_cpu: "250m"
resources_memory: "256Mi"
- env: development
cluster: https://kubernetes.default.svc
namespace: development
replicas: "1"
resources_cpu: "100m"
resources_memory: "128Mi"
template:
metadata:
name: 'my-app-{{env}}'
labels:
environment: '{{env}}'
spec:
project: default
source:
repoURL: https://github.com/mon-org/mon-app
targetRevision: HEAD
path: 'k8s/overlays/{{env}}'
kustomize:
images:
- my-app=my-registry/my-app:{{env}}
destination:
server: '{{cluster}}'
namespace: '{{namespace}}'
syncPolicy:
automated:
prune: true
selfHeal: true
Generator Git Directory
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: cluster-addons
namespace: argocd
spec:
generators:
- git:
repoURL: https://github.com/mon-org/cluster-addons
revision: HEAD
directories:
- path: addons/*
template:
metadata:
name: '{{path.basename}}'
spec:
project: cluster-addons
source:
repoURL: https://github.com/mon-org/cluster-addons
targetRevision: HEAD
path: '{{path}}'
destination:
server: https://kubernetes.default.svc
namespace: '{{path.basename}}'
syncPolicy:
automated:
prune: true
selfHeal: true
Generator Matrix (Multi-Cluster + Multi-App)
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: matrix-apps
namespace: argocd
spec:
generators:
- matrix:
generators:
# Clusters
- clusters:
selector:
matchLabels:
env: production
# Applications
- git:
repoURL: https://github.com/mon-org/apps
revision: HEAD
directories:
- path: apps/*
template:
metadata:
name: '{{name}}-{{path.basename}}'
spec:
project: default
source:
repoURL: https://github.com/mon-org/apps
targetRevision: HEAD
path: '{{path}}'
destination:
server: '{{server}}'
namespace: '{{path.basename}}'
Configuration Multi-Cluster
Ajouter un Cluster
# Méthode 1 : Via CLI (recommandée)
argocd cluster add <context-name> --name production-cluster
# Méthode 2 : Via Secret (pour automation)
cat > cluster-secret.yaml << 'EOF'
apiVersion: v1
kind: Secret
metadata:
name: production-cluster
namespace: argocd
labels:
argocd.argoproj.io/secret-type: cluster
stringData:
name: production-cluster
server: https://production-api.example.com:6443
config: |
{
"bearerToken": "<service-account-token>",
"tlsClientConfig": {
"insecure": false,
"caData": "<base64-ca-cert>"
}
}
EOF
kubectl apply -f cluster-secret.yaml
Service Account pour Cluster Distant
# À appliquer sur le cluster distant
apiVersion: v1
kind: ServiceAccount
metadata:
name: argocd-manager
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: argocd-manager
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin # Ou un rôle plus restrictif
subjects:
- kind: ServiceAccount
name: argocd-manager
namespace: kube-system
---
# Kubernetes 1.24+ : Créer le token manuellement
apiVersion: v1
kind: Secret
metadata:
name: argocd-manager-token
namespace: kube-system
annotations:
kubernetes.io/service-account.name: argocd-manager
type: kubernetes.io/service-account-token
RBAC et Sécurité
Configuration RBAC
# argocd-rbac-cm ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-rbac-cm
namespace: argocd
data:
# Politique par défaut
policy.default: role:readonly
# Définition des rôles
policy.csv: |
# Rôle Admin
p, role:admin, applications, *, */*, allow
p, role:admin, clusters, *, *, allow
p, role:admin, repositories, *, *, allow
p, role:admin, projects, *, *, allow
# Rôle Développeur
p, role:developer, applications, get, */*, allow
p, role:developer, applications, sync, */*, allow
p, role:developer, applications, action/*, */*, allow
p, role:developer, logs, get, */*, allow
# Rôle Viewer
p, role:viewer, applications, get, */*, allow
p, role:viewer, logs, get, */*, allow
# Rôle par Projet
p, role:team-a, applications, *, team-a/*, allow
p, role:team-a, logs, get, team-a/*, allow
# Attribution des rôles aux groupes
g, admin@example.com, role:admin
g, developers, role:developer
g, team-a-members, role:team-a
# Scope des tokens
scopes: '[groups, email]'
Configuration SSO (OIDC)
# argocd-cm ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
namespace: argocd
data:
# OIDC Configuration
oidc.config: |
name: Keycloak
issuer: https://keycloak.example.com/realms/master
clientID: argocd
clientSecret: $oidc.keycloak.clientSecret
requestedScopes:
- openid
- profile
- email
- groups
requestedIDTokenClaims:
groups:
essential: true
# Ou pour Azure AD
# oidc.config: |
# name: Azure AD
# issuer: https://login.microsoftonline.com/<tenant-id>/v2.0
# clientID: <client-id>
# clientSecret: $oidc.azure.clientSecret
# requestedScopes:
# - openid
# - profile
# - email
url: https://argocd.example.com
Sync Waves et Hooks
Contrôler l'ordre de déploiement des ressources est crucial pour les applications complexes.
<svg viewBox="0 0 800 200" xmlns="http://www.w3.org/2000/svg">
<rect width="800" height="200" fill="#0f172a"/>
<!-- Timeline -->
<line x1="50" y1="100" x2="750" y2="100" stroke="#475569" stroke-width="2"/>
<!-- Wave -2 : PreSync -->
<g transform="translate(100, 100)">
<circle r="25" fill="#ef4444"/>
<text y="5" text-anchor="middle" fill="white" font-size="10" font-weight="bold">-2</text>
<text y="50" text-anchor="middle" fill="#94a3b8" font-size="10">PreSync</text>
<text y="65" text-anchor="middle" fill="#64748b" font-size="9">Migrations</text>
</g>
<!-- Wave -1 : Database -->
<g transform="translate(250, 100)">
<circle r="25" fill="#f97316"/>
<text y="5" text-anchor="middle" fill="white" font-size="10" font-weight="bold">-1</text>
<text y="50" text-anchor="middle" fill="#94a3b8" font-size="10">Database</text>
<text y="65" text-anchor="middle" fill="#64748b" font-size="9">ConfigMaps</text>
</g>
<!-- Wave 0 : Backend -->
<g transform="translate(400, 100)">
<circle r="25" fill="#22c55e"/>
<text y="5" text-anchor="middle" fill="white" font-size="10" font-weight="bold">0</text>
<text y="50" text-anchor="middle" fill="#94a3b8" font-size="10">Backend</text>
<text y="65" text-anchor="middle" fill="#64748b" font-size="9">Services</text>
</g>
<!-- Wave 1 : Frontend -->
<g transform="translate(550, 100)">
<circle r="25" fill="#3b82f6"/>
<text y="5" text-anchor="middle" fill="white" font-size="10" font-weight="bold">1</text>
<text y="50" text-anchor="middle" fill="#94a3b8" font-size="10">Frontend</text>
<text y="65" text-anchor="middle" fill="#64748b" font-size="9">Ingress</text>
</g>
<!-- Wave 2 : PostSync -->
<g transform="translate(700, 100)">
<circle r="25" fill="#8b5cf6"/>
<text y="5" text-anchor="middle" fill="white" font-size="10" font-weight="bold">2</text>
<text y="50" text-anchor="middle" fill="#94a3b8" font-size="10">PostSync</text>
<text y="65" text-anchor="middle" fill="#64748b" font-size="9">Tests</text>
</g>
<!-- Arrows -->
<path d="M125 100 L225 100" stroke="#22c55e" stroke-width="2" marker-end="url(#arrow)"/>
<path d="M275 100 L375 100" stroke="#22c55e" stroke-width="2" marker-end="url(#arrow)"/>
<path d="M425 100 L525 100" stroke="#22c55e" stroke-width="2" marker-end="url(#arrow)"/>
<path d="M575 100 L675 100" stroke="#22c55e" stroke-width="2" marker-end="url(#arrow)"/>
<defs>
<marker id="arrow" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#22c55e"/>
</marker>
</defs>
</svg>
Exemple Complet avec Waves
# Wave -2 : Pre-sync hook pour migrations
apiVersion: batch/v1
kind: Job
metadata:
name: db-migration
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/hook-delete-policy: HookSucceeded
argocd.argoproj.io/sync-wave: "-2"
spec:
template:
spec:
containers:
- name: migration
image: my-app:latest
command: ["./migrate.sh"]
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-credentials
key: url
restartPolicy: Never
backoffLimit: 3
---
# Wave -1 : ConfigMaps et Secrets
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
annotations:
argocd.argoproj.io/sync-wave: "-1"
data:
config.yaml: |
environment: production
log_level: info
---
# Wave 0 : Deployment principal
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
annotations:
argocd.argoproj.io/sync-wave: "0"
spec:
replicas: 3
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
- name: backend
image: my-app:v1.0.0
ports:
- containerPort: 8080
---
# Wave 1 : Service et Ingress
apiVersion: v1
kind: Service
metadata:
name: backend
annotations:
argocd.argoproj.io/sync-wave: "1"
spec:
selector:
app: backend
ports:
- port: 80
targetPort: 8080
---
# Wave 2 : Post-sync tests
apiVersion: batch/v1
kind: Job
metadata:
name: smoke-tests
annotations:
argocd.argoproj.io/hook: PostSync
argocd.argoproj.io/hook-delete-policy: HookSucceeded
argocd.argoproj.io/sync-wave: "2"
spec:
template:
spec:
containers:
- name: tests
image: curlimages/curl:latest
command:
- /bin/sh
- -c
- |
curl -f http://backend/health || exit 1
echo "Smoke tests passed!"
restartPolicy: Never
Notifications
Configuration Slack
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-notifications-cm
namespace: argocd
data:
# Slack service
service.slack: |
token: $slack-token
# Templates
template.app-deployed: |
message: |
Application {{.app.metadata.name}} is now running new version.
slack:
attachments: |
[{
"title": "{{.app.metadata.name}}",
"title_link": "{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
"color": "#18be52",
"fields": [{
"title": "Sync Status",
"value": "{{.app.status.sync.status}}",
"short": true
}, {
"title": "Repository",
"value": "{{.app.spec.source.repoURL}}",
"short": true
}]
}]
template.app-sync-failed: |
message: |
Application {{.app.metadata.name}} sync has failed!
slack:
attachments: |
[{
"title": "{{.app.metadata.name}}",
"title_link": "{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
"color": "#E96D76",
"fields": [{
"title": "Sync Status",
"value": "{{.app.status.sync.status}}",
"short": true
}, {
"title": "Error",
"value": "{{.app.status.operationState.message}}",
"short": false
}]
}]
# Triggers
trigger.on-deployed: |
- description: Application is synced and healthy
when: app.status.operationState.phase in ['Succeeded'] and app.status.health.status == 'Healthy'
send: [app-deployed]
trigger.on-sync-failed: |
- description: Application sync has failed
when: app.status.operationState.phase in ['Error', 'Failed']
send: [app-sync-failed]
# Subscriptions par défaut
subscriptions: |
- recipients:
- slack:devops-alerts
triggers:
- on-sync-failed
- recipients:
- slack:deployments
triggers:
- on-deployed
---
apiVersion: v1
kind: Secret
metadata:
name: argocd-notifications-secret
namespace: argocd
stringData:
slack-token: xoxb-your-slack-token
Notification par Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: critical-app
annotations:
# Notification spécifique pour cette app
notifications.argoproj.io/subscribe.on-sync-failed.slack: critical-alerts
notifications.argoproj.io/subscribe.on-deployed.slack: critical-deployments
Secrets Management
Sealed Secrets
# Installer le controller
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/controller.yaml
# Installer kubeseal CLI
brew install kubeseal # ou télécharger depuis GitHub
# Créer un sealed secret
kubectl create secret generic my-secret \
--from-literal=password=super-secret \
--dry-run=client -o yaml | \
kubeseal --format yaml > my-sealed-secret.yaml
# Résultat : Secret chiffré stockable dans Git
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: my-secret
namespace: production
spec:
encryptedData:
password: AgBZx7bQF...encrypted...data==
template:
metadata:
name: my-secret
namespace: production
type: Opaque
External Secrets Operator
# Installation
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets external-secrets/external-secrets -n external-secrets --create-namespace
# ClusterSecretStore pour HashiCorp Vault
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: vault-backend
spec:
provider:
vault:
server: "https://vault.example.com"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "argocd"
serviceAccountRef:
name: "external-secrets"
namespace: "external-secrets"
---
# ExternalSecret
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: database-credentials
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: vault-backend
target:
name: database-credentials
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: secret/data/production/database
property: username
- secretKey: password
remoteRef:
key: secret/data/production/database
property: password
Troubleshooting
Erreurs Courantes et Solutions
1. Application stuck in "Syncing"
# Diagnostic
argocd app get my-app
kubectl get application my-app -n argocd -o yaml | grep -A 20 status
# Causes possibles :
# - Ressource bloquée en Terminating
# - Hook PreSync qui échoue
# - Problème de quota
# Solution
kubectl get pods -n <namespace> | grep -E "Terminating|Error"
kubectl delete pod <stuck-pod> --force --grace-period=0
# Forcer le refresh
argocd app refresh my-app --hard
2. "Unable to create application: permission denied"
# Vérifier le projet
argocd proj get default
# Le projet doit autoriser :
# - Le repository source
# - Le cluster de destination
# - Le namespace cible
# Créer un projet correctement configuré
argocd proj create my-project \
--src https://github.com/my-org/* \
--dest https://kubernetes.default.svc,production \
--dest https://kubernetes.default.svc,staging
3. "Repository not accessible"
# Vérifier la configuration du repo
argocd repo list
argocd repo get https://github.com/my-org/my-repo
# Ajouter/mettre à jour les credentials
argocd repo add https://github.com/my-org/my-repo \
--username <user> \
--password <token>
# Pour SSH
argocd repo add git@github.com:my-org/my-repo \
--ssh-private-key-path ~/.ssh/id_rsa
4. "OutOfSync" persistant
# Voir les différences
argocd app diff my-app
# Causes courantes :
# - Champs générés automatiquement (metadata.generation, status)
# - HPA modifiant les replicas
# - Mutations webhooks
# Solution : ignorer les différences légitimes
spec:
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas
- group: ""
kind: Service
jsonPointers:
- /spec/clusterIP
- /spec/clusterIPs
5. Health check failure
# Ajouter des health checks personnalisés
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
namespace: argocd
data:
resource.customizations.health.networking.k8s.io_Ingress: |
hs = {}
hs.status = "Healthy"
if obj.status ~= nil then
if obj.status.loadBalancer ~= nil then
if obj.status.loadBalancer.ingress ~= nil then
hs.status = "Healthy"
else
hs.status = "Progressing"
hs.message = "Waiting for load balancer"
end
end
end
return hs
Scripts de Diagnostic
#!/bin/bash
# argocd-diag.sh - Script de diagnostic complet
echo "=== ArgoCD Diagnostic ==="
echo -e "\n--- Pods Status ---"
kubectl get pods -n argocd
echo -e "\n--- Application Status ---"
argocd app list
echo -e "\n--- Recent Events ---"
kubectl get events -n argocd --sort-by='.lastTimestamp' | tail -20
echo -e "\n--- Controller Logs (last 50 lines) ---"
kubectl logs -n argocd -l app.kubernetes.io/name=argocd-application-controller --tail=50
echo -e "\n--- Server Logs (errors only) ---"
kubectl logs -n argocd -l app.kubernetes.io/name=argocd-server --tail=100 | grep -i error
echo -e "\n--- Resource Usage ---"
kubectl top pods -n argocd
echo -e "\n--- Repo Server Status ---"
kubectl exec -n argocd deployment/argocd-repo-server -- argocd-repo-server version
echo "=== End Diagnostic ==="
Monitoring et Métriques
ServiceMonitor pour Prometheus
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: argocd-metrics
namespace: argocd
labels:
release: prometheus
spec:
selector:
matchLabels:
app.kubernetes.io/name: argocd-metrics
endpoints:
- port: metrics
interval: 30s
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: argocd-server-metrics
namespace: argocd
labels:
release: prometheus
spec:
selector:
matchLabels:
app.kubernetes.io/name: argocd-server-metrics
endpoints:
- port: metrics
interval: 30s
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: argocd-repo-server-metrics
namespace: argocd
labels:
release: prometheus
spec:
selector:
matchLabels:
app.kubernetes.io/name: argocd-repo-server
endpoints:
- port: metrics
interval: 30s
Dashboard Grafana
{
"dashboard": {
"title": "ArgoCD Overview",
"panels": [
{
"title": "Application Sync Status",
"targets": [
{
"expr": "sum(argocd_app_info{sync_status=\"Synced\"}) by (sync_status)",
"legendFormat": "Synced"
},
{
"expr": "sum(argocd_app_info{sync_status=\"OutOfSync\"}) by (sync_status)",
"legendFormat": "OutOfSync"
}
]
},
{
"title": "Application Health",
"targets": [
{
"expr": "sum(argocd_app_info{health_status=\"Healthy\"}) by (health_status)",
"legendFormat": "Healthy"
},
{
"expr": "sum(argocd_app_info{health_status=\"Degraded\"}) by (health_status)",
"legendFormat": "Degraded"
}
]
},
{
"title": "Sync Operations",
"targets": [
{
"expr": "sum(rate(argocd_app_sync_total[5m])) by (phase)",
"legendFormat": "{{phase}}"
}
]
}
]
}
}
Alertes Prometheus
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: argocd-alerts
namespace: argocd
spec:
groups:
- name: argocd
rules:
- alert: ArgoCDAppOutOfSync
expr: argocd_app_info{sync_status="OutOfSync"} == 1
for: 15m
labels:
severity: warning
annotations:
summary: "Application {{ $labels.name }} is out of sync"
description: "Application {{ $labels.name }} has been out of sync for more than 15 minutes"
- alert: ArgoCDAppUnhealthy
expr: argocd_app_info{health_status="Degraded"} == 1
for: 5m
labels:
severity: critical
annotations:
summary: "Application {{ $labels.name }} is unhealthy"
description: "Application {{ $labels.name }} health status is Degraded"
- alert: ArgoCDSyncFailed
expr: increase(argocd_app_sync_total{phase="Failed"}[10m]) > 0
labels:
severity: critical
annotations:
summary: "ArgoCD sync failed for {{ $labels.name }}"
description: "Sync operation failed for application {{ $labels.name }}"
Best Practices Production
1. Structure des Repositories
# Pattern recommandé : Config Repo séparé
app-source-repo/ # Code applicatif (CI)
├── src/
├── tests/
├── Dockerfile
└── .github/workflows/
└── ci.yaml # Build + Push image
app-config-repo/ # Manifests K8s (CD)
├── apps/
│ └── my-app/
│ ├── base/
│ │ ├── deployment.yaml
│ │ ├── service.yaml
│ │ └── kustomization.yaml
│ └── overlays/
│ ├── development/
│ │ ├── kustomization.yaml
│ │ └── patches/
│ ├── staging/
│ │ └── kustomization.yaml
│ └── production/
│ ├── kustomization.yaml
│ └── patches/
│ └── replicas.yaml
├── cluster-addons/
│ ├── cert-manager/
│ ├── ingress-nginx/
│ └── external-secrets/
└── argocd/
├── projects/
│ └── my-project.yaml
└── applications/
└── app-of-apps.yaml
2. App of Apps Pattern
# Root application qui déploie toutes les autres
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: root-app
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://github.com/my-org/config-repo
targetRevision: HEAD
path: argocd/applications
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: true
3. Checklist Production
## Pre-Production Checklist
### Infrastructure
- [ ] ArgoCD déployé en HA (2+ replicas server, 2+ repo-server)
- [ ] Redis HA configuré
- [ ] Ingress avec TLS configuré
- [ ] Backup des secrets ArgoCD
### Sécurité
- [ ] SSO/OIDC configuré
- [ ] RBAC configuré (pas de rôle admin par défaut)
- [ ] Network policies appliquées
- [ ] Secrets management (Sealed Secrets / External Secrets)
- [ ] Audit logs activés
### Monitoring
- [ ] Métriques Prometheus collectées
- [ ] Dashboard Grafana configuré
- [ ] Alertes configurées (OutOfSync, Unhealthy, SyncFailed)
- [ ] Notifications (Slack/Teams/Email)
### Applications
- [ ] Projets ArgoCD créés par équipe/namespace
- [ ] Sync policies définies
- [ ] Resource hooks configurés
- [ ] Health checks personnalisés si nécessaire
### Disaster Recovery
- [ ] Backup de argocd-cm et argocd-secret
- [ ] Procédure de restauration documentée
- [ ] Test de restauration effectué
Comparaison des Outils GitOps
| Outil | UI | Multi-Cluster | SSO | Helm Support | Kustomize | Complexité |
|---|---|---|---|---|---|---|
| ArgoCD | Excellente | Natif | OIDC/SAML/LDAP | Natif | Natif | Moyenne |
| Flux CD | Via Weave GitOps | Oui | Limité | Natif | Natif | Faible |
| Rancher Fleet | Rancher UI | Excellent | Via Rancher | Oui | Oui | Moyenne |
| Jenkins X | Jenkins UI | Limité | Via Jenkins | Oui | Limité | Élevée |
Conclusion
GitOps avec ArgoCD transforme la gestion des déploiements Kubernetes :
- Fiabilité : Source de vérité unique dans Git
- Traçabilité : Historique complet avec
git log - Sécurité : Review process via Pull Requests
- Scalabilité : Gestion multi-cluster native
- Récupération : Rollback en un
git revert
Points Clés
- Commencez simple : Une app, un cluster, puis scalez
- Séparez les repos : Code applicatif vs Configuration
- Automatisez les secrets : Sealed Secrets ou External Secrets
- Monitorez tout : Métriques + Alertes + Notifications
- Documentez : Procédures de rollback et DR
Ressources
Besoin d'aide pour implémenter GitOps dans votre organisation ? Découvrez mes formations et services de consulting.