/** * POST TITLE: Traffic Mirroring in Service Mesh with Java * * Complete implementation of traffic mirroring (shadowing) for Istio, Linkerd, and custom solutions */ import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.KubernetesClientBuilder; import io.fabric8.kubernetes.api.model.*; import io.fabric8.istio.client.IstioClient; import io.fabric8.istio.client.DefaultIstioClient; import io.fabric8.istio.api.networking.v1alpha3.VirtualService; import io.fabric8.istio.api.networking.v1alpha3.VirtualServiceBuilder; import io.fabric8.istio.api.networking.v1alpha3.Destination; import io.fabric8.istio.api.networking.v1alpha3.HTTPRoute; import io.fabric8.istio.api.networking.v1alpha3.HTTPRouteDestination; import io.fabric8.istio.api.networking.v1alpha3.HTTPMirror; import java.util.*; import java.util.concurrent.*; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.net.URI; public class TrafficMirroringService { /** * Traffic Mirroring Configuration */ public static class MirrorConfig { private String name; private String sourceService; private String mirrorService; private double mirrorPercentage; private Map<String, String> matchConditions; private Map<String, String> headers; private boolean preserveOriginal; private int timeoutMs; private int retryCount; public MirrorConfig(String name, String sourceService, String mirrorService) { this.name = name; this.sourceService = sourceService; this.mirrorService = mirrorService; this.mirrorPercentage = 100.0; // Default: mirror all traffic this.matchConditions = new HashMap<>(); this.headers = new HashMap<>(); this.preserveOriginal = true; this.timeoutMs = 5000; this.retryCount = 0; } // Builder methods public MirrorConfig withPercentage(double percentage) { this.mirrorPercentage = Math.max(0, Math.min(100, percentage)); return this; } public MirrorConfig withMatchCondition(String header, String value) { this.matchConditions.put(header, value); return this; } public MirrorConfig withHeader(String name, String value) { this.headers.put(name, value); return this; } public MirrorConfig withTimeout(int timeoutMs) { this.timeoutMs = timeoutMs; return this; } public MirrorConfig withRetries(int retryCount) { this.retryCount = retryCount; return this; } public MirrorConfig preserveOriginal(boolean preserve) { this.preserveOriginal = preserve; return this; } // Getters public String getName() { return name; } public String getSourceService() { return sourceService; } public String getMirrorService() { return mirrorService; } public double getMirrorPercentage() { return mirrorPercentage; } public Map<String, String> getMatchConditions() { return matchConditions; } public Map<String, String> getHeaders() { return headers; } public boolean isPreserveOriginal() { return preserveOriginal; } public int getTimeoutMs() { return timeoutMs; } public int getRetryCount() { return retryCount; } } /** * Istio Traffic Mirroring Manager */ public static class IstioMirroringManager { private final IstioClient istioClient; private final String namespace; public IstioMirroringManager(IstioClient istioClient, String namespace) { this.istioClient = istioClient; this.namespace = namespace; } /** * Configure traffic mirroring using Istio VirtualService */ public void configureMirroring(MirrorConfig config) { try { System.out.println("🎯 Configuring Istio traffic mirroring: " + config.getName()); VirtualService virtualService = createVirtualService(config); // Apply or update VirtualService VirtualService existing = istioClient.v1alpha3().virtualServices() .inNamespace(namespace) .withName(config.getSourceService() + "-mirror") .get(); if (existing != null) { istioClient.v1alpha3().virtualServices() .inNamespace(namespace) .withName(config.getSourceService() + "-mirror") .patch(virtualService); System.out.println("✅ Updated existing VirtualService for mirroring"); } else { istioClient.v1alpha3().virtualServices() .inNamespace(namespace) .create(virtualService); System.out.println("✅ Created new VirtualService for mirroring"); } System.out.println("📊 Mirroring " + config.getMirrorPercentage() + "% of traffic from " + config.getSourceService() + " to " + config.getMirrorService()); } catch (Exception e) { System.err.println("❌ Failed to configure Istio mirroring: " + e.getMessage()); throw new RuntimeException("Istio mirroring configuration failed", e); } } private VirtualService createVirtualService(MirrorConfig config) { return new VirtualServiceBuilder() .withNewMetadata() .withName(config.getSourceService() + "-mirror") .withNamespace(namespace) .withLabels(createLabels(config)) .endMetadata() .withNewSpec() .withHosts(Arrays.asList(config.getSourceService())) .withHttp(createHttpRoutes(config)) .endSpec() .build(); } private List<HTTPRoute> createHttpRoutes(MirrorConfig config) { HTTPRoute route = new HTTPRoute(); // Match conditions if (!config.getMatchConditions().isEmpty()) { route.setMatch(createMatchConditions(config.getMatchConditions())); } // Route to original destination List<HTTPRouteDestination> routeDestinations = new ArrayList<>(); HTTPRouteDestination originalDest = new HTTPRouteDestination(); originalDest.setDestination(createDestination(config.getSourceService())); if (config.getMirrorPercentage() < 100) { originalDest.setWeight((int) (100 - config.getMirrorPercentage())); } routeDestinations.add(originalDest); // Mirror configuration HTTPMirror mirror = new HTTPMirror(); mirror.setDestination(createDestination(config.getMirrorService())); if (!config.getHeaders().isEmpty()) { mirror.setHeaders(createHeaders(config.getHeaders())); } route.setMirror(mirror); // If not 100% mirroring, add mirror service as additional destination if (config.getMirrorPercentage() > 0 && config.getMirrorPercentage() < 100) { HTTPRouteDestination mirrorDest = new HTTPRouteDestination(); mirrorDest.setDestination(createDestination(config.getMirrorService())); mirrorDest.setWeight((int) config.getMirrorPercentage()); routeDestinations.add(mirrorDest); } route.setRoute(routeDestinations); // Timeout and retry configuration if (config.getTimeoutMs() > 0) { route.setTimeout(String.valueOf(config.getTimeoutMs()) + "ms"); } if (config.getRetryCount() > 0) { route.setRetries(createRetryPolicy(config.getRetryCount())); } return Arrays.asList(route); } private List<io.fabric8.istio.api.networking.v1alpha3.HTTPMatchRequest> createMatchConditions( Map<String, String> conditions) { List<io.fabric8.istio.api.networking.v1alpha3.HTTPMatchRequest> matches = new ArrayList<>(); for (Map.Entry<String, String> entry : conditions.entrySet()) { io.fabric8.istio.api.networking.v1alpha3.HTTPMatchRequest match = new io.fabric8.istio.api.networking.v1alpha3.HTTPMatchRequest(); Map<String, io.fabric8.istio.api.networking.v1alpha3.StringMatch> headers = new HashMap<>(); headers.put(entry.getKey(), new io.fabric8.istio.api.networking.v1alpha3.StringMatch.Builder() .withExact(entry.getValue()) .build()); match.setHeaders(headers); matches.add(match); } return matches; } private Destination createDestination(String serviceName) { return new Destination.Builder() .withHost(serviceName) .withSubset("v1") // Default subset .build(); } private io.fabric8.istio.api.networking.v1alpha3.Headers createHeaders(Map<String, String> headers) { io.fabric8.istio.api.networking.v1alpha3.Headers headerConfig = new io.fabric8.istio.api.networking.v1alpha3.Headers(); // Add request headers Map<String, String> requestHeaders = new HashMap<>(); headers.forEach((key, value) -> requestHeaders.put(key, value)); headerConfig.setRequest(requestHeaders); return headerConfig; } private io.fabric8.istio.api.networking.v1alpha3.HTTPRetry createRetryPolicy(int retries) { return new io.fabric8.istio.api.networking.v1alpha3.HTTPRetry.Builder() .withAttempts(retries) .withPerTryTimeout("2s") .withRetryOn("5xx,gateway-error,connect-failure") .build(); } private Map<String, String> createLabels(MirrorConfig config) { Map<String, String> labels = new HashMap<>(); labels.put("app", config.getSourceService()); labels.put("mirroring-enabled", "true"); labels.put("mirror-target", config.getMirrorService()); labels.put("managed-by", "traffic-mirroring-service"); return labels; } /** * Remove mirroring configuration */ public void removeMirroring(String sourceService) { try { istioClient.v1alpha3().virtualServices() .inNamespace(namespace) .withName(sourceService + "-mirror") .delete(); System.out.println("✅ Removed mirroring configuration for: " + sourceService); } catch (Exception e) { System.err.println("❌ Failed to remove mirroring: " + e.getMessage()); } } /** * Get mirroring status */ public MirrorStatus getMirrorStatus(String sourceService) { try { VirtualService vs = istioClient.v1alpha3().virtualServices() .inNamespace(namespace) .withName(sourceService + "-mirror") .get(); if (vs == null) { return new MirrorStatus(sourceService, MirrorStatus.Status.NOT_CONFIGURED); } return new MirrorStatus( sourceService, MirrorStatus.Status.ACTIVE, extractMirrorPercentage(vs), extractMirrorTarget(vs) ); } catch (Exception e) { return new MirrorStatus(sourceService, MirrorStatus.Status.ERROR, e.getMessage()); } } private double extractMirrorPercentage(VirtualService vs) { // Extract mirror percentage from VirtualService spec // Implementation would parse the HTTPRoute destinations return 100.0; // Simplified } private String extractMirrorTarget(VirtualService vs) { // Extract mirror target from VirtualService spec // Implementation would parse the mirror destination return "unknown"; // Simplified } } /** * Custom Traffic Mirroring Proxy */ public static class CustomMirroringProxy { private final HttpClient httpClient; private final ExecutorService executor; private final Map<String, MirrorConfig> activeConfigs; private final MirroringMetrics metrics; public CustomMirroringProxy() { this.httpClient = HttpClient.newBuilder() .version(HttpClient.Version.HTTP_1_1) .connectTimeout(Duration.ofSeconds(10)) .executor(Executors.newVirtualThreadPerTaskExecutor()) .build(); this.executor = Executors.newCachedThreadPool(); this.activeConfigs = new ConcurrentHashMap<>(); this.metrics = new MirroringMetrics(); } /** * Register mirroring configuration */ public void registerMirroring(MirrorConfig config) { activeConfigs.put(config.getSourceService(), config); System.out.println("✅ Registered mirroring: " + config.getName()); } /** * Process incoming request and mirror if configured */ public CompletableFuture<HttpResponse<String>> processRequest( String sourceService, HttpRequest originalRequest, String requestBody) { MirrorConfig config = activeConfigs.get(sourceService); if (config == null || !shouldMirror(config)) { // No mirroring configured or random check failed return httpClient.sendAsync(originalRequest, HttpResponse.BodyHandlers.ofString()); } CompletableFuture<HttpResponse<String>> originalFuture = httpClient.sendAsync(originalRequest, HttpResponse.BodyHandlers.ofString()); // Mirror the request asynchronously CompletableFuture<Void> mirrorFuture = mirrorRequest(config, originalRequest, requestBody); // Return original response regardless of mirroring result return originalFuture.whenComplete((response, throwable) -> { metrics.recordMirroringAttempt(config.getSourceService(), throwable == null); }); } private boolean shouldMirror(MirrorConfig config) { if (config.getMirrorPercentage() >= 100) { return true; } double random = Math.random() * 100; return random <= config.getMirrorPercentage(); } private CompletableFuture<Void> mirrorRequest( MirrorConfig config, HttpRequest originalRequest, String requestBody) { return CompletableFuture.runAsync(() -> { try { // Create mirror request HttpRequest.Builder mirrorBuilder = HttpRequest.newBuilder() .uri(createMirrorUri(config, originalRequest.uri())) .method(originalRequest.method(), HttpRequest.BodyPublishers.ofString(requestBody)); // Copy headers originalRequest.headers().map().forEach((key, values) -> { if (!shouldSkipHeader(key)) { values.forEach(value -> mirrorBuilder.header(key, value)); } }); // Add mirror-specific headers config.getHeaders().forEach(mirrorBuilder::header); mirrorBuilder.header("X-Traffic-Mirrored", "true"); mirrorBuilder.header("X-Mirror-Source", config.getSourceService()); HttpRequest mirrorRequest = mirrorBuilder .timeout(Duration.ofMillis(config.getTimeoutMs())) .build(); // Execute mirror request with retries executeWithRetries(mirrorRequest, config.getRetryCount()) .thenAccept(response -> { metrics.recordMirrorResponse( config.getSourceService(), response.statusCode() ); System.out.println("🪞 Mirrored request to " + config.getMirrorService() + " - Status: " + response.statusCode()); }) .exceptionally(throwable -> { metrics.recordMirrorError(config.getSourceService(), throwable.getMessage()); System.err.println("❌ Mirror request failed: " + throwable.getMessage()); return null; }); } catch (Exception e) { metrics.recordMirrorError(config.getSourceService(), e.getMessage()); System.err.println("❌ Mirror request setup failed: " + e.getMessage()); } }, executor); } private URI createMirrorUri(MirrorConfig config, URI originalUri) { // Transform URI to point to mirror service String mirrorHost = config.getMirrorService().contains("://") ? config.getMirrorService() : "http://" + config.getMirrorService(); return URI.create(mirrorHost + originalUri.getPath() + (originalUri.getQuery() != null ? "?" + originalUri.getQuery() : "")); } private boolean shouldSkipHeader(String headerName) { // Skip headers that shouldn't be mirrored List<String> skipHeaders = Arrays.asList( "authorization", "cookie", "x-trace-id", "x-mirrored" ); return skipHeaders.contains(headerName.toLowerCase()); } private CompletableFuture<HttpResponse<String>> executeWithRetries( HttpRequest request, int maxRetries) { return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()) .exceptionallyCompose(throwable -> { if (maxRetries > 0) { System.out.println("🔄 Retrying mirror request (" + maxRetries + " retries left)"); return executeWithRetries(request, maxRetries - 1); } throw new CompletionException(throwable); }); } /** * Get mirroring metrics */ public MirroringMetrics getMetrics() { return metrics; } /** * Stop the proxy */ public void shutdown() { executor.shutdown(); try { if (!executor.awaitTermination(5, TimeUnit.SECONDS)) { executor.shutdownNow(); } } catch (InterruptedException e) { executor.shutdownNow(); Thread.currentThread().interrupt(); } } } /** * Mirroring Metrics Collector */ public static class MirroringMetrics { private final Map<String, ServiceMetrics> serviceMetrics; private final Object lock = new Object(); public MirroringMetrics() { this.serviceMetrics = new ConcurrentHashMap<>(); } public void recordMirroringAttempt(String serviceName, boolean success) { synchronized (lock) { ServiceMetrics metrics = serviceMetrics.computeIfAbsent( serviceName, k -> new ServiceMetrics()); metrics.totalRequests++; if (success) { metrics.successfulMirrors++; } else { metrics.failedMirrors++; } } } public void recordMirrorResponse(String serviceName, int statusCode) { synchronized (lock) { ServiceMetrics metrics = serviceMetrics.computeIfAbsent( serviceName, k -> new ServiceMetrics()); metrics.mirrorResponses.merge(statusCode, 1, Integer::sum); } } public void recordMirrorError(String serviceName, String error) { synchronized (lock) { ServiceMetrics metrics = serviceMetrics.computeIfAbsent( serviceName, k -> new ServiceMetrics()); metrics.errors.add(error); } } public Map<String, ServiceMetrics> getServiceMetrics() { return new HashMap<>(serviceMetrics); } public ServiceMetrics getMetricsForService(String serviceName) { return serviceMetrics.get(serviceName); } public static class ServiceMetrics { public long totalRequests; public long successfulMirrors; public long failedMirrors; public Map<Integer, Integer> mirrorResponses; public List<String> errors; public ServiceMetrics() { this.mirrorResponses = new HashMap<>(); this.errors = new ArrayList<>(); } public double getSuccessRate() { return totalRequests > 0 ? (double) successfulMirrors / totalRequests * 100 : 0; } public double getMirrorRate() { return totalRequests > 0 ? (double) (successfulMirrors + failedMirrors) / totalRequests * 100 : 0; } } } /** * Mirroring Status */ public static class MirrorStatus { public enum Status { NOT_CONFIGURED, ACTIVE, PAUSED, ERROR } private String sourceService; private Status status; private double mirrorPercentage; private String mirrorTarget; private String message; private Date lastUpdated; public MirrorStatus(String sourceService, Status status) { this.sourceService = sourceService; this.status = status; this.lastUpdated = new Date(); } public MirrorStatus(String sourceService, Status status, String message) { this(sourceService, status); this.message = message; } public MirrorStatus(String sourceService, Status status, double mirrorPercentage, String mirrorTarget) { this(sourceService, status); this.mirrorPercentage = mirrorPercentage; this.mirrorTarget = mirrorTarget; } // Getters public String getSourceService() { return sourceService; } public Status getStatus() { return status; } public double getMirrorPercentage() { return mirrorPercentage; } public String getMirrorTarget() { return mirrorTarget; } public String getMessage() { return message; } public Date getLastUpdated() { return lastUpdated; } } /** Spring Boot Integration */ @RestController @RequestMapping("/api/mirroring") public static class MirroringController { private final IstioMirroringManager istioManager; private final CustomMirroringProxy customProxy; private final MirroringMetrics metrics; public MirroringController(IstioMirroringManager istioManager, CustomMirroringProxy customProxy) { this.istioManager = istioManager; this.customProxy = customProxy; this.metrics = customProxy.getMetrics(); } @PostMapping("/istio") public ResponseEntity<String> configureIstioMirroring(@RequestBody MirrorConfig config) { try { istioManager.configureMirroring(config); return ResponseEntity.ok("Istio mirroring configured successfully"); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body("Failed to configure Istio mirroring: " + e.getMessage()); } } @PostMapping("/custom") public ResponseEntity<String> configureCustomMirroring(@RequestBody MirrorConfig config) { try { customProxy.registerMirroring(config); return ResponseEntity.ok("Custom mirroring configured successfully"); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body("Failed to configure custom mirroring: " + e.getMessage()); } } @DeleteMapping("/{serviceName}") public ResponseEntity<String> removeMirroring(@PathVariable String serviceName) { try { istioManager.removeMirroring(serviceName); return ResponseEntity.ok("Mirroring removed successfully"); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body("Failed to remove mirroring: " + e.getMessage()); } } @GetMapping("/status/{serviceName}") public ResponseEntity<MirrorStatus> getMirrorStatus(@PathVariable String serviceName) { try { MirrorStatus status = istioManager.getMirrorStatus(serviceName); return ResponseEntity.ok(status); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } @GetMapping("/metrics") public ResponseEntity<Map<String, MirroringMetrics.ServiceMetrics>> getMetrics() { try { Map<String, MirroringMetrics.ServiceMetrics> metrics = this.metrics.getServiceMetrics(); return ResponseEntity.ok(metrics); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } @PostMapping("/proxy/{serviceName}") public CompletableFuture<ResponseEntity<String>> proxyRequest( @PathVariable String serviceName, @RequestBody ProxyRequest request) { try { HttpRequest httpRequest = HttpRequest.newBuilder() .uri(URI.create(request.getUrl())) .method(request.getMethod(), HttpRequest.BodyPublishers.ofString(request.getBody())) .headers(convertHeaders(request.getHeaders())) .build(); return customProxy.processRequest(serviceName, httpRequest, request.getBody()) .thenApply(response -> { Map<String, String> responseHeaders = new HashMap<>(); response.headers().map().forEach((key, values) -> { if (!values.isEmpty()) { responseHeaders.put(key, values.get(0)); } }); return ResponseEntity.status(response.statusCode()) .headers(createResponseHeaders(responseHeaders)) .body(response.body()); }); } catch (Exception e) { return CompletableFuture.completedFuture( ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body("Proxy request failed: " + e.getMessage()) ); } } private String[] convertHeaders(Map<String, String> headers) { return headers.entrySet().stream() .flatMap(entry -> Stream.of(entry.getKey(), entry.getValue())) .toArray(String[]::new); } private HttpHeaders createResponseHeaders(Map<String, String> headers) { HttpHeaders httpHeaders = new HttpHeaders(); headers.forEach(httpHeaders::set); return httpHeaders; } } /** * Request/Response DTOs */ public static class ProxyRequest { private String url; private String method; private String body; private Map<String, String> headers; public ProxyRequest() { this.headers = new HashMap<>(); this.method = "GET"; } // Getters and setters public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getMethod() { return method; } public void setMethod(String method) { this.method = method; } public String getBody() { return body; } public void setBody(String body) { this.body = body; } public Map<String, String> getHeaders() { return headers; } public void setHeaders(Map<String, String> headers) { this.headers = headers; } } /** * Demo and Usage Examples */ public static void main(String[] args) { System.out.println("🪞 Traffic Mirroring Service Demo"); System.out.println("=================================\n"); // Demo 1: Istio-based Mirroring System.out.println("1. 🚀 Istio-based Traffic Mirroring"); try { IstioClient istioClient = new DefaultIstioClient(); IstioMirroringManager istioManager = new IstioMirroringManager(istioClient, "default"); MirrorConfig istioConfig = new MirrorConfig( "production-mirror", "payment-service", "payment-service-staging" ) .withPercentage(50.0) .withMatchCondition("user-agent", "mobile-app") .withHeader("X-Mirror-Source", "production") .withTimeout(3000) .withRetries(2); istioManager.configureMirroring(istioConfig); // Check status MirrorStatus status = istioManager.getMirrorStatus("payment-service"); System.out.println("📊 Mirror Status: " + status.getStatus() + ", Percentage: " + status.getMirrorPercentage() + "%"); } catch (Exception e) { System.out.println("⚠️ Istio demo skipped: " + e.getMessage()); } // Demo 2: Custom Proxy Mirroring System.out.println("\n2. ⚡ Custom Proxy Traffic Mirroring"); CustomMirroringProxy customProxy = new CustomMirroringProxy(); MirrorConfig customConfig = new MirrorConfig( "canary-test", "user-service", "http://user-service-canary:8080" ) .withPercentage(25.0) .withHeader("X-Environment", "canary") .withTimeout(5000) .preserveOriginal(true); customProxy.registerMirroring(customConfig); // Simulate some traffic System.out.println("\n3. 📨 Simulating Traffic"); simulateTraffic(customProxy, "user-service"); // Show metrics System.out.println("\n4. 📊 Mirroring Metrics"); MirroringMetrics metrics = customProxy.getMetrics(); Map<String, MirroringMetrics.ServiceMetrics> serviceMetrics = metrics.getServiceMetrics(); serviceMetrics.forEach((service, serviceMetric) -> { System.out.println("Service: " + service); System.out.println(" Total Requests: " + serviceMetric.totalRequests); System.out.println(" Mirror Rate: " + String.format("%.2f", serviceMetric.getMirrorRate()) + "%"); System.out.println(" Success Rate: " + String.format("%.2f", serviceMetric.getSuccessRate()) + "%"); }); // Cleanup customProxy.shutdown(); System.out.println("\n✅ Traffic Mirroring Demo Completed"); } private static void simulateTraffic(CustomMirroringProxy proxy, String serviceName) { ExecutorService executor = Executors.newFixedThreadPool(10); List<CompletableFuture<Void>> futures = new ArrayList<>(); for (int i = 0; i < 100; i++) { final int requestId = i; CompletableFuture<Void> future = CompletableFuture.runAsync(() -> { try { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("http://user-service/api/users/" + requestId)) .GET() .header("User-Agent", "test-client") .header("Content-Type", "application/json") .build(); proxy.processRequest(serviceName, request, "") .thenAccept(response -> { if (requestId % 20 == 0) { System.out.println(" Processed request " + requestId + " -> Status: " + response.statusCode()); } }); } catch (Exception e) { System.err.println("❌ Request " + requestId + " failed: " + e.getMessage()); } }, executor); futures.add(future); } // Wait for all requests to complete CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); executor.shutdown(); } } Maven Dependencies
<!-- pom.xml --> <dependencies> <!-- Istio Client --> <dependency> <groupId>io.fabric8</groupId> <artifactId>istio-client</artifactId> <version>6.8.0</version> </dependency> <!-- Kubernetes Client --> <dependency> <groupId>io.fabric8</groupId> <artifactId>kubernetes-client</artifactId> <version>6.8.0</version> </dependency> <!-- Spring Boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.7.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> <version>2.7.0</version> </dependency> <!-- HTTP Client --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.3.20</version> </dependency> </dependencies>
Istio VirtualService YAML Example
apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: payment-service-mirror namespace: default spec: hosts: - payment-service http: - match: - headers: user-agent: exact: mobile-app route: - destination: host: payment-service subset: v1 weight: 50 - destination: host: payment-service-staging subset: v1 weight: 50 mirror: host: payment-service-staging subset: v1 mirror_percentage: value: 50.0 timeout: 3s retries: attempts: 2 perTryTimeout: 2s retryOn: 5xx,gateway-error,connect-failure
Application Configuration
# application.yml traffic-mirroring: enabled: true istio: enabled: ${ISTIO_ENABLED:true} namespace: ${NAMESPACE:default} custom-proxy: enabled: ${CUSTOM_PROXY_ENABLED:true} timeout-ms: 5000 max-retries: 3 logging: level: com.example.mirroring: DEBUG management: endpoints: web: exposure: include: health,info,metrics endpoint: metrics: enabled: true health: enabled: true This Traffic Mirroring implementation provides:
- Istio Integration for native service mesh mirroring
- Custom Proxy for environments without service mesh
- Flexible Configuration with percentage-based mirroring
- Request Matching based on headers and conditions
- Comprehensive Metrics for monitoring mirroring effectiveness
- Error Handling with retry mechanisms
- Spring Boot Integration for easy deployment
- Real-time Monitoring with detailed metrics collection
The solution enables safe testing of new service versions, performance testing, and debugging in production-like environments without impacting real users.