<?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[Experiência do Desenvolvedor - 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[Experiência do Desenvolvedor - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/pt/search-labs/blog/category/developer-experience</link>
    </image>
    <link>https://www.elastic.co/pt/search-labs/blog/category/developer-experience</link>
    <atom:link href="https://www.elastic.co/pt/search-labs/rss/category/developer-experience.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[pt]]></language>
    <lastBuildDate>Sun, 20 Sep 2026 22:46:11 GMT</lastBuildDate>
  <item>
    <title><![CDATA[API do dashboard do Kibana: um contrato estável para todos os tipos de painéis, testado por mais de 50 equipes antes da disponibilidade geral (GA)]]></title>
    <description><![CDATA[Gerencie dashboards do Kibana como código: faça commit no Git, promova entre ambientes e automatize implantações com a API do Kibana e o Terraform.]]></description>
    <content:encoded><![CDATA[<p>As<a href="https://dashboardsapispec.kibana.dev/dashboards#tag/Dashboards"> APIs de dashboards e visualizações do Kibana</a> estão prontas para produção no Elastic 9.5, disponíveis em todos os níveis de assinatura, com total compatibilidade com versões anteriores. Defina seus dashboards como JSON, comprometa-os no Git e então implante em ambientes usando pipelines de integração contínua e implantação contínua (CI/CD),<a href="https://registry.terraform.io/providers/elastic/elasticstack/latest/docs/resources/kibana_dashboard"> Terraform</a> ou qualquer ferramenta que você já tenha. Mais de 50 equipes testaram a API durante<a href="https://www.elastic.co/search-labs/blog/kibana-dashboards-as-code-terraform-api"> a prévia técnica na versão 9.4</a>, algumas já a rodando em produção. A versão 9.5 também adiciona novos endpoints (em prévia técnica) para<a href="https://dashboardsapispec.kibana.dev/tags.html"> Tags</a>, com endpoints, <a href="https://dashboardsapispec.kibana.dev/markdowns.html"> dos painéis Markdown</a> e<a href="https://dashboardsapispec.kibana.dev/links.html#tag/Links"> Links</a> disponíveis agora no Elastic Cloud Serverless e chegando na versão 9.6.</p><h2>O que a compatibilidade com versões anteriores significa para a API do dashboard do Kibana</h2><p>Durante a prévia técnica, a configuração da API pode mudar entre os lançamentos.[1] Esse não é mais o caso. Disponibilidade geral (GA) significa:</p><ul><li><p><strong>Compatibilidade retrógrada completa.</strong> Novos campos e tipos de painel serão adicionados ao longo do tempo, mas os campos e comportamentos existentes permanecem inalterados. Quaisquer mudanças futuras que quebrem a compatibilidade seriam cuidadosamente consideradas e só seriam introduzidas em uma nova versão principal da pilha.</p></li><li><p><strong>Pronto para produção com suporte completo.</strong> A API oferece todas as garantias de compatibilidade da Elastic. Você pode usá-lo com segurança em ambientes de produção para implantações automatizadas, promoção de ambiente e gerenciamento programático do dashboard.</p></li></ul><h2>Novos endpoints da API Kibana para os painéis Tags, Markdown e Links</h2><p>O Elastic 9.5 também introduz um novo  endpoint independente para <a href="https://dashboardsapispec.kibana.dev/tags.html"><strong>Tags</strong></a>, que permite categorizar e filtrar painéis. Agora, você pode gerenciá-los programaticamente por meio de endpoints CRUD dedicados, facilitando a organização de painéis em escala entre ambientes.	</p><p>Os novos endpoints para os painéis <a href="https://dashboardsapispec.kibana.dev/markdowns.html"><strong>Markdown</strong></a> e <a href="https://dashboardsapispec.kibana.dev/links.html#tag/Links"><strong>Links</strong></a> já estão disponíveis no Serverless e serão lançados na próxima versão do stack (9.6).</p><h2>Com quais tipos de painel a API do dashboard do Kibana é compatível?</h2><p>A API do dashboard é compatível com todos os painéis <em>definidos por valor</em> na versão 9.5 (aqueles definidos diretamente em um dashboard, em oposição aos painéis da biblioteca salvos para reutilização). Cada tipo de painel compatível possui um esquema tipado e validado.</p><p><strong>Tipo de painel</strong></p><p><strong>Status</strong></p><p>Gráficos XY</p><p>Compatível</p><p>Métricas</p><p>Compatível</p><p>Pizza</p><p>Compatível</p><p>Medidor</p><p>Compatível</p><p>Heatmap</p><p>Compatível</p><p>Tabelas de dados</p><p>Compatível</p><p>Mapa de árvore</p><p>Compatível</p><p>Discover sessões</p><p>Compatível</p><p>Controles</p><p>Compatível</p><p>Markdown</p><p>Compatível</p><p>Links</p><p>Compatível</p><p>Painéis de ML</p><p>Compatível</p><p>Painéis de observabilidade</p><p>Compatível</p><p>Mapas</p><p>Em breve</p><p>Vega</p><p>Em breve</p><h2>Como gerenciar dashboards do Kibana como código</h2><p>A API de dashboards permite um fluxo de trabalho completo de dashboards como código: exportar um dashboard como JSON limpo e passível de comparação, enviá-lo ao Git como fonte de verdade, revisar mudanças em pull requests e implantar a mesma definição em desenvolvimento, staging e produção. Depois que um dashboard for gerenciado como código, trate o Git como a única fonte da verdade: as alterações feitas diretamente na UI serão substituídas na próxima vez que você implantar.</p><p>O principal desafio ao mover um dashboard entre espaços, clusters ou estágios é que os dashboards referenciam objetos como data view e visualizações da biblioteca por ID. Como esses IDs são gerados automaticamente e diferem entre ambientes, um dashboard exportado de um ambiente pode apontar para objetos que não existem em outro. Existem três maneiras de lidar com isso, listadas aqui da mais automatizada à menos automatizada:</p><ul><li><p><strong>Use Terraform.</strong> O <a href="https://registry.terraform.io/providers/elastic/elasticstack/latest/docs/resources/kibana_dashboard">provedor Terraform do Elastic Stack</a> acompanha cada recurso e mapeia os IDs automaticamente por ambiente, para que as referências permaneçam consistentes enquanto você promove um dashboard do desenvolvimento para a produção.</p></li><li><p><strong>Defina por-valor </strong><a href="https://www.elastic.co/docs/explore-analyze/visualize/esorql"><strong>a Linguagem de Consulta Elasticsearch (ES|QL)</strong></a><strong>.</strong> A maneira mais portátil de construir um painel é definir sua visualização com o ES|QL diretamente no dashboard. Uma consulta <a href="https://www.elastic.co/docs/explore-analyze/query-filter/languages/esql-kibana">ES|QL</a> lê os índices que você especificar nela; portanto, o painel não contém referências externas a data views ou objetos de biblioteca. O resultado é um dashboard portátil e totalmente independente.</p></li><li><p><strong>Atribua IDs correspondentes.</strong> Se você fizer referência a objetos salvos, como visualizações de dados ou visualizações de bibliotecas, crie-os com um ID escolhido usando PUT (upsert) em vez de POST (que gera automaticamente um ID). Use IDs legíveis por humanos, como logs-prod, para que sejam fáceis de reutilizar e reconhecer em diferentes ambientes.</p></li></ul><p>Para obter uma descrição detalhada desses padrões de portabilidade e do fluxo de trabalho completo de dashboards como código, consulte a documentação <a href="https://www.elastic.co/docs/explore-analyze/dashboards/manage-dashboards-as-code#dashboards-as-code-portability">Gerenciar dashboards como código</a>.</p><h3>Crie um dashboard do Kibana com a API Dashboards usando PUT</h3><p>Aqui está um exemplo rápido de criação de um dashboard com uma métrica usando PUT em vez de POST para atribuir um ID personalizado com o nome do dashboard (service-health-overview). A mesma lógica funciona para criar visualizações independentes salvas na biblioteca.</p>PUT kbn:/api/dashboards/service-health-overview
{
  "título": "Visão geral da saúde do serviço",
  "descrição": "Métricas principais do serviço — gerenciadas via API",
  "tags": [
    "produção",
    "equipe SRE"
  ],
  "painéis": [
    {
      "tipo": "vis",
      "grade": {
        "x": 0,
        "y": 0,
        "w": 12,
        "h": 8
      },
      "config": {
        "título": "Taxa de erro (5xx)",
        "tipo": "métrico",
        "data_source": {
          "type": "esql",
          "query": "FROM logs-* | WHERE http.response.status_code &gt;= 500 | STATS error_rate=count(*) BY host.name"
        },
        "métricas": [
          {
            "type": "primary",
            "column": "count"
          }
        ]
      }
    }
  ]
}<h2>Roadmap da API do dashboard do Kibana: Maps, Vega e endpoints independentes</h2><p>Estamos expandindo ativamente o escopo da API. Na sequência, compatibilidade com mapas e painéis Vega, adicionando esquemas tipados para eles. Também estamos construindo pontos finais CRUD independentes para sessões Discover (além do suporte existente como painéis de dashboard), Vega, maps e anotações, desacoplados do ciclo de vida do dashboard.</p><p>Para as definições completas de esquema, visite a <a href="https://dashboardsapispec.kibana.dev/dashboards#tag/Dashboards">documentação da API dos Dashboards</a>. Para usuários do Terraform, o <a href="https://registry.terraform.io/providers/elastic/elasticstack/latest/docs/resources/kibana_dashboard">provedor Terraform do Elastic Stack</a> é compatível com a API GA Dashboards.</p><h2>Nota</h2><ol><li><p>Os endpoints núcleos permanecem inalterados em relação à prévia técnica. Se você construiu integrações contra a 9.4, elas funcionam na 9.5. As únicas alterações que quebram a compatibilidade são duas pequenas que afetam os formatos de listagem do dashboard e dos formatos da unidade de duração, documentadas <a href="https://www.elastic.co/docs/release-notes/kibana/breaking-changes">aqui</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[Experiência do Desenvolvedor]]></category>
    <category><![CDATA[Integrações]]></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[Apresentando chaves de API unificadas para Elastic Cloud Serverless e Elasticsearch]]></title>
    <description><![CDATA[Saiba como a Elastic unificou o plano de controle e a autenticação do plano de dados no Serverless com uma arquitetura IAM distribuída globalmente. Use uma chave de API para as APIs da nuvem e do Elasticsearch.]]></description>
    <content:encoded><![CDATA[<p>Imagine você um engenheiro de confiabilidade de sistemas (SRE) responsável por uma frota crescente de projetos do Elastic Cloud Serverless: Elastic Observability para sua infraestrutura de produção, Elastic Security para sua equipe do centro de operações de segurança (SOC) e Elasticsearch para sua aplicação voltada ao cliente. Cada projeto tem a própria chave de API do Elasticsearch. Seu pipeline de integração contínua e entrega contínua (CI/CD) precisa de uma chave separada da Cloud API para provisionar e gerenciar esses projetos. O dia de rotação chega todo trimestre: você passa por cada projeto, gera novas chaves, atualiza o state do Terraform, reimplanta os pipelines e torce para que nada fique para trás. Quando um incidente acontece às 2h e é preciso revogar o acesso rápido, você se vê comparando uma planilha de credenciais para saber qual chave pertence a qual projeto e a qual serviço.</p><p>Hoje, essa história fica muito mais simples. <strong>Chaves de API do Elastic Cloud</strong> agora podem ser usadas para autenticar diretamente em comparação a APIs <strong>do Elasticsearch</strong> e <strong>Kibana</strong> no <strong>Elastic Cloud Serverless</strong>. Agora você pode usar uma única credencial para gerenciar os recursos da sua <em>organização e</em> executar operações de dados, como a Elasticsearch Query Language (ES|QL), ingestão de dados e alertas.</p><p>Vamos ver por que construímos isso, como projetamos uma camada de identidade distribuída globalmente para possibilitar o recurso e como ele estabelece a base para a busca entre projetos.</p><h2>O ônus da gestão de segredos</h2><p>Construir pipelines confiáveis de CI/CD, fluxos de trabalho GitOps ou automação Terraform em plataformas de dados tem um custo oculto: a proliferação de segredos.</p><p>No modelo anterior, os desenvolvedores lidavam com uma história de autenticação desarticulada:</p><ul><li><p><strong>Plano de controle (chaves da API do Elastic Cloud):</strong> chaves com escopo organizacional usadas para criar projetos, convidar usuários e gerenciar as cobranças via <a href="https://www.elastic.co/docs/api/doc/cloud/">API do Elastic Cloud</a>.</p></li><li><p><strong>Plano de dados (chaves da API do Elasticsearch):</strong> Chaves com escopo de projeto criadas <em>dentro</em> de um projeto Serverless específico para interagir com as APIs do <a href="https://www.elastic.co/docs/api/doc/elasticsearch-serverless/">Elasticsearch</a> e do <a href="https://www.elastic.co/docs/api/doc/serverless">Kibana</a>.</p></li></ul><p>Nesse caso, seu script de implantação precisava se autenticar no Elastic Cloud, provisionar um projeto Serverless, extrair uma chave de API do Elasticsearch recém-criada desse projeto específico e, em seguida, inserir <em>essa</em> segunda chave na aplicação ou na ferramenta de automação mais adiante, o que resultava em pipelines complexos, logs de auditoria fragmentados e maior risco de vazamento de credenciais.</p><h2>Autenticação unificada no Elastic Cloud Serverless</h2><p>Com este lançamento, a separação para projetos Serverless foi eliminada. Agora você pode criar uma chave de API do Elastic Cloud explicitamente autorizada para <strong>nuvem, Elasticsearch e Kibana APIs</strong>.</p><ul><li><p><strong>Antes:</strong> a chave de API do Elastic Cloud era estritamente um token do plano de controle. Ela podia criar projetos, gerenciar cobranças e convidar usuários, mas tinha um limite rígido; não podia ser usada para chamar as APIs do Elasticsearch nem Kibana dentro desses projetos. Você sempre precisava de uma segunda chave específica do projeto para operações de dados.</p></li><li><p><strong>Agora:</strong> ao ter acesso a <strong>nuvem, Elasticsearch e a API Kibana</strong> ao criar uma chave de API do Elastic Cloud, o limite rígido é retirado para o Serverless. Essa chave de API se torna uma credencial verdadeiramente unificada. Ela mantém a capacidade de gerenciar a infraestrutura da sua organização, ao mesmo tempo que ganha acesso nativo para consultar, ingerir e analisar dados em qualquer projeto Serverless autorizado.</p></li></ul><p>Ao unificar tudo sob uma única chave de API do Elastic Cloud, você ganha uma única identidade que pode ter escopo definido, ser auditada, rotacionada e revogada como uma unidade. Cada chamada de API, seja para provisionar um novo projeto ou executar uma consulta ES|QL, aparece sob a mesma credencial nos seus logs de auditoria, fornecendo um único rastro a ser seguido durante investigações de incidentes ou revisões de conformidade. A rotação de credenciais agora é feita em uma etapa em vez de ser uma atualização coordenada em segredos separados do plano de controle e do plano de dados. E, como as alocações de função são por projeto, uma só chave pode abranger vários projetos, gerenciando a ingestão no seu projeto de observabilidade e executando consultas no seu projeto de segurança, sem precisar lidar com credenciais separadas para cada um.</p><p>Importante: <em>unificado</em> não significa <em>todo-poderoso</em>. Ao usar a carga útil <code>role_assignments</code>, você pode definir uma chave unificada estritamente para um único projeto e uma função específica (como somente leitura), garantindo que o raio de explosão continue totalmente contido caso uma credencial seja exposta. Se um desenvolvedor sair ou uma aplicação for desativada, você pode revogar uma única chave do console Elastic Cloud, encerrando imediatamente o acesso tanto no plano de controle quanto em todos os projetos Elasticsearch associados.</p><p><em>(Atenção: nas implantações Elastic Cloud Hosted/gerenciadas, as chaves da API da nuvem ainda gerenciam apenas o plano de controle. O suporte para estender isso às APIs de pilha hospedada está planejado para uma versão futura.)</em></p><h2>Automatizando seus fluxos de trabalho</h2><p>Começar é simples. Você pode configurar inteiramente no console Elastic Cloud ou automatizar usando a <a href="https://www.elastic.co/docs/deploy-manage/api-keys/elastic-cloud-api-keys">API do Elastic Cloud</a>.</p><p>O processo da IU não muda, mas agora você pode selecionar <strong>Nuvem, Elasticsearch e API Kibana</strong> na alocação de função do projeto.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltda0a18945295aa84/6a1707bd509168fab4e1ba19/c4f802f130655290cd474b283001a954d14c3088-2801x1681.png" alt="Tela do Elastic Cloud mostrando a página de chaves API com um modal de Criar chave API aberto, incluindo campos para nome, expiração e atribuição de funções." /><p>Veja como criar uma chave unificada programaticamente usando a API do Elastic Cloud. Observe o <code>application_roles</code> conjunto, pois é o que concede ao principal acesso nativo ao plano de dados do 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>Uma vez criado, você passa exatamente essa mesma chave no cabeçalho <code>Authorization: ApiKey</code> tanto para <code>api.elastic-cloud.com</code> quanto para seus endpoints específicos do Serverless Elasticsearch.</p><h2>Nos bastidores: construindo uma camada de identidade distribuída</h2><p>Fazer uma chave da API da nuvem funcionar tanto no plano de controle quanto no plano de dados não é tão simples como passar um token. É preciso resolver um desafio fundamental nos sistemas distribuídos.</p><p>Historicamente, as chaves de API da nuvem ficavam em um cluster de segurança global centralizado. Isso funciona nas operações de plano de controle cuja latência mais alta é aceitável. No entanto, requisições de dados do Elasticsearch exigem latência ultrabaixa. Não podemos viajar pelo globo até um plano de controle central para validar cada busca ou solicitação de ingestão.</p><p>Para resolver, introduzimos uma nova arquitetura de autenticação apoiada por um datastore distribuído globalmente. O diagrama sequencial a seguir mostra um cliente enviando uma consulta Elasticsearch, usando uma chave API do Elastic Cloud, ilustrando como a autenticação ocorre inteiramente dentro da região, sem a viagem por todo o plano de controle global. O Elasticsearch delega a autenticação ao Serviço IAM Regional, que valida a chave e resolve as alocações de função em uma réplica local do banco de dados distribuído globalmente. Uma vez autorizado, o Elasticsearch executa a consulta e retorna os resultados ao cliente.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf4fa84c3f33f88f7/6a1707baacf088989abe9a8e/3e38d7a862b9981523c5393c441b92eae13aeb90-2401x1351.webp" alt="Diagrama de sequência mostrando a solicitação de um cliente com uma chave da Cloud API fluindo pelo Elasticsearch Serverless, um serviço IAM regional e uma réplica de banco de dados distribuída antes de retornar os resultados." /><h3>Persistência distribuída globalmente</h3><p>Em vez de depender exclusivamente de um cluster de segurança centralizado, as chaves de API do Elastic Cloud e as respectivas definições de função agora ficam em um banco de dados globalmente distribuído e de alta disponibilidade. Esse banco de dados sincroniza os dados de gerenciamento de identidade e acesso (IAM) no plano de controle global e nos planos de dados regionais onde seus projetos Serverless são executados.</p><h3>Validação local com IAM regional</h3><p>Quando seu cliente envia uma requisição para o Elasticsearch usando uma chave API do Elastic Cloud, a solicitação não retorna ao plano de controle global. Em vez disso, ela é encaminhada para o novo serviço regional IAM. Ele valida a chave em relação à réplica do banco de dados local, garantindo que a autenticação ocorra com latência quase zero e completamente isolada de interrupções no plano de controle global.</p><h3>Mapeamento dinâmico de funções</h3><p>A autenticação é metade do caminho; o sistema também precisa autorizar a solicitação. O serviço IAM regional traduz na hora suas alocações de função no nível da nuvem, por exemplo, <code>application_roles</code>), em privilégios nativos do Elasticsearch. O Elasticsearch pode então autorizar e executar a solicitação no local, sem precisar de um <code>.security</code> índice local.</p><h2>A base para a busca entre projetos</h2><p>Essa arquitetura de identidade distribuída é um elemento fundamental para o futuro da plataforma Elastic.</p><p>Como a identidade e o acesso agora estão unificados e sincronizados globalmente, temos o framework necessário para transmitir sua identidade com segurança entre diferentes projetos. Isso possibilita as futuras capacidades <strong>de Busca Cruzada por Projetos (CPS)</strong> para Serverless.</p><p>Com o CPS, você poderá consultar dados que abrangem vários projetos Serverless remotos, como combinar cargas de trabalho de segurança e observabilidade, como se fossem um único conjunto de dados. Ao depender de chaves de API unificadas, o sistema pode avaliar automaticamente suas permissões simultaneamente em todos os projetos, sem exigir que você configure relacionamentos de confiança complexos, certificados ou credenciais duplicadas em cada projeto-alvo.</p><h2>Saiba mais</h2><p>Pronto para simplificar sua pilha?</p><ul><li><p>Leia a <a href="https://www.elastic.co/docs/deploy-manage/api-keys/elastic-cloud-api-keys">documentação das chaves de API do Elastic Cloud</a> para aprender como atribuir acesso ao stack.</p></li><li><p>Confira a referência <a href="https://www.elastic.co/docs/api/doc/cloud/operation/operation-create-api-key">Criar chave de API (Elastic Cloud API)</a> para automatizar a geração de chaves.</p></li><li><p>Consulte <a href="https://www.elastic.co/docs/deploy-manage/api-keys">Chaves de API Elastic</a> para uma comparação completa dos tipos de chave em toda a plataforma Elastic.</p></li></ul><p>Comece ou continue construindo no <a href="https://cloud.elastic.co/registration">Elastic Cloud</a> hoje.</p><h2>Aviso de isenção</h2><p>O lançamento e o tempo de amadurecimento de todos os recursos ou funcionalidades descritos neste artigo permanecem a exclusivo critério da Elastic. Os recursos ou funcionalidades não disponíveis no momento poderão não ser entregues ou não chegarem no prazo previsto.</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[Experiência do Desenvolvedor]]></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[Monitorando as visualizações do dashboard do Kibana com o Elastic Workflows]]></title>
    <description><![CDATA[Aprenda a usar o Elastic Workflows para coletar métricas de visualização do dashboard do Kibana a cada 30 minutos e indexá-las no Elasticsearch, para que você possa criar análises e visualizações personalizadas com base em seus próprios dados.]]></description>
    <content:encoded><![CDATA[<p>O <a href="https://www.elastic.co/kibana">Kibana</a> rastreia quantas vezes cada dashboard é visualizado, mas esses dados não são expostos nativamente em nenhum dashboard integrado. Neste artigo, vamos usar o <strong>Elastic Workflows</strong> para coletar automaticamente esses dados a cada 30 minutos e indexá-los no Elasticsearch, para que possamos criar nossa própria analítica sobre eles.</p><p>O <a href="https://www.elastic.co/docs/explore-analyze/workflows">Elastic Workflows</a> é um mecanismo de automação integrado dentro do Kibana que permite definir processos de várias etapas usando uma simples configuração YAML. Cada fluxo de trabalho pode ser acionado em um cronograma ou evento ou como uma ferramenta no <a href="https://www.elastic.co/docs/explore-analyze/ai-features/elastic-agent-builder">Elastic Agent Builder</a>, e cada etapa pode chamar APIs do Kibana, consultar o Elasticsearch ou transformar dados.</p><p>Vamos usar as contagens de visualização de dashboards como um exemplo concreto, mas o mesmo padrão se aplica a qualquer métrica exposta pela API de objetos salvos do Kibana.</p><h2>Pré-requisitos</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">autogerenciado </a>executando a versão 9.3</p></li><li><p><a href="https://www.elastic.co/docs/explore-analyze/workflows/get-started#workflows-prerequisites">Fluxos de trabalho ativados</a> (Configurações avançadas)</p></li></ul><h2>Passo 1: explorar os dados brutos no <a href="https://www.elastic.co/docs/explore-analyze/query-filter/tools/console">Dev Tools</a></h2><p>Antes de construir qualquer coisa, vamos entender quais dados temos. O Kibana armazena a maior parte de sua configuração e metadados como <a href="https://www.elastic.co/docs/api/doc/kibana/group/endpoint-saved-objects">objetos salvos</a> em um índice interno dedicado. Uma das coisas que o Kibana monitora dessa forma são as contagens de visualizações do dashboard, usando um tipo especial de objeto salvo chamado contadores de uso. Você pode consultá-los diretamente pelas Ferramentas de Desenvolvimento:</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>A resposta tem aparência semelhante a esta:</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>O campo <code>counterName</code> é o ID do dashboard, e <code>count</code> é a contagem cumulativa de visualizações para aquele dashboard naquele dia específico. Kibana cria um objeto contador por dashboard por dia; você pode ver o sufixo de data no ID do objeto (... visualizado:servidor:20260310). A contagem cresce ao longo do dia à medida que os usuários abrem o dashboard.</p><p>Em vez de replicar esse modelo de documento diário em nosso índice, criaremos um documento por execução de fluxo de trabalho. Cada documento registra quantas visualizações aquele dashboard acumulou no dia no momento da captura.</p><h2>Passo 2: criar o índice de destino</h2><p>Precisamos de um índice para armazenar os snapshots da vista do nosso dashboard. O comando a seguir cria com mapeamentos explícitos para que possamos agregar e visualizar depois. Execute isso nas ferramentas de desenvolvimento:</p>PUT dashboard-views
{
  "mappings": {
    "properties": {
      "captured_at": {
        "type": "date"
      },
      "dashboard_id": {
        "type": "keyword"
      },
      "dashboard_name": {
        "type": "keyword"
      },
      "view_count": {
        "type": "integer"
      }
    }
  }
}<p>O uso de mapeamentos <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/keyword"><code>keyword</code></a> para IDs e nomes permite <a href="https://www.elastic.co/docs/explore-analyze/query-filter/aggregations">agregações</a>. Usar <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/number"><code>integer</code></a> para <code>view_count</code> é um padrão seguro, já que o Kibana reinicia o contador diariamente e atingir o limite de 32 bits (mais de 2 bilhões de visualizações em um único dia) não é uma preocupação realista. Ainda permite operações numéricas, como <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> e <a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-metrics-min-aggregation"><code>min</code></a>, entre outras.</p><h2>Passo 3: Crie o fluxo de trabalho</h2><p>Acesse <strong>Stack Management &gt; Fluxo de trabalho &gt; Novo Fluxo de Trabalho</strong> e cole a seguinte configuração YAML do fluxo de trabalho:</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>Na próxima seção, vamos analisar o fluxo de trabalho passo a passo.</p><h3>Como funciona o fluxo de trabalho</h3><h4>Gatilhos</h4><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7672aa533b4bc9ed/6a17dc5b420229d07c29f4d4/5670991d65c64ee833924225c2d375a1be868b13-325x162.png" alt=" Gatilhos agendados" /><p>O fluxo de trabalho é executado com um gatilho programado a cada 30 minutos. Isso nos fornece dados de séries temporais sem sobrecarregar a API.</p><h4>buscar_visualizações_do_painel</h4><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltab2a16f1f11304ea/6a17dc5d25daab26f608a117/66eaec147c3d01c524c67cf1c7f663ac56a3259d-812x215.png" alt=" Buscar o dashboard" /><p>Usa <code>kibana.request</code> para chamar a API de objetos salvos do Kibana. Não é necessário configurar autenticação: o motor de fluxo de trabalho anexa automaticamente os cabeçalhos corretos com base no contexto de execução.</p><h4>index_each_dashboard (foreach)</h4><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte6b2611b0216555e/6a17dc5f445de95b584cffe5/aad45e8aed8dc81ded6260cd6199ff78dcffe3b4-1892x290.png" alt="Indexar cada dashboard" /><p>Itera sobre o array <a href="https://www.elastic.co/docs/api/doc/kibana/group/endpoint-saved-objects"><code>saved_objects</code></a> retornado pela etapa anterior. O item atual em cada iteração está disponível como <code>foreach.item</code>. Dentro do loop, executamos duas etapas aninhadas para cada dashboard.</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=": Buscar nome do dashboard" /><p>Resolve o título do dashboard legível por humanos chamando <code>GET /api/saved_objects/dashboard/{id}</code>. Adicionamos <code>on-failure: continue: true</code> para que, se um dashboard for excluído mas ainda tiver contadores de visualização, o loop continue em vez de falhar toda a execução.</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=" Solicitação do Elasticsearch" /><p>Indexa cada documento usando <code>POST /dashboard-views/_doc</code> (sem um ID explícito), o que permite que o Elasticsearch gere IDs automaticamente. Isso cria um novo documento a cada execução, construindo um histórico de contagens de visualizações ao longo do tempo, em vez de sobrescrever o snapshot anterior.</p><p>Duas coisas que valem a pena notar:</p><ul><li><p>O campo <code>captured_at</code> usa o filtro de data para formatar o carimbo de data/hora como <a href="https://www.iso.org/iso-8601-date-and-time-format.html">ISO 8601</a>. Sem isso, o valor aparece como uma string de data em JavaScript, como <code>Tue Mar 10 2026 05:03:47 GMT+0000</code>, que o Elasticsearch não mapeia como data.</p></li><li><p>O <code>view_count</code> usa a sintaxe <code>${{ }}</code> com <code>| plus: 0</code> para preservar o tipo numérico. Usar <code>{{ }}</code> o renderizaria como uma string, o que impediria operações matemáticas no dashboard.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3b94bb6c22c0253e/6a17dc6425daab37a508a11b/6d48c8784d5df6192e8b5175e69dbab5098194bc-919x774.png" alt="" /><p><em>A UI permite que você depure cada uma das etapas do fluxo de trabalho.</em></p><h2>Etapa 4: Crie o dashboard de estatísticas</h2><p>Depois que o fluxo de trabalho for executado algumas vezes e os dados forem coletados, crie um novo dashboard no Kibana usando a Data view dashboard-views.</p><p>Alguns painéis para começar:</p><ul><li><p><strong>Principais dashboards por visualizações:</strong> use um <a href="https://www.elastic.co/docs/explore-analyze/visualize/charts/bar-charts"><strong>gráfico de barras</strong></a> com <code>dashboard_name</code> no eixo X e <code>last_value(view_count)</code> no eixo Y. Isso mostra a contagem diária atual de visualizações por dashboard.</p></li><li><p><strong>Visualizações ao longo do tempo:</strong> use um <a href="https://www.elastic.co/docs/explore-analyze/visualize/charts/line-charts"><strong>gráfico de linhas</strong></a> com <code>captured_at</code> no eixo X e <code>last_value(view_count)</code> no eixo Y, dividido por <code>dashboard_name</code>. Como cada execução adiciona um novo documento, use o último valor para obter a contagem de picos por buckets, em vez de somar duplicados.</p></li><li><p><strong>Snapshot atual:</strong> use uma <a href="https://www.elastic.co/docs/explore-analyze/visualize/charts/tables"><strong>tabela de dados</strong></a> com os <code>captured_at</code> mais recentes para mostrar as contagens de visualizações mais recentes em todos os dashboards.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt18d0390e0526b215/6a17dc65a292991da7d02b79/e245b95f67daf76a2aaf4cb9df2c75ef4cfef582-1462x747.png" alt="" /><p>Como cada fluxo de trabalho cria um novo documento, você pode filtrar por faixa de tempo para analisar a atividade em períodos específicos, comparar semana a semana ou criar alertas quando um dashboard cair abaixo de um limite de visualização.</p><h2><strong>Conclusão</strong></h2><p>O Elastic Workflows é uma boa opção para esse tipo de coleta periódica de dados porque tanto a fonte (Kibana API) quanto o destino (Elasticsearch) são nativos, o que significa zero gerenciamento de credenciais. O motor de fluxo de trabalho lida automaticamente com autenticação para <code>kibana.request</code> e <code>elasticsearch.request</code> etapas, então a única coisa que você escreve é a lógica.</p><h2><strong>Recursos</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 do 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[Experiência do Desenvolvedor]]></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[Gerenciamento de dependências no Kubernetes]]></title>
    <description><![CDATA[Como simplificar o gerenciamento de dependências no Kubernetes usando a CLI do Renovate e os fluxos de trabalho do Argo.]]></description>
    <content:encoded><![CDATA[<p>Foi assim que construímos uma plataforma de gerenciamento de dependências auto-hospedada usando Kubernetes, Argo Workflows, Argo Events e CLI de Renovate para automatizar atualizações, corrigir de forma rápida vulnerabilidades e exposições comuns (CVEs) e propagar com eficiência novas versões de pacotes em milhares de repositórios.</p><h2><strong>Gerenciamento de dependências no Elastic</strong></h2><p>Na Elastic, precisamos gerenciar centenas ou até milhares de repositórios, tanto privados quanto públicos. Quando um CVE crítico é descoberto, precisamos de respostas e ações imediatas: quais repositórios são vulneráveis? Com que rapidez podemos corrigir os problemas? Além da segurança, também surgem questões de produtividade: como podemos propagar de forma rápida o lançamento de uma nova versão do pacote em todos os repositórios que dependem dela sem gastar muito tempo em tarefas manuais?</p><p>O gatilho inicial para pesquisar maneiras de fazer o gerenciamento de dependências foi a necessidade de estabelecer uma base segura com atualizações automatizadas para <a href="https://www.elastic.co/blog/reducing-cves-in-elastic-container-images">reduzir os CVEs</a>. Após considerar cuidadosamente soluções para gerenciamento de dependências, começamos a trabalhar em uma infraestrutura auto-hospedada. Estávamos usando nosso próprio cluster Kubernetes para executar o Mend Renovate Community Self-Hosted. A ideia era fornecer uma plataforma de gerenciamento de dependências que nossos usuários pudessem acessar de forma autônoma.</p><p>O experimento inicial foi bem-sucedido, então mais e mais equipes começaram a integrar nossa plataforma e usá-la no ciclo de vida diário dos repositórios para atualizações e patches de CVE. Isso aconteceu tão rápido que logo chegamos ao limite da nossa instalação auto-hospedada.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc99617fc3eed538d/6a170ea9964cea459d08bc67/e14d9f98d4eccaa08a335d5bd23d88e5debbb344-1600x1103.png" alt="Gerenciamento de dependências no Elastic" /><h3><strong>O desafio: como podemos redimensionar uma plataforma de gerenciamento de dependências em uma grande organização com um número significativo de repositórios?</strong></h3><p>Nossa plataforma de gerenciamento de dependências estava processando um repositório por vez e o modelo de processamento sequencial não conseguia acompanhar, devido ao grande número de repositórios que possuímos. Já havíamos identificado que o problema residia no conceito de que <strong>uma única instância</strong> de nossa ferramenta de gerenciamento de dependências poderia processar nossa grande e crescente lista de repositórios. Repositórios aguardavam em uma fila, às vezes por muitas horas. Mais de 50% dos nossos repositórios nem sequer eram processados diariamente. Isso significa que mais de 50% dos nossos repositórios esperaram mais de 24 horas entre as varreduras.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0d205fd379e3c07a/6a170eab961e691e1fc4cfca/45ade5bda08f82bed0b3d0d3736cbd6f056e7a4e-1312x816.jpg" alt="Problema de gerenciamento de dependências" /><p>Repositórios grandes criavam gargalos maiores, devido às bases de código consideráveis e aos múltiplos PRs abertos. Eventos do webhook do GitHub interromperam a sequência. O Automerge tornou-se não confiável porque o tempo de varredura era imprevisível. Fizemos uma promessa aos nossos usuários sobre a frequência dos escaneamentos, mas não conseguimos cumpri-la.</p><h3><strong>A decisão de criar internamente: atendendo às necessidades únicas de escala e segurança da Elastic</strong></h3><p>Enquanto considerávamos opções comerciais, incluindo a <strong>edição Mend's Renovate Self-Hosted Enterprise Self-Hosted</strong>, internamente na Elastic tivemos algumas iniciativas-chave em desenvolvimento.</p><p>Nossa decisão de criar uma plataforma interna foi motivada pelo reconhecimento de que somente uma solução personalizada poderia atender aos requisitos específicos e inegociáveis da Elastic:</p><ol><li><p><strong>Investindo em nossa plataforma interna de desenvolvedores:</strong> naquela época, já tínhamos começado a investir fortemente em nossa plataforma interna de desenvolvedores. Estávamos discutindo e projetando formas de como cada um dos nossos serviços poderia se encaixar nisso. Isso significava que queríamos testar nossas próprias regras e práticas para nossa plataforma de gerenciamento de dependências. Além disso, novas diretrizes estavam entrando em ação e queríamos projetar a plataforma antes dos eventos.</p></li><li><p><strong>Integração nativa e personalização do fluxo de trabalho:</strong> precisávamos de uma integração direta com nossas ferramentas e processos internos. Por exemplo, queríamos centralizar a configuração como código com nosso Catálogo de serviços (Backstage). Temos necessidades específicas relacionadas ao uso do Backstage que queríamos tornar compatíveis com nossa plataforma. Portanto, embora fosse possível usar as APIs Renovate Self-Hosted junto com nossa automação Backstage, isso não cobriria totalmente nossos processos internos.</p></li><li><p><strong>Segurança de defesa em profundidade específica da Elastic:</strong> nossa rigorosa conformidade de segurança exigiu mecanismos de segurança personalizados, adaptados ao nosso ecossistema. Estávamos trabalhando para <a href="https://entro.security/blog/how-elastic-scaled-secrets-nhi-security-elastics-playbook-from-visibility-to-automation/">fortalecer nosso uso de "identidades não humanas".</a> A forma como esse reforço de acesso funcionava significava que os métodos não padronizados de autenticação no GitHub não funcionariam com uma ferramenta comercial que não suportasse essa implementação interna. Nosso fluxo de trabalho incluía a implementação de um padrão de criptografia secreta de fluxo de trabalho pai-filho e o uso de tokens transitórios e de uso único do GitHub. Criar internamente foi a única maneira prática de incorporar essas camadas de segurança exclusivas e minimizar a superfície de ataque em nosso complexo ambiente multinuvem.</p></li></ol><h2><strong>A solução: orquestração de fluxo de trabalho para gerenciamento de dependências</strong></h2><p>Nossa solução começou com o fato de queríamos criar sobre a ferramenta de gerenciamento de dependências que já usávamos e não substituí-la, buscando outras soluções. Ela já demonstrava sinais de potencial, e a flexibilidade é importante para diferentes necessidades em toda a organização. Consideramos diferentes soluções, e o que nos ajudou a decidir foram as necessidades, às vezes grandes e especiais, que precisamos cobrir. Decidimos criar uma plataforma de gerenciamento de dependências confiável e escalável, na qual cada repositório será processado por conta própria, removendo gargalos e nos preparando para o crescimento.</p><p>Projetamos a plataforma seguindo três princípios fundamentais:</p><h3><strong>1. Processamento paralelo</strong></h3><p>Cada repositório recebe o próprio ambiente de processamento de gerenciamento de dependências. Não há mais filas. Nossa concorrência é limitada apenas pelo número de recursos que gastamos. Também aplicamos o agendamento distribuído inteligente para evitar que o GitHub limite a taxa.</p><h3><strong>2. Autoatendimento</strong></h3><p>Usamos nosso Catálogo de serviços (Backstage) para integrar e gerenciar automaticamente qualquer novo repositório. Usamos nossa própria definição de recursos para dar ao usuário final a opção de selecionar com que frequência um repositório será processado, quantos recursos deseja alocar para os cronogramas e se deseja desligar ou reativar o processamento por qualquer motivo. Planejamos adicionar mais opções assim conforme as necessidades dos nossos usuários evoluem e eles se familiarizam com a nova instalação.</p><h3><strong>3. Redução do escopo secreto e isolamento do espaço de nome</strong></h3><p>Para mais segurança, fornecemos aos nossos pods de gerenciamento de dependências tokens efêmeros do GitHub que são gerados no início de cada fluxo de trabalho. Além disso, isolamos nossas cargas de trabalho em espaços de nome específicos para que possam receber apenas os segredos necessários. Controlamos quais segredos podem ser acessados em cada fluxo de trabalho de gerenciamento de dependências usando o Kubernetes RBAC. Também usamos criptografia para propagar o token do GitHub do fluxo de trabalho pai para o filho.</p><p>Reconstruímos nossa plataforma usando e aproveitando o melhor de Kubernetes, do Argo Workflows que alimenta a lógica dos nossos processos, e a CLI do Renovate que está configurado para escanear e processar um repositório de cada vez.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3548539ab52fbb79/6a170eac0c48573da601ab26/5560ed20e2bd9ecdd574a9c835126d12b24c332f-1600x1157.png" alt="Visão geral dos fluxos de trabalho de gerenciamento de dependências no Kubernetes" /><p><strong>A beleza:</strong> estamos utilizando projetos open source testados de forma inovadora, fornecendo novos exemplos práticos para todos esses projetos e, ao mesmo tempo, ampliando a velocidade de desenvolvimento e consolidando a redução de CVE para nossas equipes.</p><h2><strong>Arquitetura de gerenciamento de dependências: quatro microsserviços</strong></h2><p>A plataforma é composta por quatro componentes personalizados:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6451ff19da4db511/6a170eaec1e8a562e0f88378/2b3d4046c05bb261e45d40c59f864eb51fb9eaa9-1217x1600.png" alt="Componentes para gerenciamento de dependências no Kubernetes" /><h3><strong>Operador de fluxos de trabalho (Go/Kubebuilder)</strong></h3><p>Um operador do Kubernetes gerenciando o ciclo de vida do fluxo de trabalho por meio de três definições de recursos personalizadas (CRDs):</p><ul><li><p><strong>RepoConfig CRD:</strong> fonte única de verdade para configuração de repositórios.</p></li></ul><p>É assim que o RepoConfig é definido no operador:</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>E essa é a aparência de uma instância do 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 pai:</strong> gerencia os fluxos de trabalho do CronWorkflows para varreduras agendadas.</p></li></ul><p>Dentro do loop de reconciliação do controlador principal, garantimos que as configurações de fluxo de trabalho sejam criadas e mantidas atualizadas ou até mesmo excluídas, se necessário.</p><p>Primeiro, ele recebe algumas configurações globalmente configuradas para fluxos de trabalho:</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>Ele garante que um mutex configmap esteja atualizado para evitar fluxos de trabalho semelhantes rodando juntos:</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>Depois, cria um Gerenciador de fluxo de trabalho que é a estrutura que criará ou atualizará os CronWorkflows e os Modelos de fluxo de trabalho:</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>Child CRD:</strong> gerencia WorkflowTemplates com recursos por repositório.</p></li></ul><p>O controlador filho tem uma função de reconciliação semelhante à do controlador pai, mas desta vez é responsável pelos modelos de fluxo de trabalho no espaço de nome filho que serão acionados pelos fluxos de trabalho pai.</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="Fluxos de trabalho para gerenciamento de dependências no Kubernetes" /><p>O padrão multicontrolador proporciona separação clara: o RepoConfig Controller cuida do onboarding/offboarding, o Parent Controller gerencia o escalonamento e o Child Controller gerencia os templates de execução.</p><h3><strong>GitHub Events Gateway (Go)</strong></h3><p>Um proxy seguro de webhook que recebe webhooks do GitHub, verifica assinaturas, filtra por organização/repositório e direciona para os eventos do Argo. Criamos 10 sensores distintos que respondem a interações com dashboards de dependência, eventos de PR e atualizações de pacotes.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7e748ceb93c13a5a/6a170eb1a6c2b908f8e797b4/4828625456cbd6efa8020a20f10d23f294f98a02-1306x1600.png" alt="interações de painel de dependência no Kubernetes" /><p>Esse gateway permite a integração com os apps do GitHub por meio de:</p><ul><li><p>Verificação de assinaturas de webhooks do GitHub recebidas para fins de segurança.</p></li><li><p>Encaminhando eventos válidos para o Argo Events EventSource com todos os cabeçalhos e autenticação relevantes.</p></li><li><p>Nós também configuramos um authSecret no EventSource e fornecemos isso como um cabeçalho Bearer nas requisições encaminhadas.</p></li><li><p>Fornecer loggings, métricas e lógica de repetição.</p></li></ul><p>Ele realiza diversas validações em cada solicitação de evento do GitHub.</p><p>Ele garante que alguns atributos HTTP estejam presentes:</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>Embora também valide a assinatura de cada solicitação e da organização.</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>Por fim, ele direciona para Argo Events com base no tipo de evento:</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>Do lado da Argo Events, 10 sensores observam o Argo Events EventBus para novos eventos:</p>apiVersion: argoproj.io/v1alpha1
kind: Sensor
metadata:
  name: {{ .Values.sensors.packageUpdateOnDefaultBranch.name }}
  namespace: {{ .Release.Namespace }}
spec:
  eventBusName: {{ .Values.eventBus.name }}<p>Então, o script aplica a lógica de cada sensor:</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>Sincronizador de Backstage (Go)</strong></h3><p>Este procedimento consulta nosso Catálogo de serviços (Backstage) em busca de Entidades de recursos reais do repositório, transforma-as em CRDs do RepoConfig e mantém a plataforma sincronizada com as alterações de configuração. As alterações são aplicadas em três minutos.</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>Por fim, ele grava esses dados nas instâncias do RepoConfig.</p><h3><strong>Base de fluxos de trabalho (Misto: JavaScript, Go, Helm)</strong></h3><p>A camada fundamental contém gráficos Helm, configurações JavaScript, um wrapper Go para a CLI do Renovate com suporte a criptografia e um indexador de APK personalizado para pacotes Alpine.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4c1b5b840854ddf5/6a170eb47d8d67694e70e7e8/908d19278face3ce1119dbee9146c1264b6e2f30-1600x873.png" alt=" Componentes fundamentais para gerenciamento de dependências no Kubernetes" /><h2><strong>Configuração de autoatendimento</strong></h2><p>As equipes configuram os repositórios de forma declarativa através do Backstage:</p>spec:
  renovate:
    enabled: true
    config:
      resourceGroup: LARGE      # SMALL | MEDIUM | LARGE  
      runFrequency: "0 */4 * * *"  # Every 4 hours<p>Grupos de recursos alocam CPU e memória com base no tamanho do repositório:</p><ul><li><p><strong>PEQUENA:</strong> CPU de 500m, memória de 1Gi.</p></li><li><p><strong>MÉDIA:</strong> CPU de 1000m, memória 2Gi.</p></li><li><p><strong>GRANDE:</strong> CPU de 2000 m, memória de 4Gi.</p></li></ul><p>A configuração é controlada por versão, auditável e aplicada automaticamente.</p><h2><strong>O padrão pai-filho</strong></h2><p>O modelo de execução utiliza um padrão de fluxo de trabalho pai-filho:</p><ul><li><p><strong>Fluxo de trabalho pai:</strong> CronWorkflow leve executando conforme programado. Criptografa segredos, determina se uma verificação deve ser executada, passa a configuração para o filho.</p></li><li><p><strong>Fluxo de trabalho filho:</strong> pod efêmero onde a CLI do Renovate executa. Recursos alocados dinamicamente, descriptografam segredos isoladamente, encerram após a conclusão.</p></li></ul><p>Essa separação oferece segurança (segredos criptografados no nível dos pais), otimização de recursos (os pais utilizam recursos mínimos) e escalabilidade (os filhos executam em paralelo).</p><h2><strong>Os resultados</strong></h2><h3><strong>Transformação de desempenho</strong></h3><ul><li><p><strong>Antes:</strong> um repositório por vez, alguns repositórios não seriam processados, possivelmente nem mesmo por um dia ou mais, menos de 1.000 digitalizações por dia.</p></li><li><p><strong>Após:</strong> mais de 100 varreduras simultâneas, geralmente 8.000 varreduras e até 10.000 varreduras registradas por dia, limitadas apenas pela quantidade de recursos que estamos dispostos a investir e por como lidamos com os limites de taxa do GitHub.</p></li></ul><h3><strong>Eficiência de custos</strong></h3><p>Por mais estranho que pareça, rodar 8.000 pods por dia pode te dar o mesmo resultado muito mais barato do que ter um pod de longa duração tentando alcançar os mesmos resultados.</p><p>Na configuração anterior, estávamos executando uma única instância que, em um bom dia, realizaria de 500 a 600 verificações. Ao mesmo tempo, devido ao fato de que diferentes tipos de repositórios seriam executados no mesmo pod, precisávamos dimensionar o pod para os maiores. Esse tamanho seria muito maior do que nossa oferta extra grande atual, usando 8 CPUs para o pod e 16G de memória.</p><p>Para atender à saída diária atual, o pod único precisaria executar por 12 dias. Então, comparando o custo desse único pod funcionando por 12 dias com 8.000 pods do nosso tamanho “MÉDIO” funcionando todos os dias, nosso novo design é muito mais eficiente para a mesma saída de escaneamentos:</p><p>Métrica</p><p>Cenário A (Fluxos de trabalho)</p><p>Cenário B (O único pod de longa duração)</p><p>Configuração</p><p>8.000 pods (1 vCPU / 2 GB)</p><p>1 pod (8 vCPU / 16 GB)*</p><p>Duração</p><p>10 minutos cada</p><p>12 dias contínuos</p><p>Tempo total de trabalho</p><p>1.333 horas de computação</p><p>288 horas de computação</p><p>Custo total</p><p>$ 65,83</p><p>$ 113,75</p><p>No entanto, vamos levar em consideração que nossa configuração padrão para nossas cargas de trabalho está definida como "PEQUENA", com a grande maioria funcionando com sucesso com 0,5 CPU e 1 GB de RAM, e apenas algumas precisam ser alteradas para média ou grande. Vamos ver o que acontece se 60% das nossas cargas de trabalho rodarem em "PEQUENA", 30% em "MÉDIA" e 10% em "GRANDE", o que está mais próximo da verdade.</p><p>Métrica</p><p>Cenário A (Enxame misto)</p><p>Cenário B (O de longa duração)</p><p>Estratégia</p><p>8.000 pods (tamanhos variados)</p><p>1 pod (8 vCPU / 16 GB)*</p><p>Duração</p><p>10 minutos cada</p><p>12 dias contínuos</p><p>Custo total</p><p>$ 52,66</p><p>$ 113,75</p><p>Economia</p><p>$ 61,09 (54% mais barato)</p><p>—</p><p>Podemos ver que, para a mesma saída, somos muito mais econômicos em nosso sistema atual.</p><h3><strong>Segurança aprimorada</strong></h3><ul><li><p>Tokens efêmeros do GitHub (minutos de exposição versus dias).</p></li><li><p>Isolamento de espaço de nome com limites de Controle de acesso por função (RBAC).</p></li><li><p>Criptografia de segredos em repouso nos fluxos de trabalho principais.</p></li><li><p>Acesso direto ao cofre removido.</p></li></ul><h3><strong>Desempenho previsível</strong></h3><p>Com frequência de varredura garantida, finalmente podemos definir Objetivos de nível de serviço (SLOs). A automerge funciona de forma confiável. As equipes confiam que a plataforma vai entregar o que é prometido.</p><h2><strong>Principais decisões arquitetônicas</strong></h2><p>Aqui estão algumas das principais decisões de design que moldaram a aparência da plataforma.</p><ul><li><p><strong>Por que fluxos de trabalho pai-filho?</strong></p></li></ul><p>Adotamos esse padrão para implementar uma estratégia de <strong>defesa em profundidade</strong>. Ao restringir credenciais de alto valor (como segredos de app GitHub) a um espaço de nome dedicado e bloqueado, utilizamos <strong>RBAC</strong> para garantir que pods de execução efêmeros não possam acessar arbitrariamente dados sensíveis. Vulnerabilidades recentes na cadeia de suprimentos (por exemplo, os ataques de integração contínua/entrega contínua [CI/CD] <strong>"Shai Hulud"</strong>) demonstraram a importância crítica de isolar os ambientes de execução que executam scripts dinâmicos do repositório de credenciais.</p><p>Ao mesmo tempo, essa dissociação permite a <strong>otimização granular de recursos</strong>. Os fluxos de trabalho "pai" atuam como orquestradores leves com um espaço mínimo, enquanto os fluxos de trabalho "filho" lidam com a verificação de dependências com uso intensivo de computação. Essa separação simplifica <strong>gestão de ciclo de vida,</strong> permitindo aplicar uma lógica de reconciliação distinta a cada camada, concedendo aos usuários controle sobre os parâmetros de execução (camada filha) e, ao mesmo tempo, mantendo o controle administrativo sobre a infraestrutura de agendamento e segurança (camada pai).</p><ul><li><p><strong>Por que é do tipo autoatendimento?</strong></p></li></ul><p>Eliminar nossa equipe como gargalo para a configuração do repositório era uma exigência crítica. Nossa missão era arquitetar uma <strong>plataforma escalável e de autoatendimento</strong> compatível com diversos casos de uso. Reconhecemos que atuar como <strong>guardiões</strong> de cada alteração de configuração era insustentável, dado o grande volume de repositórios. Em vez disso, adotamos uma filosofia de capacitação: fornecer os "trilhos" (infraestrutura e <strong>proteções</strong>) e capacitar os usuários a conduzir os "trens" (execução e personalização). Acreditamos que essa mudança em direção à <strong>autonomia da equipe</strong> aumenta significativamente a produtividade, permitindo que os usuários adaptem o sistema às suas necessidades operacionais específicas.</p><ul><li><p><strong>Por que o padrão Operator do Kubernetes?</strong></p></li></ul><p>Como mencionado acima, um princípio fundamental de design era garantir que a plataforma fosse totalmente <strong>autoatendida</strong>. Precisávamos de um mecanismo automatizado para capturar a intenção do usuário (como alternar varreduras, ajustar a frequência de agendamento ou ajustar limites de recursos em tempo de execução) e propagar instantaneamente essas mudanças para os fluxos de trabalho subjacentes. Antecipando requisitos futuros, o sistema também precisava ser facilmente <strong>extensível</strong>.</p><p>Para alcançar esse objetivo, desenvolvemos um <strong>operador Kubernetes personalizado para gerenciamento de dependências</strong>. Ao usar <strong>CRDs</strong> como interface de configuração, estabelecemos um <strong>ciclo de reconciliação nativo do Kubernetes</strong>. Este operador monitora continuamente o estado desejado definido pelo usuário e orquestra automaticamente as atualizações necessárias na infraestrutura do fluxo de trabalho. Isso garante uma operação perfeita e <strong>orientada a eventos</strong>, onde a lógica da plataforma lida com toda a complexidade nos bastidores.</p><ul><li><p><strong>Por que projetar um GitHub Events Gateway?</strong></p></li></ul><p>Adotar uma <strong>arquitetura orientada a eventos (EDA)</strong> foi essencial para a capacidade de resposta da plataforma. Embora os fluxos de trabalho do CronWorkflows fornecessem uma programação de linha de base confiável, precisávamos de agilidade para lidar com <strong>execuções ad hoc, </strong>como usuários acionando varreduras manualmente por meio do dashboard. Para isso, precisávamos de um <strong>gateway de ingestão</strong> dedicado para validar a integridade da carga útil e rotear as solicitações de forma inteligente.</p><p>Avaliamos as soluções existentes, incluindo o EventSource nativo do GitHub para Argo, mas identificamos riscos significativos relacionados à <strong>sobrecarga operacional</strong> e às rígidas <strong>cotas da API do GitHub</strong> (por exemplo, limites de webhook por repositório). Consequentemente, construímos um gateway personalizado para desacoplar nossa infraestrutura dessas limitações.</p><p>Fundamentalmente, esse gateway serviu como um <strong>ponto estratégico de controle de tráfego</strong> durante nossa migração. Ele funcionou como um switch, permitindo que realizássemos uma <strong>implementação gradual e granular</strong> (mudança de tráfego) do sistema legado para a nova infraestrutura. Isso garantiu que a integração de milhares de repositórios fosse um processo controlado e sem riscos, e não uma transição de "big bang".</p><p></p><h2><strong>Lições aprendidas</strong></h2><p>Algumas lições que aprendemos andam de mãos dadas com o <a href="https://www.elastic.co/about/our-source-code">Elastic Source Code</a>:</p><ol><li><p><strong>O cliente em primeiro lugar: </strong>as plataformas são criadas para os usuários. Por isso, é importante ter as necessidades dos usuários como prioridade. Isso molda a plataforma em infraestrutura e aplicativos projetados de forma eficiente, que reduzem o atrito com os usuários, simplificam a escalabilidade da plataforma e facilitam a adoção.</p></li><li><p><strong>Espaço, tempo: </strong>às vezes, o caminho de menor resistência leva a <strong>areias movediças</strong>. Inicialmente, tentamos otimizar o modelo de processamento sequencial existente, mas isso não resolveu nossos problemas; na verdade, ele apenas introduziu mais complexidade e pontas soltas. A ousada decisão de <strong>reestruturar</strong> a plataforma com processamento paralelo exigiu um esforço inicial significativo. No entanto, isso acabou abrindo caminho para um crescimento sustentável da plataforma e praticamente eliminou o trabalho administrativo diário tedioso.</p></li><li><p><strong>TI, depende: </strong>uma plataforma não pode operar isoladamente; o sucesso depende de quão bem ela se integra ao ecossistema mais amplo. Em nosso caso, a integração com o <strong>Backstage</strong> foi fundamental, pois ele serve como a fonte da verdade para a integração perfeita de serviços. Da mesma forma, a conexão com o <strong>Artifactory</strong> nos permitiu gerenciar com eficiência as atualizações de pacotes privados, e a lista de integrações essenciais continua.</p></li><li><p><strong>Progresso, perfeição SIMPLES: </strong>durante toda a implementação, testamos constantemente nossas suposições iniciais e nos adaptamos a novas barreiras à medida que elas surgiam. Em vez de ficarmos paralisados pelo perfeccionismo, adotamos uma <strong>abordagem iterativa</strong>, enfrentando desafios um a um e ajustando nossa estratégia migratória para atender às condições do mundo real.</p></li></ol><h2><strong>O que vem a seguir</strong></h2><p>A entrega da plataforma nos permite realizar trabalhos mais significativos que ajudarão a melhorar a experiência do usuário e a eficiência da nossa plataforma. Alguns exemplos são:
</p><ul><li><p><strong>Aumentar e colocar proteções na adoção do auto-merge</strong></p></li></ul><p>O recurso de auto-merge acelera significativamente a velocidade da equipe ao eliminar tarefas manuais tediosas. No entanto, precisamos nos certificar de que existam <strong>proteções</strong> rígidas para garantir que esse aumento de velocidade não prejudique a segurança.
</p><ul><li><p><strong>Melhorar a observabilidade da experiência do usuário final</strong></p></li></ul><p>Uma prioridade crítica para nosso roadmap é aprimorar a observabilidade, não apenas no nível da plataforma, mas também especificamente da <strong>perspectiva do usuário final</strong>. Embora a captura de métricas de infraestrutura seja simples, entender a experiência real do usuário exige insights mais profundos. Estamos trabalhando para definir os indicadores-chave de desempenho centrados no usuário do núcleo (KPIs) para que nossa telemetria possa detectar pontos de atrito e problemas de desempenho <strong>antes que</strong> eles se transformem em reclamações dos usuários.</p><ul><li><p><strong>Remova obstáculos para a adoção</strong></p></li></ul><p>Vislumbrando o futuro, nossa prioridade é identificar e remover quaisquer barreiras que dificultem a adoção da plataforma. Seja desenvolvendo novas integrações ou implantando conjuntos específicos de recursos, estamos comprometidos com o planejamento orientado por dados. Criamos uma plataforma projetada para escalabilidade; nosso foco agora se volta para <strong>maximizar o potencial</strong>.
</p><h2><strong>O panorama maior</strong></h2><p>O projeto de fluxos de trabalho de gerenciamento de dependências demonstra um princípio mais amplo: <strong>quando você precisa redimensionar ferramentas open source além do modelo de implantação padrão, os padrões nativos do Kubernetes fornecem um caminho a seguir</strong>.</p><p>Ao adotar:</p><ul><li><p>CRDs para configuração.</p></li><li><p>Operadores para gestão de ciclo de vida.</p></li><li><p>Arquitetura orientada por eventos para capacidade de resposta</p></li><li><p>GitOps para implantação.</p></li></ul><p>Criamos uma orquestração que se redimensiona independentemente do número de repositórios que gerencia. O desempenho da varredura de um repositório é o mesmo, independentemente de estarmos gerenciando 100 ou 1.000.</p><p>Quando um CVE crítico é anunciado, agora temos respostas em minutos, não em horas. Essa é a diferença entre um gargalo e uma vantagem competitiva.</p><h2><strong>Agradecimentos</strong></h2><p>Esta plataforma utiliza excelentes ferramentas open source:</p><ul><li><p><strong>Kubebuilder:</strong> o framework open source que usamos para iniciar nossos operadores Kubernetes que inicializam e orquestram nossos fluxos de trabalho. [<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> o open source framework no qual construímos nosso Catálogo de serviços e que usamos como nossa versão final. [<a href="https://github.com/backstage/backstage">1</a>][<a href="https://backstage.io/">2</a>]</p></li><li><p><strong>Argo Workflows e Argo Events:</strong> a open source suíte que usamos para orquestrar processos complexos e adicionar processamento dinâmico baseado em eventos. [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>CLI do Renovate:</strong> a ferramenta de gerenciamento de dependências de open source que processa nossos repositórios. [<a href="https://github.com/renovatebot/renovate">1</a>][<a href="https://docs.renovatebot.com/getting-started/running/">2</a>]</p></li></ul><p>* O modelo de preços do AWS Fargate foi usado como referência para o custo de um único pod, embora nossas cargas de trabalho não estejam necessariamente sendo executadas na AWS, mas sim em clusters Kubernetes completos.</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[Experiência do Desenvolvedor]]></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[Apresentando a interface de usuário de regras de consulta do Elasticsearch no Kibana.]]></title>
    <description><![CDATA[Aprenda a usar a interface de regras de consulta do Elasticsearch para adicionar ou excluir documentos de consultas de pesquisa usando conjuntos de regras personalizáveis no Kibana, sem afetar o ranking orgânico.]]></description>
    <content:encoded><![CDATA[<p>A função de um mecanismo de busca é retornar resultados relevantes. No entanto, existem necessidades comerciais que vão além disso — como destacar promoções, priorizar produtos sazonais ou exibir itens patrocinados — e os desenvolvedores nem sempre podem fazer isso na consulta de pesquisa.</p><p>Além disso, esses casos de uso geralmente são sensíveis ao tempo, e passar pelas etapas típicas de desenvolvimento (criar uma ramificação de código e depois esperar por um novo lançamento) é um processo demorado.</p><p>E se pudéssemos realizar todo esse processo com apenas uma chamada de API, ou melhor ainda, com apenas alguns cliques no Kibana?</p><h2>Interface do usuário de regras de consulta</h2><p>O Elasticsearch 8.10 introduziu <a href="https://www.elastic.co/blog/introducing-query-rules-elasticsearch-8-10"><strong>as Regras de Consulta</strong></a> e <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers/rule-retriever"><strong>o Recuperador de Regras</strong></a>. São ferramentas projetadas para inserir <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-pinned-query"><em>resultados fixados</em></a> nas consultas sem afetar a classificação dos resultados orgânicos com base em regras. Eles apenas adicionam lógica de negócios aos resultados de forma declarativa e simples.</p><p>Alguns casos de uso comuns para regras de consulta são:</p><ul><li><p><strong>Destacar anúncios ou promoções</strong>: Exibir itens em promoção ou patrocinados no topo.</p></li><li><p><strong>Exclusão por contexto ou geolocalização</strong>: Ocultar determinados itens quando as regulamentações locais não permitem que você os mostre.</p></li><li><p><strong>Priorizar resultados-chave</strong>: Garantir que as pesquisas populares ou fixas estejam sempre no topo, independentemente do ranking orgânico.</p></li></ul><p>Para acessar a interface e interagir com essas ferramentas, você precisa clicar no menu lateral do Kibana e ir para <strong>Regras de Consulta</strong>, em <strong>Relevância:</strong></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltac12541cddd58e36/6a170853a29299941cd00fc2/242e33e89d1a07ffa0e76009c46b3a9236722741-458x1010.png" alt="Acessando regras de consulta no Elasticsearch em termos de relevância." /><p>Assim que o menu de regras de consulta aparecer, clique em <strong>Criar seu primeiro conjunto de regras:</strong></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcc28329c0f3c3aa9/6a17085547d49c67e22d893b/30b3a91bbbf243d314cf38298e01ca5cff784430-1600x945.png" alt="Criando seu primeiro conjunto de regras de consulta no Elasticsearch" /><p>Em seguida, você precisa dar um nome ao seu conjunto de regras.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb37d271297a4f148/6a170856a29299782cd00fc6/26c5462f88678867776f933b5655ca0df0d72a16-708x446.png" alt="Como nomear seu conjunto de regras de consulta no Elasticsearch" /><p>O formulário para definir cada regra possui três componentes principais:</p><ul><li><p><strong>Critérios</strong>: As condições que devem ser cumpridas para que a regra se aplique. Por exemplo, “quando o campo query_string contém o valor <em>Christmas</em>” ou “quando o campo country é <em>CO”.</em></p></li><li><p><strong>Ação</strong>: Isto é o que você deseja que aconteça quando as condições forem atendidas. Ele pode ser fixado (fixando um documento nos primeiros resultados) ou excluído (ocultando um documento).</p></li><li><p><strong>Metadados</strong>: São os campos que acompanham a consulta quando ela é executada. Podem incluir informações do usuário (como localização ou idioma), bem como dados de pesquisa (query_string). Esses são os valores usados pelos critérios para decidir se uma regra deve ou não ser aplicada.</p></li></ul><h2>Exemplo: itens populares</h2><p>Vamos imaginar que temos um site de comércio eletrônico com diversos itens. Ao analisarmos as métricas, notamos que um dos itens mais vendidos na categoria de consoles é o "Controle sem fio DualShock 4", especialmente quando os usuários pesquisam pelas palavras-chave "PS4" ou "PlayStation 4". Assim, decidimos colocar este produto no topo dos resultados, sempre que um usuário pesquisar por essas palavras-chave.</p><p>Primeiro, vamos indexar os documentos de cada item usando uma solicitação de API em lote:</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>Se não intervirmos na consulta, o item geralmente aparece em quarto lugar. Eis a pergunta:</p>GET products/_search
{
 "query": {
   "match": {
     "name": "PlayStation 4"
   }
 }
}<p>E aqui estão os resultados.</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>Vamos criar uma regra de consulta para alterar isso. Primeiro, vamos adicioná-lo ao conjunto de regras assim:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1576d4f4a2e60548/6a170858cdacbfccb07d298d/fdc42646fb3e76a09bca7d19047a76efe343f7a2-1600x650.png" alt="Como editar um conjunto de regras de consulta no Elasticsearch" /><p>Ou <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-query-rules-put-ruleset">solicitação de API</a> equivalente:</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>Para usar o <strong>conjunto de regras </strong>em nossa consulta, devemos usar um tipo de regra de consulta. Esse tipo de consulta é composto por duas partes principais:</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>: São os metadados usados para comparar com a consulta do usuário. Neste exemplo, o conjunto de regras é ativado quando o campo query_string tem o valor “PlayStation 4”.</p></li><li><p><strong>consulta</strong>: a consulta propriamente dita que será usada para pesquisar e obter os resultados orgânicos.</p></li></ul><p>Dessa forma, primeiro você executa a consulta orgânica e, em seguida, o Elasticsearch aplica as regras do seu conjunto de regras:</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>Exemplo: metadados baseados no usuário</h2><p>Outra aplicação interessante das Regras de Consulta é usar metadados para exibir documentos específicos com base em informações contextuais do usuário ou da página da web.</p><p>Por exemplo, vamos supor que queremos destacar itens ou ofertas personalizadas com base no nível de fidelidade do usuário, representado por um valor numérico.</p><p>Podemos fazer isso inserindo esses metadados diretamente na consulta, de forma que as regras sejam ativadas quando o valor atender a determinados critérios.</p><p>Primeiro, vamos indexar um documento que somente usuários com um alto nível de fidelidade podem ver:</p>POST _bulk
{ "index": { "_index": "products", "_id": "6" } }
{ "id": "6", "name": "PlayStation Plus Deluxe Card - 12 months", "category": "membership", "brand": "Sony", "price": 300 }<p>Agora, vamos criar uma nova regra dentro do mesmo conjunto de regras para que, quando o nível de lealdade for igual ou superior a 80, o item apareça no topo dos resultados.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt158578005df8c76d/6a17085aab7f086dc0db9de3/58de12dff93305440608f51465462fcc68653a08-1421x496.png" alt="Como editar um conjunto de regras de consulta no Elasticsearch" /><p>Salve a regra e o conjunto de regras.</p><p>Aqui está a solicitação REST equivalente:</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>Agora, ao executar uma consulta, precisamos incluir o novo parâmetro <strong>loyalty_level </strong>nos metadados. Se a condição da regra for atendida, o novo documento aparecerá no topo dos resultados.</p><p>Por exemplo, ao enviar uma consulta onde o nível de lealdade é 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>Veremos o documento de fidelidade acima dos resultados:</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>No caso abaixo, como o nível de fidelidade é 70, a regra não é atendida e o item não deve aparecer no topo:</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>Aqui estão os resultados:</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>Exemplo: exclusão imediata</h2><p>Vamos supor que nosso <strong>Controle Sem Fio DualShock 4 (ID 2)</strong> esteja temporariamente indisponível e não possa ser vendido. Assim, em vez de excluir o documento manualmente ou esperar que algum processamento de dados seja iniciado, a equipe comercial decide removê-lo dos resultados da pesquisa enquanto isso.</p><p>Usaremos um processo semelhante ao que acabamos de aplicar aos itens populares, mas desta vez, em vez de selecionar <em>"Fixados"</em>, escolheremos <em>"Excluir"</em>. Essa regra funciona como uma espécie de lista negra. Altere os critérios para <strong>"Sempre"</strong> para que a exclusão funcione sempre que a consulta for executada.</p><p>A regra deve ser assim:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt38564c0b7f4a6ee2/6a17085c1949f78692e7a989/f10971e4f1bc9520105111adfa3a476581a27130-1600x623.png" alt="Exemplo de um conjunto de regras de exclusão imediata no Elasticsearch" /><p>Salve a regra e o conjunto de regras para aplicar as alterações. Aqui está a solicitação REST equivalente:</p>PUT _query_rules/my-rules
{
  "rules": [
    {
      "rule_id": "rule-6358",
      "type": "pinned",
      "criteria": [
        {
          "type": "always"
        }
      ],
      "actions": {
        "docs": [
          {
            "_index": "products",
            "_id": "2"
          }
        ]
      }
    }
  ]
}<p>Agora, ao executar a consulta novamente, você verá que o item não está mais nos resultados, mesmo que a regra anterior fosse fixá-lo. Isso ocorre porque <strong>as exclusões têm prioridade sobre a fixação dos resultados</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>Conclusão</h2><p><strong>As regras de consulta</strong> tornam muito fácil ajustar a relevância sem qualquer alteração de código. A nova <strong>interface</strong> <strong>do Kibana </strong>permite que vocêPara fazer essas alterações em questão de segundos, você e sua equipe terão mais controle sobre os resultados da pesquisa.</p><p>Além do comércio eletrônico, as Regras de Consulta podem ser aplicadas em muitos outros cenários: destacar guias de solução de problemas em portais de suporte, exibir documentos internos importantes em bases de conhecimento, promover notícias de última hora em sites de notícias ou filtrar anúncios de emprego ou conteúdo expirados. Eles podem até mesmo impor regras de conformidade, como ocultar material restrito por função de usuário ou região.</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[Noções básicas]]></category>
    <category><![CDATA[Experiência do Desenvolvedor]]></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[Construindo um agente de conhecimento com recuperação semântica usando Mastra e Elasticsearch.]]></title>
    <description><![CDATA[Aprenda como construir um agente de conhecimento com recuperação semântica usando Mastra e Elasticsearch como armazenamento vetorial para memória e recuperação de informações.]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.elastic.co/search-labs/blog/context-engineering-overview">A Engenharia de Contexto</a> está se tornando cada vez mais importante na construção de agentes e arquiteturas de IA confiáveis. À medida que os modelos se tornam cada vez melhores, sua eficácia e confiabilidade dependem menos dos dados de treinamento e mais de quão bem eles estão fundamentados no contexto correto. Agentes que conseguem recuperar e aplicar as informações mais relevantes no momento certo têm muito mais probabilidade de produzir resultados precisos e confiáveis.</p><p>Neste blog, usaremos <a href="https://mastra.ai/">o Mastra</a> para construir um agente de conhecimento que memoriza o que os usuários dizem e consegue recuperar informações relevantes posteriormente, utilizando o Elasticsearch como backend de memória e recuperação. Você pode facilmente estender esse mesmo conceito a casos de uso do mundo real, como agentes de suporte que conseguem se lembrar de conversas e soluções anteriores, permitindo que eles personalizem as respostas para usuários específicos ou apresentem soluções mais rapidamente com base no contexto prévio.</p><p>Acompanhe aqui como construir isso passo a passo. Se você se perder ou simplesmente quiser executar um exemplo finalizado, confira o repositório <a href="https://github.com/jdarmada/getting-started-mastra-elastic/tree/main">aqui</a>.</p><h2>O que é Mastra?</h2><p>Mastra é um framework TypeScript de código aberto para a construção de agentes de IA com componentes intercambiáveis para raciocínio, memória e ferramentas. Seu recurso <a href="https://mastra.ai/docs/memory/semantic-recall">de recuperação semântica</a> permite que os agentes se lembrem e recuperem interações passadas, armazenando mensagens como representações vetoriais em um banco de dados vetorial. Isso permite que os agentes mantenham o contexto e a continuidade da conversa a longo prazo. O Elasticsearch é um excelente armazenamento de vetores para habilitar esse recurso, pois oferece suporte a buscas vetoriais densas e eficientes. Quando a recuperação semântica é acionada, o agente extrai mensagens relevantes do passado para a janela de contexto do modelo, permitindo que o modelo use esse contexto recuperado como base para seu raciocínio e respostas.</p><h2>O que você precisa para começar</h2><ul><li><p>Node v18+</p></li><li><p>Elasticsearch (versão 8.15 ou mais recente)</p></li><li><p>Chave da API do Elasticsearch</p></li><li><p><a href="https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key">Chave da API OpenAI</a></p></li></ul><p>Observação: você precisará disso porque a demonstração usa o provedor OpenAI, mas o Mastra é compatível com outros SDKs de IA e provedores de modelos da comunidade, então você pode facilmente trocá-lo dependendo da sua configuração.</p><h2>Construindo um projeto Mastra</h2><p>Usaremos a CLI integrada do Mastra para fornecer a estrutura básica do nosso projeto. Execute o comando:</p>npm create mastra@latest<p>Você receberá uma série de instruções, começando com:</p><p>1. Dê um nome ao seu projeto.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt87f941f654d03827/6a16f7af67045b214d45bfa1/2b9fe559e0276140dd539e24f916a73c60870405-620x84.png" alt="Como nomear um prompt no aplicativo Mastra" /><p>2. Podemos manter esta opção padrão; fique à vontade para deixar este campo em branco.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7dbb3d4f27435cac/6a16f7b0cdacbf29497d27de/e04729eb03bce8499e973e18c28642402340d0e5-852x68.png" alt="Indicar à Mastra onde guardar os arquivos de prompts." /><p>3. Para este projeto, usaremos um modelo fornecido pela OpenAI.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1f654f6cb9397e94/6a16f7b2964cea899a08b942/a86596a469a71bdf8bd99cbaf528d0f0cf7272c0-436x222.png" alt="Selecionando um modelo fornecido pela OpenAI no Mastra" /><p>4. Selecione a opção “Ignorar por enquanto”, pois armazenaremos todas as nossas variáveis de ambiente em um arquivo `.env` que configuraremos em uma etapa posterior.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltff106117521a3519/6a16f7b3c1e8a5031af880d8/02b19ccc34af0bdacf52fd94b519d036540ca2e6-426x114.png" alt="Por enquanto, vou optar por ignorar a chave da OpenAI." /><p>5. Também podemos ignorar esta opção.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcda7d9c51c3878d7/6a16f7b450916809dbe1b892/b3fe63d19d270bc2e0de1dd92033bf8b26750819-990x208.png" alt="" /><p>Assim que a inicialização estiver concluída, podemos passar para a próxima etapa.</p><h3>Instalando dependências</h3><p>Em seguida, precisamos instalar algumas dependências:</p>npm install ai @ai-sdk/openai @elastic/elasticsearch dotenv<ul><li><p><code>ai</code> - Pacote Core AI SDK que fornece ferramentas para gerenciar modelos de IA, prompts e fluxos de trabalho em JavaScript/TypeScript. O Mastra é construído sobre o <a href="https://ai-sdk.dev/">SDK de IA</a> da Vercel, portanto, precisamos dessa dependência para permitir as interações do modelo com o seu agente.</p></li><li><p><code>@ai-sdk/openai</code> - Plugin que conecta o SDK de IA aos modelos da OpenAI (como GPT-4, GPT-4o, etc.), permitindo chamadas à API usando sua chave de API da OpenAI.</p></li><li><p><code>@elastic/elasticsearch</code> - <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/javascript">Cliente oficial do Elasticsearch para Node.js</a>, Utilizado para conectar-se ao seu Elastic Cloud ou cluster local para indexação, pesquisa e operações vetoriais.</p></li><li><p><code>dotenv</code> - Carrega variáveis de ambiente de um arquivo .env arquivo em process.env, permitindo que você insira credenciais com segurança, como chaves de API e endpoints do Elasticsearch.</p></li></ul><h3>Configurando variáveis de ambiente</h3><p>Crie um arquivo <code>.env</code> no diretório raiz do seu projeto, caso ainda não exista um. Alternativamente, você pode copiar e renomear o exemplo <code>.env</code> que eu forneci no <a href="https://github.com/jdarmada/getting-started-mastra-elastic/blob/main/.env.example">repositório</a>. Neste arquivo, podemos adicionar as seguintes variáveis:</p>ELASTICSEARCH_ENDPOINT="your-endpoint-here"
ELASTICSEARCH_API_KEY="your-key-here"
OPENAI_API_KEY="your-key-here"<p>Isso conclui a configuração básica. A partir daqui, você já pode começar a construir e orquestrar agentes. Vamos dar um passo além e adicionar o Elasticsearch como camada de armazenamento e busca vetorial.</p><h2>Adicionando o Elasticsearch como armazenamento vetorial.</h2><p>Crie uma nova pasta chamada <code>stores</code> e, dentro dela, adicione este <a href="https://github.com/jdarmada/getting-started-mastra-elastic/blob/main/src/mastra/stores/elastic-store.ts">arquivo</a>. Antes que a Mastra e a Elastic lancem uma integração oficial de armazenamento vetorial do Elasticsearch, <a href="https://github.com/abhiaiyer91">Abhi Aiyer</a>(CTO da Mastra) compartilhou esta classe protótipo inicial chamada <code>ElasticVector</code>. Em termos simples, ele conecta a abstração de memória do Mastra aos recursos de vetores densos do Elasticsearch, permitindo que os desenvolvedores utilizem o Elasticsearch como banco de dados de vetores para seus agentes.</p><p>Vamos analisar mais detalhadamente as partes importantes da integração:</p><h3>Ingestão do cliente Elasticsearch</h3><p>Esta seção define a classe <code>ElasticVector</code> e configura a conexão do cliente Elasticsearch com suporte para implantações padrão e sem servidor.</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>Isso cria uma nova interface de configuração que herda todas as opções do cliente Elasticsearch (como <code>node</code>, <code>auth</code>, <code>requestTimeout</code>) e adiciona nossas propriedades personalizadas. Isso significa que os usuários podem passar qualquer configuração válida do Elasticsearch juntamente com nossas opções específicas para ambientes sem servidor.</p></li><li><p><code>extends MastraVector</code>Isso permite que <code>ElasticVector</code> herde da classe base <code>MastraVector</code> do Mastra, que é uma interface comum à qual todas as integrações de armazenamento vetorial estão em conformidade. Isso garante que o Elasticsearch se comporte como qualquer outro backend vetorial do Mastra da perspectiva do agente.</p></li><li><p><code>private client: Client</code>Esta é uma propriedade privada que contém uma instância do cliente JavaScript do Elasticsearch. Isso permite que a classe se comunique diretamente com o seu cluster.</p></li><li><p><code>isServerless</code> e <code>deploymentChecked</code>: Essas propriedades funcionam em conjunto para detectar e armazenar em cache se estamos conectados a uma implantação do Elasticsearch sem servidor ou padrão. Essa detecção ocorre automaticamente no primeiro uso ou pode ser configurada explicitamente.</p></li><li><p><code>constructor(config: ClientOptions)</code>: Este construtor recebe um objeto de configuração (contendo suas credenciais do Elasticsearch e configurações opcionais sem servidor) e o usa para inicializar o cliente na linha <code>this.client = new Client(config)</code>.</p></li><li><p><code>super()</code>Isso chama o construtor base do Mastra, portanto, ele herda o registro de logs, os auxiliares de validação e outros recursos internos.</p></li></ul><p>Neste ponto, Mastra sabe que existe uma nova loja de vetores chamada <code>ElasticVector</code></p><h3>Detecção do tipo de implantação</h3><p>Antes de criar os índices, o adaptador detecta automaticamente se você está usando o Elasticsearch padrão ou o Elasticsearch Serverless. Isso é importante porque as implantações sem servidor não permitem a configuração manual de 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>O que está acontecendo:</p><ul><li><p>Primeiro verifica se você definiu explicitamente <code>isServerless</code> na configuração (ignora a detecção automática).</p></li><li><p>Chama a API <code>info()</code> do Elasticsearch para obter informações do cluster.</p></li><li><p>Verifica o <code>build_flavor field</code> (implantações sem servidor retornam <code>serverless</code>)</p></li><li><p>Se a opção de build não estiver disponível, a solução é verificar a descrição da versão.</p></li><li><p>Armazena o resultado em cache para evitar chamadas repetidas à API.</p></li><li><p>Se a detecção falhar, a implantação padrão será utilizada por padrão.</p></li></ul><p> Exemplo de uso:</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>Criando o armazenamento de “memória” no Elasticsearch</h3><p>A função abaixo configura um índice Elasticsearch para armazenar embeddings. Verifica se o índice já existe. Caso contrário, cria um com o mapeamento abaixo que contém um campo <code>dense_vector</code> para armazenar embeddings e métricas de similaridade personalizadas.</p><p>Algumas coisas a ter em conta:</p><ul><li><p>O parâmetro <code>dimension</code> representa o comprimento de cada vetor de incorporação, que depende do modelo de incorporação que você está usando. Em nosso caso, geraremos embeddings usando o modelo <code>text-embedding-3-small</code> da OpenAI, que produz vetores de tamanho <code>1536</code>. Usaremos esse valor como padrão.</p></li><li><p>A variável <code>similarity</code> usada no mapeamento abaixo é definida pela função auxiliar c<code>onst similarity = this.mapMetricToSimilarity(metric)</code>, que recebe o valor do parâmetro <code>metric</code> e o converte em uma palavra-chave compatível com o Elasticsearch para a métrica de distância escolhida.</p><ul><li><p>Por exemplo: Mastra usa termos gerais para similaridade de vetores como <code>cosine</code>, <code>euclidean</code> e <code>dotproduct</code>. Se passássemos a métrica <code>euclidean</code> diretamente para o mapeamento do Elasticsearch, ele geraria um erro porque o Elasticsearch espera que a palavra-chave <code>l2_norm</code> represente a distância euclidiana.</p></li></ul></li><li><p>Compatibilidade com ambientes sem servidor: o código omite automaticamente as configurações de shard e réplica para implantações sem servidor, pois estas são gerenciadas automaticamente pelo 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>Armazenar uma nova memória ou anotação após uma interação.</h3><p>Esta função recebe novos embeddings gerados após cada interação, juntamente com os metadados, e os insere ou atualiza no índice usando a API <code>bulk</code> do Elastic. A API <code>bulk</code> agrupa várias operações de gravação em uma única solicitação; essa melhoria no desempenho de indexação garante que as atualizações permaneçam eficientes à medida que a memória do nosso agente continua a crescer.</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>Consultar vetores semelhantes para recuperação semântica</h3><p>Essa função é o núcleo do recurso de recuperação semântica. O agente utiliza a busca vetorial para encontrar incorporações armazenadas semelhantes em nosso índice.</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>Por dentro do capô:</p><ul><li><p>Executa uma consulta <a href="https://www.elastic.co/docs/solutions/search/vector/knn">kNN</a> (k-vizinhos mais próximos) usando a API <code>knn</code> no Elasticsearch.</p></li><li><p>Recupera os K vetores mais semelhantes ao vetor de consulta de entrada.</p></li><li><p>Opcionalmente, aplica filtros de metadados para refinar os resultados (por exemplo, pesquisar apenas dentro de uma categoria ou intervalo de tempo específico).</p></li><li><p>Retorna resultados estruturados, incluindo o ID do documento, a pontuação de similaridade e os metadados armazenados.</p></li></ul><h2>Criando o agente de conhecimento</h2><p>Agora que vimos a conexão entre Mastra e Elasticsearch por meio da integração <code>ElasticVector</code> , vamos criar o próprio Agente de Conhecimento.</p><p>Dentro da pasta <code>agents</code>, crie um arquivo chamado <code>knowledge-agent.ts</code>. Podemos começar conectando nossas variáveis de ambiente e inicializando o cliente 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>Aqui, nós:</p><ul><li><p>Use <code>dotenv</code> para carregar nossas variáveis do nosso arquivo <code>.env</code> .</p></li><li><p>Verifique se as credenciais do Elasticsearch estão sendo inseridas corretamente e, em seguida, poderemos estabelecer uma conexão bem-sucedida com o cliente.</p></li><li><p>Passe o endpoint do Elasticsearch e a chave da API para o construtor <code>ElasticVector</code> para criar uma instância do nosso armazenamento de vetores que definimos anteriormente.</p></li><li><p>Opcionalmente, especifique <code>isServerless: true</code> se estiver usando o Elasticsearch Serverless. Isso elimina a etapa de detecção automática e melhora o tempo de inicialização. Caso seja omitido, o adaptador detectará automaticamente o seu tipo de implantação na primeira utilização.</p></li></ul><p>Em seguida, podemos definir o agente usando a classe <code>Agent</code> do 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>Os campos que podemos definir são:</p><ul><li><p><code>name</code> e <code>instructions</code>: Dê a ele uma identidade e uma função primária.</p></li><li><p><code>model</code>Estamos usando o <code>gpt-4o</code> da OpenAI por meio do pacote <code>@ai-sdk/openai</code> .</p></li><li><p><code>memory</code>:</p><ul><li><p><code>vector</code>: Aponta para o nosso armazenamento Elasticsearch, de onde os embeddings são armazenados e recuperados.</p></li><li><p><code>embedder</code>Qual modelo usar para gerar embeddings?</p></li><li><p><code>semanticRecall</code> As opções definem como funciona o recall:</p><ul><li><p><code>topK</code>Quantas mensagens semanticamente semelhantes devem ser recuperadas?</p></li><li><p><code>messageRange</code>: Qual a extensão da conversa a ser incluída em cada interação?</p></li><li><p><code>scope</code>Define o limite da memória.</p></li></ul></li></ul></li></ul><p>Quase pronto. Basta adicionarmos esse agente recém-criado à nossa configuração do Mastra. No arquivo chamado <a href="http://index.ts/"><code>index.ts</code></a>, importe o agente de conhecimento e insira-o no campo <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>Os outros campos incluem:</p><ul><li><p><code>storage</code>Este é o repositório de dados interno do Mastra para histórico de execuções, métricas de observabilidade, pontuações e caches. Para obter mais informações sobre o sistema de armazenamento Mastra, visite <a href="https://mastra.ai/docs/server-db/storage">aqui</a>.</p></li><li><p><code>logger</code>O Mastra utiliza <a href="https://github.com/pinojs/pino">o Pino</a>, que é um registrador JSON estruturado e leve. Ele registra eventos como início e término de agentes, chamadas e resultados de ferramentas, erros e tempos de resposta do LLM.</p></li><li><p><code>observability</code>Controla o rastreamento de IA e a visibilidade da execução de agentes. Ele rastreia:</p><ul><li><p>Início/fim de cada etapa de raciocínio.</p></li><li><p>Qual modelo ou ferramenta foi utilizada?</p></li><li><p>Entradas e saídas.</p></li><li><p>Pontuações e avaliações</p></li></ul></li></ul><h3>Testando o agente com o Mastra Studio</h3><p>Parabéns! Se você chegou até aqui, está pronto para executar este agente e testar suas capacidades de recuperação semântica. Felizmente, o Mastra oferece uma interface de chat integrada, então não precisamos criar a nossa própria.</p><p>Para iniciar o servidor de desenvolvimento do Mastra, abra um terminal e execute o seguinte comando:</p>npm run dev<p>Após a inicialização e o empacotamento iniciais do servidor, você deverá receber um endereço para o Playground.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte5f857fddc74ffc9/6a16f7b6a6c2b995d5e794c0/8b045f70008d26aec4d2e6b59d61085555b9c5b2-686x116.png" alt="Endereço do servidor para Playground" /><p>Cole este endereço no seu navegador e você será direcionado para o Mastra Studio.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc7fdda6ce46ce068/6a16f7b7b0367d4f7672bacf/69bc80fe8486edd9e0cf91d87b39f465aeb23111-1600x438.png" alt="Colar o endereço do Playground para acessar o Mastra Studio" /><p>Selecione a opção <code>knowledgeAgent</code> e comece a conversar.</p><p>Para um teste rápido para verificar se tudo está conectado corretamente, forneça algumas informações como: "A equipe anunciou que o desempenho de vendas em outubro aumentou 12%, impulsionado principalmente por renovações de contratos corporativos." O próximo passo é expandir o alcance aos clientes de médio porte.” Em seguida, inicie um novo bate-papo e faça uma pergunta como: "Em qual segmento de clientes dissemos que precisamos nos concentrar a seguir?" O agente de conhecimento deve ser capaz de recordar as informações que você lhe forneceu na primeira conversa. Você deverá ver uma resposta semelhante a esta:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfec3266e81a7213b/6a16f7b92b835f6f70f4afe2/da8ebddad89874023ed440a8f1ad2cb04ed043f4-1070x288.png" alt="Ao conversar com um agente de conhecimento no Mastra Studio, o agente consegue recuperar informações." /><p>Ao recebermos uma resposta como essa, significa que o agente armazenou com sucesso nossa mensagem anterior como embeddings no Elasticsearch e a recuperou posteriormente usando a busca vetorial.</p><h3>Inspecionando o armazenamento de memória de longo prazo do agente.</h3><p>Acesse a aba <code>memory</code> na configuração do seu agente no Mastra Studio. Isso permite que você veja o que seu agente aprendeu ao longo do tempo. Cada mensagem, resposta e interação que é incorporada e armazenada no Elasticsearch passa a fazer parte dessa memória de longo prazo. Você pode realizar buscas semânticas em interações passadas para encontrar rapidamente informações ou contextos que o agente aprendeu anteriormente. Este é essencialmente o mesmo mecanismo que o agente usa durante a recuperação semântica, mas aqui você pode inspecioná-lo diretamente. No exemplo abaixo, estamos pesquisando o termo "vendas" e obtendo como resultado todas as interações que incluíram algo relacionado a vendas.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte428134d7bf2a43a/6a16f7bbb0367d185872bad3/3decaa0c332d288c5ae0b11c25f592c7d50c2f0f-1104x1320.png" alt="Como inspecionar o armazenamento de memória de longo prazo dos agentes de conhecimento" /><h2>Conclusão</h2><p>Ao conectar o Mastra e o Elasticsearch, podemos fornecer memória aos nossos agentes, o que é uma camada fundamental na engenharia de contexto. Com a recuperação semântica, os agentes podem construir contexto ao longo do tempo, fundamentando suas respostas no que aprenderam. Isso significa interações mais precisas, confiáveis e naturais.</p><p>Essa integração inicial é apenas o ponto de partida. O mesmo padrão pode permitir que agentes de suporte se lembrem de chamados anteriores, bots internos recuperem documentação relevante ou assistentes de IA consigam recordar detalhes do cliente no meio da conversa. Também estamos trabalhando para uma integração oficial com o Mastra, tornando essa combinação ainda mais perfeita em um futuro próximo.</p><p>Estamos ansiosos para ver o que você vai construir em seguida. Experimente, explore <a href="https://mastra.ai/">o Mastra</a> e seus recursos de memória e sinta-se à vontade para compartilhar suas descobertas com a comunidade.</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 agêntica]]></category>
    <category><![CDATA[Experiência do Desenvolvedor]]></category>
    <category><![CDATA[Integrações]]></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>