Skip to content

Deploying to Kubernetes

Guardian discovers a Kubernetes cluster’s nodes and pods with a single in-cluster collector — the same agent binary you deploy on physical hosts, running in a different mode. It watches the cluster’s own API server from inside the cluster and reports up to the hub, so it works for private/on-prem clusters with no public API endpoint just as well as managed ones.

This is inventory only for now — nodes and pods show up in Kubernetes in the dashboard, but a NotReady node or a crash-looping pod doesn’t yet open an alert or incident the way a host going offline does.

  • Nodes — name and Ready/NotReady status
  • Pods — namespace, name, which node it’s scheduled on, and status (Running, Pending, CrashLoopBackOff, etc.)

Nothing else yet — no Deployments, Services, or Ingress. See the project roadmap if you need broader coverage.

You need cluster-admin (or equivalent) access to apply a ClusterRole.

  1. Go to Deploy Agent in the dashboard and generate an enrollment token, same as for a host agent.
  2. Save the two manifests below as rbac.yaml and deployment.yaml.
  3. Edit deployment.yaml: replace <VERSION> with a released agent version, and fill in ENROLLMENT_TOKEN and GUARDIAN_HUB_URL in its Secret.
  4. Apply both:
Terminal window
kubectl apply -f rbac.yaml
kubectl apply -f deployment.yaml
# rbac.yaml: read-only access (get, list, watch on nodes, pods and namespaces). No write access, no secrets access.
apiVersion: v1
kind: Namespace
metadata:
name: gt-guardian
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: gt-guardian-collector
namespace: gt-guardian
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: gt-guardian-collector
rules:
- apiGroups: [""]
resources: ["nodes", "pods", "namespaces"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: gt-guardian-collector
subjects:
- kind: ServiceAccount
name: gt-guardian-collector
namespace: gt-guardian
roleRef:
kind: ClusterRole
name: gt-guardian-collector
apiGroup: rbac.authorization.k8s.io
# deployment.yaml: one collector for the whole cluster. Apply rbac.yaml first.
apiVersion: v1
kind: Secret
metadata:
name: gt-guardian-collector
namespace: gt-guardian
type: Opaque
stringData:
ENROLLMENT_TOKEN: "REPLACE_WITH_YOUR_ENROLLMENT_TOKEN"
GUARDIAN_HUB_URL: "https://your-hub.example.com"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: gt-guardian-collector
namespace: gt-guardian
spec:
replicas: 1
selector:
matchLabels:
app: gt-guardian-collector
template:
metadata:
labels:
app: gt-guardian-collector
spec:
serviceAccountName: gt-guardian-collector
containers:
- name: collector
image: ghcr.io/gt-arsenals-ltd/gt-guardian-agent:<VERSION>
envFrom:
- secretRef:
name: gt-guardian-collector
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 250m
memory: 256Mi

This creates a gt-guardian namespace, a read-only ServiceAccount/ClusterRole (get/list/watch on nodes, pods, and namespaces — nothing else, no write access, no secrets access), and a single-replica Deployment. One collector watches the whole cluster — this is not a per-node DaemonSet.

Check Kubernetes in the dashboard — the cluster should appear within about 30 seconds, with its node and pod counts. Click it to expand the node and pod lists; filter pods by namespace if there are a lot of them.

On the cluster itself:

Terminal window
kubectl get pods -n gt-guardian
kubectl logs -n gt-guardian deploy/gt-guardian-collector

Should show one Running pod, with [K8s] Node and Pod informers synced — watching cluster state in its logs.

Terminal window
kubectl delete -f deployment.yaml
kubectl delete -f rbac.yaml

From the dashboard, also remove the cluster’s entry from Agents the same way you’d remove a host agent — deleting the Kubernetes resources above doesn’t do this automatically.

Deploy the collector to each cluster you want visibility into — every cluster with its own enrollment shows up as its own entry, there’s no merged multi-cluster view.