Técnico30 min

Microservicios en Kubernetes con Service Mesh

Guía completa para implementar microservicios en Kubernetes usando Istio. Desde la arquitectura hasta la implementación en producción.

DF

Diego Fernández

DevOps Engineer

Microservicios: De la Teoría a la Práctica con Kubernetes e Istio

Después de migrar monolitos a microservicios en múltiples empresas, estas son las lecciones más valiosas que hemos aprendido.

¿Por Qué Microservicios?

VentajasDesventajas
✅ Escalabilidad independiente❌ Complejidad operacional
✅ Despliegues independientes❌ Latencia de red
✅ Tecnologías heterogéneas❌ Debugging distribuido
✅ Equipos autónomos❌ Consistencia de datos

Arquitectura de Referencia

1# namespace.yaml - Organización por dominios 2apiVersion: v1 3kind: Namespace 4metadata: 5 name: ecommerce 6 labels: 7 istio-injection: enabled 8--- 9apiVersion: v1 10kind: Namespace 11metadata: 12 name: payments 13 labels: 14 istio-injection: enabled 15--- 16apiVersion: v1 17kind: Namespace 18metadata: 19 name: inventory 20 labels: 21 istio-injection: enabled

Microservicio Base con Spring Boot

1// OrderService.java 2@RestController 3@RequestMapping("/api/v1/orders") 4@Slf4j 5public class OrderController { 6 7 private final OrderService orderService; 8 private final PaymentClient paymentClient; 9 private final InventoryClient inventoryClient; 10 private final MeterRegistry meterRegistry; 11 12 @PostMapping 13 @CircuitBreaker(name = "order-creation", fallbackMethod = "createOrderFallback") 14 @Retry(name = "order-creation") 15 @Timed(value = "order.creation.time", description = "Time taken to create order") 16 public ResponseEntity<OrderResponse> createOrder(@Valid @RequestBody OrderRequest request) { 17 18 // Distributed tracing 19 Span span = tracer.currentSpan(); 20 span.tag("order.items.count", String.valueOf(request.getItems().size())); 21 22 try { 23 // Verificar inventario 24 CompletableFuture<InventoryResponse> inventoryFuture = 25 CompletableFuture.supplyAsync(() -> 26 inventoryClient.checkAvailability(request.getItems()) 27 ); 28 29 // Validar pago 30 CompletableFuture<PaymentValidation> paymentFuture = 31 CompletableFuture.supplyAsync(() -> 32 paymentClient.validatePaymentMethod(request.getPaymentInfo()) 33 ); 34 35 // Esperar respuestas 36 CompletableFuture.allOf(inventoryFuture, paymentFuture).join(); 37 38 if (!inventoryFuture.get().isAvailable()) { 39 throw new InsufficientInventoryException(); 40 } 41 42 // Crear orden 43 Order order = orderService.createOrder(request); 44 45 // Publicar evento 46 eventPublisher.publishEvent(new OrderCreatedEvent(order)); 47 48 // Métricas 49 meterRegistry.counter("orders.created", "status", "success").increment(); 50 51 return ResponseEntity.ok(OrderResponse.from(order)); 52 53 } catch (Exception e) { 54 meterRegistry.counter("orders.created", "status", "failure").increment(); 55 span.error(e); 56 throw new OrderCreationException("Failed to create order", e); 57 } 58 } 59 60 // Fallback method 61 public ResponseEntity<OrderResponse> createOrderFallback(OrderRequest request, Exception ex) { 62 log.error("Order creation failed, executing fallback", ex); 63 return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE) 64 .body(OrderResponse.fallback()); 65 } 66}

Configuración de Istio Service Mesh

1# istio-gateway.yaml 2apiVersion: networking.istio.io/v1beta1 3kind: Gateway 4metadata: 5 name: ecommerce-gateway 6spec: 7 selector: 8 istio: ingressgateway 9 servers: 10 - port: 11 number: 80 12 name: http 13 protocol: HTTP 14 hosts: 15 - "api.infraux.com" 16 - port: 17 number: 443 18 name: https 19 protocol: HTTPS 20 tls: 21 mode: SIMPLE 22 credentialName: api-tls-cert 23 hosts: 24 - "api.infraux.com" 25--- 26# Virtual Service 27apiVersion: networking.istio.io/v1beta1 28kind: VirtualService 29metadata: 30 name: ecommerce-routes 31spec: 32 hosts: 33 - "api.infraux.com" 34 gateways: 35 - ecommerce-gateway 36 http: 37 - match: 38 - uri: 39 prefix: "/api/v1/orders" 40 route: 41 - destination: 42 host: order-service.ecommerce.svc.cluster.local 43 port: 44 number: 8080 45 weight: 100 46 - match: 47 - uri: 48 prefix: "/api/v1/payments" 49 route: 50 - destination: 51 host: payment-service.payments.svc.cluster.local 52 port: 53 number: 8080

Traffic Management Avanzado

1. Canary Deployments

1# canary-deployment.yaml 2apiVersion: networking.istio.io/v1beta1 3kind: VirtualService 4metadata: 5 name: order-service-canary 6spec: 7 hosts: 8 - order-service 9 http: 10 - match: 11 - headers: 12 canary: 13 exact: "true" 14 route: 15 - destination: 16 host: order-service 17 subset: v2 18 - route: 19 - destination: 20 host: order-service 21 subset: v1 22 weight: 90 23 - destination: 24 host: order-service 25 subset: v2 26 weight: 10 27--- 28apiVersion: networking.istio.io/v1beta1 29kind: DestinationRule 30metadata: 31 name: order-service-destination 32spec: 33 host: order-service 34 subsets: 35 - name: v1 36 labels: 37 version: v1 38 - name: v2 39 labels: 40 version: v2 41 trafficPolicy: 42 connectionPool: 43 tcp: 44 maxConnections: 10 45 http: 46 http1MaxPendingRequests: 5 47 http2MaxRequests: 10

2. Circuit Breaker y Retry

1# resilience-config.yaml 2apiVersion: networking.istio.io/v1beta1 3kind: DestinationRule 4metadata: 5 name: payment-service-circuit-breaker 6spec: 7 host: payment-service 8 trafficPolicy: 9 connectionPool: 10 tcp: 11 maxConnections: 100 12 http: 13 http1MaxPendingRequests: 50 14 http2MaxRequests: 100 15 maxRequestsPerConnection: 2 16 outlierDetection: 17 consecutiveErrors: 5 18 interval: 30s 19 baseEjectionTime: 30s 20 maxEjectionPercent: 50 21 minHealthPercent: 30 22 splitExternalLocalOriginErrors: true 23--- 24apiVersion: networking.istio.io/v1beta1 25kind: VirtualService 26metadata: 27 name: payment-service-retry 28spec: 29 hosts: 30 - payment-service 31 http: 32 - retries: 33 attempts: 3 34 perTryTimeout: 5s 35 retryOn: 5xx,reset,connect-failure,refused-stream 36 timeout: 15s 37 route: 38 - destination: 39 host: payment-service

Seguridad con mTLS

1# mtls-policy.yaml 2apiVersion: security.istio.io/v1beta1 3kind: PeerAuthentication 4metadata: 5 name: default 6 namespace: istio-system 7spec: 8 mtls: 9 mode: STRICT 10--- 11# Authorization Policy 12apiVersion: security.istio.io/v1beta1 13kind: AuthorizationPolicy 14metadata: 15 name: order-service-authz 16 namespace: ecommerce 17spec: 18 selector: 19 matchLabels: 20 app: order-service 21 action: ALLOW 22 rules: 23 - from: 24 - source: 25 principals: ["cluster.local/ns/frontend/sa/webapp"] 26 to: 27 - operation: 28 methods: ["GET", "POST"] 29 paths: ["/api/v1/orders/*"] 30 - from: 31 - source: 32 namespaces: ["payments"] 33 to: 34 - operation: 35 methods: ["GET"] 36 paths: ["/api/v1/orders/*/status"]

Observabilidad Distribuida

1. Distributed Tracing

1// tracing-middleware.js 2const opentelemetry = require('@opentelemetry/api'); 3const { W3CTraceContextPropagator } = require('@opentelemetry/core'); 4 5class TracingMiddleware { 6 constructor() { 7 this.tracer = opentelemetry.trace.getTracer('order-service', '1.0.0'); 8 this.propagator = new W3CTraceContextPropagator(); 9 } 10 11 middleware() { 12 return async (req, res, next) => { 13 // Extraer contexto del header 14 const parentContext = this.propagator.extract( 15 opentelemetry.context.active(), 16 req.headers 17 ); 18 19 // Crear nuevo span 20 const span = this.tracer.startSpan( 21 `${req.method} ${req.path}`, 22 { 23 kind: opentelemetry.SpanKind.SERVER, 24 attributes: { 25 'http.method': req.method, 26 'http.url': req.url, 27 'http.target': req.path, 28 'http.host': req.hostname, 29 'http.scheme': req.protocol, 30 'http.user_agent': req.get('user-agent'), 31 } 32 }, 33 parentContext 34 ); 35 36 // Propagar contexto 37 req.span = span; 38 req.traceId = span.spanContext().traceId; 39 40 // Agregar trace ID a logs 41 req.log = (level, message, meta = {}) => { 42 logger[level](message, { 43 ...meta, 44 traceId: req.traceId, 45 spanId: span.spanContext().spanId, 46 service: 'order-service' 47 }); 48 }; 49 50 // Continuar 51 res.on('finish', () => { 52 span.setAttributes({ 53 'http.status_code': res.statusCode, 54 'http.response.size': res.get('content-length') || 0 55 }); 56 57 if (res.statusCode >= 400) { 58 span.setStatus({ 59 code: opentelemetry.SpanStatusCode.ERROR, 60 message: `HTTP ${res.statusCode}` 61 }); 62 } 63 64 span.end(); 65 }); 66 67 next(); 68 }; 69 } 70}

2. Métricas Personalizadas

1// metrics.go 2package main 3 4import ( 5 "github.com/prometheus/client_golang/prometheus" 6 "github.com/prometheus/client_golang/prometheus/promauto" 7) 8 9var ( 10 ordersProcessed = promauto.NewCounterVec( 11 prometheus.CounterOpts{ 12 Name: "orders_processed_total", 13 Help: "Total number of processed orders", 14 }, 15 []string{"status", "payment_method"}, 16 ) 17 18 orderProcessingDuration = promauto.NewHistogramVec( 19 prometheus.HistogramOpts{ 20 Name: "order_processing_duration_seconds", 21 Help: "Time taken to process orders", 22 Buckets: prometheus.ExponentialBuckets(0.001, 2, 10), 23 }, 24 []string{"service"}, 25 ) 26 27 inventoryLevel = promauto.NewGaugeVec( 28 prometheus.GaugeOpts{ 29 Name: "inventory_level", 30 Help: "Current inventory levels", 31 }, 32 []string{"product_id", "warehouse"}, 33 ) 34) 35 36func processOrder(order Order) error { 37 timer := prometheus.NewTimer(orderProcessingDuration.WithLabelValues("order-service")) 38 defer timer.ObserveDuration() 39 40 // Procesar orden 41 err := orderService.Process(order) 42 43 if err != nil { 44 ordersProcessed.WithLabelValues("failed", order.PaymentMethod).Inc() 45 return err 46 } 47 48 ordersProcessed.WithLabelValues("success", order.PaymentMethod).Inc() 49 return nil 50}

Service Mesh Patterns

1. Sidecar Pattern

1# deployment-with-sidecar.yaml 2apiVersion: apps/v1 3kind: Deployment 4metadata: 5 name: order-service 6spec: 7 template: 8 metadata: 9 annotations: 10 sidecar.istio.io/inject: "true" 11 sidecar.istio.io/proxyCPU: "100m" 12 sidecar.istio.io/proxyMemory: "128Mi" 13 spec: 14 containers: 15 - name: order-service 16 image: infraux/order-service:v2.1.0 17 ports: 18 - containerPort: 8080 19 env: 20 - name: JAEGER_AGENT_HOST 21 value: "localhost" 22 - name: JAEGER_AGENT_PORT 23 value: "6831" 24 resources: 25 requests: 26 memory: "256Mi" 27 cpu: "250m" 28 limits: 29 memory: "512Mi" 30 cpu: "500m" 31 livenessProbe: 32 httpGet: 33 path: /health/live 34 port: 8080 35 initialDelaySeconds: 30 36 periodSeconds: 10 37 readinessProbe: 38 httpGet: 39 path: /health/ready 40 port: 8080 41 initialDelaySeconds: 5 42 periodSeconds: 5

Testing de Microservicios

1// integration-test.js 2const { ServiceMesh } = require('@infraux/testing'); 3 4describe('Order Service Integration', () => { 5 let mesh; 6 7 beforeAll(async () => { 8 mesh = new ServiceMesh({ 9 services: [ 10 { name: 'order-service', port: 8080 }, 11 { name: 'payment-service', port: 8081 }, 12 { name: 'inventory-service', port: 8082 } 13 ] 14 }); 15 16 await mesh.start(); 17 }); 18 19 afterAll(async () => { 20 await mesh.stop(); 21 }); 22 23 test('should create order with payment and inventory check', async () => { 24 // Mock responses 25 mesh.mock('payment-service', '/validate', { 26 status: 200, 27 body: { valid: true, transactionId: '12345' } 28 }); 29 30 mesh.mock('inventory-service', '/check', { 31 status: 200, 32 body: { available: true, items: [{ id: 'SKU123', quantity: 10 }] } 33 }); 34 35 // Test order creation 36 const response = await mesh.request('order-service', { 37 method: 'POST', 38 path: '/api/v1/orders', 39 body: { 40 items: [{ productId: 'SKU123', quantity: 2 }], 41 paymentInfo: { method: 'credit_card', token: 'tok_123' } 42 } 43 }); 44 45 expect(response.status).toBe(201); 46 expect(response.body.orderId).toBeDefined(); 47 48 // Verify interactions 49 expect(mesh.interactions('payment-service')).toHaveLength(1); 50 expect(mesh.interactions('inventory-service')).toHaveLength(1); 51 }); 52});

Mejores Prácticas

<div class="info-box"> 💡 **Domain-Driven Design**: Organiza microservicios alrededor de dominios de negocio, no capas técnicas. </div> <div class="warning-box"> ⚠️ **Evita Distributed Monolith**: Si todos tus servicios se despliegan juntos, no tienes microservicios. </div> <div class="success-box"> ✅ **API First**: Define contratos API antes de implementar. Usa OpenAPI/AsyncAPI. </div>

Métricas de Éxito

MétricaAntes (Monolito)Después (Microservicios)
Deploy Frequency1/mes50+/día
Lead Time3 semanas2 horas
MTTR4 horas15 minutos
Change Failure Rate15%2%

Conclusión

Los microservicios no son una bala de plata. Son una herramienta poderosa cuando se usan correctamente, pero requieren madurez organizacional y técnica. Con Kubernetes e Istio, puedes construir arquitecturas resilientes y escalables que realmente entreguen valor.

En InfraUX, simplificamos esta complejidad para que puedas enfocarte en tu lógica de negocio, no en la infraestructura.

#microservicios#kubernetes#istio#service-mesh#arquitectura