Abstract
This project demonstrates a CI load-testing setup for two http-echo services running on Kubernetes. It creates a multi-node cluster locally in the CI runner with Kind, routes requests through the NGINX Ingress Controller, and uses k6 to generate traffic and report performance metrics.
The test exercises host-based routing for foo.localhost and bar.localhost, measures response times and failures, and checks results against thresholds. Once complete, the CI workflow can summarize the outcome, retain test artifacts, and remove the temporary cluster.
Table Of Contents
Open Table Of Contents
Solution overview
The test environment is built from a Kind Kubernetes cluster, an NGINX Ingress Controller, two http-echo applications, and a k6 load generator:
┌─────────────────┐
│ k6 load test │
│ │
│ foo.localhost │
│ bar.localhost │
└────────┬────────┘
│ HTTP
▼
┌─────────────────┐
│ NGINX Ingress │
│ Controller │
└────────┬────────┘
│
┌───────────┴───────────┐
▼ ▼
┌───────────────┐ ┌───────────────┐
│ foo http-echo │ │ bar http-echo │
└───────────────┘ └───────────────┘
Kind multi-node cluster
The two deployments use the hashicorp/http-echo:1.0 image and return different responses. Ingress rules route requests according to the Host header, allowing the test to exercise both services through their local hostnames.
CI workflow
The CI workflow is intended to run on pull requests to the default branch. Its stages prepare the runner, provision the cluster, run the load test, and analyze the results.
1. Set up the environment
The runner checks out the project and installs the tools required by the test: Docker, Kind, kubectl, and k6. The setup supports both AMD64 and ARM64 runners. It also maps foo.localhost and bar.localhost to 127.0.0.1 so the test can use host-based URLs.
2. Provision the cluster
Kind creates a multi-node Kubernetes cluster from the project configuration. The workflow installs NGINX Ingress, waits for the cluster components to become ready, and deploys the foo and bar applications and their ingress rules. It verifies that the pods are running and the endpoints are accessible before starting the load test.
The repository keeps the cluster configuration, Kubernetes manifests, and k6 test script separate:
kind-http-echo-test/
├── kind-config.yaml
├── k8s/
│ ├── http-echo-foo.yaml
│ └── http-echo-bar.yaml
└── loadtest/
└── load-test.js
3. Run the load test
After checking endpoint availability, the workflow runs the k6 script. The test distributes randomized requests between the two hostnames, captures host-specific metrics, and writes detailed results in JSON format.
4. Analyze and clean up
The workflow parses the test output, validates it against the configured thresholds, and generates a human-readable summary. It can fail the pipeline when requirements are not met and upload results as CI artifacts for later analysis. Finally, it deletes the Kind cluster to release runner resources.
Load test configuration
The k6 test uses three stages to exercise the services under a changing load:
| Phase | Duration | Virtual users | Purpose |
|---|---|---|---|
| Ramp-up | 10 seconds | 0 to 10 | Increase traffic gradually |
| Sustained load | 30 seconds | 20 | Evaluate performance at peak load |
| Ramp-down | 10 seconds | 20 to 0 | Reduce traffic gradually |
The documented performance thresholds are:
- Response time: 95% of requests should complete in less than 500 ms.
- Error rate: Less than 10% of requests should fail.
- Service availability: Both
fooandbarshould respond successfully.
Metrics and results
k6 captures overall request rate, response-time averages and percentiles, failure rates, and per-host performance. Example results from a successful execution:
{
"Total Requests": 450,
"Requests/sec": "9.00",
"Request Duration": {
"avg": "25.34ms",
"min": "10.12ms",
"max": "89.45ms",
"p90": "45.67ms",
"p95": "52.34ms"
},
"Failed Requests": "0.00%",
"Foo Host Duration": {
"avg": "24.56ms",
"p90": "44.23ms",
"p95": "51.12ms"
},
"Bar Host Duration": {
"avg": "26.12ms",
"p90": "47.11ms",
"p95": "53.56ms"
}
}
Along with the overall summary, the per-host measurements help reveal whether one service is slower or receiving an uneven share of traffic.
Run the project
The CI runner needs Docker, Kind, kubectl, and k6 installed. Configuration and test assets are available in the project repository:
Use the project CI workflow to create the cluster, deploy the services, run k6, inspect the generated report, and clean up the test environment.
Conclusion
Kind and k6 provide a practical way to test Kubernetes routing and service performance inside CI without provisioning a remote cluster. This example covers the full path from creating a multi-node cluster and exposing services through ingress to validating latency and error-rate thresholds.
The same pattern can be extended with additional services, traffic profiles, and performance criteria as a project’s testing needs grow.
References: