学习目标
- 掌握云原生应用的核心概念和设计原则
- 学习Docker容器化技术
- 掌握Kubernetes容器编排
- 了解微服务在云环境中的部署策略
- 学习服务网格(Service Mesh)技术
- 掌握云原生监控和日志管理
一、云原生基础概念
1. 云原生应用特征
java
复制代码
// 1. 云原生应用配置
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
@EnableConfigServer
public class CloudNativeApplication {
public static void main(String[] args) {
SpringApplication.run(CloudNativeApplication.class, args);
}
// 健康检查端点
@GetMapping("/health")
public ResponseEntity<Map<String, Object>> health() {
Map<String, Object> health = new HashMap<>();
health.put("status", "UP");
health.put("timestamp", Instant.now());
health.put("version", getClass().getPackage().getImplementationVersion());
return ResponseEntity.ok(health);
}
// 就绪检查端点
@GetMapping("/ready")
public ResponseEntity<Map<String, Object>> ready() {
Map<String, Object> ready = new HashMap<>();
ready.put("status", "READY");
ready.put("database", checkDatabaseConnection());
ready.put("redis", checkRedisConnection());
return ResponseEntity.ok(ready);
}
private String checkDatabaseConnection() {
try {
// 检查数据库连接
return "UP";
} catch (Exception e) {
return "DOWN";
}
}
private String checkRedisConnection() {
try {
// 检查Redis连接
return "UP";
} catch (Exception e) {
return "DOWN";
}
}
}
// 2. 配置外部化
@Configuration
@ConfigurationProperties(prefix = "app")
@Data
public class AppConfig {
private String name;
private String version;
private String environment;
private Database database;
private Redis redis;
private Security security;
@Data
public static class Database {
private String url;
private String username;
private String password;
private int maxConnections;
private int minConnections;
}
@Data
public static class Redis {
private String host;
private int port;
private String password;
private int timeout;
}
@Data
public static class Security {
private String jwtSecret;
private long jwtExpiration;
private boolean corsEnabled;
}
}
// 3. 环境特定配置
@Profile("dev")
@Configuration
public class DevConfig {
@Bean
public DataSource devDataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:h2:mem:testdb");
config.setUsername("sa");
config.setPassword("");
config.setMaximumPoolSize(5);
return new HikariDataSource(config);
}
}
@Profile("prod")
@Configuration
public class ProdConfig {
@Bean
public DataSource prodDataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(System.getenv("DATABASE_URL"));
config.setUsername(System.getenv("DATABASE_USERNAME"));
config.setPassword(System.getenv("DATABASE_PASSWORD"));
config.setMaximumPoolSize(20);
config.setMinimumIdle(5);
return new HikariDataSource(config);
}
}
2. 12-Factor应用原则
java
复制代码
// 1. 配置管理
@Component
public class ConfigurationManager {
@Value("${app.name:default-app}")
private String appName;
@Value("${app.version:1.0.0}")
private String appVersion;
@Value("${server.port:8080}")
private int serverPort;
@Value("${spring.profiles.active:default}")
private String activeProfile;
public String getAppName() {
return appName;
}
public String getAppVersion() {
return appVersion;
}
public int getServerPort() {
return serverPort;
}
public String getActiveProfile() {
return activeProfile;
}
// 从环境变量读取配置
public String getDatabaseUrl() {
return System.getenv("DATABASE_URL");
}
public String getRedisUrl() {
return System.getenv("REDIS_URL");
}
}
// 2. 日志管理
@Component
@Slf4j
public class LoggingService {
@PostConstruct
public void init() {
// 配置日志格式
System.setProperty("logging.pattern.console",
"%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level [%X{traceId}] %logger{36} - %msg%n");
}
public void logInfo(String message, Object... args) {
log.info(message, args);
}
public void logError(String message, Throwable throwable) {
log.error(message, throwable);
}
public void logWithTraceId(String traceId, String message, Object... args) {
MDC.put("traceId", traceId);
log.info(message, args);
MDC.remove("traceId");
}
}
// 3. 进程管理
@Component
public class ProcessManager {
@Autowired
private ApplicationContext applicationContext;
@EventListener
public void handleContextClosedEvent(ContextClosedEvent event) {
log.info("Application is shutting down...");
// 清理资源
cleanup();
}
@PreDestroy
public void cleanup() {
log.info("Cleaning up resources...");
// 关闭数据库连接池
// 关闭Redis连接
// 清理临时文件
}
}
二、Docker容器化
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
# 暴露端口
EXPOSE 8080
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
# 启动应用
ENTRYPOINT ["java", "-jar", "app.jar"]
2. Docker Compose配置
yaml
复制代码
# docker-compose.yml
version: '3.8'
services:
# 应用服务
app:
build: .
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=docker
- DATABASE_URL=jdbc:postgresql://postgres:5432/appdb
- REDIS_URL=redis://redis:6379
depends_on:
- postgres
- redis
networks:
- app-network
volumes:
- ./logs:/app/logs
restart: unless-stopped
# 数据库服务
postgres:
image: postgres:13-alpine
environment:
- POSTGRES_DB=appdb
- POSTGRES_USER=appuser
- POSTGRES_PASSWORD=apppass
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "5432:5432"
networks:
- app-network
restart: unless-stopped
# Redis服务
redis:
image: redis:6-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
networks:
- app-network
restart: unless-stopped
# Nginx反向代理
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
depends_on:
- app
networks:
- app-network
restart: unless-stopped
volumes:
postgres_data:
redis_data:
networks:
app-network:
driver: bridge
3. Docker化Spring Boot应用
java
复制代码
// 1. Docker配置类
@Configuration
@EnableConfigurationProperties
public class DockerConfig {
@Bean
@ConditionalOnProperty(name = "app.docker.enabled", havingValue = "true")
public DockerInfoContributor dockerInfoContributor() {
return new DockerInfoContributor();
}
@Bean
public HealthIndicator dockerHealthIndicator() {
return new DockerHealthIndicator();
}
}
// 2. Docker信息贡献者
@Component
public class DockerInfoContributor implements InfoContributor {
@Override
public void contribute(Info.Builder builder) {
Map<String, Object> dockerInfo = new HashMap<>();
dockerInfo.put("containerId", getContainerId());
dockerInfo.put("image", getImageName());
dockerInfo.put("version", getImageVersion());
dockerInfo.put("environment", System.getenv("ENVIRONMENT"));
builder.withDetail("docker", dockerInfo);
}
private String getContainerId() {
return System.getenv("HOSTNAME");
}
private String getImageName() {
return System.getenv("IMAGE_NAME");
}
private String getImageVersion() {
return System.getenv("IMAGE_VERSION");
}
}
// 3. Docker健康检查
@Component
public class DockerHealthIndicator implements HealthIndicator {
@Override
public Health health() {
try {
// 检查容器资源使用情况
long memoryUsed = getMemoryUsed();
long memoryTotal = getMemoryTotal();
double memoryUsage = (double) memoryUsed / memoryTotal;
if (memoryUsage > 0.9) {
return Health.down()
.withDetail("memory", "High memory usage: " + (memoryUsage * 100) + "%")
.build();
}
return Health.up()
.withDetail("memory", "Memory usage: " + (memoryUsage * 100) + "%")
.withDetail("containerId", getContainerId())
.build();
} catch (Exception e) {
return Health.down()
.withDetail("error", e.getMessage())
.build();
}
}
private long getMemoryUsed() {
// 获取内存使用量
return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
}
private long getMemoryTotal() {
// 获取总内存
return Runtime.getRuntime().maxMemory();
}
private String getContainerId() {
return System.getenv("HOSTNAME");
}
}
三、Kubernetes部署
1. Kubernetes配置文件
yaml
复制代码
# k8s/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: microservices
labels:
name: microservices
---
# k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: microservices
data:
application.yml: |
spring:
profiles:
active: k8s
datasource:
url: jdbc:postgresql://postgres-service:5432/appdb
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
redis:
host: redis-service
port: 6379
password: ${REDIS_PASSWORD}
app:
name: microservices-app
version: 1.0.0
environment: k8s
---
# k8s/secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
namespace: microservices
type: Opaque
data:
DB_USERNAME: YXBwdXNlcg== # appuser
DB_PASSWORD: YXBwcGFzcw== # apppass
REDIS_PASSWORD: cmVkaXNwYXNz # redispass
JWT_SECRET: bXlqd3RzZWNyZXQ= # myjwtsecret
---
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-deployment
namespace: microservices
labels:
app: microservices-app
spec:
replicas: 3
selector:
matchLabels:
app: microservices-app
template:
metadata:
labels:
app: microservices-app
spec:
containers:
- name: app
image: microservices-app:latest
ports:
- containerPort: 8080
env:
- name: SPRING_PROFILES_ACTIVE
value: "k8s"
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: app-secrets
key: DB_USERNAME
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: app-secrets
key: DB_PASSWORD
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: app-secrets
key: REDIS_PASSWORD
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: app-secrets
key: JWT_SECRET
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
livenessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
volumeMounts:
- name: config-volume
mountPath: /app/config
- name: logs-volume
mountPath: /app/logs
volumes:
- name: config-volume
configMap:
name: app-config
- name: logs-volume
emptyDir: {}
imagePullSecrets:
- name: regcred
---
# k8s/service.yaml
apiVersion: v1
kind: Service
metadata:
name: app-service
namespace: microservices
spec:
selector:
app: microservices-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: ClusterIP
---
# k8s/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
namespace: microservices
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "true"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
tls:
- hosts:
- api.example.com
secretName: app-tls
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app-service
port:
number: 80
2. Kubernetes服务发现
java
复制代码
// 1. Kubernetes服务发现
@Component
public class KubernetesServiceDiscovery {
@Autowired
private DiscoveryClient discoveryClient;
public List<ServiceInstance> getServiceInstances(String serviceName) {
return discoveryClient.getInstances(serviceName);
}
public String getServiceUrl(String serviceName) {
List<ServiceInstance> instances = getServiceInstances(serviceName);
if (instances.isEmpty()) {
throw new ServiceNotFoundException("No instances found for service: " + serviceName);
}
ServiceInstance instance = instances.get(0);
return instance.getUri().toString();
}
public List<String> getAllServiceNames() {
return discoveryClient.getServices();
}
}
// 2. Kubernetes配置
@Configuration
@EnableDiscoveryClient
public class KubernetesConfig {
@Bean
public KubernetesClient kubernetesClient() {
return new DefaultKubernetesClient();
}
@Bean
public ServiceDiscovery serviceDiscovery(KubernetesClient kubernetesClient) {
return new KubernetesServiceDiscovery(kubernetesClient);
}
}
// 3. 服务发现实现
public class KubernetesServiceDiscovery implements ServiceDiscovery {
private final KubernetesClient kubernetesClient;
public KubernetesServiceDiscovery(KubernetesClient kubernetesClient) {
this.kubernetesClient = kubernetesClient;
}
@Override
public List<ServiceInstance> getInstances(String serviceName) {
try {
Service service = kubernetesClient.services()
.inNamespace("microservices")
.withName(serviceName)
.get();
if (service == null) {
return Collections.emptyList();
}
List<ServiceInstance> instances = new ArrayList<>();
String namespace = service.getMetadata().getNamespace();
String serviceName = service.getMetadata().getName();
// 获取Endpoints
Endpoints endpoints = kubernetesClient.endpoints()
.inNamespace(namespace)
.withName(serviceName)
.get();
if (endpoints != null && endpoints.getSubsets() != null) {
for (EndpointSubset subset : endpoints.getSubsets()) {
for (EndpointAddress address : subset.getAddresses()) {
for (EndpointPort port : subset.getPorts()) {
ServiceInstance instance = new ServiceInstance() {
@Override
public String getServiceId() {
return serviceName;
}
@Override
public String getHost() {
return address.getIp();
}
@Override
public int getPort() {
return port.getPort();
}
@Override
public boolean isSecure() {
return false;
}
@Override
public URI getUri() {
try {
return new URI("http://" + getHost() + ":" + getPort());
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
@Override
public Map<String, String> getMetadata() {
return Collections.emptyMap();
}
};
instances.add(instance);
}
}
}
}
return instances;
} catch (Exception e) {
log.error("Failed to discover service: " + serviceName, e);
return Collections.emptyList();
}
}
}
四、服务网格(Service Mesh)
1. Istio配置
yaml
复制代码
# istio/virtual-service.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: app-virtual-service
namespace: microservices
spec:
hosts:
- app-service
http:
- match:
- headers:
user-type:
exact: premium
route:
- destination:
host: app-service
subset: premium
fault:
delay:
percentage:
value: 0.1
fixedDelay: 5s
- route:
- destination:
host: app-service
subset: standard
retries:
attempts: 3
perTryTimeout: 2s
timeout: 10s
---
# istio/destination-rule.yaml
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: app-destination-rule
namespace: microservices
spec:
host: app-service
subsets:
- name: premium
labels:
version: premium
- name: standard
labels:
version: standard
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
http1MaxPendingRequests: 50
maxRequestsPerConnection: 10
circuitBreaker:
consecutiveErrors: 3
interval: 30s
baseEjectionTime: 30s
maxEjectionPercent: 50
loadBalancer:
simple: ROUND_ROBIN
---
# istio/gateway.yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: app-gateway
namespace: microservices
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- api.example.com
tls:
httpsRedirect: true
- port:
number: 443
name: https
protocol: HTTPS
hosts:
- api.example.com
tls:
mode: SIMPLE
credentialName: app-tls
2. 服务网格集成
java
复制代码
// 1. Istio服务网格集成
@Configuration
@EnableIstio
public class IstioConfig {
@Bean
public IstioServiceMesh istioServiceMesh() {
return new IstioServiceMesh();
}
@Bean
public TrafficManagement trafficManagement() {
return new TrafficManagement();
}
}
// 2. 流量管理
@Component
public class TrafficManagement {
@Autowired
private IstioServiceMesh istioServiceMesh;
public void configureTrafficSplit(String serviceName, Map<String, Integer> versionWeights) {
try {
// 配置流量分割
VirtualService virtualService = new VirtualService();
virtualService.setMetadata(new ObjectMeta());
virtualService.getMetadata().setName(serviceName + "-virtual-service");
virtualService.getMetadata().setNamespace("microservices");
VirtualServiceSpec spec = new VirtualServiceSpec();
spec.setHosts(Arrays.asList(serviceName));
List<HTTPRoute> httpRoutes = new ArrayList<>();
HTTPRoute httpRoute = new HTTPRoute();
List<HTTPRouteDestination> destinations = new ArrayList<>();
for (Map.Entry<String, Integer> entry : versionWeights.entrySet()) {
HTTPRouteDestination destination = new HTTPRouteDestination();
destination.setDestination(new Destination(serviceName, entry.getKey()));
destination.setWeight(entry.getValue());
destinations.add(destination);
}
httpRoute.setRoute(destinations);
httpRoutes.add(httpRoute);
spec.setHttp(httpRoutes);
virtualService.setSpec(spec);
istioServiceMesh.createVirtualService(virtualService);
} catch (Exception e) {
log.error("Failed to configure traffic split for service: " + serviceName, e);
}
}
public void configureCircuitBreaker(String serviceName, CircuitBreakerConfig config) {
try {
// 配置熔断器
DestinationRule destinationRule = new DestinationRule();
destinationRule.setMetadata(new ObjectMeta());
destinationRule.getMetadata().setName(serviceName + "-destination-rule");
destinationRule.getMetadata().setNamespace("microservices");
DestinationRuleSpec spec = new DestinationRuleSpec();
spec.setHost(serviceName);
TrafficPolicy trafficPolicy = new TrafficPolicy();
CircuitBreaker circuitBreaker = new CircuitBreaker();
circuitBreaker.setConsecutiveErrors(config.getConsecutiveErrors());
circuitBreaker.setInterval(config.getInterval());
circuitBreaker.setBaseEjectionTime(config.getBaseEjectionTime());
circuitBreaker.setMaxEjectionPercent(config.getMaxEjectionPercent());
trafficPolicy.setCircuitBreaker(circuitBreaker);
spec.setTrafficPolicy(trafficPolicy);
destinationRule.setSpec(spec);
istioServiceMesh.createDestinationRule(destinationRule);
} catch (Exception e) {
log.error("Failed to configure circuit breaker for service: " + serviceName, e);
}
}
}
// 3. 服务网格监控
@Component
public class ServiceMeshMonitoring {
@Autowired
private IstioServiceMesh istioServiceMesh;
@Scheduled(fixedRate = 30000) // 每30秒检查一次
public void monitorServiceHealth() {
try {
// 获取服务健康状态
Map<String, ServiceHealth> serviceHealths = istioServiceMesh.getServiceHealths();
for (Map.Entry<String, ServiceHealth> entry : serviceHealths.entrySet()) {
String serviceName = entry.getKey();
ServiceHealth health = entry.getValue();
if (health.getStatus() == ServiceHealth.Status.UNHEALTHY) {
log.warn("Service {} is unhealthy: {}", serviceName, health.getMessage());
// 发送告警
sendAlert(serviceName, health);
}
}
} catch (Exception e) {
log.error("Failed to monitor service health", e);
}
}
private void sendAlert(String serviceName, ServiceHealth health) {
// 发送告警逻辑
log.error("ALERT: Service {} is unhealthy - {}", serviceName, health.getMessage());
}
}
五、云原生监控
1. Prometheus监控
java
复制代码
// 1. Prometheus配置
@Configuration
@EnablePrometheusMetrics
public class PrometheusConfig {
@Bean
public MeterRegistry meterRegistry() {
return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
}
@Bean
public TimedAspect timedAspect(MeterRegistry registry) {
return new TimedAspect(registry);
}
}
// 2. 自定义指标
@Component
public class CustomMetrics {
private final Counter requestCounter;
private final Timer requestTimer;
private final Gauge activeConnections;
private final Counter errorCounter;
public CustomMetrics(MeterRegistry meterRegistry) {
this.requestCounter = Counter.builder("http_requests_total")
.description("Total HTTP requests")
.tag("application", "microservices-app")
.register(meterRegistry);
this.requestTimer = Timer.builder("http_request_duration_seconds")
.description("HTTP request duration")
.tag("application", "microservices-app")
.register(meterRegistry);
this.activeConnections = Gauge.builder("active_connections")
.description("Active database connections")
.tag("application", "microservices-app")
.register(meterRegistry, this, CustomMetrics::getActiveConnections);
this.errorCounter = Counter.builder("http_errors_total")
.description("Total HTTP errors")
.tag("application", "microservices-app")
.register(meterRegistry);
}
public void incrementRequestCounter(String method, String endpoint) {
requestCounter.increment(Tags.of("method", method, "endpoint", endpoint));
}
public void recordRequestDuration(String method, String endpoint, Duration duration) {
requestTimer.record(duration, Tags.of("method", method, "endpoint", endpoint));
}
public void incrementErrorCounter(String method, String endpoint, String errorType) {
errorCounter.increment(Tags.of("method", method, "endpoint", endpoint, "error_type", errorType));
}
private double getActiveConnections() {
// 获取活跃连接数
return 10.0; // 示例值
}
}
// 3. 监控切面
@Aspect
@Component
public class MonitoringAspect {
@Autowired
private CustomMetrics customMetrics;
@Around("@annotation(org.springframework.web.bind.annotation.GetMapping) || " +
"@annotation(org.springframework.web.bind.annotation.PostMapping) || " +
"@annotation(org.springframework.web.bind.annotation.PutMapping) || " +
"@annotation(org.springframework.web.bind.annotation.DeleteMapping)")
public Object monitorRequest(ProceedingJoinPoint joinPoint) throws Throwable {
String method = getHttpMethod(joinPoint);
String endpoint = getEndpoint(joinPoint);
long startTime = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
customMetrics.incrementRequestCounter(method, endpoint);
return result;
} catch (Exception e) {
customMetrics.incrementErrorCounter(method, endpoint, e.getClass().getSimpleName());
throw e;
} finally {
long duration = System.currentTimeMillis() - startTime;
customMetrics.recordRequestDuration(method, endpoint, Duration.ofMillis(duration));
}
}
private String getHttpMethod(ProceedingJoinPoint joinPoint) {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
if (method.isAnnotationPresent(GetMapping.class)) return "GET";
if (method.isAnnotationPresent(PostMapping.class)) return "POST";
if (method.isAnnotationPresent(PutMapping.class)) return "PUT";
if (method.isAnnotationPresent(DeleteMapping.class)) return "DELETE";
return "UNKNOWN";
}
private String getEndpoint(ProceedingJoinPoint joinPoint) {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
if (method.isAnnotationPresent(RequestMapping.class)) {
RequestMapping mapping = method.getAnnotation(RequestMapping.class);
return mapping.value().length > 0 ? mapping.value()[0] : "/";
}
return "/";
}
}
2. Grafana仪表板
json
复制代码
{
"dashboard": {
"id": null,
"title": "Microservices Dashboard",
"tags": ["microservices", "spring-boot"],
"timezone": "browser",
"panels": [
{
"id": 1,
"title": "HTTP Requests",
"type": "graph",
"targets": [
{
"expr": "rate(http_requests_total[5m])",
"legendFormat": "{{method}} {{endpoint}}"
}
],
"yAxes": [
{
"label": "Requests/sec"
}
]
},
{
"id": 2,
"title": "Response Time",
"type": "graph",
"targets": [
{
"expr": "histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))",
"legendFormat": "95th percentile"
},
{
"expr": "histogram_quantile(0.50, rate(http_request_duration_seconds_bucket[5m]))",
"legendFormat": "50th percentile"
}
],
"yAxes": [
{
"label": "Response Time (s)"
}
]
},
{
"id": 3,
"title": "Error Rate",
"type": "graph",
"targets": [
{
"expr": "rate(http_errors_total[5m])",
"legendFormat": "{{error_type}}"
}
],
"yAxes": [
{
"label": "Errors/sec"
}
]
},
{
"id": 4,
"title": "Active Connections",
"type": "singlestat",
"targets": [
{
"expr": "active_connections",
"legendFormat": "Active Connections"
}
],
"valueName": "current"
}
],
"time": {
"from": "now-1h",
"to": "now"
},
"refresh": "30s"
}
}
六、日志管理
1. 结构化日志
java
复制代码
// 1. 日志配置
@Configuration
@EnableLogging
public class LoggingConfig {
@Bean
public LoggerContext loggerContext() {
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
return context;
}
@Bean
public PatternLayoutEncoder patternLayoutEncoder() {
PatternLayoutEncoder encoder = new PatternLayoutEncoder();
encoder.setPattern("%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level [%X{traceId}] %logger{36} - %msg%n");
encoder.setContext(loggerContext());
encoder.start();
return encoder;
}
}
// 2. 结构化日志服务
@Service
@Slf4j
public class StructuredLoggingService {
@Autowired
private ObjectMapper objectMapper;
public void logRequest(String traceId, String method, String endpoint, Map<String, Object> parameters) {
Map<String, Object> logData = new HashMap<>();
logData.put("traceId", traceId);
logData.put("event", "request");
logData.put("method", method);
logData.put("endpoint", endpoint);
logData.put("parameters", parameters);
logData.put("timestamp", Instant.now());
try {
String jsonLog = objectMapper.writeValueAsString(logData);
log.info(jsonLog);
} catch (Exception e) {
log.error("Failed to serialize log data", e);
}
}
public void logResponse(String traceId, String method, String endpoint, int statusCode, long duration) {
Map<String, Object> logData = new HashMap<>();
logData.put("traceId", traceId);
logData.put("event", "response");
logData.put("method", method);
logData.put("endpoint", endpoint);
logData.put("statusCode", statusCode);
logData.put("duration", duration);
logData.put("timestamp", Instant.now());
try {
String jsonLog = objectMapper.writeValueAsString(logData);
log.info(jsonLog);
} catch (Exception e) {
log.error("Failed to serialize log data", e);
}
}
public void logError(String traceId, String method, String endpoint, Throwable throwable) {
Map<String, Object> logData = new HashMap<>();
logData.put("traceId", traceId);
logData.put("event", "error");
logData.put("method", method);
logData.put("endpoint", endpoint);
logData.put("error", throwable.getMessage());
logData.put("stackTrace", getStackTrace(throwable));
logData.put("timestamp", Instant.now());
try {
String jsonLog = objectMapper.writeValueAsString(logData);
log.error(jsonLog);
} catch (Exception e) {
log.error("Failed to serialize log data", e);
}
}
private String getStackTrace(Throwable throwable) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
throwable.printStackTrace(pw);
return sw.toString();
}
}
// 3. 日志切面
@Aspect
@Component
public class LoggingAspect {
@Autowired
private StructuredLoggingService loggingService;
@Around("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
public Object logRequest(ProceedingJoinPoint joinPoint) throws Throwable {
String traceId = generateTraceId();
MDC.put("traceId", traceId);
String method = getHttpMethod(joinPoint);
String endpoint = getEndpoint(joinPoint);
Map<String, Object> parameters = getParameters(joinPoint);
loggingService.logRequest(traceId, method, endpoint, parameters);
long startTime = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
long duration = System.currentTimeMillis() - startTime;
loggingService.logResponse(traceId, method, endpoint, 200, duration);
return result;
} catch (Exception e) {
loggingService.logError(traceId, method, endpoint, e);
throw e;
} finally {
MDC.remove("traceId");
}
}
private String generateTraceId() {
return UUID.randomUUID().toString();
}
private String getHttpMethod(ProceedingJoinPoint joinPoint) {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
if (method.isAnnotationPresent(GetMapping.class)) return "GET";
if (method.isAnnotationPresent(PostMapping.class)) return "POST";
if (method.isAnnotationPresent(PutMapping.class)) return "PUT";
if (method.isAnnotationPresent(DeleteMapping.class)) return "DELETE";
return "UNKNOWN";
}
private String getEndpoint(ProceedingJoinPoint joinPoint) {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
if (method.isAnnotationPresent(RequestMapping.class)) {
RequestMapping mapping = method.getAnnotation(RequestMapping.class);
return mapping.value().length > 0 ? mapping.value()[0] : "/";
}
return "/";
}
private Map<String, Object> getParameters(ProceedingJoinPoint joinPoint) {
Map<String, Object> parameters = new HashMap<>();
Object[] args = joinPoint.getArgs();
String[] paramNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
for (int i = 0; i < args.length; i++) {
parameters.put(paramNames[i], args[i]);
}
return parameters;
}
}
2. ELK日志收集
yaml
复制代码
# docker-compose-elk.yml
version: '3.8'
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.15.0
environment:
- discovery.type=single-node
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ports:
- "9200:9200"
volumes:
- elasticsearch_data:/usr/share/elasticsearch/data
networks:
- elk
logstash:
image: docker.elastic.co/logstash/logstash:7.15.0
ports:
- "5044:5044"
volumes:
- ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf
depends_on:
- elasticsearch
networks:
- elk
kibana:
image: docker.elastic.co/kibana/kibana:7.15.0
ports:
- "5601:5601"
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
depends_on:
- elasticsearch
networks:
- elk
filebeat:
image: docker.elastic.co/beats/filebeat:7.15.0
volumes:
- ./filebeat.yml:/usr/share/filebeat/filebeat.yml
- ./logs:/usr/share/filebeat/logs
depends_on:
- logstash
networks:
- elk
volumes:
elasticsearch_data:
networks:
elk:
driver: bridge
七、综合实战练习
1. 云原生微服务项目
java
复制代码
// 1. 云原生应用主类
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
@EnableConfigServer
@EnablePrometheusMetrics
public class CloudNativeMicroservicesApplication {
public static void main(String[] args) {
SpringApplication.run(CloudNativeMicroservicesApplication.class, args);
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
public WebClient webClient() {
return WebClient.builder()
.baseUrl("http://localhost:8080")
.build();
}
}
// 2. 云原生配置
@Configuration
@EnableConfigurationProperties
public class CloudNativeConfig {
@Bean
@ConfigurationProperties(prefix = "app.cloud")
public CloudConfig cloudConfig() {
return new CloudConfig();
}
@Bean
public CloudNativeService cloudNativeService(CloudConfig cloudConfig) {
return new CloudNativeService(cloudConfig);
}
}
// 3. 云原生服务
@Service
@Slf4j
public class CloudNativeService {
private final CloudConfig cloudConfig;
private final CustomMetrics customMetrics;
private final StructuredLoggingService loggingService;
public CloudNativeService(CloudConfig cloudConfig,
CustomMetrics customMetrics,
StructuredLoggingService loggingService) {
this.cloudConfig = cloudConfig;
this.customMetrics = customMetrics;
this.loggingService = loggingService;
}
public String processRequest(String request) {
String traceId = generateTraceId();
MDC.put("traceId", traceId);
try {
loggingService.logRequest(traceId, "POST", "/process",
Map.of("request", request));
// 处理请求
String response = doProcess(request);
loggingService.logResponse(traceId, "POST", "/process", 200, 100);
customMetrics.incrementRequestCounter("POST", "/process");
return response;
} catch (Exception e) {
loggingService.logError(traceId, "POST", "/process", e);
customMetrics.incrementErrorCounter("POST", "/process", e.getClass().getSimpleName());
throw e;
} finally {
MDC.remove("traceId");
}
}
private String doProcess(String request) {
// 模拟处理逻辑
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "Processed: " + request;
}
private String generateTraceId() {
return UUID.randomUUID().toString();
}
}
2. 部署脚本
bash
复制代码
#!/bin/bash
# deploy.sh - 云原生应用部署脚本
set -e
# 配置变量
APP_NAME="microservices-app"
VERSION="1.0.0"
NAMESPACE="microservices"
REGISTRY="your-registry.com"
echo "开始部署云原生应用..."
# 1. 构建Docker镜像
echo "构建Docker镜像..."
docker build -t ${REGISTRY}/${APP_NAME}:${VERSION} .
docker push ${REGISTRY}/${APP_NAME}:${VERSION}
# 2. 部署到Kubernetes
echo "部署到Kubernetes..."
kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/configmap.yaml
kubectl apply -f k8s/secret.yaml
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml
kubectl apply -f k8s/ingress.yaml
# 3. 等待部署完成
echo "等待部署完成..."
kubectl wait --for=condition=available --timeout=300s deployment/${APP_NAME}-deployment -n ${NAMESPACE}
# 4. 检查部署状态
echo "检查部署状态..."
kubectl get pods -n ${NAMESPACE}
kubectl get services -n ${NAMESPACE}
kubectl get ingress -n ${NAMESPACE}
# 5. 运行健康检查
echo "运行健康检查..."
kubectl port-forward service/${APP_NAME}-service 8080:80 -n ${NAMESPACE} &
PORT_FORWARD_PID=$!
sleep 10
curl -f http://localhost:8080/actuator/health || {
echo "健康检查失败"
kill $PORT_FORWARD_PID
exit 1
}
kill $PORT_FORWARD_PID
echo "部署完成!"
echo "应用访问地址: http://api.example.com"
echo "监控地址: http://grafana.example.com"
echo "日志地址: http://kibana.example.com"
八、学习总结
1. 云原生核心概念
- 容器化: 使用Docker将应用打包成容器
- 编排: 使用Kubernetes管理容器生命周期
- 服务网格: 使用Istio管理服务间通信
- 监控: 使用Prometheus和Grafana监控应用
- 日志