Kubernetes
Pod and rollout failures as incidents, the cause read from the cluster, and approved rollbacks, restarts and volume growth.
With this connector 3AM watches the namespaces you choose, works out why a workload is failing from the API server alone (no metrics stack needed), and fixes it with your approval. It is also how fixes found by other connectors get carried out: when VictoriaMetrics or Prometheus proves a component is hung, this connector restarts it.
Before you start
kubectlaccess as a cluster admin, once, to create a ServiceAccount and a Role.- The namespaces 3AM should watch and act in.
- Network access from 3AM to the API server (inside the cluster that's
https://kubernetes.default.svc).
1. Create a ServiceAccount and a Role
3AM's access is one ServiceAccount, plus a Role in each namespace it watches. Both manifests are in the
kubernetes/ folder of each 3AM release. Installed from the Helm repository? curl -fsSL https://get.3am.si | sh
downloads a release without installing it.
kubectl apply -f kubernetes/3am-rbac.yaml # namespace 3am, the ServiceAccount, a token, StorageClass read
for ns in bank-prod payments-prod; do
sed "s/WATCHED_NAMESPACE/$ns/" kubernetes/3am-role.yaml | kubectl apply -f -
donerules:
- apiGroups: [""]
resources: [pods, pods/log, services]
verbs: [get, list, watch] # see failures; find which workload an address belongs to
- apiGroups: [apps]
resources: [deployments, statefulsets, daemonsets, replicasets]
verbs: [get, list, watch] # map pods to workloads, read revisions
- apiGroups: [apps]
resources: [deployments, statefulsets, daemonsets, deployments/scale, statefulsets/scale]
verbs: [patch] # restart, scale, roll back
- apiGroups: [""]
resources: [pods]
verbs: [delete] # restart_pod: the controller recreates the pod
- apiGroups: [""]
resources: [persistentvolumeclaims]
verbs: [get, list, patch] # expand_volume
- apiGroups: [""]
resources: [events]
verbs: [create] # 3AM's diagnosis as an Event on the workload (optional)Outside the namespaces, 3am-rbac.yaml grants one read-only ClusterRole: get on StorageClasses, to know whether a
volume may grow. There is no exec, no Secrets access, and nothing that deletes workloads or volumes.
To take an action away, delete its rule. The connector's health check then reports it as not allowed and 3AM hands that fix to a person.
2. Collect the token, CA and API address
kubectl -n 3am get secret 3am-connector-token -o jsonpath='{.data.token}' | base64 -d > 3am-k8s-token
kubectl config view --raw --minify -o jsonpath='{.clusters[0].cluster.certificate-authority-data}' | base64 -d > k8s-ca.pem
kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}'; echoOn EKS, AKS and GKE, use the address and CA from your kubeconfig as they are. 3AM authenticates with the ServiceAccount token, so a cloud auth plugin in your kubeconfig doesn't matter.
3. Add the connector
In the console: Connections → Add a connection → Kubernetes. Or in connectors.json:
{ "name": "k8s", "kind": "kubernetes",
"settings": { "api_url": "https://10.0.0.1:6443",
"token": { "file": "/etc/3am/secrets/3am-k8s-token" },
"ca_file": "/etc/3am/secrets/k8s-ca.pem",
"namespaces": ["bank-prod", "payments-prod"] } }kind: kubernetesalerts inactions| Setting | What to enter | Required | Default |
|---|---|---|---|
api_url | API server URL | yes | — |
tokensecret | Service-account token | yes | — |
ca_file | API server CA certificate (PEM file; empty: system CAs) | no | — |
verify_tls | Verify TLS | yes | true |
namespaces | Namespaces 3AM watches and may act in | yes | — |
4. Verify
Test connection asks the API server, namespace by namespace, whether the token can do each thing 3AM needs:
Kubernetes v1.34.0; permissions present in bank-prod, payments-prodA missing permission is named, with the namespace and what it's for:
Kubernetes v1.34.0, but the token cannot: list pods in payments-prod (see pod state)What it detects
| Signal | When |
|---|---|
KubePodCrashLooping | A container is waiting in CrashLoopBackOff |
KubeContainerOOMKilled | A container's last exit was OOMKilled, in the last 30 minutes |
KubeImagePullBackOff | ImagePullBackOff or ErrImagePull |
KubeContainerConfigError | CreateContainerConfigError |
KubePodUnschedulable | Pending and unschedulable for more than 2 minutes |
KubeDeploymentRolloutStuck | A rollout passed its progress deadline |
The names match kube-prometheus, so if your Prometheus already alerts on these, the same causes apply. Three crash-looping replicas of one workload are one incident.
What it proves and fixes
| Cause | How 3AM knows | Fix |
|---|---|---|
| Bad release | A rollout in the last 6 hours, the previous revision's pods differ, the new pods fail, and the crashed run's logs show no dependency errors | Roll back to the last revision whose pods actually differ. If Argo CD manages the workload, the rollback goes through Argo CD, because self-heal would undo a kubectl rollback |
| Dependency down | The crashed run's logs show it can't reach something (connection refused, timeout, unknown host) | None. Restarting won't help; 3AM names the dependency |
| Memory limit too low | Last exit OOMKilled in the last 30 minutes | Advice with the current limit. Rolls back instead if a release caused it |
| Missing config | CreateContainerConfigError naming the ConfigMap or Secret | Advice: create it |
| Image unavailable | The image pull fails | Advice: tag, registry or pull secret. Rolls back if the image came with a recent release |
| Unschedulable | The scheduler's reason | Advice: capacity, node selector, affinity or taints |
| Volume filling up | KubePersistentVolumeFillingUp, kubelet stats under 15% free | Grow the PVC by half, if its StorageClass allows it. Two approvers |
"Bad release" and "dependency down" exclude each other: restarting pods whose database is down is the classic wrong move. How fixes are approved and verified is the same for every connector: see How 3AM decides.
Actions
| Action | Parameters | Notes |
|---|---|---|
rollback | namespace, name | Skips restart-only revisions, unlike kubectl rollout undo |
restart | namespace, kind, name | kubectl rollout restart for a Deployment, StatefulSet or DaemonSet |
scale | namespace, kind, name, replicas | 0–100. Never a DaemonSet |
restart_pod | namespace, pod | Controller-managed pods only. Waits until the replacement is Ready |
expand_volume | namespace, pvc, size | The StorageClass must allow it; larger, and at most 4× the current size. Fails if the volume didn't actually grow |
locate | address | Read-only. The pod, workload and volume behind a URL, host:port, pod IP, or Service or pod DNS name |
volume_status | namespace, pod or pvc | Read-only. Capacity, StorageClass, whether it can grow |
logs | namespace, pod, previous | Read-only. previous=true reads the crashed run |
Every change runs first with dryRun=All: the API server and your admission webhooks validate it and nothing changes.
3AM refuses namespaces outside its list, and the Role would refuse them anyway.
Troubleshooting
the token is invalid or expired
Re-read the token from the 3am-connector-token Secret (step 2). Tokens from kubectl create token expire.
the API server's certificate is not trusted: set ca_file
Set CA certificate to k8s-ca.pem from step 2.
the token cannot: list pods in <namespace>
The Role isn't applied in that namespace. Run the loop from step 1 for it.
An action fails with the 3AM Role does not allow this
That namespace's Role is missing a rule, or you removed it on purpose. Re-apply 3am-role.yaml if you didn't.
A failing pod opens no incident
Its namespace isn't in Namespaces.
Certification
Live, on Kubernetes 1.34 with exactly this RBAC and only 3AM's token (2026-10-04):
- Connector, 32 checks. Signals, workload resolution and health reporting.
- End to end, 30 checks. Two broken releases (a config panic, a missing image) were rolled back and verified healthy. A release whose database was down was left alone. An OOM and a missing ConfigMap got advice, and nothing ran.
- Fixes for other connectors, 54 checks. With 3AM inside the cluster, hung components were each located, restarted and verified: vmagent, one vmstorage pod, Alertmanager, Prometheus and two exporters. A volume-growth request on a driver that can't grow volumes was reported as failed, not as done.