1"use strict";(self.webpackChunkinfracost_docs=self.webpackChunkinfracost_docs||[]).push([[1545],{57738(e,s,r){r.r(s),r.d(s,{assets:()=>a,contentTitle:()=>o,default:()=>h,frontMatter:()=>d,metadata:()=>n,toc:()=>l});const n=JSON.parse('{"id":"features/kubernetes","title":"Kubernetes","description":"Overview","source":"@site/docs/features/kubernetes.md","sourceDirName":"features","slug":"/features/kubernetes","permalink":"/docs/features/kubernetes","draft":false,"unlisted":false,"tags":[],"version":"current","frontMatter":{"slug":"kubernetes","title":"Kubernetes"},"sidebar":"someSidebar","previous":{"title":"Terragrunt","permalink":"/docs/features/terragrunt"},"next":{"title":"CloudFormation","permalink":"/docs/features/cloudformation"}}');var t=r(74848),i=r(28453);const d={slug:"kubernetes",title:"Kubernetes"},o=void 0,a={},l=[{value:"Overview",id:"overview",level:2},{value:"Supported resources",id:"supported-resources",level:2},{value:"How cost estimation works",id:"how-cost-estimation-works",level:2},{value:"Compute prices",id:"compute-prices",level:3},{value:"Cloud detection",id:"cloud-detection",level:3},{value:"Storage prices",id:"storage-prices",level:3},{value:"Load balancer prices",id:"load-balancer-prices",level:3},{value:"Usage values",id:"usage-values",level:2},{value:"Helm",id:"helm",level:2},{value:"Values files",id:"values-files",level:3},{value:"Dependencies",id:"dependencies",level:3},{value:"Kustomize",id:"kustomize",level:2},{value:"Tagging policies and labels",id:"tagging-policies-and-labels",level:2},{value:"Limitations",id:"limitations",level:2},{value:"Troubleshooting",id:"troubleshooting",level:2},{value:"Jobs, CronJobs or DaemonSets showing zero or low costs",id:"jobs-cronjobs-or-daemonsets-showing-zero-or-low-costs",level:3},{value:"Helm chart resources missing from the estimate",id:"helm-chart-resources-missing-from-the-estimate",level:3}];function c(e){const s={a:"a",admonition:"admonition",code:"code",h2:"h2",h3:"h3",img:"img",li:"li",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,i.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(s.h2,{id:"overview",children:"Overview"}),"\n",(0,t.jsxs)(s.p,{children:["Infracost estimates the cost of Kubernetes workloads defined in plain YAML manifests, ",(0,t.jsx)(s.a,{href:"#helm",children:"Helm"})," charts, and ",(0,t.jsx)(s.a,{href:"#kustomize",children:"Kustomize"})," projects. Manifests are read statically \u2014 no cluster connection is needed \u2014 and Kubernetes resources appear in cost estimates, pull request comments, and governance policies alongside your Terraform, CloudFormation and other resources."]}),"\n",(0,t.jsxs)(s.p,{children:["Kubernetes resources are also checked by policies: Kubernetes labels are read as tags, so your ",(0,t.jsx)(s.a,{href:"#tagging-policies-and-labels",children:"tagging policies"})," evaluate Kubernetes workloads the same way they evaluate cloud resources, and you can scope policies to Kubernetes with the IaC types filter."]}),"\n",(0,t.jsx)(s.p,{children:(0,t.jsx)(s.img,{alt:"Helm GitHub PR with cost estimates and policy checks",src:r(36502).A+"",width:"920",height:"1344"})}),"\n",(0,t.jsx)(s.p,{children:"Infracost auto-detects Kubernetes projects at the directory level. A directory is detected as a Kubernetes project when it is:"}),"\n",(0,t.jsxs)(s.ul,{children:["\n",(0,t.jsxs)(s.li,{children:["a Helm chart root (contains a ",(0,t.jsx)(s.code,{children:"Chart.yaml"}),"),"]}),"\n",(0,t.jsxs)(s.li,{children:["a Kustomize directory (contains a ",(0,t.jsx)(s.code,{children:"kustomization.yaml"}),"), or"]}),"\n",(0,t.jsxs)(s.li,{children:["a directory containing at least one Kubernetes manifest (a YAML file with ",(0,t.jsx)(s.code,{children:"apiVersion"})," and ",(0,t.jsx)(s.code,{children:"kind"}),")."]}),"\n"]}),"\n",(0,t.jsx)(s.p,{children:"For plain-manifest projects, only the files directly in the project directory are parsed together; nested directories are detected as separate projects."}),"\n",(0,t.jsx)(s.h2,{id:"supported-resources",children:"Supported resources"}),"\n",(0,t.jsx)(s.p,{children:"Infracost prices the controllers that own long-running capacity, plus the two resource types that provision billable cloud infrastru
1cture directly:"}),"\n",(0,t.jsxs)(s.table,{children:[(0,t.jsx)(s.thead,{children:(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.th,{children:"Kind"}),(0,t.jsx)(s.th,{children:"What is priced"})]})}),(0,t.jsxs)(s.tbody,{children:[(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:"Deployment"}),(0,t.jsxs)(s.td,{children:[(0,t.jsx)(s.code,{children:"replicas"})," \xd7 sum of container CPU/memory requests"]})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:"StatefulSet"}),(0,t.jsxs)(s.td,{children:[(0,t.jsx)(s.code,{children:"replicas"})," \xd7 container requests, plus one volume per ",(0,t.jsx)(s.code,{children:"volumeClaimTemplate"})," per replica"]})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:"ReplicaSet"}),(0,t.jsxs)(s.td,{children:[(0,t.jsx)(s.code,{children:"replicas"})," \xd7 container requests"]})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:"DaemonSet"}),(0,t.jsxs)(s.td,{children:["node count \xd7 container requests (one pod per node, see ",(0,t.jsx)(s.a,{href:"#usage-values",children:"usage values"}),")"]})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:"Job"}),(0,t.jsxs)(s.td,{children:[(0,t.jsx)(s.code,{children:"completions"})," \xd7 per-run runtime (see ",(0,t.jsx)(s.a,{href:"#usage-values",children:"usage values"}),")"]})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:"CronJob"}),(0,t.jsxs)(s.td,{children:["runs per month (from ",(0,t.jsx)(s.code,{children:"schedule"}),") \xd7 ",(0,t.jsx)(s.code,{children:"completions"})," \xd7 per-run runtime"]})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:"PersistentVolumeClaim"}),(0,t.jsx)(s.td,{children:"one dynamically provisioned cloud disk, priced per GiB-month by storage class"})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsxs)(s.td,{children:["Service (",(0,t.jsx)(s.code,{children:"type: LoadBalancer"})," only)"]}),(0,t.jsx)(s.td,{children:"one cloud load balancer at a fixed hourly rate"})]})]})]}),"\n",(0,t.jsxs)(s.p,{children:["Standalone Pods are not priced \u2014 a bare Pod has no replica or restart semantics to bill steadily. Other kinds (ConfigMaps, Secrets, HorizontalPodAutoscalers, custom resources such as Argo Rollouts, etc.) are not priced; see ",(0,t.jsx)(s.a,{href:"#limitations",children:"Limitations"}),"."]}),"\n",(0,t.jsxs)(s.p,{children:["Resources are named ",(0,t.jsx)(s.code,{children:"namespace.kind.name"})," in the output, for example ",(0,t.jsx)(s.code,{children:"prod.deployment.api"})," or ",(0,t.jsx)(s.code,{children:"prod.statefulset.database"}),". Resources without an explicit ",(0,t.jsx)(s.code,{children:"metadata.namespace"})," use the ",(0,t.jsx)(s.code,{children:"default"})," namespace."]}),"\n",(0,t.jsx)(s.h2,{id:"how-cost-estimation-works",children:"How cost estimation works"}),"\n",(0,t.jsxs)(s.p,{children:["A Kubernetes cluster bills on its underlying nodes, so there is no per-workload cloud price to look up at pull request time. Instead, Infracost applies a flat default rate to each workload's ",(0,t.jsx)(s.strong,{children:"requested"})," CPU and memory, the same approach as ",(0,t.jsx)(s.a,{href:"https://github.com/opencost/opencost/blob/develop/configs/aws.json",children:"OpenCost's default pricing"}),". The result is a default-rate estimate \u2014 useful for comparing workloads and catching cost changes in pull requests \u2014 not a reproduction of your cloud bill. If your cluster's node pools are defined in Terraform, those nodes are priced separately by the AWS/Azure/Google cloud prices."]}),"\n",(0,t.jsxs)(s.p,{children:["Per-pod cost is calculated from the sum of each container's resource ",(0,t.jsx)(s.strong,{children:"requests"}),"; when a container sets no request, its ",(0,t.jsx)(s.strong,{children:"limit"})," is used as a fallback (what the scheduler reserves is the best proxy for allocated cost). Sidecar-style init containers (",(0,t.jsx)(s.code,{children:"restartPolicy: Always"}),") are counted; one-shot init containers and ephemeral containers are excluded so transient startup and debug steps don't inflate the steady-state cost."]}),"\n",(0,t.jsxs)(s.p,{children:["Workloads with no explicit ",(0,t.jsx)(s.code,{children:"replicas"})," field are priced as 1 replica, and a minimum of 1 replica is always applied \u2014 including for workloads with ",(0,t.jsx)(s.code,{children:"replicas: 0"}),"."]}),"\n",(0,t.jsx)(s.h3,{id:"compute-prices",children:"Compute prices"}),"\n",(0,t.jsx)(s.p,{children:"CPU and memory use these hardcoded rates, applied over 730 hours/month:"}),"\n",(0,t.jsxs)(s.table,{children:[(0,t.jsx)(s.thead,{children:(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.th,{children:"Component"}),(0,t.jsx)(s.th,{children:"Rate"}),(0,t.jsx)(s.th,{children:"Approx. monthly"})]})}),(0,t.jsxs)(s.tbody,{children:[(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:"vCPU"}),(0,t.jsx)(s.td,{children:"$0.031611 / vCPU-hour"}),(0,t.jsx)(s.td,{children:"~$23 / vCPU / month"})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:"Memory"}),(0,t.jsx)(s.td,{children:"$0.004237 / GB-hour"}),(0,t.jsx)(s.td,{children:"~$3.09 / GB / month"})]})]})]}),"\n",(0,t.jsx)(s.p,{children:"These are the same blended defaults for all clouds. On serverless platforms such as AWS Fargate and GKE Autopilot, which bill per pod based on requested CPU and memory, the estimate scales one-for-one with what actually drives your bill \u2014 but the rates are still industry defaults, not those platforms' list prices, so treat the numbers as directional rather than
1invoice-accurate."}),"\n",(0,t.jsxs)(s.p,{children:["GPU and other extended resource requests (e.g. ",(0,t.jsx)(s.code,{children:"nvidia.com/gpu"}),") are not priced; see ",(0,t.jsx)(s.a,{href:"#limitations",children:"Limitations"}),"."]}),"\n",(0,t.jsx)(s.h3,{id:"cloud-detection",children:"Cloud detection"}),"\n",(0,t.jsx)(s.p,{children:"Storage and load balancer prices differ by cloud. Since manifests carry no cloud or region field, Infracost infers the cloud from annotations on your resources:"}),"\n",(0,t.jsxs)(s.table,{children:[(0,t.jsx)(s.thead,{children:(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.th,{children:"Annotation signal"}),(0,t.jsx)(s.th,{children:"Cloud"})]})}),(0,t.jsxs)(s.tbody,{children:[(0,t.jsxs)(s.tr,{children:[(0,t.jsxs)(s.td,{children:[(0,t.jsx)(s.code,{children:"eks.amazonaws.com/*"}),", ",(0,t.jsx)(s.code,{children:"service.beta.kubernetes.io/aws-load-balancer-*"}),", AWS CSI provisioners"]}),(0,t.jsx)(s.td,{children:"AWS"})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsxs)(s.td,{children:[(0,t.jsx)(s.code,{children:"iam.gke.io/*"}),", ",(0,t.jsx)(s.code,{children:"cloud.google.com/*"}),", ",(0,t.jsx)(s.code,{children:"networking.gke.io/*"}),", GKE CSI provisioners"]}),(0,t.jsx)(s.td,{children:"Google"})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsxs)(s.td,{children:[(0,t.jsx)(s.code,{children:"azure.workload.identity/*"}),", Azure load balancer annotations, Azure CSI provisioners"]}),(0,t.jsx)(s.td,{children:"Azure"})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:"No match"}),(0,t.jsx)(s.td,{children:"Blended default rates (storage also falls back to a storage class name hint)"})]})]})]}),"\n",(0,t.jsx)(s.h3,{id:"storage-prices",children:"Storage prices"}),"\n",(0,t.jsxs)(s.p,{children:["PersistentVolumeClaims and StatefulSet ",(0,t.jsx)(s.code,{children:"volumeClaimTemplates"})," are priced per GiB-month based on the ",(0,t.jsx)(s.code,{children:"storageClassName"})," and the detected cloud:"]}),"\n",(0,t.jsxs)(s.table,{children:[(0,t.jsx)(s.thead,{children:(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.th,{children:"Cloud"}),(0,t.jsx)(s.th,{children:"Storage classes"}),(0,t.jsx)(s.th,{children:"Default (unknown class)"})]})}),(0,t.jsxs)(s.tbody,{children:[(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:"AWS"}),(0,t.jsxs)(s.td,{children:[(0,t.jsx)(s.code,{children:"gp3"})," $0.08, ",(0,t.jsx)(s.code,{children:"gp2"})," $0.10, ",(0,t.jsx)(s.code,{children:"io1"}),"/",(0,t.jsx)(s.code,{children:"io2"})," $0.125, ",(0,t.jsx)(s.code,{children:"st1"})," $0.045, ",(0,t.jsx)(s.code,{children:"sc1"})," $0.015, ",(0,t.jsx)(s.code,{children:"standard"})," $0.05"]}),(0,t.jsxs)(s.td,{children:["$0.08 (",(0,t.jsx)(s.code,{children:"gp3"}),")"]})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:"Google"}),(0,t.jsxs)(s.td,{children:[(0,t.jsx)(s.code,{children:"pd-standard"})," $0.04, ",(0,t.jsx)(s.code,{children:"pd-balanced"}),"/",(0,t.jsx)(s.code,{children:"standard-rwo"})," $0.10, ",(0,t.jsx)(s.code,{children:"pd-ssd"}),"/",(0,t.jsx)(s.code,{children:"premium-rwo"})," $0.17"]}),(0,t.jsxs)(s.td,{children:["$0.10 (",(0,t.jsx)(s.code,{children:"pd-balanced"}),")"]})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:"Azure"}),(0,t.jsxs)(s.td,{children:[(0,t.jsx)(s.code,{children:"standard_lrs"})," $0.045, ",(0,t.jsx)(s.code,{children:"standardssd_lrs"}),"/",(0,t.jsx)(s.code,{children:"managed-csi"})," $0.075, ",(0,t.jsx)(s.code,{children:"premium_lrs"}),"/",(0,t.jsx)(s.code,{children:"managed-premium"})," $0.135"]}),(0,t.jsxs)(s.td,{children:["$0.075 (",(0,t.jsx)(s.code,{children:"StandardSSD_LRS"}),")"]})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:"Unknown"}),(0,t.jsx)(s.td,{children:"\u2014"}),(0,t.jsx)(s.td,{children:"$0.10 (blended)"})]})]})]}),"\n",(0,t.jsxs)(s.p,{children:["Matching is by the storage class ",(0,t.jsx)(s.strong,{children:"name"})," as written in the manifest, case-insensitively, against the well-known names in the table. StorageClass objects themselves are not read, so a class with a custom name is priced at the detected cloud's default rate rather than the rate of the volume type its ",(0,t.jsx)(s.code,{children:"parameters"})," actually provision \u2014 for example, an EKS StorageClass named ",(0,t.jsx)(s.code,{children:"fast-storage"})," that provisions ",(0,t.jsx)(s.code,{children:"io2"})," volumes is priced at the AWS default of $0.08/GiB (the ",(0,t.jsx)(s.code,{children:"gp3"})," rate), not the ",(0,t.jsx)(s.code,{children:"io2"})," rate."]}),"\n",(0,t.jsx)(s.h3,{id:"load-balancer-prices",children:"Load balancer prices"}),"\n",(0,t.jsxs)(s.p,{children:["A Service with ",(0,t.jsx)(s.code,{children:"type: LoadBalancer"})," is priced at the fixed hourly charge of the load balancer it provisions (data processing charges are not included):"]}),"\n",(0,t.jsxs)(s.table,{children:[(0,t.jsx)(s.thead,{children:(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.th,{children:"Cloud"}),(0,t.jsx)(s.th,{children:"Rate"})]})}),(0,t.jsxs)(s.tbody,{children:[(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:"AWS \u2014 Classic ELB (default)"}),(0,t.jsx)(s.td,{children:"$0.025 / hour"})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsxs)(s.td,{children:["AWS \u2014 NLB (",(0,t.jsx)(s.code,{children:"aws-load-balancer-type: nlb"})," or ",(0,t.jsx)(s.code,{children:"external"})," annotation)"]}),(0,t.jsx)(s.td,{children:"$0.0225 / hour"})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:"Google"}),(0,t.jsx)(s.td,{children:"$0.025 / hour"})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:"Azure"}),(0,t.jsx)(s.td,{children:"$0.025 / hour"})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:"Unknown"}),(0,t.jsx)(s.td,{children:"$0.025 / hour"})]})]})]}),"\n",(0,t.jsx)(s.h2,{id:"usage-values",children:"Usage values"}),"\n",(0,t.jsxs)(s.p,{children:["Some costs depend on runtime state that manifests don't carry. These are read from ",(0,t.jsx)(s.a,{href:"/docs/features/usage_based_resources/",children:"usage values"})," and marked as usage costs in the output:"]}),"\n",(0,t.jsxs)(s.table,{children:[(0,t.jsx)(s.thead,{children:(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.th,{children:"Kind"}),(0,t.jsx)(s.th,{children:"Usage key"}),(0,t.jsx)(s.th,{children:"Default"}),(0,t.jsx)(s.th,{children:"Meaning"})]})}),(0,t.jsxs)(s.tbody,{children:[(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:"DaemonSet"}),(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"nodes"})}),(0,t.jsx)(s.td,{children:"1"}),(0,t.jsx)(s.td,{children:"Number of nodes in the cluster (a DaemonSet runs one pod per node); whole number"})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:"Job / CronJob"}),(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"runtime_hours_per_run"})}),(0,t.jsx)(s.td,{children:"0"}),(0,t.jsxs)(s.td,{children:["How long one pod runs per execution, in hours; decimals allowed, e.g. ",(0,t.jsx)(s.code,{children:"0.25"})," for 15 minutes"]})]})]})]}),"\n",(0,t.jsxs)(s.p,{children:["This means a DaemonSet is priced for a single node, and Jobs and CronJobs show ",(0,t.jsx)(s.strong,{children:"zero cost"}),", until you provide these values. For CronJobs, the runs-per-month is calculated from the ",(0,t.jsx)(s.code,{children:"schedule"})," field (standard cron syntax including ",(0,t.jsx)(s.code,{children:"@daily"}),"-style macros, steps and ranges is supported)."]}),"\n",(0,t.jsxs)(s.p,{children:["Set the values in an ",(0,t.jsx)(s.a,{href:"/docs/features/usage_based_resources/#infracost-usageyml",children:"infracost-usage.yml"})," file, either per resource type or per resource. Kubernetes resources are addressed as ",(0,t.jsx)(s.code,{children:"namespace.kind.name"}),":"]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-yaml",children:"version: 0.1\nresource_type_default_usage:\n daemonset:\n nodes: 25 # applies to all DaemonSets\n cronjob:\n runtime_hours_per_run: 0.5\n\nresource_usage:\n prod.daemonset.log-agent:\n nodes: 50 # overrides the default for this DaemonSet\n prod.cronjob.nightly-report:\n runtime_hours_per_run: 2\n"})}),"\n",(0,t.jsx)(s.admonition,{type:"note",children:(0,t.jsxs)(s.p,{children:["Usage defaults defined in Infracost Cloud (Org Settings > Usage defaults) do not support Kubernetes resource types yet \u2014 use ",(0,t.jsx)(s.code,{children:"infracost-usage.yml"})," files for Kubernetes usage values."]})}),"\n",(0,t.jsx)(s.h2,{id:"helm",children:"Helm"}),"\n",(0,t.jsxs)(s.p,{children:["A directory containing a ",(0,t.jsx)(s.code,{children:"Chart.yaml"})," is rendered through the Helm SDK's ",(0,t.jsx)(s.code,{children:"helm template"})," code path \u2014 client-only, with no cluster \u2014 and the rendered manifests are then priced like plain manifests. Charts render with Helm's standard no-cluster defaults: the ",(0,t.jsx)(s.code,{children:"lookup"}
1)," function returns an empty result (the chart's fallback values are used), and ",(0,t.jsx)(s.code,{children:".Capabilities"})," reports Kubernetes v1.20 with the built-in API versions, so charts that check for newer cluster versions or CRDs take their fallback branch. This is not configurable yet."]}),"\n",(0,t.jsxs)(s.p,{children:["If a chart declares a ",(0,t.jsx)(s.code,{children:"required"})," value that no values file in the repo supplies, that chart fails to render \u2014 you'll see an error naming the missing value, the whole chart contributes nothing to the estimate, and the rest of the run completes normally. Supplying the value in a values file in the repo fixes it, the same as running ",(0,t.jsx)(s.code,{children:"helm template"})," locally."]}),"\n",(0,t.jsx)(s.h3,{id:"values-files",children:"Values files"}),"\n",(0,t.jsxs)(s.p,{children:["The chart's ",(0,t.jsx)(s.code,{children:"values.yaml"})," is always applied. A ",(0,t.jsx)(s.code,{children:"values-<env>.yaml"})," file in the chart root (e.g. ",(0,t.jsx)(s.code,{children:"values-prod.yaml"}),") is detected as an environment and estimated separately with that overlay applied."]}),"\n",(0,t.jsx)(s.p,{children:"Values files must be readable YAML: SOPS-encrypted or sealed values files can't be decrypted at estimate time, so replica counts or resource sizes stored in them fall back to defaults \u2014 typically pricing those workloads at or near zero. Keep cost-relevant values such as replicas and resource requests in plaintext values files; secret values never affect the estimate."}),"\n",(0,t.jsx)(s.h3,{id:"dependencies",children:"Dependencies"}),"\n",(0,t.jsxs)(s.p,{children:["Subcharts vendored under ",(0,t.jsx)(s.code,{children:"charts/"})," are rendered and priced as part of your deployment \u2014 a bundled PostgreSQL chart shows up with its own workloads and volumes, attributed to the vendored chart files. Chart conditions and tags from ",(0,t.jsx)(s.code,{children:"Chart.yaml"})," are honored."]}),"\n",(0,t.jsxs)(s.p,{children:["Dependencies declared in ",(0,t.jsx)(s.code,{children:"Chart.yaml"})," but not vendored are downloaded before rendering. Classic ",(0,t.jsx)(s.code,{children:"http(s)"})," chart repositories, ",(0,t.jsx)(s.code,{children:"git::"})," sources, ",(0,t.jsx)(s.code,{children:"s3://"})," buckets, and direct archive URLs are supported. Dependencies hosted in ",(0,t.jsx)(s.strong,{children:"OCI registries"})," (",(0,t.jsx)(s.code,{children:"oci://"}),") can't be fetched yet \u2014 you'll see an explicit fetch error for that dependency and the rest of the chart is still estimated."]}),"\n",(0,t.jsx)(s.p,{children:"Helm hooks are included in the render. Since hooks are almost always short-lived Jobs, they show no monthly cost unless you provide runtime usage data \u2014 which matches reality, as a migration job that runs for minutes doesn't move a monthly bill."}),"\n",(0,t.jsxs)(s.p,{children:["Helmfile is not supported: charts stored in the repo are detected and priced individually as Helm projects, but ",(0,t.jsx)(s.code,{children:"helmfile.yaml"})," itself \u2014 its releases, environments and value layering \u2014 is not read."]}),"\n",(0,t.jsx)(s.h2,{id:"kustomize",children:"Kustomize"}),"\n",(0,t.jsxs)(s.p,{children:["Kustomize projects are resolved statically: Infracost follows the ",(0,t.jsx)(s.code,{children:"resources:"}),"/",(0,t.jsx)(s.code,{children:"bases:"}),"/",(0,t.jsx)(s.code,{children:"components:"})," chain, applies patches and transformers, and prices the result. A base plus its overlays is treated as one project with one environment per overlay. Remote bases over git, ",(0,t.jsx)(s.code,{children:"http(s)"}),", ",(0,t.jsx)(s.code,{children:"s3"}),", and ",(0,t.jsx)(s.code,{children:"gcs"})," are downloaded automatically."]}),"\n",(0,t.jsxs)(s.table,{children:[(0,t.jsx)(s.thead,{children:(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.th,{children:"Feature"}),(0,t.jsx)(s.th,{children:"Support"}),(0,t.jsx)(s.th,{children:"Notes"})]})}),(0,t.jsxs)(s.tbody,{children:[(0,t.jsxs)(s.tr,{children:[(0,t.jsxs)(s.td,{children:[(0,t.jsx)(s.code,{children:"resources:"})," / ",(0,t.jsx)(s.code,{children:"bases:"})]}),(0,t.jsx)(s.td,{children:"\u2705"}),(0,t.jsx)(s.td,{children:"Files, directories, and remote references"})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"components:"})}),(0,t.jsx)(s.td,{children:"\u2705"}),(0,t.jsx)(s.td,{})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"replicas:"})}),(0,t.jsx)(s.td,{children:"\u2705"}),(0,t.jsx)(s.td,{})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"patches:"})}),(0,t.jsx)(s.td,{children:"\u2705"}),(0,t.jsxs)(s.td,{children:["Strategic-merge and JSON 6902, targeted by kind/name/namespace. ",(0,t.jsx)(s.code,{children:"labelSelector"}),"/",(0,t.jsx)(s.code,{children:"annotationSelector"})," targets are not supported \u2014 a warning is shown and the patch is matched on kind/name only"]})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"patchesStrategicMerge:"})}),(0,t.jsx)(s.td,{children:"\u2705"}),(0,t.jsxs)(s.td,{children:[(0,t.jsx)(s.code,{children:"$patch: delete"}),"/",(0,t.jsx)(s.code,{children:"replace"})," directives are not honored \u2014 a warning is shown and the patch is merged structurally"]})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"patchesJson6902:"})}),(0,t.jsx)(s.td,{children:"\u2705"}),(0,t.jsxs)(s.td,{children:[(0,t.jsx)(s.code,{children:"add"}),"/",(0,t.jsx)(s.code,{children:"replace"}),"/",(0,t.jsx)(s.code,{children:"remove"})," operations; ",(0,t.jsx)(s.code,{children:"move"}),"/",(0,t.jsx)(s.code,{children:"copy"}),"/",(0,t.jsx)(s.code,{children:"test"})," are skipped with a warning"]})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsxs)(s.td,{children:[(0,t.jsx)(s.code,{children:"namePrefix"})," / ",(0,t.jsx)(s.code,{children:"nameSuffix"})," / ",(0,t.jsx)(s.code,{children:"namespace"})]}),(0,t.jsx)(s.td,{children:"\u2705"}),(0,t.jsx)(s.td,{})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsxs)(s.td,{children:[(0,t.jsx)(s.code,{children:"commonLabels"})," / ",(0,t.jsx)(s.code,{children:"labels"})," / ",(0,t.jsx)(s.code,{children:"commonAnnotations"})]}),(0,t.jsx)(s.td,{children:"\u2705"}),(0,t.jsx)(s.td,{children:"Applied before tagging policies and cloud detection run"})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"images:"})}),(0,t.jsx)(s.td,{children:"\u2014"}),(0,t.jsx)(s.td,{children:"Ignored; doesn't affect costs"})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsxs)(s.td,{children:[(0,t.jsx)(s.code,{children:"configMapGenerator"})," / ",(0,t.jsx)(s.code,{children:"secretGenerator"})]}),(0,t.jsx)(s.td,{children:"\u2014"}),(0,t.jsx)(s.td,{children:"Ignored; generated objects aren't priced"})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsxs)(s.td,{children:[(0,t.jsx)(s.code,{children:"vars:"})," / ",(0,t.jsx)(s.code,{children:"replacements:"})]}),(0,t.jsx)(s.td,{children:"\u274c"}),(0,t.jsxs)(s.td,{children:["Not applied, with no warning \u2014 if you use ",(0,t.jsx)(s.code,{children:"replacements:"})," to set replica counts or resource requests, those changes are not reflected in the estimate"]})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsxs)(s.td,{children:[(0,t.jsx)(s.code,{children:"helmCharts:"})," / plugin ",(0,t.jsx)(s.code,{children:"generators"})," / ",(0,t.jsx)(s.code,{children:"transformers"})]}),(0,t.jsx)(s.td,{children:"\u274c"}),(0,t.jsx)(s.td,{children:"A warning is shown; workloads they produce aren't priced. If the chart lives in your repo, point Infracost at the chart directory itself"})]})]})]}),"\n",(0,t.jsx)(s.h2,{id:"tagging-policies-and-labels",children:"Tagging policies and labels"}),"\n",(0,t.jsxs)(s.p,{children:["Kubernetes labels are treated as tags, so ",(0,t.jsx)(s.a,{href:"/docs/infracost_cloud/tagging_policies/",children:"tagging policies"})," evaluate Kubernetes resources the same way they evaluate Terraform resources. Labels are read from the workload object's own ",(0,t.jsx)(s.code,{children:"metadata.labels"})," \u2014 after Helm templating and Kustomize label transformers are applied, so labels added by chart helpers such as ",(0,t.jsx)(s.code,{children:"_helpers.tpl"})," count. Labels set only on the pod template inside a workload are not read, so put governance labels on the workload's own metadata."]}),"\n",(0,t.jsxs)(s.p,{children:["Annotations are ",(0,t.jsx)(s.strong,{children:"not"})," checked as tags \u2014 only labels are. Some teams record metadata such as ownership in annotations because annotation values allow characters that label values don't (for example, ",(0,t.jsx)(s.code,{children:"owner: [email protected]"})," is not a valid label value). Values stored in annotations are not visible to tagging policies, so any key you want a policy to enforce must be set as a label."]}),"\n",(0,t.jsx)(s.p,{children:(0,t.jsx)(s.img,{alt:"Scope a tagging policy to Kubernetes with the IaC types filter",src:r(32495).A+"",width:"1600",height:"1054"})}),"\n",(0,t.jsxs)(s.p,{children:["Tagging policies apply to every IaC type by default, so an existing organization-wide tagging policy will start evaluating Kubernetes resources as
1soon as Kubernetes support is enabled. Since Kubernetes tagging conventions usually differ from cloud tagging conventions, we recommend using the policy's IaC types filter to give Kubernetes projects their own tagging policy \u2014 see ",(0,t.jsx)(s.a,{href:"/docs/infracost_cloud/tagging_policies/#kubernetes-tagging-policies",children:"Kubernetes tagging policies"})," for the recommended setup."]}),"\n",(0,t.jsx)(s.h2,{id:"limitations",children:"Limitations"}),"\n",(0,t.jsxs)(s.ul,{children:["\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.strong,{children:"Autoscalers are not evaluated."})," Estimates are a single monthly figure based on the manifest's ",(0,t.jsx)(s.code,{children:"replicas"})," value; HorizontalPodAutoscaler and VerticalPodAutoscaler objects are skipped. Since Deployments managed by an HPA usually omit ",(0,t.jsx)(s.code,{children:"spec.replicas"}),", they are priced as 1 replica \u2014 set ",(0,t.jsx)(s.code,{children:"spec.replicas"})," to your typical steady-state count for a more representative estimate."]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.strong,{children:"GPUs and extended resources are not priced."})," Only CPU and memory requests/limits are read; ",(0,t.jsx)(s.code,{children:"nvidia.com/gpu"})," and similar requests are skipped without a warning, so GPU-heavy workloads are estimated significantly below their real cost."]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.strong,{children:"LimitRange defaults are not applied."})," Workloads whose sizes come from a namespace LimitRange instead of explicit requests show zero compute cost. Setting requests (or limits) in the workload manifest fixes this."]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.strong,{children:"Bare Pods are not priced"}),", and neither are custom resources such as Argo Rollouts, Knative Services, or OpenShift DeploymentConfigs."]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.strong,{children:"Terraform-managed Kubernetes resources are not priced."})," Resources from the Terraform ",(0,t.jsx)(s.code,{children:"kubernetes"})," and ",(0,t.jsx)(s.code,{children:"helm"})," providers (",(0,t.jsx)(s.code,{children:"kubernetes_deployment"}),", ",(0,t.jsx)(s.code,{children:"helm_release"}),", etc.) are treated like any other unsupported Terraform resource type. Kubernetes pricing applies to YAML manifests, Helm charts and Kustomize projects."]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.strong,{children:"Template languages are not executed."})," Jsonnet, ytt, cdk8s, and Pulumi programs are not run \u2014 but if your pipeline commits the rendered YAML into the repo, that output is picked up and priced like any other manifest."]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.strong,{children:"Scaled-to-zero workloads are priced as one replica"}),", since a minimum of 1 replica is always applied."]}),"\n"]}),"\n",(0,t.jsx)(s.h2,{id:"troubleshooting",children:"Troubleshooting"}),"\n",(0,t.jsxs)(s.p,{children:["If you run into any issues, please join our ",(0,t.jsx)(s.a,{href:"https://www.infracost.io/community-chat",children:"community Slack channel"}),", we'll help you very quickly \ud83d\ude04"]}),"\n",(0,t.jsx)(s.h3,{id:"jobs-cronjobs-or-daemonsets-showing-zero-or-low-costs",children:"Jobs, CronJobs or DaemonSets showing zero or low costs"}),"\n",(0,t.jsxs)(s.p,{children:[(0,t.jsx)(s.strong,{children:"Common causes"}),": Jobs and CronJobs cost $0 until you provide ",(0,t.jsx)(s.code,{children:"runtime_hours_per_run"}),", and DaemonSets assume a 1-node cluster until you provide ",(0,t.jsx)(s.code,{children:"nodes"}),"."]}),"\n",(0,t.jsxs)(s.p,{children:[(0,t.jsx)(s.strong,{children:"Solution"}),": Add the ",(0,t.jsx)(s.a,{href:"#usage-values",children:"usage values"})," to an ",(0,t.jsx)(s.code,{children:"infracost-usage.yml"})," file."]}),"\n",(0,t.jsx)(s.h3,{id:"helm-chart-resources-missing-from-the-estimate",children:"Helm chart resources missing from the estimate"}),"\n",(0,t.jsxs)(s.p,{children:[(0,t.jsx)(s.strong,{children:"Common causes"}),": A chart failed to render (e.g. a ",(0,t.jsx)(s.code,{children:"required"})," value has no value in the repo, or a dependency couldn't be fetched) \u2014 any render error excludes the entire chart, and the error is shown in the output. Or cost-relevant values live in an encrypted values file."]}),"\n",(0,t.jsxs)(s.p,{children:[(0,t.jsx)(s.strong,{children:"Solution"}),": Check the output for the render or fetch error; supply missing values via a values file in the repo, and keep replicas and resource requests in plaintext values files."]})]})}function h(e={}){const{wrapper:s}={...(0,i.R)(),...e.components};return s?(0,t.jsx)(s,{...e,children:(0,t.jsx)(c,{...e})}):c(e)}},32495(e,s,r){const n=r.p+"assets/images/k8s-filters-41f8374ccf9dd7ac3f3d5dc8f33f2c3a.png";r.d(s,["A",0,n])},36502(e,s,r){const n=r.p+"assets/images/k8s-pr-d87b6550110b27d38d4b9fa724f73fc5.png";r.d(s,["A",0,n])},28453(e,s,r){r.d(s,{R:()=>d,x:()=>o});var n=r(96540);const t={},i=n.createContext(t);function d(e){const s=n.useContext(i);return n.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function o(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:d(e.components),n.createElement(i.Provider,{value:s},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.