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 curlandjqfor 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/vmlinuxon 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-tokenEach 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: linuxOmit 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-sensorWait 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: NoScheduleVerify 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 enduraThe 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 hooksUninstall
helm uninstall endura-sensor --namespace endura
kubectl delete secret endura-sensor-secrets --namespace enduraThe 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=.lastTimestampCheck 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-sensorCheck 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.