Inclua as dependências no arquivo pom.xml
:
<!-- built-in endpoints, como: /metrics e /health -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- micrometer para disponibilizar métrics no formato do prometheus -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<scope>runtime</scope>
</dependency>
Exponha a rota do prometheus (e outras rotas) via arquivo application.yml
/ application.properties
ou variável de ambiente. Ex via application.yml
:
# expondo rotas: /actuator/<health, info, metrics, prometheus>
management.endpoints.web.exposure.include: health,info,metrics,prometheus
# o base path padrão é /actuator, mas você pode alterar através do seguinte parâmetro
management.endpoints.web.base-path: /actuator
# para exibir o histograma de latência de requisições, use a seguinte configuraçao
# https://docs.spring.io/spring-boot/reference/actuator/metrics.html#actuator.metrics.export.prometheus
management.metrics.distribution.slo[http.server.requests]: "25ms, 50ms, 100ms, 200ms, 400ms, 800ms, 1500ms"
Acesse a rota /actuator
e confira se as rotas /actuator/metrics
e /actuator/prometheus
estão habilitadas. Se estiverem, elas aparecerão na lista, conforme exemplo:
{
"_links": {
"self": {
"href": "http://localhost:8080/actuator",
"templated": false
},
"health-path": {
"href": "http://localhost:8080/actuator/health/{*path}",
"templated": true
},
"health": {
"href": "http://localhost:8080/actuator/health",
"templated": false
},
"info": {
"href": "http://localhost:8080/actuator/info",
"templated": false
},
"prometheus": {
"href": "http://localhost:8080/actuator/prometheus",
"templated": false
},
"metrics-requiredMetricName": {
"href": "http://localhost:8080/actuator/metrics/{requiredMetricName}",
"templated": true
},
"metrics": {
"href": "http://localhost:8080/actuator/metrics",
"templated": false
}
}
}
Você pode acessar as rotas diretamente, ex:
É possível realizar queries sobre as métricas através da rota /atuator/metrics
.
Query: listando conexões do Hikari http://localhost:28080/actuator/metrics/hikaricp.connections
Query: filtro de JVM memory por area = heap
http://localhost:8080/actuator/metrics/jvm.memory.max?tag=area:heap
Query: filtro de JVM memory por id = heap
http://localhost:8080/actuator/metrics/jvm.memory.max?tag=area:heap
Query: filtro combinado de JVM memory por area = heap
e id = G1 Old Gen
http://localhost:8080/actuator/metrics/jvm.memory.max?tag=area:heap&tag=id:G1+Old+Gen
Em vez de ter que realizar queries uma a uma, o micrometer já expõe essas métricas em uma única consulta, no formato compatível com o prometheus.
Vamos vamos configurar a infra do Prometheus para executar localmente via docker compose
.
Configure o arquivo prometheus/prometheus.yml
conforme o exemplo a seguir, para instruir o prometheus sobre o local onde ele deve buscar as métricas:
scrape_configs:
- job_name: 'store-api-pull'
metrics_path: '/actuator/prometheus'
scrape_interval: 3s
static_configs:
- targets: ['host.docker.internal:8080']
labels:
application: 'store-api'
Configure o docker-compose.yml
com o serviço do prometheus, conforme exemplo a seguir:
services:
prometheus:
image: prom/prometheus
container_name: prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
ports:
- 9090:9090
restart: unless-stopped
volumes:
- ./prometheus:/etc/prometheus
- prom_data:/prometheus
volumes:
prom_data:
Após executar docker compose up -d
para subir o serviço do prometheus, com a aplicação executando, acesse http://localhost:9090
e execute algumas queries:
# todas requisições com status 4xx e 5xx
http_server_requests_seconds_count{status =~ '4.+'}
http_server_requests_seconds_count{status =~ '5.+'}
# todas as requisições, exceto /actuator/prometheus
http_server_requests_seconds_count{uri !~ ".+prometheus"}
# múltiplos filtros: url exceto prometheus e status 2xx
http_server_requests_seconds_count{uri !~ ".+prometheus", status =~ "2.+"}
# múltiplos filtros: url exceto prometheus/metrics/health e status 2xx
http_server_requests_seconds_count{uri !~ ".+prometheus|.+metrics|.+health", status =~ "2.+"}
# requisições 2xx em relação ao tempo (resolução)
http_server_requests_seconds_sum{uri !~ ".+prometheus|.+metrics|.+health", status =~ "2.+"}
Adicione a dependência do micrometer core:
<!-- https://mvnrepository.com/artifact/io.micrometer/micrometer-core -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
</dependency>
Na classe onde você quer contabilizar as métricas customizadas, inclua o @Bean preferencialmente utilizando algum mecanismo de injeção de dependências.
Exemplo 1 - construtor:
private final MeterRegistry meterRegistry;
// construtor
public ClassEspecifica(final MeterRegistry meterRegistry, ...) {
this.meterRegistry = meterRegistry;
// ... demais configurações da classe
}
Exemplo 2 - Spring Boot autowired:
@Autowired
private MeterRegistry meterRegistry;
Pode apenas aumentar ou resetar, ex: número de requisições, quantidade de erros, etc.
final Counter counterMetricaCustomizada;
// construtor
public ClassEspecifica(...) {
// ...
counterMetricaCustomizada = Counter.builder("<NOME DA MÉTRICA>")
.tag("<TAG 1>", "<VALOR DA TAG 1>")
.tag("<TAG 2>", "<VALOR DA TAG 2>")
.description("<DESCRIÇÃO QUE EXPLICA A MÉTRICA>")
.register(meterRegistry);
}
public void metodoDaClasse(...) {
// lógica do método
// ....
counterMetricaCustomizada.increment();
}
Pode aumentar ou diminuir, útil para métricas como número de pods/tasks em um cluster, quantidade de mensagens em uma fila, etc.
final AtomicInteger gaugeValueMetricaCustomizada;
// construtor
public ClassEspecifica(...) {
// ...
Gauge.builder("<NOME DA METRICA>", gaugeValueMetricaCustomizada::get)
.description("<DESCRIÇÃO DA MÉTRICA>")
.register(meterRegistry);
}
public void metodoDaClasse(...) {
// lógica do método
// ....
gaugeValueMetricaCustomizada.set(<NOVO VALOR>);
}
O Prometheus permite que você realize consultas sobre as métricas capturadas e armazenadas em sua camadade storage, mas ele não consegue entregar a usabilidade e vesatilidade de um sistema especializado em UI.
Com o Grafana você pode configurar dashboards e alarmes sofisticados, utilizando o Prometheus e muitos outros sistemas como fonte de dados.
Você pode subir o serviço do Grafana utilizando o seguinte trecho de código do docker compose.
grafana:
image: grafana/grafana
container_name: grafana
ports:
- 3000:3000
restart: unless-stopped
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=grafana
volumes:
- ./grafana:/etc/grafana/provisioning/datasources
Você pode conectar o Grafana a múltiplas fontes de dados. Para integrá-lo ao Prometheus em tempo de inicialização, crie o arquivo grafana/datasources.yml
coma seguinte estrutura (esse arquivo está sendo referenciado no trecho do docker compose):
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
Uma solução de monitoramento mais assertiva precisa ter minimamente as métricas relacionadas aos 4 golden signals.
Nem sempre é possível expor um endpoint que seja acessível pelo Prometheus. Por exemplo, você pode ter aplicações batch ou workers que são estimulados de forma ad-hoc, agendados ou a partir de eventos de fila, que podem executar em infraestrutura mais tradicional, como servidores/VMs Linux e Windows. E mesmo utilizando um ambiente conteinerizado, pode haver alguma limitação que torne a configuração muito complexa, por exemplo o service discovery para acesso à aplicação com as métricas, considerando a vida curta das aplicações.
Nesse caso você precisa instrumentar a aplicação para enviar proativamente as métricas ao Prometheus através do recurso chamado Push Gateway.
Inclua o serviço do pushgateway
no docker-compose.yml
. Ele geralmente expõe a porta 9091
para integração.
services:
# ....
# outros serviços
# ....
pushgateway:
image: prom/pushgateway
container_name: pushgateway
restart: unless-stopped
expose:
- 9091
ports:
- "9091:9091"
labels:
org.label-schema.group: "monitoring"
Configure o Prometheus para buscar as métricas na API do pushgateway no arquivo prometheus/prometheus.yml
:
scrape_configs:
# ...
# outros jobs
# ...
- job_name: 'pushgateway'
honor_labels: true
scrape_interval: 3s
static_configs:
- targets: ['pushgateway:9091']
Reinicie os serviços via docker compose, ex:
docker compose up -d
Além das dependências já adicionadas, inclua no pom.xml
a lib io.prometheus.simpleclient_pushgateway
:
<!-- Pushgatweay para envio proativo das métricas ao Prometheus -->
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>simpleclient_pushgateway</artifactId>
</dependency>
No arquivo src/main/resources/application.yml
, configure a integração com o pushgateway:
server.port: 8080
spring.application.name: store-api
management:
endpoints.web.exposure.include: health,info,metrics,prometheus
prometheus:
metrics:
export:
pushgateway:
enabled: true
base-url: http://localhost:9091
shutdown-operation: POST
push-rate: PT1S
job: store-api-push
Por default a lib io.prometheus.simpleclient_pushgateway
inicializa um @Bean
(objeto) do PrometheusPushGatewayManager
, componente responsável por integrar efetivamente com o serviço do Pushgateway. Caso queira opcionalmente customizar a criação desse componente, você pode criar uma classe de configuração (@Configuration
) conforme o exemplo a seguir:
import io.micrometer.core.aop.TimedAspect;
import io.micrometer.core.instrument.MeterRegistry;
import io.prometheus.client.CollectorRegistry;
import io.prometheus.client.exporter.PushGateway;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
import org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus.PrometheusProperties;
import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusPushGatewayManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import java.net.MalformedURLException;
import java.net.URI;
@Configuration
public class PushGatewayConfiguration {
@Value("${spring.application.name}")
private String applicationName;
@Bean
@Primary
public PrometheusPushGatewayManager prometheusPushGatewayManager(
CollectorRegistry collectorRegistry,
PrometheusProperties prometheusProperties) throws MalformedURLException {
final PrometheusProperties.Pushgateway properties = prometheusProperties.getPushgateway();
return new PrometheusPushGatewayManager(
new PushGateway(URI.create(properties.getBaseUrl()).toURL()),
collectorRegistry,
properties.getPushRate(),
applicationName,
properties.getGroupingKey(),
properties.getShutdownOperation());
}
@Bean
public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags(@Value("${spring.profiles.active:default}") String activeEnvProfile) {
return registry -> registry.config()
.commonTags(
"env", activeEnvProfile
);
}
@Bean
public TimedAspect timedAspect(MeterRegistry registry) {
return new TimedAspect(registry);
}
}
Queries sobre consumo de memória:
# consumo de memória HEAP no último minuto
jvm_memory_used_bytes{area="heap"}[1m]
# média do consumo de memória HEAP no último minuto
avg_over_time(jvm_memory_used_bytes{area="heap"}[1m]))
# soma da média de consumo de memória
sum(avg_over_time(jvm_memory_used_bytes{area="heap"}[1m]))
# soma da média de consumo de memória, porém agrupando por "application" e "instance"
sum(avg_over_time(jvm_memory_used_bytes{area="heap"}[1m])) by (application,instance)
# percentual de consumo de memória do último minuto
sum(avg_over_time(jvm_memory_used_bytes{area="heap"}[1m]))by(application,instance) * 100 / sum(avg_over_time(jvm_memory_max_bytes{area="heap"}[1m]))by(application,instance) >= 80
# média de uso de conexões sobre o connection pool
sum(avg_over_time(hikaricp_connections_active[1m]))by(application,instance) * 100 / sum(avg_over_time(hikaricp_connections[1m]))by(application, instance)
sum(avg_over_time(jdbc_connections_active[1m]))by(application,instance) * 100 / sum(avg_over_time(jdbc_connections_max[1m]))by(application, instance)
sum(avg_over_time(process_cpu_usage[1m]))by(application,instance)