Skip to main content

Consul Sample

The Consul sample demonstrates Spring Boot Admin Server integration with HashiCorp Consul for service discovery. This sample shows how to leverage Consul's powerful service registry and health checking capabilities to automatically discover and monitor Spring Boot applications.

Overview

Location: spring-boot-admin-samples/spring-boot-admin-sample-consul/

Features:

  • Automatic service discovery via Consul
  • No Admin Client required on monitored applications
  • Consul health check integration
  • Metadata-based configuration
  • Custom actuator endpoint paths
  • Spring Security integration
  • Servlet-based deployment

Prerequisites

  • Java 17 or higher
  • Maven 3.6+
  • Consul installed and running

Installing Consul

macOS

brew install consul

Linux

wget https://releases.hashicorp.com/consul/1.17.0/consul_1.17.0_linux_amd64.zip
unzip consul_1.17.0_linux_amd64.zip
sudo mv consul /usr/local/bin/

Windows

Download from: https://www.consul.io/downloads

Docker

docker run -d -p 8500:8500 -p 8600:8600/udp --name=consul consul agent -server -ui -bootstrap-expect=1 -client=0.0.0.0

Verify Installation

consul version

Running the Sample

Start Consul

# Development mode (single node)
consul agent -dev

Verify Consul is running: http://localhost:8500/ui

Start Admin Server

cd spring-boot-admin-samples/spring-boot-admin-sample-consul
mvn spring-boot:run

Access Admin UI at: http://localhost:8080

With Different Consul Host

mvn spring-boot:run -Dspring-boot.run.arguments=\
--spring.cloud.consul.host=consul-server

Insecure Mode

mvn spring-boot:run -Dspring-boot.run.profiles=insecure

Project Structure

Dependencies

<dependencies>
<!-- Admin Server -->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
</dependency>

<!-- Consul Discovery -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>

<!-- Web (Servlet) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>

<!-- Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>

Main Application Class

SpringBootAdminConsulApplication.java
@SpringBootApplication
@EnableDiscoveryClient // Enable Consul discovery
@EnableAdminServer // Enable Admin Server
public class SpringBootAdminConsulApplication {

static void main(String[] args) {
SpringApplication.run(SpringBootAdminConsulApplication.class, args);
}
}

Configuration

Admin Server Configuration

application.yml
spring:
application:
name: consul-example

cloud:
config:
enabled: false # Disable config client
consul:
host: localhost
port: 8500
discovery:
metadata:
# IMPORTANT: Use dashes, not dots in metadata keys!
management-context-path: /foo
health-path: /ping
user-name: user
user-password: password

profiles:
active:
- secure

boot:
admin:
discovery:
ignored-services: consul # Don't monitor Consul itself

management:
endpoints:
web:
exposure:
include: "*"
base-path: /foo # Custom actuator base path
path-mapping:
health: /ping # Custom health endpoint path
endpoint:
health:
show-details: ALWAYS
Metadata Key Restriction

CRITICAL: Consul metadata keys cannot contain dots. Use dashes instead:

  • management-context-path
  • management.context-path

This is a Consul limitation, not a Spring Boot Admin limitation.

Client Application Configuration

For applications to be monitored:

spring:
application:
name: my-service

cloud:
consul:
host: localhost
port: 8500
discovery:
metadata:
management-context-path: /actuator # Use dashes!
health-path: /actuator/health
# For secured actuators
user-name: ${actuator.username}
user-password: ${actuator.password}

management:
endpoints:
web:
exposure:
include: "*"

Security Configuration

Insecure Profile

@Profile("insecure")
@Configuration
public static class SecurityPermitAllConfig {

@Bean
protected SecurityFilterChain filterChain(HttpSecurity http)
throws Exception {
http.authorizeHttpRequests((authorizeRequests) ->
authorizeRequests.anyRequest().permitAll())
.csrf((csrf) -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.ignoringRequestMatchers(
adminContextPath + "/instances",
adminContextPath + "/instances/*",
adminContextPath + "/actuator/**"
));
return http.build();
}
}

Secure Profile (Default)

@Profile("secure")
@Configuration
public static class SecuritySecureConfig {

@Bean
protected SecurityFilterChain filterChain(HttpSecurity http)
throws Exception {
SavedRequestAwareAuthenticationSuccessHandler successHandler =
new SavedRequestAwareAuthenticationSuccessHandler();
successHandler.setTargetUrlParameter("redirectTo");
successHandler.setDefaultTargetUrl(adminContextPath + "/");

http.authorizeHttpRequests((authorizeRequests) ->
authorizeRequests
.requestMatchers(adminContextPath + "/assets/**")
.permitAll()
.requestMatchers(adminContextPath + "/login")
.permitAll()
.anyRequest()
.authenticated())
.formLogin((formLogin) -> formLogin
.loginPage(adminContextPath + "/login")
.successHandler(successHandler))
.logout((logout) -> logout
.logoutUrl(adminContextPath + "/logout"))
.httpBasic(Customizer.withDefaults())
.csrf((csrf) -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.ignoringRequestMatchers(
adminContextPath + "/instances",
adminContextPath + "/instances/*",
adminContextPath + "/actuator/**"
));

return http.build();
}
}

How It Works

Service Discovery Flow

  1. Application Registration:

    • Application registers with Consul on startup
    • Sends service metadata including actuator paths
    • Consul assigns service ID and health checks
  2. Health Checking:

    • Consul performs HTTP health checks
    • Marks services as passing/failing
    • Admin Server queries only healthy services
  3. Admin Discovery:

    • Admin Server queries Consul for registered services
    • Reads metadata to locate actuator endpoints
    • Begins monitoring discovered services
  4. Deregistration:

    • Application deregisters on shutdown
    • Consul removes from registry
    • Admin Server stops monitoring

Metadata Mapping

Admin Server reads specific metadata keys from Consul:

spring:
cloud:
consul:
discovery:
metadata:
# Required for endpoint detection
management-context-path: /actuator # Dashes only!
management-port: 8081 # If different

# Optional - for secured actuators
user-name: admin
user-password: ${ACTUATOR_PASSWORD}

# Custom metadata
environment: production
version: 1.0.0
team: platform

Key Mappings:

  • management-context-path → Where to find actuator endpoints
  • management-port → Management port if different from service port
  • health-path → Custom health endpoint path
  • user-name / user-password → Actuator credentials

Custom Actuator Paths

This sample demonstrates custom actuator paths:

management:
endpoints:
web:
base-path: /foo # Actuator at /foo instead of /actuator
path-mapping:
health: /ping # Health at /foo/ping instead of /foo/health

Admin Server discovers these via metadata:

spring:
cloud:
consul:
discovery:
metadata:
management-context-path: /foo
health-path: /ping

Testing the Sample

Verify Consul Registration

  1. Access Consul UI: http://localhost:8500/ui
  2. Navigate to "Services"
  3. Should see:
    • consul-example (Admin Server)
    • Other registered services

Check Service Health

In Consul UI, services should show:

  • Status: Passing (green)
  • Health check URL displayed
  • Metadata visible

Verify Admin Discovery

  1. Access Admin UI: http://localhost:8080
  2. Should see services registered in Consul
  3. Click service to view:
    • Health status
    • Metrics
    • Environment
    • Custom /ping endpoint

Test Dynamic Discovery

Register a new service:

# Register via Consul API
curl -X PUT -d '{
"Name": "test-service",
"Address": "127.0.0.1",
"Port": 8081,
"Meta": {
"management-context-path": "/actuator"
},
"Check": {
"HTTP": "http://127.0.0.1:8081/actuator/health",
"Interval": "10s"
}
}' http://localhost:8500/v1/agent/service/register

Service appears in Admin UI within seconds.

Consul Features

Health Checks

Consul supports multiple health check types:

HTTP Health Check

spring:
cloud:
consul:
discovery:
health-check-path: /actuator/health
health-check-interval: 10s
health-check-timeout: 5s

TTL Health Check

spring:
cloud:
consul:
discovery:
health-check-ttl: 30s

Application must send heartbeat to Consul every 30 seconds.

Service Tags

Add tags for filtering:

spring:
cloud:
consul:
discovery:
tags:
- production
- backend
- v1.0.0

Service Filtering

Filter services monitored by Admin:

spring:
boot:
admin:
discovery:
ignored-services:
- consul # Don't monitor Consul
- config-server # Don't monitor Config Server
services: # Only monitor these (if specified)
- my-service
- another-service

Advanced Configuration

Consul ACL

Secure Consul with ACL tokens:

spring:
cloud:
consul:
token: ${CONSUL_TOKEN}
discovery:
acl-token: ${CONSUL_ACL_TOKEN}

Consul TLS

Connect to Consul over TLS:

spring:
cloud:
consul:
scheme: https
tls:
enabled: true
key-store-path: classpath:consul-keystore.p12
key-store-password: ${KEYSTORE_PASSWORD}

Multiple Datacenters

Register in specific datacenter:

spring:
cloud:
consul:
discovery:
datacenter: dc1

Prefer IP Address

Use IP instead of hostname:

spring:
cloud:
consul:
discovery:
prefer-ip-address: true
ip-address: 192.168.1.100

Comparison: Consul vs. Eureka

FeatureConsulEureka
Health ChecksBuilt-in (HTTP, TCP, TTL, Script)Via Spring Boot actuator only
Key-Value StoreYesNo
ACLYesBasic
Multi-DCNative supportRequires setup
DNS InterfaceYesNo
Metadata KeysNo dots allowedDots allowed
ComplexityHigherLower
EcosystemHashiCorp ecosystemNetflix stack

Troubleshooting

Metadata Key Errors

Symptom: Admin Server can't find actuator endpoints

Cause: Used dots in metadata keys

Solution: Use dashes instead:

# Wrong
metadata:
management.context-path: /actuator

# Correct
metadata:
management-context-path: /actuator

Services Not Discovered

Check Consul connectivity:

# Test Consul API
curl http://localhost:8500/v1/catalog/services

# Check health
curl http://localhost:8500/v1/health/state/passing

Verify Admin logs:

tail -f logs/spring-boot-admin.log | grep -i consul

Health Check Failures

Services show as "failing" in Consul:

  1. Verify health endpoint is accessible:

    curl http://localhost:8080/actuator/health
  2. Check health check interval:

    spring:
    cloud:
    consul:
    discovery:
    health-check-interval: 30s # Increase if needed
  3. Review Consul logs:

    consul monitor

Connection Timeouts

Increase timeout values:

spring:
cloud:
consul:
discovery:
health-check-timeout: 10s # Increase from default

Production Considerations

Consul Cluster

Run Consul in cluster mode (3 or 5 nodes):

# Server node 1
consul agent -server -bootstrap-expect=3 -data-dir=/consul/data \
-bind=192.168.1.10

# Server node 2
consul agent -server -data-dir=/consul/data \
-bind=192.168.1.11 -join=192.168.1.10

# Server node 3
consul agent -server -data-dir=/consul/data \
-bind=192.168.1.12 -join=192.168.1.10

Enable ACL

spring:
cloud:
consul:
token: ${CONSUL_MANAGEMENT_TOKEN}
discovery:
acl-token: ${CONSUL_SERVICE_TOKEN}

Monitor Consul Health

Register Admin Server to monitor itself:

spring:
boot:
admin:
discovery:
ignored-services: [] # Don't ignore any services

Key Takeaways

This sample demonstrates:

Consul Integration

  • Service discovery via Consul
  • Health check integration

Metadata Handling

  • Proper metadata key formatting (dashes not dots)
  • Custom actuator paths

Production Features

  • ACL support
  • TLS encryption
  • Multi-datacenter awareness

Flexibility

  • Custom endpoint paths
  • Secure and insecure modes

Next Steps

See Also