Skip to content

Kubernetes

Deploy one Runtime Sensor per Linux node with the Endura Helm chart. The chart creates a DaemonSet with only the Linux capabilities and host access required for eBPF enforcement.

Runtime Sensor and Build Lifecycles

  • Keep one Sensor running per host or eligible node. Build containers use its socket and the CLI for job start/stop; leave their installed Sensor service stopped.

Prerequisites

  • Kubernetes 1.25 or later with supported x86-64 (AMD64) Linux nodes
  • Helm 3.12 or later and configured kubectl
  • curl and jq for resolving the selected release channel
  • Cluster permissions to create a namespace, DaemonSet, and Secret
  • Pod Security settings that allow host PID and hostPath access plus the required Linux capabilities
  • Linux kernel 5.11 or later with BTF at /sys/kernel/btf/vmlinux on every target node
  • At least 1 GB of allocatable memory per Runtime Sensor Pod
  • An Endura license key
  • Team Server URL and Runtime Sensor token for centralized management

The examples select the testing channel and resolve its current Runtime Sensor chart version from the channel manifest. The chart deploys the matching immutable image. See Release Channels before selecting another channel.

Create the Namespace and Secret

kubectl create namespace endura
kubectl create secret generic endura-sensor-secrets \
  --namespace endura \
  --from-file=ENDURA_LICENSE_KEY=/secure/path/endura-license-key \
  --from-file=ENDURA_SENSOR_TOKEN=/secure/path/endura-sensor-token

Each file must contain only its value, without a trailing newline. The chart reads but does not manage the Secret. Omit the Runtime Sensor token in standalone mode. A custom secrets.name must reference a Secret in the release namespace containing ENDURA_LICENSE_KEY.

Configure Helm Values

Create values.yaml:

sensor:
  env:
    ENDURA_TEAM_SERVER: "https://team-server.example.com"
    ENDURA_LOG_LEVEL: "info"

resources:
  limits:
    memory: 2Gi
  requests:
    cpu: 250m
    memory: 1Gi

nodeSelector:
  kubernetes.io/arch: amd64
  kubernetes.io/os: linux

Omit ENDURA_TEAM_SERVER for standalone mode. Add tolerations if the Runtime Sensor must run on tainted nodes.

Use --namespace or -n for the release namespace. The chart creates no RBAC or ServiceAccount and mounts no API token. Its schema rejects namespace, rbac, serviceAccount, and replicaCount values.

The Sensor runs as root with a read-only root filesystem, no privilege escalation, and only BPF, DAC_READ_SEARCH, PERFMON, SYS_NICE, SYS_PTRACE, and SYS_RESOURCE capabilities. The chart enables the host PID namespace and sets ENDURA_PROC_PATH=/host/proc; host IPC and network namespaces are disabled by default. It mounts host procfs, BPF, kernel, runtime, log, and identity paths, but does not mount host /tmp.

An empty image.tag uses the chart’s immutable appVersion; the default pull policy is IfNotPresent. Set image.digest to a sha256: digest to select image.repository@image.digest instead. A digest takes precedence over image.tag. Set priorityClassName to an existing PriorityClass to increase scheduling priority.

Unknown keys are rejected, including sensor.env.ENDURA_PROC_PATH; the chart sets it to /host/proc.

Install

CHANNEL=testing
CHART_VERSION=$(curl -sSf "https://repo.endurasecurity.com/${CHANNEL}/release.json" \
  | jq -er '.products.sensor.chart.version') || exit $?

helm install endura-sensor \
  "oci://ghcr.io/endurasecurity/helm/${CHANNEL}/endura-sensor" \
  --version "$CHART_VERSION" \
  --namespace endura \
  --values values.yaml || exit $?

Verify one ready Runtime Sensor Pod is scheduled on each eligible node:

# Allow the controller up to one minute to select eligible nodes.
for ((attempt = 0; attempt < 60; attempt++)); do
  DESIRED=$(kubectl get daemonset endura-sensor --namespace endura \
    --output jsonpath='{.status.desiredNumberScheduled}') || exit $?
  if [[ ${DESIRED:-0} -gt 0 ]]; then break; fi
  sleep 1
done
if [[ ${DESIRED:-0} -eq 0 ]]; then
  echo "No eligible Sensor nodes; inspect node selectors and taints." >&2
  exit 1
fi
kubectl rollout status daemonset/endura-sensor \
  --namespace endura --timeout=300s || exit $?
kubectl get daemonset endura-sensor --namespace endura
kubectl get pods --namespace endura --output wide
kubectl logs --namespace endura \
  --selector app.kubernetes.io/name=endura-sensor

Wait for nonzero desired Pods before rollout: an initial zero can falsely satisfy rollout status or Helm --wait. Rollout then waits for updated Pods. Compare desired/current/ready counts and placement against every intended build node. Probes call /endura sensor status for local availability; verify Team Server connectivity separately.

In Team Server, check each node under Runtime Sensors. Its deployment and workloads appear after a manually started workload is reported.

Configure Node Selection

Use nodeSelector, affinity, and tolerations to match the nodes that run protected workloads. A build Pod can communicate only with the Runtime Sensor on its own node, so every eligible build node must run the DaemonSet.

nodeSelector:
  kubernetes.io/os: linux
  node-role.kubernetes.io/worker: ""

tolerations:
  - operator: Exists
    effect: NoSchedule

Verify placement with kubectl get pods -n endura -o wide and compare it with kubectl get nodes.

Instrument Build Pods

Build Pods mount the node’s control socket directory and run endura job stop from a shell trap. This example uses an emptyDir workspace:

apiVersion: batch/v1
kind: Job
metadata:
  name: protected-build
spec:
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: build
          image: docker.io/almalinux:10
          workingDir: /workspace
          command: ["/bin/sh", "-c"]
          args:
            - |
              set -e
              dnf install -y curl ca-certificates
              curl -sSf https://repo.endurasecurity.com/install/endura-sensor/testing.sh | sh
              cleanup() {
                build_status=$?
                set +e
                endura job stop /workspace
                cleanup_status=$?
                if [ "$build_status" -ne 0 ]; then
                  exit "$build_status"
                fi
                exit "$cleanup_status"
              }
              trap cleanup EXIT
              endura sensor wait
              ENDURA_JOB_ID=$(endura job start --workspace /workspace)
              echo "started endura job with id ${ENDURA_JOB_ID}"
              # Run build and test commands here.
          volumeMounts:
            - name: endura-run
              mountPath: /run/endura
            - name: workspace
              mountPath: /workspace
      volumes:
        - name: endura-run
          hostPath:
            path: /run/endura
            type: Directory
        - name: workspace
          emptyDir: {}

Cleanup preserves build failures and fails successful work on stop errors. The workspace must exist and belong to its non-root user. See provider examples.

Update

Review Team Server Compatibility, then pin the new chart version from the selected channel manifest:

Before upgrading from an older chart, remove the obsolete rbac, serviceAccount, and replicaCount keys from custom values. The chart creates no Kubernetes API credentials, and its schema rejects unsupported keys.

CHANNEL=testing
CHART_VERSION=$(curl -sSf "https://repo.endurasecurity.com/${CHANNEL}/release.json" \
  | jq -er '.products.sensor.chart.version') || exit $?

helm upgrade endura-sensor \
  "oci://ghcr.io/endurasecurity/helm/${CHANNEL}/endura-sensor" \
  --version "$CHART_VERSION" \
  --namespace endura \
  --values values.yaml || exit $?

# Allow the controller up to one minute to select eligible nodes.
for ((attempt = 0; attempt < 60; attempt++)); do
  DESIRED=$(kubectl get daemonset endura-sensor --namespace endura \
    --output jsonpath='{.status.desiredNumberScheduled}') || exit $?
  if [[ ${DESIRED:-0} -gt 0 ]]; then break; fi
  sleep 1
done
if [[ ${DESIRED:-0} -eq 0 ]]; then
  echo "No eligible Sensor nodes; inspect node selectors and taints." >&2
  exit 1
fi
kubectl rollout status daemonset/endura-sensor \
  --namespace endura --timeout=300s || exit $?
kubectl get daemonset endura-sensor --namespace endura

The DaemonSet updates Runtime Sensors node by node. Drain protected work before upgrading: each Sensor restart ends its active job registrations. Start jobs again after the replacement Sensor is ready, and confirm hook coverage after a Sensor or node-kernel update.

Operate and Monitor

# Follow logs from every Runtime Sensor.
kubectl logs --namespace endura \
  --selector app.kubernetes.io/name=endura-sensor \
  --follow --prefix

# Run status in one Pod.
SENSOR_POD=$(kubectl get pods --namespace endura \
  --selector app.kubernetes.io/name=endura-sensor \
  --output jsonpath='{.items[0].metadata.name}')
kubectl exec --namespace endura "$SENSOR_POD" -- /endura sensor status
kubectl exec --namespace endura "$SENSOR_POD" -- /endura sensor hooks

Uninstall

helm uninstall endura-sensor --namespace endura
kubectl delete secret endura-sensor-secrets --namespace endura

The chart does not remove host log files or pinned state left outside Kubernetes objects. Confirm that no Runtime Sensor is running before cleaning /var/log/endura or /run/endura on nodes.

Troubleshooting

Pod Is Pending or Rejected

kubectl describe daemonset endura-sensor --namespace endura
kubectl get events --namespace endura --sort-by=.lastTimestamp

Check Pod Security admission, node selectors, taints, and allocatable resources.

If Helm reports a values-schema error, remove unsupported keys and correct the reported value type. The chart intentionally rejects unknown values.

Pod Crashes or Never Becomes Ready

kubectl logs --namespace endura \
  --selector app.kubernetes.io/name=endura-sensor \
  --previous
kubectl describe pods --namespace endura \
  --selector app.kubernetes.io/name=endura-sensor

Check the license, BTF, required capabilities, host PID access, and host paths. The configured BPF, kernel, securityfs, and procfs paths must exist as directories; the chart can create the host runtime and log directories. The host identity files must exist as files.

If the Pod reports CreateContainerConfigError, confirm that the Secret selected by secrets.name exists in the Helm release namespace and contains an ENDURA_LICENSE_KEY key.

Build Pod Cannot Reach the Runtime Sensor

Confirm that a Runtime Sensor Pod is ready on the same node and that the build Pod mounts host /run/endura at /run/endura.

Some Hooks Are Missing

Run endura sensor hooks in the affected node’s Runtime Sensor Pod. Kernel version, LSM configuration, lockdown state, and verifier support determine attachment coverage.