Depurando microsserviços no Kubernetes com Istio, OpenTelemetry e Tempo — Parte 1
Depurando microsserviços no Kubernetes com Istio, OpenTelemetry e Tempo — Parte 1
Recentemente trabalhei em um projeto paralelo para melhorar o rastreamento em Otomi implementando Grafana Tempo e OpenTelemetry. Vou compartilhar minhas experiências e configuração em dois posts (porque há muito envolvido aqui). Esta é a primeira.
Não espere uma história complicada sobre rastreamento em geral. Vou explicar a configuração completa e compartilhar minhas experiências.
Porquê este projeto
O Otomi (um PaaS auto-hospedado para Kubernetes) usa o Istio em seu núcleo e inclui uma pilha de observabilidade multilocatária avançada com registro (Loki), métricas (Prometheus), alerta (gerenciador de alertas) e rastreamento (Jaeger). Mas recebemos algumas perguntas de usuários sobre a configuração do rastreamento. Perguntas como: “Por que vejo apenas dados parciais com contexto parcial (extensões únicas)” e “Onde os rastreamentos são armazenados e por quanto tempo?
O que você precisa saber sobre rastreamento com o Istio
Em primeiro lugar, configurar o Istio para rastreamento é fácil de configurar. O Istio é responsável pelo gerenciamento do tráfego, portanto, também pode relatar rastreamentos que permitem visibilidade ao Istio e ao comportamento do aplicativo. Mas como não há código em execução dentro do próprio aplicativo para coletar dados, o Istio só pode coletar dados parciais com contexto parcial.
Ehh, o que isso significa? Bem, quando o serviço A chama o serviço B, o Istio cria uma extensão que representa o evento. No entanto, quando o serviço B chama o serviço C, o Istio não pode reconhecer que isso faz parte do mesmo rastreamento contínuo originado do serviço A. Para resolver isso, você precisará instrumentar cada serviço para extrair a propagação de contexto do Istio e injetá-la no(s) serviço(s) downstream. A instrumentação pode ser feita (manualmente) usando o SDK do OpenTelemetry ou automaticamente usando o Operador do OpenTelemetria.
Onde os vestígios são armazenados?
O Jaeger suporta nativamente dois bancos de dados NoSQL de código aberto como back-ends de armazenamento de rastreamento, Cassandra e Elasticsearch. No Otomi usamos apenas o armazenamento de objetos. Existem alguns projetos de código aberto que você pode usar para conectar o armazenamento de objetos (como o AWS S3) ao Jaeger, mas esses projetos não são mantidos ativamente. É por isso que não configuramos o Jaeger com um back-end de armazenamento. Isso me levou a considerar o uso do Grafana Tempo como backend. Então, para responder à pergunta, em um volume K8s. Isso não é o ideal, especialmente quando você tem requisitos de retenção longos.
Estendendo a configuração do rastreamento no Otomi com OpenTelemetry e Tempo
Percebi que a configuração de rastreamento no Otomi era bastante limitada, então comecei um pequeno projeto paralelo para integrar o Tempo como um backend de rastreamento e fornecer equipes (locatários) na plataforma para consultar o Tempo para ver todos os elementos envolvidos na solicitação e ver todas as inter-relações entre seus vários serviços usando um gráfico de nó no Grafana.
Otomi usa malha de serviço Istio em seu núcleo. O Istio aproveita o recurso de rastreamento distribuído do Envoy para fornecer integração de rastreamento pronta para uso. Embora os proxies do Istio possam enviar extensões automaticamente, informações adicionais são necessárias para unir essas extensões em um único rastreamento. Então, precisamos de propagação de contexto.
Isso levou à seguinte arquitetura de solução:
- Instalar o Grafana Tempo
- Instalar o OpenTracing Operator
- Configurar o Coletor OpenTelemetry
- Configure o Istio para usar o provedor de rastreamento opentelemetry e enviar extensões para o OpenTracing Collector
- Configurar a fonte de dados do Grafana para o Tempo
- Configure a fonte de dados Grafana para Loki fornecer um link direto de um traceID nos logs para o rastreamento no Tempo
- Configurar instrumentação para propagação de contexto
Instalar o Grafana Tempo
Vou usar o Tempo como backend para os traços. O Tempo pode ser configurado para usar serviços de armazenamento de objetos como AWS S3, Azure Blob ou (no meu caso) uma instância Minio local (compatível com o S3) em execução no cluster.
Antes de instalarmos o gráfico Tempo Distributed Helm, vamos primeiro examinar alguns valores importantes. Eu sempre instalo gráficos com meus próprios valores ;-)
metricsGenerator:
enabled: true
config:
storage:
path: /var/tempo/wal
wal:
remote_write_flush_deadline: 1m
remote_write:
- url: http://po-prometheus.monitoring:9090/api/v1/write
storage:
trace:
backend: s3
s3:
bucket: tempo
endpoint: minio.minio.svc.cluster.local:9000
access_key: my-access-key
secret_key: my-secret-key
insecure: true
traces:
otlp:
http:
enabled: true
grpc:
enabled: true
metaMonitoring:
serviceMonitor:
enabled: true
labels:
prometheus: system
Instale o gráfico:
helm repo add grafana https://grafana.github.io/helm-chartshelm install -f my-values.yaml tempo grafana/tempo-distributed -n tempo
Como você pode ver, estou instalando o Metrics Generator. Isso nos permitirá ver métricas relacionadas ao rastreamento no Grafana Dashboards. Mais sobre isso mais tarde. Observe também que não analisamos as opções de configuração e dimensionamento de recursos. Isso ainda é um PoC certo!
Se você estiver usando Prometheus, certifique-se de ativar o receptor de gravação remota assim:
prometheus:
prometheusSpec:
enableRemoteWriteReceiver: true
Agora você deve ver os seguintes pods em execução:
# kubectl get po -n tempo
NAME READY STATUS RESTARTS AGE
tempo-compactor-d59b598b5-8287b 1/1 Running 4 (6h19m ago) 16h
tempo-distributor-7b5b649487-fbzf2 1/1 Running 4 (6h19m ago) 16h
tempo-ingester-0 1/1 Running 4 (6h19m ago) 16h
tempo-ingester-1 1/1 Running 4 (6h19m ago) 16h
tempo-ingester-2 1/1 Running 4 (6h19m ago) 16h
tempo-memcached-0 1/1 Running 0 16h
tempo-metrics-generator-66c5dfc565-5dhsv 1/1 Running 4 (6h19m ago) 16h
tempo-querier-694cbf6d7-gxjzj 1/1 Running 4 (6h20m ago) 16h
tempo-query-frontend-67b4ff47c6-9msmv 1/1 Running 4 (6h19m ago) 16h
Observe que minha instância Minio foi configurada independentemente do Tempo. Se você ainda não tiver o Minio em execução (ou não gostar de usar o S3 ou um contêiner de armazenamento do Azure, poderá instalar o Minio usando o gráfico Tempo Helm.
Agora que temos o Tempo em funcionamento, vamos instalar o Operador OpenTelemetria. Por que estou usando o Operador? Bem, eu não sou um fã do gráfico OpenTelemetry Collector Helm porque ele nunca cria a configuração do coletor que eu quero. Se você usar o Operador OpenTelemetria, poderá criar sua própria configuração personalizada do Coletor e mais controle sobre ele. Outro benefício de utilizar o Operador, ele suporta Instrumentação automatizada!
Instalar o OpenTelemetry
A configuração é bastante simples, então vamos apenas instalá-lo:
helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
helm repo update
helm install opentelemetry-operator open-telemetry/opentelemetry-operator -n otel
Agora vem a parte interessante: configurar o Collector. Crie um recurso OpenTelemetryCollector. Você pode usar o seguinte como exemplo:
apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
metadata:
name: otel-collector
spec:
config: |
receivers:
otlp:
protocols:
grpc:
http:
processors:
memory_limiter:
check_interval: 1s
limit_percentage: 75
spike_limit_percentage: 15
batch:
send_batch_size: 10000
timeout: 10s
exporters:
logging:
loglevel: info
otlp:
endpoint: tempo-distributor.tempo.svc.cluster.local:4317
sending_queue:
enabled: true
num_consumers: 100
queue_size: 10000
retry_on_failure:
enabled: true
tls:
insecure: true
service:
pipelines:
traces:
receivers:
- otlp
processors:
- memory_limiter
- batch
exporters:
- logging
- otlp
mode: deployment
Quando o OpenTelemetryCollector for criado, você verá os seguintes pods em execução:
kubectl get po -n otel
NAME READY STATUS RESTARTS AGE
otel-collector-collector-776cdc65f8-pgmvs 1/1 Running 0 162m
otel-operator-78fc8b6975-h7lkh 2/2 Running 0 16h
Agora que temos um backend (Tempo) e um Collector (OpenTelemetry) em execução, o próximo passo é enviar alguns spans. Comecemos pelo Istio. O Istio controla todo o tráfego e ao depurar aplicativos isso se tornará um aspecto muito relevante.
Configurar o Istio para rastreamento
Bem, isso é mais fácil dizer do que fazer. Há muitas maneiras de configurar o Istio (Envoy) para rastreamento, a documentação é fragmentada e tutoriais completos são difíceis de encontrar. Você pode optar por configurar o rastreamento no defaultConfig ou usar extensionProviders. E há vários extensionProviders. Então a pergunta é: “Qual configuração usar e quando?”. Não tenho todas as respostas.
Eu decidi ir para o OpenTelemetryTracingProvider e usar o provedor de enviado padrão para adicionar o cabeçalho TRACEPARENT aos logs. A ideia aqui é usar o provedor Envoy padrão para adicionar o trace-id aos logs do sidecar istio-proxy. Isso seria bastante útil porque você pode configurar a fonte de dados Loki para criar um link do traceID diretamente para o Tempo. Mais sobre isso mais tarde.
Estou usando o Otomi e o Otomi usa o Istio Operator (versão 1.17.4). Para configurar o rastreamento no Istio, primeiro precisaremos modificar o recurso do operador Istio, usando o seguinte meshConfig.
meshConfig:
accessLogFile: /dev/stdout
accessLogFormat: |
[%START_TIME%] "%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%" %RESPONSE_CODE% %RESPONSE_FLAGS% %RESPONSE_CODE_DETAILS% %CONNECTION_TERMINATION_DETAILS% "%UPSTREAM_TRANSPORT_FAILURE_REASON%" %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% "%REQ(X-FORWARDED-FOR)%" "%REQ(USER-AGENT)%" "%REQ(X-REQUEST-ID)%" "%REQ(:AUTHORITY)%" "%UPSTREAM_HOST%" %UPSTREAM_CLUSTER% %UPSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_REMOTE_ADDRESS% %REQUESTED_SERVER_NAME% %ROUTE_NAME% traceID=%REQ(TRACEPARENT)%
enableAutoMtls: true
extensionProviders:
- opentelemetry:
port: 4317
service: otel-collector-collector.otel.svc.cluster.local
name: otel-tracing
Para habilitar o extensionProvider, você precisará criar um recurso de Telemetria:
apiVersion: telemetry.istio.io/v1alpha1
kind: Telemetry
metadata:
name: otel-tracing
namespace: istio-system
spec:
tracing:
- providers:
- name: otel-tracing
randomSamplingPercentage: 100
Ao criar esse recurso no namespace istio-system, o provedor estará ativo para todos os namespaces.
Defini o randomSamplingPercentage como 100%. Em um ambiente de produção, isso provavelmente será de 0,1%.
Depois que o operador prometheus tiver reconciliado, você verá extensões chegando que são exportadas para o Tempo.
kubectl logs otel-collector-collector-776cdc65f8-pgmvs -n otel
2023-08-09T13:07:58.186Z info TracesExporter {"kind": "exporter", "data_type": "traces", "name": "logging", "resource spans": 5, "spans": 7}
2023-08-09T13:08:08.186Z info TracesExporter {"kind": "exporter", "data_type": "traces", "name": "logging", "resource spans": 2, "spans": 6}
No Otomi também usamos o Nginx Ingress Controller. Para eventualmente ver o rastreamento completo do controlador de entrada, Istio Gateways, Ingresses e, eventualmente, o aplicativo, também configuraremos o controlador Nginx para enviar extensões para o coletor. Configure o Nginx Ingress usando os seguintes valores:
controller:
opentelemetry:
enabled: true
config:
enable-opentelemetry: true
otel-sampler: AlwaysOn
otel-sampler-ratio: 0.1
otlp-collector-host: otel-collector-collector.otel.svc
otlp-collector-port: 4317
opentelemetry-config: "/etc/nginx/opentelemetry.toml"
opentelemetry-operation-name: "HTTP $request_method $service_name $uri"
opentelemetry-trust-incoming-span: "true"
otel-max-queuesize: "2048"
otel-schedule-delay-millis: "5000"
otel-max-export-batch-size: "512"
otel-service-name: "nginx"
otel-sampler-parent-based: "true"
Veja aqui para saber mais sobre o rastreamento no Nginx Ingress usando OpenTelemetry.
Conclusão (por enquanto)
Agora temos um back-end para nossos rastreamentos, um Coletor para receber extensões de rastreamento e exportá-las para o back-end (Tempo), e o Istio e o Nginx Ingress Controller enviando extensões de rastreamento para o Coletor.
No segundo part, vamos instrumentar nosso aplicativo e configurar fontes de dados no Grafana for Tempo para ver o real poder do rastreamento no Kubernetes.