/actuator/health responder UP prova apenas que os indicadores daquele grupo estão saudáveis. Não prova que a aplicação consegue consumir a fila, atender pelo conector principal ou chamar uma dependência externa.
Esse detalhe importa porque o health check padrão pode continuar verde enquanto a fila orders acumula mensagens sem nenhum consumidor. A saída não é publicar todos os endpoints e procurar a resposta no susto. É separar acesso, exposição, liveness, readiness e métricas, cada um com uma responsabilidade verificável.
A base usada aqui é Java 25 LTS com Spring Boot 4.1.0. Essa combinação está dentro da matriz oficial de requisitos do Spring Boot: a versão 4.1.0 requer Java 17 ou superior e aceita até Java 26. O Spring Boot 3.5.16, compatível com Java 17 a 25, aparece apenas como contexto de migração; os exemplos seguem a API do Boot 4.
Acesso e exposição resolvem problemas diferentes
No Actuator, um endpoint precisa passar por duas decisões:
- Acesso define se ele pode existir como
unrestricted,read-onlyounone, por meio demanagement.endpoint.<id>.access. Comnone, o endpoint é removido do contexto. - Exposição define se um endpoint existente será publicado por HTTP ou JMX, por meio de
management.endpoints.web.exposureemanagement.endpoints.jmx.exposure. Se um id estiver emincludeeexclude, oexcludevence.
Por padrão, somente health é exposto por HTTP e JMX. No HTTP, isso disponibiliza /actuator/health; /actuator funciona como rota de descoberta dos endpoints expostos.
Logo, conceder acesso não publica automaticamente um endpoint. E colocar um endpoint no include não supera um acesso none. O asterisco continua sendo uma estratégia de configuração, só não costuma ser uma estratégia de produção.
Uma seleção conservadora para o ambiente produtivo é:
| Endpoint | Decisão | Motivo |
|---|---|---|
health | Expor | Alimenta probes e permite verificar grupos de saúde. |
info | Expor | Entrega informações operacionais escolhidas pela aplicação. |
metrics | Expor com autenticação | Permite explorar métricas em JSON; não é a rota de scrape. |
prometheus | Expor para a rede de monitoramento | Entrega o formato de scrape e exige o registry do Prometheus. |
loggers | Opcional e somente leitura | Ajuda no diagnóstico, sem permitir alteração por POST. |
heapdump, env, configprops, threaddump | Não expor | Podem revelar heap, configuração, valores ou stacks. |
shutdown, beans, mappings, conditions, httpexchanges, logfile, sessions | Não expor | Ampliam desnecessariamente a superfície operacional. |
O heapdump já tem acesso restrito por padrão no Boot 4.1, assim como shutdown. Ainda vale fixá-lo como none para registrar a intenção. Para env e configprops, mantenha show-values=never; a máscara padrão não é convite para publicar o endpoint.
Uma configuração de produção com defesa em camadas
As dependências abaixo partem de um projeto gerenciado pelo parent ou BOM do Spring Boot 4.1.0:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
</dependencies>
Separe o tráfego de gestão em uma porta privada e libere apenas os endpoints necessários:
management:
server:
port: 8081
endpoints:
access:
max-permitted: read-only
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: when-authorized
roles: ENDPOINT_ADMIN
probes:
add-additional-paths: true
group:
readiness:
include: readinessState,rabbit,ordersQueue
env:
show-values: never
heapdump:
access: none
O teto max-permitted: read-only prevalece sobre permissões mais amplas. Ele também impede que uma futura inclusão de loggers habilite a escrita por acidente. Se a equipe realmente precisar alterar loggers em execução, terá de tratar autenticação e CSRF de forma deliberada; desabilitar CSRF globalmente porque um POST devolveu 403 apenas troca um diagnóstico por outro problema.
Com Spring Security no classpath e sem uma SecurityFilterChain própria, o Boot protege todos os endpoints do Actuator, exceto health. Quando a aplicação declara uma chain, essa configuração automática recua. Nesse caso, crie duas chains: uma para a aplicação e outra para os endpoints selecionados com EndpointRequest.toAnyEndpoint(), HTTP Basic e a autoridade ENDPOINT_ADMIN. No Boot 4, o matcher fica em org.springframework.boot.security.autoconfigure.actuate.web.servlet.EndpointRequest.
Libere sem autenticação somente as rotas de probe que o kubelet precisa acessar. A porta 8081 deve permanecer alcançável apenas pela rede operacional ou pelo cluster, não pela entrada pública da aplicação.
O health verde do RabbitMQ não mede a fila
No Boot 4, indicadores customizados implementam org.springframework.boot.health.contributor.HealthIndicator. Essa mudança de pacote importa para quem vem do Boot 3.5, cuja interface ficava sob org.springframework.boot.actuate.health.
O indicador automático do RabbitMQ também merece uma leitura precisa. O RabbitHealthIndicator do Spring Boot 4.1.0 abre uma operação pelo RabbitTemplate e lê a propriedade version do broker. Se o broker responde, o componente fica UP.
Ele não verifica quantidade de mensagens nem consumidores. Portanto, um broker acessível, zero consumidores e uma fila parada há 20 minutos ainda produzem um rabbit verde. É um verde educado, mas não especialmente útil para o pedido que continua esperando.
Para verificar a condição operacional da fila orders, adicione um indicador específico:
package academy.devdojo.orders;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.HealthIndicator;
import org.springframework.stereotype.Component;
@Component
public class OrdersQueueHealthIndicator implements HealthIndicator {
private static final int MAX_PENDING_MESSAGES = 1_000;
private final AmqpAdmin amqpAdmin;
public OrdersQueueHealthIndicator(AmqpAdmin amqpAdmin) {
this.amqpAdmin = amqpAdmin;
}
@Override
public Health health() {
var properties = this.amqpAdmin.getQueueProperties("orders");
if (properties == null) {
return Health.down()
.withDetail("queue", "orders")
.withDetail("reason", "missing")
.build();
}
int messages = (Integer) properties.get(RabbitAdmin.QUEUE_MESSAGE_COUNT);
int consumers = (Integer) properties.get(RabbitAdmin.QUEUE_CONSUMER_COUNT);
var health = Health.up()
.withDetail("messages", messages)
.withDetail("consumers", consumers);
return (consumers == 0 || messages > MAX_PENDING_MESSAGES)
? health.down().build()
: health.build();
}
}
O nome da classe sem o sufixo HealthIndicator vira o id ordersQueue, usado no grupo de readiness do YAML. O limite de mil mensagens é uma decisão explícita do exemplo, não uma recomendação universal; ajuste-o ao comportamento aceitável da fila real.
A correção concreta quando esse indicador acusa zero consumidores é restabelecer o listener responsável e então drenar a fila. Se a fila não existe, corrija sua declaração ou o nome configurado. Durante o diagnóstico, consulte /actuator/health/readiness com uma credencial autorizada e acompanhe os campos messages e consumers; depois confirme que o grupo voltou a UP. Isso observa o problema pela porta privada sem publicar detalhes de saúde na internet.
Para uma API downstream, crie outro HealthIndicator, faça um GET curto com RestClient, use timeout na faixa de 200 a 300 ms e devolva Health.down(ex) em falha de I/O. Só então acrescente o id desse indicador — e, se houver DataSource, o db automático do starter JDBC — ao include de readiness. O YAML acima lista só o que o snippet realmente registra, porque um membro desconhecido no grupo falha a subida. O Actuator não cria automaticamente um health indicator para clientes HTTP.
Liveness não é prontidão com outro nome
No Boot 4, os grupos de liveness e readiness estão habilitados por padrão. Eles ficam disponíveis em:
/actuator/health/liveness/actuator/health/readiness
Os grupos padrão não incluem banco, RabbitMQ nem indicadores customizados. A separação é intencional:
- Liveness responde se o processo local precisa ser reiniciado. Não inclua banco, API externa, cache ou fila. Uma falha externa em liveness faz o kubelet reiniciar réplicas saudáveis e pode transformar uma indisponibilidade compartilhada em uma tempestade de reinícios.
- Readiness responde se a réplica deve receber tráfego. Aqui cabem
rabbit,ordersQueuee, depois de existirem no contexto,dbe um indicador HTTP. Aceite a consequência: se uma dependência compartilhada cair, todas as réplicas podem ficar não prontas e retirar o serviço inteiro do balanceamento.
Indicadores DOWN e OUT_OF_SERVICE resultam em HTTP 503; UP e UNKNOWN, em HTTP 200. Para o kubelet, respostas de 200 a 399 são sucesso.
A porta de gestão separada cria mais uma sutileza: ela pode responder enquanto o conector principal está quebrado. Com add-additional-paths: true, o Boot publica /livez e /readyz na porta principal. As probes podem testar o caminho realmente usado pela aplicação:
startupProbe:
httpGet:
path: /livez
port: 8080
livenessProbe:
httpGet:
path: /livez
port: 8080
readinessProbe:
httpGet:
path: /readyz
port: 8080
A startupProbe evita que uma inicialização longa seja confundida com processo morto. Enquanto isso, readiness segura o tráfego. Kubernetes e OpenShift usam esse mesmo modelo de probes; não é necessário inventar uma rota especial para o OpenShift.
Se um indicador ficar lento, o Actuator registra aviso após 10 segundos por padrão. O limite pode ser ajustado com management.endpoint.health.logging.slow-indicator-threshold, mas um health check de downstream deve continuar curto. Probe não é lugar para executar uma auditoria completa da cadeia distribuída.
Métricas respondem perguntas que health checks não respondem
Health é um estado discreto para automação. Tendência, volume e latência pertencem às métricas e à observabilidade com Micrometer Observation.
Com micrometer-registry-prometheus no projeto e prometheus incluído na exposição, o scrape fica em /actuator/prometheus. As duas condições são necessárias; o endpoint não é exposto por padrão. Já /actuator/metrics serve para explorar as medições em JSON, não para o scrape do Prometheus.
Uma configuração mínima do Prometheus aponta para a porta privada de gestão:
scrape_configs:
- job_name: devdojo-orders
metrics_path: /actuator/prometheus
static_configs:
- targets: ["orders-service:8081"]
Entre as métricas fornecidas estão http.server.requests, http.client.requests, famílias jvm.*, processo, sistema, disco, uptime, application.started.time, application.ready.time, eventos de logger e pools de executor. Para estatísticas JVM de virtual threads, acrescente micrometer-java21.
Histogramas adequados ao Prometheus podem ser habilitados com management.metrics.distribution.percentiles-histogram; percentis calculados no processo usam .percentiles, e limites de buckets definidos pela aplicação usam .slo. A documentação não fornece um p95, p99 ou SLO pronto para o serviço. Esses valores só fazem sentido depois de observar a carga e as dependências reais, por isso nenhum número é presumido aqui.
Como verificar antes de liberar
Com a aplicação rodando, faça a checagem pela rede que alcança a porta de gestão:
curl -i http://localhost:8081/actuator
curl -i http://localhost:8081/actuator/health
curl -i http://localhost:8081/actuator/health/liveness
curl -i http://localhost:8081/actuator/health/readiness
curl -i -u actuator-admin:SENHA http://localhost:8081/actuator/health
curl -i -u actuator-admin:SENHA http://localhost:8081/actuator/prometheus
curl -i http://localhost:8080/actuator/env
A descoberta deve listar somente o que foi exposto. O health sem autorização não deve revelar componentes; a chamada autorizada pode mostrar detalhes conforme o papel configurado. O Prometheus deve responder no formato de texto após autenticação. A tentativa pública a /actuator/env na porta 8080 deve retornar 404.
Para reproduzir o falso verde da fila sem simular carga, mantenha apenas o indicador automático rabbit, interrompa o listener e publique mensagens. O broker continuará acessível e o componente rabbit ficará UP. Depois registre OrdersQueueHealthIndicator, inclua ordersQueue em readiness e repita: zero consumidores ou mais de mil mensagens deve levar o grupo a DOWN e a resposta a 503.
Recomendação final
Em produção, exponha health, info, metrics e prometheus somente em uma porta privada; acrescente loggers apenas se houver necessidade operacional, autenticação e acesso somente leitura. Mantenha dumps, configuração e endpoints administrativos fora da exposição e limite o acesso como segunda barreira.
Use liveness apenas para falhas locais irrecuperáveis e leve banco, fila e HTTP downstream para readiness quando retirar a réplica do tráfego for realmente a resposta correta. Se a indisponibilidade compartilhada deixaria todas as réplicas não prontas, não coloque a dependência no grupo por reflexo: trate a degradação acima com timeout e circuit breaking e observe-a por métricas.
Para o caso da fila, não aceite RabbitHealthIndicator como prova de consumo. Adote um indicador de fila na readiness quando backlog ou ausência de consumidores significar que a réplica não consegue cumprir sua função; caso contrário, mantenha o sinal nas métricas e nos alertas, sem transformar cada oscilação operacional em reinício de contêiner.