Software-Architektur: Monolith vs Microservices mit API Gateway & Service Mesh
Dieser Beitrag ist eine umfassende Einführung in die Software-Architektur – inklusive Monolith vs Microservices mit API Gateway, Service Mesh, Container und Orchestrierung mit praktischen Beispielen.
In a Nutshell
Monolith-Architektur ist ein einziger, zusammenhängender Code-Block, während Microservices kleine, unabhängige Dienste sind. API Gateway koordiniert Kommunikation, Service Mesh verwaltet Microservice-Interaktionen.
Kompakte Fachbeschreibung
Software-Architektur ist die grundlegende Struktur eines Softwaresystems, bestehend aus Komponenten, deren Beziehungen und Prinzipien für Design und Evolution.
Architektur-Muster:
Monolith-Architektur
- Konzept: Einheitliche Anwendung in einer Code-Basis
- Deployment: Einmaliges Deployment der gesamten Anwendung
- Kommunikation: Direkte Methodenaufrufe innerhalb der Anwendung
- Vorteile: Einfache Entwicklung, Deployment, Debugging
- Nachteile: Skalierungsprobleme, Technologie-Beschränkungen
Microservice-Architektur
- Konzept: Kleine, autonome Dienste mit eigener Verantwortung
- Deployment: Unabhängige Deployment-Fähigkeit
- Kommunikation: Netzwerk-basierte API-Aufrufe
- Vorteile: Skalierbarkeit, Technologie-Flexibilität, Resilienz
- Nachteile: Komplexität, Netzwerk-Latenz, Verteilte Komplexität
API Gateway
- Konzept: Zentraler Eingangspunkt für Client-Anfragen
- Funktionen: Routing, Authentication, Rate Limiting, Load Balancing
- Vorteile: Zentralisierte Verwaltung, Sicherheit, Monitoring
- Implementierung: Kong, NGINX, AWS API Gateway
Service Mesh
- Konzept: Infrastrukturebene für Microservice-Kommunikation
- Funktionen: Service Discovery, Load Balancing, Circuit Breaking
- Implementierung: Istio, Linkerd, Consul Connect
- Vorteile: Transparenz, Observability, Security
Prüfungsrelevante Stichpunkte
- Software-Architektur: Grundlegende Struktur von Softwaresystemen
- Monolith: Einheitliche Anwendung mit gemeinsamer Code-Basis
- Microservices: Kleine, unabhängige Dienste mit eigenem Lifecycle
- API Gateway: Zentraler Eingangspunkt für Client-Kommunikation
- Service Mesh: Infrastruktur für Microservice-Interaktionen
- Container: Virtualisierung auf Betriebssystem-Ebene
- Orchestrierung: Automatisierte Verwaltung von Containern
- IHK-relevant: Moderne Architektur-Entscheidungen und -Muster
Kernkomponenten
- Architektur-Entscheidung: Monolith vs Microservices
- API-Management: Gateway, Routing, Security
- Service Discovery: Automatische Dienst-Erkennung
- Load Balancing: Verteilung von Anfragen
- Circuit Breaking: Schutz vor Kaskaden-Fehlern
- Containerisierung: Docker, Podman
- Orchestrierung: Kubernetes, Docker Swarm
- Monitoring: Logging, Metrics, Tracing
Praxisbeispiele
1. Monolith-Architektur mit 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;
// Hauptanwendung
@SpringBootApplication
public class MonolithApplication {
public static void main(String[] args) {
SpringApplication.run(MonolithApplication.class, args);
}
}
// User Entity
@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 und 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
@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 und 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; }
}
// Repository Interfaces
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);
}
// Business Logic Services
@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 Controllers
@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 und 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. Microservice-Architektur mit 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 (vereinfacht)
@Entity
class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String email;
// Getters und 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 für 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 und 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) {
// User-Validierung über Microservice-Aufruf
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 mit 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();
// Public Endpoints ohne Authentication
if (path.startsWith("/api/auth/login") ||
path.startsWith("/api/auth/register") ||
path.startsWith("/actuator")) {
return chain.filter(request);
}
// Token aus Header extrahieren
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);
// Token validieren (vereinfacht)
if (!isValidToken(token)) {
return Mono.error(new RuntimeException("Invalid token"));
}
// User-Informationen zum Request hinzufügen
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) {
// Token-Validierung (vereinfacht)
return token != null && !token.isEmpty();
}
private String getUserIdFromToken(String token) {
// User-ID aus Token extrahieren
return "123"; // Beispiel
}
private String getUserRoleFromToken(String token) {
// User-Rolle aus Token extrahieren
return "USER"; // Beispiel
}
@Override
public int getOrder() {
return -100; // Hohe Priorität
}
}
// 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 Anfragen pro Minute
private static final int TIME_WINDOW = 60; // Sekunden
@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);
// Alte Zeitfenster entfernen
clientRequests.entrySet().removeIf(entry ->
!entry.getKey().equals(currentTimeWindow));
// Aktuellen Zähler erhöhen
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) {
// Client-ID aus IP oder Token extrahieren
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; // Nach Authentication
}
}
// 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 für 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. Docker Container für Microservices
# Dockerfile für 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. Kubernetes Orchestrierung
# 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 mit 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
Architektur-Vergleich
Monolith vs Microservices
| Kriterium | Monolith | Microservices |
|---|---|---|
| Komplexität | Geringer | Hoch |
| Deployment | Einfach | Komplex |
| Skalierung | Vertikal | Horizontal |
| Technologie | Einheitlich | Flexibel |
| Fehlerisolierung | Schlecht | Gut |
| Team-Struktur | Zentral | Verteilt |
| Development | Schnell | Langsamer |
Deployment-Strategien
| Strategie | Beschreibung | Vorteile | Nachteile |
|---|---|---|---|
| Big Bang | Alles auf einmal | Einfach | Hochriskant |
| Blue-Green | Parallel-Systeme | Zero-Downtime | Ressourcen |
| Canary | Schrittweise | Sicher | Komplex |
| Rolling | Schrittweise | Effizient | Langsam |
Container-Technologien
Docker vs Podman
| Feature | Docker | Podman |
|---|---|---|
| Daemon | Ja | Nein |
| Rootless | Begrenzt | Voll |
| Pods | Nein | Ja |
| Dockerfile | Ja | Ja |
| Compose | Ja | Begrenzt |
Container-Registry
| Registry | Features | Preis |
|---|---|---|
| Docker Hub | Public/Private | Freemium |
| GitHub Packages | Integration | Free |
| AWS ECR | Cloud-Nativ | Pay-per-use |
| Google GCR | Cloud-Nativ | Pay-per-use |
Orchestrierung-Plattformen
Kubernetes vs Docker Swarm
| Feature | Kubernetes | Docker Swarm |
|---|---|---|
| Komplexität | Hoch | Gering |
| Skalierung | Auto | Manuell |
| Service Mesh | Ja | Nein |
| Learning Curve | Steil | Flach |
| Community | Groß | Mittel |
Service Mesh Implementierungen
Istio vs Linkerd
| Feature | Istio | Linkerd |
|---|---|---|
| Complexity | Hoch | Gering |
| Features | Umfassend | Fokussiert |
| Performance | Gut | Sehr gut |
| Ressourcen | Hoch | Gering |
| Integration | Umfassend | Einfach |
API Gateway Features
Core-Funktionen
- Routing: Anfragen zu richtigen Services
- Authentication: JWT, OAuth2, API Keys
- Authorization: Rollenbasierte Zugriffe
- Rate Limiting: Anfragelimits pro Client
- Load Balancing: Verteilung der Last
- Caching: Response-Caching
- Logging: Request/Response Logging
Advanced Features
- Circuit Breaking: Schutz vor Ausfällen
- Retry Logic: Automatische Wiederholungen
- Request/Response Transformation: Daten-Transformation
- API Versioning: Versionsverwaltung
- Analytics: Nutzungsstatistiken
Monitoring & Observability
Drei Säulen der Observability
- Metrics: Quantitative Daten (Prometheus, Grafana)
- Logging: Ereignis-basierte Daten (ELK Stack, Fluentd)
- Tracing: Request-Verfolgung (Jaeger, Zipkin)
Tools im Microservice-Kontext
# 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
Microservice-Design
- Single Responsibility: Ein Service, eine Aufgabe
- Database per Service: Eigene Datenbank pro Service
- Async Communication: Event-basierte Kommunikation
- Circuit Breaking: Schutz vor Kaskaden-Fehlern
- Health Checks: Überwachung der Service-Gesundheit
Container-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"]
Vorteile und Nachteile
Vorteile von Microservices
- Skalierbarkeit: Unabhängige Skalierung pro Service
- Flexibilität: Verschiedene Technologien möglich
- Resilienz: Ausfall eines Services不影响其他
- Team-Autonomie: Unabhängige Entwicklungsteams
- Schnellere Deployment: Kleine, häufige Deployments
Nachteile von Microservices
- Komplexität: Verteilte Systeme sind komplex
- Netzwerk-Latenz: Kommunikation über Netzwerk
- Daten-Konsistenz: Verteilte Transaktionen
- Monitoring: Komplexere Überwachung
- Debugging: Schwierigere Fehlersuche
Häufige Prüfungsfragen
-
Was sind die Hauptunterschiede zwischen Monolith und Microservices? Monolith ist eine Einheit, Microservices sind kleine, unabhängige Dienste mit eigener Verantwortung.
-
Erklären Sie die Rolle eines API Gateways! API Gateway ist zentraler Eingangspunkt für Client-Anfragen mit Routing, Authentication und Rate Limiting.
-
Wann verwendet man Container und wann Orchestrierung? Container für Isolation und Portabilität, Orchestrierung für automatisierte Verwaltung vieler Container.
-
Was ist Service Mesh und warum wird es benötigt? Service Mesh ist Infrastrukturebene für Microservice-Kommunikation mit Service Discovery und Security.
Wichtigste Quellen
- https://microservices.io/
- https://kubernetes.io/
- https://istio.io/
- https://spring.io/projects/spring-cloud