# Connect Kubernetes

> Create a namespace reader, produce its kubeconfig and verify a real cluster read.

_How-to · Updated 2026-10-26 · Describes release 0.1.164_

## Before you start

Use Bash with kubectl configured for the intended cluster. Check your current context before running the setup. Your cluster administrator must be allowed to create a service account, role bindings and a node-read ClusterRole. The AIOps or private-agent machine must reach the Kubernetes API endpoint in the generated kubeconfig.

Choose **Kubernetes (kubectl + kubeconfig)** for this guide. The separate **Kubernetes** MCP connector requires an already-running MCP server; pasting a Kubernetes API URL into that MCP field will not work.

## 1. Create read access for one namespace

Copy this entire command. It asks for the existing application namespace, grants its service account the built-in `view` role there, and adds only node `get/list` for the connection check. It creates `aiops-readonly.kubeconfig` in your current directory and refuses to overwrite it.

```bash
bash <<'BASH'
set -euo pipefail
umask 077
kubectl config current-context
read -r -p 'Existing application namespace to read: ' AIOPS_NAMESPACE </dev/tty
: "${AIOPS_NAMESPACE:?A namespace is required}"
kubectl get namespace "$AIOPS_NAMESPACE"
AIOPS_OUTPUT="$PWD/aiops-readonly.kubeconfig"
test ! -e "$AIOPS_OUTPUT" || { printf 'aiops-readonly.kubeconfig already exists; use a fresh directory.\n' >&2; exit 1; }
kubectl -n "$AIOPS_NAMESPACE" create serviceaccount aiops-reader --dry-run=client -o yaml | kubectl apply -f -
kubectl -n "$AIOPS_NAMESPACE" create rolebinding aiops-reader-view --clusterrole=view --serviceaccount="$AIOPS_NAMESPACE:aiops-reader" --dry-run=client -o yaml | kubectl apply -f -
kubectl create clusterrole aiops-reader-nodes --verb=get,list --resource=nodes --dry-run=client -o yaml | kubectl apply -f -
kubectl create clusterrolebinding "aiops-reader-nodes-$AIOPS_NAMESPACE" --clusterrole=aiops-reader-nodes --serviceaccount="$AIOPS_NAMESPACE:aiops-reader" --dry-run=client -o yaml | kubectl apply -f -
AIOPS_TEMP=$(mktemp -d)
trap 'rm -rf "$AIOPS_TEMP"' EXIT
AIOPS_SERVER=$(kubectl config view --raw --minify -o jsonpath='{.clusters[0].cluster.server}')
kubectl config view --raw --minify --flatten -o jsonpath='{.clusters[0].cluster.certificate-authority-data}' | base64 --decode > "$AIOPS_TEMP/ca.crt"
test -s "$AIOPS_TEMP/ca.crt" || { printf 'This context has no embedded CA. Ask your cluster administrator for its trusted CA.\n' >&2; exit 1; }
AIOPS_TOKEN=$(kubectl -n "$AIOPS_NAMESPACE" create token aiops-reader --duration=1h)
kubectl --kubeconfig="$AIOPS_OUTPUT" config set-cluster estate --server="$AIOPS_SERVER" --certificate-authority="$AIOPS_TEMP/ca.crt" --embed-certs=true
kubectl --kubeconfig="$AIOPS_OUTPUT" config set-credentials aiops-reader --token="$AIOPS_TOKEN"
kubectl --kubeconfig="$AIOPS_OUTPUT" config set-context estate --cluster=estate --user=aiops-reader --namespace="$AIOPS_NAMESPACE"
kubectl --kubeconfig="$AIOPS_OUTPUT" config use-context estate
kubectl --kubeconfig="$AIOPS_OUTPUT" get nodes -o name
kubectl --kubeconfig="$AIOPS_OUTPUT" get pods
printf 'Ready: %s\nThe requested token lifetime is one hour; the API server may choose a different duration.\n' "$AIOPS_OUTPUT"
BASH
```

**Expected result:** node names and the namespace's pods, or “No resources found” for an empty namespace. A forbidden or connection error must be fixed before the next step. This grants no Secrets read, pod exec or workload writes. The built-in view role can be extended by your cluster's aggregated roles, so review its effective permissions. [Kubernetes RBAC](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) · [Bound token duration](https://kubernetes.io/docs/reference/kubectl/generated/kubectl_create/kubectl_create_token/).

This is a short-lived first-use credential. For ongoing monitoring, arrange token renewal with the cluster administrator. A copied token is not automatically renewed by the connection. The connection refuses a kubeconfig whose user runs a credential plugin (`exec`, such as `aws eks get-token` or kubelogin, or the older `auth-provider`) or that names a file on the platform host (`tokenFile`, `client-certificate`, `client-key`, `certificate-authority` as a path), when you save it and again before every use: the platform runs no such plugin and reads no such file. Use a static token or a service-account token with the certificate data embedded, as the command above does (`--embed-certs=true`).

## 2. Connect and verify

Open [Data sources](/portal/datasources) (in the product: `/portal/datasources`) → **Connect datasource** → **Kubernetes (kubectl + kubeconfig)**. Choose **Investigate only** and paste the generated file into **kubeconfig YAML**. Add the namespace and service names in the connection notes. The estate inventory reads the namespace this kubeconfig's context names; if you bind the reader in more namespaces, list them all in **Namespaces**.

Select **Connect and verify**. **Expected result:** the real `get nodes` request succeeds. Namespace-only view without the node-read grant fails this verification. If the endpoint is private, [install a private agent](/help/private-access.md) inside that network first; do not replace a private API address with an unrelated public address.

## 3. Discover the application

Scan the verified connection in [Estate x-ray](/portal/xray) (in the product: `/portal/xray`). Review namespace coverage and any denied reads, then use the inventory and dependencies you trust. Missing namespaces are expected when you deliberately granted only one namespace.

## 4. Send an alert and review the evidence

[Send a validation alert](/help/send-first-alert.md) with the service you connected, then open its incident and ask which pods and events support the finding. For real Kubernetes alerts, use your Prometheus Alertmanager or other supported sender.

For a deployment restart, add an **Approved changes only (separate credential)** connection with narrowly scoped Kubernetes permissions, then enable **Allow use for approved plans**. Name the namespace in its kubeconfig context: that namespace is the scope a restart may target, and a context without one confirms none. A shared credential is also supported if you choose that use explicitly. The incident or Change prepares the exact deployment and namespace for review; there is no allowed-action setup form. Keeping separate service accounts also enforces the investigation read boundary in Kubernetes RBAC.

**Compliance on Kubernetes:** a cluster contributes change capture — the kube-apiserver audit webhook, or EKS audit logs read through the AWS connection — to the change ledger. The platform checks `EST-01` to `EST-06` read cloud accounts, not clusters. An audit webhook batch must stay under 16 MiB per POST, and under 1 MiB while an ingress-nginx in front of the platform keeps its default `proxy-body-size` ([the audit webhook](/help/change-webhook.md#kubernetes-on-prem-the-kube-apiserver-audit-webhook)).

**Change capture on EKS:** turn on the cluster's `audit` control-plane log type (EKS → the cluster → Observability → Control plane logging), then set `k8s_audit: true` on the AWS read connection of that account. The harvest reads `/aws/eks/<cluster>/cluster` with CloudWatch Logs Insights; CloudTrail never sees a `kubectl`. People's writes are recorded, and the writes of CI/CD service accounts in the namespaces listed in `k8s_ci_namespaces` — see [CI/CD service accounts](/help/changes-reference.md#k8s-ci).


## Next steps

- Next: [Connect an on-premises server](/help/setup-on-prem.md)
- [All Connect your environment pages](/help/connect-your-environment/llms.txt)
- [Documentation home](/help/)
