Skip to content
IRC-CodingIRC-Coding
Архитектура ПОМонолит МикросервисыAPI GatewayService MeshОркестрация контейнеров

Архитектура ПО: Monolith vs Microservices

Сравнение монолитной архитектуры и микросервисов: API Gateway, Service Mesh, контейнеризация и примеры.

S

schutzgeist

16 min read
Архитектура ПО: Monolith vs Microservices

Архитектура ПО: монолит vs микросервисы с API Gateway и Service Mesh

Это подробный разбор архитектуры программного обеспечения, включая сравнение монолитного подхода с микросервисами, работу API Gateway, Service Mesh, контейнеризацию и оркестрирование на практических примерах.

Вкратце

Монолитная архитектура представляет собой единый связный блок кода, в то время как микросервисы состоят из небольших независимых сервисов. API Gateway координирует взаимодействие между клиентами и сервисами, а Service Mesh управляет коммуникацией между микросервисами.

Определения

Архитектура ПО – это фундаментальная структура программной системы, состоящая из компонентов, их взаимосвязей и принципов проектирования и эволюции системы.

Архитектурные паттерны:

Монолитная архитектура

  • Суть: единое приложение с одной кодовой базой
  • Развертывание: вся система развертывается одновременно
  • Общение: методы вызываются напрямую внутри приложения
  • Плюсы: простая разработка, развертывание и отладка
  • Минусы: сложность масштабирования, привязка к технологиям

Архитектура микросервисов

  • Суть: небольшие, независимые сервисы с четкой ответственностью
  • Развертывание: каждый сервис развертывается отдельно
  • Общение: взаимодействие через сетевые API
  • Плюсы: масштабируемость, свобода в выборе технологий, устойчивость
  • Минусы: сложность управления, задержки в сети, распределенные системные проблемы

API Gateway

  • Суть: единая точка входа для запросов клиентов
  • Функции: маршрутизация, аутентификация, ограничение частоты, балансировка нагрузки
  • Плюсы: централизованное управление, безопасность, мониторинг
  • Реализация: Kong, NGINX, AWS API Gateway

Service Mesh

  • Суть: инфраструктурный слой для коммуникации микросервисов
  • Функции: обнаружение сервисов, балансировка нагрузки, circuit breaking
  • Реализация: Istio, Linkerd, Consul Connect
  • Плюсы: прозрачность, наблюдаемость, безопасность

Ключевые понятия для понимания

  • Архитектура ПО: базовая структура программных систем
  • Монолит: единое приложение с общей кодовой базой
  • Микросервисы: небольшие независимые сервисы со своим жизненным циклом
  • API Gateway: центральная точка входа для клиентских запросов
  • Service Mesh: инфраструктура для взаимодействия микросервисов
  • Контейнеры: виртуализация на уровне операционной системы
  • Оркестрирование: автоматизированное управление контейнерами
  • Актуально для: современные решения архитектурных задач

Основные компоненты

  1. Выбор архитектуры: монолит или микросервисы
  2. Управление API: gateway, маршрутизация, безопасность
  3. Обнаружение сервисов: автоматическое определение доступных сервисов
  4. Распределение нагрузки: балансировка между экземплярами
  5. Защита от сбоев: circuit breaking при возникновении проблем
  6. Контейнеризация: Docker, Podman
  7. Оркестрирование: Kubernetes, Docker Swarm
  8. Наблюдаемость: логирование, метрики, трейсинг

Примеры из практики

1. Монолитная архитектура на 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;

// Основное приложение
@SpringBootApplication
public class MonolithApplication {
    public static void main(String[] args) {
        SpringApplication.run(MonolithApplication.class, args);
    }
}

// Сущность 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 и 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; }
}

// Сущность 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 и 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; }
}

// Интерфейсы репозиториев
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);
}

// Сервисы бизнес-логики
@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);
    }
}

// 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);
    }
}

// DTO
class CreateOrderRequest {
    private Long userId;
    private String product;
    private Double amount;
    
    // Getters и 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. Архитектура микросервисов с 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 (упрощённо)
@Entity
class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String username;
    private String email;
    
    // Getters и 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 для 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 и 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) {
        // Валидация пользователя через вызов микросервиса
        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 с 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();
        
        // Публичные endpoint'ы без аутентификации
        if (path.startsWith("/api/auth/login") || 
            path.startsWith("/api/auth/register") ||
            path.startsWith("/actuator")) {
            return chain.filter(request);
        }
        
        // Извлечение токена из заголовка
        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);
        
        // Валидация токена (упрощённо)
        if (!isValidToken(token)) {
            return Mono.error(new RuntimeException("Invalid token"));
        }
        
        // Добавление информации о пользователе в запрос
        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) {
        // Валидация токена (упрощённо)
        return token != null && !token.isEmpty();
    }
    
    private String getUserIdFromToken(String token) {
        // Извлечение ID пользователя из токена
        return "123"; // Пример
    }
    
    private String getUserRoleFromToken(String token) {
        // Извлечение роли пользователя из токена
        return "USER"; // Пример
    }
    
    @Override
    public int getOrder() {
        return -100; // Высокий приоритет
    }
}

// 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 запросов в минуту
    private static final int TIME_WINDOW = 60; // Секунды
    
    @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);
        
        // Удаление старых временных окон
        clientRequests.entrySet().removeIf(entry -> 
            !entry.getKey().equals(currentTimeWindow));
        
        // Увеличение счётчика для текущего окна
        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) {
        // Извлечение ID клиента из IP или токена
        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; // После аутентификации
    }
}

// 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 для проверок здоровья
@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. Docker контейнеры для Microservices

# Dockerfile для User Service
FROM openjdk:11-jre-slim

# Working Directory
WORKDIR /app

# Application JAR копieren
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. Оркестрация с 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 для 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 для конфигурации
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 для конфиденциальных данных
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 с 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 для 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 для распределения нагрузки
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 для внешних сервисов
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

Сравнение архитектур

Монолит против Микросервисов

КритерийМонолитМикросервисы
СложностьНизкаяВысокая
РазвертываниеПростоеСложное
МасштабированиеВертикальноеГоризонтальное
ТехнологияЕдинаяГибкая
Изоляция ошибокПлохаяХорошая
Структура командыЦентрализованнаяРаспределенная
РазработкаБыстраяМедленнее

Стратегии развертывания

СтратегияОписаниеПреимуществаНедостатки
Big BangВсе сразуПростоВысокий риск
Blue-GreenПараллельные системыНулевой downtimeРесурсы
CanaryПостепенноБезопасноСложно
RollingПоэтапноЭффективноМедленно

Контейнерные технологии

Docker vs Podman

ФункцияDockerPodman
DaemonДаНет
RootlessОграниченноПолностью
PodsНетДа
DockerfileДаДа
ComposeДаОграниченно

Container Registry

RegistryВозможностиЦена
Docker HubPublic/PrivateFreemium
GitHub PackagesИнтеграцияБесплатно
AWS ECRCloud-nativePay-per-use
Google GCRCloud-nativePay-per-use

Платформы оркестрации

Kubernetes vs Docker Swarm

ФункцияKubernetesDocker Swarm
СложностьВысокаяНизкая
МасштабированиеАвтоматическоеРучное
Service MeshДаНет
Кривая обученияКрутаяПологая
СообществоБольшоеСреднее

Реализации Service Mesh

Istio vs Linkerd

ФункцияIstioLinkerd
СложностьВысокаяНизкая
ФункциональностьОбширнаяСфокусированная
ПроизводительностьХорошаяОтличная
РесурсыВысокиеНизкие
ИнтеграцияОбширнаяПростая

Возможности API Gateway

Основные функции

  • Routing: маршрутизация запросов к правильным сервисам
  • Authentication: JWT, OAuth2, API Keys
  • Authorization: ролевой доступ
  • Rate Limiting: ограничение запросов на клиента
  • Load Balancing: распределение нагрузки
  • Caching: кеширование ответов
  • Logging: логирование запросов и ответов

Продвинутые функции

  • Circuit Breaking: защита от сбоев
  • Retry Logic: автоматические повторные попытки
  • Request/Response Transformation: трансформация данных
  • API Versioning: управление версиями
  • Analytics: статистика использования

Monitoring и Observability

Три столпа Observability

  1. Metrics: количественные данные (Prometheus, Grafana)
  2. Logging: событийные данные (ELK Stack, Fluentd)
  3. Tracing: отслеживание запросов (Jaeger, Zipkin)

Инструменты в контексте микросервисов

# 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

Best Practices

Проектирование микросервисов

  • Single Responsibility: один сервис, одна задача
  • Database per Service: собственная база данных на сервис
  • Async Communication: событийная коммуникация
  • Circuit Breaking: защита от каскадных сбоев
  • Health Checks: мониторинг здоровья сервиса

Best Practices для контейнеров

# 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"]

Преимущества и недостатки

Преимущества микросервисов

  • Масштабируемость: независимое масштабирование каждого сервиса
  • Гибкость: возможность использовать разные технологии
  • Устойчивость: отказ одного сервиса не влияет на остальные
  • Автономность команд: независимые команды разработки
  • Быстрое развертывание: небольшие, частые релизы

Недостатки микросервисов

  • Сложность: распределенные системы сложны в управлении
  • Сетевая задержка: коммуникация через сеть
  • Консистентность данных: распределенные транзакции
  • Мониторинг: сложная система мониторинга
  • Отладка: сложнее искать ошибки

Часто задаваемые вопросы на собеседованиях

  1. Какие основные различия между монолитом и микросервисами? Монолит представляет собой единое целое, а микросервисы это небольшие, независимые сервисы с собственной ответственностью.

  2. Объясните роль API Gateway. API Gateway служит центральной точкой входа для клиентских запросов, обеспечивая маршрутизацию, аутентификацию и rate limiting.

  3. Когда использовать контейнеры и когда оркестрацию? Контейнеры используют для изоляции и переносимости, оркестрацию для автоматизированного управления множеством контейнеров.

  4. Что такое Service Mesh и зачем он нужен? Service Mesh это инфраструктурный уровень для коммуникации микросервисов, обеспечивающий сервис-дискавери и безопасность.

Основные источники

  1. https://microservices.io/
  2. https://kubernetes.io/
  3. https://istio.io/
  4. https://spring.io/projects/spring-cloud
Назад к блогу
Share:

Похожие статьи