Ecstaticloud
Initializing0%
Ecstaticloud Logo
Back to Insights
DevOps & AISeptember 13, 2026

Building Self-Healing Kubernetes Clusters: Combining eBPF Observability with Local LLM Remediation Agents

Learn how to leverage kernel-level eBPF tracing alongside local LLM agents to automatically detect, diagnose, and resolve complex Kubernetes runtime anomalies in real time. We walk through the architecture, code, and guardrails necessary to achieve true zero-touch incident response without risking cluster stability.

Traditional Kubernetes operational models rely heavily on reactive telemetry: user-space metrics scraped by Prometheus, aggregated stdout logs, and standardized cluster events. While this stack works for simple failure modes—like an Out-Of-Memory (OOM) kill or a failing liveness probe—it falls short when confronted with insidious runtime anomalies. Silent deadlock loops, kernel-level socket exhaustion, CPU throttling caused by cgroup v2 race conditions, and micro-segmentation drop issues often leave traditional metrics looking entirely normal while application throughput plummets.

By the time a human operator correlates the Prometheus alerts, reads the aggregated Grafana dashboards, and executes a triage runbook, the Mean Time to Resolution (MTTR) has stretched into hours.

To achieve true zero-touch incident response, we must bridge two revolutionary paradigms: kernel-level eBPF (Extended Berkeley Packet Filter) observability for instant, high-cardinality signal detection, and Local Large Language Model (LLM) agents for real-time contextual reasoning and safe remediation synthesis.

In this technical deep dive, we will design, architect, and implement a self-healing Kubernetes control loop that runs entirely on-premise or within your isolated VPC, ensuring high security, deterministic safety guardrails, and sub-second anomaly remediation.


Architectural Blueprint

The architecture replaces reactive polling with event-driven kernel tracing, feeding real-time context into a local reasoning engine governed by strict programmatic safety bounds.

┌────────────────────────────────────────────────────────────────────────┐
│                          KUBERNETES NODE                               │
│                                                                        │
│  ┌────────────────────────┐         ┌──────────────────────────────┐   │
│  │    Application Pod     │         │   Tetragon eBPF Agent        │   │
│  │                        │         │                              │   │
│  │  [User Space Code]     │         │  [kprobes / tracepoints]     │   │
│  └───────────┬────────────┘         └──────────────┬───────────────┘   │
│              │ Syscalls                            │ Kernel Ring Buffer│
└──────────────┼─────────────────────────────────────┼───────────────────┘
               │                                     │
               ▼                                     ▼
   ┌───────────────────────┐             ┌───────────────────────┐
   │ Kernel Space (cgroups)│             │ eBPF Event Processor  │
   └───────────────────────┘             └───────────┬───────────┘
                                                     │ JSON Telemetry
                                                     ▼
                                         ┌───────────────────────┐
                                         │  NATS JetStream Bus   │
                                         └───────────┬───────────┘
                                                     │
                                                     ▼
┌────────────────────────────────────────────────────────────────────────┐
│                     REMEDIATION CONTROL PLANE                          │
│                                                                        │
│  ┌──────────────────────────────────────────────────────────────────┐  │
│  │                   Self-Healing Controller Engine                 │  │
│  │                                                                  │  │
│  │  1. Event Aggregation & Deduplication                            │  │
│  │  2. K8s API Context Enrichment (Logs, Describes, Resource Specs) │  │
│  └──────────────────────────────┬───────────────────────────────────┘  │
│                                 │ Enriched Context Prompt              │
│                                 ▼                                      │
│  ┌──────────────────────────────────────────────────────────────────┐  │
│  │                  Local LLM Agent (Ollama/vLLM)                   │  │
│  │               Model: Llama-3-70B-Instruct / Qwen2.5              │  │
│  │                                                                  │  │
│  │  Outputs: Structured JSON Remediation Plan                       │  │
│  └──────────────────────────────┬───────────────────────────────────┘  │
│                                 │ Raw Plan                             │
│                                 ▼                                      │
│  ┌──────────────────────────────────────────────────────────────────┐  │
│  │                  Deterministic Safety Engine                     │  │
│  │                                                                  │  │
│  │  - Schema Validation (Pydantic)                                  │  │
│  │  - OPA/Gatekeeper Policy Check (RBAC & Destructive Action Rules) │  │
│  └──────────────────────────────┬───────────────────────────────────┘  │
│                                 │ Approved Mutation Payload            │
│                                 ▼                                      │
│  ┌──────────────────────────────────────────────────────────────────┐  │
│  │                     Kubernetes API Server                        │  │
│  │              (Executes Patch, Evict, Scale, etc.)                │  │
│  └──────────────────────────────────────────────────────────────────┘  │
└────────────────────────────────────────────────────────────────────────┘

Component Breakdown

  1. Kernel Telemetry Layer (eBPF / Tetragon): Captures high-cardinality syscalls (execve, tcp_connect, sys_enter_write), socket drops, and resource starvation directly from the Linux kernel without mutating application code.
  2. Event Streaming (NATS JetStream): Buffers and decouples kernel traces, delivering them to the remediation pipeline with low overhead.
  3. Context Enrichment Engine: Intercepts kernel telemetry and dynamically queries the K8s API to append application logs, deployment specs, cluster events, and recent configuration changes.
  4. Local Reasoning Engine (Local LLM via vLLM or Ollama): Analyzes enriched context payloads to identify root causes and generate structured, execution-ready remediation instructions.
  5. Deterministic Safety Guardrails (OPA & Pydantic Engine): Validates the LLM output against strict policy constraints to prevent improper mutations, infinite loops, and destructive operations.

Layer 1: Deep Kernel Observability with eBPF

Standard pod probes only check if an HTTP endpoint returns 200 OK or if a TCP socket opens. They fail when an application deadlocks internally, runs out of file descriptors, or suffers from severe TCP retransmissions.

Using Cilium Tetragon, we attach eBPF tracing policies to monitor critical kernel-space tracepoints and kprobes.

Tetragon TracingPolicy: Monitoring TCP State Anomalies and File Descriptor Leakage

The following TracingPolicy monitors kernel functions responsible for sock memory pressure and aborted TCP connections (tcp_drop), generating structured events when workloads start failing silently at the network boundary:

apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
  name: monitor-network-kernel-anomalies
  namespace: kube-system
spec:
  kprobes:
    - call: "tcp_drop"
      syscall: false
      args:
        - index: 0
          type: "sock"
      selectors:
        - matchNamespaces:
            - production
          matchArgs:
            - index: 0
              operator: "NotEqual"
              values:
                - "0"
    - call: "sys_enter_write"
      syscall: true
      args:
        - index: 0
          type: "int" # File Descriptor
        - index: 1
          type: "char_buf" # Buffer payload
      selectors:
        - matchNamespaces:
            - production
          matchArgs:
            - index: 0
              operator: "Equal"
              values:
                - "1" # stdout write tracking for hanging threads

When an application container experiences severe socket drops or enters an unrecoverable IO blocking loop, Tetragon emits a real-time JSON event via its gRPC/stdout stream directly to our event streaming topology:

{
  "process_kprobe": {
    "process": {
      "exec_id": "YWJjZGVmZ2hpamsxMjM0NTY3ODk6MQ==",
      "pod": {
        "namespace": "production",
        "name": "payment-processor-78f94986b-x92zk",
        "container": {
          "id": "containerd://a9f23e41b...",
          "name": "payment-api"
        }
      }
    },
    "parent": "systemd",
    "function_name": "tcp_drop",
    "args": [
      {
        "sock_arg": {
          "saddr": "10.244.1.45",
          "daddr": "10.244.2.89",
          "sport": 443,
          "dport": 52104,
          "state": "TCP_CLOSE"
        }
      }
    ]
  },
  "time": "2023-10-27T14:32:01.082341123Z"
}

Layer 2: The Local AI Engine & Context Synthesis

Why Local LLMs?

  1. Zero Data Exfiltration: Cluster topologies, internal stack traces, API keys, and internal IP addresses never cross your network boundary.
  2. Deterministic Latency: Local inference runtimes (like vLLM or Ollama) running on GPU-accelerated control plane nodes avoid public cloud API rate limits and unpredictable network latency.
  3. Cost Predictability: Eliminates unbounded token-billing spikes during catastrophic cluster cascading failures.

Constructing the Context Pipeline

An eBPF alert alone is insufficient for an LLM to accurately determine root cause. The Self-Healing Controller must enrich kernel signals with user-space K8s state context:

[eBPF Event Payload] 
       │
       ├─► Fetch Pod Spec & Owner Reference (Deployment/StatefulSet)
       ├─► Query recent K8s Events (e.g., OOMKilled, FailedScheduling)
       ├─► Fetch last 50 lines of stdout/stderr tail
       └─► Query Node Metrics (CPU throttling, memory pressure)
       │
       ▼
[Enriched Prompt Built]

Layer 3: Building the Remediation Controller

Below is a production-grade Python controller that connects to the event stream, enriches anomalies with cluster state, passes the context to a local LLM running via Ollama, parses the structured response using Pydantic, and validates execution instructions against explicit guardrails.

#!/usr me/bin/env python3
import json
import logging
import os
from typing import Literal, Optional, List
from pydantic import BaseModel, Field, ValidationError
import requests
from kubernetes import client, config

logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")

# Load Kubernetes configuration inside the cluster
try:
    config.load_incluster_config()
except config.ConfigException:
    config.load_kube_config()

v1 = client.CoreV1Api()
apps_v1 = client.AppsV1Api()

# Local LLM Configuration
OLLAMA_ENDPOINT = os.getenv("OLLAMA_ENDPOINT", "http://ollama.control-plane.svc.cluster.local:11434/api/generate")
MODEL_NAME = os.getenv("MODEL_NAME", "llama3:70b-instruct")

# ------------------------------------------------------------------------------
# Structured Output Schema Definitions
# ------------------------------------------------------------------------------

class RemediationAction(BaseModel):
    action_type: Literal[
        "RESTART_POD", 
        "SCALE_DEPLOYMENT", 
        "CLEAR_EVICTED_PODS", 
        "PATCH_RESOURCE_LIMITS", 
        "NO_ACTION"
    ] = Field(description="The exact remediation operation to perform.")
    
    target_namespace: str = Field(description="Target K8s namespace.")
    target_resource_name: str = Field(description="Target Deployment or Pod name.")
    scale_replicas: Optional[int] = Field(default=None, description="New replica count if action is SCALE_DEPLOYMENT.")
    cpu_limit_patch: Optional[str] = Field(default=None, description="New CPU limit patch, e.g., '1000m'.")
    memory_limit_patch: Optional[str] = Field(default=None, description="New Memory limit patch, e.g., '1Gi'.")
    reasoning: str = Field(description="Root cause diagnosis and rationale behind the remediation choice.")

# ------------------------------------------------------------------------------
# Context Enrichment Engine
# ------------------------------------------------------------------------------

def get_pod_context(namespace: str, pod_name: str) -> dict:
    """Enriches eBPF signals with Kubernetes user-space context."""
    context = {"pod_events": [], "logs": "", "status": {}}
    try:
        # Fetch Pod Logs
        logs = v1.read_namespaced_pod_log(name=pod_name, namespace=namespace, tail_lines=30)
        context["logs"] = logs
    except Exception as e:
        context["logs"] = f"Failed to fetch logs: {str(e)}"

    try:
        # Fetch Pod Events
        events = v1.list_namespaced_event(namespace=namespace, field_selector=f"involvedObject.name={pod_name}")
        context["pod_events"] = [e.message for e in events.items[-5:]]
    except Exception as e:
        context["pod_events"] = [f"Failed to fetch events: {str(e)}"]

    return context

# ------------------------------------------------------------------------------
# LLM Inference
# ------------------------------------------------------------------------------

def query_local_llm(ebpf_event: dict, enriched_context: dict) -> Optional[RemediationAction]:
    prompt = f"""
[SYSTEM INSTRUCTION]
You are an expert Kubernetes Reliability Engineer. Analyze the following eBPF kernel event and correlated pod state.
Select the safest, most effective remediation action. Return ONLY a valid JSON object matching the requested schema.

[EBPF KERNEL EVENT]
{json.dumps(ebpf_event, indent=2)}

[ENRICHED POD CONTEXT]
{json.dumps(enriched_context, indent=2)}

[OUTPUT SCHEMA]
Return JSON formatted exactly as follows:
{{
  "action_type": "RESTART_POD" | "SCALE_DEPLOYMENT" | "PATCH_RESOURCE_LIMITS" | "NO_ACTION",
  "target_namespace": "<string>",
  "target_resource_name": "<string>",
  "scale_replicas": <int or null>,
  "cpu_limit_patch": "<string or null>",
  "memory_limit_patch": "<string or null>",
  "reasoning": "<string reasoning>"
}}
"""

    payload = {
        "model": MODEL_NAME,
        "prompt": prompt,
        "format": "json",
        "stream": False,
        "options": {"temperature": 0.0} # Zero temperature for deterministic output
    }

    try:
        response = requests.post(OLLAMA_ENDPOINT, json=payload, timeout=30)
        response.raise_for_status()
        raw_response = response.json().get("response", "{}")
        
        # Parse and enforce structural typing via Pydantic
        action = RemediationAction.model_validate_json(raw_response)
        return action
    except (requests.RequestException, ValidationError) as err:
        logging.error(f"Failed to process or parse LLM output: {err}")
        return None

# ------------------------------------------------------------------------------
# Execution & Safety Enforcement Layer
# ------------------------------------------------------------------------------

def apply_remediation(action: RemediationAction):
    """Executes validated remediation calls against the Kubernetes API."""
    logging.info(f"Processing Remediation: {action.action_type} for {action.target_resource_name}")
    logging.info(f"LLM Reasoning: {action.reasoning}")

    if action.action_type == "RESTART_POD":
        v1.delete_namespaced_pod(
            name=action.target_resource_name,
            namespace=action.target_namespace,
            body=client.V1DeleteOptions(grace_period_seconds=5)
        )
        logging.info(f"Successfully deleted pod {action.target_resource_name} to force restart.")

    elif action.action_type == "SCALE_DEPLOYMENT":
        if not action.scale_replicas or action.scale_replicas > 10:
            logging.warning("Guardrail Triggered: Aborting scale operation. Replicas out of safe bounds.")
            return

        apps_v1.patch_namespaced_deployment_scale(
            name=action.target_resource_name,
            namespace=action.target_namespace,
            body={"spec": {"replicas": action.scale_replicas}}
        )
        logging.info(f"Scaled deployment {action.target_resource_name} to {action.scale_replicas}.")

    elif action.action_type == "NO_ACTION":
        logging.info("No action required by LLM evaluation.")

# ------------------------------------------------------------------------------
# Main Event Loop Processing Entrypoint
# ------------------------------------------------------------------------------

def process_ebpf_anomaly(raw_ebpf_json: str):
    ebpf_event = json.loads(raw_ebpf_json)
    
    # Extract identify keys from Tetragon event payload
    pod_name = ebpf_event.get("process_kprobe", {}).get("process", {}).get("pod", {}).get("name")
    namespace = ebpf_event.get("process_kprobe", {}).get("process", {}).get("pod", {}).get("namespace")

    if not pod_name or not namespace:
        logging.warning("Event missing pod identifier specs. Skipping processing.")
        return

    # Enrich Telemetry
    context = get_pod_context(namespace, pod_name)
    
    # Inference
    remediation_plan = query_local_llm(ebpf_event, context)
    
    if remediation_plan:
        apply_remediation(remediation_plan)

if __name__ == "__main__":
    # Mock Tetragon TCP drop anomaly event payload for demonstration
    sample_event = json.dumps({
        "process_kprobe": {
            "process": {
                "pod": {
                    "namespace": "production",
                    "name": "payment-processor-78f94986b-x92zk"
                }
            },
            "function_name": "tcp_drop"
        }
    })
    process_ebpf_anomaly(sample_event)

Layer 4: Zero-Trust Guardrails & Safety Architecture

Allowing a language model to directly invoke write actions on a live Kubernetes cluster introduces non-deterministic operational risks. An agent hallucination could trigger a destructive node drain or scale a deployment to zero during a false positive panic.

To achieve zero-trust autonomous operations, LLM outputs must pass through programmatic safety guardrails before execution.

                  ┌───────────────────────────────┐
                  │    LLM Remediation Proposal   │
                  └───────────────┬───────────────┘
                                  │
                                  ▼
           ┌─────────────────────────────────────────────┐
           │        Pydantic Structural Parsing          │
           │  (Ensures correct JSON types & valid enum)  │
           └──────────────────────┬──────────────────────┘
                                  │
                                  ▼
           ┌─────────────────────────────────────────────┐
           │    Policy Engine (OPA/Gatekeeper / Rego)    │
           │                                             │
           │ - Maximum pod eviction rate per hour        │
           │ - Protected Namespaces (e.g., kube-system)  │
           │ - Maximum scaling limits (e.g., max 10 pods)│
           │ - Forbidden verbs (e.g., DELETE PVC/Node)   │
           └──────────────────────┬──────────────────────┘
                                  │
                       ┌──────────┴──────────┐
                       │                     │
                [ Policy Passed ]     [ Policy Violation ]
                       │                     │
                       ▼                     ▼
             Execute Patch via        Emit Alert to Slack/
             Kubernetes API           PagerDuty for Human
                                      Review

OPA Policy Enforcer (Rego Definition)

Below is an Open Policy Agent (OPA) policy that enforces runtime boundary limits on any agent-generated mutation proposal:

package k8s.remediation.guardrails

default allow = false

# Allow Pod Restart ONLY if NOT in system namespaces
allow {
    input.action_type == "RESTART_POD"
    not is_protected_namespace(input.target_namespace)
}

# Allow Deployment Scaling ONLY within pre-defined safe thresholds
allow {
    input.action_type == "SCALE_DEPLOYMENT"
    not is_protected_namespace(input.target_namespace)
    input.scale_replicas > 0
    input.scale_replicas <= 10
}

# Protected Namespaces Rule
is_protected_namespace(namespace) {
    protected_namespaces := ["kube-system", "cert-manager", "ingress-nginx", "database"]
    protected_namespaces[_] == namespace
}

By decoupling reasoning from execution, the LLM functions purely as an operational analyst. The deterministic policy engine acts as the executor, enforcing absolute control over cluster changes.


Real-World Scenario: Diagnosing & Healing a Silent Socket Leak

Let's walk through an incident where this autonomous pipeline resolves a complex issue without human intervention.

Step 1: The Failure Mode

An application service (payment-api) suffers from a silent file-descriptor/socket leak due to unclosed HTTP keep-alive connections in a third-party SDK.

  • Prometheus View: CPU and Memory metrics remain normal (within 40% limits). Liveness probes continue returning HTTP 200 on /healthz because the health check uses a dedicated lightweight threadpool.
  • Application Reality: New incoming user connections hit a kernel-level hang when trying to allocate sockets, resulting in TCP_CLOSE drops. User traffic is failing with connection timeouts.

Step 2: eBPF Detection

Tetragon catches the tcp_drop kernel tracepoint firing rapidly for payment-api pods and sends structured JSON events to NATS JetStream within 12 milliseconds.

Step 3: Synthesis & Context Enrichment

The Self-Healing Controller intercepts the message, retrieves recent logs (showing java.net.SocketException: Too many open files), and packages the data for the local model (Llama-3-70B-Instruct).

Step 4: Local Reasoning Engine Evaluation

The local model processes the event payload and context, determining that the application is experiencing resource handle exhaustion that normal health checks miss:

{
  "action_type": "RESTART_POD",
  "target_namespace": "production",
  "target_resource_name": "payment-processor-78f94986b-x92zk",
  "scale_replicas": null,
  "cpu_limit_patch": null,
  "memory_limit_patch": null,
  "reasoning": "Kernel tracing reports abnormal tcp_drop rates coupled with socket allocation failures. Container liveness probe is non-responsive to network socket starvation. Executing target pod deletion to cycle file handles while maintaining deployment availability."
}

Step 5: Safety Guardrail & Autonomous Execution

  1. The response passes Pydantic type checks.
  2. The OPA Policy Engine confirms production is allowed for pod restarts and that the target is not in kube-system.
  3. The Kubernetes API executes a graceful eviction. A new pod spins up on clean kernel socket limits.
  4. Total MTTR: 1.8 seconds. (Zero human paging required).

Conclusion & Implementation Strategy

Combining eBPF observability with Local LLM agents transforms Kubernetes operations from reactive, alert-heavy workflows into proactive, self-healing control loops.

Key Takeaways for Cloud Architects:

  • eBPF provides the signal: Move beyond user-space application metrics. Trace low-level kernel anomalies directly at the tracepoint layer to catch silent failures, socket leaks, and deadlocks.
  • Local LLMs provide the reasoning: Keep models on-premise using platforms like vLLM or Ollama to reduce operational costs, enforce low latency, and secure cluster telemetry.
  • Guardrails provide safety: Never execute LLM outputs directly. Require strict schema validation and policy engines (such as OPA) to enforce boundaries and prevent dangerous mutations.

By running deep kernel telemetry into deterministic AI control loops, platform teams can eliminate routine operational overhead and maintain high cluster reliability automatically.