Skip to content
IRC-CodingIRC-Coding
Arquitectura de SoftwareMonolith MicroservicesAPI GatewayService MeshOrquestación de Contenedores

Monolith vs Microservices: API Gateway y Service Mesh

Comparativa de arquitectura: Monolith vs Microservices con API Gateway, Service Mesh y orquestación de contenedores. Guía práctica.

S

schutzgeist

16 min read
Monolith vs Microservices: API Gateway y Service Mesh

Arquitectura de software: Monolito vs Microservicios con API Gateway y Service Mesh

Este artículo es una introducción completa a la arquitectura de software, incluyendo monolito vs microservicios con API Gateway, Service Mesh, contenedores y orquestación con ejemplos prácticos.

En resumen

La arquitectura monolítica es un único bloque de código cohesionado, mientras que los microservicios son servicios pequeños e independientes. El API Gateway coordina la comunicación y el Service Mesh gestiona las interacciones entre microservicios.

Descripción técnica compacta

Arquitectura de software es la estructura fundamental de un sistema de software, compuesta por componentes, sus relaciones y los principios que guían su diseño y evolución.

Patrones de arquitectura:

Arquitectura monolítica

  • Concepto: Aplicación unificada en una base de código única
  • Deployment: Despliegue único de toda la aplicación
  • Comunicación: Llamadas directas a métodos dentro de la aplicación
  • Ventajas: Desarrollo simple, despliegue directo, debugging sencillo
  • Desventajas: Problemas de escalado, restricciones tecnológicas

Arquitectura de microservicios

  • Concepto: Servicios pequeños y autónomos con responsabilidades específicas
  • Deployment: Capacidad de despliegue independiente
  • Comunicación: Llamadas a API basadas en red
  • Ventajas: Escalabilidad, flexibilidad tecnológica, resiliencia
  • Desventajas: Complejidad, latencia de red, complejidad distribuida

API Gateway

  • Concepto: Punto de entrada centralizado para solicitudes de clientes
  • Funciones: Enrutamiento, autenticación, limitación de tasa, balanceo de carga
  • Ventajas: Gestión centralizada, seguridad, monitoreo
  • Implementación: Kong, NGINX, AWS API Gateway

Service Mesh

  • Concepto: Capa de infraestructura para la comunicación entre microservicios
  • Funciones: Service Discovery, balanceo de carga, circuit breaking
  • Implementación: Istio, Linkerd, Consul Connect
  • Ventajas: Transparencia, observabilidad, seguridad

Puntos clave para evaluaciones

  • Arquitectura de software: Estructura fundamental de sistemas de software
  • Monolito: Aplicación unificada con base de código compartida
  • Microservicios: Servicios pequeños e independientes con ciclo de vida propio
  • API Gateway: Punto de entrada centralizado para la comunicación de clientes
  • Service Mesh: Infraestructura para interacciones entre microservicios
  • Contenedores: Virtualización a nivel de sistema operativo
  • Orquestación: Gestión automatizada de contenedores
  • Relevante para evaluaciones: Decisiones y patrones de arquitectura modernos

Componentes principales

  1. Decisión de arquitectura: Monolito vs microservicios
  2. Gestión de API: Gateway, enrutamiento, seguridad
  3. Service Discovery: Descubrimiento automático de servicios
  4. Balanceo de carga: Distribución de solicitudes
  5. Circuit Breaking: Protección contra fallos en cascada
  6. Containerización: Docker, Podman
  7. Orquestación: Kubernetes, Docker Swarm
  8. Monitoreo: Logging, métricas, tracing

Ejemplos prácticos

1. Arquitectura monolítica con Java Spring Boot

package com.example.monolith;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.*;
import org.springframework.stereotype.Service;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.beans.factory.annotation.Autowired;
import javax.persistence.*;
import java.util.List;
import java.util.Optional;

// Aplicación principal
@SpringBootApplication
public class MonolithApplication {
    public static void main(String[] args) {
        SpringApplication.run(MonolithApplication.class, args);
    }
}

// Entidad User
@Entity
@Table(name = "users")
class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, unique = true)
    private String username;
    
    @Column(nullable = false)
    private String email;
    
    @Column(nullable = false)
    private String password;
    
    // Getters y Setters
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    
    public String getPassword() { return password; }
    public void setPassword(String password) { this.password = password; }
}

// Entidad Order
@Entity
@Table(name = "orders")
class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = "user_id", nullable = false)
    private User user;
    
    @Column(nullable = false)
    private String product;
    
    @Column(nullable = false)
    private Double amount;
    
    @Column(nullable = false)
    private String status;
    
    // Getters y Setters
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    
    public User getUser() { return user; }
    public void setUser(User user) { this.user = user; }
    
    public String getProduct() { return product; }
    public void setProduct(String product) { this.product = product; }
    
    public Double getAmount() { return amount; }
    public void setAmount(Double amount) { this.amount = amount; }
    
    public String getStatus() { return status; }
    public void setStatus(String status) { this.status = status; }
}

// Interfaces Repository
interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByUsername(String username);
    boolean existsByUsername(String username);
}

interface OrderRepository extends JpaRepository<Order, Long> {
    List<Order> findByUserId(Long userId);
    List<Order> findByStatus(String status);
}

// Servicios de lógica de negocio
@Service
class UserService {
    @Autowired
    private UserRepository userRepository;
    
    public User createUser(User user) {
        if (userRepository.existsByUsername(user.getUsername())) {
            throw new IllegalArgumentException("Username already exists");
        }
        return userRepository.save(user);
    }
    
    public Optional<User> getUserById(Long id) {
        return userRepository.findById(id);
    }
    
    public Optional<User> getUserByUsername(String username) {
        return userRepository.findByUsername(username);
    }
    
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }
    
    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }
}

@Service
class OrderService {
    @Autowired
    private OrderRepository orderRepository;
    
    @Autowired
    private UserService userService;
    
    public Order createOrder(Long userId, String product, Double amount) {
        Optional<User> userOpt = userService.getUserById(userId);
        if (!userOpt.isPresent()) {
            throw new IllegalArgumentException("User not found");
        }
        
        Order order = new Order();
        order.setUser(userOpt.get());
        order.setProduct(product);
        order.setAmount(amount);
        order.setStatus("PENDING");
        
        return orderRepository.save(order);
    }
    
    public Optional<Order> getOrderById(Long id) {
        return orderRepository.findById(id);
    }
    
    public List<Order> getOrdersByUserId(Long userId) {
        return orderRepository.findByUserId(userId);
    }
    
    public Order updateOrderStatus(Long id, String status) {
        Optional<Order> orderOpt = orderRepository.findById(id);
        if (!orderOpt.isPresent()) {
            throw new IllegalArgumentException("Order not found");
        }
        
        Order order = orderOpt.get();
        order.setStatus(status);
        return orderRepository.save(order);
    }
    
    public void deleteOrder(Long id) {
        orderRepository.deleteById(id);
    }
}

// Controladores REST
@RestController
@RequestMapping("/api/users")
class UserController {
    @Autowired
    private UserService userService;
    
    @PostMapping
    public User createUser(@RequestBody User user) {
        return userService.createUser(user);
    }
    
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.getUserById(id)
            .orElseThrow(() -> new IllegalArgumentException("User not found"));
    }
    
    @GetMapping
    public List<User> getAllUsers() {
        return userService.getAllUsers();
    }
    
    @DeleteMapping("/{id}")
    public void deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
    }
}

@RestController
@RequestMapping("/api/orders")
class OrderController {
    @Autowired
    private OrderService orderService;
    
    @PostMapping
    public Order createOrder(@RequestBody CreateOrderRequest request) {
        return orderService.createOrder(request.getUserId(), request.getProduct(), request.getAmount());
    }
    
    @GetMapping("/{id}")
    public Order getOrder(@PathVariable Long id) {
        return orderService.getOrderById(id)
            .orElseThrow(() -> new IllegalArgumentException("Order not found"));
    }
    
    @GetMapping("/user/{userId}")
    public List<Order> getOrdersByUser(@PathVariable Long userId) {
        return orderService.getOrdersByUserId(userId);
    }
    
    @PutMapping("/{id}/status")
    public Order updateOrderStatus(@PathVariable Long id, @RequestBody String status) {
        return orderService.updateOrderStatus(id, status);
    }
    
    @DeleteMapping("/{id}")
    public void deleteOrder(@PathVariable Long id) {
        orderService.deleteOrder(id);
    }
}

// DTOs
class CreateOrderRequest {
    private Long userId;
    private String product;
    private Double amount;
    
    // Getters y Setters
    public Long getUserId() { return userId; }
    public void setUserId(Long userId) { this.userId = userId; }
    
    public String getProduct() { return product; }
    public void setProduct(String product) { this.product = product; }
    
    public Double getAmount() { return amount; }
    public void setAmount(Double amount) { this.amount = amount; }
}

2. Arquitectura de Microservicios con Spring Cloud

// User Microservice
package com.example.userservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.web.bind.annotation.*;
import org.springframework.stereotype.Service;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.beans.factory.annotation.Autowired;
import javax.persistence.*;
import java.util.List;
import java.util.Optional;

@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }
}

// User Entity (simplificada)
@Entity
class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String username;
    private String email;
    
    // Getters y Setters
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
}

interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByUsername(String username);
}

@Service
class UserService {
    @Autowired
    private UserRepository userRepository;
    
    public User createUser(User user) {
        return userRepository.save(user);
    }
    
    public Optional<User> getUserById(Long id) {
        return userRepository.findById(id);
    }
    
    public Optional<User> getUserByUsername(String username) {
        return userRepository.findByUsername(username);
    }
    
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }
}

@RestController
@RequestMapping("/api/users")
class UserController {
    @Autowired
    private UserService userService;
    
    @PostMapping
    public User createUser(@RequestBody User user) {
        return userService.createUser(user);
    }
    
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.getUserById(id)
            .orElseThrow(() -> new RuntimeException("User not found"));
    }
    
    @GetMapping
    public List<User> getAllUsers() {
        return userService.getAllUsers();
    }
}

// Order Microservice
package com.example.orderservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import org.springframework.stereotype.Service;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.beans.factory.annotation.Autowired;
import javax.persistence.*;
import java.util.List;
import java.util.Optional;

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}

// Feign Client para User Service
@FeignClient(name = "user-service")
interface UserServiceClient {
    @GetMapping("/api/users/{id}")
    User getUser(@PathVariable("id") Long id);
}

// Order Entity
@Entity
class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private Long userId;
    private String product;
    private Double amount;
    private String status;
    
    // Getters y Setters
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    
    public Long getUserId() { return userId; }
    public void setUserId(Long userId) { this.userId = userId; }
    
    public String getProduct() { return product; }
    public void setProduct(String product) { this.product = product; }
    
    public Double getAmount() { return amount; }
    public void setAmount(Double amount) { this.amount = amount; }
    
    public String getStatus() { return status; }
    public void setStatus(String status) { this.status = status; }
}

interface OrderRepository extends JpaRepository<Order, Long> {
    List<Order> findByUserId(Long userId);
}

@Service
class OrderService {
    @Autowired
    private OrderRepository orderRepository;
    
    @Autowired
    private UserServiceClient userServiceClient;
    
    public Order createOrder(Order order) {
        // Validar usuario mediante llamada a microservicio
        try {
            User user = userServiceClient.getUser(order.getUserId());
            if (user == null) {
                throw new RuntimeException("User not found");
            }
        } catch (Exception e) {
            throw new RuntimeException("User service unavailable");
        }
        
        order.setStatus("PENDING");
        return orderRepository.save(order);
    }
    
    public Optional<Order> getOrderById(Long id) {
        return orderRepository.findById(id);
    }
    
    public List<Order> getOrdersByUserId(Long userId) {
        return orderRepository.findByUserId(userId);
    }
    
    public Order updateOrderStatus(Long id, String status) {
        Optional<Order> orderOpt = orderRepository.findById(id);
        if (!orderOpt.isPresent()) {
            throw new RuntimeException("Order not found");
        }
        
        Order order = orderOpt.get();
        order.setStatus(status);
        return orderRepository.save(order);
    }
}

@RestController
@RequestMapping("/api/orders")
class OrderController {
    @Autowired
    private OrderService orderService;
    
    @PostMapping
    public Order createOrder(@RequestBody Order order) {
        return orderService.createOrder(order);
    }
    
    @GetMapping("/{id}")
    public Order getOrder(@PathVariable Long id) {
        return orderService.getOrderById(id)
            .orElseThrow(() -> new RuntimeException("Order not found"));
    }
    
    @GetMapping("/user/{userId}")
    public List<Order> getOrdersByUser(@PathVariable Long userId) {
        return orderService.getOrdersByUserId(userId);
    }
    
    @PutMapping("/{id}/status")
    public Order updateOrderStatus(@PathVariable Long id, @RequestBody String status) {
        return orderService.updateOrderStatus(id, status);
    }
}

3. API Gateway con Spring Cloud Gateway

package com.example.apigateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

@SpringBootApplication
public class ApiGatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(ApiGatewayApplication.class, args);
    }
    
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
            // User Service Routes
            .route("user-service", r -> r.path("/api/users/**")
                .uri("lb://user-service"))
            
            // Order Service Routes
            .route("order-service", r -> r.path("/api/orders/**")
                .uri("lb://order-service"))
            
            // Product Service Routes
            .route("product-service", r -> r.path("/api/products/**")
                .uri("lb://product-service"))
            
            // Authentication Service Routes
            .route("auth-service", r -> r.path("/api/auth/**")
                .uri("lb://auth-service"))
            
            .build();
    }
}

// Authentication Filter
@Component
class AuthenticationFilter implements GlobalFilter, Ordered {
    
    @Override
    public Mono<Void> filter(ServerHttpRequest request, GatewayFilterChain chain) {
        String path = request.getURI().getPath();
        
        // Endpoints públicos sin autenticación
        if (path.startsWith("/api/auth/login") || 
            path.startsWith("/api/auth/register") ||
            path.startsWith("/actuator")) {
            return chain.filter(request);
        }
        
        // Extraer token del encabezado
        String authHeader = request.getHeaders().getFirst("Authorization");
        
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            return Mono.error(new RuntimeException("Missing or invalid token"));
        }
        
        String token = authHeader.substring(7);
        
        // Validar token (simplificado)
        if (!isValidToken(token)) {
            return Mono.error(new RuntimeException("Invalid token"));
        }
        
        // Agregar información del usuario al request
        ServerHttpRequest modifiedRequest = request.mutate()
            .header("X-User-ID", getUserIdFromToken(token))
            .header("X-User-Role", getUserRoleFromToken(token))
            .build();
        
        return chain.filter(modifiedRequest);
    }
    
    private boolean isValidToken(String token) {
        // Validación de token (simplificada)
        return token != null && !token.isEmpty();
    }
    
    private String getUserIdFromToken(String token) {
        // Extraer ID de usuario del token
        return "123"; // Ejemplo
    }
    
    private String getUserRoleFromToken(String token) {
        // Extraer rol de usuario del token
        return "USER"; // Ejemplo
    }
    
    @Override
    public int getOrder() {
        return -100; // Alta prioridad
    }
}

// Rate Limiting Filter
@Component
class RateLimitingFilter implements GlobalFilter, Ordered {
    
    private final Map<String, Map<String, Integer>> rateLimitMap = new ConcurrentHashMap<>();
    private static final int RATE_LIMIT = 100; // 100 solicitudes por minuto
    private static final int TIME_WINDOW = 60; // Segundos
    
    @Override
    public Mono<Void> filter(ServerHttpRequest request, GatewayFilterChain chain) {
        String clientId = getClientId(request);
        String currentTimeWindow = getCurrentTimeWindow();
        
        rateLimitMap.putIfAbsent(clientId, new ConcurrentHashMap<>());
        Map<String, Integer> clientRequests = rateLimitMap.get(clientId);
        
        // Eliminar ventanas de tiempo antiguas
        clientRequests.entrySet().removeIf(entry -> 
            !entry.getKey().equals(currentTimeWindow));
        
        // Incrementar contador actual
        int currentCount = clientRequests.getOrDefault(currentTimeWindow, 0);
        
        if (currentCount >= RATE_LIMIT) {
            return Mono.error(new RuntimeException("Rate limit exceeded"));
        }
        
        clientRequests.put(currentTimeWindow, currentCount + 1);
        return chain.filter(request);
    }
    
    private String getClientId(ServerHttpRequest request) {
        // Extraer ID del cliente desde IP o token
        return request.getRemoteAddress() != null ? 
            request.getRemoteAddress().getAddress().getHostAddress() : "unknown";
    }
    
    private String getCurrentTimeWindow() {
        return String.valueOf(System.currentTimeMillis() / (TIME_WINDOW * 1000));
    }
    
    @Override
    public int getOrder() {
        return -99; // Después de autenticación
    }
}

// Load Balancing Configuration
@Component
class LoadBalancerConfig {
    
    @Bean
    public ReactorLoadBalancer<ServiceInstance> userServiceLoadBalancer(
            Environment environment,
            LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new RoundRobinLoadBalancer(
            loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
            name);
    }
}

// Circuit Breaker Configuration
@Component
class CircuitBreakerConfig {
    
    @Bean
    public CircuitBreaker circuitBreaker() {
        CircuitBreakerConfig config = CircuitBreakerConfig.custom()
            .failureRateThreshold(50)
            .waitDurationInOpenState(Duration.ofMillis(1000))
            .slidingWindowSize(20)
            .minimumNumberOfCalls(10)
            .build();
        
        return CircuitBreaker.of("myCircuitBreaker", config);
    }
}

// API Gateway Controller para Health Checks
@RestController
@RequestMapping("/gateway")
class GatewayController {
    
    @GetMapping("/health")
    public Map<String, String> health() {
        Map<String, String> status = new HashMap<>();
        status.put("status", "UP");
        status.put("service", "api-gateway");
        status.put("timestamp", Instant.now().toString());
        return status;
    }
    
    @GetMapping("/routes")
    public Map<String, Object> getRoutes() {
        Map<String, Object> routes = new HashMap<>();
        routes.put("user-service", "/api/users/**");
        routes.put("order-service", "/api/orders/**");
        routes.put("product-service", "/api/products/**");
        routes.put("auth-service", "/api/auth/**");
        return routes;
    }
}

4. Contenedores Docker para Microservicios

# Dockerfile para User Service
FROM openjdk:11-jre-slim

# Working Directory
WORKDIR /app

# Application JAR kopieren
COPY target/user-service-0.0.1-SNAPSHOT.jar app.jar

# Expose Port
EXPOSE 8081

# Environment Variables
ENV JAVA_OPTS="-Xmx512m -Xms256m"
ENV SPRING_PROFILES_ACTIVE=docker

# Health Check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:8081/actuator/health || exit 1

# Application starten
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
# Dockerfile für Order Service
FROM openjdk:11-jre-slim

WORKDIR /app

COPY target/order-service-0.0.1-SNAPSHOT.jar app.jar

EXPOSE 8082

ENV JAVA_OPTS="-Xmx512m -Xms256m"
ENV SPRING_PROFILES_ACTIVE=docker

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:8082/actuator/health || exit 1

ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
# Dockerfile für API Gateway
FROM openjdk:11-jre-slim

WORKDIR /app

COPY target/api-gateway-0.0.1-SNAPSHOT.jar app.jar

EXPOSE 8080

ENV JAVA_OPTS="-Xmx512m -Xms256m"
ENV SPRING_PROFILES_ACTIVE=docker

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:8080/gateway/health || exit 1

ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

5. Orquestación con Kubernetes

# Namespace
apiVersion: v1
kind: Namespace
metadata:
  name: microservices

---
# User Service Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-service
  namespace: microservices
spec:
  replicas: 3
  selector:
    matchLabels:
      app: user-service
  template:
    metadata:
      labels:
        app: user-service
    spec:
      containers:
      - name: user-service
        image: user-service:latest
        ports:
        - containerPort: 8081
        env:
        - name: SPRING_PROFILES_ACTIVE
          value: "kubernetes"
        - name: SPRING_DATASOURCE_URL
          value: "jdbc:postgresql://postgres-service:5432/userdb"
        - name: SPRING_DATASOURCE_USERNAME
          value: "postgres"
        - name: SPRING_DATASOURCE_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: password
        livenessProbe:
          httpGet:
            path: /actuator/health
            port: 8081
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /actuator/health
            port: 8081
          initialDelaySeconds: 5
          periodSeconds: 5
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"

---
# User Service Service
apiVersion: v1
kind: Service
metadata:
  name: user-service
  namespace: microservices
spec:
  selector:
    app: user-service
  ports:
  - protocol: TCP
    port: 8081
    targetPort: 8081
  type: ClusterIP

---
# Order Service Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
  namespace: microservices
spec:
  replicas: 2
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
      - name: order-service
        image: order-service:latest
        ports:
        - containerPort: 8082
        env:
        - name: SPRING_PROFILES_ACTIVE
          value: "kubernetes"
        - name: USER_SERVICE_URL
          value: "http://user-service:8081"
        livenessProbe:
          httpGet:
            path: /actuator/health
            port: 8082
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /actuator/health
            port: 8082
          initialDelaySeconds: 5
          periodSeconds: 5
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"

---
# Order Service Service
apiVersion: v1
kind: Service
metadata:
  name: order-service
  namespace: microservices
spec:
  selector:
    app: order-service
  ports:
  - protocol: TCP
    port: 8082
    targetPort: 8082
  type: ClusterIP

---
# API Gateway Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-gateway
  namespace: microservices
spec:
  replicas: 2
  selector:
    matchLabels:
      app: api-gateway
  template:
    metadata:
      labels:
        app: api-gateway
    spec:
      containers:
      - name: api-gateway
        image: api-gateway:latest
        ports:
        - containerPort: 8080
        env:
        - name: SPRING_PROFILES_ACTIVE
          value: "kubernetes"
        livenessProbe:
          httpGet:
            path: /gateway/health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /gateway/health
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"

---
# API Gateway Service
apiVersion: v1
kind: Service
metadata:
  name: api-gateway
  namespace: microservices
spec:
  selector:
    app: api-gateway
  ports:
  - protocol: TCP
    port: 8080
    targetPort: 8080
  type: LoadBalancer

---
# Horizontal Pod Autoscaler für User Service
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: user-service-hpa
  namespace: microservices
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: user-service
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80

---
# ConfigMap für Konfiguration
apiVersion: v1
kind: ConfigMap
metadata:
  name: microservices-config
  namespace: microservices
data:
  application.yml: |
    spring:
      cloud:
        kubernetes:
          discovery:
            enabled: true
          config:
            enabled: true
    management:
      endpoints:
        web:
          exposure:
            include: health,info,metrics,prometheus
      endpoint:
        health:
          show-details: always

---
# Secret für sensible Daten
apiVersion: v1
kind: Secret
metadata:
  name: db-secret
  namespace: microservices
type: Opaque
data:
  password: cGFzc3dvcmQxMjM=  # Base64 encoded "password123"
  username: cG9zdGdyZXM=      # Base64 encoded "postgres"

6. Service Mesh con Istio

# Istio Gateway
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: microservices-gateway
  namespace: microservices
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*"

---
# Virtual Service für API Gateway
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: api-gateway-vs
  namespace: microservices
spec:
  hosts:
  - "*"
  gateways:
  - microservices-gateway
  http:
  - match:
    - uri:
        prefix: /
    route:
    - destination:
        host: api-gateway
        port:
          number: 8080

---
# Destination Rules für Load Balancing
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: user-service-dr
  namespace: microservices
spec:
  host: user-service
  trafficPolicy:
    loadBalancer:
      simple: ROUND_ROBIN
    connectionPool:
      tcp:
        maxConnections: 100
      http:
        http1MaxPendingRequests: 50
        maxRequestsPerConnection: 10
    circuitBreaker:
      consecutiveErrors: 3
      interval: 30s
      baseEjectionTime: 30s

---
# Service Entry für externe Services
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
  name: external-api
  namespace: microservices
spec:
  hosts:
  - external-api.example.com
  ports:
  - number: 443
    name: https
    protocol: HTTPS
  resolution: DNS

---
# Authorization Policy
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: allow-api-access
  namespace: microservices
spec:
  selector:
    matchLabels:
      app: api-gateway
  rules:
  - from:
    - source:
        principals: ["cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account"]
  - to:
    - operation:
        methods: ["GET", "POST", "PUT", "DELETE"]

---
# Request Authentication
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
  name: jwt-auth
  namespace: microservices
spec:
  selector:
    matchLabels:
      app: api-gateway
  jwtRules:
  - issuer: "https://auth.example.com"
    jwksUri: "https://auth.example.com/.well-known/jwks.json"
    forwardOriginalToken: true

Comparativa de Arquitectura

Monolito vs Microservicios

CriterioMonolitoMicroservicios
ComplejidadBajaAlta
DeploymentSimpleComplejo
EscaladoVerticalHorizontal
TecnologíaUniformeFlexible
Aislamiento de fallosDeficienteBueno
Estructura de equiposCentralizadaDistribuida
DesarrolloRápidoMás lento

Estrategias de Deployment

EstrategiaDescripciónVentajasDesventajas
Big BangTodo de una vezSimpleAlto riesgo
Blue-GreenSistemas paralelosZero-DowntimeRecursos
CanaryGradualSeguroComplejo
RollingGradualEficienteLento

Tecnologías de Contenedores

Docker vs Podman

CaracterísticaDockerPodman
DaemonNo
RootlessLimitadoCompleto
PodsNo
Dockerfile
ComposeLimitado

Registros de Contenedores

RegistroCaracterísticasPrecio
Docker HubPúblico/PrivadoFreemium
GitHub PackagesIntegraciónGratis
AWS ECRNativo en la nubePago por uso
Google GCRNativo en la nubePago por uso

Plataformas de Orquestación

Kubernetes vs Docker Swarm

CaracterísticaKubernetesDocker Swarm
ComplejidadAltaBaja
EscaladoAutomáticoManual
Service MeshNo
Curva de aprendizajePronunciadaSuave
ComunidadGrandeMediana

Implementaciones de Service Mesh

Istio vs Linkerd

CaracterísticaIstioLinkerd
ComplejidadAltaBaja
CaracterísticasExhaustivasEnfocadas
RendimientoBuenoMuy bueno
RecursosAltosBajos
IntegraciónExhaustivaSimple

Características de API Gateway

Funciones Básicas

  • Routing: Enrutamiento de solicitudes a los servicios correctos
  • Authentication: JWT, OAuth2, claves API
  • Authorization: Control de acceso basado en roles
  • Rate Limiting: Límites de solicitudes por cliente
  • Load Balancing: Distribución de carga
  • Caching: Almacenamiento en caché de respuestas
  • Logging: Registro de solicitudes y respuestas

Características Avanzadas

  • Circuit Breaking: Protección contra fallos en cascada
  • Retry Logic: Reintentos automáticos
  • Request/Response Transformation: Transformación de datos
  • API Versioning: Gestión de versiones
  • Analytics: Estadísticas de uso

Monitoreo y Observabilidad

Tres Pilares de la Observabilidad

  1. Metrics: Datos cuantitativos (Prometheus, Grafana)
  2. Logging: Datos basados en eventos (ELK Stack, Fluentd)
  3. Tracing: Rastreo de solicitudes (Jaeger, Zipkin)

Herramientas en el Contexto de Microservicios

# Prometheus Monitoring
apiVersion: v1
kind: ServiceMonitor
metadata:
  name: user-service-monitor
  namespace: microservices
spec:
  selector:
    matchLabels:
      app: user-service
  endpoints:
  - port: http
    path: /actuator/prometheus
    interval: 30s

Mejores Prácticas

Diseño de Microservicios

  • Single Responsibility: Un servicio, una responsabilidad
  • Database per Service: Base de datos propia por servicio
  • Async Communication: Comunicación basada en eventos
  • Circuit Breaking: Protección contra fallos en cascada
  • Health Checks: Monitoreo de la salud del servicio

Mejores Prácticas con Contenedores

# Best Practice Dockerfile
FROM alpine:latest

# Non-root User
RUN addgroup -g 1001 -S appgroup && \
    adduser -u 1001 -S appuser -G appgroup

USER appuser

# Minimal Image
COPY --from=builder /app/target/app.jar /app/app.jar

WORKDIR /app

EXPOSE 8080

# Health Check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1

ENTRYPOINT ["java", "-jar", "app.jar"]

Ventajas e Inconvenientes

Ventajas de los Microservicios

  • Escalabilidad: Escalado independiente por servicio
  • Flexibilidad: Diferentes tecnologías posibles
  • Resiliencia: El fallo de un servicio no afecta a los otros
  • Autonomía de equipos: Equipos de desarrollo independientes
  • Despliegues más rápidos: Despliegues pequeños y frecuentes

Inconvenientes de los Microservicios

  • Complejidad: Los sistemas distribuidos son complejos
  • Latencia de red: Comunicación a través de la red
  • Consistencia de datos: Transacciones distribuidas
  • Monitoreo: Supervisión más compleja
  • Debugging: Depuración más difícil

Preguntas Frecuentes de Examen

  1. ¿Cuáles son las diferencias principales entre monolito y microservicios? Un monolito es una unidad única, mientras que los microservicios son servicios pequeños e independientes, cada uno con su propia responsabilidad.

  2. ¡Explique el papel de un API Gateway! El API Gateway es el punto de entrada central para las solicitudes de clientes, proporcionando enrutamiento, autenticación y limitación de velocidad.

  3. ¿Cuándo se usan contenedores y cuándo orquestación? Los contenedores se usan para aislamiento y portabilidad, mientras que la orquestación se utiliza para la gestión automatizada de muchos contenedores.

  4. ¿Qué es Service Mesh y por qué se necesita? Service Mesh es una capa de infraestructura para la comunicación entre microservicios, proporcionando descubrimiento de servicios y seguridad.

Recursos Principales

  1. https://microservices.io/
  2. https://kubernetes.io/
  3. https://istio.io/
  4. https://spring.io/projects/spring-cloud
Volver al blog
Share:

Entradas relacionadas