<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title><![CDATA[Expérience développeur - Elasticsearch Labs]]></title>
    <description><![CDATA[Articles and tutorials from the Search team at Elastic]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[Expérience développeur - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/fr/search-labs/blog/category/developer-experience</link>
    </image>
    <link>https://www.elastic.co/fr/search-labs/blog/category/developer-experience</link>
    <atom:link href="https://www.elastic.co/fr/search-labs/rss/category/developer-experience.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[fr]]></language>
    <lastBuildDate>Mon, 28 Sep 2026 09:42:05 GMT</lastBuildDate>
  <item>
    <title><![CDATA[API Kibana Dashboards : un contrat stable pour chaque type de panneau, testé par plus de 50 équipes avant DG]]></title>
    <description><![CDATA[Gérez les tableaux de bord Kibana sous forme de code : enregistrez-les dans Git, promouvez-les dans différents environnements et automatisez les déploiements avec l'API Kibana et Terraform.]]></description>
    <content:encoded><![CDATA[<p>Les<a href="https://dashboardsapispec.kibana.dev/dashboards#tag/Dashboards"> API Kibana Dashboards et de visualisation</a> sont prêtes pour la production dans Elastic 9.5 ; elles sont disponibles pour tous les niveaux d'abonnement et offrent une rétrocompatibilité totale. Définissez vos tableaux de bord au format JSON, validez-les dans Git, puis déployez-les dans différents environnements à l'aide de pipelines d'intégration et de déploiement continus (CI/CD), de <a href="https://registry.terraform.io/providers/elastic/elasticstack/latest/docs/resources/kibana_dashboard">Terraform</a> ou de tout autre outil que vous utilisez déjà. Plus de 50 équipes ont testé l'API lors de la <a href="https://www.elastic.co/search-labs/blog/kibana-dashboards-as-code-terraform-api">préversion technique 9.4</a>, certaines l'exploitant même déjà en production. La version 9.5 ajoute également de nouveaux points de terminaison (en préversion technique) pour le panneau <a href="https://dashboardsapispec.kibana.dev/tags.html">Tags</a>, avec des points de terminaison pour les panneaux <a href="https://dashboardsapispec.kibana.dev/markdowns.html"> Markdown</a> et<a href="https://dashboardsapispec.kibana.dev/links.html#tag/Links"> Links</a> disponibles dès maintenant dans Elastic Cloud Serverless et prochainement dans la version 9.6.</p><h2>Ce que la rétrocompatibilité implique pour l'API Kibana Dashboards</h2><p>Pendant la préversion technique, la forme de l'API pouvait changer d'une version à l'autre.[1] Ce n'est plus le cas. La disponibilité générale (DG) signifie :</p><ul><li><p><strong>Rétrocompatibilité totale.</strong> De nouveaux champs et types de panneaux seront ajoutés au fil du temps, mais les champs et comportements existants demeureront inchangés. Toute modification majeure susceptible de rompre la compatibilité sera examinée avec la plus grande attention et ne sera introduite que dans une nouvelle version majeure de la pile technologique.</p></li><li><p><strong>Prête pour la production avec compatibilité totale.</strong> L'API bénéficie des garanties de compatibilité complète d'Elastic. Vous pouvez l'utiliser en toute sécurité dans les environnements de production pour les déploiements automatisés, la promotion de l'environnement et la gestion programmatique des tableaux de bord.</p></li></ul><h2>Nouveaux points de terminaison de l'API Kibana pour les panneaux Tags, Markdown et Links</h2><p>Elastic 9.5 introduit également un nouveau point de terminaison autonome pour le panneau <a href="https://dashboardsapispec.kibana.dev/tags.html"><strong>Tags</strong></a>, qui vous permet de catégoriser et filtrer les tableaux de bord. Vous pouvez désormais les gérer par programmation via des points de terminaison CRUD dédiés, ce qui facilite leur organisation à grande échelle dans différents environnements.	</p><p>De nouveaux points de terminaison pour les panneaux <a href="https://dashboardsapispec.kibana.dev/markdowns.html"><strong>Markdown</strong></a> et <a href="https://dashboardsapispec.kibana.dev/links.html#tag/Links"><strong>Liens</strong></a> sont désormais disponibles dans Serverless et seront intégrés dans la prochaine version de la pile (9.6).</p><h2>Quels types de panneaux l'API Kibana Dashboards prend-elle en charge ?</h2><p>L'API Dashboards prend en charge tous les panneaux <em>par valeur</em> de la version 9.5 (ceux définis directement dans un tableau de bord, par opposition aux panneaux de bibliothèque enregistrés pour être réutilisés). Chaque type de panneau pris en charge dispose d'un schéma typé et validé.</p><p><strong>Type de panneau</strong></p><p><strong>État</strong></p><p>Graphiques XY</p><p>Compatibles</p><p>Métriques</p><p>Compatibles</p><p>Circulaires</p><p>Compatibles</p><p>Jauge</p><p>Compatibles</p><p>Carte thermique</p><p>Compatibles</p><p>Tables de données</p><p>Compatibles</p><p>Arborescence</p><p>Compatibles</p><p>Sessions Discover</p><p>Compatibles</p><p>Contrôles</p><p>Compatibles</p><p>Markdown</p><p>Compatibles</p><p>Liens</p><p>Compatibles</p><p>Panneaux ML</p><p>Compatibles</p><p>Panneaux Observability</p><p>Compatibles</p><p>Maps</p><p>Bientôt disponible</p><p>Vega</p><p>Bientôt disponible</p><h2>Comment gérer les tableaux de bord Kibana sous forme de code</h2><p>L'API Dashboards permet un workflow complet de tableaux de bord sous forme de code : exportez un tableau de bord au format JSON propre et facilement comparable, validez-le dans Git comme source de référence, examinez les modifications via des requêtes pull et déployez la même définition dans les environnements de développement, de préproduction et de production. Une fois qu'un tableau de bord est géré sous forme de code, considérez Git comme source de référence unique : les modifications effectuées directement dans l'interface utilisateur seront écrasées lors du prochain déploiement.</p><p>La principale difficulté lors du transfert d'un tableau de bord entre des espaces, des clusters ou des environnements est que les tableaux de bord font référence à des objets (data views ou visualisations de bibliothèques, par exemple) au moyen d'identifiants. Comme ces identifiants sont générés automatiquement et varient d'un environnement à l'autre, un tableau de bord exporté depuis un environnement peut pointer vers des objets inexistants dans un autre. Il existe trois méthodes pour gérer cette situation, présentées ici de la plus automatisée à la moins automatisée :</p><ul><li><p><strong>Utilisez Terraform.</strong> Le <a href="https://registry.terraform.io/providers/elastic/elasticstack/latest/docs/resources/kibana_dashboard">fournisseur Elastic Stack Terraform</a> suit chaque ressource et associe automatiquement les identifiants par environnement, afin que les références restent cohérentes lorsque vous faites passer un tableau de bord de l'environnement de développement à l'environnement de production.</p></li><li><p><strong>Définissez par valeur les </strong><a href="https://www.elastic.co/docs/explore-analyze/visualize/esorql"><strong>panneaux Elasticsearch Query Language (ES|QL)</strong></a><strong>.</strong> La méthode la plus portable pour créer un panneau consiste à définir sa visualisation à l'aide d'ES|QL directement dans le tableau de bord. Une requête <a href="https://www.elastic.co/docs/explore-analyze/query-filter/languages/esql-kibana">ES|QL</a> lit les données à partir des index qui y sont spécifiés ; le panneau ne contient donc aucune référence externe à des data views ou à des objets de bibliothèque. On obtient ainsi un tableau de bord entièrement autonome et portable.</p></li><li><p><strong>Attribuer les identifiants correspondants.</strong> Si vous référencez des objets enregistrés, tels que des data views ou des visualisations de bibliothèque, créez-les avec un identifiant spécifique en utilisant la méthode PUT (upsert) plutôt que POST (qui génère automatiquement un identifiant). Utilisez des identifiants explicites et lisibles, comme logs-prod, afin qu'ils soient faciles à reconnaître et à réutiliser d'un environnement à l'autre.</p></li></ul><p>Pour une présentation détaillée de ces modèles de portabilité et du workflow complet des tableaux de bord sous forme de code, consultez la documentation <a href="https://www.elastic.co/docs/explore-analyze/dashboards/manage-dashboards-as-code#dashboards-as-code-portability">Gérer les tableaux de bord sous forme de code</a>.</p><h3>Création d'un tableau de bord Kibana avec l'API Dashboards en utilisant PUT</h3><p>Voici un exemple rapide de création d'un tableau de bord avec un panneau de métriques, utilisant la méthode PUT au lieu de POST pour attribuer un identifiant personnalisé basé sur le nom du tableau de bord (service-health-overview). La même logique s'applique à la création de visualisations autonomes enregistrées dans la bibliothèque.</p>PUT kbn:/api/dashboards/service-health-overview
{
  "title": "Service health overview",
  "description": "Key service metrics — managed via API",
  "tags": [
    "production",
    "sre-team"
  ],
  "panels": [
    {
      "type": "vis",
      "grid": {
        "x": 0,
        "y": 0,
        "w": 12,
        "h": 8
      },
      "config": {
        "title": "Error rate (5xx)",
        "type": "metric",
        "data_source": {
          "type": "esql",
          "query": "FROM logs-* | WHERE http.response.status_code &gt;= 500 | STATS error_rate=count(*) BY host.name"
        },
        "metrics": [
          {
            "type": "primary",
            "column": "count"
          }
        ]
      }
    }
  ]
}<h2>Roadmap de l'API Kibana Dashboards : Maps, Vega et points de terminaison autonomes</h2><p>Nous travaillons activement à étendre la surface de l'API. La prise en charge des panneaux Maps et Vega est la prochaine étape, avec ajout de schémas typés. Nous développons également des points de terminaison CRUD autonomes pour les sessions Discover (au-delà de leur prise en charge actuelle en tant que panneaux de tableau de bord), les panneaux Vega, Maps et les Annotations, le tout dissocié du cycle de vie des tableaux de bord.</p><p>Pour les définitions complètes des schémas, consultez la <a href="https://dashboardsapispec.kibana.dev/dashboards#tag/Dashboards">documentation de l'API Dashboards</a>. Pour les utilisateurs de Terraform, le <a href="https://registry.terraform.io/providers/elastic/elasticstack/latest/docs/resources/kibana_dashboard">fournisseur Elastic Stack Terraform</a> prend en charge l'API Dashboards en DG.</p><h2>Remarque</h2><ol><li><p>Les points de terminaison principaux restent inchangés par rapport à la préversion technique. Si vous avez développé des intégrations pour la version 9.4, elles fonctionneront avec la version 9.5. Les seuls changements entraînant une rupture de compatibilité sont deux modifications mineures concernant la liste des tableaux de bord et les formats d'unité de durée ; elles sont documentées <a href="https://www.elastic.co/docs/release-notes/kibana/breaking-changes">ici</a>.</p></li></ol>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/dashboards-as-code-kibana-api</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/dashboards-as-code-kibana-api</guid>
    <category><![CDATA[Kibana]]></category>
    <category><![CDATA[Expérience développeur]]></category>
    <category><![CDATA[Intégrations]]></category>
    <dc:creator><![CDATA[Teresa Alvarez Soler]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8ed7e33de291f255/6a730619c8b7ac02b251f9d3/image1.png" length="0" type="image/png"/>
    <pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Présentation des clés API unifiées pour Elastic Cloud Serverless et Elasticsearch]]></title>
    <description><![CDATA[Découvrez comment Elastic unifie l’authentification des plans de contrôle et des plans de données dans Serverless grâce à une architecture IAM distribuée à l’échelle mondiale. Utilisez une seule clé API pour les API Cloud et Elasticsearch.]]></description>
    <content:encoded><![CDATA[<p>Imaginez que vous êtes ingénieur en fiabilité des sites (SRE) responsable d’un parc croissant de projets Elastic Cloud Serverless : Elastic Observability pour votre infrastructure de production, Elastic Security pour votre centre des opérations de sécurité (SOC) et Elasticsearch pour votre application orientée client. Chaque projet dispose de sa propre clé API Elasticsearch. Votre pipeline d’intégration continue et de déploiement continu (CI/CD) nécessite une clé API Cloud distincte pour provisionner et gérer ces projets. Chaque trimestre arrive le jour de rotation : vous parcourez chaque projet, générez de nouvelles clés, mettez à jour l’état Terraform, redéployez vos pipelines et espérez que rien ne passe entre les mailles du filet. Lorsqu’un incident survient à 2 h du matin et que vous devez révoquer rapidement des accès, vous consultez une feuille de calcul de secrets pour déterminer quelle clé correspond à quel projet et à quel service.</p><p>Aujourd’hui, tout devient beaucoup plus simple. Les <strong>clés API Elastic Cloud</strong> peuvent désormais être utilisées pour s’authentifier directement auprès des API <strong>Elasticsearch</strong> et <strong>Kibana</strong> dans <strong>Elastic Cloud Serverless</strong>. Vous pouvez désormais utiliser un seul identifiant pour gérer les ressources de votre organisation <em>et</em> exécuter des opérations sur les données, comme des requêtes Elasticsearch Query Language (ES|QL), l’ingestion de données et l’alerting.</p><p>Voyons pourquoi nous avons conçu cette solution, comment nous avons mis en place une couche d’identité distribuée à l’échelle mondiale pour la rendre possible, et comment elle pose les bases d’une recherche interprojets.</p><h2>Le fardeau de la gestion des secrets</h2><p>Mettre en place des pipelines CI/CD fiables, des workflows GitOps ou une automatisation Terraform autour des plateformes de données a un coût caché : la prolifération des secrets.</p><p>Dans le modèle précédent, les développeurs étaient confrontés à une gestion de l’authentification fragmentée :</p><ul><li><p><strong>Plan de contrôle (clés API Elastic Cloud) :</strong> clés au périmètre de l’organisation utilisées pour créer des projets, inviter des utilisateurs et gérer la facturation via l’<a href="https://www.elastic.co/docs/api/doc/cloud/">API Elastic Cloud</a>.</p></li><li><p><strong>Plan de données (clés API Elasticsearch) :</strong> Clés de portée projet créées <em>à l'intérieur</em> d'un projet Serverless spécifique pour interagir avec <a href="https://www.elastic.co/docs/api/doc/elasticsearch-serverless/">Elasticsearch</a> et <a href="https://www.elastic.co/docs/api/doc/serverless">Kibana</a> API.</p></li></ul><p>Cela signifiait que votre script de déploiement devait s’authentifier auprès d’Elastic Cloud, provisionner un projet Serverless, extraire une clé API Elasticsearch nouvellement générée depuis ce projet, puis injecter <em>cette</em> seconde clé dans l’application en aval ou l’outil d’automatisation, ce qui entraînait des pipelines complexes, des journaux d’audit fragmentés et un risque accru de fuite d’identifiants.</p><h2>Authentification unifiée dans Elastic Cloud Serverless</h2><p>Avec cette version, cette séparation disparaît pour les projets Serverless. Vous pouvez désormais créer une clé API Elastic Cloud explicitement autorisée pour les <strong>API Cloud, Elasticsearch et Kibana</strong>.</p><ul><li><p><strong>Avant :</strong> une clé API Elastic Cloud était strictement un jeton du plan de contrôle. Elle permettait de créer des projets, de gérer la facturation et d’inviter des utilisateurs, mais présentait une limite stricte : elle ne pouvait pas être utilisée pour appeler les API Elasticsearch ou Kibana au sein de ces projets. Vous aviez toujours besoin d’une seconde clé, spécifique au projet, pour les opérations sur les données.</p></li><li><p><strong>Maintenant :</strong> en optant pour l'accès aux <strong>API Cloud, Elasticsearch et Kibana</strong> lors de la création d'une clé API Elastic Cloud, la limite stricte est supprimée pour Serverless. Cette clé API devient un véritable identifiant unifié. Il conserve sa capacité à gérer l'infrastructure de votre organisation, tout en bénéficiant d'un accès natif pour interroger, ingérer et analyser les données de tout projet Serverless autorisé.</p></li></ul><p>En unifiant cela avec une seule clé API Elastic Cloud, vous disposez d’une identité unique pouvant être définie par périmètre, auditée, renouvelée et révoquée comme une seule entité. Chaque appel API, qu’il serve à provisionner un nouveau projet ou à exécuter une requête ES|QL, apparaît sous le même identifiant dans vos journaux d’audit, vous offrant une trace unique à suivre lors des enquêtes d’incident ou des audits de conformité. La rotation des identifiants devient une opération en une seule étape, au lieu d’une mise à jour coordonnée entre les secrets du plan de contrôle et du plan de données. Et comme les rôles sont attribués par projet, une seule clé peut couvrir plusieurs projets : gérer l’ingestion dans votre projet d’observabilité et exécuter des requêtes dans votre projet de sécurité, sans avoir à manipuler des identifiants distincts pour chacun.</p><p>Il est important de noter qu' <em>unifié</em> ne signifie pas <em>tout-puissant</em>. En utilisant le payload <code>role_assignments</code>, vous pouvez restreindre la portée d'une clé unifiée à un seul projet et à un rôle spécifique (lecture seule, par exemple), ce qui garantit que le rayon d'impact reste totalement contenu si un identifiant est exposé. En cas de départ d'un développeur ou de mise hors service d'une application, vous pouvez révoquer une seule clé depuis la console Elastic Cloud, ce qui met immédiatement fin à l'accès à la fois au plan de contrôle et à tous les projets Elasticsearch associés.</p><p><em>(Remarque : pour les déploiements Elastic Cloud Hosted/gérés, les clés API Cloud ne gèrent toujours que le plan de contrôle.) La prise en charge de l’extension aux API de la stack hébergée est prévue dans une prochaine version.)</em></p><h2>Automatiser vos workflows</h2><p>La mise en route est simple. Vous pouvez configurer cela entièrement via la console Elastic Cloud ou l'automatiser en utilisant l'<a href="https://www.elastic.co/docs/deploy-manage/api-keys/elastic-cloud-api-keys">API Elastic Cloud</a>.</p><p>Le processus de l'interface utilisateur reste le même, mais vous pouvez désormais sélectionner l'accès aux <strong>API Cloud, Elasticsearch et Kibana</strong> dans l'affectation des rôles du projet.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltda0a18945295aa84/6a1707bd509168fab4e1ba19/c4f802f130655290cd474b283001a954d14c3088-2801x1681.png" alt="Écran Elastic Cloud affichant la page des clés API avec une modale Créer une clé API ouverte, incluant des champs pour le nom, l'expiration et les attributions de rôles." /><p>Voici comment créer une clé unifiée de façon programmatique en utilisant l’API Elastic Cloud. Notez le tableau <code>application_roles</code>, car c’est ce qui donne à la clé un accès natif au plan de données Elasticsearch :</p>curl -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: ApiKey $EC_API_KEY" \
  "https://api.elastic-cloud.com/api/v1/users/auth/keys" \
  -d '{
    "description": "unified-automation-key",
    "expiration": "90d",
    "role_assignments": {
      "project": {
        "elasticsearch": [
          {
            "role_id": "elasticsearch-admin",
            "organization_id": "YOUR_ORG_ID",
            "all": false,
            "project_ids": ["YOUR_PROJECT_ID"],
            "application_roles": ["admin"]
          }
        ]
      }
    }
  }'<p>Une fois créée, il vous suffit de transmettre exactement la même clé dans l'en-tête <code>Authorization: ApiKey</code> à <code>api.elastic-cloud.com</code> et à vos points de terminaison Elasticsearch Serverless spécifiques.</p><h2>En coulisses : conception d’une couche d’identité distribuée</h2><p>Faire fonctionner une clé API Cloud à la fois sur le plan de contrôle et sur le plan de données ne se résume pas à transmettre un jeton. Cela nécessite de résoudre un défi fondamental des systèmes distribués.</p><p>Historiquement, les clés API Cloud étaient stockées dans un cluster de sécurité global centralisé. Cela fonctionne bien pour les opérations du plan de contrôle, où une latence plus élevée est acceptable. Cependant, les requêtes de données Elasticsearch nécessitent une latence extrêmement faible. Nous ne pouvons pas nous permettre un aller-retour à travers le globe vers un plan de contrôle central pour valider chaque requête de recherche ou d’ingestion.</p><p>Pour résoudre ce problème, nous avons introduit une nouvelle architecture d’authentification reposant sur un datastore distribué à l’échelle mondiale. Le diagramme de séquence suivant montre un client envoyant une requête Elasticsearch à l’aide d’une clé API Elastic Cloud, illustrant comment l’authentification s’effectue entièrement dans la région locale, sans aller-retour vers le plan de contrôle global. Elasticsearch délègue l’authentification au service IAM régional, qui valide la clé et résout ses attributions de rôles à partir d’une réplique locale de la base de données distribuée à l’échelle mondiale. Une fois autorisé, Elasticsearch exécute la requête et renvoie les résultats au client.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf4fa84c3f33f88f7/6a1707baacf088989abe9a8e/3e38d7a862b9981523c5393c441b92eae13aeb90-2401x1351.webp" alt="Diagramme de séquence illustrant une requête client avec une clé API Cloud transitant par Elasticsearch Serverless, un service IAM régional et une réplique de base de données distribuée, avant le retour des résultats." /><h3>Persistance distribuée à l’échelle mondiale</h3><p>Au lieu de s’appuyer uniquement sur un cluster de sécurité centralisé, les clés API Elastic Cloud et leurs définitions de rôles associées sont désormais stockées dans une base de données distribuée à l’échelle mondiale et hautement disponible. Cette base de données synchronise les données de gestion des identités et des accès (IAM) entre le plan de contrôle global et les plans de données régionaux où vos projets Serverless s’exécutent réellement.</p><h3>Validation locale avec IAM régional</h3><p>Lorsque votre client envoie une requête à Elasticsearch à l’aide d’une clé API Elastic Cloud, la requête ne revient pas vers le plan de contrôle global. Elle est plutôt redirigée vers le nouveau service IAM régional. Celui-ci valide la clé à partir de la réplique locale de la base de données, garantissant une authentification avec une latence quasi nulle et totalement isolée des pannes du plan de contrôle global.</p><h3>Mappage dynamique des rôles</h3><p>L'authentification n'est que la moitié de la bataille ; le système doit également autoriser la demande. Le service IAM régional traduit instantanément vos attributions de rôles au niveau du cloud (par exemple, <code>application_roles</code>) en privilèges Elasticsearch natifs. Elasticsearch peut alors autoriser et exécuter la requête localement, sans jamais avoir besoin d’un index <code>.security</code> local.</p><h2>La base de la recherche interprojets</h2><p>Cette architecture d'identité distribuée est un élément fondamental pour l'avenir de la plateforme Elastic.</p><p>L'identité et l'accès étant désormais unifiés et synchronisés à l'échelle mondiale, nous disposons du framework nécessaire pour transmettre votre identité en toute sécurité entre différents projets. Cela permet d'activer les capacités de <strong>recherche inter-projets (CPS)</strong> à venir pour Serverless.</p><p>Avec CPS, vous pourrez interroger des données couvrant plusieurs projets Serverless distants, par exemple en combinant des charges de travail de sécurité et d’observabilité, aussi simplement que s’il s’agissait d’un seul jeu de données. En s’appuyant sur des clés API unifiées, le système peut automatiquement évaluer vos autorisations sur l’ensemble des projets simultanément, sans vous obliger à configurer des relations de confiance complexes, des certificats ou des identifiants dupliqués pour chaque projet cible.</p><h2>En savoir plus</h2><p>Êtes-vous prêt à simplifier votre stack ?</p><ul><li><p>Lisez la <a href="https://www.elastic.co/docs/deploy-manage/api-keys/elastic-cloud-api-keys">documentation sur les clés API d'Elastic Cloud</a> pour savoir comment attribuer l'accès à la pile.</p></li><li><p>Consultez la <a href="https://www.elastic.co/docs/api/doc/cloud/operation/operation-create-api-key">référence Create API key (Elastic Cloud API)</a> pour automatiser la génération de clés.</p></li><li><p>Consultez <a href="https://www.elastic.co/docs/deploy-manage/api-keys">les clés API Elastic</a> pour une comparaison complète des types de clés sur la plateforme Elastic.</p></li></ul><p>Commencez ou continuez à construire dans <a href="https://cloud.elastic.co/registration">Elastic Cloud</a> dès aujourd’hui.</p><h2>Avis de non-responsabilité</h2><p>La publication et la date de publication de toute fonctionnalité ou fonction décrite dans le présent article restent à la seule discrétion d'Elastic. Toute fonctionnalité ou fonction qui n'est actuellement pas disponible peut ne pas être livrée à temps ou ne pas être livrée du tout.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elastic-cloud-api-keys-unified-serverless</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elastic-cloud-api-keys-unified-serverless</guid>
    <category><![CDATA[Elastic Cloud Serverless]]></category>
    <category><![CDATA[Expérience développeur]]></category>
    <dc:creator><![CDATA[ Alex Chalkias]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt16ca1a6af7e5bab8/6a1707b7a6c2b900abe7965b/864e229f00eb2018084f13dd7f0e390e18383ed4-1980x1188.png" length="0" type="image/png"/>
    <pubDate>Mon, 20 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Monitorer des vues des tableaux de bord Kibana avec Elastic Workflows]]></title>
    <description><![CDATA[Découvrez comment utiliser Elastic Workflows pour collecter les indicateurs des vues du tableau de bord Kibana toutes les 30 minutes et les indexer dans Elasticsearch, afin de pouvoir créer des analyses et des visualisations personnalisées à partir de vos propres données.]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.elastic.co/kibana">Kibana</a> enregistre le nombre de consultations de chaque tableau de bord, mais ces données ne sont pas accessibles nativement dans les tableaux de bord intégrés. Dans cet article, nous utiliserons <strong>Elastic Workflows</strong> pour collecter automatiquement ces données toutes les 30 minutes et les indexer dans Elasticsearch, afin de pouvoir créer nos propres analyses.</p><p><a href="https://www.elastic.co/docs/explore-analyze/workflows">Elastic Workflows</a> est un moteur d'automatisation intégré à Kibana qui vous permet de définir des processus à plusieurs étapes à l'aide d'une configuration YAML simple. Chaque workflow peut être déclenché selon une planification ou un événement, ou en tant qu'outil dans <a href="https://www.elastic.co/docs/explore-analyze/ai-features/elastic-agent-builder">Elastic Agent Builder</a>, et chaque étape peut appeler les API de Kibana, interroger Elasticsearch ou transformer des données.</p><p>Nous utiliserons les compteurs de vues du tableau de bord comme exemple concret, mais le même modèle s'applique à n'importe quel indicateur exposé via l'API des objets enregistrés de Kibana.</p><h2>Produits requis</h2><ul><li><p><a href="https://www.elastic.co/cloud">Elastic Cloud</a> ou cluster <a href="https://www.elastic.co/docs/deploy-manage/deploy/self-managed">autogéré </a>exécutant la version 9.3</p></li><li><p><a href="https://www.elastic.co/docs/explore-analyze/workflows/get-started#workflows-prerequisites">Workflows activés</a> (paramètres avancés)</p></li></ul><h2>Étape 1 : Explorer les données brutes dans les <a href="https://www.elastic.co/docs/explore-analyze/query-filter/tools/console">Outils de développement</a></h2><p>Avant toute chose, examinons les données disponibles. Kibana stocke la majeure partie de sa configuration et de ses métadonnées sous forme d'<a href="https://www.elastic.co/docs/api/doc/kibana/group/endpoint-saved-objects">objets enregistrés</a> dans un index interne dédié. Parmi les éléments suivis par Kibana figurent les consultations des tableaux de bord, grâce à un type d'objet enregistré spécifique appelé "compteurs d'utilisation". Vous pouvez les interroger directement depuis les outils de développement :</p>GET kbn:/api/saved_objects/_find?type=usage-counter&amp;filter=usage-counter.attributes.domainId:"dashboard"%20and%20usage-counter.attributes.counterType:"viewed"&amp;per_page=10000<p>La réponse se présente comme suit :</p>{
  "page": 1,
  "per_page": 10000,
  "total": 1,
  "saved_objects": [
    {
      "type": "usage-counter",
      "id": "dashboard:346f3c64-ebca-484d-9d57-ec600067d596:viewed:server:20260310",
      "attributes": {
        "domainId": "dashboard",
        "counterName": "346f3c64-ebca-484d-9d57-ec600067d596",
        "counterType": "viewed",
        "source": "server",
        "count": 1
      },
      ...
    }
  ]<p>Le champ <code>counterName</code> est l'identifiant du tableau de bord, et <code>count</code> est le nombre cumulé de vues pour ce tableau de bord ce jour-là. Kibana crée un objet compteur par tableau de bord par jour ; vous pouvez voir le suffixe de date dans l'ID de l'objet (...viewed:server:20260310). Le nombre augmente tout au long de la journée lorsque les utilisateurs ouvrent le tableau de bord.</p><p>Plutôt que de reproduire ce modèle de document quotidien dans notre index, nous créerons un document par exécution de workflow. Chaque document enregistre le nombre de vues que ce tableau de bord avait accumulées pour la journée au moment de la capture.</p><h2>Étape 2 : Créer l'index de destination</h2><p>Nous avons besoin d'un index pour stocker les snapshots de nos vues de tableau de bord. La commande suivante le crée avec des mappings explicites afin de pouvoir les agréger et les visualiser ultérieurement. Exécutez cette commande dans les outils de développement :</p>PUT dashboard-views
{
  "mappings": {
    "properties": {
      "captured_at": {
        "type": "date"
      },
      "dashboard_id": {
        "type": "keyword"
      },
      "dashboard_name": {
        "type": "keyword"
      },
      "view_count": {
        "type": "integer"
      }
    }
  }
}<p>L'utilisation des mappings <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/keyword"><code>keyword</code></a> pour les ID et les noms permet des <a href="https://www.elastic.co/docs/explore-analyze/query-filter/aggregations">agrégations</a>. L'utilisation de <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/number"><code>integer</code></a> pour <code>view_count</code> est une valeur par défaut sûre, car Kibana réinitialise le compteur quotidiennement ; atteindre la limite de 32 bits (plus de 2 milliards de vues en une seule journée) n'est donc pas un problème réaliste. Il prend toujours en compte les opérations numériques, comme <a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-metrics-max-aggregation"><code>max</code></a>, <a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-metrics-avg-aggregation"><code>avg</code></a> et <a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-metrics-min-aggregation"><code>min</code></a> entre autres.</p><h2>Étape 3 : Créer le workflow</h2><p>Accédez à <strong>Stack Management &gt; Workflows &gt; Nouveau workflow</strong>, puis collez la configuration YAML de workflow suivante :</p>name: dashboard-views-ingestion
triggers:
  - type: scheduled
    with:
      every: 30m

steps:
  - name: fetch_dashboard_views
    type: kibana.request
    with:
      method: GET
      path: &gt;-
        /api/saved_objects/_find?type=usage-counter&amp;per_page=10000&amp;filter=usage-counter.attributes.domainId:"dashboard"%20and%20usage-counter.attributes.counterType:"viewed"

  - name: index_each_dashboard
    type: foreach
    foreach: "{{ steps.fetch_dashboard_views.output.saved_objects }}"
    steps:
      - name: fetch_dashboard_name
        type: kibana.request
        with:
          method: GET
          path: /api/saved_objects/dashboard/{{ foreach.item.attributes.counterName }}
        on-failure:
          continue: true

      - name: index_doc
        type: elasticsearch.request
        with:
          method: POST
          path: /dashboard-views/_doc
          body:
            dashboard_id: "{{ foreach.item.attributes.counterName }}"
            dashboard_name: "{{ steps.fetch_dashboard_name.output.attributes.title }}"
            view_count: "${{ foreach.item.attributes.count | plus: 0 }}"
            captured_at: "{{ execution.startedAt | date: '%Y-%m-%dT%H:%M:%SZ' }}"<p>Dans la section suivante, nous allons détailler le workflow étape par étape.</p><h3>Fonctionnement du workflow</h3><h4>Déclencheurs</h4><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7672aa533b4bc9ed/6a17dc5b420229d07c29f4d4/5670991d65c64ee833924225c2d375a1be868b13-325x162.png" alt=" Déclencheurs planifiés" /><p>Le workflow s'exécute sur un déclencheur planifié toutes les 30 minutes. Cela nous permet d'obtenir des données temporelles sans solliciter l'API.</p><h4>récupérer les vues du tableau de bord</h4><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltab2a16f1f11304ea/6a17dc5d25daab26f608a117/66eaec147c3d01c524c67cf1c7f663ac56a3259d-812x215.png" alt=" Récupérer le tableau de bord" /><p>Utilise <code>kibana.request</code> pour appeler l'API des objets enregistrés de Kibana. Aucune configuration d'authentification n'est nécessaire : le moteur de workflow associe automatiquement les en-têtes corrects en fonction du contexte de l'exécution.</p><h4>index_each_dashboard (foreach)</h4><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte6b2611b0216555e/6a17dc5f445de95b584cffe5/aad45e8aed8dc81ded6260cd6199ff78dcffe3b4-1892x290.png" alt="Indexer chaque tableau de bord" /><p>Itère sur le tableau <a href="https://www.elastic.co/docs/api/doc/kibana/group/endpoint-saved-objects"><code>saved_objects</code></a> renvoyé par l'étape précédente. L'élément actuel de chaque itération est disponible sous la forme <code>foreach.item</code>. À l'intérieur de la boucle, nous exécutons deux étapes imbriquées pour chaque tableau de bord.</p><p><strong>1. </strong><strong><code>fetch_dashboard_name</code></strong><strong> :</strong></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb3733b3c24629abf/6a17dc60a292993fe3d02b75/db21ec5094b743018b9cd66c5052681f14c7d7e3-1999x431.png" alt=": Récupérer le nom du tableau de bord" /><p>Résout le titre du tableau de bord lisible par l'utilisateur en appelant <code>GET /api/saved_objects/dashboard/{id}</code>. Nous ajoutons <code>on-failure: continue: true</code> pour que, si un tableau de bord a été supprimé mais qu'il contient encore des compteurs de vues, la boucle continue au lieu d'interrompre l'exécution.</p><p><strong>2. </strong><strong><code>index_doc</code></strong><strong> :</strong></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt385c1f29d717c280/6a17dc62faa91353cb93c759/f49dd0c9f0817bb1e1e5d9f4a2b05d13ef331054-1999x626.png" alt=" Demande Elasticsearch" /><p>Indexe chaque document à l'aide de <code>POST /dashboard-views/_doc</code> (sans identifiant explicite), ce qui permet à Elasticsearch de générer automatiquement des identifiants. Un nouveau document est ainsi créé à chaque exécution, ce qui permet d'établir un historique du nombre de vues au fil du temps plutôt que d'écraser le snapshot précédent.</p><p>Deux choses à noter :</p><ul><li><p>Le champ <code>captured_at</code> utilise le filtre de date pour formater l'horodatage au format <a href="https://www.iso.org/iso-8601-date-and-time-format.html">ISO 8601</a>. Sans ce filtre, la valeur apparaît sous la forme d'une chaîne de date JavaScript, comme <code>Tue Mar 10 2026 05:03:47 GMT+0000</code>, qu'Elasticsearch ne pourra pas interpréter comme une date.</p></li><li><p>Le <code>view_count</code> utilise la syntaxe <code>${{ }}</code> avec <code>| plus: 0</code> pour préserver le type numérique. L'utilisation de <code>{{ }}</code> le rendrait sous forme de chaîne, ce qui empêcherait les opérations mathématiques dans le tableau de bord.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3b94bb6c22c0253e/6a17dc6425daab37a508a11b/6d48c8784d5df6192e8b5175e69dbab5098194bc-919x774.png" alt="" /><p><em>L'interface utilisateur vous permet de déboguer efficacement chacune des étapes du workflow.</em></p><h2>Étape 4 : Créer le tableau de bord des statistiques</h2><p>Une fois que le workflow a été exécuté plusieurs fois et que les données ont été collectées, créez un nouveau tableau de bord dans Kibana en utilisant la data view dashboard-views.</p><p>Voici quelques panneaux pour commencer :</p><ul><li><p><strong>Principaux tableaux de bord par vues</strong> : utilisez un <a href="https://www.elastic.co/docs/explore-analyze/visualize/charts/bar-charts"><strong>graphique à barres</strong></a> avec <code>dashboard_name</code> sur l'axe des X et <code>last_value(view_count)</code> sur l'axe des Y. Cela indique le nombre actuel de vues quotidiennes par tableau de bord.</p></li><li><p><strong>Vues au fil du temps</strong> : utilisez un <a href="https://www.elastic.co/docs/explore-analyze/visualize/charts/line-charts"><strong>graphique linéaire</strong></a> avec <code>captured_at</code> sur l'axe X et <code>last_value(view_count)</code> sur l'axe Y, décomposé par <code>dashboard_name</code>. Comme chaque exécution ajoute un nouveau document, utilisez la dernière valeur pour obtenir le nombre maximal par intervalle temporel au lieu d'additionner les doublons.</p></li><li><p><strong>Snapshot actuel</strong> : utilisez un <a href="https://www.elastic.co/docs/explore-analyze/visualize/charts/tables"><strong>tableau de données</strong></a> avec les dernières valeurs <code>captured_at</code> pour afficher les nombres de vues les plus récents sur tous les tableaux de bord.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt18d0390e0526b215/6a17dc65a292991da7d02b79/e245b95f67daf76a2aaf4cb9df2c75ef4cfef582-1462x747.png" alt="" /><p>Étant donné que chaque workflow crée un nouveau document, vous pouvez filtrer par plage horaire pour analyser l'activité sur des périodes spécifiques, comparer d'une semaine à l'autre ou créer des alertes lorsqu'un tableau de bord passe en dessous d'un seuil d'affichage.</p><h2><strong>Conclusion</strong></h2><p>Elastic Workflows est parfaitement adapté à ce type de collecte de données périodique, car la source (API Kibana) et la destination (Elasticsearch) sont natives, ce qui élimine toute gestion d'identifiants. Le moteur de workflow gérant automatiquement l'authentification pour les étapes <code>kibana.request</code> et <code>elasticsearch.request</code>, vous n'avez qu'à écrire la logique.</p><h2><strong>Ressources</strong></h2><ul><li><p><a href="https://www.elastic.co/docs/explore-analyze/workflows">Elastic Workflows</a></p></li><li><p><a href="https://www.elastic.co/docs/api/doc/kibana/">API Kibana</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/monitor-kibana-dashboard-views-elastic-workflows</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/monitor-kibana-dashboard-views-elastic-workflows</guid>
    <category><![CDATA[Expérience développeur]]></category>
    <dc:creator><![CDATA[Gustavo Llermaly]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltef604bbb6dee6be0/6a17dc67a29299db23d02b7d/0ed94ce00962287b5507f45c92ecb60fdcbf2718-1280x720.png" length="0" type="image/png"/>
    <pubDate>Fri, 03 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Gestion des dépendances sur Kubernetes]]></title>
    <description><![CDATA[Comment simplifier la gestion des dépendances sur Kubernetes en utilisant Renovate CLI et Argo Workflows.]]></description>
    <content:encoded><![CDATA[<p>Voici comment nous avons construit une plateforme de gestion des dépendances auto-hébergée en utilisant Kubernetes, Argo Workflows, Argo Events et Renovate CLI pour automatiser les mises à jour, traiter rapidement les vulnérabilités et expositions courantes (CVE) et transmettre efficacement les nouvelles versions de packages à des milliers de référentiels.</p><h2><strong>Gestion des dépendances chez Elastic</strong></h2><p>Chez Elastic, nous devons gérer des centaines, voire des milliers de référentiels privés et publics. Lorsqu'une CVE critique est découverte, nous avons besoin de réponses et d'actions immédiates : quels référentiels sont vulnérables ? Dans quel délai pouvons-nous les réparer ? Outre la sécurité, des questions de productivité se posent également : comment transmettre rapidement la publication d'une nouvelle version d'un package à tous les référentiels qui en dépendent, sans passer trop de temps à effectuer des tâches manuelles ?</p><p>La recherche de méthodes de gestion des dépendances a été motivée à l'origine par la nécessité d'établir une base sécurisée avec des mises à jour automatisées pour <a href="https://www.elastic.co/blog/reducing-cves-in-elastic-container-images">réduire les CVE</a>. Après avoir soigneusement réfléchi aux solutions de gestion des dépendances, nous avons d'abord commencé à travailler sur une infrastructure auto-hébergée. Nous utilisions notre propre cluster Kubernetes pour exécuter Mend Renovate Community auto-hébergé. L'idée était de pouvoir fournir une plateforme de gestion des dépendances à laquelle nos utilisateurs pourraient accéder en libre-service.</p><p>L'expérience initiale s'est avérée fructueuse, si bien que de plus en plus d'équipes ont commencé à adopter notre plateforme et à l'utiliser dans le cycle de vie quotidien de leurs référentiels pour les mises à jour et les correctifs CVE. Cela s'est passé si vite que nous avons rapidement atteint les limites de notre installation auto-hébergée.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc99617fc3eed538d/6a170ea9964cea459d08bc67/e14d9f98d4eccaa08a335d5bd23d88e5debbb344-1600x1103.png" alt="Gestion des dépendances chez Elastic" /><h3><strong>Le défi : comment pouvons-nous scaler une plateforme de gestion des dépendances dans une grande organisation disposant d'un nombre important de référentiels ?</strong></h3><p>Notre plateforme de gestion des dépendances traitait un référentiel à la fois et le modèle de traitement séquentiel ne pouvait pas suivre le rythme, compte tenu du grand nombre de référentiels que nous gérons. Nous avions déjà constaté que le problème provenait du fait <strong>qu'une seule instance</strong> de notre outil de gestion des dépendances pouvait traiter notre liste importante et toujours croissante de référentiels. Les référentiels attendaient parfois pendant plusieurs heures dans une file d'attente. Plus de 50 % de nos référentiels n'étaient même pas traités quotidiennement. Autrement dit, plus de la moitié de nos référentiels attendaient plus de 24 heures entre les analyses.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0d205fd379e3c07a/6a170eab961e691e1fc4cfca/45ade5bda08f82bed0b3d0d3736cbd6f056e7a4e-1312x816.jpg" alt="Problème de gestion des dépendances" /><p>Les grands référentiels ont créé des goulots d'étranglement plus importants, en raison de leurs bases de code volumineuses et de leurs multiples requêtes pull ouvertes. Les événements du webhook GitHub ont perturbé la séquence. La fusion automatique est devenue peu fiable car le moment des analyses était imprévisible. Nous avions fait une promesse à nos utilisateurs concernant la fréquence des analyses, mais nous n'avons pas pu la tenir.</p><h3><strong>La décision de développer en interne : répondre aux besoins uniques de scalabilité et de sécurité d'Elastic</strong></h3><p>Bien que nous envisagions des options commerciales, dont l'<strong>édition auto-hébergée Renovate Enterprise de Mend</strong>, nous avions en interne chez Elastic quelques initiatives clés en cours de développement.</p><p>Notre décision de créer une plateforme en interne a été motivée par la prise de conscience que seule une solution hautement personnalisée pouvait répondre aux exigences spécifiques et non négociables d'Elastic :</p><ol><li><p><strong>Investissement dans notre plateforme de développement interne</strong> : à l'époque, nous avions déjà investi massivement dans notre plateforme de développement interne. Nous réfléchissions à la manière d'intégrer chacun de nos services à cette plateforme et nous concevions des solutions pour y parvenir. Cela impliquait de tester nos propres règles et pratiques pour notre plateforme de gestion des dépendances. De plus, de nouvelles directives entraient en vigueur et nous souhaitions concevoir la plateforme en amont.</p></li><li><p><strong>Intégration native et personnalisation du workflow</strong> : nous avions besoin d'une intégration simple à nos outils et processus internes. Par exemple, nous souhaitions centraliser la configuration sous forme de code avec notre catalogue de services (Backstage). L'utilisation de Backstage nous impose des exigences spécifiques avec lesquelles nous voulions que notre plateforme soit compatible. Ainsi, bien qu'il soit possible d'utiliser les API auto-hébergées de Renovate en complément de notre automatisation Backstage, cela ne couvrirait pas entièrement nos processus internes.</p></li><li><p><strong>Sécurité renforcée par défense en profondeur spécifique à Elastic</strong> : notre conformité stricte en matière de sécurité exigeait des mécanismes de sécurité sur mesure, adaptés à notre écosystème. Nous nous efforcions de <a href="https://entro.security/blog/how-elastic-scaled-secrets-nhi-security-elastics-playbook-from-visibility-to-automation/">renforcer la sécurité de notre utilisation des "identités non humaines".</a> Ce renforcement des accès impliquait que les méthodes d'authentification non standard auprès de GitHub ne fonctionneraient pas avec un outil standard qui ne prenait pas en charge cette implémentation interne. Notre workflow comprenait la mise en œuvre d'un modèle de chiffrement des secrets de workflow parent-enfant et l'utilisation de jetons GitHub temporaires à usage unique. Le développement en interne était la seule solution pratique pour intégrer ces couches de sécurité uniques et minimiser la surface d'attaque dans notre environnement multicloud complexe.</p></li></ol><h2><strong>La solution : l'orchestration des workflows pour la gestion des dépendances</strong></h2><p>Notre solution est née de notre volonté de tirer parti de l'outil de gestion des dépendances que nous utilisions déjà plutôt que de le remplacer et rechercher d'autres solutions. Cet outil avait démontré son potentiel, et sa flexibilité est essentielle pour répondre aux différents besoins de notre organisation. Nous avons examiné différentes solutions, et ce qui a guidé notre choix, ce sont les besoins importants et parfois spécifiques que nous devons satisfaire. Nous avons donc décidé de créer une plateforme de gestion des dépendances fiable et évolutive, où chaque référentiel est traité individuellement, éliminant ainsi les goulots d'étranglement et nous préparant à la croissance.</p><p>Nous avons conçu la plateforme en respectant trois principes fondamentaux :</p><h3><strong>1. Traitement parallèle</strong></h3><p>Chaque référentiel est doté de son propre environnement de gestion des dépendances. Plus de files d'attente. Notre simultanéité d'exécution n'est limitée que par le nombre de ressources que nous utilisons. Nous avons également appliqué une programmation distribuée intelligente pour éviter d'être limité par GitHub.</p><h3><strong>2. Libre-service</strong></h3><p>Nous utilisons notre catalogue de services (Backstage) pour intégrer et gérer automatiquement les nouveaux référentiels. Grâce à notre propre système de définition des ressources, l'utilisateur final peut choisir la fréquence de traitement des référentiels, le nombre de ressources à allouer à ses planifications, et activer ou désactiver le traitement à tout moment. Nous prévoyons d'ajouter d'autres options à mesure que les besoins de nos utilisateurs évoluent et qu'ils se familiarisent avec la nouvelle installation.</p><h3><strong>3. Réduction de la portée des secrets et de l’isolation des espaces de noms</strong></h3><p>Pour une sécurité accrue, nous fournissons à nos modules de gestion des dépendances des jetons GitHub éphémères qui sont générés au début de chaque workflow. En outre, nous isolons nos charges de travail dans des espaces de noms spécifiques afin de ne leur fournir que les secrets nécessaires. Nous contrôlons les secrets qui peuvent être accessibles par chaque workflow de gestion des dépendances en utilisant le RBAC de Kubernetes. Nous utilisons également le chiffrement pour transmettre le jeton GitHub du workflow parent au workflow enfant.</p><p>Nous avons reconstruit notre plateforme en utilisant Kubernetes et en exploitant sa puissance. Argo Workflows gère la logique de nos processus et Renovate CLI est configuré pour analyser et traiter un référentiel à la fois.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3548539ab52fbb79/6a170eac0c48573da601ab26/5560ed20e2bd9ecdd574a9c835126d12b24c332f-1600x1157.png" alt="Aperçu des workflows de gestion des dépendances dans Kubernetes" /><p><strong>L'intérêt de ce modèle</strong> : nous utilisons des projets open source éprouvés d'une manière originale, en fournissant de nouveaux exemples fonctionnels pour tous ces projets et, en même temps, en amplifiant la vitesse de développement et en consolidant la réduction des CVE pour nos équipes.</p><h2><strong>Architecture de gestion des dépendances : Quatre microservices</strong></h2><p>La plateforme comprend quatre composants conçus sur mesure :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6451ff19da4db511/6a170eaec1e8a562e0f88378/2b3d4046c05bb261e45d40c59f864eb51fb9eaa9-1217x1600.png" alt="Composants pour la gestion des dépendances dans Kubernetes" /><h3><strong>Opérateur de workflow (Go/Kubebuilder)</strong></h3><p>Un opérateur Kubernetes gérant le cycle de vie du workflow à travers trois définitions de ressources personnalisées (CRD) :</p><ul><li><p><strong>CRD RepoConfig</strong> : source unique de référence pour la configuration du référentiel.</p></li></ul><p>Voici comment RepoConfig est défini dans l'opérateur :</p>// RepoConfig is the Schema for the repoconfigs API
type RepoConfig struct {
	metav1.TypeMeta `json:",inline"`

	// metadata is a standard object metadata
	// +optional
	metav1.ObjectMeta `json:"metadata,omitempty,omitzero"`

	// spec defines the desired state of RepoConfig
	// +required
	Spec RepoConfigSpec `json:"spec"`

	// status defines the observed state of RepoConfig
	// +optional
	Status RepoConfigStatus `json:"status,omitempty,omitzero"`
}<p>Et voici à quoi ressemblerait une instance de RepoConfig :</p>apiVersion: workflows.elastic.co/v1
kind: RepoConfig
metadata:
  generation: 3
  name: elastic-test-repo
  namespace: dependency-management-operator
spec:
  owner: group:my-team
  renovate:
    config:
      resourceGroup: SMALL
      runFrequency: 4h
    enabled: true
  repository: elastic/test-repo<ul><li><p><strong>CRD parent</strong> : gère les workflows Cron pour les analyses programmées.</p></li></ul><p>À l'intérieur de la boucle de rapprochement du contrôleur parent, nous nous assurons que les paramètres du workflow sont créés et maintenus à jour, voire supprimés si nécessaire.</p><p>Tout d'abord, le contrôleur parent obtient certains paramètres configurés globalement pour les workflows :</p>func (r *ParentReconciler) reconcileSubResources(ctx context.Context, req ctrl.Request, parent *workflowsv1.Parent) error {
	logger := logf.FromContext(ctx)
	logger.Info("Reconcile SubResources for Parent", "name", req.NamespacedName)
	wfSet := workflowsettings.WorkflowSettings{
		RunFrequency:   parent.Spec.RunFrequency,
		ResourceGroups: "parent",
	}<p>Il s'assure que la configuration du mutex est à jour afin d'empêcher l'exécution simultanée de workflows similaires :</p>	cfMngr := resources.NewConfigMapManager(r.Client, r.Scheme, r.OperatorConfig.ParentNamespace)
	err := cfMngr.CreateOrUpdateSyncMutexConfigmap(ctx, fmt.Sprintf("%s%s", r.OperatorConfig.ResourcesPrefix, r.OperatorConfig.SyncMutexCfgMapName), strings.TrimPrefix(parent.Spec.Repository, "elastic/"), r.OperatorConfig.SemaphoreConcurrencyLimit)<p>Ensuite, il crée un gestionnaire de workflow qui est la structure qui va créer ou mettre à jour les workflows Cron et les modèles de workflow :</p>	wfMngr := resources.NewArgoWorkflowManager(r.Client,
		r.Scheme,
		curateResourceName(
			strings.ReplaceAll(parent.Spec.Repository, "/", "-"),
		),
		parent.Namespace,
		"parent-workflow",
		false).
		WithOrganization(r.OperatorConfig.GitHubOrg).
		WithRepoName(parent.Spec.Repository).
		Init(true, true).
		WithPrefix(r.OperatorConfig.ResourcesPrefix).
		WithWfTemplateName(r.OperatorConfig.ParentWorkflowTemplate).
		WithResources(wfSet.GetResourceCategory()).
		WithSchedule(wfSet.GetCronSchedule()).
		WithImagePullSecrets([]corev1.LocalObjectReference{{
			Name: r.OperatorConfig.WorkflowImagePullSecrets,
		}}).
		AddArgument(true, true, "extra_cli_args").
		SetArgument(true, false, "extra_cli_args", "none").
		AddTemplate(resources.NewParentDAGTemplateInstance()).
		AddTemplate(resources.NewWorkflowsTemplateInstance("check-child-workflows", r.OperatorConfig.WorkflowImagePullPolicy, r.OperatorConfig.WorkflowNodeSelector)).
		AddTemplate(resources.NewWorkflowsTemplateInstance("security", r.OperatorConfig.WorkflowImagePullPolicy, r.OperatorConfig.WorkflowNodeSelector)).
		AddTemplate(resources.NewWorkflowsTemplateInstance("submit-child-workflow", r.OperatorConfig.WorkflowImagePullPolicy, r.OperatorConfig.WorkflowNodeSelector))
	wfMngr.OverWriteCommand("submit-child-workflow", r.OperatorConfig.ChildNamespace)
	wfMngr.OverwriteWfTemplateName("parent-wftmpl")
	wfMngr.AddSynchronization(fmt.Sprintf("%s%s", r.OperatorConfig.ResourcesPrefix, r.OperatorConfig.SyncMutexCfgMapName), "{{workflow.parameters.repo_name}}")
	err = wfMngr.CreateOrUpdateCronWorkflow(ctx)
	if err != nil {
		return fmt.Errorf("failed to create or update cron workflow: %w", err)
	}
	err = wfMngr.CreateOrUpdateWorkflowTemplate(ctx)
	if err != nil {
		return fmt.Errorf("failed to create or update workflow template: %w", err)
	}
	return nil<ul><li><p><strong>CRD enfant</strong> : gère les modèles de workflow avec des ressources par référentiel.</p></li></ul><p>Le contrôleur enfant a une mission de rapprochement similaire à celle du parent, mais ici, il est responsable des modèles de workflow dans l'espace de noms enfant qui seront déclenchés par les workflows parents.</p>func (r *ChildReconciler) reconcileSubResources(ctx context.Context, req ctrl.Request, child *workflowsv1.Child) error {
	logger := logf.FromContext(ctx)
	logger.Info("Reconcile SubResources for Child", "name", req.NamespacedName)
	wfSet := workflowsettings.WorkflowSettings{
		ResourceGroups: child.Spec.ResourceCategory,
	}
	wfMngr := resources.NewArgoWorkflowManager(r.Client,
		r.Scheme,
		curateResourceName(
			strings.ReplaceAll(child.Spec.Repository, "/", "-"),
		),
		child.Namespace,
		"runner",
		true).
		Init(false, true). // only manage workflow template
		WithPrefix(r.OperatorConfig.ResourcesPrefix).
		WithSuffix("-child-wftmpl").
		WithRepoName(child.Spec.Repository).
		WithOrganization(r.OperatorConfig.GitHubOrg).
		WithResources(wfSet.GetResourceCategory()). // will override resources of presets if set
		WithImagePullSecrets([]corev1.LocalObjectReference{{
			Name: r.OperatorConfig.WorkflowImagePullSecrets,
		}}).
		AddTemplate(resources.NewWorkflowsTemplateInstance("runner", r.OperatorConfig.WorkflowImagePullPolicy, r.OperatorConfig.WorkflowNodeSelector)).
		AddArgument(false, true, "repo_full_name").
		AddArgument(false, true, "repo_name").
		AddArgument(false, true, "encrypted_token").
		AddArgument(false, true, "extra_cli_args")
	wfMngr.OverWriteCommand("runner", r.OperatorConfig.ChildNamespace)
	err := wfMngr.CreateOrUpdateWorkflowTemplate(ctx)
	if err != nil {
		return fmt.Errorf("failed to create or update workflow template: %w", err)
	}
	return nil
}<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta735156ba6e370ef/6a170eaf7d8d6706fd70e7e4/7ac70492a1266ba02cb8afbafc5a486cb38a0edc-1600x1290.png" alt="Workflows pour la gestion des dépendances dans Kubernetes" /><p>Le modèle multicontrôleur offre une séparation claire : le contrôleur RepoConfig gère l'intégration/dissociation, le contrôleur parent gère la planification, et le contrôleur enfant gère les modèles d'exécution.</p><h3><strong>Portail d'événements GitHub (Go)</strong></h3><p>Proxy sécurisé de webhook qui reçoit les webhooks GitHub, vérifie les signatures, filtre par organisation/référentiel, et redirige vers Argo Events. Nous avons conçu 10 capteurs distincts répondant aux interactions du tableau de bord des dépendances, aux événements de requêtes pull (PR) et aux mises à jour des packages.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7e748ceb93c13a5a/6a170eb1a6c2b908f8e797b4/4828625456cbd6efa8020a20f10d23f294f98a02-1306x1600.png" alt="Interactions du tableau de bord des dépendances sur Kubernetes" /><p>Cette passerelle permet l'intégration aux applications GitHub par :</p><ul><li><p>Vérification de la sécurité des signatures des webhooks GitHub entrants.</p></li><li><p>Transmission des événements valides à l'EventSource Argo Events avec tous les en-têtes correspondants et l'authentification.</p></li><li><p>Nous configurons également un authSecret sur l'EventSource et le fournissons comme en-tête Bearer dans les requêtes transférées.</p></li><li><p>Fourniture de logging, indicateurs, et logique de tentatives.</p></li></ul><p>Le webhook effectue diverses validations sur chaque requête d'événement GitHub.</p><p>Il s'assure que certains attributs HTTP sont présents :</p>// ValidateRequestMethod checks if the request method is POST.
func ValidateRequestMethod(r *http.Request) error {
	if r.Method != http.MethodPost {
		return fmt.Errorf("method not allowed, only POST is accepted")
	}
	return nil
}

// ValidateRequiredHeaders checks for required GitHub headers.
func ValidateRequiredHeaders(r *http.Request) error {
	eventType := r.Header.Get("X-GitHub-Event")
	deliveryID := r.Header.Get("X-GitHub-Delivery")
	signature := r.Header.Get("X-Hub-Signature-256")
	if eventType == "" || deliveryID == "" || signature == "" {
		return fmt.Errorf("missing required GitHub headers")
	}
	return nil
}

// ValidateUserAgent checks that the User-Agent header starts with GitHub-Hookshot/
func ValidateUserAgent(r *http.Request) error {
	userAgent := r.Header.Get("User-Agent")
	if !strings.HasPrefix(userAgent, "GitHub-Hookshot/") {
		return fmt.Errorf("invalid User-Agent")
	}
	return nil
}<p>Tout en validant également la signature de chaque requête et son organisation :</p>// ValidateSignature verifies the GitHub webhook signature.
func ValidateSignature(r *http.Request, secret string) ([]byte, error) {
	payload, err := GitHub.ValidatePayload(r, []byte(secret))
	if err != nil {
		return nil, fmt.Errorf("invalid GitHub signature: %w", err)
	}
	return payload, nil
}

// ValidateAllowedOwner checks if the organization login is in the allowed organizations list.
func ValidateAllowedOwner(payload []byte, allowedGitHubOrganizations []string) (string, error) {
	var orgLogin string
	var payloadMap map[string]any
	if err := json.Unmarshal(payload, &amp;payloadMap); err == nil {
		if orgObj, ok := payloadMap["organization"].(map[string]any); ok {
			if login, ok := orgObj["login"].(string); ok {
				orgLogin = login
			} else if name, ok := orgObj["name"].(string); ok {
				orgLogin = name
			}
		}
	}
	if !slices.Contains(allowedGitHubOrganizations, orgLogin) {
		return orgLogin, fmt.Errorf("organization login not allowed")
	}
	return orgLogin, nil
}<p>Enfin, il redirige vers Argo Events en fonction du type d'événement :</p>	// Map eventType to Argo `EventSource` path
	var endpoint string
	switch eventType {
	case "push":
		endpoint = "/push"
	case "issues":
		endpoint = "/issues"
	case "pull_request":
		endpoint = "/pull-requests"
	default:
		slog.Info("Ignoring unhandled event type", "event_type", eventType, "delivery_id", deliveryID)
		w.WriteHeader(http.StatusOK)
		_,  = w.Write([]byte("ok"))
		return
	}
	forwardURL := h.config.ArgoEventSourceForwardURL + endpoint<p>Du côté d'Argo Events, 10 capteurs surveillent l'EventBus d'Argo pour détecter les nouveaux événements.</p>apiVersion: argoproj.io/v1alpha1
kind: Sensor
metadata:
  name: {{ .Values.sensors.packageUpdateOnDefaultBranch.name }}
  namespace: {{ .Release.Namespace }}
spec:
  eventBusName: {{ .Values.eventBus.name }}<p>Le script applique ensuite la logique de chaque capteur :</p>script: |
          local e = event
          if not e or not e.body or not e.body.repository then
            return false
          end

          -- e.g., "refs/heads/main"
          local ref = e.body.ref
          local default_branch = e.body.repository.default_branch
          if not ref or not default_branch then
            return false
          end

          local expected = "refs/heads/" .. default_branch
          if ref ~= expected then
            return false
          end

        {{- if .Values.sensors.packageUpdateOnDefaultBranch.packageFiles }}
          patterns = { {{- range $i, $f := .Values.sensors.packageUpdateOnDefaultBranch.packageFiles }}{{ if $i }}, {{ end }}"{{ $f }}"{{- end }} }
        {{- end }}

          local function anyMatch(path)
            if type(path) ~= "string" then return false end
            for _, pat in ipairs(patterns) do
              -- match filename at repo root, or anywhere under subdirs
              if path:match(pat) or path:match(".+/" .. pat) then
                return true
              end
            end
            return false
          end

          local function filesContainPackage(paths)
            if type(paths) ~= "table" then return false end
            for _, p in ipairs(paths) do
              if anyMatch(p) then return true end
            end
            return false
          end

          -- Inspect all commits (GitHub includes added/modified/removed lists)
          local commits = e.body.commits
          if type(commits) ~= "table" then
            -- Fallback: some payloads include only head_commit
            commits = {}
            if type(e.body.head_commit) == "table" then
              table.insert(commits, e.body.head_commit)
            end
          end

          for _, c in ipairs(commits) do
            if filesContainPackage(c.added) or filesContainPackage(c.modified) or filesContainPackage(c.removed) then
              return true
            end
          end

          return false<h3><strong>Backstage Syncer (Go)</strong></h3><p>Ce composant interroge notre catalogue de services (Backstage) pour obtenir les entités de ressources réelles du référentiel, les transforme en CRD RepoConfig et maintient la plateforme synchronisée avec les modifications de configuration. Celles-ci sont appliquées en trois minutes.</p>repoMap := make(map[string]map[string]interface{})
			for i := range entities {
				entity := &amp;entities[i]
				if entity.Spec.Type != "GitHub-repository" {
					continue
				}

				implRaw, err := json.Marshal(entity.Spec.Implementation)
				if err != nil {
					logger.Error("Failed to marshal implementation", "error", err)
					continue
				}

				var implMap map[string]interface{}
				err = json.Unmarshal(implRaw, &amp;implMap)
				if err != nil {
					logger.Error("Failed to unmarshal implementation map", "error", err)
					continue
				}
				var repoName string
				if specMap, ok := implMap["spec"].(map[string]interface{}); ok {
					if repo, ok := specMap["repository"].(string); ok {
						repoName = repo
					}
				}
				if repoName == "" {
					continue
				}

				var workflowsRaw []byte
				if v, ok := implMap["spec"].(map[string]interface{}); ok {
					if r, ok := v["renovate"]; ok {
						workflowsRaw,  = json.Marshal(r)
					} else {
						workflowsRaw = []byte(`{}`)
					}
				} else {
					workflowsRaw = []byte(`{}`)
				}

				var workflowsWithDefaults schema.WorkflowsMetadata
				err = json.Unmarshal(workflowsRaw, &amp;rworkflowsWithDefaults)
				if err != nil {
					logger.Error("Failed to unmarshal workflows config", "error", err)
					continue
				}

				workflowsMap := map[string]interface{}{
					"enabled":        workflowsWithDefaults.Enabled,
					"require_pr":     workflowsWithDefaults.RequirePr,
					"resource_group": string(workflowsWithDefaults.ResourceGroup),
					"run_frequency":  string(workflowsWithDefaults.RunFrequency),
				}
				repoMap[repoName] = map[string]interface{}{
					"renovate": workflowsMap,
					"owner":    entity.Spec.Owner,
				}
			}
			logger.Info("Fetched GitHub Repository data from Backstage", "repository_count", len(repoMap), "status_code", resp.StatusCode)<p>Enfin, il écrit ces données dans des instances RepoConfig.</p><h3><strong>Base de workflows (mixte : JavaScript, Go, Helm)</strong></h3><p>La couche de base contient des charts Helm, des configurations JavaScript, un wrapper Go pour Renovate CLI avec prise en charge du chiffrement et un indexeur APK personnalisé pour les packages Alpine.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4c1b5b840854ddf5/6a170eb47d8d67694e70e7e8/908d19278face3ce1119dbee9146c1264b6e2f30-1600x873.png" alt=" Composants de base pour la gestion des dépendances sur Kubernetes" /><h2><strong>Configuration en libre-service</strong></h2><p>Les équipes configurent leurs référentiels de manière déclarative via Backstage :</p>spec:
  renovate:
    enabled: true
    config:
      resourceGroup: LARGE      # SMALL | MEDIUM | LARGE  
      runFrequency: "0 */4 * * *"  # Every 4 hours<p>Les groupes de ressources allouent le processeur et la mémoire en fonction de la taille du référentiel :</p><ul><li><p><strong>SMALL</strong> : CPU 500 m, mémoire 1 Go.</p></li><li><p><strong>MEDIUM</strong> : CPU 1000 m, mémoire 2 Go.</p></li><li><p><strong>LARGE</strong> : CPU 2000 m, mémoire 4 Go.</p></li></ul><p>La configuration est versionnée, auditable et s'applique automatiquement.</p><h2><strong>Le modèle parent-enfant</strong></h2><p>Le modèle d'exécution utilise un modèle de workflow parent-enfant :</p><ul><li><p><strong>Workflow parent</strong> : workflow Cron léger qui s'exécute comme prévu. Chiffre les secrets, détermine si une analyse doit être exécutée, transmet la configuration à l'enfant.</p></li><li><p><strong>Workflow enfant</strong> : pod éphémère où s'exécute Renovate CLI. Allocation dynamique des ressources, déchiffrement des secrets de manière isolée, arrêt après exécution.</p></li></ul><p>Cette séparation offre sécurité (secrets chiffrés au niveau parent), optimisation des ressources (les parents utilisent des ressources minimales) et scalabilité (les enfants s'exécutent en parallèle).</p><h2><strong>Résultats</strong></h2><h3><strong>Transformation des performances</strong></h3><ul><li><p><strong>Avant</strong> : un référentiel à la fois, certains référentiels n'étaient pas traités, parfois même pendant un jour ou plus, moins de 1 000 analyses par jour.</p></li><li><p><strong>Après</strong> : plus de 100 analyses simultanées, généralement 8 000 analyses et jusqu'à 10 000 analyses enregistrées par jour, limitées uniquement par la quantité de ressources que nous sommes prêts à consacrer et par la façon dont nous gérons les limites de débit de GitHub.</p></li></ul><h3><strong>Rentabilité</strong></h3><p>Cependant, aussi étrange que cela puisse paraître, exécuter 8 000 pods par jour peut permettre d'obtenir le même résultat à moindre coût qu'avec un seul pod fonctionnant en continu pour tenter d'atteindre le même résultat.</p><p>Dans la configuration précédente, nous utilisions une seule instance qui, en conditions optimales, effectuait 500 à 600 analyses. Par ailleurs, comme différents types de référentiels étaient exécutés sur le même pod, nous devions dimensionner celui-ci pour les plus volumineux. Ce dimensionnement était bien supérieur à notre offre actuelle "extra large", qui utilise 8 cœurs de processeur et 16 Go de mémoire par pod.</p><p>Pour traiter le volume quotidien actuel, le pod unique devrait s'exécuter pendant 12 jours. Ainsi, en comparant le coût de ce pod unique fonctionnant pendant 12 jours à celui de 8 000 pods de taille "MEDIUM" s'exécutant chaque jour, notre nouvelle architecture est bien plus efficace pour un même volume d'analyses.</p><p>Métrique</p><p>Scénario A (workflows)</p><p>Scénario B (pod unique et exécution de longue durée)</p><p>Configuration</p><p>8 000 pods (1 vCPU/2 Go)</p><p>1 pod (8 vCPU / 16 Go)*</p><p>Durée</p><p>10 minutes chacun</p><p>12 jours en continu</p><p>Temps de travail total</p><p>1 333 heures de calcul</p><p>288 heures de calcul</p><p>Coût total</p><p>65,83 $</p><p>113,75 $</p><p>Cependant, prenons en considération le fait que notre valeur par défaut pour nos charges de travail est définie sur "SMALL", la grande majorité fonctionnant correctement avec une utilisation CPU de 0,5 Go et 1 Go de RAM, et seules quelques-unes nécessitant une configuration moyenne ou grande. Voyons ce qui se passe si 60 % de nos charges de travail s'exécutent sur "SMALL", 30 % sur "MEDIUM" et 10 % sur "LARGE", ce qui est plus proche de la réalité.</p><p>Métrique</p><p>Scénario A (essaim mixte)</p><p>Scénario B (exécution de longue durée)</p><p>Stratégie</p><p>8 000 pods (tailles variées)</p><p>1 pod (8 vCPU / 16 Go)*</p><p>Durée</p><p>10 minutes chacun</p><p>12 jours en continu</p><p>Coût total</p><p>52,66 $</p><p>113,75 $</p><p>Économies</p><p>61,09 $ (54 % moins cher)</p><p>—</p><p>Nous pouvons constater que, pour le même volume, nous sommes beaucoup plus rentables dans notre configuration actuelle.</p><h3><strong>Sécurité renforcée</strong></h3><ul><li><p>Jetons GitHub éphémères (minutes d'exposition contre plusieurs jours).</p></li><li><p>Isolation d'espace de nom avec limites de contrôle d'accès basé sur les rôles (RBAC).</p></li><li><p>Chiffrement des secrets au repos dans les workflows parents.</p></li><li><p>Accès direct au coffre-fort supprimé.</p></li></ul><h3><strong>Performance prévisible</strong></h3><p>Grâce à une fréquence d'analyse garantie, nous pouvons enfin définir des objectifs de niveau de service (SLO). La fusion automatique fonctionne de manière fiable. Les équipes ont confiance en la plateforme pour tenir ses promesses.</p><h2><strong>Décisions architecturales clés</strong></h2><p>Voici quelques-unes des décisions de conception majeures qui ont façonné l'apparence de la plateforme.</p><ul><li><p><strong>Pourquoi des workflows parent-enfant ?</strong></p></li></ul><p>Nous avons adopté ce modèle pour mettre en œuvre une stratégie de <strong>défense en profondeur.</strong> En limitant les identifiants sensibles (tels que les secrets d'applications GitHub) à un espace de noms dédié et sécurisé, nous utilisons le <strong>RBAC</strong> pour garantir que les pods d'exécution éphémères ne puissent pas accéder arbitrairement aux données sensibles. De récentes vulnérabilités de la chaîne d'approvisionnement (par exemple, les attaques <strong>"Shai Hulud"</strong> ciblant l'intégration continue et le déploiement continu [CI/CD]) ont démontré l'importance cruciale d'isoler les environnements d'exécution qui exécutent des scripts dynamiques depuis le magasin d'identifiants.</p><p>Simultanément, ce découplage permet <strong>une optimisation granulaire des ressources</strong>. Les workflows "parents" agissent comme des orchestrateurs légers avec un encombrement minimal, tandis que les workflows "enfants" gèrent l'analyse des dépendances gourmande en ressources IT. Cette séparation simplifie la <strong>gestion du cycle de vie</strong> en nous permettant d'appliquer une logique de rapprochement distincte à chaque couche, offrant ainsi aux utilisateurs le contrôle des paramètres d'exécution (enfants) tout en conservant le contrôle administratif sur l'infrastructure de planification et de sécurité (parents).</p><ul><li><p><strong>Pourquoi en libre-service ?</strong></p></li></ul><p>Il était essentiel d'éliminer notre équipe comme goulot d'étranglement pour la configuration des référentiels. Notre mission était de concevoir une plateforme scalable et <strong>en libre-service</strong>, capable de prendre en charge divers cas d'utilisation. Nous avons constaté qu'il était impossible, compte tenu du nombre considérable de référentiels, de jouer le rôle de <strong>contrôleur d'accès</strong> pour chaque modification de configuration. Nous avons donc adopté une approche axée sur l'enablement : fournir les "rails" (l'infrastructure et les <strong>garde-fous</strong>), tout en donnant aux utilisateurs les moyens d'agir et de conduire les "trains" (l'exécution et la personnalisation). Nous pensons que cette évolution vers l'<strong>autonomie des équipes</strong> améliore considérablement la productivité en permettant aux utilisateurs d'adapter le système à leurs besoins opérationnels spécifiques.</p><ul><li><p><strong>Pourquoi le modèle d'opérateur Kubernetes ?</strong></p></li></ul><p>Comme indiqué précédemment, un principe de conception fondamental consistait à garantir que la plateforme soit entièrement <strong>en libre-service</strong>. Nous avions besoin d'un mécanisme automatisé pour capturer les intentions de l'utilisateur (par exemple, activer/désactiver les analyses, ajuster la fréquence de planification ou paramétrer les limites des ressources d'exécution) et transmettre instantanément ces modifications aux workflows sous-jacents. Afin d'anticiper les besoins futurs, le système devait aussi être facilement <strong>extensible</strong>.</p><p>Pour ce faire, nous avons développé un <strong>opérateur Kubernetes de gestion des dépendances</strong> personnalisé. En utilisant les <strong>CRD</strong> comme interface de configuration, nous avons établi une <strong>boucle de rapprochement native Kubernetes</strong>. Cet opérateur surveille en permanence l'état souhaité défini par l'utilisateur et orchestre automatiquement les mises à jour nécessaires de l'infrastructure de workflow. Ceci garantit un fonctionnement transparent et <strong>piloté par les événements</strong>, où la logique de la plateforme gère toute la complexité en arrière-plan.</p><ul><li><p><strong>Pourquoi concevoir une passerelle d’événements GitHub ?</strong></p></li></ul><p>L'adoption d'une <strong>architecture pilotée par les événements (EDA)</strong> était essentielle à la réactivité de la plateforme. Si les workflows Cron fournissaient une planification de base fiable, nous avions besoin d'agilité pour gérer les <strong>exécutions ad hoc</strong>, comme le déclenchement manuel d'analyses par les utilisateurs via le tableau de bord. Pour ce faire, nous avions besoin d'une <strong>passerelle d'ingestion</strong> dédiée afin de valider l'intégrité des données et acheminer intelligemment les requêtes.</p><p>Nous avons évalué les solutions existantes, notamment l'EventSource natif de GitHub pour Argo, mais nous avons identifié des risques importants liés à la <strong>surcharge opérationnelle</strong> et aux <strong>quotas stricts de l'API GitHub</strong> (par exemple, les limites de webhook par référentiel). Par conséquent, nous avons développé une passerelle personnalisée afin de découpler notre infrastructure de ces limitations.</p><p>Cette passerelle s'est avérée cruciale en tant que <strong>point de contrôle du trafic</strong> lors de notre migration. Elle a agi comme un commutateur, nous permettant d'effectuer un <strong>déploiement progressif et granulaire</strong> (transfert de trafic) de l'ancien système vers la nouvelle infrastructure. Ainsi, l'intégration de milliers de référentiels s'est déroulée de manière contrôlée et sans risque, plutôt que par une transition brutale.</p><p></p><h2><strong>Enseignements</strong></h2><p>Certains enseignements que nous avons tirés vont de pair avec le <a href="https://www.elastic.co/about/our-source-code">code source d'Elastic</a> :</p><ol><li><p><strong>Priorité au client</strong> : les plateformes sont conçues pour les utilisateurs. Il est donc essentiel de placer leurs besoins au cœur de nos priorités. Cela permet de concevoir une infrastructure et des applications performantes qui réduisent les obstacles pour les utilisateurs, simplifient le scaling de la plateforme et facilitent son adoption.</p></li><li><p><strong>Espace, temps</strong> : parfois, la voie de la facilité mène à des <strong>sables mouvants</strong>. Nous avons d'abord tenté d'optimiser le modèle de traitement séquentiel existant, mais cela n'a pas résolu nos problèmes ; au contraire, cela n'a fait qu'accroître la complexité et créer des zones d'ombre. La décision audacieuse de <strong>repenser l'architecture</strong> de la plateforme avec un traitement parallèle a nécessité un investissement initial important. Cependant, elle a finalement ouvert la voie à une croissance durable de la plateforme et a quasiment éliminé les tâches administratives quotidiennes fastidieuses.</p></li><li><p><strong>Informatique, dépendances</strong> : une plateforme ne peut pas fonctionner de manière isolée ; son succès dépend de la manière dont elle s'intègre dans un écosystème plus large. Dans notre cas, l'intégration à <strong>Backstage</strong> était essentielle, car elle constitue la source de référence pour une intégration fluide des services. De même, la connexion à <strong>Artifactory</strong> nous a permis de gérer efficacement les mises à jour des packages privés, et la liste des intégrations essentielles ne s'arrête pas là.</p></li><li><p><strong>Progrès, perfection SIMPLE</strong> : tout au long de la mise en œuvre, nous avons constamment testé nos hypothèses initiales et nous nous sommes adaptés aux nouveaux obstacles à mesure qu'ils apparaissaient. Plutôt que de nous laisser paralyser par le perfectionnisme, nous avons adopté une <strong>approche itérative</strong>, en relevant les défis les uns après les autres et en ajustant notre stratégie de migration aux conditions réelles.</p></li></ol><h2><strong>Prochaines étapes</strong></h2><p>La mise en place de la plateforme nous permet de nous consacrer à des tâches plus importantes qui contribueront à améliorer l'expérience utilisateur et l'efficacité de notre plateforme. En voici quelques exemples :
</p><ul><li><p><strong>Renforcer et garantir l'adoption de la fusion automatique</strong></p></li></ul><p>La fonctionnalité de fusion automatique accélère considérablement la rapidité de l'équipe en éliminant les tâches manuelles fastidieuses. Toutefois, il est essentiel de mettre en place des <strong>garde-fous</strong> stricts afin de garantir que cette rapidité accrue ne s'obtienne pas au détriment de la sécurité.
</p><ul><li><p><strong>Améliorer la visibilité sur l'expérience des utilisateurs finaux</strong></p></li></ul><p>L'une des priorités essentielles de notre feuille de route est l'amélioration de l'observabilité, non seulement au niveau de la plateforme, mais aussi <strong>du point de vue de l'utilisateur final</strong>. Si la collecte des indicateurs d'infrastructure est simple, la compréhension de l'expérience utilisateur réelle exige une analyse plus approfondie. Nous travaillons à la définition d'indicateurs clés de performance (KPI) centrés sur l'utilisateur afin que notre système de télémétrie puisse détecter les points de friction et les problèmes de performance <strong>avant</strong> qu'ils ne se transforment en plaintes d'utilisateurs.</p><ul><li><p><strong>Supprimer les obstacles à une plus grande adoption</strong></p></li></ul><p>Pour l'avenir, notre priorité est d'identifier et de lever les obstacles à l'adoption de la plateforme. Qu'il s'agisse de développer de nouvelles intégrations ou de déployer des fonctionnalités spécifiques, nous privilégions une planification fondée sur les données. Nous avons développé avec succès une plateforme conçue pour évoluer ; notre objectif est désormais <strong>d'en maximiser le potentiel</strong>.
</p><h2><strong>Le tableau d'ensemble</strong></h2><p>Le projet de workflows de gestion des dépendances illustre un principe plus large : <strong>lorsque vous devez scaler des outils open source au-delà de leur modèle de déploiement par défaut, les modèles natifs Kubernetes offrent une voie à suivre.</strong></p><p>En adoptant :</p><ul><li><p>Les CRD pour la configuration.</p></li><li><p>Les opérateurs pour la gestion du cycle de vie.</p></li><li><p>Une architecture basée sur les événements pour une meilleure réactivité</p></li><li><p>GitOps pour le déploiement.</p></li></ul><p>Nous avons conçu une orchestration qui scale indépendamment du nombre de référentiels gérés. Les performances d'analyse d'un seul référentiel restent identiques, que nous en gérions 100 ou 1 000.</p><p>Lorsqu'une CVE critique est annoncée, nous obtenons désormais des réponses en quelques minutes, et non plus en quelques heures. C'est ce qui fait la différence entre un goulot d'étranglement et un avantage concurrentiel.</p><h2><strong>Remerciements</strong></h2><p>Cette plateforme s'appuie sur d'excellents outils open source :</p><ul><li><p><strong>Kubebuilder</strong> : le framework open source que nous avons utilisé pour lancer nos opérateurs Kubernetes qui démarrent et orchestrent nos workflows. [<a href="https://github.com/kubernetes-sigs/kubebuilder">1</a>][<a href="https://book.kubebuilder.io/">2</a>]</p></li><li><p><strong>Backstage</strong> : le framework open source sur lequel nous avons construit notre catalogue de services et que nous utilisons comme source de référence. [<a href="https://github.com/backstage/backstage">1</a>][<a href="https://backstage.io/">2</a>]</p></li><li><p><strong>Argo Workflows et Argo Events</strong> : la suite open source que nous avons utilisée pour orchestrer des processus complexes et ajouter un traitement dynamique basé sur des événements. [1][<a href="https://argo-workflows.readthedocs.io/en/stable/">2</a>][<a href="https://argoproj.github.io/argo-events/">3</a>][<a href="https://github.com/argoproj/argo-events">4</a>]</p></li><li><p><strong>Renovate CLI</strong> : l'outil open source de gestion des dépendances qui traite nos référentiels. [<a href="https://github.com/renovatebot/renovate">1</a>][<a href="https://docs.renovatebot.com/getting-started/running/">2</a>]</p></li></ul><p>* Le modèle de tarification AWS Fargate a été utilisé comme référence pour le coût d'un seul pod, bien que nos charges de travail ne s'exécutent pas nécessairement sur AWS et s'exécutent sur des clusters Kubernetes complets.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/dependency-management-kubernetes</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/dependency-management-kubernetes</guid>
    <category><![CDATA[Expérience développeur]]></category>
    <dc:creator><![CDATA[Nikos Fotiou]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6033995d6660d149/6a170eb5839dfa63f1dcff9b/00519840e6eec7101c1fb096afcae976ee0c454e-1280x720.png" length="0" type="image/png"/>
    <pubDate>Thu, 19 Feb 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Présentation de l'interface utilisateur des règles de requête Elasticsearch dans Kibana]]></title>
    <description><![CDATA[Découvrez comment utiliser l'interface utilisateur Elasticsearch Query Rules pour ajouter ou exclure des documents des requêtes de recherche à l'aide d'ensembles de règles personnalisables dans Kibana, sans affecter le classement organique.]]></description>
    <content:encoded><![CDATA[<p>Le rôle d'un moteur de recherche est de renvoyer des résultats pertinents. Cependant, certains besoins professionnels vont au-delà, comme la mise en évidence des ventes, la priorité donnée aux produits saisonniers ou la présentation d'articles sponsorisés, et les développeurs ne peuvent pas toujours le faire dans la requête de recherche.</p><p>En outre, ces cas d'utilisation sont généralement sensibles au temps, et passer par les étapes de développement habituelles (créer une branche de code et attendre une nouvelle version) est un processus qui prend beaucoup de temps.</p><p>Et si nous pouvions réaliser l'ensemble de ce processus par un simple appel d'API ou, mieux encore, en quelques clics dans Kibana ?</p><h2>Règles d'interrogation</h2><p>Elasticsearch 8.10 a introduit les <a href="https://www.elastic.co/blog/introducing-query-rules-elasticsearch-8-10"><strong>règles de requête</strong></a> et le <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers/rule-retriever"><strong>récupérateur de règles</strong></a>. Il s'agit d'outils conçus pour injecter des <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-pinned-query"><em>résultats épinglés</em></a> dans les requêtes sans affecter le classement des résultats organiques sur la base de règles. Ils ne font qu'ajouter une logique d'entreprise aux résultats d'une manière simple et déclarative.</p><p>Voici quelques exemples d'utilisation courante des règles de requête :</p><ul><li><p><strong>Mise en évidence des annonces ou des ventes promues</strong>: Afficher les articles en vente ou sponsorisés en haut de la page.</p></li><li><p><strong>Exclusion en fonction du contexte ou de la géolocalisation</strong>: Masquer certains éléments lorsque la réglementation locale ne permet pas de les afficher.</p></li><li><p><strong>Donner la priorité aux résultats clés</strong>: Veiller à ce que les recherches populaires ou fixes soient toujours en tête, quel que soit le classement organique.</p></li></ul><p>Pour accéder à l'interface et interagir avec ces outils, vous devez cliquer sur le menu latéral de Kibana et aller dans <strong>Règles de requête</strong>, sous <strong>Pertinence</strong>:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltac12541cddd58e36/6a170853a29299941cd00fc2/242e33e89d1a07ffa0e76009c46b3a9236722741-458x1010.png" alt="Accès aux règles de requête dans Elasticsearch sous pertinence" /><p>Lorsque le menu des règles de requête s'affiche, cliquez sur <strong>Créer votre premier jeu de règles :</strong></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcc28329c0f3c3aa9/6a17085547d49c67e22d893b/30b3a91bbbf243d314cf38298e01ca5cff784430-1600x945.png" alt="Créer son premier jeu de règles de requête dans Elasticsearch" /><p>Ensuite, vous devez nommer votre jeu de règles.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb37d271297a4f148/6a170856a29299782cd00fc6/26c5462f88678867776f933b5655ca0df0d72a16-708x446.png" alt="Nommer vos règles de requête ruleset dans Elasticsearch" /><p>Le formulaire permettant de définir chaque règle comporte trois éléments clés :</p><ul><li><p><strong>Critères</strong>: Les conditions qui doivent être remplies pour que la règle s'applique. Par exemple, "lorsque le champ query_string contient la valeur <em>Christmas</em>" ou "lorsque le champ country est <em>CO".</em></p></li><li><p><strong>Action</strong>: C'est ce que vous voulez qu'il se passe lorsque les conditions sont remplies. Il peut être épinglé (fixation d'un document dans les premiers résultats) ou exclu (masquage d'un document).</p></li><li><p><strong>Métadonnées</strong>: Il s'agit des champs qui accompagnent la requête lors de son exécution. Elles peuvent inclure des informations sur l'utilisateur (comme la localisation ou la langue) ainsi que des données de recherche (query_string). Il s'agit des valeurs utilisées par les critères pour décider d'appliquer ou non une règle.</p></li></ul><h2>Exemple : articles populaires</h2><p>Imaginons que nous ayons un site de commerce électronique proposant différents articles. En vérifiant les mesures, nous remarquons que l'un des articles les plus vendus dans la catégorie des consoles est la "manette sans fil DualShock 4", en particulier lorsque les utilisateurs recherchent les mots clés "PS4" ou "PlayStation 4". Nous décidons donc de placer ce produit en tête des résultats lorsqu'un utilisateur effectue une recherche avec ces mots-clés.</p><p>Tout d'abord, nous allons indexer les documents pour chaque article à l'aide d'une requête API en bloc :</p>POST _bulk
{ "index": { "_index": "products", "_id": "1" } }
{ "id": "1", "name": "PlayStation 4 Slim 1TB", "category": "console", "brand": "Sony", "price": 1200 }
{ "index": { "_index": "products", "_id": "2" } }
{ "id": "2", "name": "DualShock 4 Wireless Controller", "category": "accessory", "brand": "Sony", "price": 250 }
{ "index": { "_index": "products", "_id": "3" } }
{ "id": "3", "name": "PlayStation 4 Camera", "category": "accessory", "brand": "Sony", "price": 200 }
{ "index": { "_index": "products", "_id": "4" } }
{ "id": "4", "name": "PlayStation 4 VR Headset", "category": "accessory", "brand": "Sony", "price": 900 }
{ "index": { "_index": "products", "_id": "5" } }
{ "id": "5", "name": "Charging Station for DualShock 4", "category": "accessory", "brand": "Sony", "price": 80 }<p>Si nous n'intervenons pas dans la requête, l'article apparaît généralement en quatrième position. Voici la requête :</p>GET products/_search
{
 "query": {
   "match": {
     "name": "PlayStation 4"
   }
 }
}<p>Et voici les résultats</p>{
 "took": 1,
 "timed_out": false,
 "_shards": {
   "total": 1,
   "successful": 1,
   "skipped": 0,
   "failed": 0
 },
 "hits": {
   "total": {
     "value": 5,
     "relation": "eq"
   },
   "max_score": 0.6973252,
   "hits": [
     {
       "_index": "products",
       "_id": "3",
       "_score": 0.6973252,
       "_source": {
         "id": "3",
         "name": "PlayStation 4 Camera",
         "category": "accessory",
         "brand": "Sony",
         "price": 200
       }
     },
     {
       "_index": "products",
       "_id": "1",
       "_score": 0.6260078,
       "_source": {
         "id": "1",
         "name": "PlayStation 4 Slim 1TB",
         "category": "console",
         "brand": "Sony",
         "price": 1200
       }
     },
     {
       "_index": "products",
       "_id": "4",
       "_score": 0.6260078,
       "_source": {
         "id": "4",
         "name": "PlayStation 4 VR Headset",
         "category": "accessory",
         "brand": "Sony",
         "price": 900
       }
     },
     {
       "_index": "products",
       "_id": "2",
       "_score": 0.08701137,
       "_source": {
         "id": "2",
         "name": "DualShock 4 Wireless Controller",
         "category": "accessory",
         "brand": "Sony",
         "price": 250
       }
     },
     {
       "_index": "products",
       "_id": "5",
       "_score": 0.07893815,
       "_source": {
         "id": "5",
         "name": "Charging Station for DualShock 4",
         "category": "accessory",
         "brand": "Sony",
         "price": 80
       }
     }
   ]
 }
}<p>Créons une règle de requête pour modifier cela. Tout d'abord, ajoutons-le au jeu de règles comme suit :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1576d4f4a2e60548/6a170858cdacbfccb07d298d/fdc42646fb3e76a09bca7d19047a76efe343f7a2-1600x650.png" alt="Comment modifier un jeu de règles de requête dans Elasticsearch ?" /><p>Ou <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-query-rules-put-ruleset">demande d'API</a> équivalente :</p>PUT _query_rules/my-rules
{
  "rules": [
    {
      "rule_id": "rule-1232",
      "type": "pinned",
      "criteria": [
        {
          "type": "exact",
          "metadata": "query_string",
          "values": [
            "PS4",
            "PlayStation 4"
          ]
        }
      ],
      "actions": {
        "docs": [
          {
            "_index": "products",
            "_id": "2"
          }
        ]
      }
    }
  ]
}<p>Pour utiliser l'<strong>ensemble de règles </strong>dans notre requête, nous devons utiliser un type de règle de requête. Ce type de requête se compose de deux parties principales :</p>GET /products/_search
{
 "retriever": {
   "rule": {
     "retriever": {
       "standard": {
         "query": {
           "match": { "name": "PlayStation 4" }
         }
       }
     },
     "match_criteria": {
       "query_string": "PlayStation 4"
     },
     "ruleset_ids": ["my-rules"]
   }
 }
}<ul><li><p><strong>match_criteria</strong>: Il s'agit des métadonnées utilisées pour la comparaison avec la requête de l'utilisateur. Dans cet exemple, le jeu de règles est activé lorsque le champ query_string a la valeur "PlayStation 4".</p></li><li><p><strong>requête</strong>: la requête réelle qui sera utilisée pour effectuer la recherche et obtenir les résultats organiques.</p></li></ul><p>De cette façon, vous exécutez d'abord la requête organique, puis Elasticsearch applique les règles de votre ensemble de règles :</p>{
 "took": 17,
 "timed_out": false,
 "_shards": {
   "total": 1,
   "successful": 1,
   "skipped": 0,
   "failed": 0
 },
 "hits": {
   "total": {
     "value": 5,
     "relation": "eq"
   },
   "max_score": 1.7014122e+38,
   "hits": [
     {
       "_index": "products",
       "_id": "2",
       "_score": 1.7014122e+38,
       "_source": {
         "id": "2",
         "name": "DualShock 4 Wireless Controller",
         "category": "accessory",
         "brand": "Sony",
         "price": 250
       }
     },
     {
       "_index": "products",
       "_id": "3",
       "_score": 0.6973252,
       "_source": {
         "id": "3",
         "name": "PlayStation 4 Camera",
         "category": "accessory",
         "brand": "Sony",
         "price": 200
       }
     },
     {
       "_index": "products",
       "_id": "1",
       "_score": 0.6260078,
       "_source": {
         "id": "1",
         "name": "PlayStation 4 Slim 1TB",
         "category": "console",
         "brand": "Sony",
         "price": 1200
       }
     },
     {
       "_index": "products",
       "_id": "4",
       "_score": 0.6260078,
       "_source": {
         "id": "4",
         "name": "PlayStation 4 VR Headset",
         "category": "accessory",
         "brand": "Sony",
         "price": 900
       }
     },
     {
       "_index": "products",
       "_id": "5",
       "_score": 0.07893815,
       "_source": {
         "id": "5",
         "name": "Charging Station for DualShock 4",
         "category": "accessory",
         "brand": "Sony",
         "price": 80
       }
     }
   ]
 }
}<h2>Exemple : métadonnées basées sur l'utilisateur</h2><p>Une autre application intéressante des règles d'interrogation consiste à utiliser les métadonnées pour afficher des documents spécifiques sur la base d'informations contextuelles provenant de l'utilisateur ou de la page web.</p><p>Par exemple, imaginons que nous souhaitions mettre en avant des articles ou des ventes personnalisées en fonction du niveau de fidélité d'un utilisateur, représenté par une valeur numérique.</p><p>Nous pouvons le faire en intégrant ces métadonnées directement dans la requête, de sorte que les règles s'activent lorsque la valeur en question répond à certains critères.</p><p>Tout d'abord, nous allons indexer un document que seuls les utilisateurs ayant un niveau de fidélité élevé peuvent consulter :</p>POST _bulk
{ "index": { "_index": "products", "_id": "6" } }
{ "id": "6", "name": "PlayStation Plus Deluxe Card - 12 months", "category": "membership", "brand": "Sony", "price": 300 }<p>Maintenant, créons une nouvelle règle dans le même jeu de règles pour que lorsque le niveau de fidélité est égal ou supérieur à 80, l'article apparaisse en tête des résultats.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt158578005df8c76d/6a17085aab7f086dc0db9de3/58de12dff93305440608f51465462fcc68653a08-1421x496.png" alt="Comment modifier un jeu de règles de requête dans Elasticsearch ?" /><p>Enregistrez la règle et le jeu de règles.</p><p>Voici la requête REST équivalente :</p>PUT _query_rules/my-rules
{
  "rules": [
    {
      "rule_id": "pin-premiun-user",
      "type": "pinned",
      "criteria": [
        {
          "type": "gte",
          "metadata": "loyalty_level",
          "values": [
            80
          ]
        }
      ],
      "actions": {
        "docs": [
          {
            "_index": "products",
            "_id": "6"
          }
        ]
      }
    }
  ]
}<p>Désormais, lors de l'exécution d'une requête, nous devons inclure le nouveau paramètre <strong>loyalty_level </strong>dans les métadonnées. Si la condition de la règle est remplie, le nouveau document apparaît en tête des résultats.</p><p>Par exemple, lors de l'envoi d'une requête dont le niveau de fidélité est 80 :</p>POST /products/_search
{
  "retriever": {
    "rule": {
      "retriever": {
        "standard": {
          "query": {
            "match": {
              "name": "PlayStation"
            }
          }
        }
      },
      "match_criteria": {
        "query_string": "PlayStation",
        "loyalty_level": 80
      },
      "ruleset_ids": ["my-rules"]
    }
  }
}<p>Nous verrons le document de fidélisation en haut des résultats :</p>{
  "took": 31,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 4,
      "relation": "eq"
    },
    "max_score": 1.7014122e+38,
    "hits": [
      {
        "_index": "products",
        "_id": "6",
        "_score": 1.7014122e+38,
        "_source": {
          "id": "6",
          "name": "PlayStation Plus Deluxe Card - 12 months",
          "category": "membership",
          "brand": "Sony",
          "price": 300
        }
      },
      {
        "_index": "products",
        "_id": "3",
        "_score": 0.5054567,
        "_source": {
          "id": "3",
          "name": "PlayStation 4 Camera",
          "category": "accessory",
          "brand": "Sony",
          "price": 200
        }
      },
      {
        "_index": "products",
        "_id": "1",
        "_score": 0.45618832,
        "_source": {
          "id": "1",
          "name": "PlayStation 4 Slim 1TB",
          "category": "console",
          "brand": "Sony",
          "price": 1200
        }
      },
      {
        "_index": "products",
        "_id": "4",
        "_score": 0.45618832,
        "_source": {
          "id": "4",
          "name": "PlayStation 4 VR Headset",
          "category": "accessory",
          "brand": "Sony",
          "price": 900
        }
      }
    ]
  }
}<p>Dans le cas ci-dessous, le niveau de fidélité étant de 70, la règle n'est pas respectée et l'objet ne doit pas apparaître en haut de la liste :</p>POST /products/_search
{
  "retriever": {
    "rule": {
      "retriever": {
        "standard": {
          "query": {
            "match": {
              "name": "PlayStation"
            }
          }
        }
      },
      "match_criteria": {
        "query_string": "PlayStation",
        "loyalty_level": 70
      },
      "ruleset_ids": ["my-rules"]
    }
  }
}<p>Voici les résultats :</p>{
  "took": 7,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 4,
      "relation": "eq"
    },
    "max_score": 0.5054567,
    "hits": [
      {
        "_index": "products",
        "_id": "3",
        "_score": 0.5054567,
        "_source": {
          "id": "3",
          "name": "PlayStation 4 Camera",
          "category": "accessory",
          "brand": "Sony",
          "price": 200
        }
      },
      {
        "_index": "products",
        "_id": "1",
        "_score": 0.45618832,
        "_source": {
          "id": "1",
          "name": "PlayStation 4 Slim 1TB",
          "category": "console",
          "brand": "Sony",
          "price": 1200
        }
      },
      {
        "_index": "products",
        "_id": "4",
        "_score": 0.45618832,
        "_source": {
          "id": "4",
          "name": "PlayStation 4 VR Headset",
          "category": "accessory",
          "brand": "Sony",
          "price": 900
        }
      },
      {
        "_index": "products",
        "_id": "6",
        "_score": 0.3817649,
        "_source": {
          "id": "6",
          "name": "PlayStation Plus Deluxe Card - 12 months",
          "category": "membership",
          "brand": "Sony",
          "price": 300
        }
      }
    ]
  }
}<h2>Exemple : exclusion immédiate</h2><p>Supposons que notre <strong>manette sans fil DualShock 4 (ID 2)</strong> soit temporairement indisponible et ne puisse être vendue. Ainsi, au lieu de supprimer manuellement le document ou d'attendre qu'un processus de données se mette en place, l'équipe commerciale décide de le supprimer des résultats de recherche en attendant.</p><p>Nous utiliserons un processus similaire à celui que nous venons d'appliquer aux articles populaires, mais cette fois-ci, au lieu de sélectionner <em>Épinglé</em>, nous choisirons <em>Exclure</em>. Cette règle fonctionne comme une sorte de liste noire. Changez les critères en <strong>Toujours</strong> pour que l'exclusion fonctionne à chaque fois que la requête est exécutée.</p><p>La règle devrait ressembler à ceci :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt38564c0b7f4a6ee2/6a17085c1949f78692e7a989/f10971e4f1bc9520105111adfa3a476581a27130-1600x623.png" alt="Exemple d'un jeu de règles d'exclusion immédiate dans Elasticsearch" /><p>Enregistrez la règle et le jeu de règles pour appliquer les modifications. Voici la requête REST équivalente :</p>PUT _query_rules/my-rules
{
  "rules": [
    {
      "rule_id": "rule-6358",
      "type": "pinned",
      "criteria": [
        {
          "type": "always"
        }
      ],
      "actions": {
        "docs": [
          {
            "_index": "products",
            "_id": "2"
          }
        ]
      }
    }
  ]
}<p>Maintenant, lorsque nous exécutons à nouveau la requête, vous verrez que l'élément ne figure plus dans les résultats, bien que la règle préalable soit de l'épingler. En effet, <strong>les exclusions ont la priorité sur les résultats de l'épinglage</strong>.</p>{
 "took": 6,
 "timed_out": false,
 "_shards": {
   "total": 1,
   "successful": 1,
   "skipped": 0,
   "failed": 0
 },
 "hits": {
   "total": {
     "value": 4,
     "relation": "eq"
   },
   "max_score": 2.205655,
   "hits": [
     {
       "_index": "products",
       "_id": "3",
       "_score": 2.205655,
       "_source": {
         "id": "3",
         "name": "PlayStation 4 Camera",
         "category": "accessory",
         "brand": "Sony",
         "price": 200
       }
     },
     {
       "_index": "products",
       "_id": "1",
       "_score": 1.9738505,
       "_source": {
         "id": "1",
         "name": "PlayStation 4 Slim 1TB",
         "category": "console",
         "brand": "Sony",
         "price": 1200
       }
     },
     {
       "_index": "products",
       "_id": "4",
       "_score": 1.9738505,
       "_source": {
         "id": "4",
         "name": "PlayStation 4 VR Headset",
         "category": "accessory",
         "brand": "Sony",
         "price": 900
       }
     },
     {
       "_index": "products",
       "_id": "5",
       "_score": 0.69247496,
       "_source": {
         "id": "5",
         "name": "Charging Station for DualShock 4",
         "category": "accessory",
         "brand": "Sony",
         "price": 80
       }
     }
   ]
 }
}<h2>Conclusion</h2><p>Les <strong>règles de requête</strong> permettent d'ajuster très facilement la pertinence sans modifier le code. La nouvelle interface <strong>utilisateur</strong> <strong>Kibana </strong>vous permetd'effectuer ces modifications en quelques secondes, ce qui vous donne, ainsi qu'à votre équipe commerciale, un meilleur contrôle sur vos résultats de recherche.</p><p>Au-delà du commerce électronique, les règles de requête peuvent servir à de nombreux autres scénarios : mise en évidence des guides de dépannage dans les portails d'assistance, mise en évidence des documents internes clés dans les bases de connaissances, promotion des dernières nouvelles dans les sites d'information ou filtrage des offres d'emploi ou des listes de contenu expirées. Ils peuvent même appliquer des règles de conformité, par exemple en masquant les documents à diffusion restreinte en fonction du rôle de l'utilisateur ou de la région.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-query-rules-ui-introduction</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-query-rules-ui-introduction</guid>
    <category><![CDATA[Les bases]]></category>
    <category><![CDATA[Expérience développeur]]></category>
    <dc:creator><![CDATA[Jhon Guzmán]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt565ed0eb407e098d/6a17085d8b73cb363d189fb1/1fb10bd31c509cc9b9bb4f71f49970f140e6c36f-1600x945.png" length="0" type="image/png"/>
    <pubDate>Fri, 07 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Construire un agent de connaissance avec rappel sémantique en utilisant Mastra et Elasticsearch]]></title>
    <description><![CDATA[Apprenez à construire un agent de connaissance avec rappel sémantique en utilisant Mastra et Elasticsearch comme magasin vectoriel pour la mémoire et la recherche d'informations.]]></description>
    <content:encoded><![CDATA[<p>L'<a href="https://www.elastic.co/search-labs/blog/context-engineering-overview">ingénierie contextuelle</a> devient de plus en plus importante dans la construction d'agents et d'architectures d'IA fiables. Au fur et à mesure que les modèles s'améliorent, leur efficacité et leur fiabilité dépendent moins de leurs données d'entraînement que de leur ancrage dans le bon contexte. Les agents qui peuvent récupérer et appliquer les informations les plus pertinentes au bon moment sont beaucoup plus susceptibles de produire des résultats précis et fiables.</p><p>Dans ce blog, nous utiliserons <a href="https://mastra.ai/">Mastra</a> pour construire un agent de connaissance qui se souvient de ce que les utilisateurs disent et peut rappeler les informations pertinentes plus tard, en utilisant Elasticsearch comme mémoire et backend de récupération. Vous pouvez facilement étendre ce même concept à des cas d'utilisation réels, comme des agents d'assistance qui peuvent se souvenir de conversations et de résolutions antérieures, ce qui leur permet d'adapter les réponses à des utilisateurs spécifiques ou de trouver des solutions plus rapidement en fonction du contexte antérieur.</p><p>Suivez ici les étapes de sa construction. Si vous vous perdez ou si vous voulez simplement exécuter un exemple fini, consultez le repo <a href="https://github.com/jdarmada/getting-started-mastra-elastic/tree/main">ici.</a></p><h2>Qu'est-ce que Mastra ?</h2><p>Mastra est un framework TypeScript open-source pour la construction d'agents d'intelligence artificielle avec des parties interchangeables pour le raisonnement, la mémoire et les outils. Sa fonction de <a href="https://mastra.ai/docs/memory/semantic-recall">rappel sémantique</a> permet aux agents de se souvenir des interactions passées et de les retrouver en stockant les messages sous forme d'enchâssements dans une base de données vectorielle. Cela permet aux agents de conserver le contexte et la continuité de la conversation à long terme. Elasticsearch est un excellent magasin de vecteurs pour activer cette fonctionnalité, car il prend en charge la recherche vectorielle dense efficace. Lorsque le rappel sémantique est déclenché, l'agent introduit les messages antérieurs pertinents dans la fenêtre contextuelle du modèle, ce qui permet à ce dernier d'utiliser le contexte récupéré comme base de son raisonnement et de ses réponses.</p><h2>Ce qu'il faut pour commencer</h2><ul><li><p>Node v18+</p></li><li><p>Elasticsearch (version 8.15 ou plus récente)</p></li><li><p>Clé API Elasticsearch</p></li><li><p><a href="https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key">Clé API OpenAI</a></p></li></ul><p>Note : Vous en aurez besoin parce que la démo utilise le fournisseur OpenAI, mais Mastra prend en charge d'autres SDK d'IA et fournisseurs de modèles communautaires, vous pouvez donc facilement l'échanger en fonction de votre configuration.</p><h2>Construire un projet Mastra</h2><p>Nous utiliserons le CLI intégré de Mastra pour fournir l'échafaudage de notre projet. Exécutez la commande :</p>npm create mastra@latest<p>Vous obtiendrez une série d'invites, commençant par :</p><p>1. Donnez un nom à votre projet.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt87f941f654d03827/6a16f7af67045b214d45bfa1/2b9fe559e0276140dd539e24f916a73c60870405-620x84.png" alt="Nommer une invite dans l'application Mastra" /><p>2. Nous pouvons conserver cette valeur par défaut ; n'hésitez pas à la laisser vide.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7dbb3d4f27435cac/6a16f7b0cdacbf29497d27de/e04729eb03bce8499e973e18c28642402340d0e5-852x68.png" alt="Indiquer à mastra où conserver les fichiers d'invite" /><p>3. Pour ce projet, nous utiliserons un modèle fourni par OpenAI.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1f654f6cb9397e94/6a16f7b2964cea899a08b942/a86596a469a71bdf8bd99cbaf528d0f0cf7272c0-436x222.png" alt="Sélection d'un modèle fourni par OpenAI dans Mastra" /><p>4. Sélectionnez l'option "Skip for now" car nous allons stocker toutes nos variables d'environnement dans un fichier `.env` que nous configurerons plus tard.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltff106117521a3519/6a16f7b3c1e8a5031af880d8/02b19ccc34af0bdacf52fd94b519d036540ca2e6-426x114.png" alt="Sélectionner l'option &quot;ignorer pour l'instant&quot; pour la clé OpenAI" /><p>5. Nous pouvons également ignorer cette option.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcda7d9c51c3878d7/6a16f7b450916809dbe1b892/b3fe63d19d270bc2e0de1dd92033bf8b26750819-990x208.png" alt="" /><p>Une fois l'initialisation terminée, nous pouvons passer à l'étape suivante.</p><h3>Installation des dépendances</h3><p>Ensuite, nous devons installer quelques dépendances :</p>npm install ai @ai-sdk/openai @elastic/elasticsearch dotenv<ul><li><p><code>ai</code> - Ensemble de SDK d'IA de base qui fournit des outils pour gérer les modèles d'IA, les invites et les flux de travail en JavaScript/TypeScript. Mastra est construit sur le <a href="https://ai-sdk.dev/">SDK AI</a> de Vercel, nous avons donc besoin de cette dépendance pour permettre les interactions du modèle avec votre agent.</p></li><li><p><code>@ai-sdk/openai</code> - Plugin qui connecte le SDK AI aux modèles OpenAI (comme GPT-4, GPT-4o, etc.), permettant des appels API en utilisant votre clé API OpenAI.</p></li><li><p><code>@elastic/elasticsearch</code> - <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/javascript">Client Elasticsearch officiel pour Node.js</a>, utilisé pour se connecter à votre Elastic Cloud ou à votre cluster local pour l'indexation, la recherche et les opérations vectorielles.</p></li><li><p><code>dotenv</code> - Charge les variables d'environnement à partir d'un fichier .env dans le fichier process.env, vous permettant d'injecter en toute sécurité des informations d'identification telles que des clés d'API et des points d'extrémité Elasticsearch.</p></li></ul><h3>Configuration des variables d'environnement</h3><p>Créez un fichier <code>.env</code> dans le répertoire racine de votre projet si vous n'en avez pas déjà un. Vous pouvez également copier et renommer l'exemple <code>.env</code> que j'ai fourni dans le <a href="https://github.com/jdarmada/getting-started-mastra-elastic/blob/main/.env.example">répertoire.</a> Dans ce fichier, nous pouvons ajouter les variables suivantes :</p>ELASTICSEARCH_ENDPOINT="your-endpoint-here"
ELASTICSEARCH_API_KEY="your-key-here"
OPENAI_API_KEY="your-key-here"<p>Voilà qui conclut la configuration de base. À partir de là, vous pouvez déjà commencer à construire et à orchestrer des agents. Nous allons aller plus loin et ajouter Elasticsearch en tant que couche de stockage et de recherche vectorielle.</p><h2>Ajouter Elasticsearch comme magasin de vecteurs</h2><p>Créez un nouveau dossier appelé <code>stores</code> et ajoutez-y ce <a href="https://github.com/jdarmada/getting-started-mastra-elastic/blob/main/src/mastra/stores/elastic-store.ts">fichier</a>. Avant que Mastra et Elastic ne proposent une intégration officielle de Elasticsearch vector store, <a href="https://github.com/abhiaiyer91">Abhi Aiyer</a>(Mastra CTO) a partagé ce prototype de classe appelé <code>ElasticVector</code>. Simplement, il relie l'abstraction mémoire de Mastra aux capacités vectorielles denses d'Elasticsearch, de sorte que les développeurs peuvent utiliser Elasticsearch comme base de données vectorielle pour leurs agents.</p><p>Examinons plus en détail les éléments importants de l'intégration :</p><h3>Ingestion du client Elasticsearch</h3><p>Cette section définit la classe <code>ElasticVector</code> et met en place la connexion du client Elasticsearch avec un support pour les déploiements standards et sans serveur.</p>export interface ElasticVectorConfig extends ClientOptions {
    /**
     * Explicitly specify if connecting to Elasticsearch Serverless.
     * If not provided, will be auto-detected on first use.
     */
    isServerless?: boolean;
    
    /**
     * Maximum documents to count accurately when describing indices.
     * Higher values provide accurate counts but may impact performance on large indices.
     * 
     * @default 10000
     */
    maxCountAccuracy?: number;
}

export class ElasticVector extends MastraVector {
    private client: Client;
    private isServerless: boolean | undefined;
    private deploymentChecked: boolean = false;
    private readonly maxCountAccuracy: number;

    constructor(config: ElasticVectorConfig) {
        super();
        this.client = new Client(config);
        this.isServerless = config.isServerless;
        this.maxCountAccuracy = config.maxCountAccuracy ?? 10000;
    }
}<ul><li><p><code>ElasticVectorConfig extends ClientOptions</code>: Ceci crée une nouvelle interface de configuration qui hérite de toutes les options du client Elasticsearch (comme <code>node</code>, <code>auth</code>, <code>requestTimeout</code>) et ajoute nos propriétés personnalisées. Cela signifie que les utilisateurs peuvent passer n'importe quelle configuration Elasticsearch valide avec nos options spécifiques au serveur.</p></li><li><p><code>extends MastraVector</code>: Cela permet à <code>ElasticVector</code> d'hériter de la classe de base <code>MastraVector</code> de Mastra, qui est une interface commune à laquelle se conforment toutes les intégrations de magasins vectoriels. Cela garantit qu'Elasticsearch se comporte comme n'importe quel autre backend vectoriel Mastra du point de vue de l'agent.</p></li><li><p><code>private client: Client</code>: Il s'agit d'une propriété privée qui contient une instance du client JavaScript Elasticsearch. Cela permet à la classe de s'adresser directement à votre cluster.</p></li><li><p><code>isServerless</code> et <code>deploymentChecked</code>: Ces propriétés fonctionnent ensemble pour détecter et mettre en cache si nous sommes connectés à un déploiement Elasticsearch standard ou sans serveur. Cette détection se fait automatiquement lors de la première utilisation ou peut être configurée explicitement.</p></li><li><p><code>constructor(config: ClientOptions)</code>: Ce constructeur prend un objet de configuration (contenant vos identifiants Elasticsearch et des paramètres serverless optionnels) et l'utilise pour initialiser le client dans la ligne <code>this.client = new Client(config)</code>.</p></li><li><p><code>super()</code>: Il appelle le constructeur de base de Mastra, ce qui lui permet d'hériter de la journalisation, des aides à la validation et d'autres crochets internes.</p></li></ul><p>À ce stade, Mastra sait qu'il existe un nouveau magasin de vecteurs appelé <code>ElasticVector</code></p><h3>Détection du type de déploiement</h3><p>Avant de créer des index, l'adaptateur détecte automatiquement si vous utilisez Elasticsearch standard ou Elasticsearch Serverless. C'est important car les déploiements sans serveur ne permettent pas la configuration manuelle des shards.</p>private async detectServerless(): Promise&lt;boolean&gt; {
    // Return cached result if already detected
    if (this.deploymentChecked) {
        return this.isServerless ?? false;
    }

    // Use explicit configuration if provided
    if (this.isServerless !== undefined) {
        this.deploymentChecked = true;
        this.logger?.info(
            `Using explicit deployment type: ${this.isServerless ? 'Serverless' : 'Standard'}`
        );
        return this.isServerless;
    }

    try {
        const info = await this.client.info();
        
        // Primary detection: build flavor (most reliable)
        const isBuildFlavorServerless = info.version?.build_flavor === 'serverless';
        
        // Secondary detection: tagline (fallback)
        const isTaglineServerless = info.tagline?.toLowerCase().includes('serverless') ?? false;
        
        this.isServerless = isBuildFlavorServerless || isTaglineServerless;
        this.deploymentChecked = true;
        
        this.logger?.info(
            `Auto-detected ${this.isServerless ? 'Serverless' : 'Standard'} Elasticsearch deployment`,
            { 
                buildFlavor: info.version?.build_flavor, 
                version: info.version?.number,
                detectionMethod: isBuildFlavorServerless ? 'build_flavor' : 'tagline'
            }
        );
        
        return this.isServerless;
    } catch (error) {
        this.logger?.warn(
            'Could not auto-detect deployment type, assuming Standard Elasticsearch. ' +
            'Set isServerless: true explicitly in config if using Serverless.',
            { error: error instanceof Error ? error.message : String(error) }
        );
        this.isServerless = false;
        this.deploymentChecked = true;
        return false;
    }
}<p>Ce qui se passe :</p><ul><li><p>Vérifie d'abord si vous avez explicitement défini <code>isServerless</code> dans la configuration (ignore l'autodétection).</p></li><li><p>Appelle l'API <code>info()</code> d'Elasticsearch pour obtenir des informations sur les clusters.</p></li><li><p>Vérifie le <code>build_flavor field</code> (les déploiements sans serveur renvoient <code>serverless</code>).</p></li><li><p>Renvoie à la vérification du slogan si la saveur de la construction n'est pas disponible</p></li><li><p>Met en cache le résultat afin d'éviter les appels répétés à l'API</p></li><li><p>Déploiement standard par défaut en cas d'échec de la détection</p></li></ul><p> Exemple d'utilisation :</p>// Option 1: Auto-detect (recommended)
const vector = new ElasticVector({
    node: 'https://your-cluster.es.cloud',
    auth: { apiKey: 'your-api-key' }
});
// Detection happens automatically on first index operation

// Option 2: Explicit configuration (faster startup)
const vector = new ElasticVector({
    node: 'https://your-serverless.es.cloud',
    auth: { apiKey: 'your-api-key' },
    isServerless: true  // Skips auto-detection
});<h3>Création du magasin "memory" dans Elasticsearch</h3><p>La fonction ci-dessous met en place un index Elasticsearch pour le stockage des embeddings. Il vérifie si l'index existe déjà. Si ce n'est pas le cas, il en crée un avec le mappage ci-dessous qui contient un champ <code>dense_vector</code> pour stocker les embeddings et les métriques de similarité personnalisées.</p><p>Quelques points à noter :</p><ul><li><p>Le paramètre <code>dimension</code> est la longueur de chaque vecteur d'intégration, qui dépend du modèle d'intégration utilisé. Dans notre cas, nous allons générer des embeddings en utilisant le modèle <code>text-embedding-3-small</code> d'OpenAI, qui produit des vecteurs de taille <code>1536</code>. Nous l'utiliserons comme valeur par défaut.</p></li><li><p>La variable <code>similarity</code> utilisée dans la correspondance ci-dessous est définie à partir de la fonction d'aide c<code>onst similarity = this.mapMetricToSimilarity(metric)</code>, qui prend la valeur du paramètre <code>metric</code> et la convertit en un mot-clé compatible avec Elasticsearch pour la métrique de distance choisie.</p><ul><li><p>Par exemple : Mastra utilise des termes généraux pour la similarité vectorielle comme <code>cosine</code>, <code>euclidean</code>, et <code>dotproduct</code>. Si nous devions passer la métrique <code>euclidean</code> directement dans le mappage Elasticsearch, une erreur se produirait car Elasticsearch s'attend à ce que le mot-clé <code>l2_norm</code> représente la distance euclidienne.</p></li></ul></li><li><p>Compatibilité sans serveur : Le code omet automatiquement les paramètres de shard et de réplique pour les déploiements sans serveur, car ils sont gérés automatiquement par Elasticsearch Serverless.</p></li></ul>async createIndex(params: CreateIndexParams): Promise&lt;void&gt; {
    const { indexName, dimension = 1536, metric = 'cosine' } = params;

    try {
        const exists = await this.client.indices.exists({ index: indexName });

        if (exists) {
            try {
                await this.validateExistingIndex(indexName, dimension, metric);
                this.logger?.info(`Index "${indexName}" already exists and is valid`);
                return;
            } catch (validationError) {
                throw new Error(
                    `Index "${indexName}" exists but does not match the required configuration: ${
                        validationError instanceof Error ? validationError.message : String(validationError)
                    }`
                );
            }
        }

        const isServerless = await this.detectServerless();
        const similarity = this.mapMetricToSimilarity(metric);

        const indexConfig: any = {
            index: indexName,
            mappings: {
                properties: {
                    vector: {
                        type: 'dense_vector',
                        dims: dimension,
                        index: true,
                        similarity: similarity,
                    },
                    metadata: {
                        type: 'object',
                        enabled: true,
                        dynamic: true, // Allows flexible metadata structures
                    },
                },
            },
        };

        // Only configure shards/replicas for non-serverless deployments
        // Serverless manages infrastructure automatically
        if (!isServerless) {
            indexConfig.settings = {
                number_of_shards: 1,
                number_of_replicas: 0, // Increase for production HA deployments
            };
        }

        await this.client.indices.create(indexConfig);

        this.logger?.info(
            `Created ${isServerless ? 'Serverless' : 'Standard'} Elasticsearch index "${indexName}"`,
            { dimension, metric, similarity }
        );
    } catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        this.logger?.error(`Failed to create index "${indexName}": ${errorMessage}`);
        throw new Error(`Failed to create index "${indexName}": ${errorMessage}`);
    }
}<h3>Enregistrement d'un nouveau souvenir ou d'une nouvelle note après une interaction</h3><p>Cette fonction prend les nouveaux embeddings générés après chaque interaction, ainsi que les métadonnées, puis les insère ou les met à jour dans l'index à l'aide de l'API <code>bulk</code> d'Elastic. L'API <code>bulk</code> regroupe plusieurs opérations d'écriture en une seule demande ; cette amélioration de nos performances d'indexation garantit que les mises à jour restent efficaces alors que la mémoire de notre agent ne cesse de croître.</p>async upsert(params: UpsertVectorParams): Promise&lt;string[]&gt; {
    const { indexName, vectors, metadata = [], ids } = params;

    try {
        // Generate unique IDs if not provided
        const vectorIds = ids || vectors.map((_, i) =&gt; 
            `vec_${Date.now()}_${i}_${Math.random().toString(36).substr(2, 9)}`
        );

        const operations = vectors.flatMap((vec, index) =&gt; [
            { index: { _index: indexName, _id: vectorIds[index] } },
            {
                vector: vec,
                metadata: metadata[index] || {},
            },
        ]);

        const response = await this.client.bulk({
            refresh: true,
            operations,
        });

        if (response.errors) {
            const erroredItems = response.items.filter((item: any) =&gt; item.index?.error);
            const erroredIds = erroredItems.map((item: any) =&gt; item.index?._id);
            const errorDetails = erroredItems.slice(0, 3).map((item: any) =&gt; ({
                id: item.index?._id,
                error: item.index?.error?.reason || item.index?.error,
                type: item.index?.error?.type
            }));
            
            const errorMessage = `Failed to upsert ${erroredIds.length}/${vectors.length} vectors`;
            console.error(`${errorMessage}. Sample errors:`, JSON.stringify(errorDetails, null, 2));
            this.logger?.error(errorMessage, { 
                failedCount: erroredIds.length, 
                totalCount: vectors.length,
                sampleErrors: errorDetails 
            });
            
            // Still return successfully inserted IDs
            const successfulIds = vectorIds.filter((id, idx) =&gt; 
                !erroredIds.includes(id)
            );
            
            if (successfulIds.length === 0) {
                throw new Error(`${errorMessage}. All operations failed. See logs for details.`);
            }
            
            return successfulIds;
        }

        this.logger?.info(`Successfully upserted ${vectors.length} vectors to "${indexName}"`);
        return vectorIds;
    } catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        this.logger?.error(`Failed to upsert vectors to "${indexName}": ${errorMessage}`);
        throw new Error(`Failed to upsert vectors to "${indexName}": ${errorMessage}`);
    }
}<h3>Interrogation des vecteurs similaires pour le rappel sémantique</h3><p>Cette fonction est au cœur de la fonction de rappel sémantique. L'agent utilise la recherche vectorielle pour trouver des enregistrements similaires dans notre index.</p>async query(params: QueryVectorParams&lt;any&gt;): Promise&lt;QueryResult[]&gt; {
    const { indexName, queryVector, topK = 10, filter, includeVector = false } = params;

    try {
        const knnQuery: any = {
            field: 'vector',
            query_vector: queryVector,
            k: topK,
            num_candidates: Math.max(topK * 10, 100), // Search more candidates for better recall
        };

        // Apply metadata filters if provided
        if (filter) {
            knnQuery.filter = this.buildElasticFilter(filter);
        }

        const sourceFields = ['metadata'];
        if (includeVector) {
            sourceFields.push('vector');
        }

        const response = await this.client.search({
            index: indexName,
            knn: knnQuery,
            size: topK,
            _source: sourceFields,
        });

        const results = response.hits.hits.map((hit: any) =&gt; ({
            id: hit._id,
            score: hit._score || 0,
            metadata: hit._source?.metadata || {},
            vector: includeVector ? hit._source?.vector : undefined,
        }));

        this.logger?.debug(`Query returned ${results.length} results from "${indexName}"`);
        return results;
    } catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        this.logger?.error(`Failed to query vectors from "${indexName}": ${errorMessage}`);
        throw new Error(`Failed to query vectors from "${indexName}": ${errorMessage}`);
    }
}<p>Sous le capot :</p><ul><li><p>Exécute une requête <a href="https://www.elastic.co/docs/solutions/search/vector/knn">kNN</a> (k-nearest neighbors) à l'aide de l'API <code>knn</code> dans Elasticsearch.</p></li><li><p>Récupère les K premiers vecteurs similaires au vecteur d'entrée de la requête.</p></li><li><p>Possibilité d'appliquer des filtres de métadonnées pour limiter les résultats (par exemple, recherche uniquement dans une catégorie ou une période spécifique).</p></li><li><p>Renvoie des résultats structurés comprenant l'identifiant du document, le score de similarité et les métadonnées stockées.</p></li></ul><h2>Création de l'agent de connaissance</h2><p>Maintenant que nous avons vu la connexion entre Mastra et Elasticsearch à travers l'intégration <code>ElasticVector</code>, créons l'agent de connaissance lui-même.</p><p>Dans le dossier <code>agents</code>, créez un fichier appelé <code>knowledge-agent.ts</code>. Nous pouvons commencer par connecter nos variables d'environnement et initialiser le client Elasticsearch.</p>import { Agent } from '@mastra/core/agent';
import { Memory } from '@mastra/memory';
import { openai } from '@ai-sdk/openai';
import { Client } from '@elastic/elasticsearch';
import { ElasticVector } from '../stores/elastic-store';
import dotenv from "dotenv";

dotenv.config();

const ELASTICSEARCH_ENDPOINT = process.env.ELASTICSEARCH_ENDPOINT;
const ELASTICSEARCH_API_KEY = process.env.ELASTICSEARCH_API_KEY;

//Error check for undefined credentials
if (!ELASTICSEARCH_ENDPOINT || !ELASTICSEARCH_API_KEY) {
  throw new Error('Missing Elasticsearch credentials');
}

//Check to see if a connection can be established
const testClient = new Client({
  node: ELASTICSEARCH_ENDPOINT,
  auth: { 
    apiKey: ELASTICSEARCH_API_KEY 
  },
});

try {
  await testClient.ping();
  console.log('Connected to Elasticsearch successfully');
} catch (error: unknown) {
  if (error instanceof Error) {
    console.error('Failed to connect to Elasticsearch:', error.message);
  } else {
    console.error('Failed to connect to Elasticsearch:', error);
  }
  process.exit(1);
}
//Initialize the Elasticsearch vector store
const vectorStore = new ElasticVector({
  node: ELASTICSEARCH_ENDPOINT,
  auth: {
    apiKey: ELASTICSEARCH_API_KEY,
  },
//Optional: Explicitly set to true if using Elasticsearch Serverless to skip auto-detection and improve startup time
//isServerless: true,
});<p>Ici, nous :</p><ul><li><p>Utilisez <code>dotenv</code> pour charger nos variables à partir de notre fichier <code>.env</code>.</p></li><li><p>Vérifiez que les informations d'identification Elasticsearch sont injectées correctement et que nous pouvons établir une connexion réussie avec le client.</p></li><li><p>Passez le point de terminaison Elasticsearch et la clé API dans le constructeur <code>ElasticVector</code> pour créer une instance de notre magasin vectoriel que nous avons défini plus tôt.</p></li><li><p>Spécifiez éventuellement <code>isServerless: true</code> si vous utilisez Elasticsearch Serverless. Cela permet d'éviter l'étape d'autodétection et d'améliorer le temps de démarrage. S'il est omis, l'adaptateur détectera automatiquement votre type de déploiement lors de la première utilisation.</p></li></ul><p>Ensuite, nous pouvons définir l'agent à l'aide de la classe <code>Agent</code> de Mastra.</p>export const knowledgeAgent = new Agent({
    name: 'KnowledgeAgent',
    instructions: 'You are a helpful knowledge assistant.',
    model: openai('gpt-4o'),
    memory: new Memory({

        vector: vectorStore,

        //embedder used to create embeddings for each message
        embedder: 'openai/text-embedding-3-small',

        //set semantic recall options
        options: {
            semanticRecall: {
                topK: 3, // retrieve 3 similar messages
                messageRange: 2, // include 2 messages before/after each match
                scope: 'resource',
            },
        },
    }),
});<p>Les champs que nous pouvons définir sont les suivants :</p><ul><li><p><code>name</code> et <code>instructions</code>: lui donner une identité et une fonction première.</p></li><li><p><code>model</code>: Nous utilisons <code>gpt-4o</code> d'OpenAI à travers le paquet <code>@ai-sdk/openai</code>.</p></li><li><p><code>memory</code>:</p><ul><li><p><code>vector</code>: Pointe vers notre magasin Elasticsearch, de sorte que les embeddings sont stockés et récupérés à partir de ce magasin.</p></li><li><p><code>embedder</code>: Quel modèle utiliser pour générer des embeddings ?</p></li><li><p><code>semanticRecall</code> décident de la manière dont le rappel fonctionne :</p><ul><li><p><code>topK</code>: Nombre de messages sémantiquement similaires à récupérer.</p></li><li><p><code>messageRange</code>: Quelle partie de la conversation doit être incluse dans chaque match.</p></li><li><p><code>scope</code>: Définit la limite de la mémoire.</p></li></ul></li></ul></li></ul><p>Presque terminé. Il ne nous reste plus qu'à ajouter cet agent nouvellement créé à notre configuration Mastra. Dans le fichier appelé <a href="http://index.ts/"><code>index.ts</code></a>, importez l'agent de connaissance et insérez-le dans le champ <code>agents</code>.</p>export const mastra = new Mastra({
  agents: { knowledgeAgent },
  storage: new LibSQLStore({
    // stores observability, scores, ... into memory storage, if it needs to persist, change to file:../mastra.db
    url: ":memory:",
  }),
  logger: new PinoLogger({
    name: 'Mastra',
    level: 'info',
  }),
  telemetry: {
    // Telemetry is deprecated and will be removed in the Nov 4th release
    enabled: false, 
  },
  observability: {
    // Enables DefaultExporter and CloudExporter for AI tracing
    default: { enabled: true }, 
  },
});<p>Les autres champs sont les suivants :</p><ul><li><p><code>storage</code>: Il s'agit du magasin de données interne de Mastra pour l'historique des exécutions, les mesures d'observabilité, les scores et les caches. Pour plus d'informations sur le stockage Mastra, <a href="https://mastra.ai/docs/server-db/storage">cliquez ici.</a></p></li><li><p><code>logger</code>: Mastra utilise <a href="https://github.com/pinojs/pino">Pino</a>, qui est un enregistreur JSON structuré et léger. Il capture des événements tels que le démarrage et l'arrêt de l'agent, les appels d'outils et les résultats, les erreurs et les temps de réponse du LLM.</p></li><li><p><code>observability</code>: Contrôle le suivi de l'IA et la visibilité de l'exécution pour les agents. Il suit :</p><ul><li><p>Début/fin de chaque étape du raisonnement.</p></li><li><p>Quel modèle ou outil a été utilisé.</p></li><li><p>Entrées et sorties.</p></li><li><p>Notes et évaluations</p></li></ul></li></ul><h3>Test de l'agent avec Mastra Studio</h3><p>Félicitations ! Si vous êtes arrivé jusqu'ici, vous êtes prêt à faire fonctionner cet agent et à tester ses capacités de rappel sémantique. Heureusement, Mastra fournit une interface de chat intégrée, ce qui nous évite d'avoir à créer notre propre interface.</p><p>Pour démarrer le serveur de développement Mastra, ouvrez un terminal et exécutez la commande suivante :</p>npm run dev<p>Après le regroupement initial et le démarrage du serveur, celui-ci devrait vous fournir une adresse pour le terrain de jeu.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte5f857fddc74ffc9/6a16f7b6a6c2b995d5e794c0/8b045f70008d26aec4d2e6b59d61085555b9c5b2-686x116.png" alt="Adresse du serveur pour Playground" /><p>Collez cette adresse dans votre navigateur et vous serez accueilli par le Mastra Studio.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc7fdda6ce46ce068/6a16f7b7b0367d4f7672bacf/69bc80fe8486edd9e0cf91d87b39f465aeb23111-1600x438.png" alt="Coller l'adresse du terrain de jeu pour accéder à Mastra Studio" /><p>Sélectionnez l'option <code>knowledgeAgent</code> et discutez.</p><p>Pour vérifier rapidement si tout est bien branché, donnez-lui des informations telles que : "L'équipe a annoncé que les ventes d'octobre ont augmenté de 12%, principalement grâce aux renouvellements de contrats d'entreprise. La prochaine étape consistera à élargir le champ d'action aux clients du marché intermédiaire". Ensuite, démarrez un nouveau chat et posez une question du type : "Sur quel segment de clientèle avons-nous dit que nous devions nous concentrer ensuite ?". L'agent de connaissance doit pouvoir se souvenir des informations que vous lui avez communiquées lors de la première conversation. Vous devriez obtenir une réponse du type</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfec3266e81a7213b/6a16f7b92b835f6f70f4afe2/da8ebddad89874023ed440a8f1ad2cb04ed043f4-1070x288.png" alt="Chat avec un agent de connaissance dans Mastra Studio- l'agent peut rappeler des informations" /><p>Une telle réponse signifie que l'agent a stocké avec succès notre message précédent sous forme d'éléments intégrés dans Elasticsearch et qu'il l'a récupéré ultérieurement à l'aide d'une recherche vectorielle.</p><h3>Inspection de la mémoire à long terme de l'agent</h3><p>Rendez-vous sur l'onglet <code>memory</code> dans la configuration de votre agent dans le Studio Mastra. Cela vous permet de voir ce que votre agent a appris au fil du temps. Chaque message, réponse et interaction qui est intégré et stocké dans Elasticsearch fait partie de cette mémoire à long terme. Vous pouvez effectuer une recherche sémantique dans les interactions passées pour retrouver rapidement les informations ou le contexte que l'agent a appris précédemment. Il s'agit essentiellement du même mécanisme que celui utilisé par l'agent lors du rappel sémantique, mais ici, vous pouvez l'inspecter directement. Dans l'exemple ci-dessous, nous recherchons le terme "ventes" et nous obtenons en retour toutes les interactions qui contiennent un élément relatif aux ventes.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte428134d7bf2a43a/6a16f7bbb0367d185872bad3/3decaa0c332d288c5ae0b11c25f592c7d50c2f0f-1104x1320.png" alt="Comment inspecter les agents de connaissance stockés dans la mémoire à long terme" /><h2>Conclusion</h2><p>En connectant Mastra et Elasticsearch, nous pouvons donner à nos agents de la mémoire, qui est une couche clé dans l'ingénierie contextuelle. Grâce au rappel sémantique, les agents peuvent construire un contexte au fil du temps, en fondant leurs réponses sur ce qu'ils ont appris. Cela signifie des interactions plus précises, plus fiables et plus naturelles.</p><p>Cette intégration précoce n'est que le point de départ. Le même modèle peut permettre aux agents d'assistance de se souvenir des tickets précédents, aux robots internes de retrouver la documentation pertinente ou aux assistants d'IA de se souvenir des détails d'un client au cours d'une conversation. Nous travaillons également à l'intégration officielle de Mastra, afin de rendre cette association encore plus transparente dans un avenir proche.</p><p>Nous sommes impatients de voir ce que vous allez construire. Essayez-le, explorez <a href="https://mastra.ai/">Mastra</a> et ses fonctions de mémoire, et n'hésitez pas à partager vos découvertes avec la communauté.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/knowledge-agent-semantic-recall-mastra-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/knowledge-agent-semantic-recall-mastra-elasticsearch</guid>
    <category><![CDATA[IA agentique]]></category>
    <category><![CDATA[Expérience développeur]]></category>
    <category><![CDATA[Intégrations]]></category>
    <dc:creator><![CDATA[JD Armada]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt09afdbff05603865/6a16f7bd839dfabbf2dcfcb5/b8d51c2726d5573385c9246a7821d12ade4f1b0e-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 06 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>