Monitorando RabbitMQ implantado no Kubernetes como Cluster Operator com Datadog Autodiscovery
Tags: kubernetes, datadog, rabbitmq, observability
Published: 2023-04-13 14:44:42

## Introdução 
Atualmente temos duas maneiras de realizar o monitoramento de clusters RabbitMQ, via Management Plugin e via OpenMetricsV2 (Prometheus Plugin).

Via management Plugin, realizamos queries diretamente na API do RabbitMQ (via Rabbitmq CLI), sendo assim necessitamos de criação de usuários com permissão para realizar as queries, o que adiciona etapas extras para reproduzir em larga escala, além de ser mais custoso em questão de performance do cluster.

Via OpenMetricsV2, a partir da versão 3.8 do RabbitMQ ele já vem pronto para ser monitorado, não necessitando de nenhuma configuração extra na parte do Rabbit. 

Porém se você tentou seguir a documentação do datadog a respeito da integração, deve ter notado que ela é um pouco vaga e omissa em diversos pontos, nesse tópico vou demonstrar um passo a passo como realizar o deploy do rabbit via Cluster Operator em um cluster kubernetes, e monitorar as filas e consumers via datadog.

## Pré-requisitos 
Nessa documentação não vou abordar tópicos sobre como subir um Cluster Kubernetes e nem sobre como Instalar o Datadog Cluster Agent. Ela parte do princípio de que você já tenha isso rodando no seu ambiente. 
O [cluster operator](https://github.com/rabbitmq/cluster-operator#supported-versions) do RabbitMQ solicita que o Kubernetes esteja na versão 1.19 ou superior.
Para o [Datadog Autodiscovery](https://docs.datadoghq.com/containers/cluster_agent/clusterchecks/?tab=daemonset) funcionar com o rabbitMQ, você vai precisar do [datadog agent 7.42](https://github.com/DataDog/datadog-agent/releases/tag/7.42.0) ou superior rodando no seu cluster, pois só a partir dela que temos o suporte do [RabbitMQ Prometheus Metris]
(https://github.com/DataDog/integrations-core/pull/13662).

![rabbitmq release notes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8jbv3nv6t26lz4347wbt.png)

Por último, é necessário [habilitar o ClusterChecks do datadog agent]
(https://docs.datadoghq.com/containers/cluster_agent/clusterchecks/?tab=helm#set-up-cluster-check-dispatching), definindo spec.features.clusterChecks.enabled como true no deploy do cluster agent.

![datadog documentation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sa66vhxflp9t4rmqb0h7.png)


## Deploy do RabbitMQ Cluster Operator
Vamos usar de base o exemplo [hello-world](https://github.com/rabbitmq/cluster-operator/blob/main/docs/examples/hello-world/rabbitmq.yaml) disponibilizado no repositório do RabbitMQ Cluster Operator:
```yaml
apiVersion: rabbitmq.com/v1beta1
kind: RabbitmqCluster
metadata:
  name: hello-world
```

Primeira coisa que vou fazer é alterar o nome de hello-world para rabbitmq, e especificar que ele vai subir no [namespace](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/) rabbitmq. 
Também no campo `spec` vou dizer que eu quero que meu serviço seja do tipo [NodePort](https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport), definir que meu rabbitmq suba com 3 réplicas, definir os resources de cada replica e definir qual imagem docker eu quero que essas replicas rodem para ter mais controle sobe a versão do rabbitmq que estou rodando (além de permitir usar imagens personalizadas futuramente se eu quiser por exemplo adicionar um plugin de terceiros dentro dela):

```yaml
apiVersion: rabbitmq.com/v1beta1
kind: RabbitmqCluster
metadata:
  name: rabbitmq
  namespace: rabbitmq
spec:
  service:
    type: NodePort
  replicas: 3
  image: rabbitmq:3.11-management
  resources:
    limits:
      cpu: 500m
      memory: 2Gi
    requests:
      cpu: 250m
      memory: 512Mi
```

Como definimos que o nosso serviço é do tipo NodePort, toda vez que um deploy rodar vai ser atribuído uma porta aleatória para o nosso NodePort, em ambinetes produtivos precisamos pelo menos garantir que essa porta nunca mude, podemos fazer isso usando a tag override. Vou criar um override para sobrescrever o yaml do cluster operator e fixar a nodePort do rabbitmq para 30672:

```yaml
apiVersion: rabbitmq.com/v1beta1
kind: RabbitmqCluster
metadata:
  name: rabbitmq
  namespace: rabbitmq
spec:
  service:
    type: NodePort
  replicas: 3
  image: rabbitmq:3.11-management
  resources:
    limits:
      cpu: 500m
      memory: 2Gi
    requests:
      cpu: 250m
      memory: 512Mi
  override:
    service:
      spec:
        ports:
        - name: tcp-amqp
          protocol: TCP
          port: 5672
          targetPort: 5672
          nodePort: 30672
```
Por último vou adicionar alguns plugins extras e configurar o log level do rabbit como critical para não gerar logs desnecessários, para essas configurações específicas do rabbitmq existem tags personalizadas do próprio operator.

```yaml
apiVersion: rabbitmq.com/v1beta1
kind: RabbitmqCluster
metadata:
  name: rabbitmq
  namespace: rabbitmq
spec:
  service:
    type: NodePort
  replicas: 3
  image: rabbitmq:3.11-management
  resources:
    limits:
      cpu: 500m
      memory: 2Gi
    requests:
      cpu: 250m
      memory: 512Mi
  override:
    service:
      spec:
        ports:
        - name: tcp-amqp
          protocol: TCP
          port: 5672
          targetPort: 5672
          nodePort: 30672
  rabbitmq:
    additionalConfig: |
      log.default.level = critical
    additionalPlugins:
    - rabbitmq_peer_discovery_k8s
    - rabbitmq_prometheus
    - rabbitmq_management
```
Agora basta salvar nosso yaml, vou salvar como "rabbitmq.yml", e aplicar utilizando o comando `kubectl apply -f rabbitmq.yml` para subir um rabbit no cluster kubernetes.

## Habilitando a integração com datadog
A primeira coisa que precisamos fazer é acessar a [integração com rabbitmq no datadog](https://app.datadoghq.com/integrations/rabbitmq), e clicar em instalar para habilitar o plugin.

![rabbitmq integration](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uwex7xa1ydf05ldc5btj.png)

Na tela da integração já temos uma parte da documentação disponível a respeito da integração via plugin do prometheus:

![documentação integração rabbitmq prometheus](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lpq0kgnywa0apu4ghg4t.png)

Aqui explica como habilitar o scraping do endpoint /metrics,
que nos trás métricas agregadas, ou seja, sem dividir por queue ou vhost, se eu quiser ver status só de uma fila, preciso pegar métricas não agregadas pelo endpoint detailed.

A documentação cita que é possível fazer o scraping do /metrics/detailed, mas até o dia dessa publicação não explica como (Criei um [Pull Request](https://github.com/DataDog/integrations-core/pull/14360) lá no repositório da integração adicionando um exemplo, talvez quando vocês esteja lendo esse artigo já tenha sido feito o merge e a documentação esteja mais completa). 

Para entender como fazer scraping do /metrics/detailed, atualmente você precisa compreender [como esse endpoint funciona](https://www.rabbitmq.com/prometheus.html#detailed-endpoint) e quais parâmetros ele precisa receber, e também precisa dar uma lida no [código fonte da integração](https://github.com/DataDog/integrations-core/tree/master/rabbitmq)

### Configurando o scraping de métricas agregadas

Como vimos, o rabbitmq disponibiliza dois endpoints de métricas, sendo eles um para métricas agregadas e um desagregadas,
para começar, vamos configurar o scraping do endpoint /metrics e coletar as métricas agregadas.
Para isso precisamos adicionar uma anotação no nosso deploy do RabbitMQ dizendo qual plugin utilizar e qual a url base, aqui um modelo disponibilizado na [documentação sobre os autodiscoverys](https://docs.datadoghq.com/containers/kubernetes/integrations/?tab=kubernetesadv2#configuration):
![integration yaml example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yv7j6zyd3ekfjasn2ja6.png)
Também na documentação da integração com o rabbitMQ, ele já nos informa o seguinte: 
![documentação datadog](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0btd7xbmweo8wikssait.png)
Juntando essas duas informações, então sabemos que nosso annotation tem que ficar da seguinte forma:
```yaml
  annotations:
    ad.datadoghq.com/rabbitmq.checks: |
      {
        "rabbitmq": {
          "init_config": {},
          "instances": [
            {
              "prometheus_plugin": { 
                "url": "http://%%host%%:15692"
               }
             }
           ]
         }
```
Esse annotation tem que ser adicionado nos Pods que rodam nosso RabbitMQ, como o stateful-set do RabbitMQ é criado via cluster Operator, precisamos adicionar isso via override no yaml do cluster operator, da seguinte forma:

```yaml
apiVersion: rabbitmq.com/v1beta1
kind: RabbitmqCluster
metadata:
  name: rabbitmq
  namespace: rabbitmq
spec:
  service:
    type: NodePort
  replicas: 3
  image: rabbitmq:3.11-management
  resources:
    limits:
      cpu: 500m
      memory: 2Gi
    requests:
      cpu: 250m
      memory: 512Mi
  override:
    service:
      spec:
        ports:
        - name: tcp-amqp
          protocol: TCP
          port: 5672
          targetPort: 5672
          nodePort: 30672
    statefulSet:
      spec:
        template:
          metadata:
            annotations:
              ad.datadoghq.com/rabbitmq.checks: |
                {
                  "rabbitmq": {
                    "init_config": {},
                    "instances": [
                        {
                          "prometheus_plugin": { 
                            "url": "http://%%host%%:15692"
                          }
                        }
                    ]
                  }
                }              
  rabbitmq:
    additionalConfig: |
      log.default.level = critical
    additionalPlugins:
    - rabbitmq_peer_discovery_k8s
    - rabbitmq_prometheus
    - rabbitmq_management
```
Após realizar um kubectl apply desse yaml, os pods vão ser recriados e subir com essas annotations, a partir daí o datadog já vai começar a coletar as métricas agregadas.

### Prometheus endpoint: /metrics/detailed

Para métricas desagregadas precisamos utilizar o endpoint /metrics/detailed

![documentação rabbitmq](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kwzgtjpxnixakogohdjl.png) retirado da [documentação.](https://www.rabbitmq.com/prometheus.html#detailed-endpoint)

Conforme cita na documentação, o endpoint por padrão não retorna nada, você tem que passar parâmetros para dizer o que quer retornar.

Você pode passar o parâmetro "vhost=nome_do_vhost" caso queira filtrar apenas métricas de um vhost específico, ou pode não filtrar por vhost caso queira coletar dados de todos os vhosts.

Você deve passar quais os grupos de métricas quer coletar no campo "family", na documentação ele cita as "Generic metrics", que são metricas agregadas mesmo no endpoint detailed, essas a gente já consegue coletar no endpoint /metrics, o que nos interessa aqui são as Queue metrics

![queue metrics](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lairs7layuwl61qvekdq.png)

Dessas podemos começar com as duas primeiras, `queue_coarse_metrics` e `queue_consumer_count`, que já vão nos trazer quantidades de mensagem por tipo, fila e vhost, e quantidade de consumidores nessas filas:

![queue metrics](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/elz8dd47nbsougdg448i.png)

Então nosso query final vai ficar:
`%%host%%:15692/metrics/detailed?family=queue_coarse_metrics&family=queue_consumer_count`

onde `%%host%%` é um [Autodiscovery Template Variable](https://docs.datadoghq.com/agent/guide/template_variables/), e a porta `15692` é a porta padrão do plugin do prometheus do rabbitmq.

### coletando métricas desagregadas com o Datadog
![datadog documentation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ppk07kokvrqik3xdayd1.png)

A documentação do datadog cita que é possível coletar essas métricas, mas não trás nenhum exemplo de como, para entender como precisamos ler o código fonte do plugin de integração, mais especificamente [este arquivo](https://github.com/DataDog/integrations-core/blob/master/rabbitmq/datadog_checks/rabbitmq/openmetrics/check.py):
![integration plugin source code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zty10djpjnnb34ksnfgk.png)
Aqui podemos ver na linha 13 onde ele pega o campo `url` que definimos lá no annotation, e utiliza ele como `base_url`, na linha 16 da mesma maneira ele coleta um campo `unaggregated_endpoint` e seta na variável unagg_ep.

Agora sabemos que o nome do campo que temos que passar lá no annotations é `unaggregated_endpoint`, apenas precisamos entender melhor como esse valor é usado para definir o formato correto.

Podemos ver na linha 20, que o valor desse campo é juntado com a string `/metrics/`, então sabemos que não precisa passar o `/metrics/` pois ele já é inserido via código.

Por fim, ele é adicionado ao array endpoints, e esse array é utilizado na linha 27 sendo adicionado á url base.

Sendo assim, esse valor recebe apenas o que fica pra frente do 
`/metrics/` do nosso query, ficando: 
`"unaggregated_endpoint":"detailed?family=queue_coarse_metrics&family=queue_consumer_count"`

Agora basta adicionarmos esse campo no nosso annotation da seguinte forma:


```yaml
apiVersion: rabbitmq.com/v1beta1
kind: RabbitmqCluster
metadata:
  name: rabbitmq
  namespace: rabbitmq
spec:
  service:
    type: NodePort
  replicas: 3
  image: rabbitmq:3.11-management
  resources:
    limits:
      cpu: 500m
      memory: 2Gi
    requests:
      cpu: 250m
      memory: 512Mi
  override:
    service:
      spec:
        ports:
        - name: tcp-amqp
          protocol: TCP
          port: 5672
          targetPort: 5672
          nodePort: 30672
    statefulSet:
      spec:
        template:
          metadata:
            annotations:
              ad.datadoghq.com/rabbitmq.checks: |
                {
                  "rabbitmq": {
                    "init_config": {},
                    "instances": [
                        {
                          "prometheus_plugin": { 
                            "url": "http://%%host%%:15692",
                            "unaggregated_endpoint":"detailed?family=queue_coarse_metrics&family=queue_consumer_count"
                          }
                        }
                    ]
                  }
                }              
  rabbitmq:
    additionalConfig: |
      log.default.level = critical
    additionalPlugins:
    - rabbitmq_peer_discovery_k8s
    - rabbitmq_prometheus
    - rabbitmq_management
```

Realizar um novo deploy com o comando kubectl apply -f rabbitmq.yaml
E esperar o datadog começar a coletar as métricas separadas por queue e vhost.

Pode ver as métricas que estão disponíveis nessa documentação:
https://www.rabbitmq.com/prometheus.html#detailed-endpoint

Ou pode ir no datadog em Metrics > Summary e filtrar pela tag: "kube_app_name: rabbitmq"

Tem também o dashboard padrão de RabbitMQ OpenMetrics que já vem com a integração: 
https://app.datadoghq.com/dash/integration/37/rabbitmq-overview-openmetrics-version

![dashboard](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d3qi2sjj80in3w90cmn1.png)