Skip to Content

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

CapabilityData sourceDoes Cardinal SaaS need access to the cluster API?
Query collected metrics, logs, and traces; build dashboards and telemetry alertsTelemetry exported by collectors and queried through your configured data sourceNo. Collectors access the cluster as needed and export telemetry through the configured collection path.
Configure Kubernetes event overlays in ExploreKubernetes integration, with stored event history when availableYes 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 MapKubernetes APIYes. 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 API

The 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

CapabilityEnabled
Explore (chart markers)Always
AgentAlways

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):

FieldRequiredDescription
API Server URLYesThe 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 CertificateYesThe cluster’s CA in PEM format
Bearer TokenYesA long-lived ServiceAccount token (see Prerequisites below)
Default NamespaceNoUsed when a tool call omits one

Client-certificate authentication is also supported as an alternative to bearer tokens.

Optional Metadata

FieldDescription
NameDisplay name for this integration
DescriptionHuman-readable description
Planner HintGuidance 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 ClusterRole and ServiceAccount

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-system

Generating 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-token

Then 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 -d

The API server URL comes from your kubeconfig:

kubectl config view --raw -o jsonpath='{.clusters[?(@.name=="<your-context>")].cluster.server}'

Setup

  1. Apply the ClusterRole and ServiceAccount manifests above to your cluster.
  2. Create the long-lived token secret and extract the token, CA, and API server URL.
  3. In Cardinal, navigate to Settings > Integrations and click Add Integration.
  4. Select Kubernetes.
  5. Enter the API Server URL, CA Certificate, and Bearer Token.
  6. Click Test Connection — Cardinal verifies the credentials and confirms the role is read-only via a SelfSubjectAccessReview preflight.
  7. Fill in Name, Description, and Planner Hint as needed.
  8. 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:

  1. Put everything in a dedicated Project so uninstall is a single oc delete project.
  2. Add OpenShift-native resources (route.openshift.io/routes, apps.openshift.io/deploymentconfigs) to the ClusterRole so 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 EOF

2. 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 EOF

3. 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 -d

Paste 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} EOF

Steps 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 -d

When 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:

  1. Open any Metrics or Logs dashboard in Explore.
  2. Click Event Sources in the chart toolbar.
  3. Toggle Kubernetes on and step through the wizard: pick a cluster, namespaces (or * for all), and event reasons to surface.
  4. 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

SymptomCheck
API works from your laptop but Test Connection cannot reach itVerify 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 errorVerify the API URL and the CA certificate for that endpoint.
401 UnauthorizedVerify the bearer token or client certificate is valid. Replace expired credentials in the integration.
403 ForbiddenVerify the ServiceAccount’s Role/ClusterRole and bindings cover the requested resources and namespaces.
Recent Events appear but older history does notVerify 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 themMatch 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 ClusterRole excludes mutating verbs and secrets. The agent’s MCP server registers no create, update, or delete tools. Each first-time tool call against a cluster runs a SelfSubjectAccessReview preflight; if any mutating verb is mistakenly granted to the bound credentials, Cardinal logs a high-severity warning so you can tighten the role.
  • No secrets access 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.

Last updated on