Add a Chart for Locust Distributed Load Testing (#637)

* Add first locust chart draft

* Move to stable

* Improve post installation NOTES

* Enable Health Check on locust master

* Enable hard limit on Kubernetes Resources

* Ensure master is ready before starting workers

* Fix quotes for Worker deployment
This commit is contained in:
Vincent De Smet
2017-04-03 15:24:05 -07:00
committed by Lachlan Evenson
parent e4b2d1e441
commit d763d9933c
10 changed files with 333 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
name: locust
description: A chart for locust distributed load testing
version: 0.1.0
sources:
- https://github.com/honestbee/distributed-load-testing
+71
View File
@@ -0,0 +1,71 @@
# Locust Helm Chart
This is a templated deployment of [Locust](locust.io) for Distributed Load
testing using Kubernetes.
## Pre Requisites:
* Requires (and tested with) helm `v2.1.2` or above.
## Chart details
This chart will do the following:
* Convert all files in `tasks/` folder into a configmap
* Create a Locust master and Locust worker deployment with the Target host
and Tasks file specified.
### Installing the chart
To install the chart with the release name `locust-nymph` in the default namespace:
```bash
helm repo add incubator http://storage.googleapis.com/kubernetes-charts-incubator
helm install -n locust-nymph --set master.config.target-host=http://site.example.com incubator/locust
```
| Parameter | Description | Default |
| ---------------------------- | ---------------------------------- | ----------------------------------------------------- |
| `Name` | Locust master name | `locust` |
| `image.repository` | Locust container image name | `quay.io/honestbee/locust` |
| `image.tag` | Locust Container image tag | `0.7.5` |
| `service.type` | k8s service type exposing master | `NodePort` |
| `service.nodePort` | Port on cluster to expose master | `0` |
| `master.config.target-host` | locust target host | `http://site.example.com` |
| `worker.config.locust-script`| locust script to run | `/locust-tasks/tasks.py` |
| `worker.replicaCount` | Number of workers to run | `2` |
Specify parameters using `--set key=value[,key=value]` argument to `helm install`
Alternatively a YAML file that specifies the values for the parameters can be provided like this:
```bash
$ helm install --name my-release -f values.yaml incubator/locust
```
You can start the swarm from the command line using Port forwarding as follows:
Get the Locust URL following the Post Installation notes.
for example:
```bash
export LOCUST_URL=http://127.0.0.1:8089
```
Start / Monitor & Stop the Locust swarm via the web panel or with following commands:
Start:
```bash
curl -XPOST $LOCUST_URL/swarm -d"locust_count=100&hatch_rate=10"
```
Monitor:
```bash
watch -n 1 "curl -s $LOCUST_URL/stats/requests | jq -r '[.user_count, .total_rps, .state] | @tsv'"
```
Stop:
```bash
curl $LOCUST_URL/stop
```
+12
View File
@@ -0,0 +1,12 @@
from locust import HttpLocust, TaskSet, task
import json, requests
class ElbTasks(TaskSet):
@task(1)
def status(self):
self.client.get("/status")
class ElbWarmer(HttpLocust):
task_set = ElbTasks
min_wait = 1000
max_wait = 3000
+24
View File
@@ -0,0 +1,24 @@
{{ .Chart.Name }} installed!
Get the Locust URL to visit by running these commands in the same shell:
{{- if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get svc -n {{ .Release.Namespace }} {{ template "locust.master-svc" . }} -o jsonpath='{.spec.ports[?(@.name=="{{ .Values.service.name }}")].nodePort}')
export NODE_IP=$(kubectl get no -o jsonpath="{.items[0].status.addresses[0].address}")
export LOCUST_URL=http://$NODE_IP:$NODE_PORT/
{{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status by running 'kubectl get svc -n {{ .Release.Namespace }} -w {{ template "locust.master-svc" . }}'
export SERVICE_IP=$(kubectl get svc -n {{ .Release.Namespace }} {{ template "locust.master-svc" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
export LOCUST_URL=http://$SERVICE_IP:{{ .Values.service.externalPort }}/
{{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get po --namespace {{ .Release.Namespace }} -l "app={{ template "locust.fullname" . }},component=master" -o jsonpath="{.items[0].metadata.name}")
export LOCUST_URL=http://127.0.0.1:{{ .Values.service.externalPort }}
kubectl port-forward $POD_NAME {{ .Values.service.externalPort }}:{{ .Values.service.externalPort }} &
{{- end }}
For more information on Distributed load testing on Kubernetes using Locust, visit:
https://cloud.google.com/solutions/distributed-load-testing-using-kubernetes
+27
View File
@@ -0,0 +1,27 @@
{{/* vim: set filetype=mustache: */}}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
*/}}
{{- define "locust.fullname" -}}
{{- printf "%s-%s" .Release.Name "locust" | trunc 63 -}}
{{- end -}}
{{- define "locust.master-svc" -}}
{{- printf "%s-%s" .Release.Name "master-svc" | trunc 63 -}}
{{- end -}}
{{- define "locust.master" -}}
{{- printf "%s-%s" .Release.Name "master" | trunc 63 -}}
{{- end -}}
{{- define "locust.worker" -}}
{{- printf "%s-%s" .Release.Name "worker" | trunc 63 -}}
{{- end -}}
{{/*
Create fully qualified configmap name.
*/}}
{{- define "locust.worker-configmap" -}}
{{- printf "%s-%s" .Release.Name "worker" -}}
{{- end -}}
@@ -0,0 +1,67 @@
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: {{ template "locust.master" . }}
labels:
app: {{ template "locust.fullname" . }}
heritage: {{ .Release.Service | quote }}
release: {{ .Release.Name | quote }}
chart: "{{.Chart.Name}}-{{.Chart.Version}}"
component: master
spec:
replicas: 1
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: {{default 1 .Values.master.maxSurge}}
maxUnavailable: {{default 1 .Values.master.maxUnavailable}}
template:
metadata:
labels:
release: {{ .Release.Name | quote }}
app: {{ template "locust.fullname" . }}
component: "master"
spec:
containers:
- name: locust
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
resources:
{{ toYaml .Values.worker.resources | indent 10 }}
env:
{{- range $key, $value := .Values.master.config }}
- name: {{ $key | upper | replace "-" "_" }}
value: {{ $value | quote }}
{{- end }}
- name: LOCUST_MODE
value: "master"
- name: LOCUST_SCRIPT
value: {{ index .Values.worker.config "locust-script" | quote }}
ports:
- containerPort: {{ .Values.service.internalPort }}
name: loc-master-web
protocol: TCP
- containerPort: 5557
name: loc-master-p1
protocol: TCP
- containerPort: 5558
name: loc-master-p2
protocol: TCP
volumeMounts:
- name: locust-tasks
mountPath: /locust-tasks/
livenessProbe:
periodSeconds: 30
httpGet:
path: /
port: {{ .Values.service.internalPort }}
readinessProbe:
periodSeconds: 30
httpGet:
path: /
port: {{ .Values.service.internalPort }}
volumes:
- name: "locust-tasks"
configMap:
name: {{ template "locust.worker-configmap" . }}
restartPolicy: Always
+30
View File
@@ -0,0 +1,30 @@
apiVersion: v1
kind: Service
metadata:
name: {{ template "locust.master-svc" . }}
labels:
heritage: {{ .Release.Service | quote }}
release: {{ .Release.Name | quote }}
chart: "{{.Chart.Name}}-{{.Chart.Version}}"
app: {{ template "locust.fullname" . }}
component: "master"
spec:
type: {{ .Values.service.type }}
ports:
- name: {{ .Values.service.name }}
port: {{ .Values.service.externalPort }}
targetPort: {{ .Values.service.internalPort }}
nodePort: {{ .Values.service.nodePort}}
protocol: TCP
- name: master-p1
port: 5557
protocol: TCP
targetPort: 5557
- name: master-p2
port: 5558
protocol: TCP
targetPort: 5558
selector:
app: {{ template "locust.fullname" . }}
component: "master"
sessionAffinity: None
+11
View File
@@ -0,0 +1,11 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ template "locust.worker-configmap" . }}
labels:
heritage: {{ .Release.Service | quote }}
release: {{ .Release.Name | quote }}
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
app: {{ template "locust.fullname" . }}
data:
{{ (.Files.Glob "tasks/*").AsConfig | indent 2 }}
@@ -0,0 +1,50 @@
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: {{ template "locust.worker" . }}
labels:
heritage: {{ .Release.Service | quote }}
release: {{ .Release.Name | quote }}
chart: "{{.Chart.Name}}-{{.Chart.Version}}"
app: {{ template "locust.fullname" . }}
component: worker
spec:
replicas: {{default 2 .Values.worker.ReplicaCount}}
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: {{default 1 .Values.worker.maxSurge}}
maxUnavailable: {{default 1 .Values.worker.maxUnavailable}}
template:
metadata:
labels:
app: {{ template "locust.fullname" . }}
component: worker
spec:
containers:
- name: locust
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
volumeMounts:
- name: locust-tasks
mountPath: /locust-tasks/
env:
{{- range $key, $value := .Values.worker.config }}
- name: {{ $key | upper | replace "-" "_" }}
value: {{ $value | quote }}
{{- end }}
- name: LOCUST_MODE
value: "worker"
- name: LOCUST_MASTER
value: {{ template "locust.master-svc" . }}
- name: LOCUST_MASTER_WEB
value: "{{ .Values.service.internalPort }}"
- name: TARGET_HOST
value: {{ index .Values.master.config "target-host" | quote }}
resources:
{{ toYaml .Values.worker.resources | indent 10 }}
restartPolicy: Always
volumes:
- name: "locust-tasks"
configMap:
name: {{ template "locust.worker-configmap" . }}
+36
View File
@@ -0,0 +1,36 @@
Name: locust
image:
repository: quay.io/honestbee/locust
tag: 0.7.5
pullPolicy: IfNotPresent
service:
name: master-web
type: NodePort
externalPort: 8089
internalPort: 8089
nodePort: 0
master:
config:
target-host: https://site.example.com
resources:
limits:
cpu: 100m
memory: 128Mi
requests:
cpu: 100m
memory: 128Mi
worker:
config:
# all files from tasks folder are mounted under `/locust-tasks`
locust-script: "/locust-tasks/tasks.py"
replicaCount: 2
resources:
limits:
cpu: 100m
memory: 128Mi
requests:
cpu: 100m
memory: 128Mi