Skip to main content
Source

This page is generated from devops-agent/eks-cost-intelligence/references/findings-format.md. Edit the source, not this page.

Findings Format

Part of: eks-cost-intelligence Purpose: Output schema, severity thresholds, finding types enumeration, and remediation snippet templates for cost intelligence findings


Finding Schema​

Every finding produced by the assessment engine MUST include all of the following fields:

finding:
id: string # unique identifier, e.g. "compute-over-provisioned-checkout"
dimension: string # one of: compute | spot_graviton | networking | storage | observability | idle
severity: CRITICAL | HIGH | MEDIUM | LOW
affected_resource: string # namespace/workload or resource identifier
current_state: string # what is happening now (human-readable description)
monthly_cost: number # current monthly cost of this resource ($)
monthly_waste: number # estimated waste ($) — drives severity classification
monthly_savings: number # projected savings after fix ($)
effort: Low | Medium | High # implementation effort
fix_summary: string # one-line description of the fix
remediation: string # kubectl/Terraform/YAML snippet (ready to apply)
confidence: high | medium | low # estimate confidence level
data_sources: list[string] # which data sources informed this finding

Field Descriptions​

FieldTypeDescription
idstringUnique identifier combining dimension prefix and resource, e.g. compute-over-provisioned-checkout
dimensionenumThe cost dimension this finding belongs to: compute, spot_graviton, networking, storage, observability, or idle
severityenumCRITICAL, HIGH, MEDIUM, or LOW — determined by monthly_waste thresholds below
affected_resourcestringThe specific resource, e.g. payments/checkout (namespace/deployment) or node-group-general
current_statestringHuman-readable description of the current inefficiency
monthly_costnumberCurrent monthly cost of the affected resource in USD
monthly_wastenumberEstimated monthly waste in USD (cost that could be eliminated)
monthly_savingsnumberProjected monthly savings after implementing the fix
effortenumImplementation effort: Low (< 1 hour), Medium (1–4 hours), High (> 4 hours or requires planning)
fix_summarystringOne-line description of the recommended fix
remediationstringReady-to-apply code snippet (kubectl command, YAML manifest, or Terraform block)
confidenceenumConfidence in the estimate: high (metrics-backed), medium (partial data), low (heuristic-based)
data_sourceslistData sources used, e.g. ["metrics-server", "Cost Explorer"] or ["kubernetes-api", "ec2-describe-instances"]

Severity Thresholds​

Severity is determined by the monthly_waste value:

Monthly WasteSeverityDescription
> $500/monthCRITICALMajor cost inefficiency requiring immediate attention
$200–$500/monthHIGHSignificant waste that should be addressed within days
$50–$200/monthMEDIUMModerate waste worth addressing in the next sprint
< $50/monthLOWMinor inefficiency — address opportunistically

Severity Assignment Rules​

  1. Severity is ALWAYS determined by monthly_waste — no manual overrides
  2. When multiple resources share the same finding type, aggregate waste determines severity for the group finding
  3. When waste cannot be precisely calculated (confidence: low), use conservative estimates and note the uncertainty
  4. Findings with confidence: low should not be classified above HIGH regardless of estimated waste

Finding Types​

Finding types are organized by dimension. Each type maps to a specific cost inefficiency pattern:

Compute Dimension (compute)​

TypeDescriptionTypical Severity
over_provisioned_podsCPU/memory requests significantly exceed actual P95 usageMEDIUM–CRITICAL
missing_resource_requestsWorkloads without CPU/memory requests definedLOW–MEDIUM
idle_nodesNodes running at < 10% utilization for extended periodsHIGH–CRITICAL
consolidation_disabledKarpenter installed but consolidation not enabled or ineffectiveMEDIUM–HIGH

Spot/Graviton Dimension (spot_graviton)​

TypeDescriptionTypical Severity
spot_opportunityStateless multi-replica workloads running exclusively on On-DemandHIGH–CRITICAL
graviton_opportunityx86 workloads with arm64-compatible images not using GravitonMEDIUM–HIGH
low_instance_diversitySpot NodePools with insufficient instance type diversityMEDIUM
missing_interruption_handlingSpot nodes without Node Termination Handler or Karpenter interruption handlingMEDIUM

Networking Dimension (networking)​

TypeDescriptionTypical Severity
cross_az_trafficHigh cross-AZ data transfer without topology-aware routingMEDIUM–HIGH
nat_gateway_aws_trafficAWS service traffic (ECR, S3, STS) routed through NAT GatewayMEDIUM–HIGH
missing_vpc_endpointsVPC endpoints not configured for frequently accessed AWS servicesMEDIUM
instance_mode_lbLoad balancers using instance mode where IP mode would reduce cross-AZ hopsLOW–MEDIUM

Storage Dimension (storage)​

TypeDescriptionTypical Severity
gp2_volumesEBS volumes using gp2 instead of gp3 (20% cost reduction available)MEDIUM
unused_pvcsPVCs bound but not mounted by any running podLOW–HIGH
oversized_volumesProvisioned capacity significantly exceeds used capacityMEDIUM–HIGH
missing_efs_tieringEFS volumes without Intelligent-Tiering or lifecycle policiesLOW–MEDIUM

Observability Dimension (observability)​

TypeDescriptionTypical Severity
excessive_control_plane_loggingAll EKS control plane log types enabled unnecessarilyLOW–MEDIUM
high_cardinality_metricsPrometheus/CloudWatch scraping high-cardinality metric sourcesMEDIUM–HIGH
debug_logging_productionWorkloads logging at DEBUG/TRACE level in productionLOW–MEDIUM
missing_log_filteringNo log filtering or sampling in the logging pipelineLOW–MEDIUM

Idle Resources Dimension (idle)​

TypeDescriptionTypical Severity
orphaned_load_balancersLoadBalancer Services with no healthy backend endpointsHIGH–CRITICAL
zero_scale_deploymentsDeployments scaled to zero replicas for extended periodsLOW–MEDIUM
empty_namespacesNamespaces with no running workloads but allocated quotasLOW
orphaned_config_resourcesConfigMaps/Secrets not referenced by any running workloadLOW

Remediation Snippet Templates​

Each finding type has a ready-to-apply remediation template. Replace <placeholders> with actual values from the finding.

VPA Recommendation (over_provisioned_pods)​

# Install VPA and create a recommendation-only VPA for the workload
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: <deployment-name>-vpa
namespace: <namespace>
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: <deployment-name>
updatePolicy:
updateMode: "Off" # Recommendation-only mode — review before applying
resourcePolicy:
containerPolicies:
- containerName: "*"
controlledResources: ["cpu", "memory"]
# View VPA recommendations after ~5 minutes of data collection
kubectl get vpa <deployment-name>-vpa -n <namespace> \
-o jsonpath='{.status.recommendation.containerRecommendations[*]}' | jq '.'

# Apply recommended requests manually (after review)
kubectl set resources deployment/<deployment-name> -n <namespace> \
-c <container-name> \
--requests="cpu=<target-cpu>,memory=<target-memory>"

gp3 StorageClass (gp2_volumes)​

# Create a gp3 StorageClass and set as default
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gp3
annotations:
storageclass.kubernetes.io/is-default-class: "true"
provisioner: ebs.csi.aws.com
parameters:
type: gp3
fsType: ext4
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Delete
allowVolumeExpansion: true
# Remove default annotation from existing gp2 StorageClass
kubectl annotate storageclass gp2 \
storageclass.kubernetes.io/is-default-class- --overwrite

# For existing volumes: modify EBS volume type directly (no downtime with EBS CSI >= 1.19)
aws ec2 modify-volume --volume-id <vol-id> --volume-type gp3

# Verify migration
aws ec2 describe-volumes --volume-ids <vol-id> \
--query 'Volumes[0].{Type:VolumeType,State:State}'

Karpenter Consolidation (consolidation_disabled)​

# Enable Karpenter consolidation in NodePool spec
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: <nodepool-name>
spec:
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 5m # Production: 5m minimum; Non-prod: 30s acceptable
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["on-demand"]
# Verify consolidation is active
kubectl get nodepools -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.disruption.consolidationPolicy}{"\n"}{end}'

# Check consolidation events
kubectl get events --field-selector reason=Consolidating -A --sort-by='.lastTimestamp' | tail -10

VPC Endpoints — Terraform (nat_gateway_aws_traffic / missing_vpc_endpoints)​

# Terraform: VPC endpoints for ECR, S3, and STS
# Eliminates NAT Gateway charges for AWS service traffic

# ECR API endpoint (Interface type)
resource "aws_vpc_endpoint" "ecr_api" {
vpc_id = var.vpc_id
service_name = "com.amazonaws.${var.region}.ecr.api"
vpc_endpoint_type = "Interface"
subnet_ids = var.private_subnet_ids
security_group_ids = [aws_security_group.vpc_endpoints.id]
private_dns_enabled = true

tags = {
Name = "${var.cluster_name}-ecr-api"
}
}

# ECR Docker endpoint (Interface type)
resource "aws_vpc_endpoint" "ecr_dkr" {
vpc_id = var.vpc_id
service_name = "com.amazonaws.${var.region}.ecr.dkr"
vpc_endpoint_type = "Interface"
subnet_ids = var.private_subnet_ids
security_group_ids = [aws_security_group.vpc_endpoints.id]
private_dns_enabled = true

tags = {
Name = "${var.cluster_name}-ecr-dkr"
}
}

# S3 endpoint (Gateway type — no hourly charge)
resource "aws_vpc_endpoint" "s3" {
vpc_id = var.vpc_id
service_name = "com.amazonaws.${var.region}.s3"
vpc_endpoint_type = "Gateway"
route_table_ids = var.private_route_table_ids

tags = {
Name = "${var.cluster_name}-s3"
}
}

# STS endpoint (Interface type — required for IRSA/Pod Identity)
resource "aws_vpc_endpoint" "sts" {
vpc_id = var.vpc_id
service_name = "com.amazonaws.${var.region}.sts"
vpc_endpoint_type = "Interface"
subnet_ids = var.private_subnet_ids
security_group_ids = [aws_security_group.vpc_endpoints.id]
private_dns_enabled = true

tags = {
Name = "${var.cluster_name}-sts"
}
}

# Security group for Interface endpoints
resource "aws_security_group" "vpc_endpoints" {
name_prefix = "${var.cluster_name}-vpce-"
vpc_id = var.vpc_id

ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = [var.vpc_cidr]
description = "HTTPS from VPC"
}

tags = {
Name = "${var.cluster_name}-vpc-endpoints"
}
}

Topology-Aware Routing (cross_az_traffic)​

# Kubernetes 1.31+ (Beta, enabled by default) — use the trafficDistribution field
# This became GA/Stable in Kubernetes 1.33
apiVersion: v1
kind: Service
metadata:
name: <service-name>
namespace: <namespace>
spec:
trafficDistribution: PreferClose # Beta in 1.31, GA in 1.33
selector:
app: <app-label>
ports:
- port: 80
targetPort: 8080
# Kubernetes 1.27–1.30 — use the topology-mode annotation (legacy approach)
# This annotation is superseded by trafficDistribution in 1.31+ but still functional
apiVersion: v1
kind: Service
metadata:
name: <service-name>
namespace: <namespace>
annotations:
service.kubernetes.io/topology-mode: Auto
spec:
selector:
app: <app-label>
ports:
- port: 80
targetPort: 8080
# Ensure pods are spread across AZs (required for topology routing to be effective)
apiVersion: apps/v1
kind: Deployment
metadata:
name: <deployment-name>
namespace: <namespace>
spec:
replicas: 3 # Minimum 3 replicas for effective zone distribution
template:
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: <app-label>

Spot NodePool (spot_opportunity)​

# Karpenter NodePool with Spot priority and On-Demand fallback
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: <workload-name>-spot
spec:
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"] # Spot preferred, On-Demand fallback
- key: karpenter.k8s.aws/instance-category
operator: In
values: ["c", "m", "r"] # Broad category for availability
- key: karpenter.k8s.aws/instance-generation
operator: Gt
values: ["5"] # Current-gen instances only
- key: karpenter.k8s.aws/instance-size
operator: In
values: ["large", "xlarge", "2xlarge", "4xlarge"]
- key: kubernetes.io/arch
operator: In
values: ["amd64", "arm64"] # Include Graviton for additional savings
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 30s # Aggressive consolidation for Spot
limits:
cpu: "100"
memory: 400Gi
# Verify Spot nodes are being provisioned
kubectl get nodes -l karpenter.sh/capacity-type=spot \
-o custom-columns=NAME:.metadata.name,TYPE:.metadata.labels.karpenter\\.sh/capacity-type,INSTANCE:.metadata.labels.node\\.kubernetes\\.io/instance-type

# Check Spot vs On-Demand distribution
echo "Spot nodes: $(kubectl get nodes -l karpenter.sh/capacity-type=spot --no-headers | wc -l)"
echo "On-Demand nodes: $(kubectl get nodes -l karpenter.sh/capacity-type=on-demand --no-headers | wc -l)"

Finding Prioritization Rules​

When presenting findings in the report, apply the following sort order:

  1. Primary sort: Severity (CRITICAL > HIGH > MEDIUM > LOW)
  2. Secondary sort: Monthly savings (descending — highest savings first)
  3. Tertiary sort: Effort (ascending — Low effort first for tie-breaking)

This ensures the most impactful, easiest-to-implement fixes appear at the top of the recommendations list.


Example Finding​

finding:
id: "compute-over-provisioned-payments-checkout"
dimension: "compute"
severity: "CRITICAL"
affected_resource: "payments/checkout"
current_state: "Deployment requests 4 CPU / 8Gi memory per pod (3 replicas) but P95 usage is 0.8 CPU / 2.1Gi"
monthly_cost: 1120
monthly_waste: 847
monthly_savings: 720
effort: "Low"
fix_summary: "Install VPA in recommendation mode, then right-size requests to P95 + 20% headroom"
remediation: |
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: checkout-vpa
namespace: payments
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: checkout
updatePolicy:
updateMode: "Off"
confidence: "high"
data_sources:
- "metrics-server"
- "Container Insights"
- "Cost Explorer"

This reference file is part of the eks-cost-intelligence skill, provided as sample code for educational and demonstration purposes only. See the project's README and LICENSE for full terms.