JMeter’s default configuration is a single-threaded juggernaut, but when you need to flood a system with load from many machines, its Kubernetes deployment becomes a distributed ballet.
Let’s see it in action. Imagine you’ve got a JMeter test plan (my_test.jmx) and you want to run it across 10 Kubernetes pods, each acting as a JMeter client. Your Kubernetes cluster is already set up.
First, you need a Docker image that has JMeter installed and can run a test. A simple Dockerfile might look like this:
FROM alpine:latest
RUN apk add --no-cache openjdk11-jdk wget && \
wget https://archive.apache.org/dist/jmeter/binaries/apache-jmeter-5.5.tgz && \
tar -xzf apache-jmeter-5.5.tgz && \
mv apache-jmeter-5.5 /opt/jmeter && \
rm apache-jmeter-5.5.tgz
ENV PATH="/opt/jmeter/bin:$PATH"
Build this image and push it to a registry your Kubernetes cluster can access (e.g., Docker Hub, Google Container Registry). Let’s say your image is your-docker-repo/jmeter-client:latest.
Now, you need a Kubernetes Job to orchestrate this. This Job will launch multiple pods, each running your JMeter client image.
apiVersion: batch/v1
kind: Job
metadata:
name: jmeter-load-test
spec:
completions: 10 # This is key: we want 10 successful pods
parallelism: 10 # And we want them to start concurrently
template:
spec:
restartPolicy: Never # Important: Job pods shouldn't restart on failure
containers:
- name: jmeter-client
image: your-docker-repo/jmeter-client:latest
command: ["jmeter", "-n", "-t", "/jmeter/my_test.jmx", "-l", "/jmeter/results.jtl", "-R", "192.168.1.100,192.168.1.101"] # Placeholder for RMI hosts
volumeMounts:
- name: test-plan
mountPath: /jmeter
volumes:
- name: test-plan
configMap:
name: jmeter-test-plan # We'll create this ConfigMap next
The problem here is that JMeter needs to know about its master (the pod orchestrating the test) and its slaves (the other pods running the actual test load). By default, JMeter uses RMI (Remote Method Invocation) for this. The -R flag in the command is where you’d list the IP addresses of your slave JMeter instances. However, in Kubernetes, pod IPs are ephemeral and dynamic. This makes the -R flag with static IPs a non-starter.
This is where the JMeter Master/Slave setup in Kubernetes pattern comes in. You don’t typically use -R directly. Instead, you designate one pod as the master and the others as slaves. The master pod then discovers and communicates with the slaves.
Here’s how you actually set it up in Kubernetes:
- The Master Pod: This pod will run JMeter in server mode. It listens for incoming RMI connections from the slaves.
- The Slave Pods: These pods will run JMeter in client mode, connecting to the master and executing the test plan.
You’ll need two Kubernetes Deployments (or Jobs if the test is a one-off): one for the master and one for the slaves.
Master Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: jmeter-master
spec:
replicas: 1
selector:
matchLabels:
app: jmeter
role: master
template:
metadata:
labels:
app: jmeter
role: master
spec:
containers:
- name: jmeter-master
image: your-docker-repo/jmeter-master:latest # Image with jmeter and rmi enabled
ports:
- containerPort: 1099 # RMI default port
- containerPort: 50000 # RMI default high port range
command: ["sh", "-c", "jmeter -s -j /jmeter/jmeter.log"] # Run in server mode
env:
- name: JMETER_MASTER_HOST
value: "jmeter-master-svc" # Kubernetes Service name for the master
Slave Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: jmeter-slaves
spec:
replicas: 10 # Scale this to your needs
selector:
matchLabels:
app: jmeter
role: slave
template:
metadata:
labels:
app: jmeter
role: slave
spec:
containers:
- name: jmeter-slave
image: your-docker-repo/jmeter-client:latest # Image with jmeter client
command: ["sh", "-c", "jmeter -n -t /jmeter/my_test.jmx -l /jmeter/results.jtl -H $JMETER_MASTER_HOST -P 1099"] # Connect to master
env:
- name: JMETER_MASTER_HOST
valueFrom:
service:
name: jmeter-master-svc # Reference the master service
port: 1099
volumeMounts:
- name: test-plan
mountPath: /jmeter
volumes:
- name: test-plan
configMap:
name: jmeter-test-plan
You’ll also need a Service for the master so the slaves can reliably find it.
Master Service:
apiVersion: v1
kind: Service
metadata:
name: jmeter-master-svc
spec:
selector:
app: jmeter
role: master
ports:
- protocol: TCP
port: 1099
targetPort: 1099
- protocol: TCP
port: 50000 # RMI dynamic ports
targetPort: 50000
And a ConfigMap to hold your test plan:
apiVersion: v1
kind: ConfigMap
metadata:
name: jmeter-test-plan
data:
my_test.jmx: |
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" ...>
... your test plan ...
</jmeterTestPlan>
The jmeter -s command starts JMeter in server mode. The jmeter -n -t ... -H $JMETER_MASTER_HOST -P 1099 command tells the slave to run a non-GUI test, targeting the specified master host and port. The valueFrom.service in the slave deployment automatically resolves the master’s IP through the Kubernetes DNS.
When you scale the jmeter-slaves deployment to 10 replicas, Kubernetes will launch 10 slave pods. Each slave pod, upon starting, will try to connect to the jmeter-master-svc on port 1099. The JMeter master, running in server mode, will accept these connections. The master then coordinates the test execution, distributing the load across all connected slaves. The results are typically aggregated on the master or collected from individual slaves.
The real magic happens because Kubernetes manages the IP addresses and network routing. The jmeter-master-svc provides a stable DNS name (jmeter-master-svc) that the slaves can use to find the master, regardless of which specific pod IP the master is running on. The RMI communication then flows through Kubernetes networking.
The most surprising thing about this setup is that JMeter’s RMI, which feels ancient, actually works quite well in Kubernetes because the cluster handles the dynamic IP resolution and network proxying for you. You don’t need to pre-configure RMI registry hosts for each slave.
The next hurdle you’ll likely face is managing and collecting the large volumes of result data (.jtl files) generated by many distributed JMeter slaves.