Article
AWS App Mesh is a service mesh that provides application-level networking to make it easy for microservices to communicate with each other across multiple types of compute infrastructure. For Java applications, App Mesh offers powerful traffic management, observability, and security features without requiring code changes.
What is AWS App Mesh?
AWS App Mesh is a service mesh that uses the Envoy proxy sidecar to handle all inter-service communication. It provides:
- Traffic Management: Canary deployments, circuit breakers, retries
- Observability: Metrics, logs, and traces for all service communication
- Security: TLS encryption, service-to-service authentication
- Resilience: Timeouts, retries, and fault injection
Key Benefits for Java Applications:
- No Code Changes: Most features work without modifying Java application code
- Traffic Control: Advanced routing and canary deployments
- Observability: Unified monitoring across all services
- Security: Automatic mTLS between services
- Multi-Platform: Works with ECS, EKS, EC2, and on-premises
Architecture Overview
Java Microservices with App Mesh: ┌─────────────────────────────────────────────────────────────┐ │ AWS App Mesh Control Plane │ └─────────────────────────────────────────────────────────────┘ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ Order Service │ │ User Service │ │ Payment Service │ │ (Spring Boot) │ │ (Quarkus) │ │ (Micronaut) │ ├─────────────────┤ ├─────────────────┤ ├─────────────────┤ │ Envoy Proxy │ │ Envoy Proxy │ │ Envoy Proxy │ │ (Sidecar) │ │ (Sidecar) │ │ (Sidecar) │ └─────────────────┘ └─────────────────┘ └─────────────────┘
Setup and Prerequisites
1. Install AWS CLI and Requirements
# Install AWS CLI curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" unzip awscliv2.zip sudo ./aws/install # Configure AWS CLI aws configure # Install jq for JSON processing sudo apt-get install jq # Install kubectl (for EKS) curl -O https://s3.us-west-2.amazonaws.com/amazon-eks/1.28.3/2023-11-14/bin/linux/amd64/kubectl chmod +x kubectl && sudo mv kubectl /usr/local/bin/
2. Create App Mesh Resources
# Create App Mesh aws appmesh create-mesh --mesh-name java-app-mesh # Verify mesh creation aws appmesh describe-mesh --mesh-name java-app-mesh
Java Application Setup
1. Spring Boot Order Service
// OrderServiceApplication.java
package com.example.orderservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@SpringBootApplication
@RestController
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
@GetMapping("/health")
public Map<String, String> health() {
return Map.of("status", "healthy", "service", "order-service");
}
@GetMapping("/orders")
public List<Order> getOrders() {
return Arrays.asList(
new Order("1", "pending", 99.99),
new Order("2", "completed", 149.99)
);
}
@GetMapping("/orders/{id}")
public Order getOrder(@PathVariable String id) {
return new Order(id, "processing", 199.99);
}
@PostMapping("/orders")
public Order createOrder(@RequestBody OrderRequest request) {
return new Order(
UUID.randomUUID().toString(),
"created",
request.amount()
);
}
// Internal endpoint for service-to-service communication
@GetMapping("/internal/orders/{userId}/recent")
public List<Order> getUserRecentOrders(@PathVariable String userId) {
// This would be called by other services via App Mesh
return Arrays.asList(
new Order("1", "completed", 99.99),
new Order("2", "completed", 149.99)
);
}
public record Order(String id, String status, double amount) {}
public record OrderRequest(double amount, String userId) {}
}
application.yml:
# src/main/resources/application.yml
spring:
application:
name: order-service
profiles:
active: ${PROFILE:default}
server:
port: 8080
servlet:
context-path: /
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
metrics:
enabled: true
logging:
level:
com.example: DEBUG
org.springframework.web: INFO
app:
version: 1.0.0
mesh:
enabled: true
2. User Service (Quarkus)
// UserResource.java
package com.example.userservice;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import java.util.List;
@Path("/api")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class UserResource {
@RestClient
OrderServiceClient orderServiceClient;
@GET
@Path("/health")
public HealthResponse health() {
return new HealthResponse("healthy", "user-service");
}
@GET
@Path("/users")
public List<User> getUsers() {
return List.of(
new User("1", "John Doe", "[email protected]", "active"),
new User("2", "Jane Smith", "[email protected]", "active")
);
}
@GET
@Path("/users/{id}")
public User getUser(@PathParam("id") String id) {
return new User(id, "Test User", "[email protected]", "active");
}
@GET
@Path("/users/{id}/orders")
public UserOrders getUserOrders(@PathParam("id") String userId) {
// Call order service through App Mesh
List<Order> orders = orderServiceClient.getUserRecentOrders(userId);
return new UserOrders(userId, orders);
}
@POST
@Path("/users")
public User createUser(User user) {
return new User(
java.util.UUID.randomUUID().toString(),
user.name(),
user.email(),
"active"
);
}
public record HealthResponse(String status, String service) {}
public record User(String id, String name, String email, String status) {}
public record UserOrders(String userId, List<Order> orders) {}
public record Order(String id, String status, double amount) {}
}
Order Service Client:
// OrderServiceClient.java
package com.example.userservice;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import java.util.List;
@RegisterRestClient(baseUri = "http://order-service.java-apps:8080")
public interface OrderServiceClient {
@GET
@Path("/internal/orders/{userId}/recent")
List<Order> getUserRecentOrders(@PathParam("userId") String userId);
}
application.properties:
# src/main/resources/application.properties quarkus.application.name=user-service quarkus.application.version=1.0.0 quarkus.log.level=INFO quarkus.log.category."com.example".level=DEBUG quarkus.http.port=8080 quarkus.health.extensions.enabled=true %prod.quarkus.log.level=INFO
App Mesh Configuration
1. Virtual Nodes
# app-mesh/virtual-nodes/order-service-vn.json
{
"meshName": "java-app-mesh",
"spec": {
"listeners": [
{
"portMapping": {
"port": 8080,
"protocol": "http"
},
"healthCheck": {
"protocol": "http",
"path": "/health",
"healthyThreshold": 2,
"unhealthyThreshold": 2,
"timeoutMillis": 2000,
"intervalMillis": 5000
}
}
],
"serviceDiscovery": {
"awsCloudMap": {
"namespaceName": "java-apps.local",
"serviceName": "order-service",
"attributes": [
{
"key": "version",
"value": "1.0.0"
}
]
}
},
"logging": {
"accessLog": {
"file": {
"path": "/dev/stdout"
}
}
}
},
"virtualNodeName": "order-service-vn"
}
# app-mesh/virtual-nodes/user-service-vn.json
{
"meshName": "java-app-mesh",
"spec": {
"listeners": [
{
"portMapping": {
"port": 8080,
"protocol": "http"
},
"healthCheck": {
"protocol": "http",
"path": "/api/health",
"healthyThreshold": 2,
"unhealthyThreshold": 2,
"timeoutMillis": 2000,
"intervalMillis": 5000
}
}
],
"serviceDiscovery": {
"awsCloudMap": {
"namespaceName": "java-apps.local",
"serviceName": "user-service",
"attributes": [
{
"key": "version",
"value": "1.0.0"
}
]
}
},
"backends": [
{
"virtualService": {
"virtualServiceName": "order-service.java-apps.local"
}
}
],
"logging": {
"accessLog": {
"file": {
"path": "/dev/stdout"
}
}
}
},
"virtualNodeName": "user-service-vn"
}
2. Virtual Services
# app-mesh/virtual-services/order-service-vs.json
{
"meshName": "java-app-mesh",
"spec": {
"provider": {
"virtualNode": {
"virtualNodeName": "order-service-vn"
}
}
},
"virtualServiceName": "order-service.java-apps.local"
}
# app-mesh/virtual-services/user-service-vs.json
{
"meshName": "java-app-mesh",
"spec": {
"provider": {
"virtualNode": {
"virtualNodeName": "user-service-vn"
}
}
},
"virtualServiceName": "user-service.java-apps.local"
}
3. Create App Mesh Resources Script
#!/bin/bash # setup-app-mesh.sh set -e MESH_NAME="java-app-mesh" NAMESPACE="java-apps.local" echo "Setting up AWS App Mesh for Java applications..." # Create CloudMap namespace aws servicediscovery create-private-dns-namespace \ --name $NAMESPACE \ --vpc vpc-0a1b2c3d4e5f67890 # Create virtual nodes echo "Creating virtual nodes..." aws appmesh create-virtual-node \ --mesh-name $MESH_NAME \ --virtual-node-name order-service-vn \ --cli-input-json file://app-mesh/virtual-nodes/order-service-vn.json aws appmesh create-virtual-node \ --mesh-name $MESH_NAME \ --virtual-node-name user-service-vn \ --cli-input-json file://app-mesh/virtual-nodes/user-service-vn.json # Create virtual services echo "Creating virtual services..." aws appmesh create-virtual-service \ --mesh-name $MESH_NAME \ --virtual-service-name order-service.$NAMESPACE \ --cli-input-json file://app-mesh/virtual-services/order-service-vs.json aws appmesh create-virtual-service \ --mesh-name $MESH_NAME \ --virtual-service-name user-service.$NAMESPACE \ --cli-input-json file://app-mesh/virtual-services/user-service-vs.json echo "App Mesh setup completed!"
Kubernetes Deployment (EKS)
1. Namespace and Service Accounts
# k8s/namespace.yaml apiVersion: v1 kind: Namespace metadata: name: java-apps labels: name: java-apps appmesh.k8s.aws/sidecarInjectorWebhook: enabled --- apiVersion: v1 kind: ServiceAccount metadata: name: order-service-account namespace: java-apps labels: app: order-service --- apiVersion: v1 kind: ServiceAccount metadata: name: user-service-account namespace: java-apps labels: app: user-service
2. Order Service Deployment
# k8s/order-service.yaml apiVersion: apps/v1 kind: Deployment metadata: name: order-service namespace: java-apps 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: # App Mesh annotations appmesh.k8s.aws/virtualNode: order-service-vn appmesh.k8s.aws/ports: "8080" spec: serviceAccountName: order-service-account containers: - name: order-service image: 123456789012.dkr.ecr.us-west-2.amazonaws.com/order-service:1.0.0 ports: - containerPort: 8080 name: http env: - name: SPRING_PROFILES_ACTIVE value: "kubernetes" - name: JAVA_OPTS value: "-Xmx512m -Xms256m -Djava.security.egd=file:/dev/./urandom" - name: APPMESH_ENABLED value: "true" resources: requests: memory: "512Mi" cpu: "250m" limits: memory: "1Gi" cpu: "500m" livenessProbe: httpGet: path: /health port: 8080 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /health port: 8080 initialDelaySeconds: 5 periodSeconds: 5 --- apiVersion: v1 kind: Service metadata: name: order-service namespace: java-apps annotations: # CloudMap service registration cloudmap.amazonaws.com/namespace: java-apps.local cloudmap.amazonaws.com/service: order-service cloudmap.amazonaws.com/instanceAttributes: version=v1 spec: ports: - port: 80 targetPort: 8080 protocol: TCP name: http selector: app: order-service type: ClusterIP
3. User Service Deployment
# k8s/user-service.yaml apiVersion: apps/v1 kind: Deployment metadata: name: user-service namespace: java-apps labels: app: user-service version: v1 spec: replicas: 2 selector: matchLabels: app: user-service version: v1 template: metadata: labels: app: user-service version: v1 annotations: # App Mesh annotations appmesh.k8s.aws/virtualNode: user-service-vn appmesh.k8s.aws/ports: "8080" spec: serviceAccountName: user-service-account containers: - name: user-service image: 123456789012.dkr.ecr.us-west-2.amazonaws.com/user-service:1.0.0 ports: - containerPort: 8080 name: http env: - name: QUARKUS_PROFILE value: "kubernetes" - name: JAVA_OPTS value: "-Xmx256m -Xms128m" - name: APPMESH_ENABLED value: "true" resources: requests: memory: "256Mi" cpu: "100m" limits: memory: "512Mi" cpu: "250m" livenessProbe: httpGet: path: /api/health port: 8080 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /api/health port: 8080 initialDelaySeconds: 5 periodSeconds: 5 --- apiVersion: v1 kind: Service metadata: name: user-service namespace: java-apps annotations: # CloudMap service registration cloudmap.amazonaws.com/namespace: java-apps.local cloudmap.amazonaws.com/service: user-service cloudmap.amazonaws.com/instanceAttributes: version=v1 spec: ports: - port: 80 targetPort: 8080 protocol: TCP name: http selector: app: user-service type: ClusterIP
Advanced Traffic Management
1. Virtual Router for Canary Deployments
# app-mesh/virtual-routers/order-service-router.json
{
"meshName": "java-app-mesh",
"spec": {
"listeners": [
{
"portMapping": {
"port": 8080,
"protocol": "http"
}
}
]
},
"virtualRouterName": "order-service-router"
}
2. Route Configuration
# app-mesh/routes/order-service-route.json
{
"meshName": "java-app-mesh",
"spec": {
"httpRoute": {
"action": {
"weightedTargets": [
{
"virtualNode": "order-service-v1",
"weight": 90
},
{
"virtualNode": "order-service-v2",
"weight": 10
}
]
},
"match": {
"prefix": "/"
},
"retryPolicy": {
"httpRetryEvents": ["gateway-error", "client-error"],
"tcpRetryEvents": ["connection-error"],
"maxRetries": 2,
"perRetryTimeout": {
"unit": "ms",
"value": 2000
}
},
"timeout": {
"idle": {
"unit": "ms",
"value": 5000
},
"perRequest": {
"unit": "ms",
"value": 2000
}
}
}
},
"virtualRouterName": "order-service-router",
"routeName": "order-service-route"
}
3. Update Virtual Service to Use Router
# app-mesh/virtual-services/order-service-vs-v2.json
{
"meshName": "java-app-mesh",
"spec": {
"provider": {
"virtualRouter": {
"virtualRouterName": "order-service-router"
}
}
},
"virtualServiceName": "order-service.java-apps.local"
}
Java Client Configuration
1. Resilient HTTP Client with App Mesh
// AppMeshHttpClient.java
package com.example.config;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.retry.backoff.FixedBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.web.client.RestTemplate;
import java.time.Duration;
@Configuration
@EnableRetry
public class AppMeshHttpClient {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.setConnectTimeout(Duration.ofSeconds(2))
.setReadTimeout(Duration.ofSeconds(5))
.additionalInterceptors(new AppMeshHeaderInterceptor())
.build();
}
@Bean
public RetryTemplate retryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
backOffPolicy.setBackOffPeriod(1000L); // 1 second between retries
retryTemplate.setBackOffPolicy(backOffPolicy);
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(3);
retryTemplate.setRetryPolicy(retryPolicy);
return retryTemplate;
}
}
// AppMeshHeaderInterceptor.java
package com.example.config;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import java.io.IOException;
public class AppMeshHeaderInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
// Add headers for App Mesh observability
request.getHeaders().add("x-envoy-decorator-operation",
request.getMethod().name() + " " + request.getURI().getPath());
request.getHeaders().add("x-envoy-max-retries", "2");
request.getHeaders().add("x-envoy-upstream-rq-timeout-ms", "5000");
return execution.execute(request, body);
}
}
2. Service Client with Circuit Breaker
// OrderServiceClient.java
package com.example.clients;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.retry.annotation.Retry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.util.Collections;
import java.util.List;
@Component
public class OrderServiceClient {
private static final String ORDER_SERVICE = "orderService";
@Autowired
private RestTemplate restTemplate;
@Autowired
private RetryTemplate retryTemplate;
@CircuitBreaker(name = ORDER_SERVICE, fallbackMethod = "getUserOrdersFallback")
@Retry(name = ORDER_SERVICE)
public List<Object> getUserOrders(String userId) {
String url = "http://order-service.java-apps.local/internal/orders/{userId}/recent";
return retryTemplate.execute(context -> {
return restTemplate.getForObject(url, List.class, userId);
});
}
// Fallback method
public List<Object> getUserOrdersFallback(String userId, Exception e) {
// Return empty list or cached data
return Collections.emptyList();
}
}
Observability and Monitoring
1. Custom Metrics for Java Services
// AppMeshMetrics.java
package com.example.metrics;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class AppMeshMetrics {
private final Counter serviceRequests;
private final Counter serviceErrors;
private final Timer serviceLatency;
public AppMeshMetrics(MeterRegistry registry) {
this.serviceRequests = Counter.builder("appmesh.service.requests")
.description("Number of service requests through App Mesh")
.tag("service", "order-service")
.register(registry);
this.serviceErrors = Counter.builder("appmesh.service.errors")
.description("Number of service errors through App Mesh")
.tag("service", "order-service")
.register(registry);
this.serviceLatency = Timer.builder("appmesh.service.latency")
.description("Service latency through App Mesh")
.tag("service", "order-service")
.register(registry);
}
public void recordRequest() {
serviceRequests.increment();
}
public void recordError() {
serviceErrors.increment();
}
public void recordLatency(long duration, TimeUnit unit) {
serviceLatency.record(duration, unit);
}
}
2. Distributed Tracing
// TracingConfiguration.java
package com.example.config;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Tracer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class TracingConfiguration {
@Bean
public Tracer tracer(OpenTelemetry openTelemetry) {
return openTelemetry.getTracer("order-service");
}
}
// TracedService.java
package com.example.service;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class TracedService {
@Autowired
private Tracer tracer;
public void processOrder(String orderId) {
Span span = tracer.spanBuilder("processOrder")
.setAttribute("order.id", orderId)
.startSpan();
try (Scope scope = span.makeCurrent()) {
// Business logic here
span.addEvent("Order processing started");
// Add more tracing
Span dependencySpan = tracer.spanBuilder("callUserService")
.startSpan();
try (Scope dependencyScope = dependencySpan.makeCurrent()) {
// Call user service through App Mesh
callUserService(orderId);
} finally {
dependencySpan.end();
}
span.addEvent("Order processing completed");
} finally {
span.end();
}
}
private void callUserService(String orderId) {
// Implementation to call user service
}
}
Deployment Scripts
1. Complete Deployment Script
#!/bin/bash # deploy-java-app-mesh.sh set -e REGION="us-west-2" CLUSTER_NAME="java-apps-cluster" MESH_NAME="java-app-mesh" echo "Deploying Java applications with AWS App Mesh..." # Build and push Docker images echo "Building Docker images..." docker build -t order-service:1.0.0 -f Dockerfile.order . docker build -t user-service:1.0.0 -f Dockerfile.user . # Tag and push to ECR aws ecr get-login-password --region $REGION | docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-west-2.amazonaws.com docker tag order-service:1.0.0 123456789012.dkr.ecr.us-west-2.amazonaws.com/order-service:1.0.0 docker push 123456789012.dkr.ecr.us-west-2.amazonaws.com/order-service:1.0.0 docker tag user-service:1.0.0 123456789012.dkr.ecr.us-west-2.amazonaws.com/user-service:1.0.0 docker push 123456789012.dkr.ecr.us-west-2.amazonaws.com/user-service:1.0.0 # Update kubeconfig aws eks update-kubeconfig --region $REGION --name $CLUSTER_NAME # Deploy to Kubernetes echo "Deploying to Kubernetes..." kubectl apply -f k8s/namespace.yaml kubectl apply -f k8s/order-service.yaml kubectl apply -f k8s/user-service.yaml # Wait for deployment echo "Waiting for deployments to be ready..." kubectl wait --for=condition=ready pod -l app=order-service -n java-apps --timeout=300s kubectl wait --for=condition=ready pod -l app=user-service -n java-apps --timeout=300s # Verify App Mesh configuration echo "Verifying App Mesh configuration..." kubectl get pods -n java-apps -l app=order-service kubectl get pods -n java-apps -l app=user-service # Test service communication echo "Testing service communication..." kubectl exec -n java-apps deployment/order-service -c order-service -- curl -s http://user-service/java-apps/api/health echo "Deployment completed successfully!"
Best Practices
1. Security
- Use mTLS for service-to-service communication
- Implement proper IAM roles for service accounts
- Use network policies with App Mesh
- Regularly rotate certificates
2. Performance
- Configure appropriate timeouts and retries
- Use connection pooling in Java clients
- Monitor Envoy proxy resource usage
- Implement circuit breakers in Java code
3. Observability
- Enable access logs in App Mesh
- Implement distributed tracing
- Use CloudWatch metrics and logs
- Set up alerts for service errors
Conclusion
AWS App Mesh provides Java microservices with powerful service mesh capabilities:
- Traffic Management: Advanced routing, canary deployments, circuit breaking
- Observability: Unified metrics, logs, and traces across all services
- Security: Automatic mTLS and service authentication
- Resilience: Built-in retries, timeouts, and fault injection
- No Code Changes: Most features work without modifying Java applications
By implementing AWS App Mesh with Java microservices, teams can achieve better reliability, security, and observability while maintaining development velocity and operational efficiency.