Load-test rig: k6 swarm script + in-cluster target drone

This commit is contained in:
Adam Carr
2026-09-19 18:16:57 -07:00
parent e46cf90e69
commit 8451333c3a
6 changed files with 337 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
apiVersion: v1
kind: Namespace
metadata:
name: loadtest
---
apiVersion: v1
kind: ConfigMap
metadata:
name: k6-script
namespace: loadtest
data:
test.js: |
// Generic load generator. Configured by env:
// TARGET_URL - full URL to hit
// VUS - virtual users per pod (default 20)
// DURATION - k6 duration string (default "60s")
import http from 'k6/http';
import { check, sleep } from 'k6';
const target = __ENV.TARGET_URL || 'http://example.com';
const vus = parseInt(__ENV.VUS || '20');
const duration = __ENV.DURATION || '60s';
export const options = {
stages: [
{ duration: '10s', target: vus },
{ duration: duration, target: vus },
{ duration: '5s', target: 0 },
],
thresholds: { http_req_failed: ['rate<0.01'] },
noConnectionReuse: false,
};
export default function () {
const res = http.get(target, { timeout: '10s' });
check(res, { 'status 2xx/3xx': (r) => r.status < 300 });
sleep(1 / vus * 10);
}

View File

@@ -0,0 +1,31 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: target-drone
namespace: loadtest
spec:
replicas: 3
selector:
matchLabels: {app: target-drone}
template:
metadata:
labels: {app: target-drone}
spec:
containers:
- name: nginx
image: nginx:1.29-alpine
ports: [{containerPort: 80}]
resources:
requests: {cpu: 10m, memory: 16Mi}
limits: {memory: 64Mi}
---
apiVersion: v1
kind: Service
metadata:
name: target-drone
namespace: loadtest
spec:
selector: {app: target-drone}
ports:
- port: 80
targetPort: 80

View File

@@ -0,0 +1,65 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-datasources
namespace: monitoring
data:
datasources.yaml: |
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus.monitoring.svc:9090
isDefault: true
---
apiVersion: v1
kind: Secret
metadata:
name: grafana-admin
namespace: monitoring
stringData:
admin-user: adamcarr
admin-password: planck-lab-admin
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: grafana
namespace: monitoring
spec:
replicas: 1
selector:
matchLabels: {app: grafana}
template:
metadata:
labels: {app: grafana}
spec:
containers:
- name: grafana
image: grafana/grafana:12.0.2
env:
- {name: GF_SECURITY_ADMIN_USER, valueFrom: {secretKeyRef: {name: grafana-admin, key: admin-user}}}
- {name: GF_SECURITY_ADMIN_PASSWORD, valueFrom: {secretKeyRef: {name: grafana-admin, key: admin-password}}}
- {name: GF_AUTH_ANONYMOUS_ENABLED, value: "true"}
ports:
- containerPort: 3000
resources:
requests: {cpu: 50m, memory: 128Mi}
limits: {memory: 512Mi}
volumeMounts:
- {name: datasources, mountPath: /etc/grafana/provisioning/datasources}
volumes:
- name: datasources
configMap: {name: grafana-datasources}
---
apiVersion: v1
kind: Service
metadata:
name: grafana
namespace: monitoring
spec:
selector: {app: grafana}
ports:
- port: 3000
targetPort: 3000

View File

@@ -0,0 +1,41 @@
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: node-exporter
namespace: monitoring
labels: {app: node-exporter}
spec:
selector:
matchLabels: {app: node-exporter}
template:
metadata:
labels: {app: node-exporter}
spec:
hostNetwork: true
hostPID: true
tolerations:
- operator: Exists
containers:
- name: node-exporter
image: quay.io/prometheus/node-exporter:v1.9.1
args:
- --path.procfs=/host/proc
- --path.sysfs=/host/sys
- --collector.filesystem.mount-points-exclude=^/(dev|proc|sys|var/lib/docker/.+|var/lib/kubelet/.+)($|/)
ports:
- containerPort: 9100
hostPort: 9100
resources:
requests: {cpu: 20m, memory: 32Mi}
limits: {memory: 64Mi}
volumeMounts:
- {name: proc, mountPath: /host/proc, readOnly: true}
- {name: sys, mountPath: /host/sys, readOnly: true}
- {name: root, mountPath: /rootfs, readOnly: true}
volumes:
- name: proc
hostPath: {path: /proc}
- name: sys
hostPath: {path: /sys}
- name: root
hostPath: {path: /}

View File

@@ -0,0 +1,115 @@
apiVersion: v1
kind: Namespace
metadata:
name: monitoring
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: prometheus
namespace: monitoring
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: prometheus
rules:
- apiGroups: [""]
resources: [nodes, nodes/proxy, services, endpoints, pods]
verbs: [get, list, watch]
- apiGroups: ["extensions", "networking.k8s.io"]
resources: [ingresses]
verbs: [get, list, watch]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: prometheus
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: prometheus
subjects:
- kind: ServiceAccount
name: prometheus
namespace: monitoring
---
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-config
namespace: monitoring
data:
prometheus.yml: |
global:
scrape_interval: 15s
external_labels:
cluster: planck
scrape_configs:
- job_name: prometheus
static_configs:
- targets: ["localhost:9090"]
- job_name: node-exporter
kubernetes_sd_configs:
- role: node
relabel_configs:
- source_labels: [__meta_kubernetes_node_address_InternalIP]
target_label: instance_ip
- action: labelmap
regex: __meta_kubernetes_node_label_(.+)
metric_relabel_configs:
- source_labels: [__name__]
regex: go_.*
action: drop
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: prometheus
namespace: monitoring
spec:
serviceName: prometheus
replicas: 1
selector:
matchLabels: {app: prometheus}
template:
metadata:
labels: {app: prometheus}
spec:
serviceAccountName: prometheus
containers:
- name: prometheus
image: prom/prometheus:v3.6.0
args:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.retention.time=15d
- --storage.tsdb.path=/data
ports:
- containerPort: 9090
resources:
requests: {cpu: 150m, memory: 512Mi}
limits: {memory: 1Gi}
volumeMounts:
- {name: config, mountPath: /etc/prometheus}
- {name: data, mountPath: /data}
volumes:
- name: config
configMap: {name: prometheus-config}
volumeClaimTemplates:
- metadata: {name: data}
spec:
accessModes: [ReadWriteOnce]
storageClassName: local-path
resources:
requests: {storage: 20Gi}
---
apiVersion: v1
kind: Service
metadata:
name: prometheus
namespace: monitoring
spec:
selector: {app: prometheus}
ports:
- port: 9090
targetPort: 9090

47
scripts/loadtest.sh Executable file
View File

@@ -0,0 +1,47 @@
#!/bin/bash
# Spawn a k6 load swarm against a target.
# Usage: ./scripts/loadtest.sh <target-url> [pods] [vus-per-pod] [duration]
# Example: ./scripts/loadtest.sh https://staging.example.com 20 50 5m
# -> 20 pods x 50 virtual users = 1000 concurrent requests, 5 minutes
set -euo pipefail
TARGET=${1:?usage: loadtest.sh <target-url> [pods] [vus-per-pod] [duration]}
PODS=${2:-20}
VUS=${3:-20}
DURATION=${4:-60s}
NAME="loadtest-$(date +%s)"
kubectl -n loadtest apply -f - <<EOF
apiVersion: batch/v1
kind: Job
metadata:
name: $NAME
spec:
parallelism: $PODS
completions: $PODS
ttlSecondsAfterFinished: 300
template:
spec:
restartPolicy: Never
containers:
- name: k6
image: grafana/k6:1.4.0
command: ["k6", "run", "/scripts/test.js"]
env:
- {name: TARGET_URL, value: "$TARGET"}
- {name: VUS, value: "$VUS"}
- {name: DURATION, value: "$DURATION"}
resources:
requests: {cpu: 100m, memory: 128Mi}
limits: {memory: 256Mi}
volumeMounts:
- {name: scripts, mountPath: /scripts}
volumes:
- name: scripts
configMap: {name: k6-script}
EOF
echo "Swarm '$NAME' launched: $PODS pods x $VUS vus for $DURATION against $TARGET"
echo "Watch: kubectl -n loadtest get pods -w"
echo "Logs: kubectl -n loadtest logs -f job/$NAME --all-pods=true --tail=5"
echo "Stop: kubectl -n loadtest delete job $NAME"