Amazon EKS Cost Optimization Quiz
Last Updated: September 12, 2026
Examples are independent configuration alternatives, not executed deployments or measured savings. Merge changes into the existing resource owner; do not apply every example successively over the same workload. Replace placeholder application images and application-owned hooks, verify account/context/Region and compatible controllers, and test behavior before operational use.
This quiz tests your understanding of strategies, tools, and best practices for optimizing costs in Amazon EKS clusters.
Quiz Overview
- Compute resource optimization
- Storage cost optimization
- Networking cost optimization
- Cluster management cost optimization
- Cost monitoring and analysis
- Cost optimization tools and best practices
Multiple Choice Questions
1. What is the most effective strategy for optimizing compute costs in Amazon EKS?
- A. Always use the largest instance types
- B. Use only on-demand instances for all workloads
- C. Combine Spot Instances, right-sizing, and auto-scaling
- D. Consolidate all workloads into a single node group
Show Answer
Answer: C. Combine Spot Instances, right-sizing, and auto-scaling
Explanation: The most effective strategy for optimizing compute costs in Amazon EKS is to combine Spot Instances, right-sizing, and auto-scaling. Use Spot only where interruption tolerance and available capacity permit, and verify savings against workload SLOs. Neither this combination nor an advertised maximum discount guarantees lower cost or unchanged performance.
Key Compute Optimization Strategies:
Spot Instance Utilization:
- Up to 90% cost savings compared to on-demand
- Suitable for fault-tolerant workloads
- Implement interruption handling mechanisms
Right-sizing:
- Select instances based on actual resource usage
- Eliminate over-provisioned resources
- Optimize resource requests and limits
Auto-scaling Implementation:
- Node-level scaling through Cluster Autoscaler or Karpenter
- Pod-level scaling through Horizontal Pod Autoscaler
- Resource adjustment based on demand
The NodePool/EC2NodeClass require an installed compatible Karpenter controller/CRDs, scoped controller IAM, node role, discovery resources, and interruption queue. EKS 1.36 requires Karpenter >=1.13 in the current matrix. al2023@latest is a moving selector: review resolved AMIs and pin a tested alias/AMI for a controlled rollout. Instance lists and capacity limits are illustrative and are not a spending cap. Managed node groups handle their own interruption path; avoid a second handler over Karpenter-managed nodes.
The HPA requires resource metrics and requests; with CPU and memory targets it selects the largest desired replica count. The VPA below is Off to provide recommendations without changing those denominators. Auto is deprecated in favor of explicit modes such as Recreate; automatic CPU/memory changes must be coordinated with HPA. A preStop hook consumes the termination grace period and cannot guarantee a Spot deadline or successful cleanup.
Implementation Methods:
Create Node Group with Spot Instances:
bash# Create Spot Instance node group using eksctl eksctl create nodegroup \ --cluster my-cluster \ --region us-west-2 \ --managed \ --name spot-ng \ --node-type m5.large \ --nodes-min 2 \ --nodes-max 10 \ --spotDeploy and Configure Karpenter:
yaml# Karpenter NodePool apiVersion: karpenter.sh/v1 kind: NodePool metadata: name: default spec: template: spec: requirements: - key: "karpenter.sh/capacity-type" operator: In values: ["spot"] - key: "kubernetes.io/arch" operator: In values: ["amd64"] - key: "kubernetes.io/os" operator: In values: ["linux"] - key: "node.kubernetes.io/instance-type" operator: In values: ["m5.large", "m5a.large", "m5d.large", "m5ad.large", "m4.large"] nodeClassRef: group: karpenter.k8s.aws kind: EC2NodeClass name: default limits: cpu: 1000 memory: 1000Gi disruption: consolidationPolicy: WhenEmpty consolidateAfter: 30s --- # Karpenter NodeClass apiVersion: karpenter.k8s.aws/v1 kind: EC2NodeClass metadata: name: default spec: amiSelectorTerms: - alias: al2023@latest role: KarpenterNodeRole subnetSelectorTerms: - tags: karpenter.sh/discovery: my-cluster securityGroupSelectorTerms: - tags: karpenter.sh/discovery: my-cluster tags: karpenter.sh/discovery: my-clusterConfigure Horizontal Pod Autoscaler:
yamlapiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: web-app spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: web-app minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 - type: Resource resource: name: memory target: type: Utilization averageUtilization: 80Configure Vertical Pod Autoscaler:
yamlapiVersion: autoscaling.k8s.io/v1 kind: VerticalPodAutoscaler metadata: name: web-app-vpa spec: targetRef: apiVersion: "apps/v1" kind: Deployment name: web-app updatePolicy: updateMode: "Off" resourcePolicy: containerPolicies: - containerName: '*' minAllowed: cpu: 50m memory: 100Mi maxAllowed: cpu: 1 memory: 1Gi controlledResources: ["cpu", "memory"]
Optimization Strategies by Workload Type:
Stateless Applications:
- Prioritize Spot Instances
- Implement horizontal scaling
- Deploy across multiple availability zones
Stateful Applications:
- Mix on-demand and Spot Instances
- Select appropriate instance types
- Balance storage performance and cost
Batch Jobs:
- Maximize Spot Instance usage
- Implement job retry mechanisms
- Schedule within deadlines/capacity availability; no general off-peak On-Demand tariff
Best Practices:
Optimize Resource Requests and Limits:
yamlapiVersion: apps/v1 kind: Deployment metadata: name: web-app spec: replicas: 3 template: spec: containers: - name: web-app image: web-app:1.0 resources: requests: cpu: 100m memory: 256Mi limits: cpu: 500m memory: 512Mi metadata: labels: app: web-app selector: matchLabels: app: web-appOptimize Node Affinity and Pod Distribution:
yamlapiVersion: apps/v1 kind: Deployment metadata: name: web-app spec: replicas: 3 template: spec: affinity: nodeAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 1 preference: matchExpressions: - key: node.kubernetes.io/instance-type operator: In values: - m5.large - m5a.large podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - web-app topologyKey: kubernetes.io/hostname containers: - name: web-app image: registry.example.com/team/web-app:REVIEWED_TAG metadata: labels: app: web-app selector: matchLabels: app: web-appHandle Spot Instance Interruptions:
yamlapiVersion: apps/v1 kind: Deployment metadata: name: web-app spec: replicas: 3 template: spec: terminationGracePeriodSeconds: 60 containers: - name: web-app image: web-app:1.0 lifecycle: preStop: exec: command: - /bin/sh - -c - sleep 10; /app/cleanup.sh metadata: labels: app: web-app selector: matchLabels: app: web-app
Additional implementation considerations
- An eksctl configuration can define an On-Demand group for system workloads and a diversified Spot group for applications. Labels alone do not isolate workloads. Configure selectors/tolerations deliberately; do not taint the only bootstrap nodes before system controllers can run.
- In an existing Terraform module,
aws_eks_node_groupcan setcapacity_type = "SPOT"andscaling_config. The cluster, node IAM role/policies, subnets, and provider must already be defined. Coordinatedesired_sizeownership with the autoscaler. - EKS node-group tags are not a substitute for the actual ASG discovery tags used by Cluster Autoscaler. Inspect the ASGs and their existing tag owner before changing them. Bind the autoscaler policy to a dedicated controller identity; node-role add-on permissions are not the intended identity.
- Keep AWS read/discovery actions separate from the two scaling write actions. Restrict
autoscaling:SetDesiredCapacityandautoscaling:TerminateInstanceInAutoScalingGroupto ASGs carrying the reviewed enabled/cluster ownership tags, as in the official EKS policy.
The following eksctl fragment adds node groups to an existing cluster. Verify the owner's private subnets, node IAM policies, and existing group/ASG tags; it is not a standalone cluster-creation recipe.
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: my-cluster
region: us-west-2
managedNodeGroups:
- name: system-ng
instanceType: m5.large
desiredCapacity: 2
minSize: 2
maxSize: 4
labels:
workload-type: system
privateNetworking: true
- name: app-spot-ng
instanceTypes:
- m5.large
- m5a.large
- m5d.large
- m4.large
spot: true
desiredCapacity: 3
minSize: 1
maxSize: 10
labels:
workload-type: application
privateNetworking: trueThe Terraform fragment assumes the named cluster/node-role resources, private-subnet variable, and provider already exist in the owning module. Its IAM policy must be attached to the dedicated controller identity.
# Spot managed node group
resource "aws_eks_node_group" "spot" {
cluster_name = aws_eks_cluster.main.name
node_group_name = "spot-ng"
node_role_arn = aws_iam_role.node_role.arn
subnet_ids = var.private_subnet_ids
capacity_type = "SPOT"
instance_types = ["m5.large", "m5a.large", "m5d.large", "m4.large"]
scaling_config {
desired_size = 3
min_size = 1
max_size = 10
}
labels = {
"workload-type" = "application"
}
tags = {
Team = "platform"
Environment = "development"
}
}
# Cluster Autoscaler IAM policy
resource "aws_iam_policy" "cluster_autoscaler" {
name = "EKSClusterAutoscalerPolicy"
description = "Policy for Cluster Autoscaler"
policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Effect = "Allow",
Action = [
"autoscaling:DescribeAutoScalingGroups",
"autoscaling:DescribeAutoScalingInstances",
"autoscaling:DescribeLaunchConfigurations",
"autoscaling:DescribeTags",
"autoscaling:DescribeScalingActivities",
"ec2:DescribeImages",
"ec2:DescribeInstanceTypes",
"ec2:GetInstanceTypesFromInstanceRequirements",
"eks:DescribeNodegroup",
"ec2:DescribeLaunchTemplateVersions"
],
Resource = "*"
},
{
Effect = "Allow",
Action = [
"autoscaling:SetDesiredCapacity",
"autoscaling:TerminateInstanceInAutoScalingGroup"
],
Resource = "*",
Condition = {
StringEquals = {
"aws:ResourceTag/k8s.io/cluster-autoscaler/enabled" = "true",
"aws:ResourceTag/k8s.io/cluster-autoscaler/${aws_eks_cluster.main.name}" = "owned"
}
}
}
]
})
}Issues with other options:
- A. Always use the largest instance types: This leads to over-provisioning with unnecessary costs and may not match workload requirements.
- B. Use only on-demand instances for all workloads: On-demand instances cost more than Spot Instances, and many workloads can run effectively on Spot Instances.
- D. Consolidate all workloads into a single node group: This makes it difficult to meet various workload requirements, lacks resource isolation, and makes cost allocation and optimization challenging.
2. What is the most effective approach for optimizing storage costs in Amazon EKS?
- A. Use the cheapest storage type for all workloads
- B. Migrate all data to S3
- C. Select storage types matching workload requirements and implement lifecycle management
- D. Minimize all volume sizes
Show Answer
Answer: C. Select storage types matching workload requirements and implement lifecycle management
Explanation: The most effective approach for optimizing storage costs in Amazon EKS is to select storage types that match workload requirements and implement lifecycle management. This approach minimizes costs while meeting performance requirements and leverages appropriate storage tiers based on data value and access patterns.
Key Storage Optimization Strategies:
Select Appropriate Storage Types for Workloads:
- High performance needs: io2, gp3 (EBS)
- Shared access needs: EFS
- Large-scale data processing: FSx for Lustre
- Archive data: S3, S3 Glacier
Storage Lifecycle Management:
- Frequently accessed data: High-performance storage
- Occasionally accessed data: Standard storage
- Rarely accessed data: Low-cost archive storage
Efficient Volume Management:
- Set appropriate volume sizes
- Identify and remove unused volumes
- Manage snapshot lifecycles
StorageClass examples require installed CSI drivers and correct IAM/KMS/topology settings. WaitForFirstConsumer aligns EBS provisioning with scheduling; Retain leaves released volumes billable until the owner handles them. Bound PVCs cannot change class or shrink in place. Snapshot/restore or supported volume-type modification requires an owner-reviewed migration and restore test. Auto Mode uses a different EBS provisioner; do not silently substitute it into standard CSI examples.
create-file-system has no --lifecycle-policies option. Use put-lifecycle-configuration on the existing filesystem, preserving the complete desired policy array as shown below. S3 transitions also need minimum-duration/size, request cost, versioning, and recovery-latency review; replacing a lifecycle configuration must preserve unrelated rules.
Implementation Methods:
EBS Volume Optimization:
yamlapiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: ebs-gp3 provisioner: ebs.csi.aws.com parameters: type: gp3 iops: '3000' throughput: '125' encrypted: 'true' allowVolumeExpansion: true volumeBindingMode: WaitForFirstConsumer reclaimPolicy: RetainEFS Lifecycle Management:
bashset -euo pipefail : "${AWS_REGION:?Set the reviewed Region}" : "${FILE_SYSTEM_ID:?Set the existing reviewed EFS filesystem ID}" aws efs describe-lifecycle-configuration --region "$AWS_REGION" \ --file-system-id "$FILE_SYSTEM_ID" --query LifecyclePolicies --output json \ > efs-lifecycle-policies.json # Edit the exported array; an IA entry is {"TransitionToIA":"AFTER_30_DAYS"}. # Preserve required Archive/return-to-primary entries before submitting the whole policy. aws efs put-lifecycle-configuration --region "$AWS_REGION" \ --file-system-id "$FILE_SYSTEM_ID" \ --lifecycle-policies file://efs-lifecycle-policies.jsonS3 Lifecycle Policy:
json{ "Rules": [ { "ID": "Move to IA after 30 days, Glacier after 90 days", "Status": "Enabled", "Transitions": [ { "Days": 30, "StorageClass": "STANDARD_IA" }, { "Days": 90, "StorageClass": "GLACIER" } ], "Expiration": { "Days": 365 }, "Filter": { "Prefix": "eks-backups/" } } ] }EBS Snapshot Lifecycle Management:
yamlapiVersion: snapshot.storage.k8s.io/v1 kind: VolumeSnapshotClass metadata: name: ebs-snapshot driver: ebs.csi.aws.com deletionPolicy: Delete
Scheduled snapshots and storage review
The snapshot schedule below is suspended until the owner confirms the existing team-a/database-data PVC, standard EBS CSI driver, snapshot controller/CRDs, and the ebs-snapshot class. The mounted template uses generateName to avoid a fixed-name collision. The Role permits snapshot creation in this namespace; it does not constrain the source PVC field. Application consistency/quiescing and restore verification are separate responsibilities. A successful Job confirms API creation, not snapshot readiness; monitor readyToUse and errors separately. Job history limits do not expire VolumeSnapshots/EBS snapshots. Define retention and recovery ownership before enabling the schedule; deletionPolicy: Delete deletes the backing snapshot when its Kubernetes snapshot is deleted.
apiVersion: v1
kind: ServiceAccount
metadata:
name: snapshot-creator
namespace: team-a
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: snapshot-creator
namespace: team-a
rules:
- apiGroups: ["snapshot.storage.k8s.io"]
resources: ["volumesnapshots"]
verbs: ["create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: snapshot-creator
namespace: team-a
subjects:
- kind: ServiceAccount
name: snapshot-creator
namespace: team-a
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: snapshot-creator
---
apiVersion: v1
kind: ConfigMap
metadata:
name: snapshot-templates
namespace: team-a
data:
snapshot.yaml: |
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
generateName: database-data-
namespace: team-a
spec:
volumeSnapshotClassName: ebs-snapshot
source:
persistentVolumeClaimName: database-data
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: volume-snapshot
namespace: team-a
spec:
suspend: true
schedule: "0 1 * * *"
timeZone: Etc/UTC
concurrencyPolicy: Forbid
startingDeadlineSeconds: 1800
successfulJobsHistoryLimit: 1
failedJobsHistoryLimit: 2
jobTemplate:
spec:
backoffLimit: 0
activeDeadlineSeconds: 120
template:
spec:
serviceAccountName: snapshot-creator
restartPolicy: Never
securityContext:
runAsNonRoot: true
runAsUser: 65532
seccompProfile:
type: RuntimeDefault
containers:
- name: snapshot-creator
image: registry.k8s.io/kubectl:v1.36.2
command: ["kubectl"]
args: ["create", "-f", "/snapshots/snapshot.yaml", "--namespace=team-a"]
env:
- name: HOME
value: /tmp
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
memory: 128Mi
volumeMounts:
- name: snapshots
mountPath: /snapshots
readOnly: true
- name: tmp
mountPath: /tmp
volumes:
- name: snapshots
configMap:
name: snapshot-templates
- name: tmp
emptyDir: {}Match storage to access semantics and recovery goals: databases may need io2/gp3 IOPS, shared web content may use EFS, logs/archives often fit S3, and analytics/AI may require FSx for Lustre or EBS throughput. Hot/warm/cold is a classification example, not an automatic EBS→EFS→S3 migration pipeline. Prepare the referenced efs-standard/ebs-io2 classes separately. An EFS PVC request of 100Gi does not enforce a filesystem quota or purchase that capacity, and PVC labels do not automatically become AWS billing tags.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: hot-data
namespace: team-a
spec:
accessModes:
- ReadWriteOnce
storageClassName: ebs-gp3
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: warm-data
namespace: team-a
spec:
accessModes:
- ReadWriteMany
storageClassName: efs-standard
resources:
requests:
storage: 100GiapiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: database-data
labels:
app: database
environment: production
cost-center: cc-123
namespace: team-a
spec:
accessModes:
- ReadWriteOnce
storageClassName: ebs-io2
resources:
requests:
storage: 100GiThe following Terraform fragment requires existing configured providers/caller identity and reviewed resource ownership. Mount targets, network/IAM access, application backups, and retention are separate prerequisites; it is not a complete production storage deployment.
# Standard EBS CSI gp3 StorageClass
resource "kubernetes_storage_class_v1" "ebs_gp3" {
metadata {
name = "ebs-gp3"
}
storage_provisioner = "ebs.csi.aws.com"
parameters = {
type = "gp3"
iops = "3000"
throughput = "125"
encrypted = "true"
}
allow_volume_expansion = true
volume_binding_mode = "WaitForFirstConsumer"
reclaim_policy = "Retain"
}
# Encrypted EFS filesystem
resource "aws_efs_file_system" "eks_efs" {
creation_token = "eks-efs"
encrypted = true
lifecycle_policy {
transition_to_ia = "AFTER_30_DAYS"
}
tags = {
Name = "eks-efs"
}
}
# S3 bucket and lifecycle policy
resource "aws_s3_bucket" "eks_data" {
bucket = "eks-data-${data.aws_caller_identity.current.account_id}"
tags = {
Name = "eks-data"
}
}
resource "aws_s3_bucket_lifecycle_configuration" "eks_data_lifecycle" {
bucket = aws_s3_bucket.eks_data.id
rule {
id = "archive-rule"
status = "Enabled"
filter {
prefix = "eks-backups/"
}
transition {
days = 30
storage_class = "STANDARD_IA"
}
transition {
days = 90
storage_class = "GLACIER"
}
expiration {
days = 365
}
}
}Read-only PVC candidate inventory
Save the Python below as unreferenced_pvcs.py and use the explicit context/namespace commands. It reports Bound PVCs not referenced by the supplied current Pod snapshot, comparing (namespace, name) and handling non-PVC volumes. Kubernetes does not supply a standard pv_used annotation. List snapshots are not atomic and omit future/scaled-down controller consumers, pending jobs, and backup/recovery requirements. No deletion decision follows from this output. For EBS candidates use the account/tag-scoped query in EKS06 question 6; available means unattached, not unused.
set -euo pipefail
: "${KUBE_CONTEXT:?Select the reviewed cluster context}"
: "${KUBE_NAMESPACE:?Select the reviewed namespace}"
kubectl --context "$KUBE_CONTEXT" --namespace "$KUBE_NAMESPACE" \
get pvc --chunk-size=500 -o json > pvcs.json
kubectl --context "$KUBE_CONTEXT" --namespace "$KUBE_NAMESPACE" \
get pods --chunk-size=500 -o json > pods.json
python3 unreferenced_pvcs.pyimport json
from pathlib import Path
def unreferenced_claims(claims, pods):
referenced = set()
for pod in pods["items"]:
namespace = pod["metadata"]["namespace"]
for volume in pod.get("spec", {}).get("volumes") or []:
claim = volume.get("persistentVolumeClaim")
if claim:
referenced.add((namespace, claim["claimName"]))
result = []
for claim in claims["items"]:
metadata = claim["metadata"]
identity = (metadata["namespace"], metadata["name"])
if claim.get("status", {}).get("phase") != "Bound" or identity in referenced:
continue
spec = claim.get("spec", {})
result.append({
"namespace": identity[0],
"name": identity[1],
"storageClass": spec.get("storageClassName"),
"requested": ((spec.get("resources") or {}).get("requests") or {}).get("storage"),
"meaning": "not referenced by the supplied current Pod snapshot; review required",
})
return result
if __name__ == "__main__":
claims = json.loads(Path("pvcs.json").read_text())
pods = json.loads(Path("pods.json").read_text())
print(json.dumps(unreferenced_claims(claims, pods), indent=2))Issues with other options:
- A. Use the cheapest storage type for all workloads: The cheapest storage may not meet performance requirements, potentially causing application performance degradation and business impact.
- B. Migrate all data to S3: S3 is suitable for some data types but not appropriate for latency-sensitive workloads or applications requiring block storage.
- D. Minimize all volume sizes: Excessively minimizing volume sizes can cause space shortage issues, and some volume types (e.g., gp2) have performance determined by size.
3. What is the most effective strategy for optimizing networking costs in Amazon EKS?
- A. Use the most expensive network bandwidth for all traffic
- B. Place all services in a single availability zone
- C. Optimize traffic patterns, minimize data transfer costs, and utilize VPC endpoints
- D. Block all network traffic
Show Answer
Answer: C. Optimize traffic patterns, minimize data transfer costs, and utilize VPC endpoints
Explanation: The most effective strategy for optimizing networking costs in Amazon EKS is to optimize traffic patterns, minimize data transfer costs, and utilize VPC endpoints. This approach improves network traffic efficiency and reduces unnecessary costs by considering AWS network cost models.
Key Networking Cost Optimization Strategies:
Traffic Pattern Optimization:
- Minimize cross-availability zone traffic
- Minimize cross-region traffic
- Implement locality-aware routing
Minimize Data Transfer Costs:
- Use compression and efficient data formats
- Implement caching strategies
- Eliminate unnecessary data transfers
VPC Endpoint Utilization:
- Private connections to AWS services
- Bypass internet gateways
- Reduce data transfer costs
Topology spread controls placement; it does not select the endpoints used by Service traffic. spec.topologyKeys was removed. The example uses trafficDistribution: PreferSameZone (stable in Kubernetes 1.35+) with fallback, not a zero-cross-AZ guarantee. Verify the actual proxy/CNI behavior and availability constraints.
S3/DynamoDB gateway endpoints have different pricing from interface endpoints. ECR image pulls need ecr.api, ecr.dkr, and S3 connectivity, private DNS, and endpoint security-group ingress on TCP 443 from the intended nodes/Pods. The CLI shows one interface endpoint; provision its DKR counterpart as in the Terraform fragment below. Review endpoint policies and the actual regional traffic/cost model; “private” does not imply free.
Implementation Methods:
Availability Zone-Aware Pod Placement:
yamlapiVersion: apps/v1 kind: Deployment metadata: name: web-app spec: replicas: 6 template: spec: topologySpreadConstraints: - maxSkew: 1 topologyKey: topology.kubernetes.io/zone whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: app: web-app containers: - name: web-app image: registry.example.com/team/web-app:REVIEWED_TAG metadata: labels: app: web-app selector: matchLabels: app: web-appService Topology Routing:
yamlapiVersion: v1 kind: Service metadata: name: web-app spec: selector: app: web-app ports: - port: 80 targetPort: 8080 trafficDistribution: PreferSameZoneVPC Endpoint Configuration:
bash# Create S3 VPC endpoint aws ec2 create-vpc-endpoint \ --vpc-id vpc-12345678 \ --service-name com.amazonaws.us-west-2.s3 \ --route-table-ids rtb-12345678 # Create DynamoDB VPC endpoint aws ec2 create-vpc-endpoint \ --vpc-id vpc-12345678 \ --service-name com.amazonaws.us-west-2.dynamodb \ --route-table-ids rtb-12345678 # Create ECR API VPC endpoint aws ec2 create-vpc-endpoint \ --vpc-id vpc-12345678 \ --service-name com.amazonaws.us-west-2.ecr.api \ --vpc-endpoint-type Interface \ --subnet-ids subnet-12345678 subnet-87654321 \ --security-group-ids sg-12345678 \ --private-dns-enabledLocality Routing with Istio:
yamlapiVersion: networking.istio.io/v1 kind: DestinationRule metadata: name: web-app spec: host: web-app trafficPolicy: loadBalancer: simple: LEAST_REQUEST localityLbSetting: enabled: true outlierDetection: consecutive5xxErrors: 5 interval: 5s baseEjectionTime: 30s
Istio locality failover needs endpoint locality information and outlier detection. failover.from/to identify Regions, so putting AZ names there is incorrect. The DestinationRule above uses default locality priorities (same zone before other zones/Regions) with outlier detection; its sample ejection thresholds need workload testing. It is a separate option from kube-proxy Service traffic distribution, and a mesh is not required just to reduce transfer cost.
Additional routing, endpoint, and billing examples
The NetworkPolicy permits same-namespace frontend→API→database traffic plus a standard CoreDNS Deployment path. Add other required dependencies and verify CNI enforcement. Auto Mode node-local DNS or another resolver path needs its own appropriate allowance. Match selectors to the actual managed labels.
VirtualService evaluates the first matching rule, so the premium match precedes the catch-all and the matching DestinationRule subsets are defined. Workloads must carry those version labels. An end-user header supplied by a client is not proof of entitlement; enforce authorization server-side. The EnvoyFilter targets the actual ingress gateway namespace/labels and requires validation against the installed Istio/Envoy version. It compresses HTTP responses at the gateway; installing an outbound sidecar compressor does not by itself compress uploads. The legacy content_length/content_type fields are deprecated, not removed. This example uses response_direction_config/common_config; test CPU, latency, security, and protocol behavior.
The Terraform fragment assumes the VPC, private subnets, endpoint security group, and route table already exist in the owning module. NAT and route associations remain with that VPC owner. Sharing one zonal NAT across AZs is an availability/transfer-cost tradeoff, not the default HA saving; evaluate a regional NAT option separately.
Save the Python as network_costs.py. Set EXPECTED_CALLER_ACCOUNT, LINKED_ACCOUNT_ID, START_DATE, and END_DATE. reviewed-usage-types.json must contain a JSON array of exact USAGE_TYPE values selected from Cost Explorer/GetDimensionValues. This example uses the commercial AWS billing endpoint in us-east-1; verify the calling account and read permissions. API queries can incur charges. Dimensions in GetCostAndUsage do not support CONTAINS, so the helper uses exact EQUALS filters. It completes pagination and preserves Decimal amounts, currency units, and estimated state. Empty results/failures do not establish zero total network cost. Account-level rows require additional verified tag/resource mapping before attribution to a specific EKS cluster. Only the selected usage types are covered; ambiguous service/Regional/Region substring matching cannot reliably classify internet, AZ, and inter-Region charges. Dates use UTC with an exclusive End; check available history and billing finality. The former 100/50/200 USD thresholds were illustrative triage settings, not universal optimization decisions.
apiVersion: apps/v1
kind: Deployment
metadata:
name: database
spec:
replicas: 3
template:
spec:
affinity:
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- api-server
topologyKey: topology.kubernetes.io/zone
containers:
- name: database
image: registry.example.com/team/database:REVIEWED_TAG
metadata:
labels:
app: database
selector:
matchLabels:
app: databaseapiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-network-policy
spec:
podSelector:
matchLabels:
app: api-server
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
egress:
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: api-service
spec:
hosts:
- api-service
http:
- match:
- headers:
end-user:
exact: premium-user
route:
- destination:
host: api-service
subset: premium
- route:
- destination:
host: api-service
subset: v1
weight: 90
- destination:
host: api-service
subset: v2
weight: 10
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: api-service-subsets
spec:
host: api-service
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
- name: premium
labels:
version: premiumapiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: compression-filter
namespace: istio-system
spec:
workloadSelector:
labels:
istio: ingressgateway
configPatches:
- applyTo: HTTP_FILTER
match:
context: GATEWAY
listener:
filterChain:
filter:
name: envoy.filters.network.http_connection_manager
subFilter:
name: envoy.filters.http.router
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.compressor
typed_config:
'@type': type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor
response_direction_config:
common_config:
min_content_length: 100
content_type:
- application/json
- text/html
compressor_library:
name: gzip
typed_config:
'@type': type.googleapis.com/envoy.extensions.compression.gzip.compressor.v3.Gzip
memory_level: 3
window_bits: 10
compression_level: BEST_COMPRESSION
compression_strategy: DEFAULT_STRATEGY+-------------------+ +-------------------+ +-------------------+
| | | | | |
| VPC Endpoint | | NAT Gateway | | Internet |
| (AWS Services) | | (External APIs) | | Gateway |
+-------------------+ +-------------------+ +-------------------+
| | |
v v v
+---------------------------------------------------------------+
| |
| EKS Cluster VPC |
| |
+---------------------------------------------------------------+
| | |
v v v
+-------------------+ +-------------------+ +-------------------+
| | | | | |
| AZ-a | | AZ-b | | AZ-c |
| Workloads | | Workloads | | Workloads |
| | | | | |
+-------------------+ +-------------------+ +-------------------+
| | |
v v v
+---------------------------------------------------------------+
| |
| Service Mesh |
| (Locality-aware routing) |
| |
+---------------------------------------------------------------+# Gateway VPC endpoints
resource "aws_vpc_endpoint" "s3" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.s3"
route_table_ids = [aws_route_table.private.id]
vpc_endpoint_type = "Gateway"
tags = {
Name = "s3-endpoint"
}
}
resource "aws_vpc_endpoint" "dynamodb" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.dynamodb"
route_table_ids = [aws_route_table.private.id]
vpc_endpoint_type = "Gateway"
tags = {
Name = "dynamodb-endpoint"
}
}
# Interface VPC endpoints
resource "aws_vpc_endpoint" "ecr_api" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.ecr.api"
vpc_endpoint_type = "Interface"
subnet_ids = aws_subnet.private[*].id
security_group_ids = [aws_security_group.vpc_endpoints.id]
private_dns_enabled = true
tags = {
Name = "ecr-api-endpoint"
}
}
resource "aws_vpc_endpoint" "ecr_dkr" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.ecr.dkr"
vpc_endpoint_type = "Interface"
subnet_ids = aws_subnet.private[*].id
security_group_ids = [aws_security_group.vpc_endpoints.id]
private_dns_enabled = true
tags = {
Name = "ecr-dkr-endpoint"
}
}import json
import os
import re
from datetime import date
from decimal import Decimal
from pathlib import Path
import boto3
from botocore.config import Config
def network_cost_rows(client, start, end, linked_account, usage_types):
for value in [start, end]:
if date.fromisoformat(value).isoformat() != value:
raise ValueError("Use YYYY-MM-DD dates")
if start >= end:
raise ValueError("Start must precede exclusive End")
if not re.fullmatch(r"[0-9]{12}", linked_account):
raise ValueError("A reviewed linked account ID is required")
if not isinstance(usage_types, list) or not usage_types or any(
not isinstance(value, str) or not value.strip() for value in usage_types
):
raise ValueError("Provide exact reviewed USAGE_TYPE values")
request = {
"TimePeriod": {"Start": start, "End": end},
"Granularity": "DAILY",
"Metrics": ["UnblendedCost"],
"GroupBy": [
{"Type": "DIMENSION", "Key": "SERVICE"},
{"Type": "DIMENSION", "Key": "USAGE_TYPE"},
],
"Filter": {
"And": [
{"Dimensions": {"Key": "LINKED_ACCOUNT", "Values": [linked_account],
"MatchOptions": ["EQUALS"]}},
{"Dimensions": {"Key": "USAGE_TYPE", "Values": usage_types,
"MatchOptions": ["EQUALS"]}},
]
},
}
rows = []
seen_tokens = set()
for _ in range(100):
response = client.get_cost_and_usage(**request)
for period in response["ResultsByTime"]:
for group in period["Groups"]:
service, usage_type = group["Keys"]
metric = group["Metrics"]["UnblendedCost"]
amount = Decimal(metric["Amount"])
if not amount.is_finite() or not metric["Unit"]:
raise ValueError("Invalid monetary amount/unit")
rows.append({
"start": period["TimePeriod"]["Start"],
"endExclusive": period["TimePeriod"]["End"],
"service": service,
"usageType": usage_type,
"amount": str(amount),
"unit": metric["Unit"],
"estimated": period.get("Estimated"),
})
token = response.get("NextPageToken")
if not token:
return rows
if token in seen_tokens:
raise RuntimeError("Repeated pagination token")
seen_tokens.add(token)
request["NextPageToken"] = token
raise RuntimeError("Page limit exceeded; narrow the query")
def main():
expected_caller = os.environ["EXPECTED_CALLER_ACCOUNT"]
if not re.fullmatch(r"[0-9]{12}", expected_caller):
raise ValueError("Set the reviewed calling account")
session = boto3.Session(region_name="us-east-1")
config = Config(connect_timeout=5, read_timeout=30,
retries={"mode": "standard", "total_max_attempts": 4})
identity = session.client("sts", config=config).get_caller_identity()
if identity["Account"] != expected_caller:
raise RuntimeError("Calling account mismatch")
usage_types = json.loads(Path("reviewed-usage-types.json").read_text())
rows = network_cost_rows(
session.client("ce", config=config),
os.environ["START_DATE"], os.environ["END_DATE"],
os.environ["LINKED_ACCOUNT_ID"], usage_types,
)
print(json.dumps({"basis": "UnblendedCost", "rows": rows}, indent=2))
if __name__ == "__main__":
main()Issues with other options:
- A. Use the most expensive network bandwidth for all traffic: This incurs unnecessary costs, and not all workloads require high bandwidth.
- B. Place all services in a single availability zone: This may fail the multi-AZ availability requirements of critical workloads. A single-AZ choice for a noncritical environment still needs an explicit failure-tolerance decision.
- D. Block all network traffic: This is impractical and severely limits application functionality.
4. What is the most effective approach for optimizing Amazon EKS cluster management costs?
- A. Create as many clusters as possible
- B. Consolidate all workloads into a single cluster
- C. Optimize cluster count based on workload requirements and minimize management overhead
- D. Manage clusters manually
Show Answer
Answer: C. Optimize cluster count based on workload requirements and minimize management overhead
Explanation: The most effective approach for optimizing Amazon EKS cluster management costs is to optimize cluster count based on workload requirements and minimize management overhead. This approach balances cluster management costs and operational complexity while meeting workload isolation and security requirements.
Key Cluster Management Cost Optimization Strategies:
Maintain Appropriate Cluster Count:
- Cluster separation based on business requirements
- Environment-based cluster separation (development, staging, production)
- Consider security and compliance requirements
Minimize Management Overhead:
- Utilize automated cluster management tools
- Implement Infrastructure as Code (IaC)
- Centralized monitoring and logging
Optimize Cluster Resources:
- Appropriate control plane configuration
- Efficient node group management
- Utilize shared services
The short eksctl command illustrates creation, not a production network/IAM preset. Select the EKS version from the AWS catalog, review the VPC and API endpoint exposure using the creation guide, and keep controller permissions on dedicated identities. Node-group size bounds do not install an autoscaler or impose a spending cap. Standard/extended support fees, optional Provisioned Control Plane capacity, and shared management dependencies also affect the comparison.
The Terraform example uses the checked module 21.25.0, whose minimum AWS provider is 6.59; the shown provider pin is 6.64.0. Inputs deliberately have no guessed environment defaults. Supply owned private networking and API reachability, reviewed access entries, compatible/pinned add-ons with their IAM associations, and managed node groups. Standard EC2 nodes need the appropriate CNI/DNS/proxy path; this is not an Auto Mode example. Coordinate desired-size ownership with the autoscaler and use separate state/provider identities where the isolation model requires it. No plan/apply or production readiness is claimed.
Implementation Methods:
Optimized EKS Cluster Configuration:
bash: "${EKS_VERSION:?Select a version offered by the AWS EKS support catalog}" # Create optimized cluster using eksctl eksctl create cluster \ --name optimized-cluster \ --region us-west-2 \ --version "$EKS_VERSION" \ --nodegroup-name standard-workers \ --node-type m5.large \ --nodes-min 2 \ --nodes-max 10 \ --managedCluster Management Automation with Terraform:
hclterraform { required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" version = "= 6.64.0" } } } variable "region" { type = string } variable "account_id" { type = string } variable "cluster_name" { type = string } variable "environment" { type = string } variable "kubernetes_version" { type = string } variable "vpc_id" { type = string } variable "private_subnet_ids" { type = list(string) } variable "reviewed_addons" { type = any } variable "reviewed_access_entries" { type = any } variable "managed_groups" { type = any } provider "aws" { region = var.region allowed_account_ids = [var.account_id] } module "eks" { source = "terraform-aws-modules/eks/aws" version = "21.25.0" name = var.cluster_name kubernetes_version = var.kubernetes_version endpoint_private_access = true endpoint_public_access = false vpc_id = var.vpc_id subnet_ids = var.private_subnet_ids addons = var.reviewed_addons access_entries = var.reviewed_access_entries eks_managed_node_groups = var.managed_groups tags = { Environment = var.environment Terraform = "true" } }Cluster Configuration Management with GitOps:
yamlapiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: cluster-config namespace: argocd spec: project: cluster-config source: repoURL: https://github.com/myorg/cluster-config.git targetRevision: REPLACE_WITH_REVIEWED_COMMIT_SHA path: configs destination: server: https://kubernetes.default.svc namespace: default syncPolicy: automated: enabled: false prune: false selfHeal: falseMulti-tenant Cluster Configuration:
yaml# Namespace resource quota apiVersion: v1 kind: ResourceQuota metadata: name: team-a-quota namespace: team-a spec: hard: requests.cpu: "10" requests.memory: 20Gi limits.cpu: "20" limits.memory: 40Gi pods: "50" services: "20" persistentvolumeclaims: "30"
Isolation, environment inputs, and context-aware evidence
The Argo CD Application requires an installed controller, the reviewed repository/commit, and a pre-created cluster-config AppProject restricting repositories, destinations, and resource kinds. Replace the placeholder SHA and repository before use. Automatic synchronization/pruning is disabled for initial review; enable only the intended reconciliation behavior through the owner. Shared management credentials/controllers can expand the failure or security impact across otherwise separate clusters.
ResourceQuota constrains admission and object counts, not bills or tenant authorization. NetworkPolicy is additive across policies and requires enforcement by the CNI. The namespace example allows traffic within team-a, to/from the named shared namespace, and to a standard CoreDNS Deployment; adapt node-local/Auto Mode DNS and other dependencies explicitly. The shared API example allows only team-a to Pods labeled app: shared-api on TCP 8080. Reconcile any existing broad policies instead of leaving an older allow-all policy in place. RBAC, admission, IAM, and trust boundaries remain separate controls.
The environment tfvars below are overrides for the reviewed module above, not calls to an unprovided ./modules/eks-cluster. Merge the appropriate account/network/version/add-on/access values into each environment's inputs and use its own state/backend and identity. Instance lists and counts are illustrative; review interruption tolerance and current regional options.
The collection script operates on one explicit context per run. Set KUBE_CONTEXT, EXPECTED_API_SERVER, EXPECTED_CALLER_ACCOUNT, LINKED_ACCOUNT_ID, BILLING_TAG_KEY, BILLING_TAG_VALUE, START_DATE, and END_DATE; repeat deliberately for each cluster. The API-server URL must match the reviewed EKS cluster. It saves actual Metrics API samples with their timestamps/windows/units and all tag-scoped Cost Explorer pages, instead of relabeling one context's requested resources as every cluster's utilization. Metrics Server and read permissions are prerequisites. Missing node samples are not zero usage, and short samples are not peak-demand history.
This uses commercial AWS billing endpoints and exclusive end dates. Choose available billing history; the former 2023 dates were configuration examples, not measured results. The tag mapping must exist in billing and may omit untagged/shared charges. Raw monetary units, cost basis, and estimated status are preserved; the script does not infer a total from the first group or recommend downsizing from a request/capacity threshold.
A large shared cluster can reduce duplicated control-plane/shared-service overhead but increases coordination and failure impact. Smaller clusters can improve independent maintenance and workload boundaries while increasing duplicated capacity and operations. Choose a hybrid split from trust, workload, and recovery requirements; cluster count alone does not guarantee isolation or lower total cost.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: namespace-isolation
namespace: team-a
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector: {}
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: shared-services
egress:
- to:
- podSelector: {}
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: shared-services
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53apiVersion: v1
kind: Namespace
metadata:
name: shared-services
labels:
name: shared-services
access: global
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-team-a
namespace: shared-services
spec:
podSelector:
matchLabels:
app: shared-api
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: team-a
ports:
- protocol: TCP
port: 8080# production.tfvars: merge with the production account/network/add-on/access inputs.
cluster_name = "production"
environment = "production"
managed_groups = {
critical = {
instance_types = ["m5.large"]
capacity_type = "ON_DEMAND"
min_size = 3
max_size = 10
desired_size = 3
}
general = {
instance_types = ["m5.large", "m5a.large"]
capacity_type = "SPOT"
min_size = 3
max_size = 20
desired_size = 3
}
}# development.tfvars: use a separate state/backend and the development identity.
cluster_name = "development"
environment = "development"
managed_groups = {
default = {
instance_types = ["m5.large", "m5a.large"]
capacity_type = "SPOT"
min_size = 1
max_size = 5
desired_size = 1
}
}import json
import os
import re
import subprocess
from datetime import date
from pathlib import Path
def read_json(command):
completed = subprocess.run(command, check=True, capture_output=True, text=True, timeout=60)
return json.loads(completed.stdout)
def collect(context, expected_server, caller_account, linked_account, tag_key, tag_value, start, end,
run=subprocess.run, query=read_json):
if not all([context, expected_server, tag_key, tag_value]):
raise ValueError("Explicit context, API server, and billing tag mapping are required")
if not all(re.fullmatch(r"[0-9]{12}", account) for account in [caller_account, linked_account]):
raise ValueError("Use reviewed 12-digit account IDs")
if any(date.fromisoformat(value).isoformat() != value for value in [start, end]) or start >= end:
raise ValueError("Use YYYY-MM-DD with Start before exclusive End")
server = run(
["kubectl", "--context", context, "config", "view", "--minify",
"--output", "jsonpath={.clusters[0].cluster.server}"],
check=True, capture_output=True, text=True, timeout=30,
).stdout.strip()
if server != expected_server:
raise RuntimeError("Kubernetes context/API-server mismatch")
identity = query(["aws", "sts", "get-caller-identity", "--region", "us-east-1",
"--output", "json", "--no-cli-pager"])
if identity["Account"] != caller_account:
raise RuntimeError("Calling AWS account mismatch")
usage = query(["kubectl", "--context", context, "get", "--raw",
"/apis/metrics.k8s.io/v1beta1/nodes"])
if usage.get("kind") != "NodeMetricsList" or not isinstance(usage.get("items"), list):
raise ValueError("Unexpected node metrics response")
expression = {"And": [
{"Dimensions": {"Key": "LINKED_ACCOUNT", "Values": [linked_account], "MatchOptions": ["EQUALS"]}},
{"Tags": {"Key": tag_key, "Values": [tag_value], "MatchOptions": ["EQUALS"]}},
]}
base = [
"aws", "ce", "get-cost-and-usage", "--region", "us-east-1",
"--time-period", json.dumps({"Start": start, "End": end}),
"--granularity", "MONTHLY", "--metrics", "UnblendedCost",
"--group-by", "Type=DIMENSION,Key=SERVICE",
"--filter", json.dumps(expression), "--output", "json", "--no-cli-pager",
]
pages = []
token = None
seen = set()
for _ in range(100):
page = query(base + (["--next-page-token", token] if token else []))
if not isinstance(page.get("ResultsByTime"), list):
raise ValueError("Unexpected cost response")
pages.append(page)
token = page.get("NextPageToken")
if not token:
return {
"context": context, "apiServer": server, "nodeUsage": usage,
"billingScope": expression, "basis": "UnblendedCost", "costPages": pages,
}
if token in seen:
raise RuntimeError("Repeated cost pagination token")
seen.add(token)
raise RuntimeError("Page limit exceeded; narrow the query")
if __name__ == "__main__":
result = collect(
os.environ["KUBE_CONTEXT"], os.environ["EXPECTED_API_SERVER"],
os.environ["EXPECTED_CALLER_ACCOUNT"], os.environ["LINKED_ACCOUNT_ID"],
os.environ["BILLING_TAG_KEY"], os.environ["BILLING_TAG_VALUE"],
os.environ["START_DATE"], os.environ["END_DATE"],
)
Path("cluster-cost-and-usage.json").write_text(json.dumps(result, indent=2) + "\n")
print("Saved raw metrics and tag-scoped billing pages; no resizing recommendation was made.")Issues with other options:
- A. Create as many clusters as possible: This increases control plane costs and management overhead for each cluster, reducing resource utilization.
- B. Consolidate all workloads into a single cluster: This may be suitable in some environments but doesn't consider security requirements, workload isolation, and failure blast radius.
- D. Manage clusters manually: Manual management increases error potential, degrades consistency, and increases operational overhead.
5. What is the most effective approach for cost monitoring and allocation in Amazon EKS?
- A. Only review AWS bills
- B. Implement tagging strategy, cost allocation tools, and continuous monitoring
- C. Allocate the same cost to all resources
- D. Use resources without cost monitoring
Show Answer
Answer: B. Implement tagging strategy, cost allocation tools, and continuous monitoring
Explanation: The most effective approach for cost monitoring and allocation in Amazon EKS is to implement a tagging strategy, cost allocation tools, and continuous monitoring. This approach helps accurately track costs, allocate them by team or project, and identify cost optimization opportunities.
Key Cost Monitoring and Allocation Strategies:
Comprehensive Tagging Strategy:
- Tags by business unit, team, project, environment
- Apply consistent tagging rules
- Implement automated tagging
Cost Allocation Tool Utilization:
- AWS Cost Explorer and AWS Budgets
- Specialized tools like Kubecost or CloudHealth
- Custom dashboards and reports
Continuous Monitoring and Optimization:
- Regular cost review and analysis
- Anomaly detection and alerts
- Implement optimization recommendations
Kubernetes labels, AWS resource tags, and activated billing tags are distinct. Namespace labels do not automatically propagate to Pods or AWS resources; put workload labels on the Pod template. The shown Organizations tag policy validates configured tag keys/values on supported resources, including EKS clusters. It does not add tags or configure required-tag enforcement. Review inherited policy and separate missing-tag controls. Billing activation/backfill is a separate operation; up to 12 months of backfill requires tags that actually existed historically.
The CUR command below creates a legacy CUR, not a Cost Explorer dashboard or CUR 2.0 export. AWS still supports legacy CUR. New designs should evaluate Data Exports/CUR 2.0 in the FinOps guide. The legacy example requires an existing bucket in the declared Region, a reviewed CUR delivery policy, and reporting permissions. It does not create the bucket, Glue schema, Athena integration, or dashboard. Follow the delivery service-specific policy; do not substitute the CUR 2.0 bcm-data-exports policy for the legacy service.
Implementation Methods:
Implement Tagging Strategy:
yamlapiVersion: v1 kind: Namespace metadata: name: team-a labels: team: team-a cost-center: cc-123 environment: production project: project-x --- apiVersion: apps/v1 kind: Deployment metadata: name: web-app namespace: team-a labels: app: web-app team: team-a cost-center: cc-123 environment: production project: project-x spec: selector: matchLabels: app: web-app template: metadata: labels: app: web-app team: team-a cost-center: cc-123 environment: production project: project-x spec: containers: - name: web-app image: registry.example.com/team/web-app:REVIEWED_TAGAWS Tag Policy Configuration:
json{ "tags": { "team": { "tag_key": { "@@assign": "team" }, "tag_value": { "@@assign": [ "team-a", "team-b", "platform" ] }, "enforced_for": { "@@assign": [ "ec2:instance", "ec2:volume", "eks:cluster" ] } }, "cost-center": { "tag_key": { "@@assign": "cost-center" }, "enforced_for": { "@@assign": [ "ec2:instance", "ec2:volume", "eks:cluster" ] } }, "environment": { "tag_key": { "@@assign": "environment" }, "tag_value": { "@@assign": [ "production", "staging", "development" ] }, "enforced_for": { "@@assign": [ "ec2:instance", "ec2:volume", "eks:cluster" ] } } } }Kubecost Installation and Configuration: Use the checked Kubecost 3.2.4 installation in the source guide, or OpenCost 1.121.2/chart 2.5.31 in EKS06 question 6. Kubecost 3.x uses ClickHouse/direct finops-agent collection; 2.x Prometheus values and command-line license tokens are not this setup. Reuse one owned deployment and review its migration, licensing, and storage requirements.
Legacy CUR Delivery Setup:
bash# Create cost and usage report aws cur put-report-definition \ --report-definition '{ "ReportName": "eks-cost-report", "TimeUnit": "HOURLY", "Format": "Parquet", "Compression": "Parquet", "AdditionalSchemaElements": ["RESOURCES"], "S3Bucket": "my-cost-reports", "S3Prefix": "eks-costs", "S3Region": "us-east-1", "AdditionalArtifacts": ["ATHENA"], "RefreshClosedReports": true, "ReportVersioning": "OVERWRITE_REPORT" }'
Allocation review and supplementary configuration
Reuse Q4’s context/account/tag-scoped collector for actual Metrics API samples and billing pages. Requests/limits are declared configuration, not actual usage; a request/limit ratio below 0.5 does not justify reducing requests. Compare the same workload, time window, units, and coverage instead of current replica counts against historical usage. Reconcile team/cost-center allocations with untagged and shared costs. For operational reporting/alerts, use the explicit collection/validation workflow in the FinOps guide; failures/missing data must not become zero.
The 1,000 USD/80% budget below is illustrative. Verify the real activated Budgets tag-filter format and coverage; processed billing alerts do not impose a hard spending cap. The Terraform fragment assumes an existing CUR bucket, caller identity, providers, and Region input. Its owner must configure Athena result access, retention/encryption, Glue tables, and query permissions. Terraform supports aws_quicksight_dashboard, but account edition, datasets/templates, and permissions still require configuration.
aws resourcegroupstaggingapi tag-resources --region us-west-2 \
--resource-arn-list arn:aws:eks:us-west-2:123456789012:cluster/my-cluster \
--tags team=platform,cost-center=cc-100,environment=productionapiVersion: v1
kind: Namespace
metadata:
name: team-a-production
labels:
team: team-a
environment: production
cost-center: cc-123
---
apiVersion: v1
kind: Namespace
metadata:
name: team-a-development
labels:
team: team-a
environment: development
cost-center: cc-123apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
template:
spec:
containers:
- name: web-app
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
image: registry.example.com/team/web-app:REVIEWED_TAG
metadata:
labels:
app: web-app
selector:
matchLabels:
app: web-appaws budgets create-budget \
--account-id 123456789012 \
--budget '{
"BudgetName": "EKS-Monthly",
"BudgetLimit": {
"Amount": "1000",
"Unit": "USD"
},
"CostFilters": {
"TagKeyValue": [
"user:kubernetes.io/cluster/my-cluster$owned"
]
},
"TimeUnit": "MONTHLY",
"BudgetType": "COST"
}' \
--notifications-with-subscribers '[
{
"Notification": {
"NotificationType": "ACTUAL",
"ComparisonOperator": "GREATER_THAN",
"Threshold": 80,
"ThresholdType": "PERCENTAGE"
},
"Subscribers": [
{
"SubscriptionType": "EMAIL",
"Address": "team@example.com"
}
]
}
]'resource "aws_cur_report_definition" "eks_cost" {
report_name = "eks-cost-report"
time_unit = "HOURLY"
format = "Parquet"
compression = "Parquet"
additional_schema_elements = ["RESOURCES"]
s3_bucket = aws_s3_bucket.cost_reports.id
s3_prefix = "eks-costs"
s3_region = "us-east-1"
additional_artifacts = ["ATHENA"]
refresh_closed_reports = true
report_versioning = "OVERWRITE_REPORT"
}
resource "aws_s3_bucket" "athena_results" {
bucket = "eks-cost-athena-results-${data.aws_caller_identity.current.account_id}-${var.region}"
tags = {
Name = "EKS Cost Athena Results"
}
}
resource "aws_athena_workgroup" "eks_cost" {
name = "eks-cost-analysis"
configuration {
result_configuration {
output_location = "s3://${aws_s3_bucket.athena_results.bucket}/output/"
}
}
}For the regional TagResources call, verify the target Region and inspect FailedResourcesMap; a successful CLI exit does not prove every requested resource was tagged. Confirm actual resource tags before relying on billing allocation.
Estimated allocation-rate dashboard
This is an instantaneous CPU+memory allocation-rate estimate in USD/hour for one cluster’s Prometheus. It is not an invoice or complete namespace cost. The inputs are OpenCost allocation/price metrics (job="opencost" in this example) and kube-state-metrics Pod labels (job="kube-state-metrics"). Confirm the actual job labels and complete CPU/memory/price coverage. Keep the exporter’s workload namespace labels with the appropriate ServiceMonitor honor-label configuration. A shared backend needs consistent cluster identity in every aggregation and join.
Merge the first values fragment into the owned kube-prometheus-stack release. Pod team labels must be bounded and present on the Pod template; a namespace label does not create them. Missing team labels are grouped as unassigned. The queries deduplicate identical scrape copies; resolve conflicting price/ownership sources separately. GPU, storage, network, control-plane, idle/shared allocation, discounts, credits, and taxes are outside these two components. Missing data is not zero cost, and a rate is not a monthly dollar amount.
The PrometheusRule namespace/release label must match the source guide’s selectors. Replace REPLACE_WITH_PROMETHEUS_UID with the reviewed single-cluster data source before provisioning the dashboard. The ConfigMap’s grafana_dashboard: "1" label matches the source guide’s sidecar; its JSON is actually provided below. Verify rule health, data coverage, and dashboard queries. These examples were not connected to a live billing/Grafana deployment.
kube-state-metrics:
metricLabelsAllowlist:
- pods=[team]apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: eks-allocation-rate
namespace: monitoring
labels:
release: monitoring
spec:
groups:
- name: eks-allocation-rate
rules:
- record: eks_review:pod_cpu_memory_cost_per_hour:sum
expr: "sum by (namespace, pod) (\n (\n max by (namespace, pod, container,\
\ node) (\n container_cpu_allocation{job=\"opencost\"}\n )\n *\
\ on (node) group_left\n max by (node) (node_cpu_hourly_cost{job=\"opencost\"\
})\n )\n +\n (\n max by (namespace, pod, container, node) (\n container_memory_allocation_bytes{job=\"\
opencost\"}\n ) / 1073741824\n * on (node) group_left\n max by (node)\
\ (node_ram_hourly_cost{job=\"opencost\"})\n )\n)"
- record: eks_review:team_cpu_memory_cost_per_hour:sum
expr: "sum by (team) (\n label_replace(\n eks_review:pod_cpu_memory_cost_per_hour:sum\n\
\ * on (namespace, pod) group_left (label_team)\n max by (namespace,\
\ pod, label_team) (kube_pod_labels{job=\"kube-state-metrics\",label_team!=\"\
\"}),\n \"team\", \"$1\", \"label_team\", \"(.+)\"\n )\n or\n label_replace(\n\
\ eks_review:pod_cpu_memory_cost_per_hour:sum\n unless on (namespace,\
\ pod) max by (namespace, pod, label_team) (kube_pod_labels{job=\"kube-state-metrics\"\
,label_team!=\"\"}),\n \"team\", \"unassigned\", \"namespace\", \".*\"\n\
\ )\n)"apiVersion: v1
kind: ConfigMap
metadata:
name: cost-dashboard
namespace: monitoring
labels:
grafana_dashboard: '1'
data:
cost-dashboard.json: "{\n \"title\": \"Estimated CPU and memory allocation rate\"\
,\n \"uid\": \"eks-allocation-rate\",\n \"schemaVersion\": 39,\n \"version\"\
: 1,\n \"refresh\": \"1m\",\n \"time\": {\n \"from\": \"now-6h\",\n \"\
to\": \"now\"\n },\n \"panels\": [\n {\n \"id\": 1,\n \"title\"\
: \"CPU + memory allocation by namespace (USD/hour)\",\n \"type\": \"timeseries\"\
,\n \"gridPos\": {\n \"x\": 0,\n \"y\": 0,\n \"w\":\
\ 12,\n \"h\": 8\n },\n \"datasource\": {\n \"type\":\
\ \"prometheus\",\n \"uid\": \"REPLACE_WITH_PROMETHEUS_UID\"\n },\n\
\ \"fieldConfig\": {\n \"defaults\": {\n \"unit\": \"currencyUSD\"\
\n },\n \"overrides\": []\n },\n \"targets\": [\n \
\ {\n \"refId\": \"A\",\n \"expr\": \"sum by (namespace)\
\ (eks_review:pod_cpu_memory_cost_per_hour:sum)\",\n \"legendFormat\"\
: \"{{namespace}}\"\n }\n ]\n },\n {\n \"id\": 2,\n \
\ \"title\": \"CPU + memory allocation by team (USD/hour)\",\n \"type\"\
: \"timeseries\",\n \"gridPos\": {\n \"x\": 12,\n \"y\": 0,\n\
\ \"w\": 12,\n \"h\": 8\n },\n \"datasource\": {\n \
\ \"type\": \"prometheus\",\n \"uid\": \"REPLACE_WITH_PROMETHEUS_UID\"\
\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"unit\"\
: \"currencyUSD\"\n },\n \"overrides\": []\n },\n \"targets\"\
: [\n {\n \"refId\": \"A\",\n \"expr\": \"eks_review:team_cpu_memory_cost_per_hour:sum\"\
,\n \"legendFormat\": \"{{team}}\"\n }\n ]\n }\n ]\n}"Issues with other options:
- A. Only review AWS bills: AWS bills only provide high-level cost information, making it difficult to identify detailed cost allocation or optimization opportunities.
- C. Allocate the same cost to all resources: This doesn't accurately reflect actual resource usage and cost generation, failing to clarify cost responsibility by team or project.
- D. Use resources without cost monitoring: Without cost monitoring, you cannot detect cost increases early or identify optimization opportunities, making budget management difficult.
6. Which approach best supports a scalable EKS cost-optimization workflow?
- A. Change resources manually without measuring outcomes
- B. Consider only billing data and ignore workload telemetry
- C. Combine cost visibility, resource telemetry, and appropriate autoscaling with clear ownership
- D. Run uncoordinated optimizers over the same resources
Show Answer
Answer: C. Combine cost visibility, resource telemetry, and appropriate autoscaling with clear ownership
Explanation: The named tools are examples of complementary roles, not a requirement to install all of them. Choose an owned cost/telemetry path and workload-appropriate scaling. EKS Auto Mode, self-managed Karpenter, or Cluster Autoscaler may already own node capacity; separate ownership and avoid competing controllers. Savings and unchanged SLOs must be measured, and installing cost tools does not automatically wire billing data into HPA/Karpenter policies.
Key Cost Optimization Tools and Features:
Kubecost:
- Kubernetes resource cost visibility
- Cost allocation by namespace, deployment, service
- Cost optimization recommendations
- Cost forecasting and budget management
Karpenter:
- Intelligent node provisioning and management
- Instance selection within workload, capacity, and pricing constraints
- Provisioning/scaling behavior that must be verified in the actual environment
- Spot Instance utilization optimization
AWS Cost Explorer:
- Cost analysis across AWS services
- Tag-based cost allocation
- Cost trends and forecasting
- Reserved Instance and Savings Plans recommendations
Kubernetes Auto-scaling Tools:
- Horizontal Pod Autoscaler (HPA)
- Vertical Pod Autoscaler (VPA)
- Cluster Autoscaler
- Cluster Proportional Autoscaler
Implementation Methods:
Kubecost Installation and Configuration: Reuse the checked installation and allocation-data prerequisites from Q5 and the source guide. Kubecost 3.2.4 uses the current
kubecost/kubecostchart and ClickHouse/direct-agent architecture; product features depend on edition/configuration. OpenCost is a separate option. Do not deploy a second collector over an existing owned setup.Karpenter Installation and Configuration: Save the following as
karpenter-values.yaml, replace the account/role/cluster/endpoint/queue with reviewed existing values, and follow the Karpenter guide. The checked example uses 1.14.1; EKS 1.36 needs at least 1.13 in the compatibility matrix. These aresettings.*keys, not ignoredcontroller.clusterName/clusterEndpointvalues. The IRSA trust must matchsystem:serviceaccount:karpenter:karpenterand its audience; do not combine conflicting IRSA/Pod Identity assignments. Prepare controller/node IAM, node access, subnets/security groups, the interruption queue/event wiring, and reliable bootstrap capacity first. Fresh Helm installs and CRD upgrades have different ownership/lifecycle steps; the controller upgrade alone does not upgrade existing CRDs.
serviceAccount:
create: true
name: karpenter
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/KarpenterControllerRole-my-cluster
settings:
clusterName: my-cluster
clusterEndpoint: https://REPLACE_WITH_REVIEWED_EKS_ENDPOINT
interruptionQueue: my-cluster: "${KUBE_CONTEXT:?Select the reviewed context matching the values file}"
KARPENTER_VERSION="1.14.1"
helm template karpenter oci://public.ecr.aws/karpenter/karpenter \
--version "$KARPENTER_VERSION" --namespace karpenter \
-f karpenter-values.yaml > karpenter-rendered.yaml
# Fresh installation only after the owned IAM, node access, queue, CRDs, and bootstrap capacity are ready.
helm install karpenter oci://public.ecr.aws/karpenter/karpenter \
--kube-context "$KUBE_CONTEXT" \
--version "$KARPENTER_VERSION" --namespace karpenter --create-namespace \
-f karpenter-values.yaml --wait --timeout 5m# Karpenter NodePool and NodeClass configuration
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: default
spec:
template:
spec:
requirements:
- key: "karpenter.sh/capacity-type"
operator: In
values: ["spot", "on-demand"]
- key: "kubernetes.io/arch"
operator: In
values: ["amd64"]
- key: "kubernetes.io/os"
operator: In
values: ["linux"]
- key: "node.kubernetes.io/instance-type"
operator: In
values: ["m5.large", "m5a.large", "m5d.large", "m4.large", "t3.large", "t3a.large"]
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
limits:
cpu: 1000
memory: 1000Gi
disruption:
consolidationPolicy: WhenEmpty
consolidateAfter: 30s
---
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
name: default
spec:
amiSelectorTerms:
- alias: al2023@latest
role: KarpenterNodeRole
subnetSelectorTerms:
- tags:
karpenter.sh/discovery: my-cluster
securityGroupSelectorTerms:
- tags:
karpenter.sh/discovery: my-cluster
tags:
karpenter.sh/discovery: my-clusterConfigure Horizontal Pod Autoscaler:
yamlapiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: web-app spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: web-app minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 - type: Resource resource: name: memory target: type: Utilization averageUtilization: 80Configure Vertical Pod Autoscaler:
yamlapiVersion: autoscaling.k8s.io/v1 kind: VerticalPodAutoscaler metadata: name: web-app-vpa spec: targetRef: apiVersion: "apps/v1" kind: Deployment name: web-app updatePolicy: updateMode: "Off" resourcePolicy: containerPolicies: - containerName: '*' minAllowed: cpu: 50m memory: 100Mi maxAllowed: cpu: 1 memory: 1Gi controlledResources: ["cpu", "memory"]
Tool Integration and Workflow:
Cost Visibility and Analysis:
- Kubecost: In-cluster resource cost analysis
- AWS Cost Explorer: Cost analysis across AWS services
- Integrated dashboards: Overall cost overview and trends
Automated Resource Optimization:
- Karpenter: Optimal node provisioning and management
- HPA/VPA: Workload-level resource optimization
- Spot Instance utilization: Cost-efficient computing resources
Cost Allocation and Responsibility:
- Tag-based cost allocation
- Cost analysis by namespace and label
- Cost reporting by team and project
Continuous Optimization and Improvement:
- Implement cost optimization recommendations
- Regular cost review and analysis
- Set and track cost reduction goals
Owned integration and API validation
The VPA examples use Off while HPA uses CPU/memory utilization denominators. Enable automatic request changes only after coordinating that control loop. Cluster Proportional Autoscaler scales component replicas from cluster-size signals; it is not an equivalent node provisioner. Karpenter limits, instance preferences, and consolidation delay are not spending or termination-time guarantees. Inspect resolved AMIs and pin a tested version/ID for controlled changes instead of treating al2023@latest as immutable.
PriorityClass controls scheduling/preemption priority, not cost allocation or guaranteed availability. Govern who can assign priorities; scheduler preemption makes a best-effort attempt to respect PDBs. Review disruption and workload recovery. Cost observations and recommendations need an explicit reviewed policy/change path before controllers act; the architecture diagram does not implement that integration.
The Terraform alternative uses Helm provider 3.3.0 values-file configuration and AWS provider 6.64.0. Supply the explicit Kubernetes context and reviewed chart values, including compatible identity, storage, and license-secret references; it does not create those prerequisites. Do not put raw license/cloud credentials into values or state. Reconcile an existing release with its owner instead of installing it again through a second manager. The 1,000 USD/80% budget is illustrative, uses a verified billing tag filter, and is not a hard spending cap. No cluster/Helm provisioning or notification was executed in this audit.
The API example below explicitly targets OpenCost 1.121.2 from the earlier setup, not a guessed Kubecost 3.x URL. Its /allocation API accepts namespace,controllerKind,controller aggregation; deployment is not the documented generic aggregation key. The response data is an array of allocation-set maps, not a direct namespace/Deployment dictionary. Validate HTTP/API errors and data coverage; an empty result is not zero cost or evidence of overprovisioning. Allocation quantities, usage, requests, byte-hours/core-hours, and current replica counts have different meanings and time windows. Inspect the versioned schema and comparable workload metrics before recommending rightsizing. Raw allocation estimates must be reconciled with billing and idle/shared-cost policies.
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
template:
spec:
containers:
- name: web-app
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
image: registry.example.com/team/web-app:REVIEWED_TAG
metadata:
labels:
app: web-app
selector:
matchLabels:
app: web-appapiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: spot-pool
spec:
template:
spec:
requirements:
- key: "karpenter.sh/capacity-type"
operator: In
values: ["spot"]
- key: "node.kubernetes.io/instance-type"
operator: In
values: ["m5.large", "m5a.large", "m5d.large", "m4.large", "t3.large", "t3a.large"]
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
limits:
cpu: 1000
memory: 1000GiapiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
globalDefault: false
description: High priority pods
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: low-priority
value: 10000
globalDefault: false
description: Low priority pods
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: critical-app
spec:
template:
spec:
priorityClassName: high-priority
containers:
- name: critical-app
image: registry.example.com/team/critical-app:REVIEWED_TAG
metadata:
labels:
app: critical-app
selector:
matchLabels:
app: critical-appaws budgets create-budget \
--account-id 123456789012 \
--budget '{
"BudgetName": "EKS-Monthly",
"BudgetLimit": {
"Amount": "1000",
"Unit": "USD"
},
"CostFilters": {
"TagKeyValue": [
"user:kubernetes.io/cluster/my-cluster$owned"
]
},
"TimeUnit": "MONTHLY",
"BudgetType": "COST"
}' \
--notifications-with-subscribers '[
{
"Notification": {
"NotificationType": "ACTUAL",
"ComparisonOperator": "GREATER_THAN",
"Threshold": 80,
"ThresholdType": "PERCENTAGE"
},
"Subscribers": [
{
"SubscriptionType": "EMAIL",
"Address": "team@example.com"
}
]
}
]'terraform {
required_version = ">= 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = "= 6.64.0"
}
helm = {
source = "hashicorp/helm"
version = "= 3.3.0"
}
}
}
variable "region" { type = string }
variable "account_id" { type = string }
variable "kubeconfig_path" { type = string }
variable "kube_context" { type = string }
variable "reviewed_kubecost_values_path" { type = string }
variable "reviewed_karpenter_values_path" { type = string }
variable "budget_name" { type = string }
variable "budget_tag_filter" {
type = string
description = "Actual activated Budgets TagKeyValue, for example user:cluster$production."
}
variable "notification_email" {
type = string
description = "Approved recipient; applying this configures real notifications."
}
provider "aws" {
region = var.region
allowed_account_ids = [var.account_id]
}
provider "helm" {
kubernetes = {
config_path = var.kubeconfig_path
config_context = var.kube_context
}
}
resource "helm_release" "kubecost" {
name = "kubecost"
repository = "https://kubecost.github.io/kubecost/"
chart = "kubecost"
version = "3.2.4"
namespace = "kubecost"
create_namespace = true
values = [file(var.reviewed_kubecost_values_path)]
wait = true
timeout = 300
}
resource "helm_release" "karpenter" {
name = "karpenter"
repository = "oci://public.ecr.aws/karpenter"
chart = "karpenter"
version = "1.14.1"
namespace = "karpenter"
create_namespace = true
values = [file(var.reviewed_karpenter_values_path)]
wait = true
timeout = 300
}
resource "aws_budgets_budget" "eks" {
account_id = var.account_id
name = var.budget_name
budget_type = "COST"
limit_amount = "1000"
limit_unit = "USD"
time_unit = "MONTHLY"
cost_filter {
name = "TagKeyValue"
values = [var.budget_tag_filter]
}
notification {
comparison_operator = "GREATER_THAN"
threshold = 80
threshold_type = "PERCENTAGE"
notification_type = "ACTUAL"
subscriber_email_addresses = [var.notification_email]
}
}: "${KUBE_CONTEXT:?Select the reviewed OpenCost cluster context}"
# Terminal 1: authorized local access to the OpenCost 1.121.2 service from EKS06.
kubectl --context "$KUBE_CONTEXT" -n opencost port-forward --address 127.0.0.1 svc/opencost 9003:9003curl --fail --silent --show-error --max-time 30 --get \
http://127.0.0.1:9003/allocation \
--data-urlencode 'window=1d' \
--data-urlencode 'aggregate=namespace,controllerKind,controller' \
--data-urlencode 'accumulate=true' > allocation.jsonimport json
from pathlib import Path
response = json.loads(Path("allocation.json").read_text())
if response.get("code") != 200 or not isinstance(response.get("data"), list):
raise ValueError("Unexpected allocation API response; do not replace failure with zero")
if not all(isinstance(allocation_set, dict) for allocation_set in response["data"]):
raise ValueError("Expected an array of allocation-set maps")
if not response["data"] or not any(response["data"]):
raise SystemExit("No allocation data for the selected window; investigate coverage")
else:
print("Allocation response shape accepted; review units, window, and coverage before analysis")The other choices omit measurement, workload signals, or coordinated ownership. Competing controllers can undermine stability and cost predictability.