基于KEDA的下一代弹性伸缩 十八岁 2026-09-26 2026-09-26
k8s弹性能力-基于KEDA的下一代弹性伸缩 1.k8s原生弹性伸缩—HPA 1.1HPA介绍 在 Kubernetes(K8s)中,HPA 代表 Horizontal Pod Autoscaler (水平 Pod 自动扩缩容)。
它的主要作用是根据实际的资源利用率或自定义指标,自动增减工作负载中 Pod 的副本数量(Replicas) 。例如,当系统流量激增导致 CPU 占用率超过预设阈值时,HPA 会自动增加 Pod 数量来分摊负载;当负载下降时,它会自动缩减 Pod 数量以节约资源 📈。
1.2HPA资源定义 通常习惯用命令创建一个HPA,这里的yaml配置理解即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: web-app-hpa namespace: default spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: web-app-deployment minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 - type: Resource resource: name: memory target: type: AverageValue averageValue: 500Mi
HPA注意事项:
必须安装metrics-server或其他自定义metrics-server
pod必须配置resources.requests参数
不能扩容无法缩放的对象,比如DaemonSet
1.3HPA基于cpu的弹性伸缩 在使用HPA之前要保证集群正常,metrics-server服务正常,使用kubectl top node或者kubectl top pod能看到资源使用情况。
创建一个测试的deployment服务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 cat hpa-nginx.yaml apiVersion: apps/v1 kind: Deployment metadata: labels: app: nginx name: nginx-server spec: replicas: 1 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:latest name: nginx resources: limits: cpu: 1000m memory: 128Mi requests: #必须要添加requests配置,HPA 基于此值计算百分比 cpu: 10m memory: 64Mi
创建上述deploy资源:
1 2 3 4 5 6 [root@k8s-master01 28-keda]# kubectl create -f hpa-nginx.yaml deployment.apps/nginx-server created [root@k8s-master01 28-keda]# kubectl get po NAME READY STATUS RESTARTS AGE nginx-server-774655c66b-nhjpq 1/1 Running 0 13s
为deployment创建一个service,用来做压力测试:
1 2 3 4 5 6 7 [root@k8s-master01 28-keda]# kubectl expose deployment nginx-server --port=80 service/nginx-server exposed [root@k8s-master01 28-keda]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 259d nginx-server ClusterIP 10.96.41.1 <none> 80/TCP 27s
创建一个HPA资源,指定当 CPU 平均使用率达到 10%时触发扩容,且最小副本数为1,最大副本数为 10:
1 kubectl autoscale deployment nginx-server --cpu-percent=10 --min=1 --max=10
主要参数说明:
deployment:表示要自动扩缩容的资源类型是deployment。
nginx-server:指定deployment的名字。
--cpu-percent:目标 CPU 平均使用率(基于 Pod 的 resources.requests.cpu 百分比)。
--min:允许缩容到的最小 Pod 副本数。
--max:允许扩容到的最大 Pod 副本数。
1 2 3 4 5 6 [root@k8s-master01 28-keda]# kubectl autoscale deployment nginx-server --cpu-percent=10 --min=1 --max=10 horizontalpodautoscaler.autoscaling/nginx-server autoscaled [root@k8s-master01 28-keda]# kubectl get hpa NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE nginx-server Deployment/nginx-server cpu: 0%/10% 1 10 1 31s
使用ab命令进行压测:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # 用 Apache Bench 发起海量并发请求(-c 并发数,-n 总请求数) # 把 IP 和 PORT 换成你的实际值 ab -n 10000 -c 200 http://10.96.41.1/ # 同时观察pod数量的变化 [root@k8s-master01 28-keda]# kubectl get po NAME READY STATUS RESTARTS AGE nginx-server-8669dbb5f-4t7ht 1/1 Running 0 30m nginx-server-8669dbb5f-58wbw 1/1 Running 0 29s nginx-server-8669dbb5f-7gzf5 1/1 Running 0 14s nginx-server-8669dbb5f-7wstr 1/1 Running 0 29s nginx-server-8669dbb5f-fmc5p 1/1 Running 0 14s nginx-server-8669dbb5f-jtc2m 1/1 Running 0 14s nginx-server-8669dbb5f-pwz8f 1/1 Running 0 29s nginx-server-8669dbb5f-w7vjc 1/1 Running 0 14s
计算平均利用率公式:
平均利用率并不是看节点配置,也不是看 limits,而是看实际用量与请求总量的比例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # 所有pod的实际资源用量是kubectl top pod中获取的值,例如:108m [root@k8s-master01 28-keda]# kubectl top po NAME CPU(cores) MEMORY(bytes) nginx-server-8669dbb5f-4t7ht 108m 15Mi # 所有pod的requests设定的值就是在资源yaml中定义的resources.requests的值,例如: resources: limits: cpu: 1000m memory: 128Mi requests: #必须要添加requests配置 cpu: 10m #这里设定的是10m memory: 64Mi # 当前平均利用率是在创建hpa时指定--cpu-percent=10,这里指定的是10% # 根据上述公式计算108/10x100%的值大于10%时就会触发扩容
HPA 缩容有冷却时间,不会立刻回到最小副本数 。
Kubernetes HPA 默认有一个 5 分钟的缩容稳定窗口 (--horizontal-pod-autoscaler-downscale-stabilization),防止频繁扩缩容抖动。即使 CPU 已经降到 0m,HPA 也要等满 5 分钟 才开始缩容。
1.4HPA基于内存的弹性伸缩 创建一个用于测试的 Deployment,使用stress镜像来模拟服务使用具体的内存:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 cat memory-app.yaml apiVersion: apps/v1 kind: Deployment metadata: name: memory-consumer spec: replicas: 1 selector: matchLabels: app: memory-consumer template: metadata: labels: app: memory-consumer spec: containers: - image: registry.cn-beijing.aliyuncs.com/dotbalo/stress:latest name: memory-consumer args: - stress - --vm - '1' - --vm-bytes - 64M - --verbose - --vm-keep - --timeout - '3600' resources: limits: cpu: 500m memory: 1024Mi requests: cpu: 200m memory: 128Mi
创建资源:
1 2 [root@k8s-master01 28-keda]# kubectl create -f memory-app.yaml deployment.apps/memory-consumer created
查看pod所用内存情况:
1 2 3 4 5 6 7 8 9 10 [root@k8s-master01 28-keda]# kubectl get po NAME READY STATUS RESTARTS AGE memory-consumer-b9f4bbd7b-c62z4 1/1 Running 0 11s nginx-server-8669dbb5f-4t7ht 1/1 Running 0 91m # 可以看到memory-consumer-b9f4bbd7b-c62z4 使用内存为64Mi [root@k8s-master01 28-keda]# kubectl top pod NAME CPU(cores) MEMORY(bytes) memory-consumer-b9f4bbd7b-c62z4 500m 64Mi nginx-server-8669dbb5f-4t7ht 0m 15Mi
创建一个HPA资源,指定当pod的内存使用率超过80%时进行扩容:
因为 kubectl autoscale 这个快捷命令在设计上仅支持基于 CPU 的自动扩缩容 (它只有 --cpu-percent 参数,没有提供 --memory-percent 或 --memory 等选项),所以创建一个基于内存扩容的hpa我用yaml定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 cat memory-consumer-hpa.yaml apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: mem-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: memory-consumer minReplicas: 1 maxReplicas: 10 metrics: - type: Resource resource: name: memory target: type: Utilization averageUtilization: 80
创建这个hpa:
1 2 [root@k8s-master01 28-keda]# kubectl create -f memory-consumer-hpa.yaml horizontalpodautoscaler.autoscaling/mem-hpa created
查看创建的hpa:
1 2 3 4 [root@k8s-master01 28-keda]# kubectl get hpa NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE mem-hpa Deployment/memory-consumer memory: 50%/80% 1 10 1 23s nginx-server Deployment/nginx-server cpu: 0%/10% 1 10 1 108m
接下来修改deployment的启动参数让服务占用更多内存:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [root@k8s-master01 28-keda]# kubectl edit deployments.apps memory-consumer spec: containers: - args: - stress - --vm - "2" #把这里原来的1改为2 - --vm-bytes - 64M - --verbose - --vm-keep - --timeout - "3600" image: registry.cn-beijing.aliyuncs.com/dotbalo/stress:latest imagePullPolicy: Always
此时hpa已经检测到内存已经超过80%,会触发pod的扩容
1 2 3 4 5 6 7 8 9 10 11 [root@k8s-master01 28-keda]# kubectl get hpa NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE mem-hpa Deployment/memory-consumer memory: 100%/80% 1 10 2 70m nginx-server Deployment/nginx-server cpu: 0%/10% 1 10 1 179m [root@k8s-master01 28-keda]# kubectl top pod NAME CPU(cores) MEMORY(bytes) memory-consumer-85c99f84b7-bnvsk 501m 128Mi memory-consumer-85c99f84b7-shx6q 500m 128Mi memory-consumer-85c99f84b7-zwbt5 499m 128Mi nginx-server-8669dbb5f-4t7ht 0m 15Mi
因为deployment资源中定义的pod所占用的内存是128Mi,所以即使扩容了,新启动的pod还是会用128Mi的内存,而该服务不会自动释放内存,所以就导致一直在扩容。
cpu是及时释放,内存是惰性释放,在生产环境中很少使用基于内存的扩缩容,因为应用可能不会主动释放内存,就会导致HPA一直处于扩容的状态
2.k8s下一代弹性伸缩—KEDA 2.1KEDA介绍 KEDA(全称:Kubernetes Event-Driven Autoscaler)是一个基于Kubernetes的事件驱动自动伸缩器。使用KEDA,可以根据需要处理的事件数量、消息队列来驱动Kubernetes中任何服务的伸缩。 KEDA的核心思想是:只要有任务需要处理时,才扩展应用程序,并且在没有工作时缩减资源,甚至可以将副本缩容到零这不仅提高了资源利用率,还降低了成本。
k8s原生的HAP只能根据两个指标(CPU和Memory)进行扩缩容,这并不能解决生产中实际的问题,需要基于更多的指标进行扩缩容,此时就需要借助KEDA
基于事件扩缩容
基于消息队列扩缩容
基于流量扩缩容
基于自定义指标扩缩容
基于各种策略扩缩容
2.2KEDA核心资源 2.2.1ScaledObject Scaled0bject:用于控制Deployment等资源的副本数,可以指定多种事件和消息来源控制资源的副本数,同时支持Scale to Zero
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 apiVersion: keda.sh/v1alpha1 kind: ScaledObject metadata: name: rabbitmq-consumer-scaledobject namespace: default spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: rabbitmq-consumer pollingInterval: 30 cooldownPeriod: 300 minReplicaCount: 0 maxReplicaCount: 30 triggers: - type: rabbitmq metadata: protocol: amqp queueName: order-tasks mode: QueueLength value: "20" authenticationRef: name: keda-trigger-auth
2.2.2ScaledJob ScaledJob 是 KEDA 中专门用来管理一次性批处理任务(Kubernetes Job 🏃)的自定义资源。
面向运行即结束的任务(ScaledJob) :ScaledJob 针对的是处理完就退出 的任务。当队列或事件源出现积压时,KEDA 会直接创建新的 Kubernetes Job 实例;任务完成后,容器退出并释放所有资源。
对比维度
ScaledObject 📦
ScaledJob ⚡
管理对象
Deployment / StatefulSet
Kubernetes Job
Pod 生命周期
长时间运行、持续拉取事件
运行单次或批次处理,完成后退出(Completed)
典型场景
轻量消息消费、API 网关
视频转码 🎬、报表生成 📑、AI 离线推理 🧠
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 apiVersion: keda.sh/v1alpha1 kind: ScaledJob metadata: name: image-process-scaledjob namespace: default spec: jobTargetRef: parallelism: 1 completions: 1 backoffLimit: 2 template: spec: restartPolicy: Never containers: - name: processor image: myrepo/resizer:v1 resources: requests: cpu: 500m memory: 512Mi pollingInterval: 30 successfulJobsHistoryLimit: 5 failedJobsHistoryLimit: 5 maxReplicaCount: 20 scalingStrategy: strategy: "default" triggers: - type: rabbitmq metadata: protocol: amqp queueName: image-resize-tasks mode: QueueLength value: "1" authenticationRef: name: keda-trigger-auth
2.2.3TriggerAuthentication 在 KEDA 的架构中,TriggerAuthentication (触发器认证凭据)是一个非常关键的解耦与安全组件 🔐。
当我们配置 ScaledObject 或 ScaledJob 去监听外部系统(例如 RabbitMQ、Kafka、Redis 或云厂商的队列服务)时,KEDA 需要登录凭据 去查询指标。
如果直接把账号、密码或连接串明文写在每个 ScaledObject 里存在安全风险
TriggerAuthentication 正是用来在指标触发器(Scaler)和实际的凭据提供方(如 Kubernetes Secret、云厂商 IAM 角色、HashiCorp Vault 等)之间架起桥梁 。它把“怎么认证”从“监控什么指标”中剥离出来。
在 KEDA 中,TriggerAuthentication(以及 ClusterTriggerAuthentication)支持多种认证参数来源。你提到的三种主要方式是:
这三种方式可以单独使用,也可以混合使用(在同一个 TriggerAuthentication 中同时定义多个来源)。下面分别介绍使用方式。
方式1:secretTargetRef(从 Secret 读取) 最常用、最安全的方式 ,适合存放密码、连接字符串、Token、证书等敏感信息。
工作原理 :KEDA 直接从指定的 Secret 中读取指定 key 的值,并映射到 scaler 需要的 parameter。
KEDA 官方当前文档中,secretTargetRef 本身是可选的;但只要写了一条 secretTargetRef,这一条里面的 parameter、name、key 三个字段都是必填字段 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 apiVersion: v1 kind: Secret metadata: name: rabbitmq-credentials namespace: default type: Opaque stringData: rabbitmq-connection-string: "amqp://admin:123456@rabbitmq.default.svc.cluster.local:5672/" --- apiVersion: keda.sh/v1alpha1 kind: TriggerAuthentication metadata: name: keda-trigger-auth-rabbitmq namespace: default spec: secretTargetRef: - parameter: hsot name: rabbitmq-credentials key: rabbitmq-connection-string
在 ScaledObject 中引用:
1 2 3 4 5 6 7 triggers: - type: rabbitmq metadata: queueName: hello authenticationRef: name: keda-trigger-auth-rabbitmq
注意事项 :
Secret 必须与 TriggerAuthentication 在同一命名空间(除非使用 ClusterTriggerAuthentication)。
值需要 base64 编码(标准 Kubernetes Secret 要求)。
这是目前推荐的敏感信息存储方式。
方式二:configMapTargetRef(从 ConfigMap 读取) 适合非敏感配置 ,例如用户名、主机地址、某些非机密参数等。
工作原理 :与 secretTargetRef 几乎完全相同,只是从 ConfigMap 读取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 apiVersion: v1 kind: ConfigMap metadata: name: my-auth-configmap namespace: default data: username: myuser host: rabbitmq.default.svc.cluster.local --- apiVersion: keda.sh/v1alpha1 kind: TriggerAuthentication metadata: name: my-trigger-auth namespace: default spec: configMapTargetRef: - parameter: username name: my-auth-configmap key: username - parameter: host name: my-auth-configmap key: host
字段
值从哪里来
对应谁
说明
parameter
由具体的 Scaler 决定
对应 Scaler 文档中定义的认证参数名
这是最关键的字段。它必须与目标 scaler 支持的认证参数名称完全一致。
name
你自己创建的 ConfigMap 名称
对应 Kubernetes 中的 ConfigMap 资源
就是你写的那个 kind: ConfigMap 的 metadata.name。
key
ConfigMap 的 data 部分
对应 ConfigMap 里的 具体 key
读取 data 下面的某个 key 的值,作为 parameter 的实际内容。
注意事项 :
parameter 不是随便起的名字 ,它必须严格匹配目标 Scaler 文档中定义的 Authentication Parameters 。
确定步骤:
先确定你要用哪个 scaler(例如 rabbitmq、kafka、prometheus、azure-servicebus 等)。
去 KEDA 官方文档查看该 scaler 的 Authentication Parameters 部分。
文档里写的参数名(如 username、password、host、connection、bearerToken 等),就是你这里要填的 parameter 值。
ConfigMap 中的数据是明文(不像 Secret 有 base64 编码要求,但也因此不适合敏感信息)。
命名空间规则与 secretTargetRef 相同。
官方文档中通常放在 Authentication Providers 的 Config Map 页面说明。
方式三:env(从目标 Pod 的环境变量读取) 从被缩放的工作负载(Deployment / StatefulSet 等)的容器环境变量中读取值 。
工作原理 :KEDA 会查找 scaleTargetRef 指向的资源中指定容器的环境变量,把对应的值映射到认证参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 apiVersion: keda.sh/v1alpha1 kind: TriggerAuthentication metadata: name: my-trigger-auth namespace: default spec: env: - parameter: username name: MY_USERNAME containerName: my-container - parameter: password name: MY_PASSWORD containerName: my-container
对应 Deployment 示例(环境变量需要先存在):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 apiVersion: apps/v1 kind: Deployment metadata: name: my-app spec: template: spec: containers: - name: my-container image: my-image env: - name: MY_USERNAME valueFrom: secretKeyRef: name: my-secret key: username - name: MY_PASSWORD valueFrom: secretKeyRef: name: my-secret key: password
注意事项 :
环境变量必须已经存在于目标工作负载的容器中。
containerName 可选,默认取 ScaledObject 中配置的容器或第一个容器。
这种方式本质上是“间接”引用,值仍然可以来自 Secret,但通过容器环境变量暴露。
3.KEDA部署安装 官网:https://keda.sh/docs/2.20/deploy/
添加KEDA的helm源:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 helm repo add kedacore https://kedacore.github.io/charts helm repo update helm install keda kedacore/keda --namespace keda --create-namespace # 如果镜像下载慢也可以使用下面命令单独指定镜像下载地址 helm install keda kedacore/keda \ --namespace keda \ --create-namespace \ --version 2.20.2 \ --set image.keda.registry=registry.cn-beijing.aliyuncs.com \ --set image.keda.repository=k8s-liujunwei/keda \ --set image.keda.tag=2.20.2 \ --set image.metricsApiServer.registry=registry.cn-beijing.aliyuncs.com \ --set image.metricsApiServer.repository=k8s-liujunwei/keda-metrics-apiserver \ --set image.metricsApiServer.tag=2.20.2 \ --set image.webhooks.registry=registry.cn-beijing.aliyuncs.com \ --set image.webhooks.repository=k8s-liujunwei/keda-admission-webhooks \ --set image.webhooks.tag=2.20.2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 [root@k8s-master01 ~]# helm repo add kedacore https://kedacore.github.io/charts "kedacore" has been added to your repositories [root@k8s-master01 ~]# helm repo update Hang tight while we grab the latest from your chart repositories... ...Successfully got an update from the "aliyun" chart repository ...Successfully got an update from the "kedacore" chart repository ...Successfully got an update from the "bitnami" chart repository Update Complete. ⎈Happy Helming!⎈ [root@k8s-master01 ~]# helm install keda kedacore/keda --namespace keda --create-namespace NAME: keda LAST DEPLOYED: Thu Sep 17 22:37:39 2026 NAMESPACE: keda STATUS: deployed REVISION: 1 DESCRIPTION: Install complete TEST SUITE: None NOTES: :::^. .::::^: ::::::::::::::: .:::::::::. .^. 7???~ .^7????~. 7??????????????. :?????????77!^. .7?7. 7???~ ^7???7~. ~!!!!!!!!!!!!!!. :????!!!!7????7~. .7???7. 7???~^7????~. :????: :~7???7. :7?????7. 7???7????!. ::::::::::::. :????: .7???! :7??77???7. 7????????7: 7???????????~ :????: :????: :???7?5????7. 7????!~????^ !77777777777^ :????: :????: ^???7?#P7????7. 7???~ ^????~ :????: :7???! ^???7J#@J7?????7. 7???~ :7???!. :????: .:~7???!. ~???7Y&@#7777????7. 7???~ .7???7: !!!!!!!!!!!!!!! :????7!!77????7^ ~??775@@@GJJYJ?????7. 7???~ .!????^ 7?????????????7. :?????????7!~: !????G@@@@@@@@5??????7: ::::. ::::: ::::::::::::::: .::::::::.. .::::JGGGB@@@&7::::::::: ?@@#~ P@B^ :&G: !5. .Kubernetes Event-driven Autoscaling (KEDA) - Application autoscaling made simple. Get started by deploying Scaled Objects to your cluster: - Information about Scaled Objects : https://keda.sh/docs/latest/concepts/ - Samples: https://github.com/kedacore/samples Get information about the deployed ScaledObjects: kubectl get scaledobject [--namespace <namespace>] Get details about a deployed ScaledObject: kubectl describe scaledobject <scaled-object-name> [--namespace <namespace>] Get information about the deployed TriggerAuthentications: kubectl get triggerauthentication [--namespace <namespace>] Get details about a deployed TriggerAuthentication: kubectl describe triggerauthentication <trigger-authentication-name> [--namespace <namespace>] Get an overview of the Horizontal Pod Autoscalers (HPA) that KEDA is using behind the scenes: kubectl get hpa [--all-namespaces] [--namespace <namespace>] ------------------------------------------------------------------------------------- WARNING - Running on unsupported Kubernetes version "1.31". KEDA 2.20 is supported and tested on Kubernetes "1.33" or higher. See https://keda.sh/docs/latest/operate/cluster/ for details. ------------------------------------------------------------------------------------- Learn more about KEDA: - Documentation: https://keda.sh/ - Support: https://keda.sh/support/ - File an issue: https://github.com/kedacore/keda/issues/new/choose
查看服务状态:
1 2 3 4 5 6 7 8 9 10 [root@k8s-master01 ~]# helm list -n keda NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION keda keda 1 2026-09-17 22:37:39.282948424 +0800 CST deployed keda-2.20.2 2.20.2 [root@k8s-master01 keda]# kubectl get po -n keda NAME READY STATUS RESTARTS AGE keda-admission-webhooks-755bcc4f55-kk6rt 1/1 Running 1 (4h19m ago) 4h19m keda-operator-674bb4df78-fdk9b 1/1 Running 1 (4h19m ago) 4h19m keda-operator-metrics-apiserver-54fd8bf9db-cv7hf 1/1 Running 1 (4h19m ago) 4h19m
卸载:
1 2 helm uninstall keda –n keda kubectl delete ns keda
4.实战案例 4.1周期性扩缩容 KEDA 中说的周期性扩缩容 ,通常指使用 Cron Scaler ,按照预先定义好的时间段,让工作负载在特定时间自动维持一定数量的 Pod,且支持缩容至 0。假设有个服务只有每天早上 7-9 点属于 业务高峰,就可以利用 KEDA 实现在 7-9 点扩展服务,除此之外的时间在缩减副本,以节省资 源。
先启动一个nginx的deployment来模拟业务的服务:
1 2 3 4 5 6 # 创建一个deployment,名字是my-app,镜像用的nginx,副本是1 [root@k8s-master01 keda]# kubectl create deployment my-app --image=registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:latest --replicas=1 [root@k8s-master01 keda]# kubectl get deployments.apps NAME READY UP-TO-DATE AVAILABLE AGE my-app 1/1 1 1 57s
在创建Cron类型的ScaledObject:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 vim cron-app.yaml apiVersion: keda.sh/v1alpha1 kind: ScaledObject metadata: name: my-app-cron-scaler spec: scaleTargetRef: name: my-app minReplicaCount: 1 maxReplicaCount: 10 triggers: - type: cron metadata: timezone: Asia/Shanghai start: "30 14 * * *" end: "50 14 * * *" desiredReplicas: "3"
创建资源:
1 2 3 4 5 6 7 8 9 10 11 12 [root@k8s-master01 28-keda]# kubectl create -f cron-app.yaml scaledobject.keda.sh/my-app-cron-scaler created # so是ScaledObject的缩写 [root@k8s-master01 28-keda]# kubectl get so NAME SCALETARGETKIND SCALETARGETNAME MIN MAX READY ACTIVE FALLBACK PAUSED TRIGGERS AUTHENTICATIONS AGE my-app-cron-scaler apps/v1.Deployment my-app 1 10 True False False False cron 115s [root@k8s-master01 28-keda]# kubectl get hpa NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE keda-hpa-my-app-cron-scaler Deployment/my-app 0/1 (avg) 1 10 1 2m8s
观察pod数量:
1 2 3 4 5 6 7 8 9 10 # 可以看到在02:30时任务已经触发,pod已经扩容到3个 [root@k8s-master01 28-keda]# date Sun Sep 20 02:30:25 PM CST 2026 [root@k8s-master01 28-keda]# kubectl get po NAME READY STATUS RESTARTS AGE my-app-7866957447-dtj8p 1/1 Running 1 (30m ago) 40h my-app-7866957447-p24g9 1/1 Running 0 6s my-app-7866957447-rcg5f 1/1 Running 0 6s
4.2基于RabbitMQ消息队列长度实现扩缩容 KEDA 支持基于消息队列的弹性伸缩,比如基于 RabbitMQ、Kafka、Redis 队列进行扩缩容, 以便更快的处理处理。
整体逻辑: 生产者产生消息 → RabbitMQ 队列堆积 → KEDA 监控队列 → KEDA 生成/管理 HPA → HPA 调整消费者 Deployment 副本数 → 消费者处理消息 → 队列下降 → HPA 缩容
架构如下:
组件
作用
实验中的对象
RabbitMQ
消息队列,负责暂存消息
rabbitmq Deployment
Producer
往 RabbitMQ 写消息
rabbitmq-publish Job
Consumer
从 RabbitMQ 消费消息
rabbitmq-consumer Deployment
KEDA
根据 RabbitMQ 队列长度调整 Consumer 副本数
ScaledObject
注意: 在实际的生产环境中不要用deployment来部署RabbitMQ ,这里是用于实验
第一步:创建一个RabbitMQ 服务 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 vim rabbitmq.yaml apiVersion: v1 kind: Secret metadata: name: rabbitmq-secret type: Opaque stringData: default-user: "admin" default-pass: "asdqwe123" --- apiVersion: v1 kind: Service metadata: name: rabbitmq labels: app: rabbitmq spec: type: NodePort ports: - name: amqp port: 5672 targetPort: 5672 - name: management port: 15672 targetPort: 15672 selector: app: rabbitmq --- apiVersion: apps/v1 kind: Deployment metadata: name: rabbitmq spec: replicas: 1 selector: matchLabels: app: rabbitmq template: metadata: labels: app: rabbitmq spec: terminationGracePeriodSeconds: 60 containers: - name: rabbitmq image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/rabbitmq:4.0.2-management imagePullPolicy: IfNotPresent env: - name: TZ value: Asia/Shanghai - name: LANG value: C.UTF-8 - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: RABBITMQ_DEFAULT_USER valueFrom: secretKeyRef: name: rabbitmq-secret key: default-user - name: RABBITMQ_DEFAULT_PASS valueFrom: secretKeyRef: name: rabbitmq-secret key: default-pass ports: - name: amqp containerPort: 5672 - name: management containerPort: 15672 resources: requests: cpu: "1000m" memory: "2Gi" limits: cpu: "1000m" memory: "2Gi" startupProbe: tcpSocket: port: 5672 initialDelaySeconds: 10 periodSeconds: 5 failureThreshold: 60 timeoutSeconds: 10 readinessProbe: tcpSocket: port: 5672 initialDelaySeconds: 0 periodSeconds: 30 timeoutSeconds: 5 failureThreshold: 3 livenessProbe: tcpSocket: port: 5672 initialDelaySeconds: 0 periodSeconds: 30 timeoutSeconds: 5 failureThreshold: 3
创建服务:
1 2 3 4 [root@k8s-master01 28-keda]# kubectl create -f rabbitmq.yaml secret/rabbitmq-secret created service/rabbitmq created deployment.apps/rabbitmq created
查看pod和service:
1 2 3 4 5 6 7 8 [root@k8s-master01 28-keda]# kubectl get po NAME READY STATUS RESTARTS AGE my-app-7866957447-dtj8p 1/1 Running 1 (8h ago) 47h rabbitmq-d48cb6779-tq8v4 1/1 Running 0 33s [root@k8s-master01 28-keda]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 264d rabbitmq NodePort 10.96.43.50 <none> 5672:31362/TCP,15672:31687/TCP 3m58s
浏览器访问:
第二步:创建生产者 使用一个 CronJob 模拟写入消息,没隔五分钟写入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 vim rabbitmq-publish-cronjob.yaml apiVersion: batch/v1 kind: CronJob metadata: name: rabbitmq-publish spec: schedule: "*/30 * * * *" timeZone: "Asia/Shanghai" concurrencyPolicy: Forbid successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 1 startingDeadlineSeconds: 60 jobTemplate: spec: backoffLimit: 4 template: spec: restartPolicy: Never containers: - name: rabbitmq-client image: registry.cn-beijing.aliyuncs.com/dotbalo/rabbitmqpublish:v1.0 imagePullPolicy: IfNotPresent env: - name: AMQP_USER valueFrom: secretKeyRef: name: rabbitmq-secret key: default-user - name: AMQP_PASS valueFrom: secretKeyRef: name: rabbitmq-secret key: default-pass command: - "send" - "amqp://$(AMQP_USER):$(AMQP_PASS)@rabbitmq-headless.svc.cluster.local:5672" - "10"
创建CronJob服务:
1 2 [root@k8s-master01 28-keda]# kubectl create -f rabbitmq-publish-cronjob.yaml cronjob.batch/rabbitmq-publish created
查看服务:
1 2 3 4 5 6 7 8 9 10 [root@k8s-master01 28-keda]# kubectl get cronjobs.batch NAME SCHEDULE TIMEZONE SUSPEND ACTIVE LAST SCHEDULE AGE rabbitmq-publish */5 * * * * Asia/Shanghai False 0 71s 13m [root@k8s-master01 28-keda]# kubectl get po NAME READY STATUS RESTARTS AGE my-app-7866957447-dtj8p 1/1 Running 1 (8h ago) 2d rabbitmq-d48cb6779-tq8v4 1/1 Running 0 27m rabbitmq-publish-29831900-4zz9j 0/1 Completed 0 13m rabbitmq-publish-29831905-kww9l 0/1 Completed 0 8m57s rabbitmq-publish-29831910-9rkrk 0/1 Completed 0 3m57s
可以看到计划任务已经执行了三次,每次产生10条消息,也就是应该有30条消息带消费:
第三步:创建消费者-Consumer 创建一个模拟消费消息的程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 apiVersion: apps/v1 kind: Deployment metadata: name: rabbitmq-consumer spec: replicas: 1 selector: matchLabels: app: rabbitmq-consumer template: metadata: labels: app: rabbitmq-consumer spec: containers: - name: rabbitmq-consumer image: registry.cn-beijing.aliyuncs.com/dotbalo/rabbitmq-consumer:v1.0 imagePullPolicy: Always env: - name: AMQP_USER valueFrom: secretKeyRef: name: rabbitmq-secret key: default-user - name: AMQP_PASS valueFrom: secretKeyRef: name: rabbitmq-secret key: default-pass command: ["/bin/sh" , "-c" ] args: - | echo "Starting consumer with user: ${AMQP_USER}" receive "amqp://${AMQP_USER}:${AMQP_PASS}@rabbitmq.default.svc.cluster.local:5672"
创建消费程序:
1 2 [root@k8s-master01 28-keda]# kubectl create -f rabbitmq-consumer.yaml deployment.apps/rabbitmq-consumer created
上述步骤在生产中正常已经存在,重点是下面的步骤
第四步:创建 TriggerAuthentication 从第四步开始是KEDA扩容的重点部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 cat rabbitmq-TriggerAuth.yaml apiVersion: v1 kind: Secret metadata: name: rabbitmq-auth-secret type: Opaque stringData: host: "amqp://rabbitmq.default.svc.cluster.local:5672" username: "admin" password: "asdqwe123" --- apiVersion: keda.sh/v1alpha1 kind: TriggerAuthentication metadata: name: keda-trigger-auth-rabbitmq-conn spec: secretTargetRef: - parameter: host name: rabbitmq-auth-secret key: host - parameter: username name: rabbitmq-auth-secret key: username - parameter: password name: rabbitmq-auth-secret key: password
创建上述资源:
1 2 3 4 [root@k8s-master01 28-keda]# kubectl create -f rabbitmq-TriggerAuth.yaml secret/rabbitmq-auth-secret created triggerauthentication.keda.sh/keda-trigger-auth-rabbitmq-conn created
第五步:创建ScaledObject KEDA 的 RabbitMQ Scaler 是用来根据队列长度伸缩消费者(Consumer) 服务的,而不是伸缩 RabbitMQ Broker 本身,注意spec.scaleTargetRef.name中指定的服务名是消费消息的程序名字,当消息产生堆积时,ScaledObject会对该服务进行扩容,以此来加快处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 cat rabbitmq-scaledobject.yaml apiVersion: keda.sh/v1alpha1 kind: ScaledObject metadata: name: rabbitmq-scaledobject spec: scaleTargetRef: name: rabbitmq-consumer minReplicaCount: 0 maxReplicaCount: 10 pollingInterval: 10 cooldownPeriod: 300 triggers: - type: rabbitmq metadata: protocol: amqp queueName: hello mode: QueueLength value: "50" authenticationRef: name: keda-trigger-auth-rabbitmq-conn
创建上述资源:
1 2 3 4 5 6 [root@k8s-master01 28-keda]# kubectl create -f rabbitmq-scaledobject.yaml scaledobject.keda.sh/rabbitmq-scaledobject created [root@k8s-master01 28-keda]# kubectl get so NAME SCALETARGETKIND SCALETARGETNAME MIN MAX READY ACTIVE FALLBACK PAUSED TRIGGERS AUTHENTICATIONS AGE rabbitmq-scaledobject apps/v1.Deployment rabbitmq 0 10 False False False False rabbitmq keda-trigger-auth-rabbitmq-conn 64s
查看创建的scaledobject资源:kubectl get so
列名
含义
当前值说明
NAME
ScaledObject 资源的名称
rabbitmq-scaledobject
SCALETARGETKIND
被伸缩目标的资源类型(Kind)
apps/v1.Deployment(表示目标是 Deployment)
SCALETARGETNAME
被伸缩目标的资源名称
rabbitmq(对应你配置里的 scaleTargetRef.name)
MIN
最小副本数(minReplicaCount)
0(允许缩到 0)
MAX
最大副本数(maxReplicaCount)
10
READY
ScaledObject 是否已就绪(能否正常工作)
True表示正常 ,False ← 当前有问题
ACTIVE
当前是否处于「活跃」状态(是否正在根据指标伸缩)
False(因为 READY 是 False,所以也不会 Active)
FALLBACK
是否触发了 Fallback(备用伸缩策略)
False(未触发)
PAUSED
是否被手动暂停伸缩
False(未暂停)
TRIGGERS
使用的触发器类型
rabbitmq
AUTHENTICATIONS
引用的 TriggerAuthentication 名称
keda-trigger-auth-rabbitmq-conn
AGE
资源创建后经过的时间
64s
第六步:发送大量消息 修改第二步中创建的生产者,把command指令中参数10该大,改成300,观察消费者的pod数量变化
1 2 3 4 5 6 7 8 9 10 # 此时还没有开始发送大量消息,rabbitmq-consumer服务的pod只有两个 [root@k8s-master01 28-keda]# kubectl get po NAME READY STATUS RESTARTS AGE my-app-7866957447-dtj8p 1/1 Running 1 (27h ago) 2d19h rabbitmq-consumer-5cffd78bf8-jn2ss 1/1 Running 0 3s rabbitmq-consumer-5cffd78bf8-nlfl6 1/1 Running 0 19m rabbitmq-d48cb6779-97cvz 1/1 Running 0 34m rabbitmq-publish-29833050-dlfnb 0/1 Completed 0 14m rabbitmq-publish-29833055-zsnfq 0/1 Completed 0 9m11s rabbitmq-publish-29833060-bdf2k 0/1 Completed 0 4m11s
定时任务触发执行消息发送后查看Rabbitmq控制台是否有消息堆积:此时已经产生消息堆积
1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 此时定时任务rabbitmq-publish触发执行,rabbitmq-consumer服务的pod随之被扩容为6个 [root@k8s-master01 28-keda]# kubectl get po NAME READY STATUS RESTARTS AGE my-app-7866957447-dtj8p 1/1 Running 1 (27h ago) 2d19h rabbitmq-consumer-5cffd78bf8-dqgs8 1/1 Running 0 37s rabbitmq-consumer-5cffd78bf8-hnsf6 1/1 Running 0 52s rabbitmq-consumer-5cffd78bf8-jn2ss 1/1 Running 0 111s rabbitmq-consumer-5cffd78bf8-mp4tg 1/1 Running 0 37s rabbitmq-consumer-5cffd78bf8-nlfl6 1/1 Running 0 20m rabbitmq-consumer-5cffd78bf8-wkstm 1/1 Running 0 52s rabbitmq-d48cb6779-97cvz 1/1 Running 0 36m rabbitmq-publish-29833055-zsnfq 0/1 Completed 0 10m rabbitmq-publish-29833060-bdf2k 0/1 Completed 0 5m59s rabbitmq-publish-29833065-g2jw5 0/1 Completed 0 59s
当堆积消息处理完成,冷却时间超过5分钟后没有产生新的消息堆积时,rabbitmq-consumer的pod会进行缩容,这里就不演示了。
4.3基于MySQL数据库扩容 角色如下:
组件
作用
mysql Deployment
保存业务数据
insert-orders-job
模拟业务系统不断产生订单
update-orders Deployment
模拟后台 Worker,处理 pending 订单
KEDA
监控 MySQL 中待处理订单数量
HPA
根据 KEDA 提供的指标修改 update-orders 副本数
第一步:环境准备 创建MySQL实例:
1 2 3 4 5 6 7 8 9 10 [root@k8s-master01 mysql]# kubectl create deployment mysql --image=registry.cn-beijing.aliyuncs.com/k8s-liujunwei/mysql:8.0.39 deployment.apps/mysql created [root@k8s-master01 mysql]# kubectl set env deploy mysql MYSQL_ROOT_PASSWORD=password deployment.apps/mysql env updated [root@k8s-master01 mysql]# kubectl get po NAME READY STATUS RESTARTS AGE mysql-c4f7b9749-wvlzs 1/1 Running 0 3s [root@k8s-master01 mysql]# kubectl expose deployment mysql --port=3306 service/mysql exposed
创建用于测试的库和表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 [root@k8s-master01 mysql]# kubectl exec -ti mysql-c4f7b9749-wvlzs -- bash bash-5.1# mysql -uroot -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 21 Server version: 8.0.39 MySQL Community Server - GPL Copyright (c) 2000, 2024, Oracle and/or its affiliates. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> create database dukuan; Query OK, 1 row affected (0.01 sec) mysql> use dukuan; Database changed # 创建表 mysql> CREATE TABLE orders ( id INT AUTO_INCREMENT PRIMARY KEY, customer_name VARCHAR(100), order_amount DECIMAL(10, 2), status ENUM('pending', 'processed') DEFAULT 'pending', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); Query OK, 0 rows affected (0.03 sec) mysql>
创建一个程序模拟写入数据的程序:
1 2 kubectl create job insert-orders-job \ --image=registry.cn-beijing.aliyuncs.com/dotbalo/mysql:insert
查看表中的数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 mysql> select * from orders;+-----+---------------+--------------+-----------+---------------------+ | id | customer_name | order_amount | status | created_at | +-----+---------------+--------------+-----------+---------------------+ | 1 | Ivan | 878.01 | processed | 2026-09-22 05:52:59 | | 2 | Bob | 878.00 | processed | 2026-09-22 05:52:59 | | 3 | Grace | 878.00 | processed | 2026-09-22 05:52:59 | | 4 | Judy | 878.01 | processed | 2026-09-22 05:52:59 | | 5 | Grace | 878.01 | processed | 2026-09-22 05:52:59 | | 6 | Frank | 878.01 | processed | 2026-09-22 05:52:59 | | 7 | Grace | 878.00 | pending | 2026-09-22 05:52:59 | | 8 | Alice | 878.00 | processed | 2026-09-22 05:52:59 | | 9 | Grace | 878.00 | pending | 2026-09-22 05:52:59 | | 10 | Charlie | 878.00 | processed | 2026-09-22 05:52:59 | | 11 | Bob | 878.01 | pending | 2026-09-22 05:52:59 | | 12 | Alice | 878.01 | pending | 2026-09-22 05:52:59 | | 13 | Heidi | 878.01 | pending | 2026-09-22 05:52:59 | | 14 | Eve | 878.00 | processed | 2026-09-22 05:52:59 | | 15 | Eve | 878.00 | processed | 2026-09-22 05:52:59 | | 16 | Frank | 18.01 | pending | 2026-09-22 05:53:00 | | 17 | Frank | 18.01 | pending | 2026-09-22 05:53:00 | | 18 | Ivan | 18.00 | processed | 2026-09-22 05:53:00 | | 19 | Alice | 18.00 | processed | 2026-09-22 05:53:00 | | 20 | Frank | 18.00 | pending | 2026-09-22 05:53:00 | | 21 | Frank | 18.00 | pending | 2026-09-22 05:53:00 | | 22 | Grace | 18.00 | processed | 2026-09-22 05:53:00 | | 23 | Bob | 18.01 | pending | 2026-09-22 05:53:00 | | 24 | David | 18.01 | processed | 2026-09-22 05:53:00 | | 25 | Alice | 18.00 | pending | 2026-09-22 05:53:00 | | 26 | Charlie | 18.00 | processed | 2026-09-22 05:53:00 | | 27 | Charlie | 18.01 | pending | 2026-09-22 05:53:00 | | 28 | Alice | 18.00 | pending | 2026-09-22 05:53:00 | | 29 | David | 18.01 | pending | 2026-09-22 05:53:00 | | 30 | Judy | 18.00 | pending | 2026-09-22 05:53:00 | | 31 | Judy | 18.01 | pending | 2026-09-22 05:53:00 | | 32 | David | 148.01 | processed | 2026-09-22 05:53:01 | | 33 | Ivan | 148.00 | pending | 2026-09-22 05:53:01 | | 34 | Charlie | 148.01 | pending | 2026-09-22 05:53:01 | | 35 | Frank | 148.00 | pending | 2026-09-22 05:53:01 | | 36 | David | 148.00 | processed | 2026-09-22 05:53:01 | | 37 | Ivan | 148.00 | processed | 2026-09-22 05:53:01 | | 38 | Heidi | 148.01 | pending | 2026-09-22 05:53:01 | | 39 | Judy | 148.01 | processed | 2026-09-22 05:53:01 | | 40 | Grace | 148.00 | processed | 2026-09-22 05:53:01 | | 41 | Bob | 148.00 | processed | 2026-09-22 05:53:01 | | 42 | Charlie | 148.01 | pending | 2026-09-22 05:53:01 | | 43 | Bob | 148.01 | pending | 2026-09-22 05:53:01 | | 44 | Heidi | 148.01 | pending | 2026-09-22 05:53:01 | | 45 | Bob | 148.01 | processed | 2026-09-22 05:53:01 | | 46 | Frank | 148.00 | pending | 2026-09-22 05:53:01 | | 47 | Charlie | 148.01 | processed | 2026-09-22 05:53:01 | | 48 | Alice | 148.01 | processed | 2026-09-22 05:53:01 | | 49 | Bob | 148.01 | processed | 2026-09-22 05:53:01 | | 50 | Judy | 148.00 | processed | 2026-09-22 05:53:01 | | 51 | Ivan | 148.00 | pending | 2026-09-22 05:53:01 | | 52 | Frank | 148.00 | processed | 2026-09-22 05:53:01 | | 53 | Frank | 148.00 | processed | 2026-09-22 05:53:01 | | 54 | Heidi | 148.00 | processed | 2026-09-22 05:53:01 | | 55 | Frank | 148.01 | processed | 2026-09-22 05:53:02 | | 56 | Eve | 278.00 | processed | 2026-09-22 05:53:02 | | 57 | Heidi | 278.01 | processed | 2026-09-22 05:53:02 | | 58 | Frank | 278.00 | processed | 2026-09-22 05:53:02 | | 59 | David | 278.00 | pending | 2026-09-22 05:53:02 | | 60 | Frank | 278.01 | processed | 2026-09-22 05:53:02 | | 61 | David | 278.01 | pending | 2026-09-22 05:53:02 | | 62 | Heidi | 278.01 | pending | 2026-09-22 05:53:02 | | 63 | Grace | 278.00 | processed | 2026-09-22 05:53:02 | | 64 | David | 278.00 | pending | 2026-09-22 05:53:02 | | 65 | Frank | 278.01 | pending | 2026-09-22 05:53:02 | | 66 | Ivan | 278.01 | processed | 2026-09-22 05:53:02 | | 67 | Heidi | 278.00 | pending | 2026-09-22 05:53:02 | | 68 | Ivan | 278.00 | processed | 2026-09-22 05:53:02 | | 69 | Charlie | 278.00 | processed | 2026-09-22 05:53:02 | | 70 | Bob | 409.00 | pending | 2026-09-22 05:53:03 | | 71 | Frank | 409.00 | processed | 2026-09-22 05:53:03 | | 72 | Eve | 409.01 | processed | 2026-09-22 05:53:03 | | 73 | Eve | 409.00 | pending | 2026-09-22 05:53:03 | | 74 | Eve | 409.00 | pending | 2026-09-22 05:53:03 | | 75 | Eve | 409.00 | processed | 2026-09-22 05:53:03 | | 76 | Frank | 409.00 | pending | 2026-09-22 05:53:03 | | 77 | Alice | 409.00 | pending | 2026-09-22 05:53:03 | | 78 | Bob | 409.01 | processed | 2026-09-22 05:53:03 | | 79 | Heidi | 409.01 | processed | 2026-09-22 05:53:03 | | 80 | David | 409.00 | pending | 2026-09-22 05:53:03 | | 81 | Frank | 409.01 | pending | 2026-09-22 05:53:03 | | 82 | Heidi | 409.01 | pending | 2026-09-22 05:53:03 | | 83 | Alice | 409.00 | processed | 2026-09-22 05:53:03 | | 84 | Grace | 409.01 | pending | 2026-09-22 05:53:03 | | 85 | Judy | 409.00 | processed | 2026-09-22 05:53:03 | | 86 | Charlie | 409.00 | pending | 2026-09-22 05:53:04 | | 87 | David | 539.00 | processed | 2026-09-22 05:53:04 | | 88 | Bob | 539.00 | pending | 2026-09-22 05:53:04 | | 89 | Ivan | 539.01 | pending | 2026-09-22 05:53:04 | | 90 | Alice | 539.01 | pending | 2026-09-22 05:53:04 | | 91 | Grace | 539.01 | processed | 2026-09-22 05:53:04 | | 92 | Grace | 539.01 | pending | 2026-09-22 05:53:04 | | 93 | Ivan | 539.01 | processed | 2026-09-22 05:53:04 | | 94 | Bob | 539.01 | pending | 2026-09-22 05:53:04 | | 95 | Charlie | 539.01 | pending | 2026-09-22 05:53:04 | | 96 | Ivan | 539.00 | pending | 2026-09-22 05:53:04 | | 97 | Frank | 539.00 | pending | 2026-09-22 05:53:04 | | 98 | Ivan | 539.00 | pending | 2026-09-22 05:53:04 | | 99 | Alice | 539.01 | processed | 2026-09-22 05:53:04 | | 100 | Eve | 539.00 | pending | 2026-09-22 05:53:04 | +-----+---------------+--------------+-----------+---------------------+ 100 rows in set (0.00 sec) # 有51条status=pending的数据 mysql> SELECT COUNT(*) FROM orders WHERE status='pending' ; +----------+ | COUNT(*) | +----------+ | 51 | +----------+ 1 row in set (0.00 sec)
创建数据处理程序:
1 kubectl create deploy update-orders --image=registry.cn-beijing.aliyuncs.com/dotbalo/mysql:process
第二步:创建ScaledObject 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 cat mysql-ScaledObject.yaml apiVersion: v1 kind: Secret metadata: name: mysql-secrets-keda namespace: default type: Opaque stringData: mysql_conn_str: root:password@tcp(mysql.default.svc.cluster.local:3306)/dukuan --- apiVersion: keda.sh/v1alpha1 kind: TriggerAuthentication metadata: name: keda-trigger-auth-mysql-secret namespace: default spec: secretTargetRef: - parameter: connectionString name: mysql-secrets-keda key: mysql_conn_str --- apiVersion: keda.sh/v1alpha1 kind: ScaledObject metadata: name: mysql-scaledobject namespace: default spec: scaleTargetRef: name: update-orders pollingInterval: 5 cooldownPeriod: 30 minReplicaCount: 0 maxReplicaCount: 5 triggers: - type: mysql metadata: queryValue: "5" query: "SELECT COUNT(*) FROM orders WHERE status='pending'" authenticationRef: name: keda-trigger-auth-mysql-secret
创建资源:
1 2 3 4 [root@k8s-master01 mysql]# kubectl create -f mysql-ScaledObject.yaml secret/mysql-secrets-keda created triggerauthentication.keda.sh/keda-trigger-auth-mysql-secret created scaledobject.keda.sh/mysql-scaledobject created
查看资源:
1 2 3 4 5 6 7 8 9 10 11 [root@k8s-master01 mysql]# kubectl get so NAME SCALETARGETKIND SCALETARGETNAME MIN MAX READY ACTIVE FALLBACK PAUSED TRIGGERS AUTHENTICATIONS AGE mysql-scaledobject apps/v1.Deployment update-orders 0 5 True False False False mysql keda-trigger-auth-mysql-secret 39s [root@k8s-master01 mysql]# kubectl get po NAME READY STATUS RESTARTS AGE insert-orders-job-zn9hv 0/1 Completed 0 68m mysql-56bff5bf4b-29lz4 1/1 Running 0 72m [root@k8s-master01 mysql]# kubectl get deployments.apps NAME READY UP-TO-DATE AVAILABLE AGE mysql 1/1 1 1 130m update-orders 0/0 0 0 57m
可以看到在业务空闲时,处理数据的程序update-orders副本数被缩容至0。
第三步:执行insert-orders程序写入数据 执行job向数据库中写入数据,观察update-orders程序副本数量的变化
1 2 [root@k8s-master01 mysql]# kubectl create job insert-orders-job1 \ --image=registry.cn-beijing.aliyuncs.com/dotbalo/mysql:insert
查看pod是否有扩容,可以看到update-orders的副本已经扩容为5个共同处理任务:
1 2 3 4 5 6 7 8 9 10 [root@k8s-master01 mysql]# kubectl get po NAME READY STATUS RESTARTS AGE insert-orders-job-zn9hv 0/1 Completed 0 73m insert-orders-job1-jvb8r 0/1 Completed 0 29s mysql-56bff5bf4b-29lz4 1/1 Running 0 77m update-orders-74785c856d-42l92 1/1 Running 0 17s update-orders-74785c856d-4b52w 1/1 Running 0 22s update-orders-74785c856d-7gkdv 0/1 ContainerCreating 0 2s update-orders-74785c856d-9t9qt 1/1 Running 0 17s update-orders-74785c856d-bt5hb 0/1 ContainerCreating 0 17s
当数据处理完成后会缩容到0个副本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # pod正在删除 [root@k8s-master01 mysql]# kubectl get po NAME READY STATUS RESTARTS AGE insert-orders-job-zn9hv 0/1 Completed 0 73m insert-orders-job1-jvb8r 0/1 Completed 0 58s mysql-56bff5bf4b-29lz4 1/1 Running 0 77m update-orders-74785c856d-42l92 1/1 Terminating 0 46s update-orders-74785c856d-4b52w 1/1 Terminating 0 51s update-orders-74785c856d-7gkdv 1/1 Terminating 0 31s update-orders-74785c856d-9t9qt 1/1 Terminating 0 46s update-orders-74785c856d-bt5hb 1/1 Terminating 0 46s # update-orders程序已经被缩至0副本 [root@k8s-master01 mysql]# kubectl get deployments.apps NAME READY UP-TO-DATE AVAILABLE AGE mysql 1/1 1 1 137m update-orders 0/0 0 0 64m
上述案例在实际的生产环境中需要注意一下问题:
避免全表扫描拖垮数据库
案例中的 SQL 为:SELECT COUNT(*) FROM orders WHERE status='pending'。在百万/千万级大表上,若没有合理索引,高频的 COUNT(*) 会触发慢查询甚至锁表。
生产要求 :必须在 status(或 status, created_at 组合索引)上建立高效索引,或者引入一个轻量级的计数维护表/Redis 计数器供 KEDA 探测。
控制 KEDA 探测频率与连接数
案例中 pollingInterval: 5(每 5 秒查一次)过于激进。多套业务同时伸缩时,KEDA 的轮询会占用大量 DB 活跃连接。
生产要求 :将 pollingInterval 调整为 15 到 30 秒;建议给 KEDA 分配一个只读且限制了最大并发连接数的专属只读账号(只赋予必要表/视图的 SELECT 权限)。
多 Pod 并发处理的数据争抢 与重复消费
案例中 update-orders 扩容到 5 个 Pod 后,多个 Pod 同时执行查询和处理。如果没有防并发机制,多个 Pod 会捞出相同的 pending 记录并重复处理。
生产要求:应该使用行级锁更新,乐观锁/分布式锁,业务幂等性
4.4ScaledJob实现任务处理 ScaledJob 是 KEDA(Kubernetes Event-driven Autoscaling)提供的一种自定义资源(Custom Resource Definition, CRD),用于对 Kubernetes 中 Job 进行事件驱动的自动扩缩容。
与 KEDA 的 ScaledObject(ScaledObject主要用来扩缩 Deployment、StatefulSet 等长期运行的工作负载)不同,ScaledJob 专门面向 批处理 / 一次性任务 场景。
工作原理是:根据外部事件源(如消息队列长度、Kafka 积压、Azure Queue、Redis 列表等)的指标,动态创建 Kubernetes Job 。每个 Job 通常处理一个(或少量)事件后结束,而不是像 Deployment 那样持续运行多个 Pod。
下面演示的案例是实现一个基于 Redis 队列的任务自动处理系统:当 Redis 队列中积累了一定数量的任务时,KEDA 自动创建 Kubernetes Job,由 Job 启动 Pod 执行任务;任务处理完成后,Pod 退出,Job 标记为 Complete。
第一步:环境准备 创建一个redis实例:
1 2 3 4 5 6 7 8 9 helm repo add bitnami https://charts.bitnami.com/bitnami helm upgrade --install redis bitnami/redis \ --set global.imageRegistry=docker.kubeasy.com \ --set global.redis.password=dukuan \ --set architecture=standalone \ --set master.persistence.enabled=false \ --version 20.1.6
参数
作用
helm repo add bitnami URL
添加 Bitnami Helm 仓库,名称为 bitnami。
upgrade --install
如果 Release 已存在则升级,不存在则安装。
redis
Helm Release 名称,后续可通过 helm delete redis 删除。
bitnami/redis
使用 Bitnami 仓库中的 Redis Chart。
--set global.imageRegistry=...
将镜像仓库地址设置为 docker.kubeasy.com,用于从指定镜像仓库拉取镜像。
--set global.redis.password=dukuan
设置 Redis 密码。
--set architecture=standalone
使用单实例架构,不部署 Redis Sentinel 或 Redis Cluster。
--set master.persistence.enabled=false
关闭 Redis 主节点的持久化 PVC,测试数据只保存在容器运行期间的内存中。
--version 20.1.6
指定 Redis Helm Chart 版本,不是 Redis 服务端版本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 [root@k8s-master01 ~]# helm upgrade --install redis bitnami/redis --set global.imageRegistry=docker.kubeasy.com --set global.redis.password=dukuan --set architecture=standalone --set master.persistence.enabled=false --version 20.1.6 Release "redis" has been upgraded. Happy Helming! NAME: redis LAST DEPLOYED: Sat Sep 26 18:05:24 2026 NAMESPACE: default STATUS: deployed REVISION: 2 DESCRIPTION: Upgrade complete TEST SUITE: None NOTES: CHART NAME: redis CHART VERSION: 20.1.6 APP VERSION: 7.4.0 ** Please be patient while the chart is being deployed ** Redis® can be accessed via port 6379 on the following DNS name from within your cluster: redis-master.default.svc.cluster.local To get your password run: export REDIS_PASSWORD=$(kubectl get secret --namespace default redis -o jsonpath="{.data.redis-password}" | base64 -d) To connect to your Redis® server: 1. Run a Redis® pod that you can use as a client: kubectl run --namespace default redis-client --restart='Never' --env REDIS_PASSWORD=$REDIS_PASSWORD --image docker.kubeasy.com/bitnami/redis:7.4.0-debian-12-r4 --command -- sleep infinity Use the following command to attach to the pod: kubectl exec --tty -i redis-client \ --namespace default -- bash 2. Connect using the Redis® CLI: REDISCLI_AUTH="$REDIS_PASSWORD" redis-cli -h redis-master To connect to your database from outside the cluster execute the following commands: kubectl port-forward --namespace default svc/redis-master 6379:6379 & REDISCLI_AUTH="$REDIS_PASSWORD" redis-cli -h 127.0.0.1 -p 6379 WARNING: There are "resources" sections in the chart not set. Using "resourcesPreset" is not recommended for production. For production installations, please set the following values according to your workload needs: - replica.resources - master.resources +info https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
Redis服务已经成功运行:
1 2 3 dukuan[root@k8s-master01 ~]# kubectl get po NAME READY STATUS RESTARTS AGE redis-master-0 1/1 Running 0 91m
第二步:创建 Secret 和 TriggerAuthentication 这一步是让 KEDA 有权限使用 Redis 密码连接 Redis,读取队列长度。
创建Secret,来存储redis的密码:
1 2 3 4 5 6 7 8 9 [root@k8s-master01 28 -keda ] kind: Secret apiVersion: v1 metadata: name: redis-so-secret type: Opaque stringData: REDIS_USER: "" REDIS_PASSWORD: "dukuan"
1 2 [root@k8s-master01 28-keda]# kubectl create -f redis-secret.yaml secret/redis-so-secret created
创建TriggerAuthentication:
1 2 3 4 5 6 7 8 9 10 11 12 13 [root@k8s-master01 28 -keda ] apiVersion: keda.sh/v1alpha1 kind: TriggerAuthentication metadata: name: redis-so-ta spec: secretTargetRef: - parameter: username name: redis-so-secret key: REDIS_USER - parameter: password name: redis-so-secret key: REDIS_PASSWORD
1 2 [root@k8s-master01 28-keda]# kubectl create -f redis-so-ta.yaml triggerauthentication.keda.sh/redis-so-ta created
第三步:向Redis队列中写入测试任务 进入redis容器中执行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [root@k8s-master01 28-keda]# kubectl exec -ti redis-master-0 -- bash I have no name!@redis-master-0:/data$ redis-cli 127.0.0.1:6379> auth dukuan OK 127.0.0.1:6379> LPUSH test_list "t1" "t2" "t3" (integer) 3 127.0.0.1:6379> LRANGE test_list 0 -1 1) "t3" 2) "t2" 3) "t1" 127.0.0.1:6379> RPOP test_list "t1" 127.0.0.1:6379> RPOP test_list "t2" 127.0.0.1:6379> RPOP test_list "t3"
第四步:创建 ScaledJob 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 apiVersion: keda.sh/v1alpha1 kind: ScaledJob metadata: name: redis-scaledjob spec: jobTargetRef: parallelism: 1 completions: 1 backoffLimit: 3 template: spec: restartPolicy: Never containers: - name: redis-consumer image: registry.cn-beijing.aliyuncs.com/dotbalo/redis:process pollingInterval: 30 successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 3 maxReplicaCount: 5 triggers: - type: redis metadata: address: redis-master.default.svc.cluster.local:6379 listName: test_list listLength: "5" authenticationRef: name: redis-so-ta
创建资源:
1 2 3 4 5 [root@k8s-master01 28-keda]# kubectl create -f redis-sj.yaml scaledjob.keda.sh/redis-scaledjob created [root@k8s-master01 28-keda]# kubectl get sj NAME MIN MAX READY ACTIVE PAUSED TRIGGERS AUTHENTICATIONS AGE redis-scaledjob 5 True False False redis redis-so-ta 6s
第五步:向redis中写入数据观察pod 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 127.0.0.1:6379> LPUSH test_list "t1" "t2" "t3" (integer) 3 127.0.0.1:6379> LPUSH test_list "t1" "t2" "t3" (integer) 6 127.0.0.1:6379> LPUSH test_list "t1" "t2" "t3" (integer) 9 127.0.0.1:6379> LRANGE test_list 0 -1 1) "t3" 2) "t2" 3) "t1" 4) "t3" 5) "t2" 6) "t1" 7) "t3" 8) "t2" 9) "t1" 127.0.0.1:6379>
可以看到已经触发任务,创建不同的job来处理任务,执行完成就退出了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 [root@k8s-master01 28-keda]# kubectl get jobs.batch NAME STATUS COMPLETIONS DURATION AGE redis-scaledjob-5j92b Complete 1/1 13s 17s redis-scaledjob-9fj25 Complete 1/1 15s 17s [root@k8s-master01 28-keda]# kubectl get po NAME READY STATUS RESTARTS AGE redis-master-0 1/1 Running 0 4h5m redis-scaledjob-5j92b-m6fqd 0/1 Completed 0 19s redis-scaledjob-9fj25-599pg 0/1 Completed 0 19s # 查看日志 kubectl logs -f redis-scaledjob-9fj25-599pg Processing message: t2 Message processed successfully. Processing message: t1 Message processed successfully. Processing message: t3 Message processed successfully. Processing message: t2 Message processed successfully. No more messages in the queue. Exiting...
在实际生产环境中,作为 Kubernetes 运维人员,你通常不需要编写 Job 内部的消息消费、图片处理、视频转码等业务代码。你主要负责让 KEDA 根据队列指标自动创建 Job,并确保这些 Job 能够正常运行。
但这不代表运维完全不需要关心 Job 如何处理消息。
运维必须知道开发程序的基本消费行为。 例如,一个 Pod 是处理一条消息后退出,还是循环处理到队列为空才退出;任务失败时返回什么退出码;程序是否支持安全重试。这些会直接影响 ScaledJob 的配置和运行效果。
一个特别容易忽略的问题:
假设 Redis 队列有 100 条消息,KEDA 创建了 5 个 Job。
如果开发的程序每个 Pod 只处理一条消息就退出,那么最多只处理 5 条消息,剩余 95 条还在队列中。后续 KEDA 需要再次检测并创建 Job。
如果开发的程序每个 Pod 会循环处理消息,直到队列为空才退出,那么这 5 个 Job 可以持续消费队列中的任务。
两种程序对应的扩缩容行为、任务吞吐量和资源消耗会明显不同。
因此,运维不必编写消费代码,但需要了解消费程序的执行模型,才能正确设置 listLength、maxReplicaCount 和 Job 的完成策略。