diff --git a/stable/locust/Chart.yaml b/stable/locust/Chart.yaml new file mode 100644 index 0000000000..6ee7d87e31 --- /dev/null +++ b/stable/locust/Chart.yaml @@ -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 diff --git a/stable/locust/README.md b/stable/locust/README.md new file mode 100644 index 0000000000..5c50790614 --- /dev/null +++ b/stable/locust/README.md @@ -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 +``` diff --git a/stable/locust/tasks/tasks.py b/stable/locust/tasks/tasks.py new file mode 100644 index 0000000000..9a81034c67 --- /dev/null +++ b/stable/locust/tasks/tasks.py @@ -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 \ No newline at end of file diff --git a/stable/locust/templates/NOTES.txt b/stable/locust/templates/NOTES.txt new file mode 100644 index 0000000000..b090947187 --- /dev/null +++ b/stable/locust/templates/NOTES.txt @@ -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 diff --git a/stable/locust/templates/_helpers.tpl b/stable/locust/templates/_helpers.tpl new file mode 100644 index 0000000000..97d5e0a7e8 --- /dev/null +++ b/stable/locust/templates/_helpers.tpl @@ -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 -}} diff --git a/stable/locust/templates/master-deploy.yaml b/stable/locust/templates/master-deploy.yaml new file mode 100644 index 0000000000..0635f8bb9b --- /dev/null +++ b/stable/locust/templates/master-deploy.yaml @@ -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 diff --git a/stable/locust/templates/master-svc.yaml b/stable/locust/templates/master-svc.yaml new file mode 100644 index 0000000000..0e30a4f5a9 --- /dev/null +++ b/stable/locust/templates/master-svc.yaml @@ -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 diff --git a/stable/locust/templates/worker-cm.yaml b/stable/locust/templates/worker-cm.yaml new file mode 100644 index 0000000000..e4d20117aa --- /dev/null +++ b/stable/locust/templates/worker-cm.yaml @@ -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 }} diff --git a/stable/locust/templates/worker-deploy.yaml b/stable/locust/templates/worker-deploy.yaml new file mode 100644 index 0000000000..2cdf183027 --- /dev/null +++ b/stable/locust/templates/worker-deploy.yaml @@ -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" . }} diff --git a/stable/locust/values.yaml b/stable/locust/values.yaml new file mode 100644 index 0000000000..fe7b7c8d14 --- /dev/null +++ b/stable/locust/values.yaml @@ -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