Quando apresentamos o projeto NGINX Modern Apps Reference Architecture (MARA) no outono passado na Sprint 2.0, enfatizamos nossa intenção de que ele não fosse um "brinquedo" como algumas arquiteturas, mas sim uma solução "sólida, testada e pronta para ser implantada em aplicativos de produção ao vivo em execução em ambientes Kubernetes". Para tal projeto, ferramentas de observabilidade são um requisito absoluto. Todos os membros da equipe MARA vivenciaram em primeira mão como a falta de percepção do status e do desempenho torna o desenvolvimento e a entrega de aplicativos um exercício de frustração. Chegamos imediatamente a um consenso de que o MARA deve incluir instrumentação para depuração e rastreamento em um ambiente de produção.
Outro princípio orientador do MARA é a preferência por soluções de código aberto. Nesta postagem, descrevemos como nossa busca por uma ferramenta de observabilidade multifuncional e de código aberto nos levou ao OpenTelemetry e, em seguida, detalhamos as compensações, decisões de design, técnicas e tecnologias que usamos para integrar o OpenTelemetry a um aplicativo de microsserviços criado com Python, Java e NGINX.
Esperamos que ouvir sobre nossas experiências ajude você a evitar possíveis armadilhas e a acelerar sua adoção do OpenTelemetry. Observe que a publicação é um relatório de progresso com prazo determinado – prevemos que as tecnologias que discutimos amadurecerão dentro de um ano. Além disso, embora apontemos deficiências atuais em alguns projetos, somos imensamente gratos por todo o trabalho de código aberto que está sendo feito e estamos ansiosos para vê-lo progredir.
Como aplicativo a ser integrado a uma solução de observabilidade, escolhemos o Bank of Sirius , nosso fork do aplicativo de exemplo Bank of Anthos do Google. É um aplicativo web com uma arquitetura de microsserviços que pode ser implantado via infraestrutura como código . Há inúmeras maneiras de melhorar esse aplicativo em termos de desempenho e confiabilidade, mas ele está maduro o suficiente para ser considerado um aplicativo brownfield . Dessa forma, acreditamos que este é um bom exemplo para mostrar como integrar o OpenTelemetry em um aplicativo, porque , em teoria, o rastreamento distribuído gera insights valiosos sobre as deficiências da arquitetura de um aplicativo.
Conforme mostrado no diagrama, os serviços que dão suporte ao aplicativo são relativamente simples.
Nosso caminho para selecionar o OpenTelemetry foi bastante tortuoso e teve várias etapas.
Antes de avaliar as ferramentas de observabilidade de código aberto disponíveis, identificamos quais aspectos da observabilidade nos interessam. Com base em nossas experiências anteriores, elaboramos a seguinte lista.
É claro que não esperávamos que uma única ferramenta ou abordagem de código aberto incluísse todos esses recursos, mas pelo menos isso nos deu uma base aspiracional para comparar as ferramentas disponíveis. Consultamos a documentação de cada ferramenta para descobrir quais dos sete recursos em nossa lista de desejos ela suporta. A tabela resume nossas descobertas.
tecnologia | Registro | Rastreamento Distribuído | Métricas | Agregação de erros | Verificações de integridade | Introspecção em tempo de execução | Despejos de pilha/núcleo |
---|---|---|---|---|---|---|---|
ELK + APM elástico | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
Grafana | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
tronco cinza | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
Jaeger | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
Censo Aberto | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
OpenTelemetry | Beta | ✅ | ✅ | ✅ | ✅ * | ❌ | ❌ |
Prometeu | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
EstatísticasD | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
Zipkin | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
* Como uma extensão
Construir esta mesa foi um rude despertar. As várias ferramentas diferem tanto em suas capacidades e propósitos pretendidos que não poderíamos considerá-las todas membros da mesma categoria – era como comparar maçãs com torradeiras!
Por exemplo, ELK ( Elasticsearch-Logstash-Kibana , mais Filebeat ) e Zipkin fazem coisas tão fundamentalmente diferentes que tentar compará-los é mais confuso do que qualquer outra coisa. Infelizmente, o “aumento da missão” apenas turva as águas ainda mais – sem dúvida, em resposta a solicitações dos usuários, foram adicionados recursos secundários ao propósito principal da ferramenta e criam sobreposição com outras ferramentas. Superficialmente, o ELK faz armazenamento e visualização de logs, enquanto o Zipkin faz rastreamento distribuído. Mas quando você se aprofunda um pouco mais no portfólio de produtos da Elastic, você rapidamente se depara com o Elastic APM , que oferece suporte ao rastreamento distribuído e até mesmo é compatível com Jaeger .
Além da questão do desvio da missão, muitas das ferramentas podem ser integradas entre si, resultando em diferentes combinações de coletores, agregadores, painéis e assim por diante. Algumas tecnologias são compatíveis entre si e outras não.
Por isso, esta tabela de comparação não nos deu uma imagem precisa o suficiente para basear nossa escolha. Precisávamos fazer uma investigação qualitativa sobre os objetivos de cada projeto, princípios orientadores e possíveis direções futuras, raciocinando que quanto mais semelhantes os valores de um projeto forem aos nossos, maior a probabilidade de permanecermos compatíveis ao longo do tempo. Visitando as páginas do projeto, imediatamente notamos isso na página do OpenCensus .
O OpenCensus e o OpenTracing se fundiram para formar o OpenTelemetry, que serve como a próxima versão principal do OpenCensus e do OpenTracing. O OpenTelemetry oferecerá compatibilidade com as integrações existentes do OpenCensus e continuaremos a fazer patches de segurança para as bibliotecas existentes do OpenCensus por dois anos.
Esse foi um dado essencial para nós. Sabíamos que não podíamos garantir que nossa escolha seria à prova do futuro, mas queríamos ter pelo menos certeza de que ela teria um apoio sólido da comunidade no futuro. Com essas informações, poderíamos riscar o OpenCensus da nossa lista de candidatos. Provavelmente também não foi uma boa ideia usar o Jaeger, porque é a implementação de referência para o projeto OpenTracing , agora oficialmente obsoleto – a maior parte das novas contribuições irá para o OpenTelemetry.
Depois lemos sobre o OpenTelemetry Collector .
O OpenTelemetry Collector oferece uma implementação independente de fornecedor sobre como receber, processar e exportar dados de telemetria. Além disso, elimina a necessidade de executar, operar e manter vários agentes/coletores para oferecer suporte a formatos de dados de telemetria de código aberto (por exemplo, Jaeger, Prometheus, etc.) enviados para vários back-ends comerciais ou de código aberto.
O OpenTelemetry Collector atua como um agregador que nos permite misturar e combinar diferentes métodos de coleta e instrumentação de observabilidade com diferentes backends. Basicamente, nosso aplicativo pode coletar rastros com o Zipkin e métricas do Prometheus , que podemos então enviar para um backend configurável e visualizar com o Grafana . Várias outras permutações desse design são possíveis, então podemos testar diferentes abordagens para ver quais tecnologias são mais adequadas ao nosso caso de uso.
Basicamente, fomos convencidos pelo OpenTelemetry Collector porque, em teoria, ele nos permite alternar entre tecnologias de observabilidade. Apesar da relativa imaturidade do projeto, decidimos ousadamente mergulhar e usar o OpenTelemetry apenas com integrações de código aberto, porque a única maneira de entender o cenário é usar as tecnologias.
No entanto, faltam algumas peças no OpenTelemetry Collector, então temos que contar com outras tecnologias para esses recursos. As seções a seguir resumem nossas escolhas e o raciocínio por trás delas.
O registro é uma parte aparentemente simples da observabilidade que rapidamente leva a decisões complicadas. É simples porque você apenas coleta a saída do log de seus contêineres, mas é complicado porque você precisa decidir onde armazenar os dados, como transportá-los para esse armazenamento, como indexá-los para torná-los úteis e por quanto tempo mantê-los. Para serem úteis, os arquivos de log precisam ser facilmente pesquisáveis com base em critérios suficientemente diferentes para atender às necessidades de vários pesquisadores.
Analisamos o suporte de registro com o OpenTelemetry Collector e, no momento em que este artigo foi escrito, ele ainda estava em fase beta. Decidimos usar o ELK para exploração madeireira no curto prazo enquanto continuamos a investigar outras opções.
Na ausência de qualquer motivo convincente para não fazê-lo, optamos por usar a ferramenta Elasticsearch , o que nos permitiu dividir a implantação em nós de ingestão, coordenação, mestre e dados . Para facilitar a implantação, usamos um gráfico Bitnami . Para transportar dados, implantamos o Filebeat como parte de um Kubernetes DaemonSet. A funcionalidade de pesquisa foi adicionada implantando o Kibana e aproveitando os índices, pesquisas, visualizações e painéis pré-carregados.
Rapidamente ficou claro que, embora essa solução funcionasse, a configuração padrão consumia muitos recursos, o que dificultava a execução em um ambiente com menor consumo de recursos, como K3S ou Microk8s . Adicionar a capacidade de ajustar o número de réplicas para cada componente resolveu esse problema, mas levou a algumas falhas que eram potencialmente causadas pelo esgotamento de recursos ou volumes excessivos de dados.
Longe de ficarmos decepcionados com isso, estamos vendo isso como uma oportunidade de comparar nosso sistema de registro com diferentes configurações, bem como investigar outras opções, como Grafana Loki e Graylog . Podemos muito bem descobrir que soluções de registro mais leves não fornecem o conjunto completo de recursos que alguns usuários precisam e podem obter de ferramentas que consomem mais recursos. Dada a natureza modular do MARA , provavelmente construiremos módulos adicionais para essas opções para dar mais opções aos usuários.
Além de determinar qual ferramenta fornece a funcionalidade de rastreamento que desejamos, precisamos considerar como implementar a solução e quais tecnologias precisam ser integradas a ela.
Primeiro, queríamos ter certeza de que qualquer instrumentação não afetaria negativamente a qualidade do serviço do aplicativo em si. Todos nós havíamos trabalhado com sistemas em que o desempenho caía previsivelmente uma vez por hora enquanto os logs eram exportados, e não tínhamos vontade de reviver essa experiência. A arquitetura do OpenTelemetry Collector foi atraente nesse aspecto porque você pode executar uma instância por host. Cada coletor recebe dados de clientes e agentes em todos os diferentes aplicativos em execução no host (em contêineres ou não). O coletor agrega e potencialmente compacta os dados antes de enviá-los para um backend de armazenamento, o que parecia ideal.
Em seguida, avaliamos o suporte ao OpenTelemetry nas diferentes linguagens de programação e estruturas que estamos usando no aplicativo. Aqui, as coisas ficaram um pouco mais complicadas. Apesar de usar apenas as duas linguagens de programação e estruturas associadas mostradas na tabela, o nível de complexidade foi surpreendentemente alto.
Linguagem | Estrutura | Número de serviços |
---|---|---|
Java | Bota de mola | 3 |
Python | Frasco | 3 |
Para adicionar rastreamento em nível de idioma, primeiro testamos os agentes de instrumentação automática do OpenTelemetry, mas descobrimos que sua saída era desorganizada e confusa. Temos certeza de que isso melhorará à medida que as bibliotecas de instrumentação automática amadurecerem, mas, por enquanto, descartamos os agentes OpenTelemetry e decidimos incluir o rastreamento em nosso código.
Antes de avançarmos para uma implementação direta do rastreamento no código, primeiro conectamos o OpenTelemetry Collector para enviar todos os dados de rastreamento para uma instância Jaeger em execução local, onde poderíamos ver a saída mais facilmente. Isso foi muito útil, pois pudemos brincar com a apresentação visual dos dados de rastreamento enquanto descobríamos como integrar totalmente o OpenTelemetry. Por exemplo, se descobrirmos que uma biblioteca de cliente HTTP não incluiu dados de rastreamento ao fazer chamadas para um serviço dependente, colocamos o problema em nossa lista de correções imediatamente. Jaeger apresenta uma bela visão de todos os diferentes intervalos dentro de um único traço:
Adicionar trace ao nosso código Python foi relativamente simples. Adicionamos dois arquivos de origem Python que são referenciados por todos os nossos serviços e atualizamos os respectivos arquivos requirements.txt para incluir as dependências opentelemetry-instrumentation-*
relevantes. Isso significa que poderíamos usar a mesma configuração de rastreamento para todos os serviços Python, bem como incluir o ID de rastreamento de cada solicitação em mensagens de log e incorporar o ID de rastreamento em solicitações para serviços dependentes.
Em seguida, voltamos nossa atenção para os serviços Java. Usar bibliotecas Java do OpenTelemetry diretamente em um projeto greenfield é relativamente simples – tudo o que você precisa fazer é importar as bibliotecas necessárias e usar a API de rastreamento diretamente . Entretanto, se você estiver usando o Spring como nós, terá decisões adicionais a tomar.
O Spring já tem uma API de rastreamento distribuída, o Spring Cloud Sleuth . Ele fornece uma fachada sobre a implementação de rastreamento distribuído subjacente que faz o seguinte, conforme descrito na documentação:
spring-cloud-sleuth-zipkin
estiver disponível, … [gera e relata] rastros compatíveis com Zipkin via HTTP. Por padrão, ele os envia para um serviço coletor Zipkin no host local (porta 9411). Configure a localização do serviço usando spring.zipkin.baseUrl
.A API também nos permite adicionar rastros às tarefas anotadas em @Scheduled
.
Em outras palavras, usando apenas o Spring Cloud Sleuth, obtemos rastros no nível dos pontos de extremidade do serviço HTTP imediatamente, o que é um bom benefício. Como nosso projeto já usa Spring, decidimos manter tudo dentro dessa estrutura e utilizar os recursos fornecidos. Entretanto, descobrimos alguns problemas enquanto estávamos conectando tudo com o Maven :
opentelemetry-instrumentation-api
. Atualmente, não há nenhuma versão recente não alfa 1. x desta biblioteca.spring-cloud-build
dos repositórios do Spring Snapshot devido à maneira como o Spring Cloud Sleuth codifica suas referências de dependência para lançamentos importantes.Isso tornou nossa definição de projeto Maven um pouco mais complicada, porque tivemos que fazer o Maven extrair dos repositórios Spring e também do Maven Central, uma indicação clara de quão inicial estava o suporte ao OpenTelemetry no Spring Cloud. No entanto, continuamos e criamos um módulo de telemetria comum para configurar o rastreamento distribuído usando o Spring Cloud Sleuth e o OpenTelemetry, completo com várias funções auxiliares e extensões relacionadas à telemetria.
No módulo de telemetria comum, estendemos a funcionalidade de rastreamento fornecida pelas bibliotecas Spring Cloud Sleuth e OpenTelemetry fornecendo:
de autoconfiguração
habilitadas pelo Spring que configuram o rastreamento e a funcionalidade estendida para o projeto e carregam atributos de recursos de rastreamento adicionais.Além disso, implementamos um cliente HTTP compatível com Spring que é apoiado pelo Apache HTTP Client, porque queríamos mais métricas e personalização ao fazer chamadas HTTP entre serviços. Nesta implementação, os identificadores de rastreamento e intervalo são passados como cabeçalhos quando serviços dependentes são chamados, permitindo que eles sejam incluídos na saída do rastreamento. Além disso, essa implementação fornece métricas de pool de conexão HTTP que são agregadas pelo OpenTelemetry.
No geral, foi difícil conectar o rastreamento com o Spring Cloud Sleuth e o OpenTelemetry, mas acreditamos que valeu a pena. Esperamos que este projeto e esta publicação ajudem a iluminar o caminho para outros que queiram seguir esse caminho.
Para obter rastros que conectam todos os serviços para todo o ciclo de vida de uma solicitação, precisávamos integrar o OpenTelemetry ao NGINX. Usamos o módulo OpenTelemetry NGINX (ainda em beta) para esse propósito. Prevendo que poderia ser potencialmente difícil obter binários funcionais do módulo que funcionassem com todas as versões do NGINX, criamos um repositório GitHub de imagens de contêiner que incorporam módulos NGINX não suportados. Executamos compilações noturnas e distribuímos o binário do módulo por meio de imagens Docker que são fáceis de importar.
Ainda não integramos esse processo ao processo de construção do NGINX Ingress Controller no projeto MARA , mas planejamos fazer isso em breve.
Depois de concluir a integração do rastreamento com o OpenTelemetry, nos concentramos nas métricas. Não havia métricas existentes para nossos aplicativos baseados em Python, e decidimos adiar a adição delas por enquanto. Para aplicativos Java, o código-fonte original do Bank of Anthos , que usa o Micrometer em conjunto com o Stackdriver do Google Cloud, oferece suporte a métricas. No entanto, removemos esse código do Bank of Sirius após bifurcar o Bank of Anthos, porque ele não nos permitiu configurar um backend de métricas. No entanto, o fato de os ganchos métricos já estarem presentes demonstrou a necessidade de uma integração métrica adequada.
Para chegar a uma solução configurável e pragmática, primeiro analisamos o suporte métrico nas bibliotecas Java do OpenTelemetry e no Micrometer. A partir de uma busca por comparações entre as tecnologias, vários resultados enumeraram as deficiências do OpenTelemetry usado como uma API de métricas na JVM, embora as métricas do OpenTelemetry ainda estivessem em alfa no momento da redação deste artigo. O Micrometer é uma camada de fachada de métricas madura para a JVM, semelhante ao slf4j no fornecimento de um wrapper de API comum que oferece uma implementação de métricas configurável em vez de uma implementação de métricas própria. Curiosamente, esta é a API de métricas padrão do Spring.
Neste ponto, estávamos ponderando os seguintes fatos:
Após alguns experimentos, decidimos que a abordagem mais pragmática seria usar a fachada do Micrometer com uma implementação de suporte do Prometheus e configurar o OpenTelemetry Collector para usar a API do Prometheus para extrair métricas do aplicativo. Sabíamos por vários artigos que tipos de métricas ausentes no OpenTelemetry podem causar problemas, mas nossos casos de uso não precisam desses tipos, então o compromisso era aceitável.
Descobrimos uma coisa interessante sobre o OpenTelemetry Collector: embora o tenhamos configurado para receber rastros via OTLP e métricas via API do Prometheus, ele ainda pode ser configurado para enviar ambos os tipos de dados para um receptor de dados externo usando OTLP ou qualquer outro protocolo compatível. Isso nos permitiu testar facilmente nosso aplicativo com o LightStep .
No geral, codificar métricas em Java foi bastante simples porque as escrevemos em conformidade com a API Micrometer, que tem vários exemplos e tutoriais disponíveis. Provavelmente a coisa mais difícil para as métricas e o rastreamento foi obter o gráfico de dependência do Maven no arquivo pom.xml para telemetry-common
corretamente.
O projeto OpenTelemetry não inclui agregação de erros em sua missão em si e não fornece uma implementação tão elegante de marcação de erros quanto soluções como Sentry ou Honeybadger.io . No entanto, decidimos usar o OpenTelemetry para agregação de erros no curto prazo, em vez de adicionar outra ferramenta. Com uma ferramenta como o Jaeger, podemos procurar por error=true
para encontrar todos os rastros com uma condição de erro. Isso pelo menos nos dá uma ideia do que geralmente está dando errado. No futuro, poderemos considerar adicionar a integração com o Sentry .
No contexto do nosso aplicativo, as verificações de integridade permitem que o Kubernetes saiba se um serviço está íntegro ou concluiu sua fase de inicialização. Nos casos em que o serviço não estiver íntegro, o Kubernetes pode ser configurado para encerrar ou reiniciar instâncias. Em nossa aplicação, decidimos não usar verificações de integridade do OpenTelemetry porque achamos a documentação insuficiente.
Em vez disso, para os serviços JVM, usamos um projeto Spring chamado Spring Boot Actuator , que fornece não apenas pontos de extremidade de verificação de integridade, mas também pontos de extremidade de introspecção de tempo de execução e despejo de heap. Para os serviços Python, usamos o módulo Flask Management Endpoints , que fornece um subconjunto de recursos do Spring Boot Actuator. Atualmente, ele fornece apenas informações de aplicativos personalizáveis e verificações de integridade.
O Spring Boot Actuator se conecta à JVM e ao Spring para fornecer introspecção, monitoramento e pontos de extremidade de verificação de integridade. Além disso, ele fornece uma estrutura para adicionar informações personalizadas aos dados padrão apresentados em seus terminais. Os endpoints fornecem introspecção de tempo de execução em coisas como estado de cache, ambiente de tempo de execução, migrações de banco de dados, verificações de integridade, informações de aplicativos personalizáveis, métricas, trabalhos periódicos, estado de sessão HTTP e despejos de thread.
Os endpoints de verificação de integridade implementados pelo Spring Boot Actuator têm uma configuração modular de modo que a integridade de um serviço pode ser composta de várias verificações individuais categorizadas como atividade ou prontidão. Uma verificação de integridade completa que exibe todos os módulos de verificação também está disponível e normalmente se parece com isto .
Os pontos de extremidade informativos são definidos em um documento JSON como um único objeto JSON de alto nível e uma série de chaves e valores hierárquicos. Normalmente, o documento especifica o nome e a versão do serviço, arquitetura, nome do host, informações do sistema operacional, ID do processo, nome do executável e detalhes sobre o host, como ID da máquina ou ID de serviço exclusivo.
Você deve se lembrar da tabela em Comparando recursos de ferramentas com a lista de desejos que nenhuma das ferramentas oferece suporte à introspecção de tempo de execução ou despejos de heap/core. Entretanto, o Spring, como nossa estrutura subjacente, suporta ambos – embora tenha dado algum trabalho conectar os recursos de observabilidade ao aplicativo. Conforme detalhado na seção anterior, para introspecção em tempo de execução usamos um módulo Python em conjunto com o Spring Boot Actuator.
Da mesma forma, para despejos de heap, usamos os pontos de extremidade de despejo de thread fornecidos pelo Spring Boot Actuator para realizar um subconjunto dos recursos que queríamos. Não podemos obter core dumps sob demanda nem heap dumps da JVM no nível ideal de granularidade, mas obtemos algumas das funcionalidades desejadas com pouco esforço adicional. Infelizmente, os despejos de núcleo para os serviços Python exigirão um trabalho adicional considerável, que adiamos para uma data posterior.
Depois de muito chorar e questionar nossas escolhas de vida, convergimos para o uso das seguintes tecnologias para observabilidade no MARA (a seguir, OTel significa OpenTelemetry):
erro
Esta implementação é um instantâneo no tempo. Ele definitivamente mudará e evoluirá conforme o desenvolvimento continua. Em breve, executaremos o aplicativo por meio de testes de carga extensivos. Esperamos aprender muito sobre as deficiências da nossa abordagem de observabilidade e adicionar recursos de observabilidade adicionais.
Experimente a Arquitetura de Referência de Aplicativos Modernos e o aplicativo de exemplo (Bank of Sirius). Se você tiver ideias sobre como podemos melhorar, suas contribuições serão bem-vindas em nosso repositório do GitHub !
Esta postagem faz parte de uma série. À medida que adicionamos recursos ao MARA ao longo do tempo, publicamos os detalhes no blog:
"Esta postagem do blog pode fazer referência a produtos que não estão mais disponíveis e/ou não têm mais suporte. Para obter as informações mais atualizadas sobre os produtos e soluções F5 NGINX disponíveis, explore nossa família de produtos NGINX . O NGINX agora faz parte do F5. Todos os links anteriores do NGINX.com redirecionarão para conteúdo semelhante do NGINX no F5.com."