基于KEDA的下一代弹性伸缩

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 # HPA 对象的名称
namespace: default # 部署的目标命名空间,HPA有命名空间隔离,要和扩容的资源在一个空间
spec:
# 目标对象配置
scaleTargetRef:
apiVersion: apps/v1 # 目标资源的 API 版本
kind: Deployment # 目标资源类型(通常为 Deployment 或 StatefulSet)
name: web-app-deployment # 需要被自动扩缩容的目标 Deployment 名称

# 副本数限制
minReplicas: 2 # 自动缩容时的最小 Pod 副本数
maxReplicas: 10 # 自动扩容时的最大 Pod 副本数

# 触发扩缩容的指标列表(支持多指标并存,HPA 会取计算出的最大副本数)
metrics:
# 示例 1: 基于 CPU 平均利用率百分比
- type: Resource # 指标类型:Resource 代表系统资源(CPU/Memory)
resource:
name: cpu # 资源名称:cpu
target:
type: Utilization # 目标类型:Utilization 表示按利用率百分比计算
averageUtilization: 70 # 目标平均利用率:70%(基于Pod配置的resources.requests.cpu 计算)

# 示例 2: 基于内存平均使用量绝对值
- type: Resource
resource:
name: memory # 资源名称:memory
target:
type: AverageValue # 目标类型:AverageValue 表示按具体的绝对值计算
averageValue: 500Mi # 单个 Pod 的目标平均内存使用量(达到此值后会触发扩容)

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 # 执行的目标命令:Linux 压力测试工具
- --vm # 压测类型:指定启动内存(Virtual Memory)测试进程
- '1' # 进程数量:启动 1 个内存压测 worker
- --vm-bytes # 内存配额:指定每个 worker 分配的内存大小
- 64M # 目标数值:分配 64MB 内存
- --verbose # 日志输出:打印详细的压测过程与状态日志
- --vm-keep # 内存策略:持续占用内存(重写 dirty page),防止释放后重新分配
- --timeout # 控制运行时长的参数
- '3600' # 持续时长:维持压测运行的秒数(1 小时)

resources:
limits:
cpu: 500m
memory: 1024Mi
requests: #必须要添加requests配置, HPA 基于此值计算百分比
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 #这里指定的是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 #定义使用率超过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 # ScaledObject 的名称
namespace: default # 所在命名空间(需与目标工作负载在同一个命名空间中)
spec:
# 目标缩放对象
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: rabbitmq-consumer # 对应被扩缩容的 Deployment 名称

# 轮询与冷却时间
pollingInterval: 30 # 轮询周期(秒):KEDA 每隔 30 秒检查一次指标
cooldownPeriod: 300 # 冷却等待时间(秒):指标归零后,等待 5 分钟再执行“缩容到 0”

# 副本数范围
minReplicaCount: 0 # 最小副本数:设为 0 即开启“Scale-to-Zero”(无消息时完全停止 Pod)
maxReplicaCount: 30 # 最大副本数:防暴涨上限

# 触发器列表(定义依据什么事件进行伸缩)
triggers:
- type: rabbitmq # 触发器类型:使用内置的 RabbitMQ Scaler
metadata:
protocol: amqp # 通信协议
queueName: order-tasks # 监听的目标队列名称
mode: QueueLength # 统计模式:基于当前队列排队长度
value: "20" # 目标阈值:期望每个 Pod 平均分摊处理 20 条及以上消息时触发扩容
authenticationRef:
name: keda-trigger-auth # 引用的 TriggerAuthentication 凭据名称

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          # 【KEDA 规范】KEDA 官方定义的 API 版本
kind: ScaledJob # 【KEDA 规范】固定资源类型名称
metadata:
name: image-process-scaledjob # 【自定义】当前 ScaledJob 的名称
namespace: default # 【集群来源】目标命名空间,需与关联资源在同一个空间
spec:
# 🏃 实际生成的 Kubernetes Job 模板
jobTargetRef:
parallelism: 1 # 【自定义】单个 Job 内部同时运行的 Pod 数量
completions: 1 # 【自定义】单个 Job 需要成功完成的 Pod 数量
backoffLimit: 2 # 【自定义】失败重试最大次数
template:
spec:
restartPolicy: Never # 【K8s 规范】Job 的重启策略,通常为 Never 或 OnFailure
containers:
- name: processor # 【自定义】容器名
image: myrepo/resizer:v1 # 【镜像仓库来源】镜像中心真实存在的镜像地址与版本
resources:
requests:
cpu: 500m # 【自定义】根据程序性能测算预估的需求
memory: 512Mi # 【自定义】根据程序性能测算预估的需求
# 扩缩容策略配置
pollingInterval: 30 # 【自定义】KEDA 轮询指标的间隔时间(秒)
successfulJobsHistoryLimit: 5 # 【自定义】保留已成功完成的 Job 数量上限
failedJobsHistoryLimit: 5 # 【自定义】保留失败的 Job 数量上限
maxReplicaCount: 20 # 【自定义】同时运行的最大 Job 数量上限
scalingStrategy:
strategy: "default" # 【KEDA 规范】扩缩策略,可选 default 或 custom

# 触发器配置(事件驱动源)
triggers:
- type: rabbitmq # 【KEDA 规范】触发器类型,必须是 KEDA 支持的 Scaler 名称
metadata:
protocol: amqp # 【协议来源】与连接的 MQ 协议一致
queueName: image-resize-tasks # 【业务/MQ 来源】RabbitMQ 中真实存在的队列名称
mode: QueueLength # 【KEDA 规范】统计模式,固定枚举值
value: "1" # 【自定义】阈值:此处设为 1,代表每有 1 条消息就启动 1 个 Job
authenticationRef:
name: keda-trigger-auth # 【K8s 资源来源】必须对应集群中已创建的 TriggerAuthentication 资源名

2.2.3TriggerAuthentication

在 KEDA 的架构中,TriggerAuthentication(触发器认证凭据)是一个非常关键的解耦与安全组件 🔐。

当我们配置 ScaledObject 或 ScaledJob 去监听外部系统(例如 RabbitMQ、Kafka、Redis 或云厂商的队列服务)时,KEDA 需要登录凭据去查询指标。

如果直接把账号、密码或连接串明文写在每个 ScaledObject 里存在安全风险

TriggerAuthentication 正是用来在指标触发器(Scaler)和实际的凭据提供方(如 Kubernetes Secret、云厂商 IAM 角色、HashiCorp Vault 等)之间架起桥梁 。它把“怎么认证”从“监控什么指标”中剥离出来。

在 KEDA 中,TriggerAuthentication(以及 ClusterTriggerAuthentication)支持多种认证参数来源。你提到的三种主要方式是:

  • secretTargetRef(从 Kubernetes Secret 读取)

  • configMapTargetRef(从 Kubernetes ConfigMap 读取)

  • env(从目标工作负载的容器环境变量读取)

这三种方式可以单独使用,也可以混合使用(在同一个 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 #定义的secret名字,在TriggerAuthentication中会被引用
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 # 必须与 ScaledObject 同命名空间(ClusterTriggerAuthentication 除外)
spec:
secretTargetRef:
- parameter: hsot # KEDA 对应Scaler的参数名,每个服务的都不同,具体查阅官网
name: rabbitmq-credentials # 引用上述 Secret的名字
key: rabbitmq-connection-string # 上述Secret 中的 key

在 ScaledObject 中引用:

1
2
3
4
5
6
7
triggers:
- type: rabbitmq # 或其他 scaler
metadata:
queueName: hello
# 注意:username / password 不用再写在 metadata 里
authenticationRef: #引用TriggerAuthentication
name: keda-trigger-auth-rabbitmq # name的值就是引用的TriggerAuthentication资源的名字

注意事项:

  • 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 #具体的值由具体的 Scaler 决定
name: my-auth-configmap #引用的ConfigMap 名称
key: username #ConfigMap 的 data 部分中的key
- 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。

    确定步骤:

    1. 先确定你要用哪个 scaler(例如 rabbitmq、kafka、prometheus、azure-servicebus 等)。
    2. 去 KEDA 官方文档查看该 scaler 的 Authentication Parameters 部分。
    3. 文档里写的参数名(如 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 # scaler 需要的参数名
name: MY_USERNAME # 容器中的环境变量名
containerName: my-container # 可选。默认使用 ScaledObject 的 envSourceContainerName 或第一个容器
- 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: # 从secret来
name: my-secret # 从名字是my-secret的secret获取
key: username #从username的key中获取的值赋值给环境变量MY_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 # KEDA ScaledObject 资源使用的 API 版本。
kind: ScaledObject # KEDA 自定义资源类型。
metadata:
name: my-app-cron-scaler # 当前 ScaledObject 资源的名称。必须和下面 scaleTargetRef.name 指向的目标工作负载处于同一命名空间。
spec:
# ScaledObject 的具体扩缩容配置。
scaleTargetRef:
name: my-app # 指定 KEDA 要控制的工作负载,这里的 my-app 必须和实际 Deployment 的 metadata.name 完全一致。
minReplicaCount: 1 # 最小副本数,表示 KEDA 管理该工作负载时,正常情况下最少保留 1 个 Pod。
maxReplicaCount: 10 # 最大副本数,表示 KEDA/HPA 最多允许将 my-app 扩容到 10 个副本。
# 当前配置:
# 最大 = 10
# desiredReplicas 通常应该设置在:
# minReplicaCount <= desiredReplicas <= maxReplicaCount
# 当前 1 <= 3 <= 10,因此配置合理。

triggers:
# 定义触发扩缩容的条件。
# 一个 ScaledObject 可以配置多个 trigger。
# 例如以后还可以同时加入 CPU、Prometheus、Kafka 等触发器。
- type: cron #指定当前使用 Cron Scaler,表示根据时间段进行扩缩容。
metadata:
timezone: Asia/Shanghai # Cron 时间使用的时区,Asia/Shanghai = 中国标准时间(UTC+8)。
start: "30 14 * * *" # Cron 时间表达式,表示扩容时间窗口开始时间。
# 格式:
# 分钟 小时 日 月 星期
# 30 14 * * *
# │ │
# │ └── 14点
# └───── 30分
# 即:
# 每天北京时间 14:30 开始。
# Cron 中小时使用 24 小时制:
end: "50 14 * * *" # Cron 时间表达式,表示扩容时间窗口结束时间。
desiredReplicas: "3" # 在 start ~ end 时间窗口内期望维持的副本数,minReplicaCount <= desiredReplicas <= maxReplicaCount

创建资源:

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
# 1. 凭据密钥(实际生产请使用密文管理系统或修改默认密码)
apiVersion: v1
kind: Secret
metadata:
name: rabbitmq-secret
type: Opaque
stringData:
default-user: "admin"
default-pass: "asdqwe123"
---

# 4. 业务通信 Service(应用连接与监控抓取)
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
---
# 5. StatefulSet 工作负载
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" # ← 北京时间(K8s ≥ 1.27)
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
# ============================================================
# 1. Secret - 存储 RabbitMQ 连接信息(凭证分离写法)
# ============================================================
apiVersion: v1
kind: Secret
metadata:
name: rabbitmq-auth-secret # 定义的是该Secret的名字
# 建议补充:namespace: default # 强烈建议显式指定命名空间,避免跨命名空间找不到资源
type: Opaque
stringData:
# host 只放纯地址,不包含用户名密码(推荐做法)
# 格式:amqp://<host>:<port>[/<vhost>]
# 注意:如果使用了非默认 vhost,请写成 amqp://...:5672/myvhost
host: "amqp://rabbitmq.default.svc.cluster.local:5672"

# 用户名和密码单独存放,更安全,也方便轮换
username: "admin"
password: "asdqwe123" # 生产环境请使用强密码
---
# ============================================================
# 2. TriggerAuthentication - 把 Secret 映射给 KEDA Scaler
# ============================================================
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication #资源类型是TriggerAuthentication
metadata:
name: keda-trigger-auth-rabbitmq-conn #定义资源的名字,会在ScaledObject的spec.triggers.authenticationRef中引用
# 建议补充:namespace: default
spec:
secretTargetRef:
# 映射 host 参数(纯连接地址)
- parameter: host # parameter配置的host需要和keda官网保持一致,每个服务的字段可能不同。
name: rabbitmq-auth-secret #引用的Secret的名字
key: host #把名字是rabbitmq-auth-secret的Secret中key是host的值映射给parameter中指定的host

# 映射 username 参数
# 注意:username 和 password 必须成对出现,否则 KEDA 会报错
- parameter: username
name: rabbitmq-auth-secret
key: username

# 映射 password 参数
# 官方行为:如果这里提供了 username + password,会覆盖 host 字符串里可能存在的用户和密码凭证
- 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
# ============================================================
# 3. ScaledObject - 定义基于 RabbitMQ 队列的自动伸缩规则
# ============================================================
apiVersion: keda.sh/v1alpha1
kind: ScaledObject #这里定义资源的类型是ScaledObject
metadata:
name: rabbitmq-scaledobject
# 建议补充:namespace: default
spec:
scaleTargetRef:
name: rabbitmq-consumer # 要被伸缩的 Deployment 名称(必须已存在),该deploy资源要和ScaledObject在同一个命名空空间

minReplicaCount: 0 # 允许缩到 0(事件驱动常用)
maxReplicaCount: 10 # 最大副本数,防止无限扩容
pollingInterval: 10 # 每隔多少秒检查一次队列(默认 30)
cooldownPeriod: 300 # 缩容冷却时间(秒),防止频繁抖动

triggers:
- type: rabbitmq
metadata:
# 使用 AMQP 协议查询队列长度
# 注意:
# - mode: QueueLength 推荐用 amqp 或 auto
# - 如果改成 mode: MessageRate,必须改成 protocol: http(需要 Management API)
protocol: amqp

# 要监控的队列名称(必须与 RabbitMQ 中真实存在的队列名完全一致)
queueName: hello

# 伸缩模式:按队列中的消息数量伸缩
# 可选值:QueueLength / MessageRate / DeliverGetRate 等
mode: QueueLength

# 目标值:每个 Pod 期望处理的消息数量
# 示例:队列有 150 条消息时,期望副本数 ≈ 150 / 50 = 3
# 注意:必须是字符串类型
value: "50"

# 引用上面的 TriggerAuthentication(推荐方式,凭证不进入 ScaledObject)
authenticationRef:
name: keda-trigger-auth-rabbitmq-conn #这里引用的是上面创建的TriggerAuthentication资源的名字

创建上述资源:

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 # 显式指定命名空间,避免应用到非 default 空间导致找不到 Secret
type: Opaque
stringData:
# MySQL Go-SQL-Driver 格式连接串:<username>:<password>@tcp(<host>:<port>)/<dbname>
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 # 必须与 ScaledObject 在同一命名空间
spec:
secretTargetRef:
- parameter: connectionString # KEDA MySQL scaler 识别的目标参数名(固定值)
name: mysql-secrets-keda # 引用的 Secret 资源名称
key: mysql_conn_str # Secret 中存储连接串的 key
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: mysql-scaledobject
namespace: default # 必须与目标 Deployment (update-orders) 在同一命名空间
spec:
scaleTargetRef:
name: update-orders # 要弹性扩缩容的目标 Deployment 名称
pollingInterval: 5 # 轮询探测周期(秒):每 5 秒执行一次 SQL 检查指标
cooldownPeriod: 30 # 缩容冷却等待时间(秒):指标回落后等待 30 秒才开始缩容,防止震荡
minReplicaCount: 0 # 最小副本数(业务空闲时保留 1 个 Pod)
maxReplicaCount: 5 # 最大副本数(流量洪峰时最多扩容至 5 个 Pod)
triggers:
- type: mysql # 触发器类型:使用 MySQL 查询结果作为指标
metadata:
# 目标指标阈值:每个 Pod 期望分担的指标量,建议设为业务合理的整数(例如 5 或 10)
queryValue: "5"
# 查询 SQL:必须且只能返回一行一列的单值(标量),作为当前指标总值
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&reg; 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&reg; server:

1. Run a Redis&reg; 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&reg; 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]# cat redis-secret.yaml
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]# cat redis-so-ta.yaml
apiVersion: keda.sh/v1alpha1 #KEDA 提供的 API 版本。
kind: TriggerAuthentication #创建 KEDA 认证对象。
metadata:
name: redis-so-ta #认证对象名称为 redis-so-ta。
spec:
secretTargetRef: # 指定需要从 Secret 中获取的认证参数。
- parameter: username #将 Secret 中的值映射为 Redis Scaler 使用的用户名参数。
name: redis-so-secret #指定要读取的 Secret 名称。
key: REDIS_USER #指定 Secret 中的用户名键。
- 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 # ScaledJob 资源名称
spec:
# 定义要创建的 Kubernetes Job 的模板(对应 batch/v1 JobSpec)
jobTargetRef:
parallelism: 1 # 每个 Job 中并行运行的 Pod 数量(通常设为 1)
completions: 1 # 每个 Job 需要成功完成的 Pod 数量(通常设为 1)
backoffLimit: 3 # Job 失败后最多重试次数,超过后标记为 Failed(默认 6)
template: # Pod 模板(必须)
spec:
restartPolicy: Never # 【必须】Job 要求必须是 Never 或 OnFailure,不能是 Always
containers:
- name: redis-consumer
image: registry.cn-beijing.aliyuncs.com/dotbalo/redis:process
# 建议在这里添加必要的 env、command、args、volumeMounts 等,让容器能正确消费 Redis list

pollingInterval: 30 # 轮询触发器指标的间隔(秒),默认 30
successfulJobsHistoryLimit: 3 # 保留成功完成的 Job 数量,默认 100
failedJobsHistoryLimit: 3 # 保留失败 Job 的数量,默认 100
maxReplicaCount: 5 # 同时运行的最大 Job 数量(并发上限),默认 100

# 触发器列表(可配置多个)
triggers:
- type: redis # 使用 Redis List 长度作为扩缩容指标
metadata:
address: redis-master.default.svc.cluster.local:6379 # Redis 地址(host:port 格式)
listName: test_list # 要监控的 Redis List 名称
listLength: "5" # 目标平均长度,每达到该值大致创建 1 个 Job
authenticationRef:
name: redis-so-ta # 引用 TriggerAuthentication的名字,用于提供密码等敏感信息

创建资源:

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 的完成策略。