mirror of
https://github.com/qdrant/qdrant.git
synced 2026-09-21 13:37:46 -05:00
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:
co-authored by
Claude Opus 4.8
parent
d6552e9236
commit
ce2f31c773
@@ -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": [
|
||||
|
||||
@@ -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)]
|
||||
|
||||
Reference in New Issue
Block a user