Kubernetes
Cardinal SaaS can monitor Kubernetes clusters and connect to their APIs remotely. You can use a managed cloud cluster such as EKS, GKE, or AKS, or a cluster you operate yourself. Cardinal does not need to run inside the monitored cluster or on its control-plane nodes.
There are two connections to distinguish: collectors export telemetry for dashboards and queries, while the Kubernetes integration gives Cardinal read-only access to the cluster API for live inspection and event correlation.
Kubernetes monitoring from Cardinal SaaS
| Capability | Data source | Does Cardinal SaaS need access to the cluster API? |
|---|---|---|
| Query collected metrics, logs, and traces; build dashboards and telemetry alerts | Telemetry exported by collectors and queried through your configured data source | No. Collectors access the cluster as needed and export telemetry through the configured collection path. |
| Configure Kubernetes event overlays in Explore | Kubernetes integration, with stored event history when available | Yes for the current setup flow: Cardinal discovers namespaces through the API and falls back to live Events when stored history is unavailable or empty. |
| Inspect current resources with agent tools or the Infra Map | Kubernetes API | Yes. Cardinal makes authenticated, read-only API requests. |
Running collectors in your cluster does not require self-hosting Cardinal. See OpenTelemetry Collectors for the collection path and Connecting from Cardinal SaaS below for live API access.
Connecting from Cardinal SaaS
Configure the cluster’s API Server URL, CA Certificate, and a Bearer Token or client certificate in Settings > Integrations > Kubernetes. Cardinal’s backend services connect to that URL over HTTPS:
Cardinal SaaS ── HTTPS + read-only credentials ──► Your Kubernetes APIThe requirement is network reachability from Cardinal’s backend, rather than local access to your control plane. A kubeconfig that works on your laptop does not establish connectivity from Cardinal SaaS.
- Reachable external endpoint: allow HTTPS from Cardinal to the API endpoint. If the endpoint uses a source-IP allowlist, contact support@cardinalhq.io for the egress addresses applicable to your Cardinal deployment.
- Private-only endpoint: direct API integration requires a network path from your Cardinal deployment to that endpoint. Contact support to confirm connectivity options for your environment before configuring it. Supplying credentials alone does not make a private endpoint reachable.
- Collector export only: telemetry collection can continue without direct API access from Cardinal SaaS. Live inspection, Infra Map topology, and the current event-overlay setup still require the API connection described above.
Neither an OpenTelemetry collector nor the Perch operator automatically creates a tunnel from Cardinal SaaS to your Kubernetes API. The operator installation uses a separate outbound connection to manage a self-hosted site.
Overview
The Kubernetes integration is read-only. It surfaces two capabilities:
- Chart markers — Kubernetes Events appear as colored markers on the Metrics and Logs timelines, alongside any GitHub events you have enabled. With object-event collection, Cardinal reads historical Events from Cardinal Data Lake first. Configured object-change streams can also surface configuration changes. Without stored history, Cardinal reads Events from the live API.
- MCP tools — read-only kubectl-equivalent access over the MCP endpoint:
list_events,list_resources,get_resource,get_logs,list_api_resources.
Cardinal never writes to your cluster. The integration stores your credentials encrypted at rest and transmits them per-request to the agent gateway.
Capabilities
| Capability | Enabled |
|---|---|
| Explore (chart markers) | Always |
| Agent | Always |
Configuration
The Kubernetes integration uses a multi-step setup wizard. After entering credentials, Cardinal connects to your cluster to validate access and let you choose namespaces and event types to surface in Explore.
Credentials
Token authentication (recommended):
| Field | Required | Description |
|---|---|---|
| API Server URL | Yes | The cluster’s HTTPS API endpoint reachable from your Cardinal deployment (e.g., https://abc123.gr7.us-east-2.eks.amazonaws.com). For SaaS, reachability must be from Cardinal’s backend. |
| CA Certificate | Yes | The cluster’s CA in PEM format |
| Bearer Token | Yes | A long-lived ServiceAccount token (see Prerequisites below) |
| Default Namespace | No | Used when a tool call omits one |
Client-certificate authentication is also supported as an alternative to bearer tokens.
Optional Metadata
| Field | Description |
|---|---|
| Name | Display name for this integration |
| Description | Human-readable description |
| Planner Hint | Guidance for the AI on when to use this integration (e.g., “Use for the prod-us cluster running checkout and payments”) |
Set the integration’s cluster slug to the collector’s k8s.cluster.name value when using stored Kubernetes event history. For example, if K8S_CLUSTER_NAME=prod-us, use prod-us as the slug. Cardinal Data Lake exposes that attribute as k8s_cluster_name; a different slug prevents the history queries from matching your cluster’s records.
Prerequisites
- A Kubernetes cluster (1.24+) whose HTTPS API endpoint is reachable from your Cardinal deployment; for SaaS, see Connecting from Cardinal SaaS
- Cluster-admin (or equivalent) access to install the recommended
ClusterRoleandServiceAccount
Recommended ClusterRole
Cardinal ships with a default ClusterRole that grants exactly the read verbs the agent and Explore need. Secrets are deliberately excluded.
Apply this manifest to your cluster:
apiVersion: v1
kind: ServiceAccount
metadata:
name: cardinal-mcp-reader
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: cardinal-mcp-reader
rules:
- apiGroups: [""]
resources:
- pods
- namespaces
- services
- endpoints
- events
- configmaps
- nodes
- persistentvolumeclaims
- persistentvolumes
- serviceaccounts
- replicationcontrollers
- resourcequotas
- limitranges
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
- apiGroups: ["apps"]
resources: ["deployments", "replicasets", "statefulsets", "daemonsets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["batch"]
resources: ["jobs", "cronjobs"]
verbs: ["get", "list", "watch"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses", "networkpolicies"]
verbs: ["get", "list", "watch"]
- apiGroups: ["autoscaling"]
resources: ["horizontalpodautoscalers"]
verbs: ["get", "list", "watch"]
- apiGroups: ["policy"]
resources: ["poddisruptionbudgets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch"]
- apiGroups: ["authorization.k8s.io"]
resources: ["selfsubjectaccessreviews"]
verbs: ["create"]
- apiGroups: ["metrics.k8s.io"]
resources: ["pods", "nodes"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: cardinal-mcp-reader
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cardinal-mcp-reader
subjects:
- kind: ServiceAccount
name: cardinal-mcp-reader
namespace: kube-systemGenerating a long-lived token
Kubernetes 1.24+ no longer auto-creates secrets for ServiceAccounts. Create one manually:
apiVersion: v1
kind: Secret
metadata:
name: cardinal-mcp-reader-token
namespace: kube-system
annotations:
kubernetes.io/service-account.name: cardinal-mcp-reader
type: kubernetes.io/service-account-tokenThen extract the token and CA:
kubectl -n kube-system get secret cardinal-mcp-reader-token -o jsonpath='{.data.token}' | base64 -d
kubectl -n kube-system get secret cardinal-mcp-reader-token -o jsonpath='{.data.ca\.crt}' | base64 -dThe API server URL comes from your kubeconfig:
kubectl config view --raw -o jsonpath='{.clusters[?(@.name=="<your-context>")].cluster.server}'Setup
- Apply the
ClusterRoleandServiceAccountmanifests above to your cluster. - Create the long-lived token secret and extract the token, CA, and API server URL.
- In Cardinal, navigate to Settings > Integrations and click Add Integration.
- Select Kubernetes.
- Enter the API Server URL, CA Certificate, and Bearer Token.
- Click Test Connection — Cardinal verifies the credentials and confirms the role is read-only via a
SelfSubjectAccessReviewpreflight. - Fill in Name, Description, and Planner Hint as needed.
- Click Create.
OpenShift quickstart
The setup above works on OpenShift too — the manifests are vanilla Kubernetes RBAC. Two small changes make for a smoother install:
- Put everything in a dedicated
Projectso uninstall is a singleoc delete project. - Add OpenShift-native resources (
route.openshift.io/routes,apps.openshift.io/deploymentconfigs) to theClusterRoleso the agent can inspect them.
1. Apply RBAC
oc apply -f - <<'EOF'
apiVersion: project.openshift.io/v1
kind: Project
metadata:
name: cardinal-mcp
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: cardinal-mcp-reader
namespace: cardinal-mcp
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: cardinal-mcp-reader
rules:
- apiGroups: [""]
resources:
- pods
- namespaces
- services
- endpoints
- events
- configmaps
- nodes
- persistentvolumeclaims
- persistentvolumes
- serviceaccounts
- replicationcontrollers
- resourcequotas
- limitranges
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
- apiGroups: ["apps"]
resources: ["deployments", "replicasets", "statefulsets", "daemonsets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["batch"]
resources: ["jobs", "cronjobs"]
verbs: ["get", "list", "watch"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses", "networkpolicies"]
verbs: ["get", "list", "watch"]
- apiGroups: ["autoscaling"]
resources: ["horizontalpodautoscalers"]
verbs: ["get", "list", "watch"]
- apiGroups: ["policy"]
resources: ["poddisruptionbudgets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["route.openshift.io"]
resources: ["routes"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps.openshift.io"]
resources: ["deploymentconfigs"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list", "watch"]
- apiGroups: ["authorization.k8s.io"]
resources: ["selfsubjectaccessreviews"]
verbs: ["create"]
- apiGroups: ["metrics.k8s.io"]
resources: ["pods", "nodes"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: cardinal-mcp-reader
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cardinal-mcp-reader
subjects:
- kind: ServiceAccount
name: cardinal-mcp-reader
namespace: cardinal-mcp
EOF2. Create a long-lived token Secret
oc apply -f - <<'EOF'
apiVersion: v1
kind: Secret
metadata:
name: cardinal-mcp-reader-token
namespace: cardinal-mcp
annotations:
kubernetes.io/service-account.name: cardinal-mcp-reader
type: kubernetes.io/service-account-token
EOF3. Extract the values Cardinal needs
# API server URL — e.g. https://api.acme-prod.example.com:6443
oc whoami --show-server
# CA certificate (PEM)
oc -n cardinal-mcp get secret cardinal-mcp-reader-token \
-o jsonpath='{.data.ca\.crt}' | base64 -d
# Bearer token
oc -n cardinal-mcp get secret cardinal-mcp-reader-token \
-o jsonpath='{.data.token}' | base64 -dPaste those three values into Settings > Integrations > Add Integration > Kubernetes, then click Test Connection.
Uninstall: oc delete project cardinal-mcp && oc delete clusterrole cardinal-mcp-reader && oc delete clusterrolebinding cardinal-mcp-reader.
Namespace-scoped install (no ClusterRole)
If your cluster admin can’t grant a ClusterRole — common on shared OpenShift instances or regulated environments — install Cardinal’s read access scoped to a single namespace instead. The integration’s surface narrows to that namespace: chart-marker events come from there, agent tool calls (list_resources, get_logs, list_events) operate on that namespace only, and node / persistent-volume / cluster-wide CRD discovery are unavailable.
Replace step 1 with a Role + RoleBinding in the target namespace:
TARGET_NS=checkout-prod # the project you want Cardinal to see
oc apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
name: cardinal-mcp-reader
namespace: ${TARGET_NS}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: cardinal-mcp-reader
namespace: ${TARGET_NS}
rules:
- apiGroups: [""]
resources:
- pods
- services
- endpoints
- events
- configmaps
- serviceaccounts
- replicationcontrollers
- persistentvolumeclaims
- resourcequotas
- limitranges
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
- apiGroups: ["apps"]
resources: ["deployments", "replicasets", "statefulsets", "daemonsets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["batch"]
resources: ["jobs", "cronjobs"]
verbs: ["get", "list", "watch"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses", "networkpolicies"]
verbs: ["get", "list", "watch"]
- apiGroups: ["autoscaling"]
resources: ["horizontalpodautoscalers"]
verbs: ["get", "list", "watch"]
- apiGroups: ["policy"]
resources: ["poddisruptionbudgets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["route.openshift.io"]
resources: ["routes"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps.openshift.io"]
resources: ["deploymentconfigs"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: cardinal-mcp-reader
namespace: ${TARGET_NS}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: cardinal-mcp-reader
subjects:
- kind: ServiceAccount
name: cardinal-mcp-reader
namespace: ${TARGET_NS}
EOFSteps 2 (token Secret) and 3 (extract values) are identical — just use the same namespace:
oc apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
name: cardinal-mcp-reader-token
namespace: ${TARGET_NS}
annotations:
kubernetes.io/service-account.name: cardinal-mcp-reader
type: kubernetes.io/service-account-token
EOF
oc -n ${TARGET_NS} get secret cardinal-mcp-reader-token \
-o jsonpath='{.data.token}' | base64 -d
oc -n ${TARGET_NS} get secret cardinal-mcp-reader-token \
-o jsonpath='{.data.ca\.crt}' | base64 -dWhen you register the integration in Cardinal, fill in the Default Namespace field with the target namespace (e.g. checkout-prod). Cardinal detects that the token is namespace-scoped and:
- Skips cluster-wide LIST during connection-test — namespace count reports
1. - Skips the wizard’s namespace picker — only the bound namespace is shown.
- Narrows the agent’s tool calls to that namespace.
Uninstall: oc -n ${TARGET_NS} delete role cardinal-mcp-reader rolebinding/cardinal-mcp-reader serviceaccount/cardinal-mcp-reader secret/cardinal-mcp-reader-token.
Using Explore correlation
Once the integration is created:
- Open any Metrics or Logs dashboard in Explore.
- Click Event Sources in the chart toolbar.
- Toggle Kubernetes on and step through the wizard: pick a cluster, namespaces (or
*for all), and event reasons to surface. - Markers appear inline on the chart with reason-based colors (BackOff and FailedScheduling are red; Unhealthy and Killing are amber; informational reasons are neutral).
For historical windows, enable Kubernetes event collection in addition to this integration. Stored Events survive the deletion of the Pod they refer to and remain available for your log retention period. The live API’s Event retention is typically about an hour and cannot provide the same history.
Historical reads do not turn this into a fully collector-only integration: the current wizard still discovers namespaces through the API, and an empty or failed history read triggers the live API fallback.
Troubleshooting connectivity and missing events
| Symptom | Check |
|---|---|
| API works from your laptop but Test Connection cannot reach it | Verify DNS, routing, and firewall/source-IP rules from Cardinal’s backend. A laptop VPN connection does not give SaaS the same access. |
| TLS or certificate error | Verify the API URL and the CA certificate for that endpoint. |
401 Unauthorized | Verify the bearer token or client certificate is valid. Replace expired credentials in the integration. |
403 Forbidden | Verify the ServiceAccount’s Role/ClusterRole and bindings cover the requested resources and namespaces. |
| Recent Events appear but older history does not | Verify the collector exports Kubernetes Events as logs; cluster metrics alone do not contain event history. Also check the query window and log retention. |
| Collected Events are present but the overlay misses them | Match the integration slug to k8s_cluster_name in the stored records, and check the selected namespaces and event reasons. |
What This Enables
Once configured, you can ask the AI agent questions like:
- “What pods are crashing in the ingress namespace right now?”
- “Show me events for the checkout deployment in the last 30 minutes.”
- “Get logs for the api-server-7d4c pod’s app container.”
- “List all CRDs installed on this cluster.”
Security Posture
- Credentials are encrypted at rest using your Cardinal deployment’s signing keys.
- Read-only enforcement is layered: the recommended
ClusterRoleexcludes mutating verbs andsecrets. The agent’s MCP server registers nocreate,update, ordeletetools. Each first-time tool call against a cluster runs aSelfSubjectAccessReviewpreflight; if any mutating verb is mistakenly granted to the bound credentials, Cardinal logs a high-severity warning so you can tighten the role. - No
secretsaccess is required or granted by the recommended role.
Reach out to support@cardinalhq.io for support or to ask questions not answered in our documentation.