Skip to main content

Kubernetes Inventory

The Kubernetes inventory system collects and stores cluster infrastructure data — clusters, nodes, namespaces, workloads, pods, services, ingresses, PVCs, events, jobs, HPAs, network policies, resource quotas, and endpoint slices — and exposes it through REST API endpoints with RBAC client scoping. Clusters are also registered as CMDB assets, enabling unified inventory views alongside hosts.

Phase

CMDB-2a is complete. The system collects 13 Kubernetes resource types via the cluster agent, stores them in PostgreSQL, and exposes read-only API endpoints. The cluster agent binary runs as a single-replica Deployment inside each K8s cluster.

Overview

Kubernetes inventory data flows from the cluster agent (deployed inside each K8s cluster) through NATS into the backend, where the KubernetesWorker processes it into PostgreSQL tables. Each cluster also gets an entry in the assets table for unified CMDB views.

Collected Resource Types

The cluster agent gathers 13 resource types from the K8s API:

#ResourceK8s API GroupTableDescription
1NodesCoreV1k8s_nodesStatus, capacity, allocatable, usage, addresses
2NamespacesCoreV1k8s_namespacesStatus, labels, workload/pod/service counts
3WorkloadsAppsV1k8s_workloadsDeployments, StatefulSets, DaemonSets
4PodsCoreV1k8s_podsPhase, containers, restarts, resources, owner
5ServicesCoreV1k8s_servicesType, ports, selector, cluster IP
6IngressesNetworkingV1k8s_ingressesRules, TLS, ingress class, load balancer
7PVCsCoreV1k8s_pvcsPhase, storage class, capacity, access modes
8EventsCoreV1k8s_eventsType, reason, involved object, count, timestamps
9Jobs/CronJobsBatchV1k8s_jobsStatus, completions, schedule, containers
10HPAsAutoscalingV2k8s_hpasTarget, min/max/current replicas, metrics
11Network PoliciesNetworkingV1k8s_network_policiesPod selector, ingress/egress rules, policy types
12Resource QuotasCoreV1k8s_resource_quotasHard limits, current usage per namespace
13Endpoint SlicesDiscoveryV1k8s_endpoint_slicesAddresses, ports, service association

Data Flow

NATS Message Path

Kubernetes inventory messages use the standard telemetry subject pattern with kubernetes as the message type:

proxima.{client_slug}.{env_slug}.{host_id}.kubernetes

The message is routed through the EVENTS JetStream stream to the EventsWorker, which dispatches kubernetes message types to the KubernetesWorker.

Upsert-Then-Prune Sync Pattern

The KubernetesWorker uses an upsert-then-prune pattern rather than delete-reinsert. This is important for data consistency and referential integrity:

  1. Upsert phase — Each resource is inserted or updated using ON CONFLICT ... DO UPDATE. The worker tracks all seen entity IDs during the sync.
  2. Prune phase — After all upserts, the worker deletes any rows for the cluster that were not seen in this sync cycle. This removes resources that no longer exist in the cluster.

The prune follows FK dependency order (children before parents):

k8s_endpoint_slices → k8s_resource_quotas → k8s_network_policies →
k8s_hpas → k8s_jobs → k8s_events → k8s_pvcs → k8s_ingresses →
k8s_services → k8s_pods → k8s_workloads → k8s_namespaces → k8s_nodes

All upserts and prunes run inside a single database transaction with a 60-second timeout.

Processing Steps

  1. Asset upsert — Creates or updates an assets record with asset_type = 'cluster' and maps the cluster status to an asset status.
  2. Cluster upsert — Inserts or updates the clusters record with identity, K8s metadata, cloud context, and status fields.
  3. Node sync — Upserts all nodes for the cluster. For each node, attempts to link it to an existing host via hostname or IP address matching.
  4. Namespace sync — Upserts all namespaces with workload, pod, and service counts.
  5. Workload sync — Upserts all workloads (Deployments, StatefulSets, DaemonSets) with replica status and Helm metadata.
  6. Pod sync — Upserts all pods with phase, container specs, resources, restarts, and owner references.
  7. Service sync — Upserts all services with type, ports, selectors, and IPs.
  8. Ingress sync — Upserts all ingresses with rules, TLS, and class metadata.
  9. PVC sync — Upserts all PVCs with phase, storage class, and capacity.
  10. Event sync — Upserts all events with type, reason, and involved object.
  11. Job sync — Upserts all jobs and cron jobs with schedule, completions, and status.
  12. HPA sync — Upserts all HPAs with target reference, scaling bounds, and current metrics.
  13. Network policy sync — Upserts all network policies with selectors and rules.
  14. Resource quota sync — Upserts all quotas with hard limits and current usage.
  15. Endpoint slice sync — Upserts all endpoint slices with addresses and ports.
  16. Prune — Removes entities not seen in this sync cycle.
  17. Count update — Updates denormalized counts on the cluster record.

Collection Limits and Filtering

Namespace exclusion: The collector skips namespaces listed in PROXIMA_K8S_EXCLUDE_NAMESPACES (comma-separated). Excluded namespaces and their resources are not collected or stored.

Pod limit: The collector enforces a configurable pod limit (PROXIMA_K8S_POD_LIMIT, default: 5000). When the limit is exceeded, pods are sorted with non-Running pods first (then by restart count descending), and the list is truncated. This ensures problematic pods are always included.

Event limit: Events are capped at 1000 per sync (hardcoded maxEvents). Events are sorted by LastTimestamp descending, keeping only the most recent.

Annotation stripping: The annotation kubectl.kubernetes.io/last-applied-configuration is stripped from all resources before transmission. This annotation often contains the full resource manifest and can be very large. The stripping is applied to workloads, services, ingresses, PVCs, jobs, and network policies.

Security

The cluster agent follows a strict security posture:

  • No secrets or configmaps collected — These resource types are not in the ClusterRole permissions.
  • Read-only RBAC — The ClusterRole only grants get, list, watch verbs.
  • Annotation strippingkubectl.kubernetes.io/last-applied-configuration is removed to avoid leaking full resource manifests.

Table Schemas

clusters

The primary cluster identity table. Linked to assets via asset_id.

ColumnTypeDescription
idUUIDPrimary key
asset_idUUIDFK to assets.id (ON DELETE CASCADE)
client_idUUIDFK to clients.id
environment_idUUIDFK to environments.id
nameTEXTCluster name (e.g., k8s-prod-01)
display_nameTEXTHuman-readable display name
slugTEXTURL-safe identifier (unique per environment)
versionTEXTKubernetes version (e.g., v1.29.3)
platformTEXTDistribution platform (rke2, eks, gke, aks, k3s, kubeadm)
distributionTEXTDistribution detail
api_server_urlTEXTAPI server endpoint
cluster_cidrTEXTPod CIDR range
service_cidrTEXTService CIDR range
dns_domainTEXTCluster DNS domain (default: cluster.local)
cloud_providerTEXTCloud provider (aws, gcp, azure, hetzner)
cloud_regionTEXTCloud region
cloud_account_idTEXTCloud account identifier
statusTEXTCluster health status (healthy, degraded, unreachable, unknown)
agent_statusTEXTAgent connection status (connected, disconnected)
agent_versionTEXTReporting agent version
node_countINTEGERDenormalized node count
namespace_countINTEGERDenormalized namespace count
workload_countINTEGERDenormalized workload count
last_seen_atTIMESTAMPTZLast inventory message timestamp
created_atTIMESTAMPTZRecord creation time
updated_atTIMESTAMPTZLast modification time

Indexes: client_id, environment_id, asset_id, status, agent_status, last_seen_at, unique on (environment_id, slug).

k8s_nodes

Kubernetes nodes with capacity, allocatable resources, and usage data.

ColumnTypeDescription
idUUIDPrimary key
cluster_idUUIDFK to clusters.id (ON DELETE CASCADE)
host_idUUIDFK to hosts.id (nullable, set by host linkage)
nameTEXTNode name
uidTEXTKubernetes UID
rolesTEXT[]Node roles (e.g., control-plane, worker)
labelsJSONBKubernetes labels
taintsJSONBNode taints
statusTEXTNode condition status (Ready, NotReady, Unknown)
conditionsJSONBFull conditions array
unschedulableBOOLEANWhether scheduling is disabled
kubelet_versionTEXTKubelet version
container_runtimeTEXTContainer runtime (e.g., containerd://1.7.2)
os_imageTEXTOS image (e.g., Ubuntu 22.04.3 LTS)
kernel_versionTEXTKernel version
capacity_cpu_millicoresINTEGERTotal CPU capacity
capacity_memory_bytesBIGINTTotal memory capacity
capacity_podsINTEGERMax pod count
allocatable_cpu_millicoresINTEGERAllocatable CPU
allocatable_memory_bytesBIGINTAllocatable memory
allocatable_podsINTEGERAllocatable pod count
usage_cpu_millicoresINTEGERCurrent CPU usage
usage_memory_bytesBIGINTCurrent memory usage
pod_countINTEGERCurrent pod count
provider_idTEXTCloud provider instance ID
instance_typeTEXTCloud instance type
zoneTEXTAvailability zone

Indexes: cluster_id, host_id, status, unique on (cluster_id, name) and (cluster_id, uid).

k8s_namespaces

Kubernetes namespaces with resource counts.

ColumnTypeDescription
idUUIDPrimary key
cluster_idUUIDFK to clusters.id (ON DELETE CASCADE)
nameTEXTNamespace name
uidTEXTKubernetes UID
statusTEXTNamespace phase (Active, Terminating)
labelsJSONBKubernetes labels
workload_countINTEGERWorkloads in this namespace
pod_countINTEGERPods in this namespace
service_countINTEGERServices in this namespace

Indexes: cluster_id, GIN on labels, unique on (cluster_id, name).

k8s_workloads

Kubernetes workload resources (Deployments, StatefulSets, DaemonSets, Jobs, CronJobs).

ColumnTypeDescription
idUUIDPrimary key
cluster_idUUIDFK to clusters.id (ON DELETE CASCADE)
namespace_idUUIDFK to k8s_namespaces.id (ON DELETE CASCADE)
kindTEXTResource kind (Deployment, StatefulSet, etc.)
nameTEXTWorkload name
uidTEXTKubernetes UID
namespaceTEXTNamespace name (denormalized)
replicas_desiredINTEGERDesired replica count
replicas_readyINTEGERReady replica count
replicas_availableINTEGERAvailable replica count
conditionsJSONBWorkload conditions
strategyTEXTDeployment strategy
service_accountTEXTService account name
containersJSONBContainer specs (image, resources, ports)
labelsJSONBKubernetes labels
annotationsJSONBKubernetes annotations
helm_releaseTEXTHelm release name
helm_chartTEXTHelm chart name
helm_chart_versionTEXTHelm chart version

Indexes: cluster_id, namespace_id, kind, GIN on labels, conditional index on helm_release (non-null only), unique on (cluster_id, kind, namespace, name).

Table Relationships

API Endpoints

All endpoints require authentication and the assets:read permission. Responses are scoped to the caller's allowed clients.

List Clusters

GET /api/v1/clusters

Query parameters:

ParameterTypeDescription
client_idUUIDFilter by client
environment_idUUIDFilter by environment
statusstringFilter by cluster status
searchstringName search (case-insensitive substring)
pageintPage number (default: 1)
per_pageintItems per page (default: 50)

Response:

{
"data": [
{
"id": "a1b2c3d4-...",
"asset_id": "e5f6g7h8-...",
"client_id": "c1d2e3f4-...",
"environment_id": "d4e5f6a7-...",
"name": "k8s-prod-01",
"display_name": "Production Cluster",
"slug": "k8s-prod-01",
"version": "v1.29.3",
"platform": "rke2",
"status": "healthy",
"node_count": 5,
"namespace_count": 12,
"workload_count": 47,
"last_seen_at": "2026-04-05T12:00:00Z"
}
],
"meta": {
"total": 3,
"page": 1,
"per_page": 50
}
}

Get Cluster

GET /api/v1/clusters/:clusterID

Returns full cluster details including all metadata, cloud context, and counts.

List Cluster Nodes

GET /api/v1/clusters/:clusterID/nodes

Returns all nodes in the cluster with capacity, allocatable resources, usage, and host linkage.

List Cluster Namespaces

GET /api/v1/clusters/:clusterID/namespaces

Returns all namespaces with resource counts.

List Cluster Workloads

GET /api/v1/clusters/:clusterID/workloads

Query parameters:

ParameterTypeDescription
namespacestringFilter by namespace name
kindstringFilter by resource kind
pageintPage number (default: 1)
per_pageintItems per page (default: 50)

List Cluster Pods

GET /api/v1/clusters/:clusterID/pods

Query parameters:

ParameterTypeDescription
namespacestringFilter by namespace name
node_namestringFilter by node name
phasestringFilter by pod phase
pageintPage number (default: 1)
per_pageintItems per page (default: 50)

List Node Pods

GET /api/v1/clusters/:clusterID/nodes/:nodeID/pods

Returns pods running on a specific node.

List Workload Pods

GET /api/v1/clusters/:clusterID/workloads/:workloadID/pods

Returns pods belonging to a specific workload.

List Cluster Services

GET /api/v1/clusters/:clusterID/services

List Cluster Ingresses

GET /api/v1/clusters/:clusterID/ingresses

List Cluster PVCs

GET /api/v1/clusters/:clusterID/pvcs

List Cluster Events

GET /api/v1/clusters/:clusterID/events

Query parameters:

ParameterTypeDescription
typestringFilter by event type (Normal, Warning)
pageintPage number (default: 1)
per_pageintItems per page (default: 50)

List Cluster Jobs

GET /api/v1/clusters/:clusterID/jobs

List Cluster HPAs

GET /api/v1/clusters/:clusterID/hpas

List Cluster Network Policies

GET /api/v1/clusters/:clusterID/network-policies

List Cluster Resource Quotas

GET /api/v1/clusters/:clusterID/resource-quotas

List Cluster Endpoint Slices

GET /api/v1/clusters/:clusterID/endpoint-slices

Client-Scoped Clusters

GET /api/v1/clients/:id/clusters

Returns clusters belonging to the specified client.

Environment-Scoped Clusters

GET /api/v1/environments/:envID/clusters

Returns clusters in the specified environment.

Node-Host Linkage

The KubernetesWorker automatically links K8s nodes to existing Proxima hosts when possible. This enables cross-referencing between Kubernetes node data and host-level inventory, metrics, and compliance data.

How It Works

For each node in a cluster inventory message, the worker attempts to find a matching host in the same environment by:

  1. Hostname match — Compares the node name against hosts.hostname
  2. IP match — Compares node addresses against hosts.ip_address

If a match is found, k8s_nodes.host_id is set to the matching host's ID. This linkage is tracked by the proxima_k8s_node_host_linkage_total Prometheus metric with a result label (linked or unlinked).

PQL Integration

The cluster.* PQL prefix uses this linkage to find hosts that are K8s nodes:

cluster.name = "k8s-prod-01" AND host.os = "Ubuntu"

This query joins hosts -> k8s_nodes -> clusters, so only hosts that are linked as K8s nodes will match cluster.* filters.

Stale Cluster Detection

The K8sMaintenanceWorker runs periodically and detects clusters whose collector has stopped reporting:

  • Threshold: If clusters.last_seen_at is older than 15 minutes (configurable), the cluster's agent_status is set to disconnected.
  • Event cleanup: The maintenance worker also deletes stale K8s events from PostgreSQL (older than the event retention period, default: 24 hours).
  • Metric: proxima_k8s_stale_clusters_marked_total tracks how many clusters have been marked disconnected.

Cluster Status and Asset Status Mapping

Cluster health status is mapped to the CMDB asset status when upserting the asset record:

Cluster StatusAsset Status
healthyactive
degradeddegraded
unreachableunreachable
unknownactive

CLI Seeder

The scripts/k8s-seed.sh script publishes a test Kubernetes NATS message for development and E2E testing. If kubectl is available and connected to a cluster, it reads real cluster data. Otherwise, it uses hardcoded test data.

# Default: internal / development / test-cluster
./scripts/k8s-seed.sh

# Custom slugs
./scripts/k8s-seed.sh acme production k8s-prod

# Custom NATS server
NATS_URL=nats://remote:4222 ./scripts/k8s-seed.sh

Requirements: nats CLI, jq. Optionally kubectl for real cluster data.

Observability

Prometheus Metrics

MetricTypeDescription
proxima_k8s_clusters_synced_totalcounterTotal cluster inventory syncs processed
proxima_k8s_sync_duration_secondshistogramTime to process a kubernetes message
proxima_k8s_nodes_synced_totalcounterTotal node upserts across all syncs
proxima_k8s_workloads_synced_totalcounterTotal workload upserts across all syncs
proxima_k8s_node_host_linkage_totalcounterNode-host linkage attempts and results (label: result)
proxima_k8s_stale_clusters_marked_totalcounterClusters marked disconnected by maintenance worker

OTel Spans

The KubernetesWorker creates spans for each processing step:

  • k8s.process_message — Top-level span for the entire message
  • k8s.upsert_cluster — Cluster and asset upsert
  • k8s.sync_nodes — Node sync with host linkage
  • k8s.sync_namespaces — Namespace sync
  • k8s.sync_workloads — Workload sync
  • worker.KubernetesPruneUnseen — Prune phase

Permissions

Cluster endpoints reuse the existing CMDB permissions:

PermissionGranted ToDescription
assets:readAll system rolesView clusters and sub-resources
assets:writeSuper Admin, AdminFuture: create and modify clusters

The cluster.* prefix is available in PQL for searching hosts by their associated cluster:

FieldTypeDescriptionExample
cluster.namestringCluster namecluster.name = "k8s-prod-01"
cluster.versionstringK8s versioncluster.version ~ "v1.29*"
cluster.platformstringDistributioncluster.platform = "rke2"
cluster.statusstringHealth statuscluster.status = "healthy"

The cluster.* prefix uses a two-table JOIN chain (k8s_nodes -> clusters), so only hosts that are linked as K8s nodes will match these filters.

See the PQL Reference for full syntax documentation.