A natural language Kubernetes cluster assistant and autonomous monitoring agent running on bare-metal. Ask questions about your cluster in plain English and get real-time answers powered by Claude (cloud, fast) or Ollama (local, private).
In addition to the chat interface, Cluster AI runs a background AI agent that watches your cluster 24/7 and sends a Telegram alert when something goes wrong β without you having to ask.
A background scheduler (APScheduler) runs inside the FastAPI pod every 5 minutes:
- Queries the Kubernetes API, cert-manager CRDs, Longhorn CRDs, ArgoCD CRDs, Vault health endpoint, and external HTTP endpoints
- Detects issues across all layers β compute, storage, networking, secrets, certificates, GitOps
- If new issues are found β sends data to Claude
- Claude generates a concise runbook with actionable
kubectlcommands - Delivers a Telegram alert to your phone instantly
π¨ Cluster Alert β catdevops.net
β CrashLoopBackOff: cluster-ai/cluster-ai-api-xxx (restarts: 15)
β° CertExpiringSoon: fleet-track/fleet-track-tls (expires in 6 days)
π΄ LonghornVolumeDegraded: longhorn-system/pvc-abc123 (robustness=degraded)
π€ Claude says:
Most critical: cluster-ai-api is crash looping. Check logs with:
kubectl logs -n cluster-ai deployment/cluster-ai-api --tail=50
kubectl describe pod -n cluster-ai -l app=cluster-ai-api
Likely cause: misconfigured environment variable or missing secret.
β Cluster AI Monitor
Compute:
- Pods in
CrashLoopBackOffβ alerted immediately - Delta-based restart tracking β only alerts on new restarts in the last 5 minutes, ignores historical counts accumulated over cluster lifetime
- Deployments with 0 ready replicas
- Nodes in
NotReady,DiskPressure, orMemoryPressurestate
Storage:
- Longhorn volumes in
degradedorfaultedstate
Certificates:
- cert-manager
Certificateresources expiring within 14 days - cert-manager certificates not in
Readystate (renewal failed)
Secrets:
- Vault sealed β catches silent secret failures before apps break
GitOps:
- ArgoCD applications in
Degradedhealth state - ArgoCD applications
OutOfSync
End-to-end availability:
- HTTP probes to all public endpoints via Cloudflare Tunnel β catches failures Kubernetes itself never sees (tunnel down, Envoy misconfiguration, SSL errors, app returning 5xx)
- Delta-based restart tracking β historical restarts never trigger alerts
_alertedset tracks active alerts β one message per issue, not one every 5 minutes- Alert state clears automatically when the cluster fully recovers
- Create a Telegram bot via
@BotFather - Store
TELEGRAM_BOT_TOKENandTELEGRAM_CHAT_IDin Vault - Add to ExternalSecret β secrets are injected automatically on deploy
- The scheduler starts automatically when the FastAPI pod starts
In addition to receiving alerts, you can query your cluster directly from Telegram β no SSH, no terminal, no laptop needed.
A polling loop runs every 3 seconds inside the FastAPI pod, listening for commands from your configured TELEGRAM_CHAT_ID. All commands are read-only β no write access is exposed.
| Command | Description |
|---|---|
/get nodes |
List all nodes with status, roles, CPU, memory |
/get ns |
List all namespaces |
/get pods <namespace> |
List pods in a namespace with status and restart count |
/get pods all |
List all pods across all namespaces |
/get deployments <namespace> |
List deployments with ready/desired replica count |
/get deployments all |
List all deployments across all namespaces |
/get events <namespace> |
Show recent warning events in a namespace |
/logs <namespace> <pod> |
Fetch last 30 lines of pod logs |
/status |
Full cluster summary generated by Claude |
/alerts |
Show currently active alerts |
/help |
Show command reference |
you: /get pods cluster-ai
bot: π¦ Pods β cluster-ai
β
cluster-ai-api-9bd86d7b6-smt6c 1/1 Running restarts=0
β
cluster-ai-frontend-7487fb4d-8h5jc 1/1 Running restarts=0
β
ollama-5585b55579-js8xg 1/1 Running restarts=0
you: /logs cluster-ai cluster-ai-api
bot: π Logs β cluster-ai-api-9bd86d7b6-smt6c
INFO: Cluster monitor scheduler started
INFO: Application startup complete.
INFO: Running cluster health check...
INFO: Cluster healthy β no issues detected
...
you: /status
bot: π€ Cluster Status
β
All Systems Green
- Nodes: 5/5 ready and operational
- Pods: 114/114 healthy and running
- Deployments: 42/42 up and serving traffic
- Commands are only accepted from the configured
TELEGRAM_CHAT_IDβ no other user can query your cluster - All K8s calls are read-only via
cluster-ai-saServiceAccount - No
kubectl exec,delete,apply, or any mutating operations are exposed
Ask questions about your cluster in plain English:
- "How many pods are running?"
- "What is the status of all nodes?"
- "Are there any pods in CrashLoopBackOff?"
- "Is ArgoCD healthy?"
- "Show me all deployments in the fleet-track namespace"
Answers are based on live cluster data fetched on every request β not training data.
Browser (React UI)
β
βββ POST /api/ask { question, provider }
β
FastAPI Backend (cluster-ai-api)
β
βββ Kubernetes API (via ServiceAccount) β live cluster data
β
βββ [provider=ollama] β Ollama pod (http://ollama:11434)
β βββ llama3.2:1b (local, private)
β
βββ [provider=claude] β api.anthropic.com
βββ claude-haiku-4-5-20251001
Background Monitor (APScheduler β every 5 minutes)
β
βββ Kubernetes API β pods, nodes, deployments
βββ cert-manager CRDs β certificate expiry
βββ Longhorn CRDs β volume health
βββ ArgoCD CRDs β application sync/health
βββ Vault /v1/sys/health β sealed check
βββ External HTTP probes β end-to-end availability
βββ api.anthropic.com β Claude generates runbook
βββ api.telegram.org β alert sent to phone
Telegram Bot (APScheduler β every 3 seconds)
β
βββ getUpdates (long poll) β receives commands
βββ Kubernetes API β executes read-only queries
βββ api.anthropic.com β /status summary via Claude
βββ api.telegram.org β sends response
GitOps: ArgoCD auto-syncs all manifests from this repo
Secrets: HashiCorp Vault β ESO β Kubernetes Secret
| Component | Technology |
|---|---|
| Frontend | React + Vite |
| Backend | FastAPI (Python) |
| Monitoring | APScheduler (5-min interval) |
| Telegram Bot | APScheduler (3-sec poll interval) |
| Alerts | Telegram Bot API |
| AI β Cloud | Anthropic Claude (claude-haiku-4-5-20251001) |
| AI β Local | Ollama (llama3.2:1b) |
| Container Runtime | containerd |
| Orchestration | Kubernetes (bare-metal, 5 nodes) |
| GitOps | ArgoCD (auto-sync) |
| Secrets | HashiCorp Vault + External Secrets Operator + AWS KMS auto-unseal |
| Ingress | Envoy Gateway + Cloudflare Tunnel (zero open ports) |
| Storage | Longhorn (distributed block storage) |
| Registry | GitHub Container Registry (ghcr.io) |
cluster-ai/
βββ backend/
β βββ main.py # FastAPI app (chat interface, Ollama + Claude routing)
β βββ monitor.py # Background monitor (APScheduler + all health checks + Telegram alerts)
β βββ telegram_bot.py # Telegram bot command interface (polling + command handlers)
β βββ requirements.txt
β βββ Dockerfile
βββ frontend/
β βββ src/
β β βββ App.jsx # React UI with Ollama/Claude provider toggle
β β βββ main.jsx
β βββ nginx.conf # Reverse proxy to FastAPI
β βββ Dockerfile
β βββ package.json
βββ k8s/
βββ namespace.yaml
βββ fastapi.yaml # cluster-ai-api Deployment
βββ frontend.yaml # cluster-ai-frontend Deployment
βββ ollama.yaml # Ollama Deployment (node04)
βββ ollama-pull-job.yaml # Job to pull llama3.2:1b on deploy
βββ rbac.yaml # Read-only ClusterRole (pods, nodes, certs, volumes, apps)
βββ services.yaml
βββ httproute.yaml # Envoy Gateway HTTPRoute
βββ argocd-app.yaml
- Read-only RBAC β
cluster-ai-saServiceAccount hasget/list/watchonly. No write access, no secrets access. - RBAC scope β covers core resources, cert-manager CRDs, Longhorn CRDs, ArgoCD CRDs, metrics. Explicitly no access to
secretsresource. - Blocked queries β requests containing
secret,token,password,credential,kubeconfigare rejected before reaching the LLM. - Telegram auth β bot only responds to the configured
TELEGRAM_CHAT_ID. All other senders are silently ignored. - API key management β
ANTHROPIC_API_KEY,TELEGRAM_BOT_TOKENstored in HashiCorp Vault, synced to Kubernetes Secret via External Secrets Operator. Never hardcoded. - AWS KMS β Vault auto-unseal uses AWS KMS. Master key never lives on the cluster.
- Zero open ports β all ingress via Cloudflare Tunnel. No NodePort, no LoadBalancer exposed to internet.
- Model:
llama3.2:1b - Runs on node04 (8 CPU, 32GB RAM)
- GPU: NVIDIA GTX 780M (CUDA 3.0 β too old for GPU inference, runs CPU-only)
- Response time: ~60 seconds
- Cost: Free β zero data leaves the cluster
- Model:
claude-haiku-4-5-20251001 - Runs on Anthropic's infrastructure
- Response time: ~2-3 seconds
- Cost: ~$0.00025 per query (~20,000 queries per $5)
# Store secrets in Vault
kubectl exec -it -n vault vault-0 -- vault kv put secret/cluster-ai/config \
OLLAMA_URL="http://ollama.cluster-ai.svc.cluster.local:11434" \
ANTHROPIC_API_KEY="sk-ant-..." \
TELEGRAM_BOT_TOKEN="..." \
TELEGRAM_CHAT_ID="..."
# Force immediate ESO sync
kubectl annotate externalsecret cluster-ai-secret -n cluster-ai \
force-sync=$(date +%s) --overwriteGitHub Actions builds and pushes Docker images to ghcr.io/catdevops1/cluster-ai-*:latest on every push to main. ArgoCD detects the Git change and syncs the manifests. Manual rollout required after image rebuild (using latest tag):
kubectl rollout restart deployment/cluster-ai-api -n cluster-ai
kubectl rollout restart deployment/cluster-ai-frontend -n cluster-aiKnown limitation: ArgoCD does not auto-rollout on
latesttag changes. Planned improvement: switch to commit SHA tags for fully automated rollouts.
# Port-forward to access UI locally
kubectl port-forward -n cluster-ai service/cluster-ai-frontend 8080:80 --address=0.0.0.0
# Access at
http://<node-ip>:8080Most Kubernetes dashboards show you data β this one contacts you when something breaks and tells you what to do about it.
The core engineering problem: LLMs don't have access to your cluster. The solution is to fetch live cluster state on every request and inject it as context into the prompt β so every answer is based on what's actually running right now, not training data.
Why delta-based restart tracking matters: A cluster running for 200+ days accumulates hundreds of cumulative restarts across system pods (metallb speakers, longhorn managers, kube-scheduler). A static threshold like "alert if restarts > 50" produces constant noise. Delta tracking β alert only if a pod gained 3+ new restarts in the last 5 minutes β eliminates all historical noise and only fires on real incidents.
Why the Telegram bot matters: Alerts tell you something is wrong. The bot lets you diagnose it from your phone without opening a laptop. /logs, /get pods, and /status cover 90% of first-response triage β the full incident loop from detection to diagnosis happens in Telegram.
The dual-provider architecture came from a real hardware constraint β the GPU in node04 has CUDA compute 3.0, too old for any modern inference framework. Ollama runs CPU-only at ~60 seconds per query. That pushed the Claude integration. Sometimes hardware limitations lead to better architecture decisions.
Why asyncio.to_thread() matters: The Kubernetes Python client is synchronous. Running sync K8s calls directly inside an AsyncIOScheduler job blocks the entire asyncio event loop β starving FastAPI health probes and causing the pod to be marked unhealthy by kubelet. Wrapping all sync K8s calls in asyncio.to_thread() moves them to a thread pool, keeping the event loop free.
The app uses the standard Kubernetes Python client β works on any conformant cluster. The monitoring and alerting system is cluster-distribution agnostic.
| Component | Bare-Metal (this repo) | EKS | GKE | AKS |
|---|---|---|---|---|
| Secrets | Vault + ESO + AWS KMS | AWS Secrets Manager + ESO | GCP Secret Manager | Azure Key Vault |
| Auth | K8s ServiceAccount | IRSA | Workload Identity | Managed Identity |
| Storage | Longhorn | EBS CSI Driver | Persistent Disk | Azure Disk |
| Ingress | Envoy Gateway + Cloudflare Tunnel | AWS ALB | GKE Ingress | AGIC |
| Load Balancer | MetalLB | AWS NLB/ALB | GCP LB | Azure LB |
- Replace
latestimage tags with commit SHA tags for fully automated rollouts - Add rate limiting (
slowapi) to FastAPI endpoints - Run 2+ API replicas with distributed scheduler lock (Redis) to avoid duplicate alerts
- Use IRSA instead of static credentials on EKS
- Set Anthropic monthly spend cap
- Add Telegram message truncation (4096 char hard limit)
- Persist
_restart_snapshotto ConfigMap for monitor resilience across pod restarts - Add
pods/logto ClusterRole for cross-namespace/logsbot command support
- homelab-k8s-config-pub β GitOps manifests (descheduler, external secrets, gateway, longhorn, vault)
- vault-config-pub β HashiCorp Vault + External Secrets Operator + AWS KMS auto-unseal





