Sunday, 30 August 2026

Building a Kubernetes SRE Incident Investigation Agent with Python & Agentic AI

Short Description

Build a read-only AI-powered Kubernetes SRE agent that investigates pod incidents using status, logs, previous logs, and Kubernetes events. The agent dynamically collects evidence, forms and validates hypotheses, and generates a structured RCA with root-cause confidence—without making unsupported assumptions or performing remediation.





(.venv) PS C:\Users\Raj Kumar Gupta\Desktop\Raj\minikube\k8s-day5-sre-agent> kubectl get pod

NAME                          READY   STATUS             RESTARTS        AGE

app-crash-5cf8866df5-q6k4m    0/1     CrashLoopBackOff   8 (40s ago)     17m

broken-pod                    0/1     ImagePullBackOff   0               258d

payment-api-987c667b8-xrqhf   0/1     CrashLoopBackOff   45 (3m1s ago)   20h

(.venv) PS C:\Users\Raj Kumar Gupta\Desktop\Raj\minikube\k8s-day5-sre-agent> python -m agents.sre_agent

Enter namespace [default]: default

Enter pod name: app-crash-5cf8866df5-q6k4m


======================================================================

KUBERNETES SRE INVESTIGATION

======================================================================

Namespace: default

Pod: app-crash-5cf8866df5-q6k4m


======================================================================

AGENT ITERATION 1

======================================================================


======================================================================

TOOL CALL

======================================================================

Tool: get_pod_status

Arguments: {'namespace': 'default', 'pod_name': 'app-crash-5cf8866df5-q6k4m'}

[K8S] Using local kubeconfig


======================================================================

AGENT ITERATION 2

======================================================================


======================================================================

TOOL CALL

======================================================================

Tool: get_pod_previous_logs

Arguments: {'namespace': 'default', 'pod_name': 'app-crash-5cf8866df5-q6k4m'}

[K8S] Using local kubeconfig


======================================================================

TOOL CALL

======================================================================

Tool: get_pod_events

Arguments: {'namespace': 'default', 'pod_name': 'app-crash-5cf8866df5-q6k4m'}

[K8S] Using local kubeconfig


======================================================================

AGENT ITERATION 3

======================================================================


======================================================================

TOOL CALL

======================================================================

Tool: get_pod_status

Arguments: {'namespace': 'default', 'pod_name': 'app-crash-5cf8866df5-q6k4m'}

[K8S] Using local kubeconfig


======================================================================

AGENT ITERATION 4

======================================================================


======================================================================

FINAL SRE REPORT

======================================================================

SYMPTOM

- Pod default/app-crash-5cf8866df5-q6k4m is failing with CrashLoopBackOff; container is not ready and has multiple restarts.


EVIDENCE

- Pod status (facts):

  - phase: Running

  - container "busybox": ready=false, restart_count=8, state=waiting, reason=CrashLoopBackOff, message="back-off 5m0s restarting failed container=busybox pod=app-crash-5cf8866df5-q6k4m_default(69fb3e78-994f-42cd-98f2-981bbfa58147)"

  - node: minikube, pod_ip: 10.244.0.14

- Pod previous container logs (facts):

  - "Application started"

  - "ERROR database connection refused"

- Pod events (facts):

  - Scheduled on minikube successfully.

  - Image pulls for "busybox" succeeded repeatedly.

  - Warning BackOff: "Back-off restarting failed container busybox..." count=70 between 05:50:54 and 06:06:01 UTC.

  - No warnings/errors about image pull failures, scheduling failures, or probe failures in the events listed.


HYPOTHESIS

- The application starts, immediately attempts a database connection, receives "connection refused," exits, and the container restarts. Repeated exits lead to CrashLoopBackOff.

- The underlying reason for "connection refused" could be incorrect DB host/port, missing/incorrect service/endpoints, network policy, credentials, or the DB process not accepting connections. This is not confirmed.


VALIDATION

- Correlation:

  - Pod status shows CrashLoopBackOff with 8 restarts (FACT).

  - Events show repeated back-off restarts and successful image pulls/scheduling (FACT), indicating the failure occurs during/after container start, not during scheduling or image pulling.

  - Previous logs show the last messages before termination include "ERROR database connection refused" (FACT), consistent with the container exiting shortly after startup.

- What is not proven:

  - We do not have the container’s last termination exit code or reason (e.g., Error vs OOMKilled), so we cannot assert the exact process exit details (FACT). However, there is no evidence of OOM or node pressure in events, and the log content points to an application-level exit after a DB connection failure (INFERENCE).


ROOT CAUSE

- Immediate cause confirmed: The container repeatedly exits after encountering "ERROR database connection refused" at startup, causing CrashLoopBackOff.

- Underlying cause of the database connection refusal (misconfiguration, network, service/endpoints, DB availability, credentials) is not established with the current evidence.

- Root cause not confirmed.


CONFIDENCE

- Medium. Logs and events clearly align with application exit due to a DB connection refusal, but lack of last termination details and no verification of the database endpoint/config prevents confirming the underlying root cause.


NEXT INVESTIGATION

- Retrieve container last termination details (exitCode, reason, signal) to confirm process exit conditions.

- Verify DB connectivity configuration used by this pod:

  - Environment variables, config maps, and secrets for DB host, port, database name, and credentials.

  - Container command/entrypoint to see if it exits on first connection failure.

- Validate the Kubernetes Service backing the database:

  - Confirm the Service exists, DNS name is correct, and port matches what the app uses.

  - Check Endpoints for the Service to ensure there are ready targets.

- Network path checks:

  - Confirm no NetworkPolicy or firewall rules block traffic from this pod’s namespace to the DB.

  - If possible, run a temporary debug pod in the same namespace to test TCP connectivity to the DB host:port.

- Inspect the database side:

  - Ensure the DB process is listening on the expected interface/port and accepts connections from the pod’s network.

  - Review DB logs for connection attempts/refusals at the corresponding timestamps.

- If liveness/readiness probes are configured for this pod, confirm they aren’t killing the container; correlate probe failures if any appear in describe/events.

(.venv) PS C:\Users\Raj Kumar Gupta\Desktop\Raj\minikube\k8s-day5-sre-agent> 

================================================

As a Kubernetes SRE, troubleshooting a production incident usually involves collecting information from multiple sources:

  • Pod status

  • Container restart count

  • Exit codes

  • Application logs

  • Previous container logs

  • Kubernetes events

  • Node information

  • Scheduling information

  • Application symptoms

The challenge is not simply collecting this information.

The real challenge is deciding:

What should I investigate next based on the evidence I already have?

This is where Agentic AI becomes interesting.

Instead of creating a Python script that always executes:

get pod status
    ↓
get logs
    ↓
get events

we can build an AI agent that dynamically decides what information it needs next.


The investigation flow is:

Incident
   ↓
Observe
   ↓
Collect Evidence
   ↓
Reason
   ↓
Create Hypothesis
   ↓
Validate Hypothesis
   ↓
Collect More Evidence if Required
   ↓
Confirm Root Cause
   ↓
Generate RCA

This follows the structured investigation approach described : Incident → Evidence → Hypothesis → Validation → Root Cause → Confidence → Next Investigation.


1. What Are We Building?

We are going to build a Python-based Kubernetes SRE Agent.

The user provides:

Namespace
Pod Name

The AI agent then investigates the pod using Kubernetes tools.

The architecture is:

                    USER
                      |
                      v
              +---------------+
              |   SRE AGENT   |
              |      LLM      |
              +-------+-------+
                      |
              Dynamic Planning
                      |
        +-------------+-------------+
        |             |             |
        v             v             v
   Pod Status       Logs          Events
        |             |             |
        +-------------+-------------+
                      |
                      v
                  Evidence
                      |
                      v
                 Hypothesis
                      |
                      v
                  Validation
                      |
                      v
             Root Cause Confirmed?
                /             \
              NO               YES
              |                 |
              v                 v
        More Investigation      RCA
              |
              v
        Additional Tools

The key difference from a traditional script is:

The LLM decides which tool to call based on the current evidence.


2. What Will Our Agent Be Able to Investigate?

Our first version will support four Kubernetes investigation tools:

1. get_pod_status()
2. get_pod_logs()
3. get_pod_previous_logs()
4. get_pod_events()

These allow the agent to investigate common conditions such as:

CrashLoopBackOff
ImagePullBackOff
Pending
Container restart
Application startup failure
Scheduling failure



3. Why FACT, HYPOTHESIS and ROOT CAUSE Must Be Different

This is one of the most important concepts in an SRE AI agent.

Suppose the application log says:

ERROR database connection refused

A weak AI agent might immediately report:

Root Cause:
Database is down.

That is not necessarily correct.

The actual evidence only proves:

FACT:
Application received "connection refused".

We can infer:

INFERENCE:
Application could not establish a database connection.

We can form:

HYPOTHESIS:
There may be a database connectivity problem.

But we cannot yet claim:

ROOT CAUSE:
Database is down.

We need more evidence.

Potential causes could include:

Database server
Database Service
Service endpoints
DNS
NetworkPolicy
Firewall
Credentials
Wrong hostname
Wrong port
Application configuration

Therefore:

FACT != HYPOTHESIS

HYPOTHESIS != ROOT CAUSE



4. Project Directory Structure

Create the following project:

k8s-day5-sre-agent/
│
├── .venv/
│
├── agents/
│   ├── __init__.py
│   └── sre_agent.py
│
├── tools/
│   ├── __init__.py
│   └── agent_tools.py
│
├── models/
│   ├── __init__.py
│   └── incident.py
│
├── scenarios/
│   ├── app-pending.yaml
│   ├── create_incidents.ps1
│   └── cleanup.ps1
│
├── tests/
│   ├── __init__.py
│   └── test_tools.py
│
├── .env
├── .gitignore
├── requirements.txt
└── README.md

The important components are:

agents/
    AI agent

tools/
    Kubernetes tools

models/
    Structured incident model

scenarios/
    Test incidents

tests/
    Tool testing

5. Create the Project

On Windows PowerShell:

mkdir k8s-day5-sre-agent
cd k8s-day5-sre-agent

Create the virtual environment:

python -m venv .venv

Activate it:

.\.venv\Scripts\Activate.ps1

Verify:

python --version

You should see:

Python 3.x.x

6. Install Python Dependencies

Create:

requirements.txt

Add:

openai
kubernetes
python-dotenv
pydantic

Install:

pip install -r requirements.txt

Verify:

pip list

7. Configure the OpenAI API Key

Create:

.env

Add:

OPENAI_API_KEY=YOUR_API_KEY_HERE

For example:

OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxx

Do not commit .env to Git.


8. Create .gitignore

Create:

.gitignore

Add:

.venv/
.env
__pycache__/
*.pyc

This prevents credentials and Python-generated files from being committed.


9. Kubernetes Tool Layer

The AI agent itself should not directly implement Kubernetes API calls everywhere.

Instead, we create a dedicated tool layer:

tools/agent_tools.py

This provides a clean separation:

AI Agent
   |
   v
Tool Layer
   |
   v
Kubernetes API

10. Complete tools/agent_tools.py

Create:

tools/agent_tools.py

Use the following complete code:

from kubernetes import client, config
from kubernetes.config.config_exception import ConfigException


def load_kubernetes():
    """
    Load Kubernetes configuration.

    First try in-cluster configuration.
    If that fails, use the local kubeconfig.
    """

    try:
        config.load_incluster_config()

        print("[K8S] Using in-cluster configuration")

    except ConfigException:

        config.load_kube_config()

        print("[K8S] Using local kubeconfig")


def get_pod_status(
    namespace: str,
    pod_name: str
):
    """
    Get Kubernetes pod status.

    Returns:
        - pod phase
        - node
        - pod IP
        - container state
        - restart count
        - exit code
        - termination reason
    """

    load_kubernetes()

    v1 = client.CoreV1Api()

    pod = v1.read_namespaced_pod(
        name=pod_name,
        namespace=namespace
    )

    containers = []

    if pod.status.container_statuses:

        for container in pod.status.container_statuses:

            state = {}

            if container.state:

                if container.state.waiting:

                    state = {
                        "state": "waiting",
                        "reason": container.state.waiting.reason,
                        "message": container.state.waiting.message
                    }

                elif container.state.running:

                    state = {
                        "state": "running",
                        "started_at": str(
                            container.state.running.started_at
                        )
                    }

                elif container.state.terminated:

                    state = {
                        "state": "terminated",
                        "reason": container.state.terminated.reason,
                        "exit_code": container.state.terminated.exit_code,
                        "signal": container.state.terminated.signal,
                        "message": container.state.terminated.message
                    }

            containers.append(
                {
                    "name": container.name,
                    "ready": container.ready,
                    "restart_count": container.restart_count,
                    "state": state
                }
            )

    return {
        "pod_name": pod.metadata.name,
        "namespace": pod.metadata.namespace,
        "phase": pod.status.phase,
        "node": pod.spec.node_name,
        "pod_ip": pod.status.pod_ip,
        "containers": containers
    }


def get_pod_logs(
    namespace: str,
    pod_name: str,
    container: str = None
):
    """
    Get current logs from a Kubernetes container.
    """

    load_kubernetes()

    v1 = client.CoreV1Api()

    logs = v1.read_namespaced_pod_log(
        name=pod_name,
        namespace=namespace,
        container=container,
        tail_lines=100
    )

    return {
        "pod_name": pod_name,
        "namespace": namespace,
        "log_type": "current",
        "logs": logs
    }


def get_pod_previous_logs(
    namespace: str,
    pod_name: str,
    container: str = None
):
    """
    Get logs from the previous terminated container.

    Very useful for CrashLoopBackOff.
    """

    load_kubernetes()

    v1 = client.CoreV1Api()

    logs = v1.read_namespaced_pod_log(
        name=pod_name,
        namespace=namespace,
        container=container,
        previous=True,
        tail_lines=100
    )

    return {
        "pod_name": pod_name,
        "namespace": namespace,
        "log_type": "previous",
        "logs": logs
    }


def get_pod_events(
    namespace: str,
    pod_name: str
):
    """
    Get Kubernetes events associated with a pod.
    """

    load_kubernetes()

    v1 = client.CoreV1Api()

    events = v1.list_namespaced_event(
        namespace=namespace,
        field_selector=(
            f"involvedObject.name={pod_name}"
        )
    )

    result = []

    for event in events.items:

        result.append(
            {
                "type": event.type,
                "reason": event.reason,
                "message": event.message,
                "count": event.count,
                "first_timestamp": str(
                    event.first_timestamp
                ),
                "last_timestamp": str(
                    event.last_timestamp
                )
            }
        )

    return {
        "pod_name": pod_name,
        "namespace": namespace,
        "events": result
    }

11. Important Troubleshooting Lesson

During implementation, one common mistake is accidentally creating:

tools/agent_tools.py

as an empty file.

For example:

agent_tools.py    0 bytes

Then this command:

python -c "from tools.agent_tools import get_pod_status"

will produce:

ImportError:
cannot import name 'get_pod_status'

The reason is simple:

sre_agent.py
     |
     | imports
     v
agent_tools.py
     |
     X
     |
     No functions

After adding the code above, verify:

Get-Item .\tools\agent_tools.py

The file should have a size greater than zero.


12. Test the Kubernetes Tool Import

Run:

python -c "from tools.agent_tools import get_pod_status; print('TOOLS IMPORT OK')"

Expected:

TOOLS IMPORT OK

Test all four:

python -c "from tools.agent_tools import get_pod_status, get_pod_logs, get_pod_previous_logs, get_pod_events; print('ALL 4 TOOLS IMPORT OK')"

Expected:

ALL 4 TOOLS IMPORT OK

13. Incident Model

Create:

models/incident.py

Code:

from typing import List
from pydantic import BaseModel, Field


class Evidence(BaseModel):

    fact: str

    source: str


class IncidentReport(BaseModel):

    incident: str

    symptom: str

    evidence: List[Evidence] = Field(
        default_factory=list
    )

    hypothesis: str

    validation: List[str] = Field(
        default_factory=list
    )

    root_cause: str

    confidence: str

    next_investigation: List[str] = Field(
        default_factory=list
    )

This allows us to represent the investigation in a structured format.

The intended report structure is:

SYMPTOM
EVIDENCE
HYPOTHESIS
VALIDATION
ROOT CAUSE
CONFIDENCE
NEXT INVESTIGATION

14. Building the AI SRE Agent

Now create:

agents/sre_agent.py

The agent will:

  1. Accept namespace and pod name.

  2. Ask the LLM to investigate.

  3. Allow the LLM to select tools.

  4. Execute the selected Kubernetes tool.

  5. Send the result back to the LLM.

  6. Allow the LLM to select another tool if required.

  7. Continue until sufficient evidence exists.

  8. Generate the final SRE report.


15. Complete agents/sre_agent.py

Use this complete code:

import json
import os

from dotenv import load_dotenv
from openai import OpenAI

from tools.agent_tools import (
    get_pod_status,
    get_pod_logs,
    get_pod_previous_logs,
    get_pod_events,
)


# ============================================================
# ENVIRONMENT
# ============================================================

load_dotenv()

api_key = os.getenv("OPENAI_API_KEY")

if not api_key:

    raise RuntimeError(
        "OPENAI_API_KEY is missing. "
        "Create a .env file in the project root."
    )


# ============================================================
# OPENAI CLIENT
# ============================================================

client = OpenAI(
    api_key=api_key
)


# ============================================================
# SYSTEM PROMPT
# ============================================================

SYSTEM_PROMPT = """
You are a senior Kubernetes SRE investigation agent.

Your job is to investigate Kubernetes incidents
using available READ-ONLY tools.

Investigation rules:

1. Never invent cluster state.

2. Always collect evidence before making conclusions.

3. Clearly distinguish FACT from INFERENCE.

4. Clearly distinguish HYPOTHESIS from CONFIRMED ROOT CAUSE.

5. Never claim a root cause without supporting evidence.

6. Use additional tools when evidence is insufficient.

7. Do not perform remediation.

8. Prefer the smallest number of tools necessary.

9. Correlate pod status, logs and Kubernetes events.

10. If the root cause cannot be proven, explicitly say:

    Root cause not confirmed.


Important:

Exit code 137 does NOT automatically prove OOMKilled.

Database connection refused does NOT automatically prove
that the database is down.

ImagePullBackOff should be investigated using Kubernetes
events and pod status.

Pending pods should be investigated using pod status and
Kubernetes scheduling events.


The final answer must contain:

SYMPTOM
EVIDENCE
HYPOTHESIS
VALIDATION
ROOT CAUSE
CONFIDENCE
NEXT INVESTIGATION
"""


# ============================================================
# TOOL DEFINITIONS
# ============================================================

TOOLS = [

    {
        "type": "function",

        "name": "get_pod_status",

        "description": (
            "Get Kubernetes pod status, container states, "
            "restart counts, exit codes and node information."
        ),

        "parameters": {

            "type": "object",

            "properties": {

                "namespace": {
                    "type": "string"
                },

                "pod_name": {
                    "type": "string"
                }
            },

            "required": [
                "namespace",
                "pod_name"
            ]
        }
    },


    {
        "type": "function",

        "name": "get_pod_logs",

        "description": (
            "Get current logs from a Kubernetes pod."
        ),

        "parameters": {

            "type": "object",

            "properties": {

                "namespace": {
                    "type": "string"
                },

                "pod_name": {
                    "type": "string"
                }
            },

            "required": [
                "namespace",
                "pod_name"
            ]
        }
    },


    {
        "type": "function",

        "name": "get_pod_previous_logs",

        "description": (
            "Get logs from the previous terminated "
            "container instance. Useful for CrashLoopBackOff."
        ),

        "parameters": {

            "type": "object",

            "properties": {

                "namespace": {
                    "type": "string"
                },

                "pod_name": {
                    "type": "string"
                }
            },

            "required": [
                "namespace",
                "pod_name"
            ]
        }
    },


    {
        "type": "function",

        "name": "get_pod_events",

        "description": (
            "Get Kubernetes events associated with a pod. "
            "Useful for scheduling, image pull, startup and "
            "restart failures."
        ),

        "parameters": {

            "type": "object",

            "properties": {

                "namespace": {
                    "type": "string"
                },

                "pod_name": {
                    "type": "string"
                }
            },

            "required": [
                "namespace",
                "pod_name"
            ]
        }
    }
]


# ============================================================
# TOOL EXECUTION
# ============================================================

def execute_tool(name, arguments):

    print()
    print("=" * 70)
    print("TOOL CALL")
    print("=" * 70)

    print("Tool:", name)

    print(
        "Arguments:",
        json.dumps(
            arguments,
            indent=2
        )
    )

    try:

        if name == "get_pod_status":

            return get_pod_status(
                arguments["namespace"],
                arguments["pod_name"]
            )


        elif name == "get_pod_logs":

            return get_pod_logs(
                arguments["namespace"],
                arguments["pod_name"]
            )


        elif name == "get_pod_previous_logs":

            return get_pod_previous_logs(
                arguments["namespace"],
                arguments["pod_name"]
            )


        elif name == "get_pod_events":

            return get_pod_events(
                arguments["namespace"],
                arguments["pod_name"]
            )


        else:

            return {
                "error": f"Unknown tool: {name}"
            }


    except Exception as exc:

        return {
            "error": str(exc)
        }


# ============================================================
# INVESTIGATION ENGINE
# ============================================================

def investigate(
    namespace: str,
    pod_name: str
):

    user_prompt = f"""
Investigate this Kubernetes incident.

Namespace:
{namespace}

Pod:
{pod_name}


Determine why this pod is failing.

Use evidence.

Do not make assumptions.

Do not perform remediation.

Use additional tools if the available evidence
is insufficient.

At the end produce a structured SRE incident report.
"""


    messages = [

        {
            "role": "system",

            "content": SYSTEM_PROMPT
        },

        {
            "role": "user",

            "content": user_prompt
        }
    ]


    max_iterations = 10


    for iteration in range(max_iterations):

        print()
        print("=" * 70)

        print(
            f"AGENT ITERATION {iteration + 1}"
        )

        print("=" * 70)


        response = client.responses.create(

            model="gpt-5",

            input=messages,

            tools=TOOLS
        )


        tool_outputs = []


        for item in response.output:

            if item.type == "function_call":

                name = item.name


                arguments = json.loads(
                    item.arguments
                )


                result = execute_tool(
                    name,
                    arguments
                )


                tool_outputs.append(

                    {
                        "type": "function_call_output",

                        "call_id": item.call_id,

                        "output": json.dumps(
                            result,
                            default=str
                        )
                    }

                )


        # ----------------------------------------------------
        # No more tools required
        # ----------------------------------------------------

        if not tool_outputs:

            return response.output_text


        # ----------------------------------------------------
        # Send assistant tool request back to conversation
        # ----------------------------------------------------

        messages.extend(
            response.output
        )


        # ----------------------------------------------------
        # Send tool results back to LLM
        # ----------------------------------------------------

        messages.extend(
            tool_outputs
        )


    return (
        "Investigation stopped because maximum "
        "agent iterations were reached."
    )


# ============================================================
# MAIN
# ============================================================

def main():

    namespace = input(
        "Enter namespace [default]: "
    ).strip()


    if not namespace:

        namespace = "default"


    pod_name = input(
        "Enter pod name: "
    ).strip()


    if not pod_name:

        print(
            "Pod name is required."
        )

        return


    print()

    print("=" * 70)

    print(
        "KUBERNETES SRE INVESTIGATION"
    )

    print("=" * 70)


    print(
        "Namespace:",
        namespace
    )

    print(
        "Pod:",
        pod_name
    )


    result = investigate(
        namespace,
        pod_name
    )


    print()

    print("=" * 70)

    print(
        "FINAL SRE REPORT"
    )

    print("=" * 70)


    print(result)


if __name__ == "__main__":

    main()

16. Why We Use python -m

Our project uses:

k8s-day5-sre-agent/
│
├── agents/
│   └── sre_agent.py
│
└── tools/
    └── agent_tools.py

Therefore, run:

python -m agents.sre_agent

Do not normally run:

python .\agents\sre_agent.py

The module approach allows Python to correctly find:

from tools.agent_tools import ...

17. Verify the Agent Import

Run:

python -c "import agents.sre_agent; print('AGENT IMPORT OK')"

Expected:

AGENT IMPORT OK

If you get:

ModuleNotFoundError: No module named 'tools'

you are probably executing the file directly instead of using:

python -m agents.sre_agent

18. Verify the API Key

Run:

python -c "from dotenv import load_dotenv; import os; load_dotenv(); print('API KEY STATUS:', 'SET' if os.getenv('OPENAI_API_KEY') else 'NOT SET')"

Expected:

API KEY STATUS: SET

If you get:

API KEY STATUS: NOT SET

check:

k8s-day5-sre-agent/
│
└── .env

and make sure it contains:

OPENAI_API_KEY=YOUR_API_KEY

Do not share your actual API key publicly.


19. Verify Kubernetes

Before running the AI agent:

minikube status

Then:

kubectl get nodes

Expected:

NAME       STATUS   ROLES           AGE
minikube   Ready    control-plane   ...

Then:

kubectl get pods -A

20. Create a CrashLoopBackOff Incident

We need a controlled incident for testing.

Run:

kubectl create deployment app-crash `
  --image=busybox `
  -- /bin/sh -c "echo Application started; echo ERROR database connection refused; exit 1"

Check:

kubectl get pods

After a while:

NAME                          READY   STATUS             RESTARTS
app-crash-xxxxxxxxxx          0/1     CrashLoopBackOff   3

Copy the actual pod name.


21. Manually Investigate the Incident

Before allowing AI to investigate, it is useful for an SRE to understand the raw evidence.

Run:

kubectl get pod YOUR_POD_NAME -o wide

Then:

kubectl describe pod YOUR_POD_NAME

Current logs:

kubectl logs YOUR_POD_NAME

Previous logs:

kubectl logs YOUR_POD_NAME --previous

Events:

kubectl get events --sort-by=.lastTimestamp

You may see:

Application started
ERROR database connection refused

and events such as:

BackOff

22. Understand the Evidence

The correct reasoning is:

FACT
Application logged:
database connection refused

Then:

INFERENCE
The application could not establish a database connection.

Then:

HYPOTHESIS
Database connectivity is a possible cause.

But:

ROOT CAUSE
Database is down

is not yet proven.

This is the difference between an AI chatbot that generates plausible answers and an SRE investigation agent that works from evidence.


23. Run the AI Agent

Run:

python -m agents.sre_agent

You should see:

Enter namespace [default]:

Enter:

default

Then:

Enter pod name:

Enter your actual pod:

app-crash-xxxxxxxxxx

24. What Happens Inside the Agent?

The first iteration may look conceptually like:

AGENT ITERATION 1
        |
        v
LLM asks:
"What is the pod state?"
        |
        v
get_pod_status()

The tool returns:

phase = Running
restart_count = 3
container state = terminated
exit_code = 1

The LLM then reasons:

The container is restarting.
I need logs.

It calls:

get_pod_previous_logs()

The result:

Application started
ERROR database connection refused

The LLM may then call:

get_pod_events()

The result:

BackOff

Now it has multiple pieces of evidence.


25. Dynamic Investigation

The important point is that the Python program does not explicitly contain:

if status == "CrashLoopBackOff":
    get_logs()

if logs contain "database":
    get_events()

Instead:

LLM
 |
 +--> get_pod_status()
 |
 +--> inspect result
 |
 +--> decide next action
 |
 +--> get_pod_previous_logs()
 |
 +--> inspect result
 |
 +--> decide next action
 |
 +--> get_pod_events()
 |
 +--> inspect result
 |
 +--> generate report

This is the beginning of agentic behavior.


26. Expected SRE Report

The exact wording generated by the LLM may vary, but the report should resemble:

SYMPTOM
-------
Pod app-crash-xxxx is repeatedly restarting
and is currently in CrashLoopBackOff.


EVIDENCE
--------
1. Container restart count is greater than zero.

2. Container terminated with exit code 1.

3. Previous container logs contain:
   "ERROR database connection refused"

4. Kubernetes events show BackOff.


HYPOTHESIS
----------
The application is failing because it cannot
establish database connectivity.


VALIDATION
----------
The previous container logs confirm that the
application reported a database connection failure.

However, database availability itself has not
been independently verified.


ROOT CAUSE
----------
Root cause not confirmed.

The evidence confirms an application-level
database connection failure but does not prove
that the database server itself is down.


CONFIDENCE
----------
Medium


NEXT INVESTIGATION
------------------
1. Check database Service.
2. Check Service endpoints.
3. Check DNS resolution.
4. Check NetworkPolicy.
5. Check database availability.
6. Validate application database configuration.

This is consistent with the Day-5 target report structure.


27. Incident 2 — ImagePullBackOff

Now create a bad image:

kubectl create deployment app-image `
  --image=nginx:this-image-does-not-exist

Check:

kubectl get pods

You should see something similar to:

app-image-xxxxxxxxxx    0/1    ImagePullBackOff

Now run:

python -m agents.sre_agent

Enter:

default

and the actual pod name.

The expected investigation pattern is:

Pod status
    |
    v
ImagePullBackOff
    |
    v
Kubernetes events
    |
    v
Failed to pull image
    |
    v
Diagnosis

The agent should not treat this like a CrashLoopBackOff investigation.

That is the purpose of dynamic planning.


28. Incident 3 — Pending Pod

A normal Minikube cluster may successfully schedule:

nginx

Therefore, simply creating an nginx deployment does not guarantee Pending.

Instead, deliberately create an impossible node selector.

Create:

scenarios/app-pending.yaml

Use:

apiVersion: apps/v1
kind: Deployment

metadata:
  name: app-pending

spec:
  replicas: 1

  selector:
    matchLabels:
      app: app-pending

  template:

    metadata:
      labels:
        app: app-pending

    spec:

      nodeSelector:
        sre-test-node: "does-not-exist"

      containers:

        - name: nginx
          image: nginx

Apply:

kubectl apply -f scenarios/app-pending.yaml

Check:

kubectl get pods

Expected:

app-pending-xxxxxxxxxx    0/1    Pending

29. Investigate Pending

Run:

kubectl describe pod YOUR_PENDING_POD

Near the bottom you should see scheduling events similar to:

Events:

Warning  FailedScheduling

0/1 nodes are available:
1 node(s) didn't match Pod's node affinity/selector.

The agent should follow:

Pending
   |
   v
Pod Status
   |
   v
FailedScheduling
   |
   v
Kubernetes Events
   |
   v
Node selector / scheduling constraint

30. Test Kubernetes Tools Independently

Create:

tests/test_tools.py

Use:

from tools.agent_tools import (
    get_pod_status,
    get_pod_logs,
    get_pod_previous_logs,
    get_pod_events
)


POD_NAME = "YOUR_POD_NAME"

NAMESPACE = "default"


print("=" * 70)

print("TEST 1: POD STATUS")

print("=" * 70)

result = get_pod_status(
    NAMESPACE,
    POD_NAME
)

print(result)


print()

print("=" * 70)

print("TEST 2: CURRENT LOGS")

print("=" * 70)

result = get_pod_logs(
    NAMESPACE,
    POD_NAME
)

print(result)


print()

print("=" * 70)

print("TEST 3: PREVIOUS LOGS")

print("=" * 70)

try:

    result = get_pod_previous_logs(
        NAMESPACE,
        POD_NAME
    )

    print(result)

except Exception as exc:

    print("Previous logs unavailable:")

    print(exc)


print()

print("=" * 70)

print("TEST 4: EVENTS")

print("=" * 70)

result = get_pod_events(
    NAMESPACE,
    POD_NAME
)

print(result)

Run:

python tests/test_tools.py

This lets us separate:

Kubernetes problem

from:

AI agent problem

That is an important engineering practice.


31. Cleanup Test Incidents

Create:

scenarios/cleanup.ps1

Use:

Write-Host "Cleaning SRE test incidents..."

kubectl delete deployment app-crash --ignore-not-found

kubectl delete deployment app-image --ignore-not-found

kubectl delete deployment app-pending --ignore-not-found

Write-Host ""

Write-Host "Cleanup complete."

kubectl get pods

Run:

.\scenarios\cleanup.ps1

32. Create an Automated Incident Generator

Create:

scenarios/create_incidents.ps1

Code:

Write-Host "============================================"
Write-Host "Creating Kubernetes SRE Test Incidents"
Write-Host "============================================"


Write-Host ""

Write-Host "[1] Creating CrashLoopBackOff incident"

kubectl create deployment app-crash `
  --image=busybox `
  -- /bin/sh -c "echo Application started; echo ERROR database connection refused; exit 1"


Write-Host ""

Write-Host "[2] Creating ImagePullBackOff incident"

kubectl create deployment app-image `
  --image=nginx:this-image-does-not-exist"


Write-Host ""

Write-Host "[3] Creating Pending incident"

kubectl apply -f scenarios/app-pending.yaml


Write-Host ""

Write-Host "============================================"

Write-Host "Incidents created"

Write-Host "============================================"


kubectl get pods

Run:

.\scenarios\create_incidents.ps1

33. Complete End-to-End Test

Now the complete workflow becomes:

cd k8s-day5-sre-agent

.\.venv\Scripts\Activate.ps1

minikube status

kubectl get nodes

pip install -r requirements.txt

.\scenarios\create_incidents.ps1

kubectl get pods

python -m agents.sre_agent

Investigate the CrashLoopBackOff pod.

Then:

python -m agents.sre_agent

Investigate the ImagePullBackOff pod.

Then:

python -m agents.sre_agent

Investigate the Pending pod.

Finally:

.\scenarios\cleanup.ps1

34. Complete Architecture

At this point the project looks like:

                         USER
                           |
                           v
                 +-------------------+
                 |    SRE AGENT      |
                 |      LLM          |
                 +---------+---------+
                           |
                           |
                    Dynamic Planning
                           |
          +----------------+----------------+
          |                |                |
          v                v                v
  get_pod_status()   get_pod_logs()   get_pod_events()
          |
          |
          v
get_pod_previous_logs()
          |
          +----------------+
                           |
                           v
                     EVIDENCE
                           |
                           v
                     HYPOTHESIS
                           |
                           v
                     VALIDATION
                           |
                           v
                 ROOT CAUSE CHECK
                    /          \
                   /            \
                  NO            YES
                  |              |
                  v              v
           More investigation   RCA
                  |
                  v
             More tools

35. Traditional Script vs Agentic SRE

A traditional script may look like:

get status
   ↓
get logs
   ↓
get events
   ↓
print output

The agentic approach is:

get status
   ↓
reason
   ↓
decide what evidence is missing
   ↓
get relevant tool
   ↓
reason again
   ↓
validate hypothesis
   ↓
decide whether root cause is proven
   ↓
generate RCA

This is a significant architectural difference.


36. Example: Exit Code 137

One of the important rules in our system is:

Exit code 137

must not automatically become:

OOMKilled

Why?

Because:

137 = 128 + 9

which corresponds to:

SIGKILL

Possible explanations require additional evidence.

The investigation should therefore be:

Exit code 137
       |
       v
SIGKILL
       |
       v
Possible OOM
       |
       v
Collect evidence
       |
       +----> Container state
       |
       +----> Kubernetes events
       |
       +----> Node memory
       |
       +----> Container memory limit
       |
       v
Confirm / Reject OOM hypothesis

This evidence-first reasoning is specifically emphasized in the Day-5 material.


37. Example: Database Connection Refused

Consider:

ERROR database connection refused

The agent should reason:

FACT
Application received connection refused.

        ↓

INFERENCE
Application could not establish DB connection.

        ↓

HYPOTHESIS
Database connectivity problem.

        ↓

VALIDATION
Need more evidence.

        ↓

INVESTIGATE
Service
Endpoints
DNS
NetworkPolicy
Database
Credentials
Configuration

        ↓

ROOT CAUSE
Only confirmed when evidence supports it.

This is much safer than letting an LLM invent a root cause.


38. Read-Only Safety Model

Our Day-5 agent is intentionally read-only.

The tools can:

READ pod status
READ logs
READ previous logs
READ events

They cannot:

DELETE pod
DELETE deployment
RESTART pod
SCALE deployment
PATCH deployment
CHANGE node
CHANGE network policy
CHANGE storage

This is an important production design principle.

The investigation agent should first establish:

What happened?

before eventually being allowed to answer:

What should we do?

And remediation should be a separate controlled capability.


39. Why Previous Logs Matter

For a normal running container:

kubectl logs POD

may be enough.

But for:

CrashLoopBackOff

the currently running container may have little or no useful information.

The previous container may contain:

startup logs
exception
stack trace
configuration failure
connection failure
termination reason

Therefore:

CrashLoopBackOff
      |
      v
Previous Logs
      |
      v
Failure Evidence

is an important SRE troubleshooting pattern.


40. Why Kubernetes Events Matter

Application logs tell us what the application experienced.

Kubernetes events tell us what Kubernetes experienced.

For example:

Application logs
----------------
database connection refused

versus:

Kubernetes events
-----------------
FailedScheduling
Failed to pull image
BackOff
FailedMount
Unhealthy

An SRE agent should correlate both.

Therefore:

Application Evidence
          +
Kubernetes Evidence
          =
Better Incident Diagnosis

41. Current Project Capabilities

After completing Day 5, our agent can:

✓ Connect to Kubernetes
✓ Inspect pod status
✓ Inspect container state
✓ Inspect restart counts
✓ Inspect exit codes
✓ Read current logs
✓ Read previous logs
✓ Read Kubernetes events
✓ Dynamically select tools
✓ Collect evidence
✓ Generate hypotheses
✓ Validate hypotheses
✓ Avoid unsupported root-cause claims
✓ Generate structured SRE reports

The Day-5 goal is to establish this structured evidence-driven investigation loop before moving toward broader observability and multi-agent capabilities.


42. What We Have Learned

The most important lesson from this project is:

An AI SRE agent should not be a chatbot that guesses the root cause.

It should behave more like an experienced SRE.

An experienced SRE thinks:

What do I know?
        ↓
What don't I know?
        ↓
What evidence would reduce uncertainty?
        ↓
Which command should I run?
        ↓
What did that command tell me?
        ↓
Does my hypothesis still hold?
        ↓
What should I investigate next?

Our AI agent is beginning to follow the same process.


43.  Mental Model

Remember this:

OBSERVE
   ↓
REASON
   ↓
HYPOTHESIZE
   ↓
COLLECT EVIDENCE
   ↓
VALIDATE
   ↓
REASON AGAIN
   ↓
CONFIRM ROOT CAUSE
   ↓
REPORT

This is the core mental model for building an Agentic AI SRE system.


44. Final Project Tree

The completed project should look like:

k8s-day5-sre-agent/
│
├── .venv/
│
├── agents/
│   ├── __init__.py
│   └── sre_agent.py
│
├── tools/
│   ├── __init__.py
│   └── agent_tools.py
│
├── models/
│   ├── __init__.py
│   └── incident.py
│
├── scenarios/
│   ├── app-pending.yaml
│   ├── create_incidents.ps1
│   └── cleanup.ps1
│
├── tests/
│   ├── __init__.py
│   └── test_tools.py
│
├── .env
├── .gitignore
├── requirements.txt
└── README.md

45. Final Execution Checklist

Use this checklist whenever you start the project:

# Activate environment
.\.venv\Scripts\Activate.ps1


# Check Python
python --version


# Check Kubernetes
minikube status


# Check node
kubectl get nodes


# Check API key
python -c "from dotenv import load_dotenv; import os; load_dotenv(); print('API KEY:', 'SET' if os.getenv('OPENAI_API_KEY') else 'NOT SET')"


# Check Kubernetes tools
python -c "from tools.agent_tools import get_pod_status, get_pod_logs, get_pod_previous_logs, get_pod_events; print('ALL 4 TOOLS IMPORT OK')"


# Check agent
python -c "import agents.sre_agent; print('AGENT IMPORT OK')"


# Run agent
python -m agents.sre_agent


Tuesday, 23 December 2025

Analyze kernel core dump (vmcore) files on Rocky Linux 8.10 after a kernel panic.

This is production-grade, exactly how SRE teams do RCA.


๐Ÿง  End-to-End Flow (Rocky Linux 8.10)

Image

Image

Image

Kernel Panic
   ↓
kdump captures vmcore
   ↓
System reboots
   ↓
vmcore saved in /var/crash
   ↓
Analyze using crash + kernel-debuginfo
   ↓
Root Cause Analysis

✅ STEP 0: Confirm OS & Kernel (Baseline)

cat /etc/os-release
uname -r

Expected:

Rocky Linux 8.10
4.18.0-553.el8_10.x86_64   (example)

⚠️ Kernel version MUST match debuginfo


1️⃣ STEP 1: Confirm It Was a Kernel Panic

After the node rebooted:

journalctl -b -1 -k | tail -50

Look for:

Kernel panic - not syncing
BUG: unable to handle kernel NULL pointer dereference
watchdog: soft lockup

Check reboot reason:

last -x | grep reboot

If kernel panic is confirmed → continue.


2️⃣ STEP 2: Verify kdump Is Enabled (MANDATORY)

Check kdump service

systemctl status kdump

Expected:

Active: active (exited)

Check crashkernel parameter

cat /proc/cmdline

Must include:

crashkernel=512M

❌ If missing → vmcore will NOT be generated


3️⃣ STEP 3: Locate vmcore Files

Rocky Linux stores core dumps here:

ls -lh /var/crash/

Example:

/var/crash/127.0.0.1-2025-12-22-14:32/
 ├── vmcore
 ├── vmcore-dmesg.txt

๐Ÿ“Œ Files meaning:

  • vmcore → full memory dump (used by crash tool)

  • vmcore-dmesg.txt → kernel logs at crash time (fast RCA)


4️⃣ STEP 4: Install Required Packages (Safe in Production)

Install crash utility

yum install -y crash

Install matching kernel debuginfo

dnf debuginfo-install kernel-$(uname -r)

Verify debuginfo installed correctly

ls -lh /usr/lib/debug/lib/modules/$(uname -r)/vmlinux

Expected:

-rwxr-xr-x 1 root root 300M+ vmlinux

❌ If vmlinux is missing → analysis will fail


5️⃣ STEP 5: Start vmcore Analysis (MOST IMPORTANT)

Run:

crash \
/usr/lib/debug/lib/modules/$(uname -r)/vmlinux \
/var/crash/*/vmcore

You will enter:

crash>

6️⃣ STEP 6: Mandatory crash Commands (DO NOT SKIP)

๐Ÿ”ด 1. Check panic message

crash> log

Shows:

  • Panic reason

  • RIP (crashed function)

  • Kernel BUG info


๐Ÿ”ด 2. Stack trace of crashed CPU

crash> bt

This usually directly shows the faulty module or function.


๐Ÿ”ด 3. Stack trace of all CPUs

crash> bt -a

Use this to detect:

  • Soft lockups

  • Hung CPUs

  • Deadlocks


๐Ÿ”ด 4. Loaded kernel modules

crash> mod

Look for:

  • NIC drivers (mlx5_core, ixgbe)

  • Storage drivers (nvme, dm_multipath)

  • Third-party modules


๐Ÿ”ด 5. Memory status

crash> kmem -i

Checks:

  • Memory exhaustion

  • Fragmentation

  • Corruption indicators


๐Ÿ”ด 6. Slab corruption (VERY COMMON)

crash> kmem -s

Slab corruption = bad driver / kernel bug


7️⃣ STEP 7: Identify Root Cause (How to Read Output)

Example crash output

RIP: mlx5e_napi_poll
Call Trace:
 mlx5e_poll_rx_cq
 net_rx_action
 __do_softirq

Interpretation

mlx5e_*  → Mellanox NIC driver
RX path  → Network traffic triggered

Root Cause: NIC driver kernel panic


8️⃣ STEP 8: Quick RCA Using vmcore-dmesg.txt (Fastest)

When crash tool is not available:

cat /var/crash/*/vmcore-dmesg.txt | tail -50

Look for:

Kernel panic - not syncing
RIP: function_name

๐Ÿ”ฅ Often enough for initial RCA


Common Panic Patterns (Rocky Linux 8.10)

Image

Image

Image

Pattern in OutputMeaning
mlx5_coreNIC driver issue
nvmeDisk / firmware
BUG:Kernel bug
watchdogCPU soft lockup
slab corruptionMemory overwrite
net_rx_actionNetwork flood / driver

9️⃣ STEP 9: (If Kubernetes Node) Correlate with K8s

kubectl describe node <node-name>
kubectl get events -A --sort-by=.lastTimestamp

Look for:

  • Node reboot time

  • Pod evictions

  • CNI / CSI restarts

  • High CPU / DPDK pods


๐Ÿ”Ÿ STEP 10: Final RCA Template (Use This)

Incident: Kernel Panic on Worker Node
OS: Rocky Linux 8.10
Kernel: 4.18.0-553.el8_10
Time: 22-Dec-2025 14:32 IST

Root Cause:
Kernel panic caused by mlx5_core NIC driver
NULL pointer dereference during RX polling

Evidence:
- vmcore backtrace shows mlx5e_napi_poll
- vmcore-dmesg confirms RIP in NIC driver

Impact:
- Node rebooted
- Pods evicted
- 6 minutes downtime

Fix:
- Upgraded NIC firmware
- Kernel errata applied

Prevention:
- Enable reboot alerts
- Maintain kernel debuginfo cache

✅ Production Best Practices (MUST FOLLOW)

✔ Keep kdump always enabled
✔ Cache kernel-debuginfo
✔ Monitor node reboots
✔ Avoid privileged containers
✔ Keep kernel & firmware aligned
✔ Archive vmcore after RCA


Monday, 15 December 2025

k8sgpt

 Run Kubernetes AI Debugging Locally Using k8sgpt + Ollama (No OpenAI, 100% Free)

As Kubernetes clusters grow, debugging issues like ImagePullBackOff, CrashLoopBackOff, or scheduling failures becomes time-consuming.
k8sgpt solves this by analyzing your cluster and explaining issues in plain English using AI.

In this blog, I’ll show how to run k8sgpt locally with Ollama (no OpenAI key required) using Minikube on Windows.

This setup is ideal for:

  • Kubernetes SREs

  • DevOps Engineers

  • Platform teams

  • Anyone who wants AI-assisted debugging without cloud dependency


๐Ÿงฑ Architecture

Minikube (Kubernetes)
   |
k8sgpt (CLI)
   |
Ollama (Local LLM - llama3.1)

✔ Fully local
✔ No API key
✔ No cost
✔ Works offline


✅ Prerequisites

  • Windows 10/11 (64-bit)

  • Minikube installed and running

  • kubectl configured

  • Ollama installed

Verify:

kubectl get nodes
ollama list

Expected:

minikube   Ready
llama3.1

๐Ÿ”น Step 1: Download k8sgpt (Windows)

Go to:
๐Ÿ‘‰ https://github.com/k8sgpt-ai/k8sgpt/releases

Download:

k8sgpt_Windows_x86_64.zip

Extract it and move:

k8sgpt.exe → C:\Program Files\k8sgpt\

Add this directory to your PATH.

Verify:

k8sgpt version

๐Ÿ”น Step 2: Verify Kubernetes Context

kubectl config current-context

Output:

minikube

๐Ÿ”น Step 3: Remove OpenAI Backend (Important)

If OpenAI was previously configured:

k8sgpt auth remove --backends openai

This avoids quota and authentication errors.


๐Ÿ”น Step 4: Configure Ollama as AI Provider

Add Ollama with explicit model name:

k8sgpt auth add --backend ollama --model llama3.1

Set Ollama as default provider:

k8sgpt auth default --provider ollama

Verify:

k8sgpt auth list

Expected:

Default:
> ollama
Active:
> ollama

๐Ÿ”น Step 5: Verify Ollama Endpoint

curl http://localhost:11434/api/tags

You should see:

llama3.1

๐Ÿ”น Step 6: Run k8sgpt Analysis

Basic analysis:

k8sgpt analyze

With AI explanation:

k8sgpt analyze --explain

For cleaner output:

k8sgpt analyze --explain --filter=Pod,Node,Deployment

๐Ÿงช Step 7: Test with a Real Failure

Create a broken pod:

apiVersion: v1
kind: Pod
metadata:
  name: broken-pod
spec:
  containers:
  - name: test
    image: nginx:doesnotexist

Apply:

kubectl apply -f broken.yaml

Now run:

k8sgpt analyze --explain --filter=Pod

✅ Output (Example)

  • Detects ImagePullBackOff

  • Explains root cause

  • Suggests fix

  • Generated locally using llama3.1


๐Ÿง  Why This Setup Is Powerful

FeatureBenefit
Local LLMNo internet required
No OpenAIZero cost
MinikubeSafe learning environment
k8sgptFast RCA
OllamaProduction-grade local AI

๐Ÿ” Production Notes

  • This setup works the same on large clusters (50+ nodes)

  • In production, you can:

    • Run k8sgpt as CronJob

    • Integrate with Slack / MCP / ChatOps

    • Use with EFK / OpenSearch logs

    • Extend to Robin.io environments


๐Ÿ Conclusion

By combining k8sgpt + Ollama, you get an AI-powered Kubernetes debugging assistant that:

  • Runs locally

  • Costs nothing

  • Protects data privacy

  • Scales from Minikube → Production

This is an excellent way for SREs to adopt AI safely.



MCP server

Minikube + MCP server + VS code + continue + ollama


VS Code

  ↓

Continue Extension

  ↓

MCP Protocol (SSE)

  ↓

kubernetes-mcp-server

  ↓

Kubernetes API (via ServiceAccount)



VS Code
 └─ Continue Extension
     ├─ LLM (Ollama / OpenAI / etc.)
     └─ MCP Server → http://localhost:3000/mcp
         └─ kubernetes-mcp-server
             └─ Minikube cluster

๐Ÿฅ‡ OPTION 1 (RECOMMENDED): Build image locally & load into Minikube

This avoids GHCR entirely.

Step 1: Clone repo locally

git clone https://github.com/containers/kubernetes-mcp-server.git cd kubernetes-mcp-server

Step 2: Build Docker image

If using Docker (recommended):

docker build -t kubernetes-mcp-server:local .

If using Podman:

podman build -t kubernetes-mcp-server:local .

Step 3: Load image into Minikube

minikube image load kubernetes-mcp-server:local



apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: mcp-reader rules: - apiGroups: [""] resources: - pods - services - nodes - events - namespaces verbs: ["get", "list", "watch"] - apiGroups: ["apps"] resources: - deployments - replicasets - statefulsets verbs: ["get", "list", "watch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: mcp-reader-binding subjects: - kind: ServiceAccount name: mcp-sa namespace: mcp roleRef: kind: ClusterRole name: mcp-reader apiGroup: rbac.authorization.k8s.io
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kubernetes-mcp-server
  namespace: mcp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: kubernetes-mcp-server
  template:
    metadata:
      labels:
        app: kubernetes-mcp-server
    spec:
      serviceAccountName: mcp-sa
      containers:
        - name: mcp
          image: kubernetes-mcp-server:local
          imagePullPolicy: IfNotPresent
          args:
            - --port
            - "3000"
          env:
            - name: KUBERNETES_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
          ports:
            - containerPort: 3000


PS C:\Users\Raj Kumar Gupta\Desktop\Raj\minikube> kubectl port-forward -n mcp svc/kubernetes-mcp-server 3000:3000

Forwarding from 127.0.0.1:3000 -> 3000

Forwarding from [::1]:3000 -> 3000

Handling connection for 3000

============================================

STEP 1️⃣ Install Ollama (Windows)

Download Ollama

๐Ÿ‘‰ https://ollama.com/download

  1. Download Windows installer

  2. Install (default options)

  3. Reboot recommended


Verify Ollama installation

Open PowerShell:

ollama --version


PS C:\WINDOWS\system32> cd C:\Users\"Raj Kumar Gupta"\Desktop\Raj\minikube

PS C:\Users\Raj Kumar Gupta\Desktop\Raj\minikube> ollama --version

ollama version is 0.13.0

PS C:\Users\Raj Kumar Gupta\Desktop\Raj\minikube> ollama --version

ollama version is 0.13.0

PS C:\Users\Raj Kumar Gupta\Desktop\Raj\minikube> ollama pull llama3.1

pulling manifest

pulling 667b0c1932bc: 100% ▕██████████████████████████████████████████████████████████████████████████████████████████████████████████████▏ 4.9 GB

pulling 948af2743fc7: 100% ▕██████████████████████████████████████████████████████████████████████████████████████████████████████████████▏ 1.5 KB

pulling 0ba8f0e314b4: 100% ▕██████████████████████████████████████████████████████████████████████████████████████████████████████████████▏  12 KB

pulling 56bb8bd477a5: 100% ▕██████████████████████████████████████████████████████████████████████████████████████████████████████████████▏   96 B

pulling 455f34728c9b: 100% ▕██████████████████████████████████████████████████████████████████████████████████████████████████████████████▏  487 B

verifying sha256 digest

writing manifest

success

PS C:\Users\Raj Kumar Gupta\Desktop\Raj\minikube> ollama run llama3.1

>>> hello

Hello! How are you today? Is there something I can help you with or would you like to chat?


============================================

 C:\Users\Raj Kumar Gupta\.continue

{
  "models": [
    {
      "title": "Ollama (Local)",
      "provider": "ollama",
      "model": "llama3.1"
    }
  ],
  "mcpServers": [
    {
      "name": "kubernetes",
      "transport": "http",
      "url": "http://localhost:3000/mcp"
    }
  ]
}

Local AI for Kubernetes: Ollama + Continue + MCP Step-by-Step

๐Ÿ“Œ Prerequisites (Blog Section)
✔ Windows / Linux / macOS
✔ VS Code installed
✔ Kubernetes cluster (Minikube used here)
✔ kubectl configured
✔ Docker installed
✔ Basic Kubernetes knowledge

๐Ÿง  Architecture Overview (Explain in Blog)

VS Code (Continue Extension)
        |
        |  (SSE / MCP)
        v
Kubernetes MCP Server
        |
        |  (Kubernetes API)
        v
Minikube Cluster
        ^
        |
Local LLM (Ollama – Llama 3.1)

Key idea:

Continue talks to Ollama for AI reasoning and to Kubernetes MCP for real cluster data.


๐Ÿš€ Step 1: Install Ollama (Local LLM – Free)

Download Ollama

๐Ÿ‘‰ https://ollama.ai/download

Verify installation

ollama --version

Pull model (IMPORTANT)

ollama pull llama3.1

Verify model

ollama list

๐Ÿงฉ Step 2: Install Continue Extension in VS Code

  1. Open VS Code

  2. Go to Extensions

  3. Search Continue

  4. Install Continue.dev

  5. Reload VS Code


๐Ÿค– Step 3: Add Ollama Model in Continue

  1. Open Continue panel (left sidebar)

  2. Click Select model → Add Chat Model

  3. Fill details:

    • Provider: Ollama

    • Model: Llama3.1 Chat

  4. Click Connect

  5. Select Llama3.1 Chat

✅ At this point, Continue works with local AI.


☸️ Step 4: Deploy Kubernetes MCP Server

Create namespace

kubectl create namespace mcp

Deployment YAML

apiVersion: apps/v1
kind: Deployment
metadata:
  name: kubernetes-mcp-server
  namespace: mcp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: kubernetes-mcp-server
  template:
    metadata:
      labels:
        app: kubernetes-mcp-server
    spec:
      containers:
        - name: mcp
          image: ghcr.io/containers/kubernetes-mcp-server:latest
          args:
            - "--port"
            - "3000"
          ports:
            - containerPort: 3000

Service YAML

apiVersion: v1
kind: Service
metadata:
  name: kubernetes-mcp-server
  namespace: mcp
spec:
  selector:
    app: kubernetes-mcp-server
  ports:
    - port: 3000
      targetPort: 3000

Apply

kubectl apply -f deployment.yaml
kubectl apply -f service.yaml

๐Ÿ”Œ Step 5: Port-forward MCP Server

kubectl port-forward -n mcp svc/kubernetes-mcp-server 3000:3000

Keep this terminal open.


⚙️ Step 6: Configure Continue MCP (config.yaml)

Path:

C:\Users\<username>\.continue\config.yaml

Final Working Config (VERY IMPORTANT)

name: Local Config
version: 1.0.0
schema: v1

models:
  - name: Llama3.1 Chat
    provider: ollama
    model: llama3.1

mcpServers:
  - name: kubernetes
    type: sse
    url: http://localhost:3000/mcp

Reload VS Code

Ctrl + Shift + P → Reload Window

✅ Step 7: Verify Everything Works

Test AI

hello

Discover MCP tools

What tools are available?

Kubernetes real data

List pods in the mcp namespace

๐ŸŽ‰ If you see real cluster output → success!