DEVOPS
Avancé

GitOps avec ArgoCD : Setup et Best Practices

GitOps avec ArgoCD : Setup et Best Practices

Guide complet pour mettre en place GitOps avec ArgoCD : installation, configuration avancée, multi-cluster, troubleshooting et best practices production.

Florian Courouge
25 min de lecture
5,707 mots
1.5k vues
GitOps
ArgoCD
Kubernetes
CD
Automation
Multi-Cluster

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

  1. Commencez simple : Une app, un cluster, puis scalez
  2. Séparez les repos : Code applicatif vs Configuration
  3. Automatisez les secrets : Sealed Secrets ou External Secrets
  4. Monitorez tout : Métriques + Alertes + Notifications
  5. 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.

F

Florian Courouge

Expert DevOps & Kafka | Consultant freelance specialise dans les architectures distribuees et le streaming de donnees.

Articles similaires