JAVA 在 Kubernetes 集群中实现微服务自动伸缩的完整流程

Java 微服务在 Kubernetes 中的自动伸缩:一次深入的技术讲座

各位朋友,大家好!今天我们来聊聊如何使用 Java 微服务在 Kubernetes 集群中实现自动伸缩。这是一个非常热门的话题,也是构建高可用、可扩展云原生应用的关键。我们将从理论到实践,一步步地讲解如何配置和实现自动伸缩,并分享一些最佳实践。

1. 自动伸缩的意义与 Kubernetes 的核心概念

首先,我们来明确一下自动伸缩的重要性。在微服务架构中,不同的服务承担不同的职责,它们的负载也随时间变化。手动调整服务实例的数量既耗时又容易出错。自动伸缩可以根据实际负载动态地调整服务实例的数量,从而实现以下目标:

  • 提高资源利用率: 在负载较低时,减少实例数量,节省资源。
  • 提升系统性能: 在负载较高时,增加实例数量,保证响应速度。
  • 增强系统可用性: 即使部分实例故障,也能通过自动扩容来维持服务。
  • 降低运维成本: 自动化管理,减少人工干预。

在 Kubernetes 中,自动伸缩主要依赖以下几个核心概念:

  • Pod: Kubernetes 中最小的可部署单元,包含一个或多个容器。
  • Deployment: 用于管理 Pod 的声明式配置,负责创建、更新和维护 Pod 的副本。
  • Service: 用于暴露 Deployment 创建的 Pod,提供统一的访问入口。
  • Horizontal Pod Autoscaler (HPA): 核心组件,根据资源利用率自动调整 Deployment 的 Pod 副本数。
  • Metrics Server: 用于收集 Kubernetes 集群中 Pod 和 Node 的资源利用率指标。

2. 搭建基础环境:一个简单的 Java 微服务

为了演示自动伸缩,我们需要一个简单的 Java 微服务。这里,我们使用 Spring Boot 构建一个简单的 HTTP 服务,它返回一个 "Hello, World!" 字符串。

// pom.xml (部分)
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
</dependencies>
// src/main/java/com/example/demo/DemoApplication.java
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @RestController
    class HelloController {
        @GetMapping("/")
        public String hello() {
            return "Hello, World!";
        }
    }
}

这个服务非常简单,只有一个 / 接口返回 "Hello, World!"。 为了能够监控服务的资源使用情况,我们引入了 spring-boot-starter-actuator 依赖。

接下来,我们需要将这个服务打包成 Docker 镜像。

# Dockerfile
FROM openjdk:17-slim-buster

WORKDIR /app

COPY target/*.jar app.jar

EXPOSE 8080

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

构建镜像并推送到镜像仓库:

docker build -t your-dockerhub-username/demo-service:latest .
docker push your-dockerhub-username/demo-service:latest

请替换 your-dockerhub-username 为你的 Docker Hub 用户名。

3. 在 Kubernetes 中部署微服务

现在,我们将这个 Java 微服务部署到 Kubernetes 集群中。我们需要创建一个 Deployment 和一个 Service。

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-service
spec:
  replicas: 2  # 初始副本数
  selector:
    matchLabels:
      app: demo-service
  template:
    metadata:
      labels:
        app: demo-service
    spec:
      containers:
        - name: demo-service
          image: your-dockerhub-username/demo-service:latest
          ports:
            - containerPort: 8080
          resources:
            requests:
              cpu: 200m
              memory: 256Mi
            limits:
              cpu: 500m
              memory: 512Mi
# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: demo-service
spec:
  selector:
    app: demo-service
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080
  type: LoadBalancer

解释一下这两个 YAML 文件:

  • Deployment (deployment.yaml):
    • replicas: 2:指定初始的 Pod 副本数为 2。
    • selector:用于匹配 Deployment 管理的 Pod。
    • template:定义 Pod 的模板,包括容器镜像、端口、资源限制等。
    • resources: 定义了容器的资源请求和限制。requests 是 Kubernetes 调度器用于确定 Pod 能否被调度到某个节点上的最小资源需求。limits 是容器可以使用的最大资源量。
  • Service (service.yaml):
    • selector:用于匹配 Service 暴露的 Pod。
    • ports:定义 Service 的端口映射,将 Service 的 80 端口映射到 Pod 的 8080 端口。
    • type: LoadBalancer:创建一个外部负载均衡器,将流量分发到 Service 后面的 Pod。

应用这些配置:

kubectl apply -f deployment.yaml
kubectl apply -f service.yaml

等待一段时间,直到 Service 的 EXTERNAL-IP 地址可用。你可以使用 kubectl get service demo-service 命令查看 Service 的状态。

4. 配置 Horizontal Pod Autoscaler (HPA)

接下来,我们配置 HPA 来实现自动伸缩。HPA 会根据 Pod 的 CPU 利用率自动调整 Pod 副本数。

# hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: demo-service-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: demo-service
  minReplicas: 2  # 最小副本数
  maxReplicas: 5  # 最大副本数
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 50  # 目标 CPU 利用率

这个 YAML 文件定义了一个 HPA,它会监控 demo-service Deployment 的 CPU 利用率,并根据以下规则调整 Pod 副本数:

  • scaleTargetRef 指定 HPA 监控的目标对象,这里是 demo-service Deployment。
  • minReplicas 最小副本数为 2。
  • maxReplicas 最大副本数为 5。
  • metrics 定义监控指标。这里我们监控 CPU 利用率,目标是 50%。也就是说,HPA 会尽量维持 Pod 的 CPU 利用率在 50% 左右。如果 CPU 利用率超过 50%,HPA 会增加 Pod 副本数;如果 CPU 利用率低于 50%,HPA 会减少 Pod 副本数。

应用 HPA 配置:

kubectl apply -f hpa.yaml

5. 测试自动伸缩

现在,我们需要测试自动伸缩是否正常工作。我们可以使用 hey 工具模拟高并发请求,增加服务的 CPU 负载。

# 安装 hey 工具
go install github.com/rakyll/hey@latest

# 模拟高并发请求
hey -n 2000 -c 200 http://<EXTERNAL-IP>

请将 <EXTERNAL-IP> 替换为你的 Service 的 EXTERNAL-IP 地址。

运行一段时间后,你可以使用 kubectl get hpa demo-service-hpa 命令查看 HPA 的状态。你应该可以看到 DESIRED NUMBER OF PODS 逐渐增加,直到达到 maxReplicas

同时,你也可以使用 kubectl get pods 命令查看 Pod 的状态,你应该可以看到 Pod 的数量也在增加。

6. 深入理解 Metrics Server

HPA 依赖 Metrics Server 来获取 Pod 的资源利用率指标。Metrics Server 是一个轻量级的集群范围的资源使用情况数据聚合器。它从 kubelet 中收集资源指标,并将这些数据通过 Kubernetes API 提供给 HPA 和其他组件使用。

在大多数 Kubernetes 集群中,Metrics Server 已经默认安装。如果没有安装,你需要手动安装。你可以使用以下命令安装 Metrics Server:

kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

安装完成后,你可以使用 kubectl top nodekubectl top pod 命令查看节点和 Pod 的资源利用率。

7. 自定义指标自动伸缩 (Custom Metrics)

除了 CPU 和内存利用率,HPA 还支持基于自定义指标进行自动伸缩。自定义指标可以来自各种来源,例如 Prometheus、CloudWatch 等。

为了演示自定义指标自动伸缩,我们需要以下步骤:

  1. 暴露自定义指标: 在 Java 微服务中暴露自定义指标,例如每秒请求数 (RPS)。
  2. 部署 Metrics Adapter: 部署一个 Metrics Adapter,将自定义指标转换为 Kubernetes 可以理解的格式。
  3. 配置 HPA: 配置 HPA 使用自定义指标进行自动伸缩。

7.1 暴露自定义指标

我们可以使用 Micrometer 库来暴露自定义指标。Micrometer 是一个 Java 应用程序指标的简单 facade,它支持多种监控系统,例如 Prometheus、InfluxDB 等。

首先,添加 Micrometer 和 Prometheus 的依赖:

<!-- pom.xml -->
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-core</artifactId>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

然后,在 HelloController 中添加一个计数器,记录每秒请求数:

// src/main/java/com/example/demo/DemoApplication.java
package com.example.demo;

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @RestController
    class HelloController {

        private final Counter helloCounter;

        public HelloController(MeterRegistry registry) {
            this.helloCounter = Counter.builder("hello.requests")
                    .description("Number of hello requests")
                    .register(registry);
        }

        @GetMapping("/")
        public String hello() {
            helloCounter.increment();
            return "Hello, World!";
        }
    }

    @Bean
    public io.micrometer.core.instrument.MeterRegistryCustomizer<io.micrometer.core.instrument.MeterRegistry> meterRegistryCustomizer() {
        return registry -> registry.config().commonTags("application", "demo-service");
    }
}

最后,在 application.properties 文件中配置 Prometheus 端点:

# application.properties
management.endpoints.web.exposure.include=*
management.metrics.export.prometheus.enabled=true
management.server.port=8081 #Prometheus 需要单独的端口,避免与应用端口冲突

重新构建 Docker 镜像并推送到镜像仓库。

7.2 部署 Metrics Adapter

我们需要部署一个 Metrics Adapter,将 Prometheus 收集的指标转换为 Kubernetes 可以理解的格式。这里我们使用 prometheus-adapter

# 安装 prometheus-adapter
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm install prometheus-adapter prometheus-community/prometheus-adapter --set prometheus.url=http://prometheus.default.svc --set prometheus.port=9090 --set rules.default=false

你需要先安装 Prometheus。上面的命令假设 Prometheus 已经在 default 命名空间中运行,并且可以通过 http://prometheus.default.svc:9090 访问。

然后,我们需要创建一个配置规则,告诉 prometheus-adapter 如何将 Prometheus 指标转换为 Kubernetes API 对象。

# adapter-config.yaml
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
  name: v1beta1.custom.metrics.k8s.io
spec:
  group: custom.metrics.k8s.io
  groupPriorityMinimum: 100
  insecureSkipTLSVerify: true
  service:
    name: prometheus-adapter
    namespace: default # 修改为 prometheus-adapter 所在的命名空间
  version: v1beta1
  versionPriority: 100

---
apiVersion: monitoring.coreos.com/v1
kind: Prometheus
metadata:
  name: prometheus
  namespace: default # 修改为 prometheus 所在的命名空间
spec:
  serviceMonitorSelector:
    matchLabels:
      team: frontend
  ruleSelector:
    matchLabels:
      role: alert-rules
  replicas: 1

---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: demo-service-monitor
  labels:
    team: frontend
spec:
  selector:
    matchLabels:
      app: demo-service
  endpoints:
  - port: management
    interval: 30s
    path: /actuator/prometheus
  namespaceSelector:
    matchNames:
    - default # 修改为你的服务所在的命名空间
---

apiVersion: custom.metrics.k8s.io/v1beta1
kind: MetricValueList
metadata:
  name: hello.requests
items:
- apiVersion: custom.metrics.k8s.io/v1beta1
  kind: MetricValue
  describedObject:
    apiVersion: v1
    kind: Pod
    name: demo-service-pod-name
  metric:
    name: hello.requests
    selector: {}
  timestamp: "2023-10-27T10:00:00Z"
  value: "100"
# custom-metrics-rules.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  labels:
    role: alert-rules
  name: custom-metrics-rules
  namespace: default # 修改为 PrometheusRule 所在的命名空间
spec:
  groups:
  - name: custom_metrics
    rules:
    - expr: sum(rate(hello_requests_total{application="demo-service"}[1m]))
      record: hello_requests_per_second

应用这些配置:

kubectl apply -f adapter-config.yaml
kubectl apply -f custom-metrics-rules.yaml

7.3 配置 HPA

现在,我们可以配置 HPA 使用自定义指标进行自动伸缩。

# hpa-custom-metrics.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: demo-service-hpa-custom
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: demo-service
  minReplicas: 2
  maxReplicas: 5
  metrics:
    - type: Pods
      pods:
        metric:
          name: hello_requests_per_second
        target:
          type: AverageValue
          averageValue: 10 # 每秒 10 个请求

这个 YAML 文件定义了一个 HPA,它会监控 demo-service Deployment 的每秒请求数,并根据以下规则调整 Pod 副本数:

  • metrics
    • type: Pods:指定监控的是 Pod 指标。
    • metric.name:指定自定义指标的名称,这里是 hello_requests_per_second
    • target.averageValue:指定目标平均值,这里是每秒 10 个请求。

应用 HPA 配置:

kubectl apply -f hpa-custom-metrics.yaml

8. 最佳实践

  • 合理设置资源请求和限制: requestslimits 会影响 HPA 的决策。确保它们设置合理,避免资源浪费或资源不足。
  • 选择合适的监控指标: CPU 利用率和内存利用率是常用的指标,但并非适用于所有场景。选择与你的应用负载相关的指标,例如请求延迟、吞吐量等。
  • 调整 HPA 的参数: minReplicasmaxReplicas 和目标利用率都会影响自动伸缩的效果。根据实际情况调整这些参数,找到最佳配置。
  • 使用滚动更新: 在更新 Deployment 时,使用滚动更新策略,避免服务中断。
  • 监控 HPA 的状态: 定期检查 HPA 的状态,确保它正常工作。
  • 考虑使用事件驱动的自动伸缩 (KEDA): KEDA 可以根据外部事件源 (例如 Kafka 队列长度) 自动伸缩 Pod。

9. 代码和配置示例汇总

文件名 内容
pom.xml (部分) xml <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-core</artifactId> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> </dependency> </dependencies>
Dockerfile dockerfile FROM openjdk:17-slim-buster WORKDIR /app COPY target/*.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]
deployment.yaml yaml apiVersion: apps/v1 kind: Deployment metadata: name: demo-service spec: replicas: 2 selector: matchLabels: app: demo-service template: metadata: labels: app: demo-service spec: containers: - name: demo-service image: your-dockerhub-username/demo-service:latest ports: - containerPort: 8080 resources: requests: cpu: 200m memory: 256Mi limits: cpu: 500m memory: 512Mi
service.yaml yaml apiVersion: v1 kind: Service metadata: name: demo-service spec: selector: app: demo-service ports: - protocol: TCP port: 80 targetPort: 8080 type: LoadBalancer
hpa.yaml yaml apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: demo-service-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: demo-service minReplicas: 2 maxReplicas: 5 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 50
application.properties properties management.endpoints.web.exposure.include=* management.metrics.export.prometheus.enabled=true management.server.port=8081
hpa-custom-metrics.yaml yaml apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: demo-service-hpa-custom spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: demo-service minReplicas: 2 maxReplicas: 5 metrics: - type: Pods pods: metric: name: hello_requests_per_second target: type: AverageValue averageValue: 10

自动伸缩配置和最佳实践,构建高可用应用

今天我们探讨了 Java 微服务在 Kubernetes 中实现自动伸缩的完整流程,包括基础环境搭建、HPA 配置、自定义指标以及最佳实践。希望这次讲座能帮助大家更好地理解和应用自动伸缩,构建更加健壮和可扩展的云原生应用。 谢谢大家!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注