Distributed Load Testing on Kubernetes

Run distributed Locust load tests on a local Kubernetes cluster using Floci.

Introduction

30 Second Summary

You know that moment when a website crashes right as tickets go on sale? That happens when no one tested how many users the servers could handle.

In this project, you will build a distributed load-testing platform that runs entirely on your local machine. You'll spin up a local Kubernetes cluster using Floci (a free, open-source AWS emulator), deploy a target web service, and watch a fleet of Locust workers generate traffic against it in real time.

What You'll Build

Picture opening a browser dashboard, clicking Start, and watching real-time charts climb as hundreds of simulated users from multiple coordinated worker pods flood your web service.

By the end of this project, you'll have:

  • A local Kubernetes cluster powered by Floci's EKS emulator that you control with kubectl commands. No AWS account or cloud costs needed.
  • A distributed Locust load test running inside the cluster, where a master pod coordinates multiple worker pods to send hundreds of simulated users at a target service. All orchestrated by the Locust Kubernetes Operator through a single YAML file.
  • Real-time charts in the Locust web UI showing requests per second, response times, and failure rates updating live as you scale workers up and watch throughput multiply.
  • Secret Mission: Design a multi-scenario load test with weighted user types and deploy it the production way, through a Kubernetes ConfigMap.

Are there any prerequisites?

You'll need Docker Desktop and VS Code installed on a Windows machine. This project walks you through installing everything else in Step 1.

Before We Start

Before any hands-on work, let's lock in what you're about to build and why it matters.

Install Your Toolkit

Three CLI tools power your interactions with a Kubernetes cluster. The AWS CLI creates and manages clusters. kubectl deploys and inspects workloads inside them. Helm installs complex packages like the Locust Operator.

Without these tools on your machine, you have no way to talk to the local cluster you will spin up in the next step. In this step, you will install all three and configure the AWS CLI to work with Floci.

In this step, get ready to:
  • Install and configure AWS CLI v2.
  • Install kubectl.
  • Install Helm.
Install and configure AWS CLI v2

The AWS CLI lets you create and manage cloud resources from the terminal. Floci emulates AWS services locally, and the CLI is your interface to both.

  • Press the Windows key to open your search bar.
  • Type PowerShell and press Enter to open it.

Why PowerShell?

This project uses PowerShell-specific install commands. Make sure you open a regular PowerShell window, not PowerShell ISE. PowerShell ISE does not handle interactive prompts from tools like winget correctly.

  • Check if the AWS CLI is already installed by running this command:
aws --version

✔️ I see aws-cli/2.

AWS CLI v2 is already installed. Skip ahead to the configure step below.

ⓧ I see aws-cli/1.

You have the older AWS CLI v1. You need v2 for this project.

  • Install AWS CLI v2 by running this command:
irm https://awscli.amazonaws.com/v2/install.ps1 | iex

What does this command do?

This downloads and runs the official AWS CLI v2 installer for Windows. It installs for the current user by default, so no admin rights are needed.

  • After the install finishes, close and reopen PowerShell so the updated PATH takes effect.
  • Verify the install by running:
aws --version

You should see a version starting with aws-cli/2..

Still seeing aws-cli/1.?

Close all PowerShell and Command Prompt windows, then reopen PowerShell and try aws --version again. The old v1 may be earlier on your PATH.

If v1 persists, uninstall it from Settings > Apps > Installed apps, then reopen PowerShell and check again.

Help me fix my AWS CLI version.

ⓧ Command not found

The AWS CLI is not installed yet.

  • Install AWS CLI v2 by running this command:
irm https://awscli.amazonaws.com/v2/install.ps1 | iex

What does this command do?

This downloads and runs the official AWS CLI v2 installer for Windows. It installs for the current user by default, so no admin rights are needed.

  • After the install finishes, close and reopen PowerShell so the new PATH takes effect.
  • Verify the install by running:
aws --version

You should see a version starting with aws-cli/2..

Seeing a script execution error?

PowerShell may block scripts by default. Run this command to allow them for your account, then retry the install:

Set-ExecutionPolicy RemoteSigned -Scope CurrentUser

Help me fix PowerShell script execution.

Now that the AWS CLI is installed, configure it with dummy credentials for Floci.

  • Configure the AWS CLI by running this command. It will prompt you for four values:
aws configure
  • For the access key ID, enter test.
  • For the secret access key, enter test.
  • For the default region, enter us-east-1.
  • For the output format, enter json.

Why dummy credentials?

Floci emulates AWS locally and does not validate credentials. The AWS CLI still requires configured values to send API requests, so these placeholders satisfy that requirement without needing a real AWS account.

Install kubectl

The kubectl tool sends commands to your Kubernetes cluster. You use it to deploy apps, check pod status, and manage resources inside the cluster.

  • Check if kubectl is already installed by running this command:
kubectl version --client

✔️ I see a version number

kubectl is ready. Move on to installing Helm below.

ⓧ Command not found

kubectl is not installed yet.

  • Install kubectl by running this command:
winget install -e --id Kubernetes.kubectl

What does this command do?

This uses the Windows Package Manager (winget) to install the official Kubernetes kubectl binary. The -e flag ensures an exact match on the package ID.

  • If winget asks you to agree to source terms, type Y and press Enter.
  • After the install finishes, close and reopen PowerShell so the new PATH takes effect.
  • Verify the install by running:
kubectl version --client

You should see client version information in the output.

winget not recognized?

The winget command comes with the App Installer package on Windows 10 and 11. If it is missing, open the Microsoft Store and search for App Installer. Install or update it, then reopen PowerShell.

Help me get winget working.

Install Helm

Helm is a package manager for Kubernetes. You will use it later to install the Locust Kubernetes Operator into your cluster.

  • Check if Helm is already installed by running this command:
helm version

✔️ I see a version number

Helm is ready. You have all three tools installed.

ⓧ Command not found

Helm is not installed yet.

  • Install Helm by running this command:
winget install Helm.Helm
  • If winget asks you to agree to source terms, type Y and press Enter.
  • After the install finishes, close and reopen PowerShell so the new PATH takes effect.
  • Verify the install by running:
helm version

You should see Helm version information in the output.

Still seeing command not found?

Make sure you closed and reopened PowerShell after the install. The PATH update only applies to new terminal sessions.

Help me get Helm on my PATH.

Before you move on, what do you expect to see if you run all three version commands back to back?

  • Confirm all three tools are working by running each command:
aws --version
kubectl version --client
helm version

All three commands return version information without errors. Your toolkit is ready.

Your toolkit is locked in. Next up, you will spin up a local Kubernetes cluster using Floci and deploy your first service into it.

Spin Up a Local Kubernetes Cluster

Your CLI tools are installed and ready. Now you need somewhere to actually deploy workloads.

In this step, you will spin up a local Kubernetes cluster using Floci, a free, open-source AWS emulator. Floci's EKS service launches real k3s containers on your machine, giving you a working Kubernetes cluster without an AWS account or cloud costs.

In this step, get ready to:
  • Start Floci via Docker Compose and create a local EKS cluster.
  • Connect kubectl to the cluster so you can deploy workloads.
  • Deploy an nginx target service and confirm it serves traffic.
Start Floci with Docker Compose

Floci runs as a Docker container that emulates AWS services locally. To create Kubernetes clusters, Floci needs access to your machine's Docker engine so it can spin up k3s containers on your behalf.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir locust-lab
cd locust-lab
  • Confirm the folder exists by running:
ls

You should see the locust-lab folder listed (it will be empty for now).

  • Open locust-lab in VS Code. Press Cmd+Shift+P (macOS) or Ctrl+Shift+P (Windows) to open the Command Palette.
  • Type File: Open Folder and press Enter.
  • Select the locust-lab folder on your Desktop and click Open.
  • Create a file called docker-compose.yml in the locust-lab folder. This file tells Docker how to run Floci. Copy the following code into it:
services:
  floci:
    image: floci/floci:latest
    ports:
      - "4566:4566"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./data:/app/data
    environment:
      FLOCI_SERVICES_EKS_ENABLED: "true"
      FLOCI_SERVICES_EKS_MOCK: "false"
      FLOCI_HOSTNAME: floci

What does this configuration do?

The volumes section mounts the Docker socket so Floci can create k3s containers for your cluster. The ./data mount persists cluster data locally.

Setting FLOCI_SERVICES_EKS_MOCK to false tells Floci to spin up real k3s containers instead of just returning metadata. FLOCI_HOSTNAME ensures multi-container networking works correctly.

  • Save docker-compose.yml.
  • Start Floci by running this command in your terminal:
docker compose up -d

Docker pulls the Floci image and starts the container in the background. This may take a minute on the first run.

  • Confirm Floci is running by checking the container status:
docker ps

You should see a running container with the image floci/floci:latest and port 4566 mapped.

Container not starting?

Make sure Docker Desktop is running. If you see a permissions error about the Docker socket, restart Docker Desktop and try again.

If docker compose up -d says the command is not found, make sure you are using Docker Desktop v2+ which includes the docker compose plugin.

Help me troubleshoot my Floci Docker Compose setup.

Create a local EKS cluster

With Floci running, you can use the standard AWS CLI to create a Kubernetes cluster. Floci intercepts these commands and spins up a real k3s container behind the scenes.

  • Create your cluster by running this command:
aws eks create-cluster --name locust-lab --role-arn arn:aws:iam::000000000000:role/eks-role --resources-vpc-config "subnetIds=[],securityGroupIds=[]" --endpoint-url http://localhost:4566

What does this command do?

This sends a CreateCluster request to Floci at http://localhost:4566. Floci receives it and spins up a real k3s container for your cluster. The --role-arn and --resources-vpc-config are required by the AWS CLI but Floci accepts any dummy values for them.

The cluster takes 30-90 seconds to boot. The k3s container needs to start up and pass its readiness checks before the cluster reaches ACTIVE status.

  • Wait for the cluster to become ACTIVE by running:
aws eks wait cluster-active --name locust-lab --endpoint-url http://localhost:4566

This command polls the cluster status and returns when the cluster is ready. It may take up to 90 seconds.

  • Once the wait completes, extract the kubeconfig so kubectl can connect to your cluster:
aws eks update-kubeconfig --name locust-lab --endpoint-url http://localhost:4566

What is a kubeconfig?

A kubeconfig file tells kubectl where your cluster lives and how to authenticate. The update-kubeconfig command creates this file automatically at ~/.kube/config. From this point on, kubectl knows how to talk to your Floci-powered cluster.

Before you run the next command, what do you think you will see if kubectl is successfully connected to a single-node k3s cluster?

  • Verify kubectl can reach the cluster by running:
kubectl get nodes

You should see a single node with STATUS Ready. This confirms your local Kubernetes cluster is up and kubectl is connected.

Seeing connection refused or no nodes?

If you see a connection error, the k3s container may still be booting. Run docker ps and check for a container whose name starts with floci-eks-. If it is not listed, the cluster creation may have failed. Try deleting and recreating:

aws eks delete-cluster --name locust-lab --endpoint-url http://localhost:4566

Then re-run the create, wait, and update-kubeconfig commands above.

Help me connect kubectl to my Floci EKS cluster.

Deploy an nginx target service

Your cluster is running, but it has nothing deployed to it yet. You need a target web service that your load tests can hit. Nginx is a lightweight web server that is perfect for this.

  • Create a file called nginx-deployment.yaml in the locust-lab folder. Start by adding the Deployment section that tells Kubernetes to run one nginx container:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-target
  labels:
    app: nginx-target
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-target
  template:
    metadata:
      labels:
        app: nginx-target
    spec:
      containers:
        - name: nginx
          image: nginx:latest
          ports:
            - containerPort: 80

What does this Deployment do?

A Deployment is a Kubernetes resource that manages a set of identical Pods. This one creates a single Pod running the nginx:latest image, listening on port 80.

The labels field with app: nginx-target is how Kubernetes identifies and groups related resources. The Service you add next uses this same label to find the Pod.

  • Now add the Service section below the Deployment in the same file. Add a --- separator line first, then paste this:
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-target
spec:
  selector:
    app: nginx-target
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  type: ClusterIP

What does the Service do?

A ClusterIP Service gives your nginx Pod a stable internal address inside the cluster. Other Pods (like your Locust workers later) can reach nginx by calling http://nginx-target instead of tracking down the Pod's IP address.

  • Save nginx-deployment.yaml.
  • Deploy nginx to your cluster by running:
kubectl apply -f nginx-deployment.yaml

You should see output confirming both the Deployment and Service were created.

  • Check that the nginx Pod is running:
kubectl get pods

You should see one Pod with STATUS Running. If the status shows ContainerCreating, wait a few seconds and run the command again.

  • Port-forward the nginx service to your local machine so you can access it in a browser:
kubectl port-forward svc/nginx-target 8080:80

What is port forwarding?

Port forwarding connects a port on your local machine to a port inside the cluster. This command maps localhost:8080 on your machine to port 80 on the nginx-target Service. This is how you can reach cluster services from your browser during development.

This command takes over the terminal. You will need to open a new terminal window for any further commands. Press Ctrl+C when you want to stop the port-forward.

  • Open your browser and navigate to http://localhost:8080.

You should see the nginx welcome page with the text "Welcome to nginx!" confirming your target service is deployed and reachable.

  • Go back to your terminal and press Ctrl+C to stop the port-forward. You do not need it running continuously.

Not seeing the nginx page?

If you get a connection refused error at http://localhost:8080, make sure the kubectl port-forward command is still running in your terminal. It must stay active for the connection to work.

If the Pod status is ImagePullBackOff, the k3s node cannot pull the nginx image. Check that Docker Desktop has internet access and run kubectl describe pod to see the error details.

Help me get nginx running in my Floci Kubernetes cluster.

✔️ Awesome, I've got everything!

Great. Double check you have saved both docker-compose.yml and nginx-deployment.yaml.

ⓧ I'd like to double check the full code

You should have two files in your locust-lab folder. Scroll up and confirm each matches:

  • docker-compose.yml with the Floci service configuration (the first code chunk in this step).
  • nginx-deployment.yaml with the Deployment section and the Service section combined (the two code chunks under "Deploy an nginx target service").

Your local Kubernetes cluster is running and has a target service ready to be load tested. Next up, you will write a load test script and run your first Locust test against nginx.

Run a Single-Node Locust Test

Your Kubernetes cluster is up and nginx is serving traffic. Now you need a way to throw requests at it and measure how it holds up under load.

Locust is a Python-based load testing tool that lets you define user behavior in plain code. In this step you will write a locustfile, run it in a single Docker container, and watch the results in Locust's web UI.

In this step, get ready to:
  • Write a locustfile that simulates user traffic against your nginx service.
  • Run Locust as a single Docker container and observe live load test results.
Write your locustfile

A locustfile is a standard Python file that tells Locust how to behave as a simulated user. Each class you define represents a type of user, and each method decorated with @task represents an action that user can take.

  • In VS Code, create a new file called locustfile.py in your project folder (the same folder that contains docker-compose.yml).
  • Paste the following code into locustfile.py:
from locust import HttpUser, task, between


class WebsiteUser(HttpUser):
    # Wait 1-3 seconds between each task
    wait_time = between(1, 3)

    @task(3)
    def view_homepage(self):
        # Hit the root path (nginx welcome page)
        self.client.get("/")

    @task(1)
    def view_missing_page(self):
        # Hit a path that returns 404, and mark it as success
        with self.client.get("/missing", catch_response=True) as response:
            if response.status_code == 404:
                response.success()

What does this code do?

WebsiteUser inherits from HttpUser, which gives every simulated user an HTTP client. The wait_time = between(1, 3) adds a random 1 to 3 second pause between each action, mimicking real human browsing.

The @task decorator marks a method as something a simulated user will do. The number in parentheses is the weight. view_homepage has weight 3 and view_missing_page has weight 1, so homepage hits happen roughly 3 times more often than the missing page hit.

The catch_response=True pattern lets you override Locust's default pass/fail logic. Normally a 404 would count as a failure, but calling response.success() marks it as intentional. This is useful for testing error-handling paths.

  • Save locustfile.py.
  • Confirm the file exists by running:
ls locustfile.py

You should see locustfile.py printed in the output.

✔️ Awesome, I've got everything!

Great. Make sure you have saved locustfile.py before moving on.

ⓧ I'd like to double check the full code

Here is the complete locustfile.py. Compare it against yours:

from locust import HttpUser, task, between


class WebsiteUser(HttpUser):
    wait_time = between(1, 3)

    @task(3)
    def view_homepage(self):
        self.client.get("/")

    @task(1)
    def view_missing_page(self):
        with self.client.get("/missing", catch_response=True) as response:
            if response.status_code == 404:
                response.success()
Run Locust in a single Docker container

Before you distribute anything, it is worth seeing Locust work in its simplest form. You will run a single Locust container on your host machine and point it at the nginx service through the port-forward you set up earlier.

  • Make sure the nginx port-forward from Step 2 is still running. If you stopped it, restart it by running:
kubectl port-forward svc/nginx-target 8080:80

Why check the port-forward?

Your Locust container runs on the Docker host, not inside the Kubernetes cluster. It reaches nginx through the port-forward on localhost:8080. If the port-forward process was stopped (for example, by closing that terminal), Locust has no route to the target service.

  • Open a new PowerShell terminal (keep the port-forward terminal running in the background).
  • Start Locust as a single container by running this command:
docker run -p 8089:8089 --mount type=bind,source=$pwd,target=/mnt/locust locustio/locust -f /mnt/locust/locustfile.py -H http://host.docker.internal:8080

What does this command do?

  • -p 8089:8089 maps port 8089 inside the container to port 8089 on your machine, exposing the Locust web UI.
  • --mount type=bind,source=$pwd,target=/mnt/locust mounts your current directory into the container so Locust can read your locustfile.py.
  • locustio/locust is the official Locust Docker image.
  • -f /mnt/locust/locustfile.py tells Locust which test file to load.
  • -H http://host.docker.internal:8080 sets the target host. host.docker.internal is a special Docker Desktop hostname that points back to your host machine, where the nginx port-forward is listening on port 8080.

Seeing a connection error?

  • Make sure the nginx port-forward terminal is still running. If you see a message about port 8080 being refused, restart the port-forward in a separate terminal.
  • Make sure Docker Desktop is running. The host.docker.internal hostname only works when Docker Desktop is active.
  • If port 8089 is already in use, stop any other process using that port and try again.

Still stuck? Help me troubleshoot my Locust Docker container not connecting to nginx.

You should see log output in your terminal indicating that Locust has started and is ready.

Start a load test from the web UI

Before you run the test, think about what you expect to see. You have two tasks in your locustfile: one that hits the homepage (which should return 200) and one that hits a missing page (which returns 404 but is marked as success). What do you think the failure rate will look like?

  • Open your browser and go to http://localhost:8089.
  • In the Locust start form, enter 10 in the Number of users field.
  • Enter 1 in the Spawn rate field (this ramps up 1 user per second).
  • The Host field should already show http://host.docker.internal:8080 because you passed -H in the Docker command.
  • Click Start.

The statistics table starts updating immediately. You should see two rows: GET / and GET /missing. The failure count for both should stay at zero because your locustfile treats the 404 on /missing as a success.

  • Click the Charts tab to see the real-time graphs.

You should see three charts updating live: total requests per second, response times (median and 95th percentile), and the number of running users ramping up to 10.

What am I looking at?

The Charts tab is where Locust shines. The top chart shows requests per second (RPS). The middle chart shows response time percentiles. The bottom chart shows the user ramp-up curve. Together they give you a live picture of how your target handles increasing load.

Right now all of this is powered by a single Locust process inside one container. That single process can only simulate so many users before it becomes the bottleneck itself. You will solve that in the next step.

  • Let the test run for about 30 seconds to collect some data, then click Stop in the top right corner.

Not seeing any requests in the charts?

  • Check that the Host field shows http://host.docker.internal:8080. If it is blank, type the URL manually and click Start again.
  • Confirm your nginx port-forward terminal is still active. It should show incoming connection log lines.

Need more help? Help me figure out why Locust is not sending requests.

You have your first load test results. A single container gave you a quick feedback loop, but that one process has a ceiling. If you needed to simulate thousands of users, you would have to manually spin up more containers and coordinate them yourself. In the next step, you will hand that coordination off to the Locust Kubernetes Operator, which manages a master and multiple workers for you automatically.

Deploy Distributed Locust via the K8s Operator

In the last step, you ran Locust as a single Docker container and saw live load test results. That single container worked, but it is limited to one process on one machine.

To generate serious load, teams run Locust in distributed mode with a master coordinating multiple workers. The Locust Kubernetes Operator makes this simple. You describe your entire load test as a single YAML resource, and the operator automatically creates the master pod, worker pods, and web UI service for you.

In this step, get ready to:
  • Install the Locust Kubernetes Operator via Helm.
  • Deploy a distributed LocustTest custom resource with 1 master and 2 workers.
  • Run a distributed load test with 50 users and confirm workers are connected.
Install the Locust Kubernetes Operator

Helm is a package manager for Kubernetes. The Locust team publishes their operator as a Helm chart, so installing it is a two-step process: add the chart repository, then install the chart.

  • Add the Locust Operator Helm repository by running this command:
helm repo add locust-operator https://locustio.github.io/k8s-operator
  • Install the operator into a dedicated namespace by running:
helm install locust-operator locust-operator/locust-operator --namespace locust-operator --create-namespace

What does this command do?

  • helm install locust-operator names this Helm release locust-operator.
  • locust-operator/locust-operator references the chart from the repo you just added.
  • --namespace locust-operator --create-namespace creates a new namespace and installs the operator there, keeping it separate from your application workloads.
  • Verify the operator pod is running by running:
kubectl get pods -n locust-operator

You should see a pod with a name starting with locust-operator in Running status.

Pod not in Running status?

  • If the pod shows ContainerCreating, wait 30 seconds and run the command again. The operator image needs to be pulled first.
  • Make sure Docker Desktop is running. The Kubernetes cluster needs Docker to pull container images.
  • help me troubleshoot the Locust Operator pod not starting

What is a Kubernetes Operator?

An operator extends the Kubernetes API with Custom Resource Definitions (CRDs). CRDs let you describe your application as a YAML object, just like built-in resources such as Deployments and Services.

The Locust Operator watches for LocustTest resources. When you create one, the operator automatically spins up the master pod, worker pods, and a web UI service. You declare what you want, and the operator makes it happen.

Define and apply the LocustTest resource

Now that the operator is watching, you can describe your entire distributed load test as a single LocustTest custom resource. The operator reads it and automatically creates all the pods and services.

  • Switch back to VS Code.
  • Create a new file called locust-test.yaml in your project folder.
  • Add the following content to locust-test.yaml:
# Define a LocustTest custom resource for the operator
apiVersion: locust.io/v1
kind: LocustTest
metadata:
  name: load-test
spec:
  image: locustio/locust:latest
  # Create 2 worker pods alongside 1 master pod
  workers: 2
  args:
    # Target the nginx service via Kubernetes internal DNS
    --host http://nginx-target
  locustfile:
    # Embed the locustfile directly in the resource
    content: |
      from locust import HttpUser, task, between

      class WebsiteUser(HttpUser):
          wait_time = between(1, 3)

          @task(3)
          def view_homepage(self):
              self.client.get("/")

          @task(1)
          def view_missing_page(self):
              with self.client.get("/missing", catch_response=True) as response:
                  if response.status_code == 404:
                      response.success()

What does this YAML do?

  • apiVersion: locust.io/v1 and kind: LocustTest tell Kubernetes this is a custom resource managed by the Locust Operator.
  • spec.workers: 2 tells the operator to create 2 worker pods alongside 1 master pod.
  • spec.args: --host http://nginx-target sets the target URL using Kubernetes service DNS. Because Locust is running inside the cluster, it can reach the nginx service by name.
  • spec.locustfile.content embeds the same locustfile you wrote in Step 3 directly inside the resource, so it runs inside the cluster pods.
  • Save locust-test.yaml.
  • Apply the resource to your cluster by running:
kubectl apply -f locust-test.yaml

Before you check the result, how many pods do you think the operator created from this single YAML file?

  • Check the pods the operator created by running:
kubectl get pods -l locust.io/test-run=load-test

You should see 3 pods: 1 master (load-test-master-...) and 2 workers (load-test-worker-...), all in Running status. One YAML file, three coordinated pods.

Pods not showing up or stuck in Pending?

  • If no pods appear, wait 15-20 seconds and run the command again. The operator needs time to read the new resource and create the pods.
  • If pods are stuck in Pending, your cluster may not have enough resources. Check Docker Desktop's resource settings and increase the memory allocation.
  • Run kubectl describe pod followed by the pod name to see detailed status and error messages.
  • help me debug why my LocustTest pods are not reaching Running status

✔️ Awesome, I've got everything!

Great. Double check you have saved locust-test.yaml and all 3 pods show Running status.

ⓧ I'd like to double check the full code

Here is the complete locust-test.yaml file. Compare it with yours:

apiVersion: locust.io/v1
kind: LocustTest
metadata:
  name: load-test
spec:
  image: locustio/locust:latest
  workers: 2
  args:
    --host http://nginx-target
  locustfile:
    content: |
      from locust import HttpUser, task, between

      class WebsiteUser(HttpUser):
          wait_time = between(1, 3)

          @task(3)
          def view_homepage(self):
              self.client.get("/")

          @task(1)
          def view_missing_page(self):
              with self.client.get("/missing", catch_response=True) as response:
                  if response.status_code == 404:
                      response.success()
Run a distributed load test

Your distributed Locust deployment is live. The operator also created a service called load-test-webui that exposes the Locust web UI. You need to port-forward it to access the UI from your browser.

Free up port 8089 first

If the single-container Locust from Step 3 is still running on port 8089, stop it first. Run docker ps to check for running Locust containers, then docker stop followed by the container ID to stop it.

  • Port-forward the Locust web UI service to your machine by running:
kubectl port-forward svc/load-test-webui 8089:8089

What does this command do?

This command forwards traffic from localhost:8089 on your machine to port 8089 on the load-test-webui service inside the cluster. The command keeps running in the foreground. Leave it running and switch to your browser.

  • Open http://localhost:8089 in your browser.
  • Enter 50 in the Number of users field.
  • Enter 10 in the Ramp up field (users started per second).
  • Click Start.

Before you check the Workers tab, how many workers do you expect to see connected?

  • Click the Workers tab in the Locust web UI.

You should see 2 workers listed, each handling a share of the 50 simulated users. The master coordinates the test and distributes users evenly across the workers.

Why is this better than one container?

In Step 3, your single Locust container ran everything in one process. Here, the master pod coordinates the test while the 2 worker pods each run their own process, generating load independently.

This is how teams generate serious load in production. Each worker can run on a different machine, so adding more workers means more concurrent load without bottlenecking on a single process.

No workers showing in the UI?

  • Make sure all 3 pods are in Running status by running kubectl get pods -l locust.io/test-run=load-test in a new terminal window.
  • If the browser shows a connection error, check that the kubectl port-forward command is still running in your terminal. If it stopped, run it again.
  • help me troubleshoot the distributed Locust deployment not showing workers

Your distributed load testing platform is live. Next up, you will scale the worker count and see how easy the Operator pattern makes it to add more firepower.

Scale Workers and Observe the Difference

Your distributed Locust deployment is running with a master coordinating two workers. All of that from a single YAML resource.

In production, two workers might not generate enough load to stress-test a real service. The Operator pattern makes scaling effortless. You change one number in your YAML, apply it, and the operator creates new pods, registers them with the master, and rebalances the load automatically.

In this step, get ready to:
  • Scale your LocustTest from 2 workers to 4 with a single YAML change.
  • Run a 200-user load test and compare throughput against the 2-worker run.
Scale the worker count to 4

The entire scaling operation is a one-line edit to your existing locust-test.yaml file. The operator watches for changes to the LocustTest resource and automatically adjusts the number of worker pods to match.

  • In VS Code, open locust-test.yaml.
  • Find this line under spec::
  workers: 2
  • Change 2 to 4, so the line now looks like this:
  workers: 4
  • Save locust-test.yaml.
  • Apply the updated resource to your cluster by running:
kubectl apply -f locust-test.yaml

Before you watch the pods, think about what you expect to happen. The cluster currently has 1 master and 2 workers. You just told the operator you want 4 workers. Will it replace all the pods, or just add the 2 missing ones?

  • Watch the pods update in real time by running:
kubectl get pods -w -l locust.io/test-run=load-test

What does this command do?

The -w flag stands for "watch." Instead of printing the pod list once and exiting, kubectl keeps the connection open and prints a new line every time a pod's status changes.

The -l locust.io/test-run=load-test flag filters to only the pods managed by your load-test LocustTest resource.

You should see 2 new worker pods appear. Their STATUS changes from Pending to ContainerCreating to Running. The operator only adds the missing pods. Your existing master and 2 workers stay untouched.

  • Once all pods show Running, press Ctrl+C to stop the watch.
  • Confirm the final pod count by running:
kubectl get pods -l locust.io/test-run=load-test

You should see 5 pods total: 1 master and 4 workers, all with STATUS Running.

Why did the operator only add 2 pods?

The operator compares the desired state (4 workers) against the current state (2 workers) and reconciles the difference. It does not tear down and rebuild the entire deployment. This is the core idea behind Kubernetes operators: you declare the end state, and the operator figures out the minimal set of changes to get there.

Pods stuck in Pending or ContainerCreating?

  • Make sure Docker Desktop is running and has enough memory allocated. Each worker pod runs a Locust container.
  • Check if the Floci container is still healthy by running docker ps and confirming it shows as running.
  • help me debug why my Locust worker pods are stuck and not reaching Running status
Run a 200-user load test and compare results

With 4 workers ready, it is time to push more load through the system and see how the extra workers change the results.

  • Port-forward the Locust web UI by running:
kubectl port-forward svc/load-test-webui 8089:8089

Port already in use?

If you see an error about port 8089 being in use, your port-forward from the previous step is still active. That is fine. Skip this command and continue to the next instruction.

  • Open your browser and go to http://localhost:8089.
  • Click the Workers tab at the top of the Locust web UI.

You should see 4 workers listed and connected. This confirms the operator successfully registered all 4 with the master.

Before you start this test, think back to your 2-worker run. Do you think 4 workers will produce noticeably higher RPS, or roughly the same?

  • Switch to the New test form on the main page.
  • Enter 200 in the Number of users field.
  • Enter 20 in the Ramp up field (users started per second).
  • Click Start.

Watch the charts on the main page as the test ramps up. The total RPS climbs as more simulated users come online, and the 200 users are split evenly across your 4 workers (about 50 each).

What should I compare?

  • Total RPS: With 200 users distributed across 4 workers, you should see higher total requests per second than the 2-worker run at 50 users.
  • Response times: The median and 95th percentile times may increase slightly under heavier load. This is expected because the nginx target is handling more concurrent requests.
  • Worker distribution: Check the Workers tab to confirm each of the 4 workers is handling a roughly equal share of the simulated users.
  • Let the test run for at least 30 seconds so the charts stabilize.
  • Click Stop when you have seen the RPS plateau and the worker distribution settle.

Why is this the production workflow?

You just scaled a distributed load-testing platform by editing a single number. No manual container management, no rewriting deployment files, no restarting the master. The operator reconciled the difference and the new workers registered automatically.

In real-world load testing, teams scale from 4 workers to 40 or 400 using the exact same pattern. The YAML edit is identical. Only the infrastructure underneath needs to be larger.

Not seeing 4 workers in the UI?

  • Go back to your terminal and run kubectl get pods -l locust.io/test-run=load-test to confirm all 4 worker pods are in Running status.
  • If workers are still starting, wait a moment and refresh the browser. Workers register with the master once they finish pulling the Locust image.
  • help me figure out why only some of my Locust workers are showing up in the web UI

✔️ Awesome, I've got everything!

Great. Double check you have saved locust-test.yaml with workers: 4.

ⓧ I'd like to double check the full code

Here is the complete locust-test.yaml as it should look now.

apiVersion: locust.io/v1
kind: LocustTest
metadata:
  name: load-test
spec:
  image: locustio/locust:latest
  workers: 4
  args:
    --host http://nginx-target
  locustfile:
    content: |
      from locust import HttpUser, task, between

      class WebsiteUser(HttpUser):
          wait_time = between(1, 3)

          @task(3)
          def view_homepage(self):
              self.client.get("/")

          @task(1)
          def view_missing_page(self):
              with self.client.get("/missing", catch_response=True) as response:
                  if response.status_code == 404:
                      response.success()

Secret mission

Multi-Scenario Load Test with ConfigMap

Your current load test uses a single user class with uniform behavior. Real-world traffic is far more varied. In this secret mission, you will write a multi-scenario locustfile with weighted user classes, deploy it via a Kubernetes ConfigMap, and observe how different endpoints receive different proportions of traffic.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally via Docker containers. There are no cloud resources or ongoing costs to worry about.

Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • Floci Docker container (runs locally, no cost).
  • k3s container created by Floci for the local EKS cluster (runs locally, no cost).
  • Kubernetes resources inside the cluster: Locust Operator pod, Locust master and worker pods, and nginx pod.
  • Local project files: docker-compose.yml, locustfile.py, nginx-deployment.yaml, locust-test.yaml, and the data directory.

Keep everything running

No action needed. Choose this if you're still actively experimenting with your local Kubernetes cluster and Locust setup.

  • Your Floci container and k3s cluster continue running inside Docker Desktop.
  • AWS CLI, kubectl, and Helm remain installed on your machine. These are general-purpose tools useful for future projects.
  • If you completed the Secret Mission, Floci is already stopped. Run docker compose up -d from the folder containing your docker-compose.yml to restart it.

Pause - I'll come back to this later

Stop the running containers to free up Docker resources. Your local project files stay on disk so you can pick up where you left off.

  • Stop Floci and the cluster by running this command from the folder containing your docker-compose.yml:
docker compose stop
  • To resume later, run docker compose start from the same folder. The cluster state may not persist after a stop, so you may need to recreate the EKS cluster and redeploy your resources.
  • If you completed the Secret Mission, Floci is already stopped. No further action needed to pause.

Delete - I don't want to use this again

Remove all project resources and start fresh. This deletes all Docker containers, volumes, and local project files.

  • If your cluster is still running, delete all LocustTest resources first by running:
kubectl delete locusttests --all
  • Remove all Docker containers and volumes by running this from the folder containing your docker-compose.yml:
docker compose down -v

What does the -v flag do?

The -v flag tells Docker Compose to also remove any named volumes defined in the compose file. This ensures no leftover data lingers after the containers are gone.

  • Delete the local project folder containing your docker-compose.yml and other project files. Open File Explorer, navigate to the folder on your Desktop, right-click it, and select Delete.
  • Optionally, uninstall the CLI tools you installed during this project. You can remove kubectl and Helm by running:
winget uninstall --id Kubernetes.kubectl
winget uninstall --id Helm.Helm
  • To uninstall AWS CLI v2, open Settings, select Apps, find AWS Command Line Interface in the list, and select Uninstall.

Nice Work!

Nice Work!

You just built a distributed load-testing platform from scratch, entirely on your local machine. From a single Docker container to a coordinated fleet of workers on Kubernetes, you experienced the full journey of scaling a real testing workload.

You've learned how to:

  • Spun up a local Kubernetes cluster using Floci's EKS emulator, deployed an nginx target service, and verified it was reachable. No AWS account needed.
  • Installed the official Locust Kubernetes Operator via Helm and deployed a distributed load test using the LocustTest CRD, with a master pod coordinating multiple workers.
  • Scaled worker pods from 2 to 4 with a single YAML change and observed the increased throughput in the Locust web UI.
  • Secret Mission: Created a multi-scenario locustfile with weighted user classes (BrowsingUser and APIUser) and deployed it via a Kubernetes ConfigMap.

Ready to quiz yourself?