System checks - Poolside
How it works
dive check (pre- and post-flight)
dive check reads a single values.yaml, which is the same file you pass to the inference Helm chart. It builds the external clients each check needs (Kubernetes API, S3, HTTP) lazily on first use, then runs the selected checks concurrently, up to 8 at a time by default. Results are streamed to stdout as each check finishes; the process exits non-zero if any check returns fail or error.
dive node-checks
dive node-checks is an orchestrator that runs from any machine with cluster access (your laptop, a CI runner, a bastion). It discovers GPU nodes and for each one deploys a short-lived privileged pod that mounts the host’s /sys and /proc read-only. The pod runs the hidden node-check-local subcommand of the same dive binary, performs all 10 host-level checks, and prints a JSON report to its logs. The orchestrator collects those logs, parses the reports, and aggregates them into a single per-node summary.
Prerequisites
| Requirement | Notes |
|---|---|
kubectl + KUBECONFIG |
Must point at the target cluster with sufficient permissions |
| NVIDIA GPU Operator >= 26.3.0 | Required for GPU Operator health check; nodes must have nvidia.com/gpu.present=true label or nvidia.com/gpu allocatable resource |
inference chart values.yaml |
Prepared with image.tag, s3.bucket, and s3.region at minimum |
dive image (for node-checks) |
Must be pushed to a registry the cluster nodes can pull from |
Locate the dive binary
When using a Poolside bundle, the dive binary is included as a tarball alongside the other bundle binaries in ./binaries.
Distribute the dive image
For dive node-checks, the cluster nodes must be able to pull the dive image. When using a Poolside bundle, the dive image is included as a tarball in ./containers alongside the other bundle images:
# Upload all bundle images (including dive) to your internal registry
./scripts/upload_images.sh <target-registry>
# Then reference it when running node checks
dive node-checks \
--namespace dive-node-checks \
--image <target-registry>/poolside/dive:v1
Air-gapped clusters: In an air-gapped environment, the cluster nodes reach the dive image through the mechanism your environment already relies on, such as an internal registry or an image cached on each node. Make the image available through that mechanism, then pass its reference to --image. Because the check pods set imagePullPolicy: IfNotPresent, a cached image is used without a registry pull even when the tag is :latest.
Run pre-flight checks
Run these checks before installing the Helm chart to catch configuration and environment issues early.
dive check \
--values values.yaml \
--namespace poolside-models \
--pre
Pre-flight check reference
| Check name | What it validates | Fail / Warn / Skip conditions |
|---|---|---|
values_required |
image.tag, s3.bucket, and s3.region are all set in values.yaml |
Fail if any field is empty |
k8s_version |
The Kubernetes API server version is >= 1.29.0 | Fail if the Kubernetes version is below the minimum required version |
node_resources |
At least one schedulable node exists with allocatable CPU and memory resources. Reports the total schedulable node count, CPU, and memory | Fail if no schedulable nodes are found |
inference_namespace_exists |
The namespace where the inference stack is installed exists | Fail if namespace is missing |
inference_checkpoint_objects |
The configured S3 bucket contains at least one object. This confirms model checkpoints have been uploaded | Fail if bucket is unconfigured, unreachable, or empty |
inference_images_pullable |
The configured inference container image exists in the registry. This probes the OCI manifest endpoint | Fail on HTTP 404 or transport errors; Warn if the image or registry is not configured; Warn on 401/403 because auth may be satisfied at runtime by IAM Roles for Service Accounts (IRSA) or a pull secret |
public_docs_image_pullable |
When offline documentation is enabled, the configured public-docs container image exists in the registry. This probes the OCI manifest endpoint |
Skip if docs.enabled=false; Fail if the image is not configured, missing, or unreachable; Warn on 401/403 because auth may be satisfied at runtime by IRSA or a pull secret |
inference_gpu_capacity |
At least one schedulable node has nvidia.com/gpu allocatable resources |
Fail if no GPU nodes are found |
inference_nvidia_driver_version |
Every GPU node labeled nvidia.com/gpu.present=true is running NVIDIA driver >= 580.65.06. This is the minimum required by the atlas container |
Fail if any node is below minimum; Warn if driver version labels are absent because GPU Feature Discovery may not have run yet |
inference_gpu_operator_healthy |
The NVIDIA GPU Operator is installed (searched in gpu-operator, nvidia-gpu-operator, then all namespaces) and GPU resources are available to the scheduler |
Fail if operator is absent or no nvidia.com/gpu resources are allocatable; Warn if GPUs are schedulable but operator was not found (unsupported configuration) |
inference_multi_node_capable |
On multi-node clusters (>= 2 GPU nodes), at least one node has a high-speed interconnect: rdma/hca allocatable resource or nvidia.com/gpu.interconnect=NVLink label |
Skip for single-node clusters; Warn if multiple GPU nodes exist but no high-speed interconnect is detected. Multi-node parallelism then falls back to PCIe/ethernet |
s3_endpoint_reachable |
TCP connectivity to a custom S3 endpoint (s3.apiUri) can be established within 5 seconds |
Skip if s3.apiUri is not set because standard AWS S3 is used; Fail if the endpoint is unreachable |
s3_credentials_secret |
The Kubernetes Secret named in s3.secretName exists and contains AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY |
Skip if s3.secretName is empty because IRSA or pod identity provides credentials; Fail if the secret is missing or incomplete |
pull_secrets_exist |
Every secret listed in global.imagePullSecrets exists in the required namespaces |
Skip if no pull secrets are configured; Fail if any secret is missing |
ingress_tls_valid |
Every entry in ingress.tls[] has a non-empty secretName |
Skip if ingress is disabled or no TLS entries are configured; Fail if any TLS entry is missing secretName |
route_tls_valid |
When OpenShift Route TLS is enabled with edge or reencrypt termination, both certificate and key are provided |
Skip if route or route TLS is disabled; Fail if cert/key are missing for termination modes that require them |
Run post-flight checks
Run these checks after the Helm chart is installed to verify that the inference stack is operating correctly.
dive check \
--values values.yaml \
--namespace poolside-models \
--post
Post-flight check reference
| Check name | What it validates | Fail / Warn / Skip conditions |
|---|---|---|
inference_pods_healthy |
All pods in the inference namespace are Running/Ready or Completed, with no CrashLoopBackOff, ImagePullBackOff, or Error states |
Fail if any pod is in a terminal failure state; Warn if pods are Pending or NotReady |
inference_model_pods_running |
All model server Deployments (label app.kubernetes.io/component=inference) have at least one available replica, and no checkpoint download init containers are stuck |
Fail if no model Deployments are found or image pull errors are detected; Warn if deployments are not ready yet because checkpoint download can take several minutes |
inference_vllm_auth_secret |
The Kubernetes Secret named in authentication.secretName exists and contains the VLLM_API_KEY data key |
Skip if authentication.secretName is not configured because API key auth is disabled; Fail if secret or key is missing |
inference_metrics_scrape |
Fetches /metrics from each configured ingress host and parses vLLM, SGLang, or TensorRT-LLM Prometheus metrics. Reports requests_running, requests_waiting, kv_cache_usage, and prefix_cache_hit_rate |
Skip if ingress is disabled; Fail if no host returns valid metrics; Warn if only some hosts respond |
inference_model_chat_completions |
For each model with a configured ingressHost or routeHost, calls GET /v1/models to discover model IDs, then sends a minimal one-token chat completion request to POST /v1/chat/completions |
Skip if no model ingress hosts are configured; Fail if any model endpoint is unreachable or returns an error; Warn if a host is reachable but no models are loaded yet |
dns_resolves |
All ingress hostnames (from ingress.tls[].hosts, models[].ingressHost, models[].routeHost) resolve via DNS |
Skip if ingress or route is disabled; Fail if any hostname does not resolve |
endpoint_reachable |
All ingress hosts respond to GET /health. This confirms the ingress controller and TLS termination are working |
Skip if ingress or route is disabled; Fail if an endpoint is unreachable with a transport error; Warn if an endpoint returns an unexpected HTTP status code or a TLS error because cert-manager may still be issuing the certificate |
Run GPU node checks
Run these checks to validate that every GPU node’s host configuration is optimal for GPU workloads. This is independent of any Helm values file and can be run before or after installing the inference stack.
dive node-checks \
--namespace dive-node-checks \
--image <target-registry>/poolside/dive:v1 \
--create-namespace
Node check reference
Checks are classified as hard (returns fail) or soft (returns warn):
| Check name | Hard/Soft | What it validates | Why it matters | Fix |
|---|---|---|---|---|
iommu_passthrough |
Hard | Kernel cmdline contains iommu=pt or IOMMU is disabled |
Translation mode adds DMA overhead that destroys GPU-to-GPU and GPUDirect RDMA bandwidth | Add iommu=pt to the kernel cmdline and reboot |
acs_disabled |
Hard | No PCI bridge has Access Control Services (ACS) enabled in a way that blocks GPU peer-to-peer traffic | ACS forces P2P traffic through the root complex, destroying NVLink and GPUDirect RDMA performance | Disable ACS on affected bridges or add pci=noacs to the kernel cmdline |
numa_gpu_affinity |
Hard | Every GPU (nvidia.com/gpu device) has a valid NUMA node assignment (NUMA node >= 0) |
NUMA node -1 means the kernel cannot determine locality, causing remote-memory-access penalties for GPU DMA buffers |
Check ACPI tables and PCIe topology; this often indicates a firmware or BIOS configuration issue |
numa_gpu_distribution |
Hard | GPUs are evenly distributed across NUMA nodes (counts differ by at most 1) | Uneven distribution suggests incorrect PCIe topology or broken ACPI tables, leading to asymmetric memory access costs | Review BIOS/UEFI PCIe topology settings |
cpu_governor |
Hard | The CPU frequency scaling governor is performance on every CPU |
powersave or ondemand cause frequency-scaling jitter affecting data loading, NCCL host code, and CUDA launch latency |
cpupower frequency-set -g performance |
cpu_cstates |
Soft | Deep C-states (C2+) are limited; max_cstate <= 1 is recommended |
Deep C-states add wakeup latency; CPUs must respond quickly to CUDA completion events | Add intel_idle.max_cstate=1 or processor.max_cstate=1 to the kernel cmdline |
transparent_hugepages |
Hard | Transparent Huge Pages (THP) is set to madvise or never, not always |
always causes latency spikes from THP compaction during memory allocation, stalling cudaMalloc and data loading |
echo madvise > /sys/kernel/mm/transparent_hugepage/enabled |
gpu_pcie_relaxed_ordering |
Soft | PCIe Relaxed Ordering is enabled on GPU devices (only when EFA/RDMA devices are present) | Without Relaxed Ordering, EFA/IB RDMA writes to GPU memory take a slower path, reducing cross-node bandwidth | Configure via BIOS/UEFI PCIe settings or GPU firmware; see NVIDIA documentation |
kernel_numa_balancing |
Hard | kernel.numa_balancing sysctl is 0 |
NUMA balancing migrates pages between nodes, conflicting with GPU DMA mappings and causing performance jitter | sysctl -w kernel.numa_balancing=0 |
zone_reclaim_mode |
Hard | vm.zone_reclaim_mode sysctl is 0 |
Non-zero values cause aggressive page reclaim within NUMA zones, leading to memory pressure and GPU DMA buffer eviction | sysctl -w vm.zone_reclaim_mode=0 |
acs_disabled and numa_gpu_* checks are skipped automatically on nodes with no detected GPUs. gpu_pcie_relaxed_ordering is skipped when no EFA/RDMA devices are present.
Output formats
Both dive check and dive node-checks support --output text (default) and --output json.
JSON output
Machine-readable, suitable for CI pipelines. Each result is an object with name and a nested result object. The result object contains status, message, optional detail, and optional fix:
[
{
"name": "inference_gpu_operator_healthy",
"result": {
"status": "pass",
"message": "NVIDIA GPU Operator healthy in \"gpu-operator\" with 4 node(s) having nvidia.com/gpu resources available"
}
},
{
"name": "inference_nvidia_driver_version",
"result": {
"status": "fail",
"message": "NVIDIA driver version < 580.65.06 on GPU node(s): node-1 (driver 535.104.12)",
"fix": "Upgrade the NVIDIA driver to >= 580.65.06"
}
}
]
Exit codes
| Exit code | Meaning |
|---|---|
0 |
All checks passed, warned, or were skipped, with no hard failures |
1 |
One or more checks returned fail or error |
CLI reference
dive check
dive check --values <values-file-path> --namespace <namespace> [--pre] [--post] [flags]
| Flag | Default | Description |
|---|---|---|
--values |
(required) | Path to the inference chart values.yaml |
--namespace |
(required) | Target namespace for deployment checks. This is where the inference stack is installed |
--pre |
false |
Run pre-install checks |
--post |
false |
Run post-install checks |
--output |
text |
Output format: text or json |
--concurrency |
8 |
Maximum number of checks to run in parallel |
dive node-checks
dive node-checks --namespace <namespace> --image <dive-image> [flags]
| Flag | Default | Description |
|---|---|---|
--namespace |
(required) | Namespace to run check pods in (must exist unless --create-namespace is set) |
--image |
(required) | Dive image the check pods run. It must be pullable by cluster nodes |
--create-namespace |
false |
Create --namespace if it does not exist (requires namespaces create permission) |
--timeout |
5m |
Per-node timeout waiting for the check pod to complete |
--concurrency |
8 |
Maximum number of nodes to check in parallel |
--output |
text |
Output format: text or json |
--keep-pods |
false |
Retain check pods after collecting results (useful for debugging) |
--sysfs-root |
/host/sys |
In-pod mount path for the host /sys |
--proc-root |
/host/proc |
In-pod mount path for the host /proc |
dive version
dive version
Prints the build version string.
Required RBAC for dive node-checks
The credentials of the user running dive node-checks must allow the following operations:
| Resource | Verbs |
|---|---|
namespaces |
get (plus create only when --create-namespace is set) |
nodes |
list |
pods |
create, get, delete |
pods/log |
get |