学习目标
掌握Spring Cloud Gateway现代API网关技术,学习分布式链路追踪,了解微服务安全认证,掌握Docker容器化部署和Kubernetes基础编排管理。
1. Spring Cloud Gateway现代API网关
1.1 Gateway核心概念与架构
核心概念:
- 路由(Route):网关的基本构建块
- 断言(Predicate):匹配条件
- 过滤器(Filter):请求和响应的处理逻辑
- 网关过滤器链:请求处理的完整流程
代码示例:
java
// 基础Gateway配置
@Configuration
@EnableWebFlux
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
// 用户服务路由
.route("user-service", r -> r.path("/api/users/**")
.filters(f -> f
.stripPrefix(2)
.addRequestHeader("X-Gateway", "true")
.addRequestHeader("X-Request-ID", generateRequestId())
.circuitBreaker(config -> config
.setName("user-service")
.setFallbackUri("forward:/fallback/user-service"))
.retry(config -> config
.setRetries(3)
.setMethods(HttpMethod.GET, HttpMethod.POST)
.setStatuses(HttpStatus.INTERNAL_SERVER_ERROR)
.setBackoff(Duration.ofMillis(100), Duration.ofMillis(1000), 2, true))
.requestRateLimiter(config -> config
.setRateLimiter(redisRateLimiter())
.setKeyResolver(ipKeyResolver()))
.modifyResponseBody(String.class, String.class,
(exchange, response) -> {
ServerHttpResponse httpResponse = exchange.getResponse();
httpResponse.getHeaders().add("X-Response-Time",
String.valueOf(System.currentTimeMillis()));
return Mono.just(response);
}))
.uri("lb://user-service"))
// 订单服务路由
.route("order-service", r -> r.path("/api/orders/**")
.filters(f -> f
.stripPrefix(2)
.addRequestHeader("X-Gateway", "true")
.addRequestHeader("X-Request-ID", generateRequestId())
.circuitBreaker(config -> config
.setName("order-service")
.setFallbackUri("forward:/fallback/order-service"))
.retry(config -> config
.setRetries(2)
.setMethods(HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT)
.setStatuses(HttpStatus.INTERNAL_SERVER_ERROR)
.setBackoff(Duration.ofMillis(200), Duration.ofMillis(2000), 2, true)))
.uri("lb://order-service"))
// 通知服务路由
.route("notification-service", r -> r.path("/api/notifications/**")
.filters(f -> f
.stripPrefix(2)
.addRequestHeader("X-Gateway", "true")
.addRequestHeader("X-Request-ID", generateRequestId())
.circuitBreaker(config -> config
.setName("notification-service")
.setFallbackUri("forward:/fallback/notification-service")))
.uri("lb://notification-service"))
// 静态资源路由
.route("static-resources", r -> r.path("/static/**")
.filters(f -> f.stripPrefix(1))
.uri("file:./static/"))
.build();
}
@Bean
public RedisRateLimiter redisRateLimiter() {
return new RedisRateLimiter(10, 20, 1);
}
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
}
private String generateRequestId() {
return UUID.randomUUID().toString();
}
}
1.2 自定义过滤器开发
全局过滤器:
java
// 认证过滤器
@Component
@Order(-100)
@Slf4j
public class AuthenticationFilter implements GlobalFilter {
@Autowired
private JwtUtil jwtUtil;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
// 跳过认证的路径
if (isSkipAuthPath(path)) {
return chain.filter(exchange);
}
String token = extractToken(request);
if (token == null) {
return unauthorizedResponse(exchange, "缺少认证令牌");
}
try {
Claims claims = jwtUtil.parseToken(token);
String userId = claims.getSubject();
String username = claims.get("username", String.class);
// 将用户信息添加到请求头
ServerHttpRequest mutatedRequest = request.mutate()
.header("X-User-ID", userId)
.header("X-Username", username)
.build();
return chain.filter(exchange.mutate().request(mutatedRequest).build());
} catch (Exception e) {
log.error("Token验证失败", e);
return unauthorizedResponse(exchange, "认证令牌无效");
}
}
private boolean isSkipAuthPath(String path) {
return path.startsWith("/api/auth/") ||
path.startsWith("/api/public/") ||
path.startsWith("/actuator/") ||
path.startsWith("/static/");
}
private String extractToken(ServerHttpRequest request) {
String authHeader = request.getHeaders().getFirst("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
return authHeader.substring(7);
}
return null;
}
private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String message) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().add("Content-Type", "application/json");
String body = String.format("{\"error\":\"%s\",\"timestamp\":\"%s\"}",
message, LocalDateTime.now());
DataBuffer buffer = response.bufferFactory().wrap(body.getBytes());
return response.writeWith(Mono.just(buffer));
}
}
// 日志过滤器
@Component
@Order(-50)
@Slf4j
public class LoggingFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String requestId = request.getHeaders().getFirst("X-Request-ID");
long startTime = System.currentTimeMillis();
return chain.filter(exchange)
.doOnSuccess(response -> {
long duration = System.currentTimeMillis() - startTime;
log.info("Gateway Request: {} {} - {} {}ms",
request.getMethod(), request.getURI().getPath(),
exchange.getResponse().getStatusCode(), duration);
})
.doOnError(error -> {
long duration = System.currentTimeMillis() - startTime;
log.error("Gateway Error: {} {} - {} {}ms",
request.getMethod(), request.getURI().getPath(),
error.getMessage(), duration);
});
}
}
// 限流过滤器
@Component
@Order(-10)
@Slf4j
public class RateLimitFilter implements GlobalFilter {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String clientIp = getClientIp(exchange.getRequest());
String key = "rate_limit:" + clientIp;
return checkRateLimit(key)
.flatMap(allowed -> {
if (allowed) {
return chain.filter(exchange);
} else {
return rateLimitExceededResponse(exchange);
}
});
}
private Mono<Boolean> checkRateLimit(String key) {
return Mono.fromCallable(() -> {
String count = redisTemplate.opsForValue().get(key);
if (count == null) {
redisTemplate.opsForValue().set(key, "1", Duration.ofMinutes(1));
return true;
}
int currentCount = Integer.parseInt(count);
if (currentCount >= 100) { // 每分钟100次请求
return false;
}
redisTemplate.opsForValue().increment(key);
return true;
});
}
private String getClientIp(ServerHttpRequest request) {
String xForwardedFor = request.getHeaders().getFirst("X-Forwarded-For");
if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
return xForwardedFor.split(",")[0].trim();
}
return request.getRemoteAddress() != null ?
request.getRemoteAddress().getAddress().getHostAddress() : "unknown";
}
private Mono<Void> rateLimitExceededResponse(ServerWebExchange exchange) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
response.getHeaders().add("Content-Type", "application/json");
String body = "{\"error\":\"Rate limit exceeded\",\"timestamp\":\"" +
LocalDateTime.now() + "\"}";
DataBuffer buffer = response.bufferFactory().wrap(body.getBytes());
return response.writeWith(Mono.just(buffer));
}
}
1.3 动态路由配置
动态路由管理:
java
// 动态路由服务
@Service
@Slf4j
public class DynamicRouteService {
@Autowired
private RouteDefinitionLocator routeDefinitionLocator;
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
public void addRoute(RouteDefinition routeDefinition) {
try {
routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
log.info("动态路由添加成功: {}", routeDefinition.getId());
} catch (Exception e) {
log.error("动态路由添加失败", e);
}
}
public void updateRoute(RouteDefinition routeDefinition) {
try {
routeDefinitionWriter.delete(Mono.just(routeDefinition.getId())).subscribe();
routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
log.info("动态路由更新成功: {}", routeDefinition.getId());
} catch (Exception e) {
log.error("动态路由更新失败", e);
}
}
public void deleteRoute(String routeId) {
try {
routeDefinitionWriter.delete(Mono.just(routeId)).subscribe();
applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
log.info("动态路由删除成功: {}", routeId);
} catch (Exception e) {
log.error("动态路由删除失败", e);
}
}
public Flux<RouteDefinition> getRoutes() {
return routeDefinitionLocator.getRouteDefinitions();
}
}
// 路由管理控制器
@RestController
@RequestMapping("/api/gateway/routes")
@Slf4j
public class RouteController {
@Autowired
private DynamicRouteService dynamicRouteService;
@PostMapping
public ResponseEntity<String> addRoute(@RequestBody RouteDefinition routeDefinition) {
dynamicRouteService.addRoute(routeDefinition);
return ResponseEntity.ok("路由添加成功");
}
@PutMapping("/{id}")
public ResponseEntity<String> updateRoute(@PathVariable String id,
@RequestBody RouteDefinition routeDefinition) {
routeDefinition.setId(id);
dynamicRouteService.updateRoute(routeDefinition);
return ResponseEntity.ok("路由更新成功");
}
@DeleteMapping("/{id}")
public ResponseEntity<String> deleteRoute(@PathVariable String id) {
dynamicRouteService.deleteRoute(id);
return ResponseEntity.ok("路由删除成功");
}
@GetMapping
public ResponseEntity<Flux<RouteDefinition>> getRoutes() {
return ResponseEntity.ok(dynamicRouteService.getRoutes());
}
}
2. Spring Cloud Sleuth分布式链路追踪
2.1 Sleuth基础配置
核心概念:
- Trace:一次完整的请求链路
- Span:链路中的每个节点
- TraceId:整个链路的唯一标识
- SpanId:每个节点的唯一标识
代码示例:
java
// Sleuth配置
@Configuration
@EnableSleuth
public class SleuthConfig {
@Bean
public Sampler alwaysSampler() {
return Sampler.ALWAYS_SAMPLE;
}
@Bean
public Sampler probabilityBasedSampler() {
return ProbabilityBasedSampler.create(0.5f); // 50%采样率
}
}
// 自定义Span处理器
@Component
@Slf4j
public class CustomSpanProcessor implements SpanHandler {
@Override
public boolean end(TraceContext traceContext, MutableSpan span, Cause cause) {
// 记录Span信息
log.info("Span结束: traceId={}, spanId={}, name={}, duration={}ms",
traceContext.traceId(),
traceContext.spanId(),
span.name(),
span.finishTimestamp() - span.startTimestamp());
// 添加自定义标签
span.tag("custom.tag", "custom-value");
span.tag("service.version", "1.0.0");
return true; // 返回true表示处理完成
}
}
// 链路追踪工具类
@Component
@Slf4j
public class TracingUtil {
@Autowired
private Tracer tracer;
public void addCustomTag(String key, String value) {
Span currentSpan = tracer.currentSpan();
if (currentSpan != null) {
currentSpan.tag(key, value);
}
}
public void addCustomEvent(String eventName) {
Span currentSpan = tracer.currentSpan();
if (currentSpan != null) {
currentSpan.event(eventName);
}
}
public void addCustomAnnotation(String annotation) {
Span currentSpan = tracer.currentSpan();
if (currentSpan != null) {
currentSpan.annotate(annotation);
}
}
public Span createNewSpan(String spanName) {
return tracer.nextSpan().name(spanName).start();
}
}
2.2 自定义链路追踪
业务链路追踪:
java
// 订单服务链路追踪
@Service
@Slf4j
public class OrderService {
@Autowired
private Tracer tracer;
@Autowired
private TracingUtil tracingUtil;
@Autowired
private UserServiceClient userServiceClient;
@Autowired
private PaymentServiceClient paymentServiceClient;
public Order createOrder(OrderRequest request) {
Span span = tracer.nextSpan()
.name("create-order")
.tag("order.amount", request.getAmount().toString())
.tag("order.userId", request.getUserId().toString())
.start();
try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
tracingUtil.addCustomEvent("订单创建开始");
// 验证用户
User user = validateUser(request.getUserId());
tracingUtil.addCustomTag("user.status", user.getStatus().name());
// 处理支付
PaymentResult payment = processPayment(request);
tracingUtil.addCustomTag("payment.transactionId", payment.getTransactionId());
// 创建订单
Order order = buildOrder(request, user, payment);
tracingUtil.addCustomTag("order.id", order.getId().toString());
tracingUtil.addCustomEvent("订单创建完成");
return order;
} finally {
span.end();
}
}
private User validateUser(Long userId) {
Span span = tracer.nextSpan()
.name("validate-user")
.tag("user.id", userId.toString())
.start();
try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
tracingUtil.addCustomEvent("开始验证用户");
User user = userServiceClient.getUserById(userId);
if (user == null) {
throw new UserNotFoundException("用户不存在");
}
tracingUtil.addCustomTag("user.found", "true");
tracingUtil.addCustomEvent("用户验证完成");
return user;
} finally {
span.end();
}
}
private PaymentResult processPayment(OrderRequest request) {
Span span = tracer.nextSpan()
.name("process-payment")
.tag("payment.amount", request.getAmount().toString())
.start();
try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
tracingUtil.addCustomEvent("开始处理支付");
PaymentRequest paymentRequest = new PaymentRequest();
paymentRequest.setOrderId(request.getOrderId());
paymentRequest.setAmount(request.getAmount());
paymentRequest.setPaymentMethod(request.getPaymentMethod());
PaymentResult payment = paymentServiceClient.charge(paymentRequest);
tracingUtil.addCustomTag("payment.success", String.valueOf(payment.isSuccess()));
tracingUtil.addCustomEvent("支付处理完成");
return payment;
} finally {
span.end();
}
}
private Order buildOrder(OrderRequest request, User user, PaymentResult payment) {
Span span = tracer.nextSpan()
.name("build-order")
.start();
try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
tracingUtil.addCustomEvent("开始构建订单");
Order order = new Order();
order.setId(request.getOrderId());
order.setUserId(request.getUserId());
order.setAmount(request.getAmount());
order.setStatus(OrderStatus.PENDING);
order.setCreatedAt(LocalDateTime.now());
order.setOrderNumber(generateOrderNumber());
order.setTransactionId(payment.getTransactionId());
Order savedOrder = orderRepository.save(order);
tracingUtil.addCustomTag("order.saved", "true");
tracingUtil.addCustomEvent("订单构建完成");
return savedOrder;
} finally {
span.end();
}
}
private String generateOrderNumber() {
return "ORD" + System.currentTimeMillis() + RandomStringUtils.randomNumeric(4);
}
}
2.3 Zipkin集成配置
Zipkin服务端配置:
java
// Zipkin配置
@Configuration
@EnableZipkinServer
public class ZipkinConfig {
@Bean
public Sampler alwaysSampler() {
return Sampler.ALWAYS_SAMPLE;
}
}
// application.yml配置
spring:
application:
name: zipkin-server
sleuth:
zipkin:
base-url: http://localhost:9411
sampler:
probability: 1.0
zipkin:
base-url: http://localhost:9411
server:
port: 9411
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always
客户端配置:
yaml
# 微服务应用配置
spring:
application:
name: order-service
sleuth:
zipkin:
base-url: http://localhost:9411
sampler:
probability: 1.0
zipkin:
base-url: http://localhost:9411
logging:
level:
org.springframework.cloud.sleuth: DEBUG
brave: DEBUG
3. Spring Cloud Security微服务安全认证
3.1 JWT认证配置
JWT工具类:
java
// JWT工具类
@Component
@Slf4j
public class JwtUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put("username", userDetails.getUsername());
claims.put("authorities", userDetails.getAuthorities());
return createToken(claims, userDetails.getUsername());
}
public String generateToken(String username, Collection<? extends GrantedAuthority> authorities) {
Map<String, Object> claims = new HashMap<>();
claims.put("username", username);
claims.put("authorities", authorities);
return createToken(claims, username);
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
public String refreshToken(String token) {
final Claims claims = getAllClaimsFromToken(token);
claims.setIssuedAt(new Date());
return createToken(claims, claims.getSubject());
}
}
3.2 安全配置
微服务安全配置:
java
// 安全配置
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
@Autowired
private JwtUtil jwtUtil;
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
private JwtRequestFilter jwtRequestFilter;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeHttpRequests(authz -> authz
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/actuator/**").permitAll()
.requestMatchers("/api/users/**").hasRole("USER")
.requestMatchers("/api/orders/**").hasRole("USER")
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
// JWT认证过滤器
@Component
@Slf4j
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private JwtUtil jwtUtil;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
final String requestTokenHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
jwtToken = requestTokenHeader.substring(7);
try {
username = jwtUtil.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
log.error("Unable to get JWT Token");
} catch (ExpiredJwtException e) {
log.error("JWT Token has expired");
} catch (MalformedJwtException e) {
log.error("JWT Token is malformed");
} catch (SignatureException e) {
log.error("JWT Token signature is invalid");
}
} else {
log.warn("JWT Token does not begin with Bearer String");
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
// 认证控制器
@RestController
@RequestMapping("/api/auth")
@Slf4j
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtUtil jwtUtil;
@Autowired
private UserService userService;
@PostMapping("/login")
public ResponseEntity<AuthResponse> login(@RequestBody LoginRequest loginRequest) {
try {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getUsername(),
loginRequest.getPassword()
)
);
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
String token = jwtUtil.generateToken(userDetails);
AuthResponse response = new AuthResponse();
response.setToken(token);
response.setType("Bearer");
response.setUsername(userDetails.getUsername());
response.setAuthorities(userDetails.getAuthorities());
return ResponseEntity.ok(response);
} catch (BadCredentialsException e) {
log.error("Invalid credentials for user: {}", loginRequest.getUsername());
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(new AuthResponse("Invalid credentials"));
}
}
@PostMapping("/register")
public ResponseEntity<AuthResponse> register(@RequestBody RegisterRequest registerRequest) {
try {
User user = userService.createUser(registerRequest);
String token = jwtUtil.generateToken(user.getUsername(), user.getAuthorities());
AuthResponse response = new AuthResponse();
response.setToken(token);
response.setType("Bearer");
response.setUsername(user.getUsername());
response.setAuthorities(user.getAuthorities());
return ResponseEntity.ok(response);
} catch (Exception e) {
log.error("User registration failed", e);
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(new AuthResponse("Registration failed: " + e.getMessage()));
}
}
@PostMapping("/refresh")
public ResponseEntity<AuthResponse> refresh(@RequestBody RefreshTokenRequest request) {
try {
String newToken = jwtUtil.refreshToken(request.getToken());
AuthResponse response = new AuthResponse();
response.setToken(newToken);
response.setType("Bearer");
return ResponseEntity.ok(response);
} catch (Exception e) {
log.error("Token refresh failed", e);
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(new AuthResponse("Token refresh failed"));
}
}
}
4. Docker容器化部署
4.1 Dockerfile编写
多阶段构建Dockerfile:
dockerfile
# 多阶段构建 - 构建阶段
FROM maven:3.8.4-openjdk-11-slim AS builder
WORKDIR /app
# 复制pom.xml并下载依赖
COPY pom.xml .
RUN mvn dependency:go-offline -B
# 复制源代码并构建
COPY src ./src
RUN mvn clean package -DskipTests
# 运行阶段
FROM openjdk:11-jre-slim
# 安装必要的工具
RUN apt-get update && \
apt-get install -y curl && \
rm -rf /var/lib/apt/lists/*
# 创建应用用户
RUN groupadd -r appuser && useradd -r -g appuser appuser
# 设置工作目录
WORKDIR /app
# 复制jar文件
COPY --from=builder /app/target/*.jar app.jar
# 创建日志目录
RUN mkdir -p /app/logs && chown -R appuser:appuser /app
# 切换到应用用户
USER appuser
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
# 暴露端口
EXPOSE 8080
# 设置JVM参数
ENV JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC -XX:+UseContainerSupport"
# 启动应用
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
4.2 Docker Compose配置
微服务Docker Compose:
yaml
# docker-compose.yml
version: '3.8'
services:
# Eureka注册中心
eureka-server:
build: ./eureka-server
container_name: eureka-server
ports:
- "8761:8761"
environment:
- SPRING_PROFILES_ACTIVE=docker
networks:
- microservices-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8761/actuator/health"]
interval: 30s
timeout: 10s
retries: 3
# 配置中心
config-server:
build: ./config-server
container_name: config-server
ports:
- "8888:8888"
environment:
- SPRING_PROFILES_ACTIVE=docker
- EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE=http://eureka-server:8761/eureka/
depends_on:
- eureka-server
networks:
- microservices-network
# API网关
api-gateway:
build: ./api-gateway
container_name: api-gateway
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=docker
- EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE=http://eureka-server:8761/eureka/
- SPRING_CLOUD_CONFIG_URI=http://config-server:8888
depends_on:
- eureka-server
- config-server
networks:
- microservices-network
# 用户服务
user-service:
build: ./user-service
container_name: user-service
ports:
- "8081:8081"
environment:
- SPRING_PROFILES_ACTIVE=docker
- EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE=http://eureka-server:8761/eureka/
- SPRING_CLOUD_CONFIG_URI=http://config-server:8888
- DATABASE_URL=jdbc:mysql://mysql:3306/userdb
- DATABASE_USERNAME=user
- DATABASE_PASSWORD=password
depends_on:
- eureka-server
- config-server
- mysql
networks:
- microservices-network
# 订单服务
order-service:
build: ./order-service
container_name: order-service
ports:
- "8082:8082"
environment:
- SPRING_PROFILES_ACTIVE=docker
- EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE=http://eureka-server:8761/eureka/
- SPRING_CLOUD_CONFIG_URI=http://config-server:8888
- DATABASE_URL=jdbc:mysql://mysql:3306/orderdb
- DATABASE_USERNAME=user
- DATABASE_PASSWORD=password
depends_on:
- eureka-server
- config-server
- mysql
networks:
- microservices-network
# MySQL数据库
mysql:
image: mysql:8.0
container_name: mysql
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=rootpassword
- MYSQL_DATABASE=userdb
- MYSQL_USER=user
- MYSQL_PASSWORD=password
volumes:
- mysql_data:/var/lib/mysql
- ./mysql/init:/docker-entrypoint-initdb.d
networks:
- microservices-network
# Redis缓存
redis:
image: redis:6.2-alpine
container_name: redis
ports:
- "6379:6379"
volumes:
- redis_data:/data
networks:
- microservices-network
# Zipkin链路追踪
zipkin:
image: openzipkin/zipkin:latest
container_name: zipkin
ports:
- "9411:9411"
environment:
- STORAGE_TYPE=mysql
- MYSQL_HOST=mysql
- MYSQL_USER=user
- MYSQL_PASS=password
- MYSQL_DB=zipkin
depends_on:
- mysql
networks:
- microservices-network
volumes:
mysql_data:
redis_data:
networks:
microservices-network:
driver: bridge
4.3 容器优化配置
应用配置优化:
yaml
# application-docker.yml
spring:
application:
name: user-service
profiles:
active: docker
datasource:
url: ${DATABASE_URL}
username: ${DATABASE_USERNAME}
password: ${DATABASE_PASSWORD}
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 10
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
jpa:
hibernate:
ddl-auto: update
show-sql: false
properties:
hibernate:
format_sql: true
use_sql_comments: true
redis:
host: ${REDIS_HOST:redis}
port: ${REDIS_PORT:6379}
timeout: 2000ms
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
server:
port: 8081
servlet:
context-path: /
tomcat:
threads:
max: 200
min-spare: 10
max-connections: 8192
accept-count: 100
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: when-authorized
metrics:
export:
prometheus:
enabled: true
logging:
level:
root: INFO
com.example: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level [%X{traceId:-},%X{spanId:-}] %logger{36} - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level [%X{traceId:-},%X{spanId:-}] %logger{36} - %msg%n"
file:
name: /app/logs/application.log
max-size: 100MB
max-history: 30
5. Kubernetes基础编排管理
5.1 Kubernetes基础概念
核心概念:
- Pod:Kubernetes的最小部署单元
- Service:服务发现和负载均衡
- Deployment:声明式更新管理
- ConfigMap:配置管理
- Secret:敏感信息管理
5.2 Kubernetes部署配置
用户服务Deployment:
yaml
# user-service-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
labels:
app: user-service
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: "k8s"
- name: EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE
value: "http://eureka-service:8761/eureka/"
- name: DATABASE_URL
value: "jdbc:mysql://mysql-service:3306/userdb"
- name: DATABASE_USERNAME
valueFrom:
secretKeyRef:
name: mysql-secret
key: username
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: password
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
livenessProbe:
httpGet:
path: /actuator/health
port: 8081
initialDelaySeconds: 60
periodSeconds: 30
readinessProbe:
httpGet:
path: /actuator/health
port: 8081
initialDelaySeconds: 30
periodSeconds: 10
volumeMounts:
- name: config-volume
mountPath: /app/config
volumes:
- name: config-volume
configMap:
name: user-service-config
用户服务Service:
yaml
# user-service-service.yaml
apiVersion: v1
kind: Service
metadata:
name: user-service
labels:
app: user-service
spec:
selector:
app: user-service
ports:
- protocol: TCP
port: 8081
targetPort: 8081
type: ClusterIP
ConfigMap配置:
yaml
# user-service-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: user-service-config
data:
application.yml: |
spring:
application:
name: user-service
datasource:
url: jdbc:mysql://mysql-service:3306/userdb
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 10
minimum-idle: 5
jpa:
hibernate:
ddl-auto: update
show-sql: false
redis:
host: redis-service
port: 6379
server:
port: 8081
management:
endpoints:
web:
exposure:
include: health,info,metrics
Secret配置:
yaml
# mysql-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: mysql-secret
type: Opaque
data:
username: dXNlcg== # base64 encoded 'user'
password: cGFzc3dvcmQ= # base64 encoded 'password'
5.3 Ingress配置
API网关Ingress:
yaml
# api-gateway-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-gateway-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "false"
nginx.ingress.kubernetes.io/rate-limit: "100"
nginx.ingress.kubernetes.io/rate-limit-window: "1m"
spec:
rules:
- host: api.example.com
http:
paths:
- path: /api/users
pathType: Prefix
backend:
service:
name: user-service
port:
number: 8081
- path: /api/orders
pathType: Prefix
backend:
service:
name: order-service
port:
number: 8082
- path: /api/notifications
pathType: Prefix
backend:
service:
name: notification-service
port:
number: 8083
6. 学习总结与练习
6.1 核心知识点回顾
-
Spring Cloud Gateway
- 现代API网关架构设计
- 路由、断言、过滤器机制
- 自定义过滤器和动态路由
- 限流、熔断、重试策略
-
分布式链路追踪
- Sleuth链路追踪原理
- Zipkin集成和配置
- 自定义Span和标签
- 业务链路监控
-
微服务安全认证
- JWT令牌认证机制
- Spring Security配置
- 微服务间安全通信
- 权限控制和授权
-
Docker容器化
- 多阶段构建优化
- Docker Compose编排
- 容器健康检查
- 镜像优化策略
-
Kubernetes编排
- Pod、Service、Deployment概念
- ConfigMap和Secret管理
- Ingress网络配置
- 资源限制和健康检查
6.2 实践练习
练习1:API网关开发
- 实现自定义认证过滤器
- 配置动态路由管理
- 实现限流和熔断功能
- 添加请求日志和监控
练习2:链路追踪集成
- 配置Sleuth和Zipkin
- 实现业务链路追踪
- 添加自定义Span和标签
- 分析链路性能瓶颈
练习3:微服务安全
- 实现JWT认证机制
- 配置微服务间安全通信
- 实现基于角色的权限控制
- 添加安全审计日志
练习4:容器化部署
- 编写优化的Dockerfile
- 配置Docker Compose编排
- 实现容器健康检查
- 优化镜像大小和启动时间
练习5:Kubernetes部署
- 编写Kubernetes部署文件
- 配置Service和Ingress
- 实现配置和密钥管理
- 设置资源限制和监控
6.3 学习建议
- 理论学习:深入理解云原生架构和容器编排原理
- 实践操作:通过实际项目练习容器化和K8s部署
- 监控运维:掌握分布式系统的监控和运维技能
- 安全防护:了解微服务安全最佳实践
- 性能优化:学习容器和K8s性能调优方法
7. 明日预告
第25天将学习:
- Spring Cloud Alibaba:阿里巴巴微服务解决方案
- Nacos:服务注册发现和配置管理
- Sentinel:流量控制和熔断降级
- Seata:分布式事务解决方案
- RocketMQ:消息队列中间件