🎉 DevOps Interview Prep Bundle is live — 1000+ Q&A across 20 topicsGet it →
All Articles

Build a Kubernetes Resource Optimizer with Claude API and Python

Build an AI tool using Claude API that analyzes your Kubernetes pod resource requests and limits, identifies over-provisioned workloads, and generates right-sized recommendations — saving 20-40% on cloud costs.

Shubham5 min read
Share:Tweet

Over-provisioned Kubernetes resources are the biggest hidden cloud cost. Most teams set requests: cpu: 1000m and never revisit it. This tool uses Claude API to analyze your actual usage and generate specific rightsizing recommendations.

What We're Building

A Python script that:

  1. Pulls resource requests/limits from all Deployments
  2. Fetches actual CPU/memory usage from Metrics Server
  3. Sends the data to Claude API for analysis
  4. Outputs a prioritized rightsizing report

Prerequisites

bash
pip install anthropic kubernetes tabulate
# Metrics Server must be installed in your cluster
kubectl top pods -A  # Test that metrics work

The Script

python
import anthropic
import json
import subprocess
from kubernetes import client, config
from tabulate import tabulate
 
 
def get_cluster_resources() -> list[dict]:
    """Pull all Deployment resource specs from Kubernetes API."""
    config.load_kube_config()
    apps_v1 = client.AppsV1Api()
    core_v1 = client.CoreV1Api()
 
    workloads = []
 
    deployments = apps_v1.list_deployment_for_all_namespaces()
    for dep in deployments.items:
        namespace = dep.metadata.namespace
        name = dep.metadata.name
        replicas = dep.spec.replicas or 1
 
        for container in dep.spec.template.spec.containers:
            resources = container.resources or client.V1ResourceRequirements()
            req = resources.requests or {}
            lim = resources.limits or {}
 
            workloads.append({
                "name": f"{namespace}/{name}/{container.name}",
                "replicas": replicas,
                "cpu_request": req.get("cpu", "not set"),
                "cpu_limit": lim.get("cpu", "not set"),
                "memory_request": req.get("memory", "not set"),
                "memory_limit": lim.get("memory", "not set"),
            })
 
    return workloads
 
 
def get_actual_usage() -> dict[str, dict]:
    """Get actual CPU/memory usage from kubectl top."""
    usage = {}
    try:
        result = subprocess.run(
            ["kubectl", "top", "pods", "-A", "--no-headers"],
            capture_output=True, text=True, timeout=30
        )
        for line in result.stdout.strip().split("\n"):
            if not line:
                continue
            parts = line.split()
            if len(parts) >= 4:
                namespace, pod_name = parts[0], parts[1]
                cpu, memory = parts[2], parts[3]
                key = f"{namespace}/{pod_name}"
                usage[key] = {"cpu": cpu, "memory": memory}
    except Exception as e:
        print(f"Could not get metrics: {e}")
 
    return usage
 
 
def analyze_with_claude(workloads: list[dict], usage: dict) -> str:
    """Send resource data to Claude API for rightsizing analysis."""
    cl = anthropic.Anthropic()
 
    workload_json = json.dumps(workloads[:50], indent=2)  # Limit to 50 for token budget
    usage_json = json.dumps(dict(list(usage.items())[:50]), indent=2)
 
    prompt = f"""You are a Kubernetes FinOps expert. Analyze these workload resource configurations and actual usage data.
 
## Declared Resources (requests/limits)
{workload_json}
 
## Actual Usage (from kubectl top pods)
{usage_json}
 
Provide:
1. Top 10 most over-provisioned workloads (highest waste)
2. Specific recommended values for cpu_request, cpu_limit, memory_request, memory_limit for each
3. Estimated monthly savings if on AWS (assume $0.048/vCPU-hour, $0.006/GB-hour)
4. Workloads with NO resource limits set (risk flag)
5. One-sentence executive summary
 
Format as JSON:
{{
  "over_provisioned": [
    {{
      "workload": "namespace/name/container",
      "current_cpu_request": "...",
      "recommended_cpu_request": "...",
      "current_memory_request": "...",
      "recommended_memory_request": "...",
      "monthly_savings_usd": X,
      "reason": "..."
    }}
  ],
  "no_limits_set": ["list of workloads"],
  "total_monthly_savings_usd": X,
  "summary": "..."
}}"""
 
    response = cl.messages.create(
        model="claude-sonnet-5",
        max_tokens=3000,
        messages=[{"role": "user", "content": prompt}]
    )
 
    return response.content[0].text
 
 
def parse_and_display(analysis_text: str):
    """Parse Claude's response and display as tables."""
    text = analysis_text.strip()
    if "```" in text:
        text = text.split("```")[1]
        if text.startswith("json"):
            text = text[4:]
 
    try:
        data = json.loads(text)
 
        print("\n" + "=" * 70)
        print("KUBERNETES RESOURCE OPTIMIZATION REPORT")
        print("=" * 70)
 
        print(f"\n{data.get('summary', '')}")
        print(f"\nTotal Potential Monthly Savings: ${data.get('total_monthly_savings_usd', 0):,.2f}")
 
        over = data.get("over_provisioned", [])
        if over:
            print("\n\nTOP OVER-PROVISIONED WORKLOADS")
            print("-" * 70)
            table_data = []
            for item in over:
                table_data.append([
                    item["workload"][:35],
                    item.get("current_cpu_request", "?"),
                    item.get("recommended_cpu_request", "?"),
                    item.get("current_memory_request", "?"),
                    item.get("recommended_memory_request", "?"),
                    f"${item.get('monthly_savings_usd', 0):.0f}/mo",
                ])
            print(tabulate(
                table_data,
                headers=["Workload", "CPU Req Now", "CPU Req Rec", "Mem Req Now", "Mem Req Rec", "Savings"],
                tablefmt="grid"
            ))
 
        no_limits = data.get("no_limits_set", [])
        if no_limits:
            print(f"\n\nWORKLOADS WITHOUT RESOURCE LIMITS ({len(no_limits)} total)")
            print("These can cause node OOM and cluster instability:")
            for w in no_limits[:10]:
                print(f"  - {w}")
 
    except json.JSONDecodeError:
        print("\nRaw Claude Analysis:")
        print(analysis_text)
 
 
def generate_patch_commands(analysis_text: str):
    """Generate kubectl patch commands from recommendations."""
    text = analysis_text.strip()
    if "```" in text:
        text = text.split("```")[1]
        if text.startswith("json"):
            text = text[4:]
 
    try:
        data = json.loads(text)
        print("\n\nKUBECTL PATCH COMMANDS (review before applying)")
        print("-" * 70)
 
        for item in data.get("over_provisioned", [])[:5]:
            parts = item["workload"].split("/")
            if len(parts) < 3:
                continue
            namespace, deployment, container = parts[0], parts[1], parts[2]
 
            cpu_req = item.get("recommended_cpu_request", "")
            mem_req = item.get("recommended_memory_request", "")
 
            if cpu_req and mem_req:
                print(f"""
# {deployment} in {namespace}
kubectl patch deployment {deployment} -n {namespace} --type='json' -p='[
  {{"op": "replace", "path": "/spec/template/spec/containers/0/resources/requests/cpu", "value": "{cpu_req}"}},
  {{"op": "replace", "path": "/spec/template/spec/containers/0/resources/requests/memory", "value": "{mem_req}"}}
]'""")
 
    except Exception:
        pass
 
 
if __name__ == "__main__":
    print("Collecting Kubernetes resource data...")
    workloads = get_cluster_resources()
    print(f"Found {len(workloads)} containers across all namespaces")
 
    print("Collecting actual usage metrics...")
    usage = get_actual_usage()
    print(f"Got metrics for {len(usage)} pods")
 
    print("Analyzing with Claude API...")
    analysis = analyze_with_claude(workloads, usage)
 
    parse_and_display(analysis)
    generate_patch_commands(analysis)

Run It

bash
# Make sure kubectl context is set to your target cluster
kubectl config current-context
 
python k8s_optimizer.py

Example Output

KUBERNETES RESOURCE OPTIMIZATION REPORT
======================================================
20 workloads are significantly over-provisioned. Rightsizing the top 10 
saves $1,847/month ($22,164 annually) with minimal risk.

Total Potential Monthly Savings: $1,847.00

TOP OVER-PROVISIONED WORKLOADS
+---------------------------------+----------+----------+----------+----------+----------+
| Workload                        | CPU Now  | CPU Rec  | Mem Now  | Mem Rec  | Savings  |
+=================================+==========+==========+==========+==========+==========+
| production/api-server/api       | 2000m    | 500m     | 4Gi      | 1Gi      | $312/mo  |
| staging/worker/worker           | 1000m    | 200m     | 2Gi      | 512Mi    | $187/mo  |
| production/redis/redis          | 500m     | 100m     | 1Gi      | 256Mi    | $143/mo  |
+---------------------------------+----------+----------+----------+----------+----------+

WORKLOADS WITHOUT RESOURCE LIMITS (8 total)
  - default/test-deployment/app
  - kube-system/metrics-server/metrics-server

Productionizing This

For ongoing optimization instead of a one-time script:

  1. Run as a CronJob in Kubernetes — weekly report via Slack webhook
  2. VPA integration — use Vertical Pod Autoscaler to auto-apply recommendations
  3. Goldilocks — open-source tool that wraps VPA recommendations in a web UI

The Claude API layer adds reasoning that pure VPA cannot — it explains why something is over-provisioned, catches configurations that would cause OOM under load spikes, and generates human-readable summaries for engineering managers.


More Kubernetes FinOps? Check out our AWS EKS cost optimization guide and Karpenter vs Cluster Autoscaler comparison.

🔧

Today I Fixed

Short real fixes from production — posted daily

Browse fixes
Newsletter

Stay ahead of the curve

Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.

Related Articles

Comments