As Kubernetes has become the standard for deploying and managing containerized applications, testing has also adapted to this environment. Ensuring the reliability and performance of your application’s endpoints is crucial, especially in cloud-native deployments. A powerful combination to achieve this is using K6 for load and performance testing and Helm with its helm test hooks to integrate testing into the Kubernetes deployment lifecycle.

In this blog post, we’ll explore how you can use K6 for endpoint testing within Kubernetes using Helm’s test hooks. This setup allows you to test your application right after it has been deployed in your Kubernetes cluster, ensuring everything is running as expected before marking the deployment as successful.

What is K6?

K6 is an open-source, developer-centric load testing tool used to test the performance of APIs, web applications, and microservices. It is known for its simplicity and flexibility, allowing users to write performance tests in JavaScript. So in your tests you have a fully loaded programming language where one can also generate different payloads for requests.

What are Helm Test Hooks?

Helm is a package manager for Kubernetes that simplifies the deployment and management of applications. Helm provides test hooks which are essentially Kubernetes jobs that can be executed to verify that the application is functioning as expected after deployment.

Example

Step 1: Write K6 Test Script with your assumptions First, you need to create a K6 test script that defines the test for your application’s endpoints:

import http from 'k6/http';
import {check, fail, group, sleep} from 'k6';

export const options = {
    vus: 1,         // with 1 virtual user
    duration: '5s', // running for 5 seconds
    thresholds: {
        // Set custom thresholds to determine failure
        http_req_failed: ['rate<0.01'],     // http errors should be less than 1%
    }
};

const BASE_URL = `${__ENV.INGRESS_HOST_URL}`

const headers = {
    'apiKey': `${__ENV.KEY_TO_ACCESS_THE_API}`,
    'Content-Type': 'application/json'
};

export default function () {

    group('/your/first/endpoint', function () {

        let res = http.get(`${BASE_URL}/path/to/your/first/endpoint`, {headers: headers});
        let success = check(res, {
            'status was 200': (r) => r.status === 200,
            'one event was returned in array': (r) => r.json().events.length === 1,
        });
        if (!success) {
            fail(`your/first/endpoint is not working`);
        }
        sleep(1)
    })

    group('/create', function () {
        const twoRandomDateTimes = getTwoRandomDateTimes();
        const payload = JSON.stringify({
            startTime: twoRandomDateTimes.start,
            endTime: twoRandomDateTimes.end,
            startNumber: "123",
        });

        let res = http.post(`${BASE_URL}/path/to/your/second/endpoint`, payload, {headers: headers});
        let success = check(res, {
            'status was 200': (r) => r.status === 200,
            'one id was returned': (r) => r.json().id !== "",
        });
        if (!success) {
            fail(`your/second/endpoint is not working`);
        }
        sleep(1)
    })
}

// omitted for the example. Returns a starting and end time on the same day
function getTwoRandomDateTimes() {
}

Step 2: Create a Helm Test Hook Next, integrate this K6 test script into your Helm chart using Helm’s test hook. Helm allows you to define Kubernetes jobs that will execute the tests. You can create a Job resource that runs the K6 test after the Helm deployment using the helm.sh/hook: test annotation.

apiVersion: batch/v1
kind: Job
metadata:
  name: "{{ include "server.fullname" . }}-smoke-test"
  labels:
    {{- include "server.labels" . | nindent 4 }}
  annotations:
    "helm.sh/hook": test
spec:
  template:
    metadata:
      labels:
        {{- include "server.labels" . | nindent 8 }}
    spec:
      containers:
        - name: k6
          image: k6:latest
          command: ['k6', 'run', '/scripts/test.js']
          env:
            - name: INGRESS_HOST_URL
              valueFrom:
                configMapKeyRef:
                  key: INGRESS_HOST_URL
                  name: app-config-map-with-ingress-url
            - name: KEY_TO_ACCESS_THE_API
              valueFrom:
                secretKeyRef:
                  name: app-secret-with-api-key
                  key: API_KEY
          volumeMounts:
            - name: "{{ include "server.fullname" . }}-smoke-test-script"
              mountPath: /scripts
      restartPolicy: Never
      volumes:
        - name: "{{ include "server.fullname" . }}-smoke-test-script"
          configMap:
            name: "{{ include "server.fullname" . }}-smoke-test-script"
  backoffLimit: 0
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: "{{ include "server.fullname" . }}-smoke-test-script"
  labels:
    {{- include "server.labels" . | nindent 4 }}
data:
  test.js: |- {{ range .Files.Lines "tests/k6.js" }}
    {{ . }}{{ end }}

I found the following file structure very suitable to run the tests also from the local machine.

VVP-overview

CI/CD integration

This will trigger the Helm deployment, and after the deployment is complete, the K6 tests will be executed as part of the Helm test hook. You can monitor the output of the tests to ensure the endpoints are working correctly.

helm upgrade --install your-chart RELEASE_NAME --wait
helm test RELEASE_NAME

FluxCD (gitops)

If you using FluxCD, there is a neat option to enable automatically helm tests after a deployment. For more details check the documentaion

spec:
  test:
    enable: true
    ignoreFailures: true

Conclusion

By combining K6 for testing with Helm’s test hooks, you can ensure that your Kubernetes-deployed applications are thoroughly tested before reaching production. This approach integrates reliability testing into your deployment process, catching issues early and improving the overall quality of your application. Testing your endpoints from an user perspective you are also making sure the ingress and the authentication layer is working as excepted.