Detect container runtime beyond Docker in telemetry (#10132)

* telemetry: detect container runtime, not just Docker

`is_docker` only checks `/.dockerenv`, a marker the Docker daemon creates.
containerd/CRI-O (Kubernetes), Podman, etc. don't, so a containerized node —
notably every Qdrant Cloud pod — reported `is_docker: false`, indistinguishable
from bare metal.

Add a `container_runtime` field (enum: none/docker/kubernetes/other) detected
from well-known markers, most specific first: KUBERNETES_SERVICE_HOST →
/.dockerenv → other container markers → none. Only docker and kubernetes are
enumerated (the modes that matter for Qdrant); the rest fold into `other`.
`is_docker` is kept but derived (== docker). Detected once via LazyLock.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Regenerate OpenAPI spec

Add the ContainerRuntime schema and container_runtime field.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kumar Shivendu
2026-08-21 17:16:30 +05:30
committed by GitHub
co-authored by Claude Opus 4.8
parent d6552e9236
commit ce2f31c773
2 changed files with 75 additions and 1 deletions
+14
View File
@@ -11933,6 +11933,7 @@
"RunningEnvironmentTelemetry": {
"type": "object",
"required": [
"container_runtime",
"cpu_flags",
"is_docker"
],
@@ -11948,6 +11949,9 @@
"is_docker": {
"type": "boolean"
},
"container_runtime": {
"$ref": "#/components/schemas/ContainerRuntime"
},
"cores": {
"type": "integer",
"format": "uint",
@@ -11996,6 +12000,16 @@
}
}
},
"ContainerRuntime": {
"description": "Container runtime Qdrant is running under (`none` if bare metal).",
"type": "string",
"enum": [
"none",
"docker",
"kubernetes",
"other"
]
},
"CpuEndian": {
"type": "string",
"enum": [
+61 -1
View File
@@ -1,4 +1,5 @@
use std::path::Path;
use std::sync::LazyLock;
use chrono::{DateTime, SubsecRound, Utc};
use common::flags::FeatureFlags;
@@ -41,6 +42,9 @@ pub struct RunningEnvironmentTelemetry {
#[anonymize(false)]
distribution_version: Option<String>,
is_docker: bool,
/// Container runtime Qdrant runs under (detects Kubernetes etc., unlike `is_docker`).
#[anonymize(false)]
container_runtime: ContainerRuntime,
// Number of CPU cores Qdrant will use (accounting for cgroup/host limits)
#[anonymize(false)]
cores: Option<usize>,
@@ -222,10 +226,13 @@ fn get_system_data(storage_path: &Path) -> RunningEnvironmentTelemetry {
#[cfg(not(feature = "gpu"))]
let gpu_devices = None;
let container_runtime = *CONTAINER_RUNTIME;
RunningEnvironmentTelemetry {
distribution,
distribution_version,
is_docker: cfg!(unix) && Path::new("/.dockerenv").exists(),
is_docker: container_runtime.is_docker(),
container_runtime,
cores: Some(common::cpu::get_num_cpus()),
cpu_cores_used: common::process_cpu_usage::process_cpu_usage_cores(),
ram_size: Some((segment::utils::mem::total_memory_bytes() / 1024) as usize),
@@ -263,6 +270,59 @@ impl CpuEndian {
}
}
/// Container runtime Qdrant is running under (`none` if bare metal).
#[derive(Serialize, Clone, Copy, Debug, JsonSchema, Anonymize)]
#[serde(rename_all = "snake_case")]
pub enum ContainerRuntime {
None,
Docker,
Kubernetes,
// Some other container (Podman, LXC, systemd-nspawn, plain containerd, …).
Other,
}
/// Detected once: the container runtime cannot change during the process's life.
static CONTAINER_RUNTIME: LazyLock<ContainerRuntime> = LazyLock::new(ContainerRuntime::detect);
impl ContainerRuntime {
/// Detect the runtime, most specific marker first.
pub fn detect() -> Self {
// k8s (containerd/CRI-O) writes no `/.dockerenv`; the injected env is the signal.
if std::env::var_os("KUBERNETES_SERVICE_HOST").is_some()
|| Path::new("/var/run/secrets/kubernetes.io").exists()
{
return ContainerRuntime::Kubernetes;
}
if Path::new("/.dockerenv").exists() {
return ContainerRuntime::Docker;
}
// Podman, LXC/nspawn, or an unlabeled container image (overlay root).
if Path::new("/run/.containerenv").exists()
|| Path::new("/run/systemd/container").exists()
|| has_overlay_root()
{
return ContainerRuntime::Other;
}
ContainerRuntime::None
}
fn is_docker(self) -> bool {
matches!(self, ContainerRuntime::Docker)
}
}
fn has_overlay_root() -> bool {
// /proc/mounts columns: device mountpoint fstype opts …
let Ok(mounts) = fs_err::read_to_string("/proc/mounts") else {
return false;
};
mounts.lines().any(|line| {
let mut cols = line.split_whitespace();
let (_device, mountpoint, fstype) = (cols.next(), cols.next(), cols.next());
mountpoint == Some("/") && fstype == Some("overlay")
})
}
#[derive(Serialize, Clone, Debug, JsonSchema, Anonymize)]
pub struct GpuDeviceTelemetry {
#[anonymize(false)]