This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Developer Guide

Build a custom Krkn scenario, package its image contract, and run it through Krkn Operator.

Develop a scenario once, then expose it through a container image that Krkn’s clients can discover and configure. This guide follows the full path from a Python plugin to an execution managed by Krkn Operator.

The example is deliberately small: wait for a configurable number of seconds, list pods in a namespace through krkn-lib, and report success or failure. It lets you verify the integration without introducing a disruptive experiment.

Companion repository: krkn-chaos/krkn-dev-demo contains the plugin skeleton, container files, and Kind and registry configuration used throughout the lab.

Start with the hands-on lab

Build a custom scenario takes you through four stages:

  1. Implement the plugin. Complete the two abstract methods and bind a scenario type to a YAML configuration.
  2. Package and test the image. Add input metadata, render the configuration, and run the container with Podman.
  3. Run through the operator. Install a private registry and Krkn Operator in Kind, publish the image, and execute it against a registered target.
  4. Diagnose the integration. Recognize discovery, image pull, permission, kubeconfig, and plugin failures independently.

The lab builds a standalone scenario image from a custom Krkn base image. It does not require a krkn-hub checkout or a rebuild of krknctl or the operator. Target registration is supplied by the participant and is outside the lab.

Understand the contracts

Read Scenario architecture for the responsibilities of the registry, operator, node runtime, image startup script, Python plugin, and target cluster.

Validate and contribute

For upstream changes, follow the contribution guidelines and include the scenario’s documentation and relevant tests. Development questions are welcome in #krkn on Kubernetes Slack.

1 - Scenario Architecture

Follow a scenario from registry metadata and typed inputs to execution against a target cluster.

A custom scenario connects two contracts: a container image contract for discovery and execution, and a Python plugin contract for the Krkn engine. The image carries the metadata needed to present a scenario to users; the plugin implements the behavior that Krkn runs.

A registry supplies image metadata to krknctl and Krkn Operator. The operator creates a scenario Pod, whose startup script turns environment variables into YAML. Krkn selects the plugin, which calls the target API through krkn-lib.

The same image connects discovery, configuration, and execution. Select the diagram to view it at full size.

Discovery reads the image

The registry stores both the image layers and the image configuration containing its labels. Clients use registry APIs to read tags, manifests, and configuration blobs. krknctl.is_a_scenario="true" identifies a scenario, and krknctl.input_fields describes its inputs.

There is no separate scenario catalog database to update. Kubernetes resources still store operator configuration, credentials, and run state. A registry providing the catalog does not mean the platform is stateless.

The input metadata is a JSON array of Krkn field descriptors. Its name, variable, and type properties connect a user-facing input to a container environment variable. It is not a general-purpose JSON Schema document. See the image contract reference.

The operator schedules the runner

The console reads scenario metadata through the operator API, presents inputs, and submits a run. The operator resolves the scenario image, checks its signature according to the configured policy, and creates the scenario Pod in the operator’s management cluster.

For the operator version used in the lab, the container receives:

  • The image reference resolved to an immutable digest.
  • Environment variables derived from the chosen parameters.
  • The selected target’s kubeconfig and any configured file mounts.
  • An image pull secret when the scenario uses a saved private registry.

The console calls these executions jobs. In this implementation the controller creates Pods directly; a console job does not imply a Kubernetes Job object.

The node’s kubelet asks its container runtime to pull the image. Registry access from the operator and image pulls from containerd are separate connections. Enabling HTTP in the operator’s registry settings does not change containerd. This distinction explains why a scenario can appear in the console while its Pod remains in ImagePullBackOff.

The image turns inputs into scenario configuration

In the demo, the user-facing wait input maps to WAIT_SECONDS; namespace maps to NAMESPACE. The image’s /home/krkn/run.sh renders these variables into a scenario YAML file and selects that file in the Krkn configuration:

kraken:
  kubeconfig_path: /home/krkn/.kube/config
  chaos_scenarios:
    - dev_demo_scenarios:
        - scenarios/dev_demo.yaml

The Krkn entrypoint starts the engine with that configuration. The plugin factory discovers Python classes, and get_scenario_types() maps dev_demo_scenarios to DevDemoScenarioPlugin. For each configured scenario file, the engine passes the path to run() together with the telemetry objects.

The plugin uses lib_telemetry.get_lib_kubernetes() to interact with the target cluster. The mounted kubeconfig determines that target; the location of the scenario Pod does not.

The container contract also supports other implementations

A scenario can implement its behavior in a shell script or another language. It still needs the metadata and runtime paths expected by its client. In particular, the operator used in this lab invokes /home/krkn/run.sh through /bin/bash -c, so an arbitrary image entrypoint alone is insufficient.

The Python path adds Krkn’s plugin discovery, telemetry integration, and scenario lifecycle. A plugin should use existing krkn-lib helpers, contain its exceptions, and return 0 on success or 1 on failure. Scenarios that modify resources also need a recovery strategy; the demo only reads resources.

What changes when you publish an update

Editing Python source does not change an already built image. Rebuild the custom base image after plugin edits, then rebuild and push the scenario image. Changes to input metadata, startup scripts, or templates require rebuilding the scenario image as well.

Existing Pods retain the image digest they were created with. Start a new run after publishing a new tag value, and confirm the resolved image in the run details. Do not assume that a retry used the updated image without checking its digest.

Continue with the hands-on lab.

2 - Build a Custom Scenario

Build, package, publish, and run a small custom Krkn plugin with Podman, Kind, and Krkn Operator.

Build a scenario that waits, lists pods in a namespace, and returns a clear result. Start with a plugin skeleton in its own repository, add two methods, package the plugin in a Krkn image, and expose it to Krkn Operator through a private registry running inside Kind.

Get the lab files: krkn-chaos/krkn-dev-demo. The commands below pin revision bf2ea8d, including the executable-script and containerd configuration fixes used in this walkthrough.

This lab focuses on the integration contract. The scenario reads Kubernetes resources and does not disrupt workloads. You provide the target cluster and register it with the operator separately; this lab creates only the Kind management cluster that hosts the operator and registry.

What you will build

  • DevDemoScenarioPlugin, discovered as dev_demo_scenarios.
  • A custom Krkn base image containing that plugin.
  • A scenario image exposing wait and namespace through OCI labels.
  • An authenticated registry, installed with Helm inside Kind.
  • An operator run whose logs show the development banner and target pod names.

The plugin is built directly into the custom base image. There is no krkn-hub checkout in this workflow, and the image metadata supplies the client-facing parameter definitions.

Before you begin

Install Git, Podman, Kind, kubectl, Helm 3, jq, envsubst from GNU gettext, OpenSSL, and an htpasswd implementation supporting bcrypt (-B). Have Python 3 available for the optional source syntax check. On macOS, start Podman Machine and use a rootful Podman connection for this Kind lab.

You also need:

  • Internet access to clone the source and pull the published images and charts.
  • A running cluster and kubeconfig for the local container test. If it is a Kind cluster, know its name from kind get clusters.
  • Free host ports 5001 for the registry and 3000 for the console forward.
  • An operator target whose Kubernetes API is reachable from the management cluster’s scenario Pods. Registering that target is outside this lab.

The examples use Kind node image kindest/node:v1.32.2 with containerd 2, Docker Registry chart 3.0.0, and Krkn Operator chart 1.1.2. These are the lab’s version choices, not a claim that they are the latest releases. The companion repository is pinned below so the permission and registry fixes demonstrated here are included.

Prepare the workspace

Run these commands in one shell and keep its exported variables for the whole lab. Paths are absolute, so later commands do not depend on the current working directory. Start from /tmp in case a previous shell was left inside a removed checkout.

cd /tmp

export KRKN_DIR=/tmp/krkn
export KRKN_DEV_PLUGIN_DIR="$KRKN_DIR/krkn/scenario_plugins/dev_demo"
export KRKN_BASE_IMAGE=krkn-dev-demo:base
export KRKN_SCENARIO_IMAGE=krkn-dev-demo:scenario
export KIND_EXPERIMENTAL_PROVIDER=podman
export KRKN_DEMO_REVISION=bf2ea8d920de5a6e5d8ea26a8d705ff600bc6684

git clone https://github.com/krkn-chaos/krkn.git "$KRKN_DIR"
git clone https://github.com/krkn-chaos/krkn-dev-demo.git "$KRKN_DEV_PLUGIN_DIR"
git -C "$KRKN_DEV_PLUGIN_DIR" checkout "$KRKN_DEMO_REVISION"

Use an unused KRKN_DIR. If /tmp/krkn already contains work, choose another absolute path before cloning. Do not remove an existing checkout to make the command succeed.

The pinned plugin repository is a separate checkout nested inside Krkn’s plugin directory. It contains:

dev_demo/
├── __init__.py
├── dev_demo_scenario_plugin.py  # helper implemented; abstract methods left for you
├── scenario.yaml               # example plugin input
├── scenario.yaml.template      # environment-to-YAML template
├── env.sh                      # container defaults
├── run.sh                      # render configuration and start Krkn
├── krknctl-input.json           # user-facing input descriptors
├── Dockerfile.base             # add the plugin to a published Krkn runtime
├── Dockerfile.template         # package the scenario contract
├── kind-config.yml             # registry port and containerd hosts directory
└── registry-values.yml         # authenticated registry Helm values

The cloned Krkn source lets you inspect its interface and loader. The base image build copies the plugin into the published Krkn runtime; it does not rebuild the rest of the cloned engine. Pin the published base image as well when turning this workshop into a repeatable release pipeline.

Follow the lab

  1. Implement the plugin.
  2. Package and test the image.
  3. Publish and run with Krkn Operator.

Keep Troubleshooting open while working. It covers the failures encountered while developing this demo, including successful discovery followed by an HTTPS image pull error.

2.1 - Implement the Plugin

Complete the abstract interface and connect a scenario YAML file to krkn-lib operations.

Starting point: complete the workspace setup. Result: a concrete plugin class that Krkn can discover and execute.

Read the prepared behavior

Open $KRKN_DEV_PLUGIN_DIR/dev_demo_scenario_plugin.py. Its DevDemoScenarioPlugin class already implements execute_demo(scenario, lib_telemetry). The method:

  1. Reads the YAML file identified by scenario.
  2. Requires wait to be an integer from 0 to 60, and namespace to be a nonempty string. A YAML boolean is not accepted as an integer.
  3. Sleeps for the requested duration.
  4. Obtains the Kubernetes helper from lib_telemetry and checks namespace existence with list_namespaces().
  5. Lists pods with list_pods(namespace) and logs a distinctive banner.
  6. Returns 0 on success and 1 on a missing namespace or another failure.

The input file is intentionally small:

wait: 2
namespace: default

The helper uses the cluster client already initialized by Krkn:

            kubernetes = lib_telemetry.get_lib_kubernetes()
            if namespace not in kubernetes.list_namespaces():
                logging.error(
                    "Development scenario: namespace '%s' does not exist", namespace
                )
                return 1

            pods = kubernetes.list_pods(namespace)

Checking the namespace list avoids intentionally triggering the library’s verbose read_namespace_status error path for a missing namespace. This demo therefore requires permission to list namespaces as well as pods. An existing namespace with no pods is a successful result and is logged as (none).

Implement the two abstract methods

Ensure these imports are present once at the top of the file:

from krkn_lib.models.telemetry import ScenarioTelemetry
from krkn_lib.telemetry.ocp import KrknTelemetryOpenshift

Paste the following methods inside DevDemoScenarioPlugin, at the same indentation level as execute_demo(). The four-space class indentation is included for copying:

    def get_scenario_types(self) -> list[str]:
        return ["dev_demo_scenarios"]

    def run(
        self,
        run_uuid: str,
        scenario: str,
        lib_telemetry: KrknTelemetryOpenshift,
        scenario_telemetry: ScenarioTelemetry,
    ) -> int:
        return self.execute_demo(scenario, lib_telemetry)

get_scenario_types() supplies the configuration key that selects this class. run() delegates to the helper, which contains the validation and exception handling. The framework supplies the run identifier and telemetry record even though this small helper does not need to use them directly.

The class remains abstract until both methods are implemented. Avoid renaming the package to dev_demo_plugin or dev_demo_scenario: those directory names violate the factory’s naming rules. The module is dev_demo_scenario_plugin.py and its class is DevDemoScenarioPlugin.

Understand the configuration binding

The main Krkn configuration selects the type and its scenario file:

kraken:
  chaos_scenarios:
    - dev_demo_scenarios:
        - scenarios/dev_demo.yaml

The engine calls run(scenario="scenarios/dev_demo.yaml", ...). The plugin opens that file and reads wait and namespace. The scenario type, file path, and field names serve different purposes; changing one does not automatically change the others.

For a source-based Krkn run, copy the sample and derive a dedicated main config:

cp "$KRKN_DEV_PLUGIN_DIR/scenario.yaml" "$KRKN_DIR/scenarios/dev_demo.yaml"
cp "$KRKN_DIR/config/config.yaml" "$KRKN_DIR/config/dev_demo_config.yaml"

Set the kraken.chaos_scenarios section in dev_demo_config.yaml to the example above. The next part packages this same binding in the image’s startup script, so a source-based run is optional.

Check before building

python3 -m py_compile "$KRKN_DEV_PLUGIN_DIR/dev_demo_scenario_plugin.py"

The plugin keeps a nonzero result meaningful: an invalid input or missing namespace must not be converted into success just to keep the container running. For a real chaos action, add restoration and rollback behavior appropriate to the resources it changes.

Next: Package and test the image.

2.2 - Package and Test the Image

Build a custom Krkn base and a standalone scenario image, then verify its metadata and runtime behavior.

Starting point: the plugin methods are implemented. Result: a locally tested image that clients can discover through its labels.

Build the custom Krkn base

Dockerfile.base overlays the local Python package on the published runtime:

FROM quay.io/krkn-chaos/krkn:latest

COPY --chown=krkn:krkn __init__.py \
    /home/krkn/kraken/krkn/scenario_plugins/dev_demo/__init__.py
COPY --chown=krkn:krkn dev_demo_scenario_plugin.py \
    /home/krkn/kraken/krkn/scenario_plugins/dev_demo/dev_demo_scenario_plugin.py
podman build \
  -f "$KRKN_DEV_PLUGIN_DIR/Dockerfile.base" \
  -t "$KRKN_BASE_IMAGE" \
  "$KRKN_DEV_PLUGIN_DIR"

This build context is the plugin repository. Using Krkn’s upstream container Dockerfile unchanged would clone Krkn from GitHub during the build and omit the local plugin. If you change the core engine or krkn-lib, build a runtime that includes those changes explicitly.

Inspect the scenario contract

The image has two user inputs in krknctl-input.json:

[
  {
    "name": "wait",
    "short_description": "Wait (seconds)",
    "description": "Seconds to wait before listing pods in the namespace (0 to 60).",
    "variable": "WAIT_SECONDS",
    "type": "number",
    "required": "false"
  },
  {
    "name": "namespace",
    "short_description": "Namespace",
    "description": "Kubernetes namespace whose pod names will be logged.",
    "variable": "NAMESPACE",
    "type": "string",
    "required": "true",
    "validator": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$",
    "validation_message": "namespace must be a Kubernetes namespace name"
  }
]

name identifies the input presented by a client; variable is the environment variable injected into the container. The string values "true" and "false" match krknctl’s field descriptor format. The plugin still enforces its own integer range: a generic number field alone does not restrict input to an integer from 0 to 60.

Dockerfile.template embeds the JSON in krknctl.input_fields, together with the other labels included in the companion image:

LABEL krknctl.is_a_scenario="true"
LABEL krknctl.privileged="false"
LABEL krknctl.has_rollback="false"
LABEL krknctl.kubeconfig_path="/home/krkn/.kube/config"
LABEL krknctl.title="Dev Demo"
LABEL krknctl.description="Waits, then logs pod names from one Kubernetes namespace."
LABEL krknctl.input_fields='$KRKNCTL_INPUT'

The same Dockerfile copies the startup script with an explicit executable bit:

COPY --chmod=0755 --chown=krkn:krkn run.sh /home/krkn/run.sh
ENTRYPOINT ["/bin/bash", "/home/krkn/run.sh"]

This permission is required because the operator invokes /home/krkn/run.sh directly. A local entrypoint that passes the script to bash can conceal a missing executable bit.

Follow the runtime configuration

env.sh supplies defaults of 2 seconds and namespace default for direct container execution. The console still requires the namespace input declared in the metadata.

scenario.yaml.template contains:

wait: ${WAIT_SECONDS}
namespace: ${NAMESPACE}

run.sh changes to /home/krkn/kraken and uses an explicit substitution list:

envsubst '$WAIT_SECONDS $NAMESPACE' \
  < scenarios/scenario.yaml.template > scenarios/dev_demo.yaml

It derives config/dev_demo_config.yaml from the base configuration, sets kraken.kubeconfig_path from KUBECONFIG, and selects only dev_demo_scenarios. For this focused demo, telemetry export, event backup, reports, resiliency scoring, and the inter-scenario stabilization delay are disabled. These are demo settings, not a requirement for custom plugins.

Finally it hands control to Krkn while preserving the process exit status:

exec ./containers/entrypoint.sh --config=config/dev_demo_config.yaml

Build and inspect the scenario image

Compact the JSON, expand only its build-time placeholder, and select the custom base image in the generated Dockerfile. The runtime placeholders in the YAML and shell files remain untouched.

export KRKNCTL_INPUT="$(jq -c . "$KRKN_DEV_PLUGIN_DIR/krknctl-input.json")"
envsubst '${KRKNCTL_INPUT}' \
  < "$KRKN_DEV_PLUGIN_DIR/Dockerfile.template" |
  sed "1s|^FROM .*|FROM $KRKN_BASE_IMAGE|" \
  > "$KRKN_DEV_PLUGIN_DIR/Dockerfile"

podman build \
  -f "$KRKN_DEV_PLUGIN_DIR/Dockerfile" \
  -t "$KRKN_SCENARIO_IMAGE" \
  "$KRKN_DEV_PLUGIN_DIR"

podman image inspect "$KRKN_SCENARIO_IMAGE" |
  jq '.[0].Config.Labels["krknctl.input_fields"] | fromjson'
podman run --rm --user 1001:1001 --entrypoint /bin/bash \
  "$KRKN_SCENARIO_IMAGE" -c 'test -x /home/krkn/run.sh'

Run against an existing Kind cluster

Set KRKN_TEST_CLUSTER to a running cluster you supply. This test does not create or register an operator target.

Kind’s host kubeconfig normally uses a loopback address. A container on the kind network needs the internal API address instead. Save that kubeconfig under your home directory so Podman Machine on macOS can mount it. The temporary directory is private; the mounted file is readable by container UID 1001.

export KRKN_TEST_CLUSTER=kind
export KRKN_TEST_NAMESPACE=default

(
  set -e
  mkdir -p "$HOME/.kube"
  KRKN_TEST_CONFIG_DIR="$(mktemp -d "$HOME/.kube/krkn-dev-demo.XXXXXX")"
  trap 'rm -rf "$KRKN_TEST_CONFIG_DIR"' EXIT
  kind get kubeconfig --internal --name "$KRKN_TEST_CLUSTER" \
    > "$KRKN_TEST_CONFIG_DIR/config"
  chmod 644 "$KRKN_TEST_CONFIG_DIR/config"

  podman run --rm --user 1001:1001 --network kind \
    -e WAIT_SECONDS=2 \
    -e NAMESPACE="$KRKN_TEST_NAMESPACE" \
    -e KUBECONFIG=/home/krkn/.kube/config \
    -v "$KRKN_TEST_CONFIG_DIR/config:/home/krkn/.kube/config:ro" \
    --entrypoint /bin/bash \
    "$KRKN_SCENARIO_IMAGE" -c 'exec /home/krkn/run.sh'
)

On a Linux host with SELinux enforcing, use :ro,Z for this dedicated temporary kubeconfig mount if a label denial prevents access.

For a non-Kind target, use a kubeconfig whose API endpoint is reachable from the container. Replace kind get kubeconfig ... with kubectl --kubeconfig "$KUBECONFIG" config view --raw --flatten --minify and omit --network kind. Flattening embeds certificate files; an external credential plugin still needs to be available in the image.

Check both outcomes

For an existing namespace, expect container exit status 0 and a log block:

##### DEVELOPMENT SCENARIO #####
Namespace: default
Pods:
  (none)
##### END DEVELOPMENT SCENARIO #####

Pod names replace (none) when the namespace contains pods. Set KRKN_TEST_NAMESPACE=dev-demo-missing-namespace to a namespace you have confirmed does not exist and repeat the test. Expect a nonzero exit status and a concise missing-namespace error. Do not create that namespace as part of this failure-path test.

Next: Publish and run with Krkn Operator.

2.3 - Publish and Run with Krkn Operator

Host an authenticated registry inside Kind, configure containerd, and run the custom scenario through Krkn Operator.

Starting point: the scenario image passes its local tests. Result: the operator discovers the image, renders its two inputs, and runs it against a target you have already registered.

The management cluster hosts the registry, operator, and scenario Pods. The target’s kubeconfig is mounted into each scenario Pod. You do not need to place the private registry on the target or configure the target’s runtime to pull this scenario image.

Podman pushes to localhost port 5001, which maps to the Kind registry NodePort 30500. Inside Kind, the operator reads metadata and containerd pulls image layers through separate HTTP connections to the stable node hostname. The scenario Pod then uses a mounted kubeconfig to reach the target API.

One registry, two client configurations. Select the diagram to view it at full size.

1. Create the management cluster

Use a dedicated name so an existing Kind cluster is preserved:

export KIND_EXPERIMENTAL_PROVIDER=podman
export KRKN_MGMT_CLUSTER=krkn-dev-demo-mgmt
export KRKN_MGMT_CONTEXT="kind-$KRKN_MGMT_CLUSTER"
export KRKN_OPERATOR_NAMESPACE=krkn-operator-system

kind create cluster --name "$KRKN_MGMT_CLUSTER" \
  --config "$KRKN_DEV_PLUGIN_DIR/kind-config.yml"
kubectl --context "$KRKN_MGMT_CONTEXT" get nodes

The repository’s kind-config.yml contains:

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
containerdConfigPatches:
  - |-
    [plugins."io.containerd.cri.v1.images".registry]
      config_path = "/etc/containerd/certs.d"
nodes:
  - role: control-plane
    image: kindest/node:v1.32.2
    extraPortMappings:
      - containerPort: 30500
        hostPort: 5001
        listenAddress: "127.0.0.1"
        protocol: TCP

The port mapping is created with the node container. Editing this file does not change an existing cluster. The containerd plugin key above is for the containerd 2 runtime in this node image.

2. Install the authenticated registry

Keep generated credentials outside the checkout. Run the following in the same shell so the registry credentials remain available for the push and UI setup:

export KRKN_DEMO_STATE_DIR="$(mktemp -d /tmp/krkn-dev-demo.XXXXXX)"
chmod 700 "$KRKN_DEMO_STATE_DIR"
export REGISTRY_USER=demo
export REGISTRY_PASSWORD="$(openssl rand -hex 16)"
htpasswd -Bbn "$REGISTRY_USER" "$REGISTRY_PASSWORD" \
  > "$KRKN_DEMO_STATE_DIR/registry.htpasswd"
chmod 600 "$KRKN_DEMO_STATE_DIR/registry.htpasswd"

helm repo add twuni https://twuni.github.io/docker-registry.helm
helm repo update twuni
helm upgrade --install demo-registry twuni/docker-registry \
  --version 3.0.0 \
  --kube-context "$KRKN_MGMT_CONTEXT" \
  --namespace registry --create-namespace \
  --values "$KRKN_DEV_PLUGIN_DIR/registry-values.yml" \
  --set-file "secrets.htpasswd=$KRKN_DEMO_STATE_DIR/registry.htpasswd" \
  --wait --timeout 5m

The Twuni chart deploys Docker Distribution. The demo values select NodePort 30500, service port 5000, and persistence.enabled: false. Storage is disposable: replacing the registry Pod or deleting the cluster can remove the pushed images. Use TLS and durable storage for a registry outside this local lab.

Verify authentication through the host port:

curl --fail --silent --show-error \
  -u "$REGISTRY_USER:$REGISTRY_PASSWORD" http://localhost:5001/v2/

Expect {}. An unauthenticated request should receive HTTP 401.

3. Configure image pulls on the management node

Use the node’s name on the Podman kind network as the registry address inside the cluster. This avoids embedding an IP that can change after a Podman Machine restart. The host continues to push through localhost:5001.

export KRKN_MGMT_NODE="$(kind get nodes --name "$KRKN_MGMT_CLUSTER" | head -n 1)"
export KRKN_REGISTRY_URL="$KRKN_MGMT_NODE:30500"
printf 'Registry URL for the operator: %s\n' "$KRKN_REGISTRY_URL"

for node in $(kind get nodes --name "$KRKN_MGMT_CLUSTER"); do
  registry_dir="/etc/containerd/certs.d/$KRKN_REGISTRY_URL"
  podman exec "$node" mkdir -p "$registry_dir"
  printf 'server = "http://%s"\n[host."http://%s"]\n  capabilities = ["pull", "resolve"]\n' \
    "$KRKN_REGISTRY_URL" "$KRKN_REGISTRY_URL" |
    podman exec -i "$node" sh -c "cat > '$registry_dir/hosts.toml'"
done

The fresh cluster already has config_path enabled. Changes to the hosts files do not require a containerd restart. For a cluster created with an older config, follow Repair an existing node before retrying a pull.

The registry URL, hosts.toml directory name, and operator pull secret must use the same host and port. The operator’s Allow Insecure HTTP option affects its registry API requests; this node configuration controls the actual pull. Skip TLS Verification only changes certificate checks and does not enable HTTP. See the containerd registry documentation.

Check that cluster Pods can resolve and reach the chosen hostname:

kubectl --context "$KRKN_MGMT_CONTEXT" -n registry run dev-demo-registry-probe \
  --image=curlimages/curl:8.9.1 --restart=Never --attach=true --rm \
  --command -- curl --silent --show-error --output /dev/null \
  --write-out 'HTTP %{http_code}\n' "http://$KRKN_REGISTRY_URL/v2/"

Expect HTTP 401: this probe intentionally sends no credentials. If hostname resolution fails, fix the Kind/Podman network before registering the URL.

4. Push the scenario tag

export KRKN_PRIVATE_SCENARIO=localhost:5001/demo/krkn-hub:dev-demo
podman tag "$KRKN_SCENARIO_IMAGE" "$KRKN_PRIVATE_SCENARIO"
printf '%s' "$REGISTRY_PASSWORD" | podman login \
  --tls-verify=false --username "$REGISTRY_USER" --password-stdin localhost:5001
podman push --tls-verify=false "$KRKN_PRIVATE_SCENARIO"

curl --fail --silent --show-error -u "$REGISTRY_USER:$REGISTRY_PASSWORD" \
  http://localhost:5001/v2/demo/krkn-hub/tags/list

The response should list dev-demo. The repository is demo/krkn-hub; the tag is the scenario identity. The operator accesses the same content as $KRKN_REGISTRY_URL/demo/krkn-hub:dev-demo, using the internal address.

5. Install Krkn Operator

helm upgrade --install krkn-operator \
  oci://quay.io/krkn-chaos/charts/krkn-operator \
  --version 1.1.2 \
  --kube-context "$KRKN_MGMT_CONTEXT" \
  --namespace "$KRKN_OPERATOR_NAMESPACE" --create-namespace \
  --wait --timeout 10m

kubectl --context "$KRKN_MGMT_CONTEXT" \
  -n "$KRKN_OPERATOR_NAMESPACE" get pods,services

In another terminal, start the console forward using explicit values so it does not depend on exports from the first shell:

kubectl --context kind-krkn-dev-demo-mgmt -n krkn-operator-system \
  port-forward service/krkn-operator-console 3000:3000

Open http://localhost:3000, create the first administrator account, and sign in. Keep the forward running. If you changed the cluster or namespace names, use those names in the second terminal as well.

6. Register the image source

In Settings → Private Registries, add:

  • Name: dev-demo-registry.
  • Registry URL: the value of $KRKN_REGISTRY_URL, without http://.
  • Scenario Repository: demo/krkn-hub.
  • Authentication Type: Username & Password.
  • Username and password: the values of $REGISTRY_USER and $REGISTRY_PASSWORD, not their variable names.
  • Allow Insecure HTTP: enabled.
  • Skip TLS Verification: disabled; this local registry serves plain HTTP.
  • Available to All Users: enabled for this dedicated demo installation.

For the default cluster name, the URL is krkn-dev-demo-mgmt-control-plane:30500. The operator stores the credentials in a Kubernetes pull secret. If you edit the URL later, use the registry form so the stored URL and authentication host stay aligned.

The new custom image is unsigned. On the same settings tab, disable Image signature verification for this dedicated lab installation. This setting is operator-wide. Normal deployments should require signatures that the operator trusts; adding an arbitrary signature is not sufficient. For the official scenario collection, see mirroring signed images.

7. Run against your registered target

Provide a target through your usual operator setup. Its API must be reachable from a scenario Pod in the management cluster, and its credentials must allow listing namespaces and pods. This lab does not create or register the target.

In Run Scenario:

  1. Select your registered target.
  2. Choose the private registry dev-demo-registry.
  3. Select dev-demo (display title Dev Demo).
  4. Set Wait (seconds) to 2 and Namespace to an existing namespace.
  5. Review the configuration and start the run.

In Jobs, inspect the resolved image, status, and logs. Expect the ##### DEVELOPMENT SCENARIO ##### banner, the selected namespace’s pod list, and a successful result. An empty existing namespace is also successful.

Inspect the Kubernetes resources separately:

kubectl --context "$KRKN_MGMT_CONTEXT" \
  -n "$KRKN_OPERATOR_NAMESPACE" get krknscenarioruns,pods

For the selected krkn-job-... Pod, use kubectl describe pod to inspect pull events and kubectl logs to inspect plugin output. The current controller creates Pods directly; kubectl get jobs can legitimately return no resources.

Run again with a namespace you have confirmed is absent. Expect the plugin to return 1 with a concise error. That failure proves the input and exit status reached the plugin; it is different from a Pod that never started.

Iterate on the scenario

After editing plugin Python, rebuild the base and scenario images. For a startup-script, template, or metadata change, rebuild the scenario image. Repeat the tag and push commands, then start a new run and inspect its resolved digest. Existing Pods retain the digest they were created with.

Continue with Troubleshooting or Testing changes.

2.4 - Troubleshooting the Demo

Diagnose discovery, registry transport, image startup, kubeconfig, and plugin failures at the layer that owns them.

First determine whether the scenario was discovered, whether its image was pulled, and whether its process started. These are separate checkpoints. Kubernetes events explain scheduling and pull failures; container logs explain startup and plugin failures.

The scenario is visible, but the pull attempts HTTPS

Typical event:

http: server gave HTTP response to HTTPS client

The operator’s registry client successfully used HTTP to list metadata. Kubelet then asked containerd to pull the image, and containerd defaulted to HTTPS. Krknctl’s insecure setting does not configure the node runtime.

Check all three items on the management node where the scenario Pod runs:

  1. The effective containerd registry config_path is /etc/containerd/certs.d.
  2. /etc/containerd/certs.d/$KRKN_REGISTRY_URL/hosts.toml exists and uses http:// for both the server and host entry.
  3. The registry host and port exactly match the Pod’s image reference.
podman exec "$KRKN_MGMT_NODE" containerd --version
podman exec "$KRKN_MGMT_NODE" cat /etc/containerd/config.toml
podman exec "$KRKN_MGMT_NODE" \
  cat "/etc/containerd/certs.d/$KRKN_REGISTRY_URL/hosts.toml"

The lab uses this containerd 2 section:

[plugins."io.containerd.cri.v1.images".registry]
  config_path = "/etc/containerd/certs.d"

Containerd 1 uses plugins."io.containerd.grpc.v1.cri".registry instead. Match the runtime you actually run; copying the older plugin key into a containerd 2 configuration can leave the intended registry configuration inactive.

Repair an existing node

If the containerd 2 registry section above is absent, append it once and restart containerd on the affected demo node:

printf '\n[plugins."io.containerd.cri.v1.images".registry]\n  config_path = "/etc/containerd/certs.d"\n' |
  podman exec -i "$KRKN_MGMT_NODE" sh -c 'cat >> /etc/containerd/config.toml'
podman exec "$KRKN_MGMT_NODE" systemctl restart containerd

If the section already exists with a different value, edit that section rather than appending a duplicate TOML table. Then write the registry hosts file. Hosts-file edits are read dynamically once config_path is active.

A Pod already in ImagePullBackOff may take several minutes to retry. During the lab, restarting kubelet on the dedicated node cleared that wait:

podman exec "$KRKN_MGMT_NODE" systemctl restart kubelet

Waiting for the normal retry is also valid. Confirm a new Pulled event; an old HTTPS failure remains in the event history after a successful recovery.

See containerd registry configuration and registry host files.

The pull reaches HTTP but returns 401

Transport is now working; authentication still needs checking. Verify the saved registry credentials and the Pod’s imagePullSecrets. The authentication key in the secret must match the registry host and port in the image reference. A login to localhost:5001 in Podman does not automatically configure a Kubernetes pull secret for the node hostname.

Update credentials through Settings → Private Registries. Do not put passwords in hosts.toml or paste decoded pull secrets into logs. The operator supplies authentication through the pull secret; the node configuration supplies the transport.

/home/krkn/run.sh: Permission denied

The operator invokes /home/krkn/run.sh directly. A script with mode 0644 can work when passed to bash locally and still fail in the operator Pod.

Use this Dockerfile instruction, then rebuild and push the scenario image:

COPY --chmod=0755 --chown=krkn:krkn run.sh /home/krkn/run.sh

Verify the built artifact as the operator’s user:

podman run --rm --user 1001:1001 --entrypoint /bin/bash \
  "$KRKN_SCENARIO_IMAGE" -c 'test -x /home/krkn/run.sh'

Start a new run and inspect its digest. Fixing the file on the host does not change an already built image, and pushing a new tag value does not rewrite an existing Pod’s image reference.

Krkn looks for /root/.kube/config

Setting KUBECONFIG does not override an explicit kraken.kubeconfig_path in the Krkn YAML configuration. The demo’s startup script sets that configuration key from KUBECONFIG and defaults it to /home/krkn/.kube/config.

Align the mounted path, the environment variable, and the main Krkn configuration. In the operator, the mount destination comes from the run’s spec.kubeconfigPath or the configured default, /home/krkn/.kube/config in this lab. The image’s krknctl.kubeconfig_path label does not control the mount. For a custom path, update the run configuration and the startup script’s KUBECONFIG value together. Running the container as root can change a home-directory fallback, which is why the demo uses an explicit path and tests startup as UID 1001.

The kubeconfig works on the host but not in the container

127.0.0.1 inside a container refers to that container. For a Kind target, generate the kubeconfig with kind get kubeconfig --internal --name ... and attach the test container to the kind network. For another target, use an API endpoint reachable from the container and include the required certificates.

If the host API port changed after a Podman Machine restart, start the stopped Kind node and refresh the host kubeconfig:

podman start "$KRKN_MGMT_NODE"
kind export kubeconfig --name "$KRKN_MGMT_CLUSTER"

Use the stable node hostname for the registry URL. If an old registry entry contains a numeric IP, update its URL in the console and write a matching hosts.toml. Do not change the target’s kubeconfig just to repair a registry URL.

The plugin is not discovered

Inspect Krkn’s factory logs. Verify the dev_demo package and __init__.py, the dev_demo_scenario_plugin.py module, the DevDemoScenarioPlugin class, and both abstract methods. The configuration type must be dev_demo_scenarios and must not collide with another plugin.

If source edits appear to have no effect, rebuild the base image containing the Python plugin before rebuilding the scenario image. The upstream Krkn Dockerfile clones its own source and does not automatically include your local plugin checkout.

The image is pushed but missing from scenario selection

Check the selected registry, repository (demo/krkn-hub), and tag (dev-demo). Inspect the image for krknctl.is_a_scenario="true" and valid JSON in krknctl.input_fields. A repository path is not a scenario name.

An unsigned custom image can also be blocked by signature verification. The lab’s override is configured on the operator, not in the plugin or image label. Keep it scoped to the dedicated lab installation.

The process starts and reports a missing namespace

This is the demo’s expected failure path: image discovery, pull, startup, and configuration injection have already succeeded. Check the selected target and namespace. Namespace existence is evaluated in the target cluster, not necessarily in the management cluster hosting the Pod.

For an existing namespace, verify that the target credentials allow listing namespaces and pods. An existing namespace with no pods should return success.

3 - Scenario Plugin API

Naming, discovery, method signatures, and configuration binding for Krkn scenario plugins.

A scenario plugin extends AbstractScenarioPlugin and implements get_scenario_types() and run(). The factory discovers it under krkn/scenario_plugins/; adding a valid plugin does not require editing a central registration list.

For a complete implementation exercise, follow Implement the plugin. The abstract class and factory are the source of truth for the checked-out Krkn version.

Package and naming rules

krkn/
└── scenario_plugins/
    └── dev_demo/
        ├── __init__.py
        └── dev_demo_scenario_plugin.py
  • Include __init__.py so the plugin directory is a Python package.
  • The plugin directory must not contain scenario or plugin in its name.
  • The module containing the plugin class ends with _scenario_plugin.py.
  • The class ends with ScenarioPlugin and uses the module’s corresponding CamelCase name: dev_demo_scenario_plugin.py becomes DevDemoScenarioPlugin.
  • Each scenario type returned by the plugin must be unique across loaded plugins.

The factory instantiates classes while discovering their types. Keep constructors free of cluster operations. If you override __init__, preserve the base class’s scenario_type argument and initialization.

Abstract methods

The current interface is:

from krkn_lib.models.telemetry import ScenarioTelemetry
from krkn_lib.telemetry.ocp import KrknTelemetryOpenshift

from krkn.scenario_plugins.abstract_scenario_plugin import AbstractScenarioPlugin


class DevDemoScenarioPlugin(AbstractScenarioPlugin):
    def get_scenario_types(self) -> list[str]:
        return ["dev_demo_scenarios"]

    def run(
        self,
        run_uuid: str,
        scenario: str,
        lib_telemetry: KrknTelemetryOpenshift,
        scenario_telemetry: ScenarioTelemetry,
    ) -> int:
        return self.execute_demo(scenario, lib_telemetry)

    # Implement execute_demo() with validation and exception handling.

The example shows the interface; execute_demo() is supplied by the demo repository. The run() signature does not include a krkn_config argument.

get_scenario_types()

Return a list of strings that can appear beneath kraken.chaos_scenarios. One class can serve multiple types. Reusing a type already claimed by another plugin causes the conflicting registration to fail and is reported in the factory logs.

run()

  • run_uuid identifies the Krkn run.
  • scenario is the path to the individual scenario configuration file, not its parsed contents. Read and validate that file in the plugin.
  • lib_telemetry is the initialized KrknTelemetryOpenshift wrapper. Its Kubernetes and OpenShift helpers use the run’s cluster credentials.
  • scenario_telemetry is the telemetry record for this scenario execution. The engine manages its timing and exit status; use it for supported scenario-specific telemetry.

Return integer 0 on success and 1 on failure. Handle exceptions within the plugin’s execution path so they do not escape run(). Log enough context to diagnose a failure without dumping sensitive HTTP headers or credentials.

Bind a type to a configuration file

In the main Krkn configuration:

kraken:
  chaos_scenarios:
    - dev_demo_scenarios:
        - scenarios/dev_demo.yaml

In scenarios/dev_demo.yaml:

wait: 2
namespace: default

The engine finds the type returned by get_scenario_types() and passes scenarios/dev_demo.yaml to run(). Relative paths are resolved from the Krkn process’s working directory. Discovery imports plugin modules even when their types are not selected; do not perform experiments at import time.

Use the injected library

    kubernetes = lib_telemetry.get_lib_kubernetes()
    namespaces = kubernetes.list_namespaces()
    pods = kubernetes.list_pods(namespace)

Prefer these helpers to creating a second Kubernetes client and loading another kubeconfig. Reusable Kubernetes or OpenShift operations belong in krkn-lib; scenario-specific sequencing belongs in the plugin. See Using and extending krkn-lib.

Before packaging

Check naming, unique type registration, configuration validation, failure handling, and cleanup behavior. A Python syntax check alone does not verify discovery or cluster access. Use the testing guide to cover those stages.

4 - Scenario Image Contract

Define discoverable scenario images with OCI labels and krknctl input descriptors.

Krknctl and Krkn Operator discover scenario definitions from container image metadata. To add a custom scenario, publish an image with the expected labels and configure its registry in the client. You do not need to add the scenario to krknctl’s source code when the existing input types cover its parameters.

The packaging lab builds this contract without rebuilding krkn-hub. The definitions are implemented in krknctl’s input field parser and type validators.

Image labels

  • krknctl.is_a_scenario identifies scenario images when set to "true".
  • krknctl.title and krknctl.description describe the scenario.
  • krknctl.input_fields contains a JSON array of field descriptors.
  • krknctl.kubeconfig_path appears in the companion image as metadata. The clients used in this lab do not read it to configure the kubeconfig mount. Set the runtime mount path independently.
  • krknctl.privileged declares a privilege requirement. It is not an RBAC grant; actual permissions depend on the client, runtime, and target credentials.
  • krknctl.has_rollback describes rollback capability. Set it to match implemented behavior, not to indicate an intended future feature.

The base image can also supply global input labels such as krknctl.input_fields.global. Keep scenario-specific fields in the scenario label rather than copying every framework setting into it.

Image labels are build artifacts. Changing a JSON file on disk does not change the published label until the scenario image is rebuilt and pushed.

Input field descriptors

krknctl-input.json contains an array, not a JSON Schema object with properties. A typical field is:

{
  "name": "namespace",
  "short_description": "Namespace",
  "description": "Kubernetes namespace whose pod names will be logged.",
  "variable": "NAMESPACE",
  "type": "string",
  "required": "true",
  "validator": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$",
  "validation_message": "namespace must be a Kubernetes namespace name"
}

For ordinary input fields, name, variable, and type are required by the parser. short_description supplies the display label; description supplies help text. variable must exactly match the environment variable consumed by the image’s startup code.

The descriptor format uses strings for required, secret, and default, including numeric and boolean defaults. Use "required": "true", not the JSON boolean true. Omit an optional property when it is not needed.

Supported input types

  • string accepts text and can apply a validator regular expression with a custom validation_message.
  • number validates a numeric value. Enforce application-specific integer constraints, ranges, and units in the runtime implementation as well.
  • boolean represents a boolean input; it still reaches the container as a string environment value.
  • enum restricts values using allowed_values and separator.
  • file describes a file input with a container mount_path.
  • file_base64 describes file content encoded for a consuming script or application. Use this exact type name, not file64.
  • group supplies presentation metadata for a group of related fields. It is metadata rather than a value injected as a normal input.

Example number and enum descriptors:

[
  {
    "name": "wait",
    "short_description": "Wait (seconds)",
    "variable": "WAIT_SECONDS",
    "type": "number",
    "default": "2",
    "required": "false"
  },
  {
    "name": "mode",
    "short_description": "Mode",
    "variable": "MODE",
    "type": "enum",
    "allowed_values": "list,inspect",
    "separator": ",",
    "default": "list",
    "required": "true"
  }
]

An input file path is a container path, not a path on the user’s workstation:

{
  "name": "settings",
  "short_description": "Settings file",
  "variable": "SETTINGS_FILE",
  "type": "file",
  "mount_path": "/home/krkn/settings.yaml",
  "required": "true"
}

Keep defaults in the input metadata and runtime defaults consistent. Client validation improves feedback, but direct podman run -e ... bypasses it. Validate all values again before performing scenario actions.

Runtime compatibility

For Krkn Operator 1.1.2, provide /bin/bash and an executable /home/krkn/run.sh. The controller invokes that script directly, mounts the target kubeconfig, injects environment variables, and records the exit status. Do not rely only on a custom OCI ENTRYPOINT being honored by every client.

The script can launch Krkn or another implementation that obeys the same runtime contract. It must read the declared inputs, use the configured file paths, and exit with a meaningful status. For the Krkn plugin path, return 0 or 1 from the plugin and preserve the engine’s process status with exec.

Kubeconfig mount path

Krkn Operator selects the container mount path from the run’s spec.kubeconfigPath (kubeconfigPath in the API request). When omitted, it uses the krknctl configuration default, /home/krkn/.kube/config in this lab. Krknctl also uses its own configured kubeconfig_path for the mount destination; the image label does not override that setting.

For a custom path, configure the client’s mount destination and make the startup script read the same path. The demo’s script reads KUBECONFIG and writes it into kraken.kubeconfig_path, so update that environment value or the script’s default as well. Changing spec.kubeconfigPath moves the mount; it does not rewrite the script or the Krkn YAML. Changing only the image label does neither.

See the operator’s mount-path selection and krknctl’s default configuration.

Inspect the built contract

podman image inspect "$KRKN_SCENARIO_IMAGE" |
  jq '.[0].Config.Labels | {
    scenario: .["krknctl.is_a_scenario"],
    title: .["krknctl.title"],
    inputs: (.["krknctl.input_fields"] | fromjson)
  }'

Verify discovery, input rendering, runtime validation, and execution separately. An HTTP-enabled registry client can successfully read these labels while the node runtime still fails to pull the image. See the registry lab.

5 - Using and Extending Krkn-lib

Reuse the cluster clients provided to scenario plugins and contribute shared Kubernetes and OpenShift helpers.

krkn-lib provides the Kubernetes, OpenShift, telemetry, metrics, and data-model building blocks used by Krkn scenarios. Prefer its existing helpers for cluster operations so scenarios share authentication, behavior, and testable interfaces.

Use the client supplied to the plugin

Krkn passes a KrknTelemetryOpenshift instance as lib_telemetry to run(). Obtain the Kubernetes helper from it:

    kubernetes = lib_telemetry.get_lib_kubernetes()
    namespaces = kubernetes.list_namespaces()
    pods = kubernetes.list_pods(namespace)

For OpenShift-specific operations, use lib_telemetry.get_lib_ocp() and the methods documented for the installed library version. The wrapper has already been initialized for the run; creating another client with a separately loaded kubeconfig can make the plugin act on the wrong cluster.

The demo’s namespace existence check lists namespaces before listing pods. Its target credentials therefore need both permissions. Always derive access requirements from the operations your implementation actually performs.

Find an existing helper

Consult the API reference and the library source. The main areas are:

  • krkn_lib.k8s and krkn_lib.ocp for cluster operations.
  • krkn_lib.telemetry for telemetry collection and integration.
  • krkn_lib.prometheus and krkn_lib.elastic for metrics and result storage.
  • krkn_lib.models for shared models.
  • krkn_lib.utils for common utilities.

Check the version installed in the runtime image when consulting a helper’s signature. A newer library checkout on the host does not change the dependency already installed in a container image.

Add reusable behavior

Place broadly reusable Kubernetes or OpenShift operations in the library. Keep experiment sequencing, scenario configuration, and success criteria in the plugin. For a new helper, document its arguments, return type, exceptions, required permissions, and behavior when resources are absent.

Clone the library and install its development dependencies using the project’s Poetry configuration:

git clone https://github.com/krkn-chaos/krkn-lib.git
cd krkn-lib
poetry install --no-interaction

Follow the repository’s tests and contribution instructions for the operation you change. Mock isolated API behavior in unit tests, and use a disposable cluster for tests that exercise real Kubernetes resources. See Testing changes.

If a plugin depends on an unreleased helper, install that library build into the custom base image before packaging the scenario. Publishing a plugin alone cannot make a missing helper appear in the runtime.

6 - Testing Changes

Verify plugin behavior, the image contract, container startup, and operator execution as separate stages.

Test the behavior at the layer you changed, then verify the next integration boundary. A correct Python method does not prove that its image can be discovered or started by the operator.

The hands-on lab supplies the concrete build and run commands. Use this page when deciding what evidence a scenario change needs before contribution.

Plugin and engine tests

For the development scenario, cover:

  • A valid configuration and an existing namespace, including one with no pods.
  • A missing namespace and a Kubernetes API failure.
  • Invalid wait values: negative, above 60, fractional, boolean, or missing.
  • Missing or invalid namespace values and unreadable or malformed YAML.
  • The expected return code and useful error context for each failure.

Mock the sleep and cluster helper in unit tests so they remain fast and do not depend on credentials. Verify externally meaningful behavior such as whether pods are listed after a successful namespace check, rather than reproducing the implementation’s internal steps in the test.

Krkn’s top-level scenario tests live under tests/, while plugin-factory tests also live under krkn/tests/. After installing the dependencies required by the checked-out version, run the relevant test module from the repository root. The existing factory test is:

cd "$KRKN_DIR"
python3 -m unittest krkn.tests.test_plugin_factory -v

Add a focused test module for your scenario and run it explicitly before the broader suite. See Adding tests for test structure and functional CI integration.

Container and metadata checks

Rebuild the custom base after changing plugin Python or dependencies. Rebuild the scenario image after changing labels, startup scripts, or templates. Inspect the resulting image, rather than only the source Dockerfile:

podman image inspect "$KRKN_SCENARIO_IMAGE" |
  jq '.[0].Config.Labels["krknctl.input_fields"] | fromjson'
podman run --rm --user 1001:1001 --entrypoint /bin/bash \
  "$KRKN_SCENARIO_IMAGE" -c 'test -x /home/krkn/run.sh'

Run the container against a disposable target and exercise both success and failure. Use the operator’s startup convention in the test: execute /home/krkn/run.sh directly under UID 1001. Check the mounted kubeconfig, generated scenario YAML, effective kraken.kubeconfig_path, and exit status.

The local test handles the internal Kind API address and the Podman Machine mount location.

Operator integration

Verify these checkpoints in order:

  1. The registry lists the scenario tag and its label JSON can be read.
  2. The console shows the expected fields and maps them to the intended environment variables.
  3. The Pod is scheduled in the management cluster and its image is pulled.
  4. The startup script runs with the operator’s user and file permissions.
  5. The plugin accesses the selected target and produces the expected result.

Capture the resolved image digest when reporting the test. Pushing a new tag value does not change the image reference of an existing Pod. Start a new run and inspect its digest after a rebuild.

For a private HTTP registry, test operator API access and node-runtime access separately. A successful listing does not validate containerd’s transport or the kubelet’s pull credentials. The troubleshooting guide shows the relevant evidence for each failure.

Functional CI infrastructure

The small demo needs only a target with namespace and pod read permissions. The full repository test suites can require additional nodes, workloads, Prometheus, Elasticsearch, or platform-specific resources. Use the fixtures and setup in the checked-out repository’s CI documentation for the tests you select.

Krkn’s shell functional runner uses CI/tests/functional_tests, individual scripts under CI/tests/, and CI/run.sh. It writes results to CI/results.markdown and logs under CI/out/. Follow the current CI instructions and adding tests guide when extending that suite.

Monitoring installation references

These services support other Krkn test suites and the metrics and long-term storage user journeys. Neither is required for the custom scenario lab, which disables telemetry export and resiliency scoring. Install them separately in an environment appropriate to those tests; this is not part of provisioning the lab’s target.

Prometheus

For a Kubernetes test environment, use the maintained kube-prometheus-stack Helm installation instructions. Select a chart version compatible with your Kubernetes version and the test requirements, then configure the tests with a reachable Prometheus endpoint. A workstation port-forward is suitable for a test process on that workstation; localhost in a scenario container refers to the container itself.

Elasticsearch

Use an Elasticsearch version supported by the library and tests you are running. Elastic documents installing ECK with Helm and deploying and accessing an Elasticsearch cluster. Follow both steps: installing the operator alone does not create an Elasticsearch instance. Supply the resulting endpoint, credentials, and TLS configuration to the selected test suite.

Use these maintained installation references instead of copying old chart values or image tags across incompatible releases.

Library changes

Test new krkn-lib helpers in the library repository first, using the test environment appropriate to their API behavior. Then install the changed library into the custom Krkn base image and rerun the affected scenario.

This second check catches dependency-version mismatches and different authentication or permission requirements. Updating the host’s virtual environment does not update a previously built container image.

Community image packaging

When contributing to krkn-hub, follow its template generation and Compose build workflow. Build the affected scenario first, inspect its labels, and run the same container and operator checks above. A collection-wide build is not a prerequisite for testing a standalone custom scenario.

See Publishing through krkn-hub.

Krknctl integration

Use the client’s private registry support to discover and exercise the published scenario. You should not need to edit krknctl’s embedded registry configuration merely to add an image that uses existing input types.

When changing the type system or client implementation itself, add tests in the corresponding krknctl packages and rebuild the client. Validate the image contract against both CLI and console behavior when the change affects their shared metadata.

7 - Adding Tests to Krkn

Add focused unit tests and functional coverage to the Krkn test suites.

This guide covers how to add both unit tests and functional tests to the krkn project. Tests are essential for ensuring code quality and preventing regressions.

Unit Tests

Unit tests in krkn are located in the tests/ directory and use Python’s unittest framework with comprehensive mocking to avoid requiring external dependencies like cloud providers or Kubernetes clusters.

Creating a Unit Test

1. File Location and Naming

Place your test file in the tests/ directory with the naming pattern test_<feature>.py:

tests/
├── test_vmi_outage.py
├── test_ibmcloud_node_scenarios.py
├── test_ibmcloud_power_node_scenarios.py
└── test_<your_feature>.py

2. Basic Test Structure

#!/usr/bin/env python3

"""
Test suite for <Feature Name>

IMPORTANT: These tests use comprehensive mocking and do NOT require any external
infrastructure, cloud credentials, or Kubernetes cluster. All API calls are mocked.

Test Coverage:
- Feature 1: Description
- Feature 2: Description

Usage:
    # Run all tests
    python -m unittest tests.test_<your_feature> -v

    # Run with coverage
    python -m coverage run -a -m unittest tests/test_<your_feature>.py -v

Assisted By: Claude Code
"""

import unittest
from unittest.mock import MagicMock, patch, Mock

# Import the classes you're testing
from krkn.scenario_plugins.<module> import YourClass


class TestYourFeature(unittest.TestCase):
    """Test cases for YourClass"""

    def setUp(self):
        """Set up test fixtures before each test"""
        # Mock environment variables if needed
        self.env_patcher = patch.dict('os.environ', {
            'API_KEY': 'test-api-key',
            'API_URL': 'https://test.example.com'
        })
        self.env_patcher.start()

        # Mock external dependencies
        self.mock_client = MagicMock()

        # Create instance to test
        self.instance = YourClass()

    def tearDown(self):
        """Clean up after each test"""
        self.env_patcher.stop()

    def test_success_scenario(self):
        """Test successful operation"""
        # Arrange: Set up test data
        expected_result = "success"

        # Act: Call the method being tested
        result = self.instance.your_method()

        # Assert: Verify the result
        self.assertEqual(result, expected_result)

    def test_failure_scenario(self):
        """Test failure handling"""
        # Arrange: Set up failure condition
        self.mock_client.some_method.side_effect = Exception("API Error")

        # Act & Assert: Verify exception is handled
        with self.assertRaises(Exception):
            self.instance.your_method()


if __name__ == '__main__':
    unittest.main()

3. Best Practices for Unit Tests

  • Use Comprehensive Mocking: Mock all external dependencies (cloud APIs, Kubernetes, file I/O)
  • Add IMPORTANT Note: Include a note in the docstring that tests don’t require credentials
  • Document Test Coverage: List what scenarios each test covers
  • Organize Tests by Category: Use section comments like # ==================== Core Tests ====================
  • Test Edge Cases: Include tests for timeouts, missing parameters, API exceptions
  • Use Descriptive Names: Test names should clearly describe what they test

4. Running Unit Tests

# Run all unit tests
python -m unittest discover -s tests -v

# Run specific test file
python -m unittest tests.test_your_feature -v

# Run with coverage
python -m coverage run -a -m unittest discover -s tests -v
python -m coverage report

Functional Tests

Functional tests in krkn are integration tests that run complete chaos scenarios against a real Kubernetes cluster (typically KinD in CI). They are located in the CI/tests/ directory.

Understanding the Functional Test Structure

CI/
├── run.sh                          # Main test runner
├── run_test.sh                     # Individual test executor
├── config/
│   ├── common_test_config.yaml     # Base configuration template
│   └── <scenario>_config.yaml      # Generated configs per scenario
├── tests/
│   ├── common.sh                   # Common helper functions
│   ├── functional_tests            # List of tests to run
│   └── test_*.sh                   # Individual test scripts
└── out/
    └── <test_name>.out             # Test output logs

Adding a New Functional Test

Step 1: Create the Test Script

Create a new test script in CI/tests/ following the naming pattern test_<scenario>.sh:

#!/bin/bash
set -xeEo pipefail

source CI/tests/common.sh

trap error ERR
trap finish EXIT

function functional_test_<your_scenario> {
  # Set environment variables for the scenario
  export scenario_type="<scenario_type>"
  export scenario_file="scenarios/kind/<scenario_file>.yml"
  export post_config=""

  # Generate config from template with variable substitution
  envsubst < CI/config/common_test_config.yaml > CI/config/<your_scenario>_config.yaml

  # Optional: View the generated config
  cat CI/config/<your_scenario>_config.yaml

  # Run kraken with coverage
  python3 -m coverage run -a run_kraken.py -c CI/config/<your_scenario>_config.yaml

  # Success message
  echo "<Your Scenario> scenario test: Success"

  # Optional: Verify expected state
  date
  kubectl get pods -n <namespace> -l <label>=<value> -o yaml
}

# Execute the test function
functional_test_<your_scenario>

Step 2: Create or Reference Scenario File

Ensure your scenario YAML file exists in scenarios/kind/:

# scenarios/kind/<your_scenario>.yml
- id: my-chaos-scenario
  config:
    namespace: default
    label_selector: app=myapp
    # ... scenario-specific configuration

Step 3: Update GitHub Actions Workflow

If you want the test to run on pull requests, add it to .github/workflows/tests.yml:

- name: Setup Pull Request Functional Tests
  if: github.event_name == 'pull_request'
  run: |
    # ... existing tests ...
    echo "test_<your_scenario>" >> ./CI/tests/functional_tests

Functional Test Patterns

Pattern 1: Simple Scenario Test

Tests a single scenario execution:

function functional_test_simple {
  export scenario_type="pod_disruption_scenarios"
  export scenario_file="scenarios/kind/pod_simple.yml"
  export post_config=""

  envsubst < CI/config/common_test_config.yaml > CI/config/simple_config.yaml
  python3 -m coverage run -a run_kraken.py -c CI/config/simple_config.yaml

  echo "Simple scenario test: Success"
}

Pattern 2: Test with Setup/Teardown

Tests that require specific cluster state:

function functional_test_with_setup {
  # Setup: Deploy test workload
  kubectl apply -f CI/templates/test_workload.yaml
  kubectl wait --for=condition=ready pod -l app=test --timeout=300s

  # Run scenario
  export scenario_type="pod_disruption_scenarios"
  export scenario_file="scenarios/kind/pod_test.yml"
  envsubst < CI/config/common_test_config.yaml > CI/config/test_config.yaml
  python3 -m coverage run -a run_kraken.py -c CI/config/test_config.yaml

  # Verify state
  kubectl get pods -l app=test

  # Teardown
  kubectl delete -f CI/templates/test_workload.yaml

  echo "Test with setup: Success"
}

Pattern 3: Multi-Step Scenario Test

Tests that run multiple related scenarios:

function functional_test_multi_step {
  # Step 1: Initial disruption
  export scenario_type="node_scenarios"
  export scenario_file="scenarios/kind/node_stop.yml"
  envsubst < CI/config/common_test_config.yaml > CI/config/node_config.yaml
  python3 -m coverage run -a run_kraken.py -c CI/config/node_config.yaml

  # Wait for recovery
  sleep 30

  # Step 2: Follow-up disruption
  export scenario_file="scenarios/kind/node_start.yml"
  envsubst < CI/config/common_test_config.yaml > CI/config/node_config.yaml
  python3 -m coverage run -a run_kraken.py -c CI/config/node_config.yaml

  echo "Multi-step scenario test: Success"
}

Configuration Variables

The common_test_config.yaml uses environment variable substitution via envsubst. Common variables include:

  • $scenario_type: The chaos scenario plugin type (e.g., pod_disruption_scenarios)
  • $scenario_file: Path to the scenario YAML file
  • $post_config: Additional post-scenario configuration

Example usage in config:

kraken:
  chaos_scenarios:
    - $scenario_type:
        - $scenario_file

Running Functional Tests

Run All Functional Tests

./CI/run.sh

This will:

  1. Create CI/out/ directory for logs
  2. Read test names from CI/tests/functional_tests
  3. Execute each test via CI/run_test.sh
  4. Generate results in CI/results.markdown

Run a Single Functional Test

./CI/run_test.sh test_<your_scenario> CI/results.markdown

View Test Results

cat CI/results.markdown

Example output:

Test                   | Result | Duration
-----------------------|--------|---------
test_pod              | Pass   | 0:2:15
test_your_scenario    | Pass   | 0:1:45

View Test Logs

cat CI/out/test_<your_scenario>.out

Error Handling

Functional tests use common error handling from CI/tests/common.sh:

trap error ERR    # Catches errors
trap finish EXIT  # Runs on script exit

# error() function handles exit codes:
# - Exit code 1: Error logged, test fails
# - Exit code 2: Expected exit, test passes (wraps to 0)

Best Practices for Functional Tests

  1. Use set -xeEo pipefail: Ensures errors are caught and commands are logged
  2. Source common.sh: Always include source CI/tests/common.sh for error handling
  3. Set Traps: Use trap error ERR and trap finish EXIT
  4. Verify State: Check cluster state before and after scenarios
  5. Clear Success Messages: Use descriptive success messages
  6. Coverage Integration: Run kraken with python3 -m coverage run -a
  7. Resource Cleanup: Clean up any resources created during the test
  8. Timeout Values: Use appropriate timeout values for kubectl wait commands

Example: Complete Functional Test

Here’s a complete example combining all the concepts:

#!/bin/bash
set -xeEo pipefail

source CI/tests/common.sh

trap error ERR
trap finish EXIT

function functional_test_my_scenario {
  # Setup: Deploy test application
  echo "Setting up test workload..."
  kubectl create namespace test-namespace || true
  kubectl apply -f CI/templates/my_test_app.yaml
  kubectl wait --for=condition=ready pod -l app=my-test-app -n test-namespace --timeout=300s

  # Configure scenario
  export scenario_type="pod_disruption_scenarios"
  export scenario_file="scenarios/kind/my_scenario.yml"
  export post_config=""

  # Generate config
  envsubst < CI/config/common_test_config.yaml > CI/config/my_scenario_config.yaml

  # Optional: Display config for debugging
  echo "Generated configuration:"
  cat CI/config/my_scenario_config.yaml

  # Run kraken scenario
  echo "Running chaos scenario..."
  python3 -m coverage run -a run_kraken.py -c CI/config/my_scenario_config.yaml

  # Verify expected state
  echo "Verifying cluster state..."
  kubectl get pods -n test-namespace -l app=my-test-app

  # Cleanup
  echo "Cleaning up..."
  kubectl delete namespace test-namespace --wait=false

  # Success
  echo "My scenario test: Success"
  date
}

# Execute the test
functional_test_my_scenario

Testing in CI

The GitHub Actions workflow (.github/workflows/tests.yml) runs functional tests:

  1. Pull Requests: Runs a subset of quick tests
  2. Main Branch: Runs all tests including integration scenarios

To add your test to CI:

- name: Setup Pull Request Functional Tests
  run: |
    echo "test_my_scenario" >> ./CI/tests/functional_tests

Debugging Failed Tests

When a functional test fails:

  1. Check the output log: cat CI/out/test_<name>.out
  2. Review the results: cat CI/results.markdown
  3. Check cluster state: kubectl get pods --all-namespaces
  4. Review kraken logs: Look for error messages in the output
  5. Verify configuration: Ensure variables are properly substituted in generated config

Summary

  • Unit Tests: Located in tests/, use comprehensive mocking, require no external dependencies
  • Functional Tests: Located in CI/tests/, run against real Kubernetes, test full scenarios
  • Test Execution: Unit tests via unittest, functional tests via CI/run.sh
  • Coverage: Both test types contribute to code coverage metrics
  • CI Integration: All tests run automatically in GitHub Actions

8 - Publishing Through Krkn-hub

Add a scenario to the community image collection after validating its standalone image contract.

Krkn-hub maintains the community collection of packaged scenario images. Add your scenario there when you want it distributed through that collection. The custom scenario lab builds and publishes its own image directly, so it does not require changes to this repository.

Before contributing packaging, validate the Python plugin, configuration template, metadata, and executable startup script. Use the testing guide to cover success and failure paths.

Add the scenario package

Create a scenario directory in krkn-hub using a neighboring scenario as a reference. A package normally includes:

  • A scenario YAML template whose configurable values come from environment variables.
  • env.sh with documented runtime defaults.
  • An executable run.sh that prepares configuration and starts Krkn.
  • krknctl-input.json describing the scenario-specific inputs.
  • Dockerfile.template with the scenario labels and required files.

Keep the names consistent across the JSON descriptor’s variable, shell defaults, YAML placeholders, and plugin configuration fields. The template file name used by run.sh must match the actual file copied into the image.

The base image must already contain the Python plugin and its dependencies. Packaging a new scenario type on a base image that predates the plugin will produce a discoverable image that fails at engine startup.

Connect the build

Follow the build conventions in the checked-out krkn-hub revision. In the repository’s template and Compose workflow, this includes registering the scenario directory in build.sh and adding its service to docker-compose.yaml. Use generated Dockerfiles in the build and confirm that the input JSON is embedded in the final image labels.

Only add shared defaults or main-configuration variables to the root templates when the behavior is shared across scenarios. A scenario-specific field belongs in its own package.

See the repository’s build script and application outages package for the current layout. Avoid instructions that depend on fixed source line numbers; the surrounding scripts evolve.

Complete the contribution

Build and test the selected scenario image before running a collection-wide build. Confirm that it works through the intended clients and that runtime permissions match the scenario’s needs. Official release publishing also includes the project’s image signing and distribution process.

Add a scenario page under content/en/docs/scenarios/ in the website and follow the website’s scenario marker conventions. Document inputs, target permissions, expected disruption, recovery behavior, and the meaningful success condition.

Use the contribution guidelines for the related code and documentation pull requests. Changes to krknctl itself are needed when extending client behavior or the type system, rather than simply introducing another image that uses the existing contract.

9 - Health Check Plugins

How to use and create health check plugins in Krkn to monitor services during chaos experiments

Health check plugins allow Krkn to continuously monitor the health of your services and infrastructure during chaos experiments. They run in background threads alongside the chaos scenario, detecting outages, tracking downtime duration, and collecting telemetry data.

Overview

The health check system uses a plugin architecture:

  • HealthCheckFactory — automatically discovers and loads all plugins from the krkn.health_checks package
  • AbstractHealthCheckPlugin — base class all plugins must extend
  • Plugins run in separate threads and write telemetry to a shared queue
  • The factory tracks all active plugin instances and provides lifecycle management (increment_all_iterations, stop_all)

Exit Codes

CodeMeaning
0Success — all health checks passed
2Critical alert detected during the run
3Health check failure (e.g. exit_on_failure: true triggered)

Built-in Plugins

HTTP Health Check (health_checks)

Monitors HTTP endpoints by making periodic GET requests. Tracks status changes, measures downtime duration, and records telemetry for each state transition.

Configuration example:

health_checks:
  interval: 2          # seconds between checks
  config:
    - url: "http://my-service/health"
      verify_url: true          # SSL certificate verification (default: true)
      exit_on_failure: false    # exit with code 3 if endpoint goes down (default: false)

    - url: "https://api.example.com/status"
      bearer_token: "your-token"   # Authorization: Bearer <token>
      exit_on_failure: true

    - url: "http://internal-service"
      auth: "username,password"    # HTTP basic auth
      verify_url: false

Config fields:

FieldTypeRequiredDefaultDescription
urlstringyes—HTTP endpoint to monitor
bearer_tokenstringno—Bearer token for Authorization header
authstringno—Basic auth as "username,password"
verify_urlboolnotrueVerify SSL certificates
exit_on_failureboolnofalseSet exit code 3 when endpoint returns non-200

KubeVirt VM Health Check (virt_health_check)

Monitors KubeVirt VirtualMachineInstance (VMI) connectivity during chaos experiments. It tracks SSH/network access to VMs, detects disconnections, and records recovery data.

Configuration example:

kubevirt_checks:
  interval: 5
  config:
    namespace: "my-namespace"
    node_name: "worker-1"        # optional: filter VMIs by node
    exit_on_failure: false
    disconnected_mode: false     # track VMs that become unreachable
    only_failures: true          # only record failed checks in telemetry
    batch_size: 10               # VMIs to check concurrently (0 = no limit)
    ssh_port: 22
    ssh_user: "cloud-user"
    ssh_private_key: "/path/to/key"

Creating a Custom Health Check Plugin

You can add your own health check plugin by following these steps.

1. Naming Conventions

The factory enforces strict naming conventions for auto-discovery. Both the file name and class name must follow these rules:

RuleExample
File must be in krkn/health_checks/krkn/health_checks/
File name must end with _health_check_plugin.pymy_service_health_check_plugin.py
Class name must be CapitalCamelCase of the file nameMyServiceHealthCheckPlugin
Class name must end with HealthCheckPluginMyServiceHealthCheckPlugin
Class must inherit from AbstractHealthCheckPlugin—

The snake_case-to-CamelCase conversion is automatic — my_service_health_check_plugin becomes MyServiceHealthCheckPlugin. The factory will reject your plugin if these rules are not followed.

2. Create the Plugin File

Create krkn/health_checks/my_service_health_check_plugin.py:

import logging
import queue
import time
from typing import Any

from krkn.health_checks.abstract_health_check_plugin import AbstractHealthCheckPlugin


class MyServiceHealthCheckPlugin(AbstractHealthCheckPlugin):
    """
    Health check plugin that monitors MyService during chaos experiments.
    """

    def __init__(
        self,
        health_check_type: str = "my_service_health_check",
        iterations: int = 1,
        **kwargs
    ):
        super().__init__(health_check_type)
        self.iterations = iterations
        self.current_iterations = 0

    def get_health_check_types(self) -> list[str]:
        """
        Returns the internal type identifiers for this plugin.
        One plugin can handle multiple type strings, but they must be
        unique across all plugins.
        """
        return ["my_service_health_check"]

    def get_config_key(self) -> str:
        """
        Returns the top-level config.yaml key this plugin reads from.
        The factory maps this key to the plugin so run_kraken.py discovers
        and starts it automatically — no code changes needed there.
        Must be unique across all plugins.
        """
        return "my_service_checks"

    def increment_iterations(self) -> None:
        """
        Called by the main run loop after each chaos iteration.
        Increment your counter so the plugin knows when to stop.
        """
        self.current_iterations += 1

    def run_health_check(
        self,
        config: dict[str, Any],
        telemetry_queue: queue.Queue,
    ) -> None:
        """
        Main health check loop. Runs in a background thread.

        - Check `self._stop_event.is_set()` to support cooperative shutdown
        - Check `self.current_iterations < self.iterations` to stop after the run
        - Put telemetry results into `telemetry_queue`
        - Set `self.ret_value = 3` to signal health check failure
        """
        if not config or not config.get("config"):
            logging.info("my_service_health_check config not defined, skipping")
            return

        interval = config.get("interval", 5)
        exit_on_failure = config.get("config", {}).get("exit_on_failure", False)
        endpoint = config.get("config", {}).get("endpoint")

        telemetry_results = []

        while self.current_iterations < self.iterations and not self._stop_event.is_set():
            healthy = self._check_my_service(endpoint)

            if not healthy:
                logging.warning(f"MyService at {endpoint} is unhealthy")
                if exit_on_failure and self.ret_value == 0:
                    self.ret_value = 3

            # Collect telemetry (structure depends on your needs)
            telemetry_results.append({
                "endpoint": endpoint,
                "status": healthy,
            })

            time.sleep(interval)

        # Always put results into the queue when done
        telemetry_queue.put(telemetry_results)

    def _check_my_service(self, endpoint: str) -> bool:
        """Check if the service is healthy. Returns True if healthy."""
        try:
            # Your service-specific health check logic here
            return True
        except Exception as e:
            logging.error(f"Health check failed: {e}")
            return False

3. Configure in config.yaml

Add a section using the key returned by get_config_key(). The factory discovers this mapping at startup — no changes to run_kraken.py are needed:

my_service_checks:
  interval: 5
  config:
    endpoint: "http://my-service:8080"
    exit_on_failure: true

Each plugin owns its own top-level config key. Multiple plugins can be active simultaneously, each reading from their own section:

health_checks:          # read by HttpHealthCheckPlugin
  interval: 2
  config:
    - url: "http://frontend/health"

my_service_checks:      # read by MyServiceHealthCheckPlugin
  interval: 5
  config:
    endpoint: "http://my-service:8080"

4. AbstractHealthCheckPlugin API Reference

Your plugin inherits the following from AbstractHealthCheckPlugin:

MemberTypeDescription
self._stop_eventthreading.EventSet when the main loop requests shutdown. Check self._stop_event.is_set() in your loop.
self.ret_valueintReturn code. 0 = success, 3 = health check failure.
stop()methodCalled by the factory to signal your loop to exit. Do not override — check _stop_event instead.
get_return_value()methodReturns self.ret_value. Used by the main loop to detect failures.
set_return_value(value)methodSets self.ret_value.

Methods you must implement:

MethodDescription
run_health_check(config, telemetry_queue)Main health check loop, runs in a background thread
get_health_check_types()Returns list of internal type identifier strings for this plugin
get_config_key()Returns the top-level config.yaml key this plugin reads from (must be unique)
increment_iterations()Increments your iteration counter when called by the factory

5. Factory Auto-Discovery

The HealthCheckFactory uses pkgutil.walk_packages to scan the krkn.health_checks package at startup. Any file ending in _health_check_plugin.py that contains a class following the naming conventions will be automatically loaded. No registration step is needed beyond placing the file in the right directory.

You can verify your plugin was loaded by checking the factory’s loaded_plugins dict:

from krkn.health_checks.health_check_factory import HealthCheckFactory

factory = HealthCheckFactory()
print(factory.loaded_plugins.keys())
# dict_keys(['http_health_check', 'virt_health_check', 'my_service_health_check'])

If your plugin fails to load (naming violation, import error, duplicate type), it will appear in factory.failed_plugins as a list of (module_name, class_name, error_message) tuples.


Questions?

For questions or guidance, reach out on the Kubernetes Slack in the #krkn channel.