在 Kubernetes 上使用 Jmeter 运行压力测试
Kubernetes 的资源和任务调度能力,能给自动化测试提供相当大力的支持,这里以 Jmeter 为例,讲讲如何在 Kubernetes 中使用 Jmeter 进行简单的性能测试。
开始之前
录制任务:本文所用镜像为 Jmeter 3.x 版本,建议提前录制一个简单的测试任务进行下面的操作。
支持 Jobs 的 Kubernetes 集群,以及缺省的 StorageClass 支持,能够实现 PVC 的动态供应。
互联网连接。
试验内容
- 搭建一个 Web DAV 服务,用于提供给 Jmeter 输入输出场所,也便于日后 CI/CD 工具的案例输入或结果输出。
- 运行单实例的 Jmeter 测试任务。
- 运行集群形式的 Jmeter 测试任务。
预备存储
这一步骤并非强制,完全可以通过 scp 或者 mount 等其他方式来实现
这里我们做一个 Web DAV 服务,挂载一个 PVC,在其中分为 input 和 output 两个目录,实际使用过程中,可以进一步按照任务或者 Job 对目录进行更详尽的规划。
首先创建名为jmeter-task
的存储卷:
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: jmeter-task
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
存储卷创建之后,可以使用 cadaver
或者 WinSCP
等工具去建立目录。
接下来上传 *.jmx
文件,到 input
目录之中,这里我录制的一个持续访问京东首页的任务,命名为 jd.jmx
。
单实例测试
单实例测试很容易,使用 Kubernetes 的 Job 方式即可:
apiVersion: batch/v1
kind: Job
metadata:
name: jmeter
spec:
template:
metadata:
name: jmeter
spec:
restartPolicy: Never
containers:
- name: jmeter
image: dustise/jmeter-server
command:
- "/jmeter/bin/jmeter"
- "-n"
- "-t"
- "/jmeter/input/jd.jmx"
- "-l"
- "/jmeter/output/log"
- "-j"
- "/jmeter/output/joker"
volumeMounts:
- name: data
mountPath: /jmeter/input
subPath: input
- name: data
mountPath: /jmeter/output
subPath: output
volumes:
- name: data
persistentVolumeClaim:
claimName: jmeter-task
上面的定义中:
任务 Pod 加载了存储卷
jmeter-task
。使用subPath
指令,分别挂载了输入和输出目录。使用
-n -t
的方式运行测试任务,并把输出文件定位到output
目录中。
接下来就可以使用 kubectl create -f jobs1.yaml
来运行这一任务。
任务启动之后,可以:
使用
kubectl get jobs
来查看任务运行状况。kubectl get pods --show-all
查看任务 Pod。kubectl logs -f [pod name
查看任务输出。
最后任务会变成完成状态,就可以在 Web DAV 中查看任务报告了。
集群测试
Jmeter 可以使用控制台+负载机的形式,使用多个节点进行压力测试,这里需要解决的一个最重要问题就是,在指派任务给负载机时,Jmeter 需要使用 -R host:port
的参数,来指定任务要调用的负载机。这一通信是无法通过 Kubernetes 方式的 Service 来完成的。必须建立 Pod 之间的通信,而 Pod 的主机名地址是很飘逸的,同时,我们还是希望负载节点的数量能够实现较为自由的伸缩,因此解决方法就只有 StatefulSet 了。
这个 YAML 很长,所以放在最后了,说说其中的要点:
注解中的
security.alpha.kubernetes.io/sysctls
:实际运行中,jmeter 负载机是需要对内核参数进行一点调整的,Pod 中可以用这一方式进行调整,https://kubernetes.io/docs/concepts/cluster-administration/sysctl-cluster/
中有更详细的关于这方面的内容讲解。spec.affinity
:这里设置 Jmeter Pod 尽量分布在不同节点上。RMI_HOST
环境变量:使用每个 Pod 的 IP 为这一变量赋值。Service:利用这个 Headless 服务,为每个 Pod 提供主机名支持。
启动这个 Statefulset 之后,会看到规律创建的 Pod 名称:
jnode-0 1/1 Running 0 1h
jnode-1 1/1 Running 0 1h
对应的主机名称就应该是 jnode-0.jfarm,jnode-1.jfarm。所以上面的 job.yaml
可以新增 -R jnode-0.jfarm:1099,jnode-1.jfarm:1099
即可。
使用 kubectl create
启动任务之后,查看该任务 Pod 的日志,会出现大致这样的内容:
Creating summariser <summary>
Created the tree successfully using /jmeter/input/jd.jmx
Configuring remote engine: jnode-0.jfarm:1099
Configuring remote engine: jnode-1.jfarm:1099
Starting remote engines
Starting the test @ Thu Nov 16 07:24:14 GMT 2017 (1510817054558)
Remote engines have been started
Waiting for possible Shutdown/StopTestNow/Heapdump message on port 4445
summary + 302 in 00:01:16 = 4.0/s Avg: 2967 Min: 2627 Max: 5457 Err: 0 (0.00%) Active: 0 Started: 20 Finished: 20
summary + 98 in 00:00:00 = 3062.5/s Avg: 3270 Min: 2635 Max: 7192 Err: 0 (0.00%) Active: 0 Started: 20 Finished: 20
summary = 400 in 00:01:16 = 5.3/s Avg: 3041 Min: 2627 Max: 7192 Err: 0 (0.00%)
Tidying up remote @ Thu Nov 16 07:25:31 GMT 2017 (1510817131966)
... end of run
可以看到,成功配置远程负载服务器之后,测试开始,最后成功完成。
Statefulset 源码
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: jnode
labels:
app: jmeter
component: node
spec:
serviceName: jfarm
replicas: 2
selector:
matchLabels:
app: jmeter
component: node
template:
metadata:
labels:
app: jmeter
component: node
annotations:
security.alpha.kubernetes.io/sysctls: net.ipv4.ip_local_port_range=10000 65000,net.ipv4.tcp_syncookies=1
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
topologyKey: kubernetes.io/hostname
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- jmeter
- key: component
operator: In
values:
- node
restartPolicy: Always
containers:
- name: jmeter
image: dustise/jmeter-server
ports:
- name: server
containerPort: 1099
- name: rmi
containerPort: 20000
env:
- name: RMI_HOST
valueFrom:
fieldRef:
fieldPath: status.podIP
---
apiVersion: v1
kind: Service
metadata:
name: jfarm
labels:
app: jmeter
spec:
clusterIP: None
ports:
- port: 1099
name: server
selector:
app: jmeter
component: node