> For the complete documentation index, see [llms.txt](https://docs.groundcover.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.groundcover.com/investigate/querying-your-groundcover-data/kubectl-access.md).

# kubectl Access

Point the standard kubectl, k9s or Lens at groundcover and read your clusters from groundcover's stored data, with groundcover RBAC deciding what each caller sees.

groundcover serves a read-only Kubernetes API for every cluster it monitors. You add it to your kubeconfig like any other cluster and use the tools you already have: `kubectl`, k9s, Lens or Freelens. No plugin or extra CLI is needed.

{% hint style="warning" %}
**Read-only.** Only read actions are supported: `get`, `describe`, `logs`, `top`, watch and `auth can-i`. Every create, update, patch and delete is refused, including `apply`, `edit`, `scale`, `rollout restart`, `cordon`, `drain` and `delete`.
{% endhint %}

<figure><img src="https://2771001740-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FUHgqKYgCiRKdOpWQdi52%2Fuploads%2Fgit-blob-c7f0b13a70f46f42dee78115aa75c634173a2424%2Fkubectl-access.gif?alt=media" alt="Setting up a groundcover kubectl context, then listing nodes and pods, reading current and previous logs, and a refused delete"><figcaption><p>Add a groundcover context, then use kubectl as usual</p></figcaption></figure>

Requests are answered from the data groundcover already stores (entities, logs, events and resource usage), not from the cluster's API server. That means:

* **No cluster credentials to hand out.** Engineers, CI jobs and coding agents get kubectl access through a groundcover API key instead of a cluster kubeconfig.
* **groundcover RBAC applies.** The API key's [policies](/administer/role-based-access-control-rbac.md) decide which clusters, environments and namespaces the caller sees, the same as in the UI. Objects and `kubectl top` usage follow the policy's workloads scope, `kubectl logs` its logs scope, and events its events scope.
* **Read-only by design.** Every write through the Kubernetes API is refused.
* **Logs outlive the node.** `kubectl logs` reads groundcover's log storage, so it returns lines the node has already rotated away, and `--previous` keeps working after the node has cleaned up the previous container. What is returned follows your log pipelines, sampling and retention.
* **The cluster is not touched.** Queries run against groundcover's storage, so heavy use puts no load on the cluster's control plane.

## Set up

### 1. Create an API key

An API key sees what its service account's [policies](/administer/role-based-access-control-rbac.md) allow. To give kubectl access to the whole workspace, use a service account bound to the **groundcover Read Only** policy. To limit it to specific clusters or namespaces:

1. **Create a policy.** Go to **Settings > Policies** and click **Create Policy**. Give it a **Policy title** and set **Permissions Level** to **Read Only**. Under **Data Scope**, keep **Simple** and fill in the conditions: **Cluster** `in` the clusters the key may see, and **Namespace** `in` the namespaces it may see. Use **'And' Condition** to add another condition, such as **Environment**. Then click **Create Policy**.
2. **Create a service account.** Go to **Settings > Access > Service Accounts**, click **Create Service Account**, and under **Policies** assign **only** this policy. See [Service Accounts](/administer/service-accounts.md). A service account's access is the union of all its policies, so one broader policy widens what the key sees.
3. **Create the key.** Go to **Settings > Access > API Keys**, click **Create API Key**, and choose that service account. See [API Keys](/administer/api-keys.md).

With such a key, kubectl lists only objects, logs, events and usage inside the scope. Other namespaces don't exist from kubectl's point of view, and cluster-wide kinds such as nodes and persistent volumes return nothing when the scope names a namespace.

### 2. Find your backend ID and cluster name

The server URL names a backend and a cluster. A **backend** is a groundcover deployment that stores data for one or more clusters. Most workspaces have one.

**In the UI:**

* **Backend ID:** go to **Settings > Access > API Keys**. The backend ID is shown on that page.
* **Cluster name:** open the cluster selector at the top of any page. The cluster names listed there are the names to use.

**With the API**, using the same API key. Replace `api.groundcover.com` with your own groundcover address if you are self-hosted:

```bash
export GC_API_KEY="<YOUR_API_KEY>"

# List backends: each one's backendId is a <BACKEND_ID>
curl -s https://api.groundcover.com/api/backends/list \
  -H "Authorization: Bearer $GC_API_KEY" | jq -r '.[].backendId'

# Set it to a backendId printed above
export GC_BACKEND_ID="<BACKEND_ID>"

# List the clusters of a backend: each name is a <CLUSTER_NAME>
curl -s -X POST https://api.groundcover.com/api/k8s/v3/clusters/list \
  -H "Authorization: Bearer $GC_API_KEY" \
  -H "X-Backend-Id: $GC_BACKEND_ID" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"sources":[]}' | jq -r '.clusters[].name'
```

### 3. Add a kubeconfig context

The server URL names the backend and the cluster:

```
https://api.groundcover.com/api/kubernetes/v1/backends/<BACKEND_ID>/clusters/<CLUSTER_NAME>
```

{% hint style="info" %}
Use your own groundcover address. The examples use `https://api.groundcover.com`. Self-hosted, on-prem and air-gapped deployments replace it with the address of their groundcover environment, and keep the rest of the path as is.
{% endhint %}

Set the three values, if you have not already, and add a context with `kubectl config`:

```bash
export GC_API_KEY="<YOUR_API_KEY>"
export GC_BACKEND_ID="<BACKEND_ID>"
export GC_CLUSTER="<CLUSTER_NAME>"

kubectl config set-cluster groundcover-$GC_CLUSTER \
  --server=https://api.groundcover.com/api/kubernetes/v1/backends/$GC_BACKEND_ID/clusters/$GC_CLUSTER
kubectl config set-credentials groundcover --token=$GC_API_KEY
kubectl config set-context groundcover-$GC_CLUSTER \
  --cluster=groundcover-$GC_CLUSTER --user=groundcover
kubectl config use-context groundcover-$GC_CLUSTER
```

Or write the kubeconfig entries directly:

```yaml
apiVersion: v1
kind: Config
clusters:
  - name: groundcover-prod
    cluster:
      server: https://api.groundcover.com/api/kubernetes/v1/backends/<BACKEND_ID>/clusters/prod
users:
  - name: groundcover
    user:
      token: <YOUR_API_KEY>
contexts:
  - name: groundcover-prod
    context:
      cluster: groundcover-prod
      user: groundcover
current-context: groundcover-prod
```

Add one context per cluster. They can share the same user entry.

Check the connection:

```bash
kubectl get namespaces
```

{% hint style="info" %}
k9s, Lens and Freelens read the same kubeconfig. Select the groundcover context and they work without further setup.
{% endhint %}

## Example commands

### Browse resources

```bash
kubectl get pods -n checkout
kubectl get pods -A -o wide
kubectl get deploy,sts,ds -n checkout
kubectl get all -n checkout
kubectl get nodes -o wide
kubectl get pvc -A
kubectl get cronjobs,jobs -A
kubectl get events -n checkout
```

### Filter and sort

```bash
# Label selectors
kubectl get pods -A -l app.kubernetes.io/name=api
kubectl get pods -n checkout -l 'tier in (web,api),env!=dev'

# Field selectors
kubectl get pods -A --field-selector status.phase!=Running
kubectl get pods -A --field-selector spec.nodeName=<NODE_NAME>

# Sorting and paging
kubectl get pods -A --sort-by=.status.startTime
kubectl get pods -A --sort-by='.status.containerStatuses[0].restartCount'
kubectl get pods -A --chunk-size=200
```

### Extract fields

```bash
kubectl get pod <POD> -n checkout -o yaml
kubectl get pods -n checkout -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[*].image}{"\n"}{end}'
kubectl get nodes -o custom-columns=NAME:.metadata.name,KUBELET:.status.nodeInfo.kubeletVersion
kubectl get deploy -A -o json | jq '.items[] | select((.status.readyReplicas // 0) != (.spec.replicas // 0)) | .metadata.name'
```

### Inspect

```bash
kubectl describe pod <POD> -n checkout
kubectl describe node <NODE_NAME>
kubectl describe deploy <DEPLOYMENT> -n checkout
kubectl rollout status deploy/<DEPLOYMENT> -n checkout
kubectl rollout history deploy/<DEPLOYMENT> -n checkout
```

### Logs

Logs are read from groundcover's log storage. Without `--since`, the last 24 hours are searched.

```bash
kubectl logs <POD> -n checkout
kubectl logs <POD> -n checkout -c <CONTAINER> --tail=100
kubectl logs <POD> -n checkout --since=15m --timestamps
kubectl logs <POD> -n checkout --previous    # the previous instance of a restarted container
kubectl logs -f <POD> -n checkout
kubectl logs deploy/<DEPLOYMENT> -n checkout
```

### Resource usage

```bash
kubectl top pod -n checkout
kubectl top pod -A --sort-by=memory
kubectl top node
```

### Watch

```bash
kubectl get pods -n checkout -w
kubectl get deploy -A --watch-only
```

### Custom resources

Custom resources that groundcover stores are discovered automatically and served like built-in kinds:

```bash
kubectl api-resources
kubectl get helmreleases -A
kubectl get scaledobjects -A
```

### Check access

```bash
kubectl auth can-i list pods
kubectl auth can-i --list
```

## What is served

| API group                | Resources                                                                                                         |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------- |
| core (`v1`)              | `pods` (with `log`), `nodes`, `namespaces`, `events`, `configmaps`, `persistentvolumes`, `persistentvolumeclaims` |
| `apps/v1`                | `deployments`, `replicasets`, `statefulsets`, `daemonsets`                                                        |
| `batch/v1`               | `jobs`, `cronjobs`                                                                                                |
| `metrics.k8s.io/v1beta1` | `pods`, `nodes` (for `kubectl top`)                                                                               |
| Custom resources         | Every custom resource kind groundcover stores for the cluster                                                     |

Resources support `get`, `list` and `watch`, with label selectors, field selectors, paging and table output. Events are listed per namespace.

## Limitations

* **Read-only.** `apply`, `create`, `edit`, `delete`, `scale`, `patch`, `rollout restart`, `cordon` and `drain` are refused.
* **No live connection to the cluster.** `exec`, `attach`, `port-forward` and `cp` are not available.
* **Secrets are never served.**
* **Only kinds groundcover stores are served.** Other kinds, such as Services, Ingresses and RBAC objects, return "the server doesn't have a resource type".
* **`kubectl explain` is not available**, since no OpenAPI schema is served.
* **Data is as fresh as groundcover's last update** of the cluster, not the cluster's live state. `--watch` checks for changes about every 15 seconds.
* **Custom resources are not converted between versions.** Each object is returned under the API version it was stored with.
* **`kubectl auth can-i` reflects what the API serves**, not your groundcover policy. Which objects you see is decided by the API key's data scope.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.groundcover.com/investigate/querying-your-groundcover-data/kubectl-access.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
