Mise en place de Notifications de Pipeline avec LifecycleHook dans ArgoWorkflow

Introduction aux mécanismes de notification dans ArgoWorkflow

Cet article explore les fonctionnalités ExitHandler et LifecycleHook dans ArgoWorkflow, permettant d'exécuter des actions basées sur l'état du pipeline, typiquement pour envoyer des notifications. Par exemple, lors de l'échec d'une étape ou d'un workflow, une alerte par email peut être déclenchée.

Dans les différentes versions d'ArgoWorkflow, deux approches existent :

  • À partir de la version v2.7, la fonctionnalité exit handler permet de spécifier un template à exécuter après la fin du workflow. Ce template peut utiliser le champ when pour des conditions basées sur le résultat du workflow. Cette approche est obsolète et n'est plus recommandée après la version v3.3.
  • Depuis la version v3.3, LifecycleHook est introduit, offrant une granularité plus fine et davantage de fonctionnalités. ExitHandler peut être considéré comme un LifecycleHook simple.

ExitHandler : une approche historique

Bien que déconseillé, ExitHandler mérite une explication. Dans ArgoWorkflow, le champ spec.onExit désigne un template exécuté à la fin du worklfow, quelle que soit son issue. Ce template peut contenir des conditions via when pour dfifrencier les scénarios de succès et d'échec.

Voici un exemple modifié de workflow avec un ExitHandler :

apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: workflow-demo-exit-
spec:
  entrypoint: simulate-failure
  onExit: post-processing
  templates:
    - name: simulate-failure
      container:
        image: busybox:latest
        command: [sh, -c]
        args: ["echo Simulation d'une défaillance; exit 1"]
    - name: post-processing
      steps:
        - - name: envoyer-notification
            template: notification-email
          - name: celebrer-succes
            template: message-succes
            when: "{{workflow.status}} == Succeeded"
          - name: echouer-notification
            template: message-echec
            when: "{{workflow.status}} != Succeeded"
    - name: notification-email
      container:
        image: busybox:latest
        command: [sh, -c]
        args: ["echo Notification envoyée : {{workflow.name}} {{workflow.status}} {{workflow.duration}}. Échecs : {{workflow.failures}}"]
    - name: message-succes
      container:
        image: busybox:latest
        command: [sh, -c]
        args: ["echo Réussite confirmée"]
    - name: message-echec
      container:
        image: busybox:latest
        command: [sh, -c]
        args: ["echo Échec détecté"]

Dans cet exemple, le template post-processing est défini via onExit. Il envoie toujours une notification, puis déclenche une action conditionnelle. Comme le workflow principal échoue (exit code 1), le message d'échec est affiché. Les pods résultants incluent ceux pour la notification et le message d'échec, avec des journaux appropriés.

Cette méthode satisfait les besoins de base pour les notifications, comme l'envoi d'emails ou l'intégration via webhooks. Cependant, ExitHandler est limité au niveau workflow, exécuté uniquement à la fin globale. LifecycleHook dans la version v3.3 répond à ce manque de granularité.

LifecycleHook : une solution flexible

LifecycleHook est une évolution d'ExitHandler, offrant plus de flexibilité. Il peut être configuré à deux niveaux : workflow et template.

Niveau workflow

Similaire à ExitHandler, mais avec plus d'options. Voici un exemple modifié :

apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: workflow-lifecycle-demo-
spec:
  entrypoint: processus-principal
  hooks:
    sortie: # Gestionnaire de sortie
      template: requete-http
    en-cours:
      expression: workflow.status == "Running"
      template: requete-http
  templates:
    - name: processus-principal
      steps:
      - - name: etape-initiale
          template: afficher-message
    - name: afficher-message
      container:
        image: alpine:3.6
        command: [sh, -c]
        args: ["echo Message de test"]
    - name: requete-http
      http:
        url: "https://example.com/api/status"

Ici, le champ hooks remplace onExit, permettant plusieurs hooks avec des expressions conditionnelles. Le template requete-http est de type HTTP, simulant une notification webhook. Comme le workflow réussit, les deux hooks s'exécutent.

Niveau template

Cette granularité permet de surveiller des étapes spécifiques. Exemple modifié :

apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: workflow-hooks-template-
spec:
  entrypoint: sequence-etapes
  templates:
    - name: sequence-etapes
      steps:
        - - name: phase-preparation
            hooks:
              en-cours:
                expression: steps["phase-preparation"].status == "Running"
                template: alerte-status
              succes:
                expression: steps["phase-preparation"].status == "Succeeded"
                template: alerte-status
            template: executer-commande
        - - name: phase-execution
            hooks:
              en-cours:
                expression: steps.phase-execution.status == "Running"
                template: alerte-status
              succes:
                expression: steps.phase-execution.status == "Succeeded"
                template: alerte-status
            template: executer-commande
    - name: executer-commande
      container:
        image: alpine:3.6
        command: [sh, -c]
        args: ["echo Exécution en cours"]
    - name: alerte-status
      http:
        url: "https://example.com/api/alerte"

Les hooks sont placés au niveau des étapes, permettant des notifications ciblées. Pour remplacer ExitHandler avec LifecycleHook, nommez simplement le hook sortie (ou exit en code), comme indiqué dans la documentation d'ArgoWorkflow.

Modèles de notification courants

Les notifications peuvent prendre diverses formes, comme les webhooks ou les emails. Voici des exemples adaptés.

Webhook

Une méthode universelle utilisant une requête HTTP. Exemple de template modifié :

apiVersion: argoproj.io/v1alpha1
kind: ClusterWorkflowTemplate
metadata:
  name: template-notif-webhook
spec:
  templates:
    - name: webhook-notification
      inputs:
        parameters:
          - name: CONDITIONS_EXECUTION # Exemples : Pending,Running,Succeeded,Failed,Error
            value: "Succeeded,Failed,Error"
          - name: POINT_FINAL_WEBHOOK
          - name: VERSION_CURL
            default: "8.4.0"
      container:
        image: curlimages/curl:{{inputs.parameters.VERSION_CURL}}
        command: [sh, -cx]
        args: [
          "curl -X POST -H \"Content-Type: application/json\" -d '{
            \"message\": \"{{workflow.name}} {{workflow.status}}\",
            \"workflow_details\": {
              \"nom\": \"{{workflow.name}}\",
              \"espace\": \"{{workflow.namespace}}\",
              \"identifiant\": \"{{workflow.uid}}\",
              \"horodatage\": \"{{workflow.creationTimestamp}}\",
              \"statut\": \"{{workflow.status}}\"
            }
          }' {{inputs.parameters.POINT_FINAL_WEBHOOK}}"
        ]

Email

Pour les notifications par email, utilisez un script Python modifié :

apiVersion: argoproj.io/v1alpha1
kind: ClusterWorkflowTemplate
metadata:
  name: template-notif-email
spec:
  templates:
    - name: envoyer-email
      inputs:
        parameters:
          - name: CONDITIONS_EXECUTION
            value: "Succeeded,Failed,Error"
          - name: SECRET_CREDENTIALS
          - name: DESTINATAIRE # Adresse email
          - name: VERSION_PYTHON
            default: "3.8-alpine"
      script:
        image: python:{{inputs.parameters.VERSION_PYTHON}}
        command: [python]
        env:
          - name: DESTINATAIRE
            value: '{{inputs.parameters.DESTINATAIRE}}'
          - name: SERVEUR_SMTP
            valueFrom:
              secretKeyRef:
                name: '{{inputs.parameters.SECRET_CREDENTIALS}}'
                key: serveur
          - name: PORT_SMTP
            valueFrom:
              secretKeyRef:
                name: '{{inputs.parameters.SECRET_CREDENTIALS}}'
                key: port
          - name: EXPEDITEUR
            valueFrom:
              secretKeyRef:
                name: '{{inputs.parameters.SECRET_CREDENTIALS}}'
                key: expediteur
          - name: UTILISATEUR
            valueFrom:
              secretKeyRef:
                name: '{{inputs.parameters.SECRET_CREDENTIALS}}'
                key: utilisateur
          - name: MOT_DE_PASSE
            valueFrom:
              secretKeyRef:
                name: '{{inputs.parameters.SECRET_CREDENTIALS}}'
                key: motdepasse
          - name: TLS_ACTIF
            valueFrom:
              secretKeyRef:
                name: '{{inputs.parameters.SECRET_CREDENTIALS}}'
                key: tls
        source: |
          import smtplib
          import ssl
          import os
          from email.mime.text import MIMEText

          serveur_smtp = os.getenv('SERVEUR_SMTP')
          port_smtp = os.getenv('PORT_SMTP')
          email_expediteur = os.getenv('EXPEDITEUR')
          emails_destinataires = os.getenv('DESTINATAIRE')
          utilisateur = os.getenv('UTILISATEUR')
          mot_de_passe = os.getenv('MOT_DE_PASSE')
          tls_actif = os.getenv('TLS_ACTIF')

          corps_message = f"Informations workflow :\nNom: {{workflow.name}}\nStatut: {{workflow.status}}"
          message = MIMEText(corps_message, 'plain', 'utf-8')
          message['From'] = email_expediteur
          message['To'] = emails_destinataires
          message['Subject'] = f"Notification : {{workflow.name}} {{workflow.status}}"

          if tls_actif == 'True':
            contexte = ssl.create_default_context()
            serveur = smtplib.SMTP_SSL(serveur_smtp, port_smtp, context=contexte)
          else:
            serveur = smtplib.SMTP(serveur_smtp, port_smtp)

          if mot_de_passe != '':
            serveur.login(utilisateur, mot_de_passe)

          for destinataire in emails_destinataires.split(';'):
            if destinataire.strip():
              serveur.sendmail(email_expediteur, destinataire.strip(), message.as_string())

          serveur.quit()

Étiquettes: ArgoWorkflow LifecycleHook ExitHandler kubernetes pipeline

Publié le 17 juillet à 08h53