Traefik Mesh is a simple yet powerful service mesh that provides traffic management, observability, and security features without requiring complex configuration. It's designed to be lightweight and easy to use.
Traefik Mesh Architecture Overview
Key Components:
- Data Plane: Proxies (Traefik) that handle actual traffic
- Control Plane: Manages configuration and service discovery
- CRDs (Custom Resource Definitions): Kubernetes resources for configuration
- mTLS: Automatic mutual TLS between services
- Access Control: Traffic policies and routing rules
Java Application → Traefik Mesh → Services
- Applications run normally, unaware of the mesh
- Traefik proxies handle service-to-service communication
- Configuration via Kubernetes CRDs
Prerequisites and Dependencies
Maven Dependencies
<properties>
<spring-boot.version>3.1.0</spring-boot.version>
<micrometer.version>1.11.5</micrometer.version>
<resilience4j.version>2.1.0</resilience4j.version>
<kubernetes-client.version>6.7.2</kubernetes-client.version>
</properties>
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Observability -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<version>${micrometer.version}</version>
</dependency>
<!-- Resilience -->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot3</artifactId>
<version>${resilience4j.version}</version>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-micrometer</artifactId>
<version>${resilience4j.version}</version>
</dependency>
<!-- Kubernetes Client -->
<dependency>
<groupId>io.kubernetes</groupId>
<artifactId>client-java</artifactId>
<version>${kubernetes-client.version}</version>
</dependency>
</dependencies>
Kubernetes Manifests for Traefik Mesh
# traefik-mesh-installation.yaml apiVersion: v1 kind: Namespace metadata: name: traefik-mesh --- apiVersion: helm.cattle.io/v1 kind: HelmChart metadata: name: traefik-mesh namespace: traefik-mesh spec: chart: traefik-mesh repo: https://helm.traefik.io/mesh targetNamespace: traefik-mesh set: dashboard.enabled: true metrics.prometheus.enabled: true tracing.jaeger.enabled: true acp.enabled: true
Java Application Configuration
1. Spring Boot Application with Mesh Awareness
@SpringBootApplication
@EnableConfigurationProperties(MeshProperties.class)
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
@Bean
public RestTemplate meshAwareRestTemplate(MeshProperties meshProperties) {
RestTemplate restTemplate = new RestTemplate();
// Add headers for mesh tracing and context propagation
restTemplate.setInterceptors(Collections.singletonList(
new MeshHeaderInterceptor(meshProperties)
));
return restTemplate;
}
@Bean
public WebClient meshAwareWebClient(MeshProperties meshProperties) {
return WebClient.builder()
.filter(new MeshWebClientFilter(meshProperties))
.build();
}
}
@ConfigurationProperties(prefix = "mesh")
@Data
public class MeshProperties {
private boolean enabled = true;
private String serviceName;
private String clusterName = "default";
private String namespace = "default";
private Tracing tracing = new Tracing();
private Metrics metrics = new Metrics();
@Data
public static class Tracing {
private boolean enabled = true;
private String headerName = "x-request-id";
private String traceIdHeader = "x-b3-traceid";
private String spanIdHeader = "x-b3-spanid";
}
@Data
public static class Metrics {
private boolean enabled = true;
private String path = "/actuator/prometheus";
}
}
2. Mesh Header Interceptor
@Component
public class MeshHeaderInterceptor implements ClientHttpRequestInterceptor {
private final MeshProperties meshProperties;
private final Tracer tracer;
public MeshHeaderInterceptor(MeshProperties meshProperties) {
this.meshProperties = meshProperties;
this.tracer = GlobalOpenTelemetry.getTracer("mesh-interceptor");
}
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
// Add mesh-specific headers
addMeshHeaders(request);
// Add tracing headers if available
addTracingHeaders(request);
// Add service identity headers
addServiceIdentityHeaders(request);
return execution.execute(request, body);
}
private void addMeshHeaders(HttpRequest request) {
if (meshProperties.isEnabled()) {
request.getHeaders().add("x-mesh-service", meshProperties.getServiceName());
request.getHeaders().add("x-mesh-namespace", meshProperties.getNamespace());
request.getHeaders().add("x-mesh-cluster", meshProperties.getClusterName());
}
}
private void addTracingHeaders(HttpRequest request) {
if (meshProperties.getTracing().isEnabled()) {
Span currentSpan = Span.current();
if (!currentSpan.getSpanContext().equals(SpanContext.getInvalid())) {
String traceId = currentSpan.getSpanContext().getTraceId();
String spanId = currentSpan.getSpanContext().getSpanId();
request.getHeaders().add(meshProperties.getTracing().getTraceIdHeader(), traceId);
request.getHeaders().add(meshProperties.getTracing().getSpanIdHeader(), spanId);
}
// Add request ID for correlation
request.getHeaders().add(meshProperties.getTracing().getHeaderName(),
UUID.randomUUID().toString());
}
}
private void addServiceIdentityHeaders(HttpRequest request) {
// Add headers that might be used for ACP (Access Control Policies)
request.getHeaders().add("x-forwarded-for", getClientIp());
request.getHeaders().add("x-forwarded-proto", "https");
request.getHeaders().add("x-forwarded-host", getHostName());
}
private String getClientIp() {
// Implementation to get client IP
return "unknown";
}
private String getHostName() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
return "unknown";
}
}
}
3. WebClient Filter for Reactive Applications
@Component
public class MeshWebClientFilter implements ExchangeFilterFunction {
private final MeshProperties meshProperties;
public MeshWebClientFilter(MeshProperties meshProperties) {
this.meshProperties = meshProperties;
}
@Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
if (!meshProperties.isEnabled()) {
return next.exchange(request);
}
// Add mesh headers to the request
ClientRequest filteredRequest = ClientRequest.from(request)
.headers(headers -> {
addMeshHeaders(headers);
addTracingHeaders(headers);
addServiceIdentityHeaders(headers);
})
.build();
return next.exchange(filteredRequest);
}
private void addMeshHeaders(HttpHeaders headers) {
headers.add("x-mesh-service", meshProperties.getServiceName());
headers.add("x-mesh-namespace", meshProperties.getNamespace());
headers.add("x-mesh-cluster", meshProperties.getClusterName());
}
private void addTracingHeaders(HttpHeaders headers) {
if (meshProperties.getTracing().isEnabled()) {
Span currentSpan = Span.current();
if (!currentSpan.getSpanContext().equals(SpanContext.getInvalid())) {
headers.add(meshProperties.getTracing().getTraceIdHeader(),
currentSpan.getSpanContext().getTraceId());
headers.add(meshProperties.getTracing().getSpanIdHeader(),
currentSpan.getSpanContext().getSpanId());
}
headers.add(meshProperties.getTracing().getHeaderName(),
UUID.randomUUID().toString());
}
}
private void addServiceIdentityHeaders(HttpHeaders headers) {
headers.add("x-forwarded-for", getClientIp());
headers.add("x-forwarded-proto", "https");
headers.add("x-forwarded-host", getHostName());
}
private String getClientIp() {
return "unknown";
}
private String getHostName() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
return "unknown";
}
}
}
Traffic Management with Traefik Mesh
1. TrafficSplit Configuration (Kubernetes CRD)
# trafficsplit-canary.yaml apiVersion: split.smi-spec.io/v1alpha4 kind: TrafficSplit metadata: name: order-service-canary namespace: default spec: service: order-service backends: - service: order-service-v1 weight: 90 - service: order-service-v2 weight: 10
2. Java Service with Canary Awareness
@Service
@Slf4j
public class OrderService {
private final RestTemplate restTemplate;
private final MeshProperties meshProperties;
private final MeterRegistry meterRegistry;
private final Counter canaryRequestsCounter;
private final Timer canaryResponseTimer;
public OrderService(RestTemplate restTemplate, MeshProperties meshProperties,
MeterRegistry meterRegistry) {
this.restTemplate = restTemplate;
this.meshProperties = meshProperties;
this.meterRegistry = meterRegistry;
// Initialize metrics
this.canaryRequestsCounter = Counter.builder("mesh.canary.requests")
.description("Number of requests to canary services")
.tag("service", "order-service")
.register(meterRegistry);
this.canaryResponseTimer = Timer.builder("mesh.canary.response.time")
.description("Response time for canary requests")
.tag("service", "order-service")
.register(meterRegistry);
}
@Retry(name = "orderService", fallbackMethod = "fallbackCreateOrder")
@CircuitBreaker(name = "orderService")
@RateLimiter(name = "orderService")
@Timed(value = "order.create", description = "Time taken to create order")
public Order createOrder(CreateOrderRequest request) {
log.info("Creating order for customer: {}", request.getCustomerId());
// The actual service call - Traefik Mesh handles routing
String serviceUrl = determineServiceUrl();
canaryRequestsCounter.increment();
return canaryResponseTimer.record(() -> {
try {
ResponseEntity<Order> response = restTemplate.postForEntity(
serviceUrl, request, Order.class);
logCanaryHit(serviceUrl);
return response.getBody();
} catch (HttpClientErrorException | HttpServerErrorException e) {
log.error("Service call failed: {} - {}", e.getStatusCode(), e.getResponseBodyAsString());
throw new ServiceException("Failed to create order", e);
}
});
}
private String determineServiceUrl() {
// In canary deployment, Traefik Mesh handles routing
// We just call the main service, and mesh routes based on TrafficSplit
return String.format("http://order-service.%s.svc.cluster.local:8080/api/orders",
meshProperties.getNamespace());
}
private void logCanaryHit(String serviceUrl) {
// Log when hitting canary services for monitoring
if (serviceUrl.contains("canary") || serviceUrl.contains("v2")) {
log.warn("Request routed to canary service: {}", serviceUrl);
}
}
private Order fallbackCreateOrder(CreateOrderRequest request, Exception e) {
log.warn("Fallback triggered for order creation: {}", e.getMessage());
// Return a default order or cached response
return Order.builder()
.id("fallback-" + UUID.randomUUID().toString())
.status(OrderStatus.PENDING)
.customerId(request.getCustomerId())
.build();
}
}
3. TrafficTarget for Access Control
# traffictarget.yaml apiVersion: access.smi-spec.io/v1alpha3 kind: TrafficTarget metadata: name: order-service-target namespace: default spec: destination: kind: ServiceAccount name: order-service namespace: default rules: - kind: HTTPRouteGroup name: order-service-routes matches: - orders-get - orders-post sources: - kind: ServiceAccount name: payment-service namespace: default - kind: ServiceAccount name: inventory-service namespace: default --- apiVersion: specs.smi-spec.io/v1alpha4 kind: HTTPRouteGroup metadata: name: order-service-routes namespace: default spec: matches: - name: orders-get pathRegex: /api/orders/.* methods: - GET - name: orders-post pathRegex: /api/orders methods: - POST
4. Access Control in Java
@Component
@Slf4j
public class MeshAccessControl {
private final MeshProperties meshProperties;
public MeshAccessControl(MeshProperties meshProperties) {
this.meshProperties = meshProperties;
}
public boolean validateAccess(HttpServletRequest request, String operation) {
// Extract headers that Traefik Mesh might set
String sourceService = request.getHeader("x-forwarded-for");
String userAgent = request.getHeader("user-agent");
String meshService = request.getHeader("x-mesh-service");
log.debug("Access validation - Source: {}, Operation: {}, MeshService: {}",
sourceService, operation, meshService);
// Implement custom access logic if needed
// Traefik Mesh ACP handles most of this, but you can add additional checks
return isAllowedSource(sourceService, operation);
}
private boolean isAllowedSource(String sourceService, String operation) {
// Custom business logic for access control
if (sourceService == null) {
return true; // Allow direct access (for testing)
}
// Example: Only allow payment service to create orders
if ("POST".equals(operation) && sourceService.contains("payment-service")) {
return true;
}
// Example: Allow inventory service to read orders
if ("GET".equals(operation) && sourceService.contains("inventory-service")) {
return true;
}
return false;
}
}
@RestController
@RequestMapping("/api/orders")
@Slf4j
public class OrderController {
private final OrderService orderService;
private final MeshAccessControl accessControl;
public OrderController(OrderService orderService, MeshAccessControl accessControl) {
this.orderService = orderService;
this.accessControl = accessControl;
}
@PostMapping
public ResponseEntity<Order> createOrder(@RequestBody CreateOrderRequest request,
HttpServletRequest httpRequest) {
// Validate access using mesh headers
if (!accessControl.validateAccess(httpRequest, "POST")) {
log.warn("Access denied for order creation from: {}",
httpRequest.getHeader("x-forwarded-for"));
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}
Order order = orderService.createOrder(request);
return ResponseEntity.ok(order);
}
@GetMapping("/{orderId}")
public ResponseEntity<Order> getOrder(@PathVariable String orderId,
HttpServletRequest httpRequest) {
// Validate access using mesh headers
if (!accessControl.validateAccess(httpRequest, "GET")) {
log.warn("Access denied for order retrieval from: {}",
httpRequest.getHeader("x-forwarded-for"));
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}
Order order = orderService.getOrder(orderId);
return ResponseEntity.ok(order);
}
}
Observability with Traefik Mesh
1. Metrics Configuration
# service-monitor.yaml apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: order-service-monitor namespace: default labels: app: order-service spec: selector: matchLabels: app: order-service endpoints: - port: http-metrics path: /actuator/prometheus interval: 15s
2. Spring Boot Actuator Configuration
# application.yaml
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
metrics:
enabled: true
prometheus:
enabled: true
metrics:
export:
prometheus:
enabled: true
distribution:
percentiles-histogram:
http.server.requests: true
tags:
application: order-service
namespace: ${MESH_NAMESPACE:default}
cluster: ${MESH_CLUSTER:default}
3. Custom Mesh Metrics
@Component
public class MeshMetricsService {
private final MeterRegistry meterRegistry;
private final Map<String, Counter> serviceCallCounters = new ConcurrentHashMap<>();
private final Map<String, Timer> serviceCallTimers = new ConcurrentHashMap<>();
public MeshMetricsService(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
public void recordServiceCall(String targetService, String operation, long durationMs, boolean success) {
String counterName = "mesh.service.calls";
String timerName = "mesh.service.duration";
// Get or create counter
Counter counter = serviceCallCounters.computeIfAbsent(targetService,
service -> Counter.builder(counterName)
.tag("target_service", service)
.tag("operation", operation)
.tag("success", String.valueOf(success))
.register(meterRegistry));
counter.increment();
// Record duration
Timer timer = serviceCallTimers.computeIfAbsent(targetService,
service -> Timer.builder(timerName)
.tag("target_service", service)
.tag("operation", operation)
.register(meterRegistry));
timer.record(durationMs, TimeUnit.MILLISECONDS);
}
public void recordCircuitBreakerState(String breakerName, String state) {
Counter.builder("mesh.circuitbreaker.state")
.tag("breaker", breakerName)
.tag("state", state)
.register(meterRegistry)
.increment();
}
public void recordRetryAttempt(String operation, int attempt) {
Counter.builder("mesh.retry.attempts")
.tag("operation", operation)
.tag("attempt", String.valueOf(attempt))
.register(meterRegistry)
.increment();
}
}
4. Distributed Tracing Integration
@Component
public class MeshTracingService {
private final Tracer tracer;
private final MeshProperties meshProperties;
public MeshTracingService(MeshProperties meshProperties) {
this.meshProperties = meshProperties;
this.tracer = GlobalOpenTelemetry.getTracer("mesh-tracing");
}
public Span startMeshSpan(String operationName, String targetService) {
return tracer.spanBuilder(operationName)
.setAttribute("mesh.service", meshProperties.getServiceName())
.setAttribute("mesh.target.service", targetService)
.setAttribute("mesh.namespace", meshProperties.getNamespace())
.setAttribute("mesh.operation", operationName)
.startSpan();
}
public void recordMeshSpanError(Span span, String errorType, String errorMessage) {
span.setStatus(StatusCode.ERROR, errorMessage);
span.setAttribute("error.type", errorType);
span.recordException(new RuntimeException(errorMessage));
}
public void addMeshAttributes(Span span, Map<String, Object> attributes) {
attributes.forEach((key, value) -> {
if (value instanceof String) {
span.setAttribute(key, (String) value);
} else if (value instanceof Long) {
span.setAttribute(key, (Long) value);
} else if (value instanceof Double) {
span.setAttribute(key, (Double) value);
} else if (value instanceof Boolean) {
span.setAttribute(key, (Boolean) value);
}
});
}
}
Resilience Patterns with Traefik Mesh
1. Circuit Breaker Configuration
# application-resilience.yaml resilience4j: circuitbreaker: instances: orderService: register-health-indicator: true sliding-window-size: 10 sliding-window-type: COUNT_BASED minimum-number-of-calls: 5 permitted-number-of-calls-in-half-open-state: 3 automatic-transition-from-open-to-half-open-enabled: true wait-duration-in-open-state: 10s failure-rate-threshold: 50 event-consumer-buffer-size: 10 retry: instances: orderService: max-attempts: 3 wait-duration: 500ms ratelimiter: instances: orderService: limit-for-period: 10 limit-refresh-period: 1s timeout-duration: 0 allow-health-indicator-to-fail: true subscribe-for-events: true timelimiter: instances: orderService: timeout-duration: 5s cancel-running-future: true
2. Resilient Service Client
@Service
@Slf4j
public class ResilientInventoryClient {
private final RestTemplate restTemplate;
private final MeshMetricsService metricsService;
private final MeshTracingService tracingService;
private final CircuitBreakerRegistry circuitBreakerRegistry;
private final RetryRegistry retryRegistry;
private final CircuitBreaker circuitBreaker;
private final Retry retry;
public ResilientInventoryClient(RestTemplate restTemplate,
MeshMetricsService metricsService,
MeshTracingService tracingService,
CircuitBreakerRegistry circuitBreakerRegistry,
RetryRegistry retryRegistry) {
this.restTemplate = restTemplate;
this.metricsService = metricsService;
this.tracingService = tracingService;
this.circuitBreakerRegistry = circuitBreakerRegistry;
this.retryRegistry = retryRegistry;
this.circuitBreaker = circuitBreakerRegistry.circuitBreaker("inventoryService");
this.retry = retryRegistry.retry("inventoryService");
}
public boolean checkInventory(List<OrderItem> items) {
Span span = tracingService.startMeshSpan("inventory.check", "inventory-service");
try (Scope scope = span.makeCurrent()) {
long startTime = System.currentTimeMillis();
// Use Circuit Breaker and Retry
Boolean result = Retry.decorateSupplier(retry,
CircuitBreaker.decorateSupplier(circuitBreaker,
() -> callInventoryService(items))
).get();
long duration = System.currentTimeMillis() - startTime;
// Record metrics
metricsService.recordServiceCall("inventory-service", "check", duration, true);
span.setStatus(StatusCode.OK);
return Boolean.TRUE.equals(result);
} catch (Exception e) {
long duration = System.currentTimeMillis() - startTime;
metricsService.recordServiceCall("inventory-service", "check", duration, false);
tracingService.recordMeshSpanError(span, "inventory_call_failed", e.getMessage());
log.error("Inventory check failed: {}", e.getMessage());
throw new ServiceException("Inventory service unavailable", e);
} finally {
span.end();
}
}
private Boolean callInventoryService(List<OrderItem> items) {
String url = "http://inventory-service.default.svc.cluster.local:8080/api/inventory/check";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<List<OrderItem>> request = new HttpEntity<>(items, headers);
ResponseEntity<Boolean> response = restTemplate.exchange(
url, HttpMethod.POST, request, Boolean.class);
return response.getBody();
}
@EventListener
public void onCircuitBreakerEvent(CircuitBreakerOnStateTransitionEvent event) {
metricsService.recordCircuitBreakerState(
event.getCircuitBreakerName(),
event.getStateTransition().getToState().name()
);
log.info("Circuit breaker state changed: {} -> {}",
event.getCircuitBreakerName(),
event.getStateTransition().getToState());
}
@EventListener
public void onRetryEvent(RetryOnRetryEvent event) {
metricsService.recordRetryAttempt(
"inventoryService",
event.getNumberOfRetryAttempts()
);
log.warn("Retry attempt {} for operation: {}",
event.getNumberOfRetryAttempts(), event.getName());
}
}
Kubernetes Deployment with Traefik Mesh
1. Deployment Configuration
# deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: order-service namespace: default labels: app: order-service version: v1 spec: replicas: 3 selector: matchLabels: app: order-service version: v1 template: metadata: labels: app: order-service version: v1 annotations: prometheus.io/scrape: "true" prometheus.io/path: "/actuator/prometheus" prometheus.io/port: "8080" traefik.mesh.smi-spec.io/traffic-split: "order-service" spec: serviceAccountName: order-service containers: - name: order-service image: order-service:latest ports: - containerPort: 8080 name: http - containerPort: 8081 name: http-metrics env: - name: MESH_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: MESH_SERVICE_NAME value: "order-service" - name: MESH_CLUSTER value: "production" - name: JAVA_OPTS value: "-javaagent:/app/opentelemetry-javaagent.jar" - name: OTEL_SERVICE_NAME value: "order-service" - name: OTEL_EXPORTER_OTLP_ENDPOINT value: "http://otel-collector:4317" resources: requests: memory: "512Mi" cpu: "250m" limits: memory: "1Gi" cpu: "500m" livenessProbe: httpGet: path: /actuator/health port: 8081 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /actuator/health/readiness port: 8081 initialDelaySeconds: 15 periodSeconds: 5 --- apiVersion: v1 kind: Service metadata: name: order-service namespace: default labels: app: order-service spec: selector: app: order-service ports: - port: 8080 targetPort: 8080 name: http - port: 8081 targetPort: 8081 name: http-metrics type: ClusterIP
2. ServiceAccount for Access Control
# serviceaccount.yaml apiVersion: v1 kind: ServiceAccount metadata: name: order-service namespace: default labels: app: order-service --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: namespace: default name: order-service rules: - apiGroups: [""] resources: ["pods"] verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["services"] verbs: ["get", "list"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: order-service namespace: default subjects: - kind: ServiceAccount name: order-service namespace: default roleRef: kind: Role name: order-service apiGroup: rbac.authorization.k8s.io
Testing and Monitoring
1. Integration Tests
@SpringBootTest
@ActiveProfiles("test")
@Testcontainers
class MeshIntegrationTest {
@Container
static GenericContainer<?> traefikMesh = new GenericContainer<>("traefik/mesh:latest")
.withExposedPorts(8080, 8081)
.withEnv("MESH_ENABLED", "true");
@Autowired
private TestRestTemplate restTemplate;
@Test
void shouldRouteThroughMesh() {
// Test that requests are properly routed through the mesh
ResponseEntity<String> response = restTemplate.getForEntity(
"/api/orders/123", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
// Verify mesh headers are present
HttpHeaders headers = response.getHeaders();
assertThat(headers.get("x-mesh-service")).isNotNull();
}
}
2. Health Checks
@Component
public class MeshHealthIndicator implements HealthIndicator {
private final MeshProperties meshProperties;
private final RestTemplate restTemplate;
public MeshHealthIndicator(MeshProperties meshProperties, RestTemplate restTemplate) {
this.meshProperties = meshProperties;
this.restTemplate = restTemplate;
}
@Override
public Health health() {
if (!meshProperties.isEnabled()) {
return Health.unknown()
.withDetail("mesh", "disabled")
.build();
}
try {
// Test mesh connectivity by calling a known service
ResponseEntity<String> response = restTemplate.getForEntity(
"http://traefik-mesh-dashboard.default.svc.cluster.local:8080",
String.class);
if (response.getStatusCode().is2xxSuccessful()) {
return Health.up()
.withDetail("mesh", "connected")
.withDetail("service", meshProperties.getServiceName())
.build();
} else {
return Health.down()
.withDetail("mesh", "unreachable")
.withDetail("status", response.getStatusCodeValue())
.build();
}
} catch (Exception e) {
return Health.down()
.withDetail("mesh", "connection_failed")
.withDetail("error", e.getMessage())
.build();
}
}
}
Best Practices
- Use Semantic Service Names: Follow naming conventions for services
- Configure Proper Resource Limits: Ensure services have appropriate CPU/memory
- Implement Graceful Shutdown: Handle SIGTERM properly
- Use mTLS: Enable mutual TLS for service-to-service communication
- Monitor Mesh Metrics: Track mesh performance and health
- Test Failure Scenarios: Verify resilience patterns work correctly
// Graceful shutdown example
@Component
public class MeshShutdownHandler {
private static final Logger logger = LoggerFactory.getLogger(MeshShutdownHandler.class);
@EventListener
public void onShutdown(ContextClosedEvent event) {
logger.info("Application shutting down, cleaning up mesh connections...");
// Close connections, release resources
// Traefik Mesh will handle draining connections
logger.info("Mesh shutdown completed");
}
}
Conclusion
Traefik Mesh integration provides:
- Simplified service mesh without complex configuration
- Automatic mTLS for secure service communication
- Traffic management with canary deployments and load balancing
- Access control through SMI specifications
- Observability with built-in metrics and tracing
By implementing the patterns shown above, Java applications can fully leverage Traefik Mesh capabilities while maintaining clean separation of concerns. The mesh handles the infrastructure concerns, allowing applications to focus on business logic while benefiting from robust service mesh features.