在 Kubernetes 上使用 Jmeter 執行壓力測試_Kubernetes中文社群
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