k6-operator

repository·main·Indexed 21 days ago

https://github.com/grafana/k6-operator

A Kubernetes-native tool for automating the deployment and management of distributed k6 load tests. It introduces Custom Resource Definitions (CRDs) including TestRun for managing test executions and PrivateLoadZone for defining designated sets of nodes for test runs, including integration with Grafana Cloud k6.

Tokens
137.6K
Snippets
367
Records
506
Agent score
73%

What's inside k6-operator

  1. What is the k6 Operator?

    main

    The k6 Operator is a Kubernetes operator designed to run distributed k6 tests within a Kubernetes cluster. It manages the lifecycle of k6 test executions by introducing two Custom Resource Definitions (CRDs):

    1. TestRun: Represents a single k6 test execution. It includes configuration options to adapt the test to different Kubernetes environments.
    2. PrivateLoadZone: Represents a load zone, which is a designated set of nodes within a cluster used to execute k6 test runs. This CRD is integrated with Grafana Cloud k6 and requires a Grafana Cloud account.
  2. How NDE enables multi-region and multi-cluster k6 execution

    main

    Currently, k6-operator is tied to a single Kubernetes cluster. Achieving multi-region or multi-cluster execution is difficult because the operator must manage the networking and orchestration of all clusters.

    Native Distributed Execution (NDE) solves this through a hierarchical coordinator architecture. Instead of the operator managing every single runner across regions, NDE allows for multiple levels of coordinators:

    1. Regional Coordinators: A coordinator is deployed in each region/cluster (acting as data relays to minimize cross-region network traffic).
    2. Central Coordinator: A top-level coordinator manages the overall test state.

    This architecture allows the k6-operator to bootstrap jobs in any region or cluster without needing to understand the underlying complex multi-cluster networking, as the k6 processes themselves handle the coordination via the NDE protocol.

  3. Project an auto-rotating podCertificate

    main

    The podCertificate projected volume source allows a pod to access an auto-rotating credential bundle (private key and certificate chain) for use as a TLS client or server. Kubelet generates a private key and requests a certificate from a named signer. The pod will not start until certificates are issued.

    Important Implementation Details:

    • Format: You can use a single file via credentialBundlePath (recommended) or separate files via keyPath and certificateChainPath.
    • Atomicity: Using credentialBundlePath is preferred because the application can read the bundle atomically. If using separate files, your application must handle the possibility that a certificate rotation occurs between reading the key and the certificate, resulting in a mismatch. You must implement logic to re-read if they are inconsistent.
    • PEM Normalization: Kubelet performs aggressive normalization on PEM contents (stripping comments/headers, deduplicating certificates, and arbitrary ordering).
    # Example conceptual structure for podCertificate
    podCertificate:
      credentialBundlePath: /etc/tls/bundle.pem
      # OR
      keyPath: /etc/tls/key.pem
      certificateChainPath: /etc/tls/chain.pem
  4. Understand the relationship between k6-operator and Native Distributed Execution (NDE)

    main

    The k6-operator is designed as a lightweight Kubernetes interface (the "glue") between the Kubernetes orchestration layer and k6. Historically, the operator has had to act as a heavy test coordinator, managing complex logic like instance synchronization, setup()/teardown() handling, and direct runner connections.

    Native Distributed Execution (NDE) shifts this coordination logic from the operator into the k6 application itself. This transition aims to:

    • Simplify the operator: It moves from being a complex coordinator to a lightweight manager that simply bootstraps k6 processes.
    • Improve reliability: By moving logic like setup()/teardown() into k6, the operator avoids "breaking the abstraction" of Kubernetes (e.g., trying to exert direct process control like sending SIGTERM to a container, which is often unreliable in Kubernetes).
    • Unify OSS and Cloud: It minimizes the logic gap between the OSS k6-operator and Grafana Cloud k6 (GCk6) by providing a common native implementation for distributed runs.
  5. Understand volume types for TestRun spec

    main

    The TestRun.spec.starter.volumes field supports various volume types to provide storage to your k6 pods. When choosing a volume type, consider the following:

    • Ephemeral Volumes (CSI/EphemeralVolumeSource): Use these if the volume is only needed while the pod runs, you need features like capacity tracking, or you are using a storage driver specified via a storageClass that supports dynamic provisioning via PersistentVolumeClaim.
    • Persistent Volumes: Use PersistentVolumeClaim or vendor-specific APIs for volumes that must persist longer than the lifecycle of an individual pod.
    • Local Ephemeral Volumes: Use CSI if your driver is intended for lightweight local ephemeral volumes.

    A pod can use both ephemeral and persistent volumes simultaneously.

  6. Provision a container with a git repository

    main

    The GitRepo volume type is deprecated. Instead of using GitRepo, you should provision a container with a git repository by:

    1. Mounting an EmptyDir into an InitContainer.
    2. Using the InitContainer to clone the repository into the EmptyDir.
    3. Mounting that same EmptyDir into the main Pod's container.
  7. Configure Pod Anti-Affinity for TestRun Runners

    main

    You can control how k6 runner pods are scheduled relative to other pods using podAntiAffinity within the TestRun.spec.runner.affinity configuration. This is useful for ensuring that k6 runners are spread across different nodes or availability zones to prevent resource contention or single points of failure.

    There are two types of anti-affinity rules:

    1. requiredDuringSchedulingIgnoredDuringExecution: Hard requirements. If the anti-affinity rules cannot be met at scheduling time, the pod will not be scheduled. All terms in the list must be satisfied (intersection).
    2. preferredDuringSchedulingIgnoredDuringExecution: Soft requirements. The scheduler will try to find nodes that satisfy these rules based on a weight (1-100). The node with the highest sum of weights is preferred.
    spec:
      runner:
        affinity:
          podAntiAffinity:
            requiredDuringSchedulingIgnoredDuringExecution:
              - podAffinityTerm:
                  labelSelector:
                    matchLabels:
                      app: k6-runner
                  topologyKey: kubernetes.io/hostname
            preferredDuringSchedulingIgnoredDuringExecution:
              - weight: 100
                podAffinityTerm:
                  labelSelector:
                    matchLabels:
                      app: k6-runner
                  topologyKey: kubernetes.io/hostname
  8. Use podCertificate for auto-rotating credentials

    main

    The podCertificate source projects an auto-rotating credential bundle (private key and certificate chain) into the pod. This allows the pod to use these credentials as a TLS client or server.

    Best Practice: Use credentialBundlePath instead of separate keyPath and certificateChainPath. The credentialBundlePath provides a single PEM file containing the private key followed by the certificate chain. This allows your application to perform an atomic read, ensuring the key and certificate are always consistent. If you use separate paths, your application must manually verify that the key and certificate match, as they might be read mid-rotation.

    Key Configuration Options:

    • keyType (string, required): The type of keypair. Valid values: RSA3072, RSA4096, ECDSAP256, ECDSAP384, ECDSAP521, ED25519.
    • signerName (string, required): The name of the signer to which CSRs will be addressed.
    • credentialBundlePath (string, optional): Path to the single PEM file containing the key and chain.
    • maxExpirationSeconds (integer, optional): Maximum lifetime for the certificate. If omitted, defaults to 86400 (24 hours). Minimum is 3600 (1 hour), maximum is 7862400 (91 days).
  9. Available Resource Types in k6.io/v1alpha1

    main

    The k6.io/v1alpha1 API group defines the core custom resources used by the k6 operator to manage load tests and execution environments. The primary resource types available are:

    • TestRun: Defines the lifecycle and configuration of a k6 test execution.
    • PrivateLoadZone: Defines a private execution environment (load zone) for running tests, typically used for testing internal services that are not exposed to the public internet.
  10. Configure Pod Affinity for TestRun Runners

    main

    You can control where k6 runner pods are scheduled in your cluster by using podAffinity within the TestRun.spec.runner.affinity configuration. This allows you to co-locate runners with specific pods or ensure they are scheduled on nodes that satisfy certain criteria.

    There are two types of affinity available:

    1. requiredDuringSchedulingIgnoredDuringExecution: Hard requirements. If these affinity rules are not met at scheduling time, the pod will not be scheduled.
    2. preferredDuringSchedulingIgnoredDuringExecution: Soft requirements. The scheduler will try to find nodes that satisfy these rules based on a weight (1-100), but it may choose a node that violates them if necessary.
  11. How the Private Load Zone (PLZ) lifecycle works

    main

    The Private Load Zone (PLZ) feature allows k6-operator to execute tests managed by Grafana Cloud k6 (GCk6). The lifecycle follows these steps:

    1. Creation: The user explicitly creates a PLZ resource using Kubernetes tooling (e.g., kubectl apply -f plz.yaml).
    2. Registration: Upon creation, k6-operator registers the PLZ with the GCk6 API.
    3. Polling: k6-operator enters a polling loop, checking the GCk6 API every 10 seconds for new test runs assigned to that PLZ.
    4. Execution: When a test run is detected, k6-operator fetches metadata (including an S3 presigned URL for the k6 archive and runner requirements) and creates a TestRun Custom Resource (CR) in Kubernetes.
    5. Cleanup: When the user deletes the PLZ resource (e.g., kubectl delete -f plz.yaml), k6-operator deregisters it from the GCk6 API.
    # Create a PLZ resource
    kubectl apply -f plz.yaml
    
    # Delete a PLZ resource
    kubectl delete -f plz.yaml
  12. Understand TestRun status and lifecycle stages

    main

    The TestRun.status field provides the observed state of a TestRun. The stage field is an enum that describes the current phase of the test execution lifecycle.

    | Name | Type | Description | Required |
    | :--- | :--- | :--- | :--- |
    | **aggregationVars** | string | | false |
    | **conditions** | []object | | false |
    | **stage** | enum | Stage describes which stage of the test execution lifecycle k6 runners are in. <br/> **Enum**: `initialization`, `initialized`, `created`, `started`, `stopped`, `finished`, `error` | false |
    | **testRunId** | string | | false |