Bläddra i källkod

Nat Gateway Network Costs Support (#3534)

Signed-off-by: Matt Bolt <mbolt35@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Matt Bolt 3 månader sedan
förälder
incheckning
4bdf813a30
38 ändrade filer med 1214 tillägg och 117 borttagningar
  1. 2 0
      configs/alibaba.json
  2. 4 2
      configs/aws.json
  3. 3 1
      configs/azure.json
  4. 3 1
      configs/default.json
  5. 3 1
      configs/gcp.json
  6. 4 2
      configs/oracle.json
  7. 4 2
      configs/otc.json
  8. 21 0
      core/pkg/opencost/allocation.go
  9. 2 0
      core/pkg/opencost/allocation_test.go
  10. 1 1
      core/pkg/opencost/bingen.go
  11. 29 9
      core/pkg/opencost/opencost_codecs.go
  12. 4 0
      core/pkg/source/datasource.go
  13. 17 0
      core/pkg/source/decoders.go
  14. 94 0
      modules/collector-source/pkg/collector/collector.go
  15. 16 0
      modules/collector-source/pkg/collector/metricsquerier.go
  16. 4 0
      modules/collector-source/pkg/metric/collector.go
  17. 18 16
      modules/collector-source/pkg/metric/metrics.go
  18. 74 0
      modules/prometheus-source/pkg/prom/metricsquerier.go
  19. 10 0
      pkg/cloud/alibaba/provider.go
  20. 10 0
      pkg/cloud/aws/provider.go
  21. 10 0
      pkg/cloud/azure/provider.go
  22. 14 4
      pkg/cloud/digitalocean/provider.go
  23. 10 0
      pkg/cloud/gcp/provider.go
  24. 2 0
      pkg/cloud/gcp/provider_test.go
  25. 3 1
      pkg/cloud/models/models.go
  26. 4 0
      pkg/cloud/models/models_test.go
  27. 2 0
      pkg/cloud/models/network.go
  28. 2 0
      pkg/cloud/oracle/ratecard.go
  29. 10 0
      pkg/cloud/otc/provider.go
  30. 10 0
      pkg/cloud/provider/customprovider.go
  31. 28 12
      pkg/cloud/provider/providerconfig.go
  32. 2 0
      pkg/cloud/scaleway/provider.go
  33. 12 0
      pkg/costmodel/allocation.go
  34. 8 0
      pkg/costmodel/allocation_helpers.go
  35. 9 5
      pkg/costmodel/costmodel.go
  36. 75 51
      pkg/costmodel/metrics.go
  37. 72 9
      pkg/costmodel/networkcosts.go
  38. 618 0
      pkg/costmodel/networkcosts_test.go

+ 2 - 0
configs/alibaba.json

@@ -12,5 +12,7 @@
     "zoneNetworkEgress": "0.02",
     "zoneNetworkEgress": "0.02",
     "regionNetworkEgress": "0.08",
     "regionNetworkEgress": "0.08",
     "internetNetworkEgress": "0.123",
     "internetNetworkEgress": "0.123",
+    "natGatewayEgress": "0.045",
+    "natGatewayIngress": "0.045",
     "defaultLBPrice": "0.007"
     "defaultLBPrice": "0.007"
 }
 }

+ 4 - 2
configs/aws.json

@@ -10,11 +10,13 @@
     "zoneNetworkEgress": "0.01",
     "zoneNetworkEgress": "0.01",
     "regionNetworkEgress": "0.01",
     "regionNetworkEgress": "0.01",
     "internetNetworkEgress": "0.143",
     "internetNetworkEgress": "0.143",
+    "natGatewayEgress": "0.045",
+    "natGatewayIngress": "0.045",
     "spotLabel": "kops.k8s.io/instancegroup",
     "spotLabel": "kops.k8s.io/instancegroup",
     "spotLabelValue": "spotinstance-nodes",
     "spotLabelValue": "spotinstance-nodes",
     "awsServiceKeyName": "",
     "awsServiceKeyName": "",
     "awsServiceKeySecret": "",
     "awsServiceKeySecret": "",
-    "awsSpotDataRegion":"",
+    "awsSpotDataRegion": "",
     "awsSpotDataBucket": "",
     "awsSpotDataBucket": "",
     "awsSpotDataPrefix": "",
     "awsSpotDataPrefix": "",
     "athenaBucketName": "",
     "athenaBucketName": "",
@@ -22,4 +24,4 @@
     "athenaDatabase": "",
     "athenaDatabase": "",
     "athenaTable": "",
     "athenaTable": "",
     "projectID": ""
     "projectID": ""
-}
+}

+ 3 - 1
configs/azure.json

@@ -10,10 +10,12 @@
     "zoneNetworkEgress": "0.01",
     "zoneNetworkEgress": "0.01",
     "regionNetworkEgress": "0.01",
     "regionNetworkEgress": "0.01",
     "internetNetworkEgress": "0.0725",
     "internetNetworkEgress": "0.0725",
+    "natGatewayEgress": "0.045",
+    "natGatewayIngress": "0.045",
     "spotLabel": "kubernetes.azure.com/scalesetpriority",
     "spotLabel": "kubernetes.azure.com/scalesetpriority",
     "spotLabelValue": "spot",
     "spotLabelValue": "spot",
     "azureSubscriptionID": "",
     "azureSubscriptionID": "",
     "azureClientID": "",
     "azureClientID": "",
     "azureClientSecret": "",
     "azureClientSecret": "",
     "azureTenantID": ""
     "azureTenantID": ""
-}
+}

+ 3 - 1
configs/default.json

@@ -9,5 +9,7 @@
     "storage": "0.00005479452",
     "storage": "0.00005479452",
     "zoneNetworkEgress": "0.01",
     "zoneNetworkEgress": "0.01",
     "regionNetworkEgress": "0.01",
     "regionNetworkEgress": "0.01",
-    "internetNetworkEgress": "0.12"
+    "internetNetworkEgress": "0.12",
+    "natGatewayEgress": "0.045",
+    "natGatewayIngress": "0.045"
 }
 }

+ 3 - 1
configs/gcp.json

@@ -10,5 +10,7 @@
     "zoneNetworkEgress": "0.01",
     "zoneNetworkEgress": "0.01",
     "regionNetworkEgress": "0.01",
     "regionNetworkEgress": "0.01",
     "internetNetworkEgress": "0.12",
     "internetNetworkEgress": "0.12",
+    "natGatewayEgress": "0.045",
+    "natGatewayIngress": "0.045",
     "billingDataDataset": ""
     "billingDataDataset": ""
-}
+}

+ 4 - 2
configs/oracle.json

@@ -1,9 +1,11 @@
 {
 {
-  "provider":"Oracle",
+  "provider": "Oracle",
   "CPU": "0.015",
   "CPU": "0.015",
   "RAM": "0.002",
   "RAM": "0.002",
   "GPU": "2.00",
   "GPU": "2.00",
   "storage": "0.00005479452",
   "storage": "0.00005479452",
   "defaultLBPrice": "0.0113",
   "defaultLBPrice": "0.0113",
-  "internetNetworkEgress": "0.0085"
+  "internetNetworkEgress": "0.0085",
+  "natGatewayEgress": "0.045",
+  "natGatewayIngress": "0.045"
 }
 }

+ 4 - 2
configs/otc.json

@@ -6,5 +6,7 @@
     "storage": "0.0",
     "storage": "0.0",
     "zoneNetworkEgress": "0.0",
     "zoneNetworkEgress": "0.0",
     "regionNetworkEgress": "0.0",
     "regionNetworkEgress": "0.0",
-    "internetNetworkEgress": "0.0"
-}
+    "internetNetworkEgress": "0.0",
+    "natGatewayEgress": "0.0",
+    "natGatewayIngress": "0.0"
+}

+ 21 - 0
core/pkg/opencost/allocation.go

@@ -106,6 +106,9 @@ type Allocation struct {
 	GPUAllocation               *GPUAllocation `json:"GPUAllocation"`       //@bingen:field[version=23]
 	GPUAllocation               *GPUAllocation `json:"GPUAllocation"`       //@bingen:field[version=23]
 	CPUCoreLimitAverage         float64        `json:"cpuCoreLimitAverage"` //@bingen:field[version=24]
 	CPUCoreLimitAverage         float64        `json:"cpuCoreLimitAverage"` //@bingen:field[version=24]
 	RAMBytesLimitAverage        float64        `json:"ramByteLimitAverage"` //@bingen:field[version=24]
 	RAMBytesLimitAverage        float64        `json:"ramByteLimitAverage"` //@bingen:field[version=24]
+	// NAT Gateway Costs
+	NetworkNatGatewayEgressCost  float64 `json:"networkNatGatewayEgressCost"`  //@bingen:field[version=25]
+	NetworkNatGatewayIngressCost float64 `json:"networkNatGatewayIngressCost"` //@bingen:field[version=25]
 }
 }
 
 
 type GPUAllocation struct {
 type GPUAllocation struct {
@@ -757,6 +760,8 @@ func (a *Allocation) Clone() *Allocation {
 		NetworkCrossZoneCost:           a.NetworkCrossZoneCost,
 		NetworkCrossZoneCost:           a.NetworkCrossZoneCost,
 		NetworkCrossRegionCost:         a.NetworkCrossRegionCost,
 		NetworkCrossRegionCost:         a.NetworkCrossRegionCost,
 		NetworkInternetCost:            a.NetworkInternetCost,
 		NetworkInternetCost:            a.NetworkInternetCost,
+		NetworkNatGatewayEgressCost:    a.NetworkNatGatewayEgressCost,
+		NetworkNatGatewayIngressCost:   a.NetworkNatGatewayIngressCost,
 		NetworkCostAdjustment:          a.NetworkCostAdjustment,
 		NetworkCostAdjustment:          a.NetworkCostAdjustment,
 		LoadBalancerCost:               a.LoadBalancerCost,
 		LoadBalancerCost:               a.LoadBalancerCost,
 		LoadBalancerCostAdjustment:     a.LoadBalancerCostAdjustment,
 		LoadBalancerCostAdjustment:     a.LoadBalancerCostAdjustment,
@@ -847,6 +852,12 @@ func (a *Allocation) Equal(that *Allocation) bool {
 	if !util.IsApproximately(a.NetworkInternetCost, that.NetworkInternetCost) {
 	if !util.IsApproximately(a.NetworkInternetCost, that.NetworkInternetCost) {
 		return false
 		return false
 	}
 	}
+	if !util.IsApproximately(a.NetworkNatGatewayEgressCost, that.NetworkNatGatewayEgressCost) {
+		return false
+	}
+	if !util.IsApproximately(a.NetworkNatGatewayIngressCost, that.NetworkNatGatewayIngressCost) {
+		return false
+	}
 	if !util.IsApproximately(a.NetworkCostAdjustment, that.NetworkCostAdjustment) {
 	if !util.IsApproximately(a.NetworkCostAdjustment, that.NetworkCostAdjustment) {
 		return false
 		return false
 	}
 	}
@@ -1409,6 +1420,8 @@ func (a *Allocation) add(that *Allocation) {
 	a.NetworkCrossZoneCost += that.NetworkCrossZoneCost
 	a.NetworkCrossZoneCost += that.NetworkCrossZoneCost
 	a.NetworkCrossRegionCost += that.NetworkCrossRegionCost
 	a.NetworkCrossRegionCost += that.NetworkCrossRegionCost
 	a.NetworkInternetCost += that.NetworkInternetCost
 	a.NetworkInternetCost += that.NetworkInternetCost
+	a.NetworkNatGatewayEgressCost += that.NetworkNatGatewayEgressCost
+	a.NetworkNatGatewayIngressCost += that.NetworkNatGatewayIngressCost
 	a.LoadBalancerCost += that.LoadBalancerCost
 	a.LoadBalancerCost += that.LoadBalancerCost
 	a.SharedCost += that.SharedCost
 	a.SharedCost += that.SharedCost
 	a.ExternalCost += that.ExternalCost
 	a.ExternalCost += that.ExternalCost
@@ -2766,6 +2779,14 @@ func (a *Allocation) SanitizeNaN() {
 		log.DedupedWarningf(5, "Allocation: Unexpected NaN found for NetworkInternetCost name:%s, window:%s, properties:%s", a.Name, a.Window.String(), a.Properties.String())
 		log.DedupedWarningf(5, "Allocation: Unexpected NaN found for NetworkInternetCost name:%s, window:%s, properties:%s", a.Name, a.Window.String(), a.Properties.String())
 		a.NetworkInternetCost = 0
 		a.NetworkInternetCost = 0
 	}
 	}
+	if math.IsNaN(a.NetworkNatGatewayEgressCost) {
+		log.DedupedWarningf(5, "Allocation: Unexpected NaN found for NetworkNatGatewayEgressCost name:%s, window:%s, properties:%s", a.Name, a.Window.String(), a.Properties.String())
+		a.NetworkNatGatewayEgressCost = 0
+	}
+	if math.IsNaN(a.NetworkNatGatewayIngressCost) {
+		log.DedupedWarningf(5, "Allocation: Unexpected NaN found for NetworkNatGatewayIngressCost name:%s, window:%s, properties:%s", a.Name, a.Window.String(), a.Properties.String())
+		a.NetworkNatGatewayIngressCost = 0
+	}
 	if math.IsNaN(a.NetworkCostAdjustment) {
 	if math.IsNaN(a.NetworkCostAdjustment) {
 		log.DedupedWarningf(5, "Allocation: Unexpected NaN found for NetworkCostAdjustment name:%s, window:%s, properties:%s", a.Name, a.Window.String(), a.Properties.String())
 		log.DedupedWarningf(5, "Allocation: Unexpected NaN found for NetworkCostAdjustment name:%s, window:%s, properties:%s", a.Name, a.Window.String(), a.Properties.String())
 		a.NetworkCostAdjustment = 0
 		a.NetworkCostAdjustment = 0

+ 2 - 0
core/pkg/opencost/allocation_test.go

@@ -3734,6 +3734,8 @@ func getMockAllocation(f float64) Allocation {
 		NetworkCrossRegionCost:         f,
 		NetworkCrossRegionCost:         f,
 		NetworkInternetCost:            f,
 		NetworkInternetCost:            f,
 		NetworkCostAdjustment:          f,
 		NetworkCostAdjustment:          f,
+		NetworkNatGatewayEgressCost:    f,
+		NetworkNatGatewayIngressCost:   f,
 		LoadBalancerCost:               f,
 		LoadBalancerCost:               f,
 		LoadBalancerCostAdjustment:     f,
 		LoadBalancerCostAdjustment:     f,
 		PVs:                            PVAllocations{{Cluster: "testPV", Name: "PVName"}: getMockPVAllocation(math.NaN())},
 		PVs:                            PVAllocations{{Cluster: "testPV", Name: "PVName"}: getMockPVAllocation(math.NaN())},

+ 1 - 1
core/pkg/opencost/bingen.go

@@ -44,7 +44,7 @@ package opencost
 // @bingen:end
 // @bingen:end
 
 
 // Allocation Version Set: Includes Allocation pipeline specific resources
 // Allocation Version Set: Includes Allocation pipeline specific resources
-// @bingen:set[name=Allocation,version=24]
+// @bingen:set[name=Allocation,version=25]
 // @bingen:generate[migrate]:Allocation
 // @bingen:generate[migrate]:Allocation
 // @bingen:generate[stringtable]:AllocationSet
 // @bingen:generate[stringtable]:AllocationSet
 // @bingen:generate:AllocationSetRange
 // @bingen:generate:AllocationSetRange

+ 29 - 9
core/pkg/opencost/opencost_codecs.go

@@ -33,6 +33,12 @@ const (
 )
 )
 
 
 const (
 const (
+	// CloudCostCodecVersion is used for any resources listed in the CloudCost version set
+	CloudCostCodecVersion uint8 = 3
+
+	// NetworkInsightCodecVersion is used for any resources listed in the NetworkInsight version set
+	NetworkInsightCodecVersion uint8 = 1
+
 	// DefaultCodecVersion is used for any resources listed in the Default version set
 	// DefaultCodecVersion is used for any resources listed in the Default version set
 	DefaultCodecVersion uint8 = 18
 	DefaultCodecVersion uint8 = 18
 
 
@@ -40,13 +46,7 @@ const (
 	AssetsCodecVersion uint8 = 21
 	AssetsCodecVersion uint8 = 21
 
 
 	// AllocationCodecVersion is used for any resources listed in the Allocation version set
 	// AllocationCodecVersion is used for any resources listed in the Allocation version set
-	AllocationCodecVersion uint8 = 24
-
-	// CloudCostCodecVersion is used for any resources listed in the CloudCost version set
-	CloudCostCodecVersion uint8 = 3
-
-	// NetworkInsightCodecVersion is used for any resources listed in the NetworkInsight version set
-	NetworkInsightCodecVersion uint8 = 1
+	AllocationCodecVersion uint8 = 25
 )
 )
 
 
 //--------------------------------------------------------------------------
 //--------------------------------------------------------------------------
@@ -477,8 +477,10 @@ func (target *Allocation) MarshalBinaryWithContext(ctx *EncodingContext) (err er
 		// --- [end][write][struct](GPUAllocation) ---
 		// --- [end][write][struct](GPUAllocation) ---
 
 
 	}
 	}
-	buff.WriteFloat64(target.CPUCoreLimitAverage)  // write float64
-	buff.WriteFloat64(target.RAMBytesLimitAverage) // write float64
+	buff.WriteFloat64(target.CPUCoreLimitAverage)          // write float64
+	buff.WriteFloat64(target.RAMBytesLimitAverage)         // write float64
+	buff.WriteFloat64(target.NetworkNatGatewayEgressCost)  // write float64
+	buff.WriteFloat64(target.NetworkNatGatewayIngressCost) // write float64
 	return nil
 	return nil
 }
 }
 
 
@@ -848,6 +850,24 @@ func (target *Allocation) UnmarshalBinaryWithContext(ctx *DecodingContext) (err
 		target.RAMBytesLimitAverage = float64(0) // default
 		target.RAMBytesLimitAverage = float64(0) // default
 	}
 	}
 
 
+	// field version check
+	if uint8(25) <= version {
+		mmm := buff.ReadFloat64() // read float64
+		target.NetworkNatGatewayEgressCost = mmm
+
+	} else {
+		target.NetworkNatGatewayEgressCost = float64(0) // default
+	}
+
+	// field version check
+	if uint8(25) <= version {
+		nnn := buff.ReadFloat64() // read float64
+		target.NetworkNatGatewayIngressCost = nnn
+
+	} else {
+		target.NetworkNatGatewayIngressCost = float64(0) // default
+	}
+
 	// execute migration func if version delta detected
 	// execute migration func if version delta detected
 	if version != AllocationCodecVersion {
 	if version != AllocationCodecVersion {
 		migrateAllocation(target, version, AllocationCodecVersion)
 		migrateAllocation(target, version, AllocationCodecVersion)

+ 4 - 0
core/pkg/source/datasource.go

@@ -93,6 +93,8 @@ type MetricsQuerier interface {
 	QueryNetInternetGiB(start, end time.Time) *Future[NetInternetGiBResult]
 	QueryNetInternetGiB(start, end time.Time) *Future[NetInternetGiBResult]
 	QueryNetInternetPricePerGiB(start, end time.Time) *Future[NetInternetPricePerGiBResult]
 	QueryNetInternetPricePerGiB(start, end time.Time) *Future[NetInternetPricePerGiBResult]
 	QueryNetInternetServiceGiB(start, end time.Time) *Future[NetInternetServiceGiBResult]
 	QueryNetInternetServiceGiB(start, end time.Time) *Future[NetInternetServiceGiBResult]
+	QueryNetNatGatewayPricePerGiB(start, end time.Time) *Future[NetNatGatewayPricePerGiBResult]
+	QueryNetNatGatewayGiB(start, end time.Time) *Future[NetNatGatewayGiBResult]
 	QueryNetTransferBytes(start, end time.Time) *Future[NetTransferBytesResult]
 	QueryNetTransferBytes(start, end time.Time) *Future[NetTransferBytesResult]
 
 
 	// Network Ingress
 	// Network Ingress
@@ -100,6 +102,8 @@ type MetricsQuerier interface {
 	QueryNetRegionIngressGiB(start, end time.Time) *Future[NetRegionIngressGiBResult]
 	QueryNetRegionIngressGiB(start, end time.Time) *Future[NetRegionIngressGiBResult]
 	QueryNetInternetIngressGiB(start, end time.Time) *Future[NetInternetIngressGiBResult]
 	QueryNetInternetIngressGiB(start, end time.Time) *Future[NetInternetIngressGiBResult]
 	QueryNetInternetServiceIngressGiB(start, end time.Time) *Future[NetInternetServiceIngressGiBResult]
 	QueryNetInternetServiceIngressGiB(start, end time.Time) *Future[NetInternetServiceIngressGiBResult]
+	QueryNetNatGatewayIngressPricePerGiB(start, end time.Time) *Future[NetNatGatewayPricePerGiBResult]
+	QueryNetNatGatewayIngressGiB(start, end time.Time) *Future[NetNatGatewayIngressGiBResult]
 	QueryNetReceiveBytes(start, end time.Time) *Future[NetReceiveBytesResult]
 	QueryNetReceiveBytes(start, end time.Time) *Future[NetReceiveBytesResult]
 
 
 	// Annotations
 	// Annotations

+ 17 - 0
core/pkg/source/decoders.go

@@ -45,6 +45,7 @@ const (
 	InternetLabel        = "internet"
 	InternetLabel        = "internet"
 	SameZoneLabel        = "same_zone"
 	SameZoneLabel        = "same_zone"
 	SameRegionLabel      = "same_region"
 	SameRegionLabel      = "same_region"
+	NatGatewayLabel      = "nat_gateway"
 )
 )
 
 
 const (
 const (
@@ -1115,10 +1116,14 @@ type NetInternetPricePerGiBResult = NetworkPricePerGiBResult
 
 
 type NetInternetServiceGiBResult = NetworkGiBResult
 type NetInternetServiceGiBResult = NetworkGiBResult
 
 
+type NetNatGatewayPricePerGiBResult = NetworkPricePerGiBResult
+type NetNatGatewayGiBResult = NetworkGiBResult
+
 type NetZoneIngressGiBResult = NetworkGiBResult
 type NetZoneIngressGiBResult = NetworkGiBResult
 type NetRegionIngressGiBResult = NetworkGiBResult
 type NetRegionIngressGiBResult = NetworkGiBResult
 type NetInternetIngressGiBResult = NetworkGiBResult
 type NetInternetIngressGiBResult = NetworkGiBResult
 type NetInternetServiceIngressGiBResult = NetworkGiBResult
 type NetInternetServiceIngressGiBResult = NetworkGiBResult
+type NetNatGatewayIngressGiBResult = NetworkGiBResult
 
 
 func DecodeNetZoneGiBResult(result *QueryResult) *NetZoneGiBResult {
 func DecodeNetZoneGiBResult(result *QueryResult) *NetZoneGiBResult {
 	return DecodeNetworkGiBResult(result)
 	return DecodeNetworkGiBResult(result)
@@ -1148,6 +1153,14 @@ func DecodeNetInternetServiceGiBResult(result *QueryResult) *NetInternetServiceG
 	return DecodeNetworkGiBResult(result)
 	return DecodeNetworkGiBResult(result)
 }
 }
 
 
+func DecodeNetNatGatewayPricePerGiBResult(result *QueryResult) *NetNatGatewayPricePerGiBResult {
+	return DecodeNetworkPricePerGiBResult(result)
+}
+
+func DecodeNetNatGatewayGiBResult(result *QueryResult) *NetNatGatewayGiBResult {
+	return DecodeNetworkGiBResult(result)
+}
+
 func DecodeNetZoneIngressGiBResult(result *QueryResult) *NetZoneIngressGiBResult {
 func DecodeNetZoneIngressGiBResult(result *QueryResult) *NetZoneIngressGiBResult {
 	return DecodeNetworkGiBResult(result)
 	return DecodeNetworkGiBResult(result)
 }
 }
@@ -1164,6 +1177,10 @@ func DecodeNetInternetServiceIngressGiBResult(result *QueryResult) *NetInternetS
 	return DecodeNetworkGiBResult(result)
 	return DecodeNetworkGiBResult(result)
 }
 }
 
 
+func DecodeNetNatGatewayIngressGiBResult(result *QueryResult) *NetNatGatewayIngressGiBResult {
+	return DecodeNetworkGiBResult(result)
+}
+
 type NetReceiveBytesResult struct {
 type NetReceiveBytesResult struct {
 	UID       string
 	UID       string
 	Cluster   string
 	Cluster   string

+ 94 - 0
modules/collector-source/pkg/collector/collector.go

@@ -69,11 +69,15 @@ func NewOpenCostMetricStore() metric.MetricStore {
 	memStore.Register(NewNetInternetGiBMetricCollector())
 	memStore.Register(NewNetInternetGiBMetricCollector())
 	memStore.Register(NewNetInternetPricePerGiBMetricCollector())
 	memStore.Register(NewNetInternetPricePerGiBMetricCollector())
 	memStore.Register(NewNetInternetServiceGiBMetricCollector())
 	memStore.Register(NewNetInternetServiceGiBMetricCollector())
+	memStore.Register(NewNetNatGatewayGiBMetricCollector())
+	memStore.Register(NewNetNatGatewayPricePerGiBMetricCollector())
 	memStore.Register(NewNetReceiveBytesMetricCollector())
 	memStore.Register(NewNetReceiveBytesMetricCollector())
 	memStore.Register(NewNetZoneIngressGiBMetricCollector())
 	memStore.Register(NewNetZoneIngressGiBMetricCollector())
 	memStore.Register(NewNetRegionIngressGiBMetricCollector())
 	memStore.Register(NewNetRegionIngressGiBMetricCollector())
 	memStore.Register(NewNetInternetIngressGiBMetricCollector())
 	memStore.Register(NewNetInternetIngressGiBMetricCollector())
 	memStore.Register(NewNetInternetServiceIngressGiBMetricCollector())
 	memStore.Register(NewNetInternetServiceIngressGiBMetricCollector())
+	memStore.Register(NewNetNatGatewayIngressPricePerGiBMetricCollector())
+	memStore.Register(NewNetNatGatewayIngressGiBMetricCollector())
 	memStore.Register(NewNetTransferBytesMetricCollector())
 	memStore.Register(NewNetTransferBytesMetricCollector())
 	memStore.Register(NewNamespaceUptimeMetricCollector())
 	memStore.Register(NewNamespaceUptimeMetricCollector())
 	memStore.Register(NewNamespaceLabelsMetricCollector())
 	memStore.Register(NewNamespaceLabelsMetricCollector())
@@ -1502,6 +1506,51 @@ func NewNetInternetServiceGiBMetricCollector() *metric.MetricCollector {
 	)
 	)
 }
 }
 
 
+//	sum(
+//		increase(
+//			kubecost_pod_network_egress_bytes_total{
+//				nat_gateway="true",
+//				<some_custom_filter>
+//			}[1h]
+//		)
+//	) by (pod_name, namespace, uid, cluster_id) / 1024 / 1024 / 1024
+
+func NewNetNatGatewayGiBMetricCollector() *metric.MetricCollector {
+	return metric.NewMetricCollector(
+		metric.NetNatGatewayGiBID,
+		metric.KubecostPodNetworkEgressBytesTotal,
+		[]string{
+			source.NamespaceLabel,
+			source.PodNameLabel,
+			source.UIDLabel,
+		},
+		aggregator.Increase,
+		func(labels map[string]string) bool {
+			natLabel, labelExists := labels[source.NatGatewayLabel]
+
+			return labelExists && natLabel == "true"
+		},
+	)
+}
+
+// avg(
+//      avg_over_time(
+// 			kubecost_network_nat_gateway_egress_cost{
+// 				<some_custom_filter>
+//			}[1h]
+//		)
+// ) by (cluster_id)
+
+func NewNetNatGatewayPricePerGiBMetricCollector() *metric.MetricCollector {
+	return metric.NewMetricCollector(
+		metric.NetNatGatewayPricePerGiBID,
+		metric.KubecostNetworkNatGatewayEgressCost,
+		[]string{},
+		aggregator.AverageOverTime,
+		nil,
+	)
+}
+
 //	sum(
 //	sum(
 //		increase(
 //		increase(
 //			container_network_receive_bytes_total{
 //			container_network_receive_bytes_total{
@@ -1636,6 +1685,51 @@ func NewNetInternetServiceIngressGiBMetricCollector() *metric.MetricCollector {
 	)
 	)
 }
 }
 
 
+// avg(
+//      avg_over_time(
+// 			kubecost_network_nat_gateway_ingress_cost{
+// 				<some_custom_filter>
+//			}[1h]
+//		)
+// ) by (cluster_id)
+
+func NewNetNatGatewayIngressPricePerGiBMetricCollector() *metric.MetricCollector {
+	return metric.NewMetricCollector(
+		metric.NetNatGatewayIngressPricePerGiBID,
+		metric.KubecostNetworkNatGatewayIngressCost,
+		[]string{},
+		aggregator.AverageOverTime,
+		nil,
+	)
+}
+
+//	sum(
+//		increase(
+//			kubecost_pod_network_ingress_bytes_total{
+//				nat_gateway="true",
+//				<some_custom_filter>
+//			}[1h]
+//		)
+//	) by (pod_name, namespace, uid, cluster_id) / 1024 / 1024 / 1024
+
+func NewNetNatGatewayIngressGiBMetricCollector() *metric.MetricCollector {
+	return metric.NewMetricCollector(
+		metric.NetNatGatewayIngressGiBID,
+		metric.KubecostPodNetworkIngressBytesTotal,
+		[]string{
+			source.NamespaceLabel,
+			source.PodNameLabel,
+			source.UIDLabel,
+		},
+		aggregator.Increase,
+		func(labels map[string]string) bool {
+			natLabel, labelExists := labels[source.NatGatewayLabel]
+
+			return labelExists && natLabel == "true"
+		},
+	)
+}
+
 //	sum(
 //	sum(
 //		increase(
 //		increase(
 //			container_network_transmit_bytes_total{
 //			container_network_transmit_bytes_total{

+ 16 - 0
modules/collector-source/pkg/collector/metricsquerier.go

@@ -452,6 +452,14 @@ func (c *collectorMetricsQuerier) QueryNetInternetServiceGiB(start, end time.Tim
 	return queryCollectorGiB(c, start, end, metric.NetInternetServiceGiBID, source.DecodeNetInternetServiceGiBResult)
 	return queryCollectorGiB(c, start, end, metric.NetInternetServiceGiBID, source.DecodeNetInternetServiceGiBResult)
 }
 }
 
 
+func (c *collectorMetricsQuerier) QueryNetNatGatewayPricePerGiB(start, end time.Time) *source.Future[source.NetNatGatewayPricePerGiBResult] {
+	return queryCollector(c, start, end, metric.NetNatGatewayPricePerGiBID, source.DecodeNetNatGatewayPricePerGiBResult)
+}
+
+func (c *collectorMetricsQuerier) QueryNetNatGatewayGiB(start, end time.Time) *source.Future[source.NetNatGatewayGiBResult] {
+	return queryCollectorGiB(c, start, end, metric.NetNatGatewayGiBID, source.DecodeNetNatGatewayGiBResult)
+}
+
 func (c *collectorMetricsQuerier) QueryNetTransferBytes(start, end time.Time) *source.Future[source.NetTransferBytesResult] {
 func (c *collectorMetricsQuerier) QueryNetTransferBytes(start, end time.Time) *source.Future[source.NetTransferBytesResult] {
 	return queryCollector(c, start, end, metric.NetTransferBytesID, source.DecodeNetTransferBytesResult)
 	return queryCollector(c, start, end, metric.NetTransferBytesID, source.DecodeNetTransferBytesResult)
 }
 }
@@ -472,6 +480,14 @@ func (c *collectorMetricsQuerier) QueryNetInternetServiceIngressGiB(start, end t
 	return queryCollectorGiB(c, start, end, metric.NetInternetServiceIngressGiBID, source.DecodeNetInternetServiceIngressGiBResult)
 	return queryCollectorGiB(c, start, end, metric.NetInternetServiceIngressGiBID, source.DecodeNetInternetServiceIngressGiBResult)
 }
 }
 
 
+func (c *collectorMetricsQuerier) QueryNetNatGatewayIngressPricePerGiB(start, end time.Time) *source.Future[source.NetNatGatewayPricePerGiBResult] {
+	return queryCollector(c, start, end, metric.NetNatGatewayPricePerGiBID, source.DecodeNetNatGatewayPricePerGiBResult)
+}
+
+func (c *collectorMetricsQuerier) QueryNetNatGatewayIngressGiB(start, end time.Time) *source.Future[source.NetNatGatewayIngressGiBResult] {
+	return queryCollectorGiB(c, start, end, metric.NetNatGatewayIngressGiBID, source.DecodeNetNatGatewayIngressGiBResult)
+}
+
 func (c *collectorMetricsQuerier) QueryNetReceiveBytes(start, end time.Time) *source.Future[source.NetReceiveBytesResult] {
 func (c *collectorMetricsQuerier) QueryNetReceiveBytes(start, end time.Time) *source.Future[source.NetReceiveBytesResult] {
 	return queryCollector(c, start, end, metric.NetReceiveBytesID, source.DecodeNetReceiveBytesResult)
 	return queryCollector(c, start, end, metric.NetReceiveBytesID, source.DecodeNetReceiveBytesResult)
 }
 }

+ 4 - 0
modules/collector-source/pkg/metric/collector.go

@@ -72,11 +72,15 @@ const (
 	NetInternetGiBID                           MetricCollectorID = "NetInternetGiB"
 	NetInternetGiBID                           MetricCollectorID = "NetInternetGiB"
 	NetInternetPricePerGiBID                   MetricCollectorID = "NetInternetPricePerGiB"
 	NetInternetPricePerGiBID                   MetricCollectorID = "NetInternetPricePerGiB"
 	NetInternetServiceGiBID                    MetricCollectorID = "NetInternetServiceGiB"
 	NetInternetServiceGiBID                    MetricCollectorID = "NetInternetServiceGiB"
+	NetNatGatewayPricePerGiBID                 MetricCollectorID = "NetNatGatewayPricePerGiB"
+	NetNatGatewayIngressPricePerGiBID          MetricCollectorID = "NetNatGatewayIngressPricePerGiB"
+	NetNatGatewayGiBID                         MetricCollectorID = "NetNatGatewayGiB"
 	NetTransferBytesID                         MetricCollectorID = "NetTransferBytes"
 	NetTransferBytesID                         MetricCollectorID = "NetTransferBytes"
 	NetZoneIngressGiBID                        MetricCollectorID = "NetZoneIngressGiB"
 	NetZoneIngressGiBID                        MetricCollectorID = "NetZoneIngressGiB"
 	NetRegionIngressGiBID                      MetricCollectorID = "NetRegionIngressGiB"
 	NetRegionIngressGiBID                      MetricCollectorID = "NetRegionIngressGiB"
 	NetInternetIngressGiBID                    MetricCollectorID = "NetInternetIngressGiB"
 	NetInternetIngressGiBID                    MetricCollectorID = "NetInternetIngressGiB"
 	NetInternetServiceIngressGiBID             MetricCollectorID = "NetInternetServiceIngressGiB"
 	NetInternetServiceIngressGiBID             MetricCollectorID = "NetInternetServiceIngressGiB"
+	NetNatGatewayIngressGiBID                  MetricCollectorID = "NetNatGatewayIngressGiB"
 	NetReceiveBytesID                          MetricCollectorID = "NetReceiveBytes"
 	NetReceiveBytesID                          MetricCollectorID = "NetReceiveBytes"
 	NamespaceUptimeID                          MetricCollectorID = "NamespaceUptime"
 	NamespaceUptimeID                          MetricCollectorID = "NamespaceUptime"
 	NamespaceLabelsID                          MetricCollectorID = "NamespaceLabels"
 	NamespaceLabelsID                          MetricCollectorID = "NamespaceLabels"

+ 18 - 16
modules/collector-source/pkg/metric/metrics.go

@@ -40,22 +40,24 @@ const (
 	KubecostPodNetworkIngressBytesTotal = "kubecost_pod_network_ingress_bytes_total"
 	KubecostPodNetworkIngressBytesTotal = "kubecost_pod_network_ingress_bytes_total"
 
 
 	// Opencost Metrics
 	// Opencost Metrics
-	KubecostClusterManagementCost     = "kubecost_cluster_management_cost"
-	KubecostNetworkZoneEgressCost     = "kubecost_network_zone_egress_cost"
-	KubecostNetworkRegionEgressCost   = "kubecost_network_region_egress_cost"
-	KubecostNetworkInternetEgressCost = "kubecost_network_internet_egress_cost"
-	PVHourlyCost                      = "pv_hourly_cost"
-	KubecostLoadBalancerCost          = "kubecost_load_balancer_cost"
-	NodeTotalHourlyCost               = "node_total_hourly_cost"
-	NodeCPUHourlyCost                 = "node_cpu_hourly_cost"
-	NodeRAMHourlyCost                 = "node_ram_hourly_cost"
-	NodeGPUHourlyCost                 = "node_gpu_hourly_cost"
-	NodeGPUCount                      = "node_gpu_count"
-	KubecostNodeIsSpot                = "kubecost_node_is_spot"
-	ContainerCPUAllocation            = "container_cpu_allocation"
-	ContainerMemoryAllocationBytes    = "container_memory_allocation_bytes"
-	ContainerGPUAllocation            = "container_gpu_allocation"
-	PodPVCAllocation                  = "pod_pvc_allocation"
+	KubecostClusterManagementCost        = "kubecost_cluster_management_cost"
+	KubecostNetworkZoneEgressCost        = "kubecost_network_zone_egress_cost"
+	KubecostNetworkRegionEgressCost      = "kubecost_network_region_egress_cost"
+	KubecostNetworkInternetEgressCost    = "kubecost_network_internet_egress_cost"
+	KubecostNetworkNatGatewayEgressCost  = "kubecost_network_nat_gateway_egress_cost"
+	KubecostNetworkNatGatewayIngressCost = "kubecost_network_nat_gateway_ingress_cost"
+	PVHourlyCost                         = "pv_hourly_cost"
+	KubecostLoadBalancerCost             = "kubecost_load_balancer_cost"
+	NodeTotalHourlyCost                  = "node_total_hourly_cost"
+	NodeCPUHourlyCost                    = "node_cpu_hourly_cost"
+	NodeRAMHourlyCost                    = "node_ram_hourly_cost"
+	NodeGPUHourlyCost                    = "node_gpu_hourly_cost"
+	NodeGPUCount                         = "node_gpu_count"
+	KubecostNodeIsSpot                   = "kubecost_node_is_spot"
+	ContainerCPUAllocation               = "container_cpu_allocation"
+	ContainerMemoryAllocationBytes       = "container_memory_allocation_bytes"
+	ContainerGPUAllocation               = "container_gpu_allocation"
+	PodPVCAllocation                     = "pod_pvc_allocation"
 
 
 	// Stat Summary Metrics
 	// Stat Summary Metrics
 	NodeCPUSecondsTotal                = "node_cpu_seconds_total"
 	NodeCPUSecondsTotal                = "node_cpu_seconds_total"

+ 74 - 0
modules/prometheus-source/pkg/prom/metricsquerier.go

@@ -1175,6 +1175,43 @@ func (pds *PrometheusMetricsQuerier) QueryNetInternetServiceGiB(start, end time.
 	return source.NewFuture(source.DecodeNetInternetServiceGiBResult, ctx.QueryAtTime(queryNetInternetGiB, end))
 	return source.NewFuture(source.DecodeNetInternetServiceGiBResult, ctx.QueryAtTime(queryNetInternetGiB, end))
 }
 }
 
 
+func (pds *PrometheusMetricsQuerier) QueryNetNatGatewayPricePerGiB(start, end time.Time) *source.Future[source.NetNatGatewayPricePerGiBResult] {
+	const queryName = "QueryNetNatGatewayPricePerGiB"
+	const queryFmtNetNatGatewayPricePerGiB = `avg(avg_over_time(kubecost_network_nat_gateway_egress_cost{%s}[%s])) by (%s)`
+
+	cfg := pds.promConfig
+
+	durStr := timeutil.DurationString(end.Sub(start))
+	if durStr == "" {
+		panic(fmt.Sprintf("failed to parse duration string passed to %s", queryName))
+	}
+
+	queryNetNatGatewayPricePerGiB := fmt.Sprintf(queryFmtNetNatGatewayPricePerGiB, cfg.ClusterFilter, durStr, cfg.ClusterLabel)
+	log.Debugf(PrometheusMetricsQueryLogFormat, queryName, end.Unix(), queryNetNatGatewayPricePerGiB)
+
+	ctx := pds.promContexts.NewNamedContext(AllocationContextName)
+	return source.NewFuture(source.DecodeNetNatGatewayPricePerGiBResult, ctx.QueryAtTime(queryNetNatGatewayPricePerGiB, end))
+}
+
+func (pds *PrometheusMetricsQuerier) QueryNetNatGatewayGiB(start, end time.Time) *source.Future[source.NetNatGatewayGiBResult] {
+	const queryName = "QueryNetNatGatewayGiB"
+	const queryFmtNetNatGatewayGiB = `sum(increase(kubecost_pod_network_egress_bytes_total{nat_gateway="true", %s}[%s:%dm])) by (pod_name, namespace, service, uid, %s) / 1024 / 1024 / 1024`
+
+	cfg := pds.promConfig
+	minsPerResolution := cfg.DataResolutionMinutes
+
+	durStr := pds.durationStringFor(start, end, minsPerResolution, true)
+	if durStr == "" {
+		panic(fmt.Sprintf("failed to parse duration string passed to %s", queryName))
+	}
+
+	queryNetNatGatewayGiB := fmt.Sprintf(queryFmtNetNatGatewayGiB, cfg.ClusterFilter, durStr, minsPerResolution, cfg.ClusterLabel)
+	log.Debugf(PrometheusMetricsQueryLogFormat, queryName, end.Unix(), queryNetNatGatewayGiB)
+
+	ctx := pds.promContexts.NewNamedContext(NetworkInsightsContextName)
+	return source.NewFuture(source.DecodeNetNatGatewayGiBResult, ctx.QueryAtTime(queryNetNatGatewayGiB, end))
+}
+
 func (pds *PrometheusMetricsQuerier) QueryNetTransferBytes(start, end time.Time) *source.Future[source.NetTransferBytesResult] {
 func (pds *PrometheusMetricsQuerier) QueryNetTransferBytes(start, end time.Time) *source.Future[source.NetTransferBytesResult] {
 	const queryName = "QueryNetTransferBytes"
 	const queryName = "QueryNetTransferBytes"
 	const queryFmtNetTransferBytes = `sum(increase(container_network_transmit_bytes_total{pod!="", %s}[%s:%dm])) by (pod_name, pod, namespace, uid, %s)`
 	const queryFmtNetTransferBytes = `sum(increase(container_network_transmit_bytes_total{pod!="", %s}[%s:%dm])) by (pod_name, pod, namespace, uid, %s)`
@@ -1270,6 +1307,43 @@ func (pds *PrometheusMetricsQuerier) QueryNetInternetServiceIngressGiB(start, en
 	return source.NewFuture(source.DecodeNetInternetServiceIngressGiBResult, ctx.QueryAtTime(queryNetIngInternetGiB, end))
 	return source.NewFuture(source.DecodeNetInternetServiceIngressGiBResult, ctx.QueryAtTime(queryNetIngInternetGiB, end))
 }
 }
 
 
+func (pds *PrometheusMetricsQuerier) QueryNetNatGatewayIngressPricePerGiB(start, end time.Time) *source.Future[source.NetNatGatewayPricePerGiBResult] {
+	const queryName = "QueryNetNatGatewayIngressPricePerGiB"
+	const queryFmtNetNatGatewayIngressPricePerGiB = `avg(avg_over_time(kubecost_network_nat_gateway_ingress_cost{%s}[%s])) by (%s)`
+
+	cfg := pds.promConfig
+
+	durStr := timeutil.DurationString(end.Sub(start))
+	if durStr == "" {
+		panic(fmt.Sprintf("failed to parse duration string passed to %s", queryName))
+	}
+
+	queryNetNatGatewayIngressPricePerGiB := fmt.Sprintf(queryFmtNetNatGatewayIngressPricePerGiB, cfg.ClusterFilter, durStr, cfg.ClusterLabel)
+	log.Debugf(PrometheusMetricsQueryLogFormat, queryName, end.Unix(), queryNetNatGatewayIngressPricePerGiB)
+
+	ctx := pds.promContexts.NewNamedContext(AllocationContextName)
+	return source.NewFuture(source.DecodeNetNatGatewayPricePerGiBResult, ctx.QueryAtTime(queryNetNatGatewayIngressPricePerGiB, end))
+}
+
+func (pds *PrometheusMetricsQuerier) QueryNetNatGatewayIngressGiB(start, end time.Time) *source.Future[source.NetNatGatewayIngressGiBResult] {
+	const queryName = "QueryNetNatGatewayIngressGiB"
+	const queryFmtNetNatGatewayIngressGiB = `sum(increase(kubecost_pod_network_ingress_bytes_total{nat_gateway="true", %s}[%s:%dm])) by (pod_name, namespace, service, uid, %s) / 1024 / 1024 / 1024`
+
+	cfg := pds.promConfig
+	minsPerResolution := cfg.DataResolutionMinutes
+
+	durStr := pds.durationStringFor(start, end, minsPerResolution, true)
+	if durStr == "" {
+		panic(fmt.Sprintf("failed to parse duration string passed to %s", queryName))
+	}
+
+	queryNetNatGatewayIngressGiB := fmt.Sprintf(queryFmtNetNatGatewayIngressGiB, cfg.ClusterFilter, durStr, minsPerResolution, cfg.ClusterLabel)
+	log.Debugf(PrometheusMetricsQueryLogFormat, queryName, end.Unix(), queryNetNatGatewayIngressGiB)
+
+	ctx := pds.promContexts.NewNamedContext(NetworkInsightsContextName)
+	return source.NewFuture(source.DecodeNetNatGatewayIngressGiBResult, ctx.QueryAtTime(queryNetNatGatewayIngressGiB, end))
+}
+
 func (pds *PrometheusMetricsQuerier) QueryNetReceiveBytes(start, end time.Time) *source.Future[source.NetReceiveBytesResult] {
 func (pds *PrometheusMetricsQuerier) QueryNetReceiveBytes(start, end time.Time) *source.Future[source.NetReceiveBytesResult] {
 	const queryName = "QueryNetReceiveBytes"
 	const queryName = "QueryNetReceiveBytes"
 	const queryFmtNetReceiveBytes = `sum(increase(container_network_receive_bytes_total{pod!="", %s}[%s:%dm])) by (pod_name, pod, namespace, uid, %s)`
 	const queryFmtNetReceiveBytes = `sum(increase(container_network_receive_bytes_total{pod!="", %s}[%s:%dm])) by (pod_name, pod, namespace, uid, %s)`

+ 10 - 0
pkg/cloud/alibaba/provider.go

@@ -570,11 +570,21 @@ func (alibaba *Alibaba) NetworkPricing() (*models.Network, error) {
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
+	nge, err := strconv.ParseFloat(cpricing.NatGatewayEgress, 64)
+	if err != nil {
+		return nil, err
+	}
+	ngi, err := strconv.ParseFloat(cpricing.NatGatewayIngress, 64)
+	if err != nil {
+		return nil, err
+	}
 
 
 	return &models.Network{
 	return &models.Network{
 		ZoneNetworkEgressCost:     znec,
 		ZoneNetworkEgressCost:     znec,
 		RegionNetworkEgressCost:   rnec,
 		RegionNetworkEgressCost:   rnec,
 		InternetNetworkEgressCost: inec,
 		InternetNetworkEgressCost: inec,
+		NatGatewayEgressCost:      nge,
+		NatGatewayIngressCost:     ngi,
 	}, nil
 	}, nil
 }
 }
 
 

+ 10 - 0
pkg/cloud/aws/provider.go

@@ -1276,11 +1276,21 @@ func (aws *AWS) NetworkPricing() (*models.Network, error) {
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
+	nge, err := strconv.ParseFloat(cpricing.NatGatewayEgress, 64)
+	if err != nil {
+		return nil, err
+	}
+	ngi, err := strconv.ParseFloat(cpricing.NatGatewayIngress, 64)
+	if err != nil {
+		return nil, err
+	}
 
 
 	return &models.Network{
 	return &models.Network{
 		ZoneNetworkEgressCost:     znec,
 		ZoneNetworkEgressCost:     znec,
 		RegionNetworkEgressCost:   rnec,
 		RegionNetworkEgressCost:   rnec,
 		InternetNetworkEgressCost: inec,
 		InternetNetworkEgressCost: inec,
+		NatGatewayEgressCost:      nge,
+		NatGatewayIngressCost:     ngi,
 	}, nil
 	}, nil
 }
 }
 
 

+ 10 - 0
pkg/cloud/azure/provider.go

@@ -1284,11 +1284,21 @@ func (az *Azure) NetworkPricing() (*models.Network, error) {
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
+	nge, err := strconv.ParseFloat(cpricing.NatGatewayEgress, 64)
+	if err != nil {
+		return nil, err
+	}
+	ngi, err := strconv.ParseFloat(cpricing.NatGatewayIngress, 64)
+	if err != nil {
+		return nil, err
+	}
 
 
 	return &models.Network{
 	return &models.Network{
 		ZoneNetworkEgressCost:     znec,
 		ZoneNetworkEgressCost:     znec,
 		RegionNetworkEgressCost:   rnec,
 		RegionNetworkEgressCost:   rnec,
 		InternetNetworkEgressCost: inec,
 		InternetNetworkEgressCost: inec,
+		NatGatewayEgressCost:      nge,
+		NatGatewayIngressCost:     ngi,
 	}, nil
 	}, nil
 }
 }
 
 

+ 14 - 4
pkg/cloud/digitalocean/provider.go

@@ -738,9 +738,11 @@ func (do *DOKS) LoadBalancerPricing() (*models.LoadBalancer, error) {
 func (do *DOKS) NetworkPricing() (*models.Network, error) {
 func (do *DOKS) NetworkPricing() (*models.Network, error) {
 	// fallback
 	// fallback
 	const (
 	const (
-		defaultZoneEgress     = 0.00
-		defaultRegionEgress   = 0.00
-		defaultInternetEgress = 0.01
+		defaultZoneEgress        = 0.00
+		defaultRegionEgress      = 0.00
+		defaultInternetEgress    = 0.01
+		defaultNatGatewayEgress  = 0.045
+		defaultNatGatewayIngress = 0.045
 	)
 	)
 
 
 	log.Infof("NetworkPricing: retrieving custom pricing data")
 	log.Infof("NetworkPricing: retrieving custom pricing data")
@@ -753,12 +755,16 @@ func (do *DOKS) NetworkPricing() (*models.Network, error) {
 			ZoneNetworkEgressCost:     defaultZoneEgress,
 			ZoneNetworkEgressCost:     defaultZoneEgress,
 			RegionNetworkEgressCost:   defaultRegionEgress,
 			RegionNetworkEgressCost:   defaultRegionEgress,
 			InternetNetworkEgressCost: defaultInternetEgress,
 			InternetNetworkEgressCost: defaultInternetEgress,
+			NatGatewayEgressCost:      defaultNatGatewayEgress,
+			NatGatewayIngressCost:     defaultNatGatewayIngress,
 		}, nil
 		}, nil
 	}
 	}
 
 
 	znec := parseWithDefault(cpricing.ZoneNetworkEgress, defaultZoneEgress, "ZoneNetworkEgress")
 	znec := parseWithDefault(cpricing.ZoneNetworkEgress, defaultZoneEgress, "ZoneNetworkEgress")
 	rnec := parseWithDefault(cpricing.RegionNetworkEgress, defaultRegionEgress, "RegionNetworkEgress")
 	rnec := parseWithDefault(cpricing.RegionNetworkEgress, defaultRegionEgress, "RegionNetworkEgress")
 	inec := parseWithDefault(cpricing.InternetNetworkEgress, defaultInternetEgress, "InternetNetworkEgress")
 	inec := parseWithDefault(cpricing.InternetNetworkEgress, defaultInternetEgress, "InternetNetworkEgress")
+	nge := parseWithDefault(cpricing.NatGatewayEgress, defaultNatGatewayEgress, "NatGatewayEgress")
+	ngi := parseWithDefault(cpricing.NatGatewayIngress, defaultNatGatewayIngress, "NatGatewayIngress")
 
 
 	log.Infof("NetworkPricing: using parsed values: zone=%.4f/GiB, region=%.4f/GiB, internet=%.4f/GIB", znec, rnec, inec)
 	log.Infof("NetworkPricing: using parsed values: zone=%.4f/GiB, region=%.4f/GiB, internet=%.4f/GIB", znec, rnec, inec)
 
 
@@ -766,6 +772,8 @@ func (do *DOKS) NetworkPricing() (*models.Network, error) {
 		ZoneNetworkEgressCost:     znec,
 		ZoneNetworkEgressCost:     znec,
 		RegionNetworkEgressCost:   rnec,
 		RegionNetworkEgressCost:   rnec,
 		InternetNetworkEgressCost: inec,
 		InternetNetworkEgressCost: inec,
+		NatGatewayEgressCost:      nge,
+		NatGatewayIngressCost:     ngi,
 	}, nil
 	}, nil
 }
 }
 
 
@@ -786,7 +794,9 @@ func isDefaultNetworkPricing(cp *models.CustomPricing) bool {
 	return cp != nil &&
 	return cp != nil &&
 		cp.ZoneNetworkEgress == "0.01" &&
 		cp.ZoneNetworkEgress == "0.01" &&
 		cp.RegionNetworkEgress == "0.01" &&
 		cp.RegionNetworkEgress == "0.01" &&
-		cp.InternetNetworkEgress == "0.12"
+		cp.InternetNetworkEgress == "0.12" &&
+		cp.NatGatewayEgress == "0.045" &&
+		cp.NatGatewayIngress == "0.045"
 }
 }
 
 
 func (do *DOKS) AllNodePricing() (interface{}, error) {
 func (do *DOKS) AllNodePricing() (interface{}, error) {

+ 10 - 0
pkg/cloud/gcp/provider.go

@@ -1142,11 +1142,21 @@ func (gcp *GCP) NetworkPricing() (*models.Network, error) {
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
+	nge, err := strconv.ParseFloat(cpricing.NatGatewayEgress, 64)
+	if err != nil {
+		return nil, err
+	}
+	ngi, err := strconv.ParseFloat(cpricing.NatGatewayIngress, 64)
+	if err != nil {
+		return nil, err
+	}
 
 
 	return &models.Network{
 	return &models.Network{
 		ZoneNetworkEgressCost:     znec,
 		ZoneNetworkEgressCost:     znec,
 		RegionNetworkEgressCost:   rnec,
 		RegionNetworkEgressCost:   rnec,
 		InternetNetworkEgressCost: inec,
 		InternetNetworkEgressCost: inec,
+		NatGatewayEgressCost:      nge,
+		NatGatewayIngressCost:     ngi,
 	}, nil
 	}, nil
 }
 }
 
 

+ 2 - 0
pkg/cloud/gcp/provider_test.go

@@ -1261,6 +1261,8 @@ func (m *mockConfig) GetCustomPricingData() (*models.CustomPricing, error) {
 		ZoneNetworkEgress:     "0.12",
 		ZoneNetworkEgress:     "0.12",
 		RegionNetworkEgress:   "0.08",
 		RegionNetworkEgress:   "0.08",
 		InternetNetworkEgress: "0.15",
 		InternetNetworkEgress: "0.15",
+		NatGatewayEgress:      "0.45",
+		NatGatewayIngress:     "0.45",
 	}, nil
 	}, nil
 }
 }
 
 

+ 3 - 1
pkg/cloud/models/models.go

@@ -137,6 +137,8 @@ type CustomPricing struct {
 	ZoneNetworkEgress            string `json:"zoneNetworkEgress"`
 	ZoneNetworkEgress            string `json:"zoneNetworkEgress"`
 	RegionNetworkEgress          string `json:"regionNetworkEgress"`
 	RegionNetworkEgress          string `json:"regionNetworkEgress"`
 	InternetNetworkEgress        string `json:"internetNetworkEgress"`
 	InternetNetworkEgress        string `json:"internetNetworkEgress"`
+	NatGatewayEgress             string `json:"natGatewayEgress"`
+	NatGatewayIngress            string `json:"natGatewayIngress"`
 	FirstFiveForwardingRulesCost string `json:"firstFiveForwardingRulesCost"`
 	FirstFiveForwardingRulesCost string `json:"firstFiveForwardingRulesCost"`
 	AdditionalForwardingRuleCost string `json:"additionalForwardingRuleCost"`
 	AdditionalForwardingRuleCost string `json:"additionalForwardingRuleCost"`
 	LBIngressDataCost            string `json:"LBIngressDataCost"`
 	LBIngressDataCost            string `json:"LBIngressDataCost"`
@@ -215,7 +217,7 @@ func SetCustomPricingField(obj *CustomPricing, name string, value string) error
 	// validation work in order to prevent "NaN" and other invalid strings
 	// validation work in order to prevent "NaN" and other invalid strings
 	// from getting set here.
 	// from getting set here.
 	switch strings.ToLower(name) {
 	switch strings.ToLower(name) {
-	case "cpu", "gpu", "ram", "spotcpu", "spotgpu", "spotram", "storage", "zonenetworkegress", "regionnetworkegress", "internetnetworkegress":
+	case "cpu", "gpu", "ram", "spotcpu", "spotgpu", "spotram", "storage", "zonenetworkegress", "regionnetworkegress", "internetnetworkegress", "natgatewayegress", "natgatewayingress":
 		// If we are sent an empty string, ignore the key and don't change the value
 		// If we are sent an empty string, ignore the key and don't change the value
 		if value == "" {
 		if value == "" {
 			return nil
 			return nil

+ 4 - 0
pkg/cloud/models/models_test.go

@@ -66,6 +66,8 @@ func TestSetSetCustomPricingField(t *testing.T) {
 		"ZoneNetworkEgress",
 		"ZoneNetworkEgress",
 		"RegionNetworkEgress",
 		"RegionNetworkEgress",
 		"InternetNetworkEgress",
 		"InternetNetworkEgress",
+		"NatGatewayEgress",
+		"NatGatewayIngress",
 	}
 	}
 
 
 	testCases := []testCase{}
 	testCases := []testCase{}
@@ -98,6 +100,8 @@ func TestSetSetCustomPricingField(t *testing.T) {
 				ZoneNetworkEgress:     defaultValue,
 				ZoneNetworkEgress:     defaultValue,
 				RegionNetworkEgress:   defaultValue,
 				RegionNetworkEgress:   defaultValue,
 				InternetNetworkEgress: defaultValue,
 				InternetNetworkEgress: defaultValue,
+				NatGatewayEgress:      defaultValue,
+				NatGatewayIngress:     defaultValue,
 			}
 			}
 			err := SetCustomPricingField(cp, tc.fieldName, tc.fieldValue)
 			err := SetCustomPricingField(cp, tc.fieldName, tc.fieldValue)
 			if err != nil && tc.expError == nil {
 			if err != nil && tc.expError == nil {

+ 2 - 0
pkg/cloud/models/network.go

@@ -11,6 +11,8 @@ type Network struct {
 	ZoneNetworkEgressCost     float64
 	ZoneNetworkEgressCost     float64
 	RegionNetworkEgressCost   float64
 	RegionNetworkEgressCost   float64
 	InternetNetworkEgressCost float64
 	InternetNetworkEgressCost float64
+	NatGatewayEgressCost      float64
+	NatGatewayIngressCost     float64
 }
 }
 
 
 // LoadBalancer is the interface by which the provider and cost model communicate LoadBalancer prices.
 // LoadBalancer is the interface by which the provider and cost model communicate LoadBalancer prices.

+ 2 - 0
pkg/cloud/oracle/ratecard.go

@@ -103,6 +103,8 @@ func (rcs *RateCardStore) ForEgressRegion(region string, defaultPricing DefaultP
 		ZoneNetworkEgressCost:     0,
 		ZoneNetworkEgressCost:     0,
 		RegionNetworkEgressCost:   egressCost,
 		RegionNetworkEgressCost:   egressCost,
 		InternetNetworkEgressCost: egressCost,
 		InternetNetworkEgressCost: egressCost,
+		NatGatewayEgressCost:      0,
+		NatGatewayIngressCost:     0,
 	}, nil
 	}, nil
 }
 }
 
 

+ 10 - 0
pkg/cloud/otc/provider.go

@@ -243,11 +243,21 @@ func (otc *OTC) NetworkPricing() (*models.Network, error) {
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
+	nge, err := strconv.ParseFloat(cpricing.NatGatewayEgress, 64)
+	if err != nil {
+		return nil, err
+	}
+	ngi, err := strconv.ParseFloat(cpricing.NatGatewayIngress, 64)
+	if err != nil {
+		return nil, err
+	}
 
 
 	return &models.Network{
 	return &models.Network{
 		ZoneNetworkEgressCost:     znec,
 		ZoneNetworkEgressCost:     znec,
 		RegionNetworkEgressCost:   rnec,
 		RegionNetworkEgressCost:   rnec,
 		InternetNetworkEgressCost: inec,
 		InternetNetworkEgressCost: inec,
+		NatGatewayEgressCost:      nge,
+		NatGatewayIngressCost:     ngi,
 	}, nil
 	}, nil
 }
 }
 
 

+ 10 - 0
pkg/cloud/provider/customprovider.go

@@ -287,11 +287,21 @@ func (cp *CustomProvider) NetworkPricing() (*models.Network, error) {
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
+	nge, err := strconv.ParseFloat(cpricing.NatGatewayEgress, 64)
+	if err != nil {
+		return nil, err
+	}
+	ngi, err := strconv.ParseFloat(cpricing.NatGatewayIngress, 64)
+	if err != nil {
+		return nil, err
+	}
 
 
 	return &models.Network{
 	return &models.Network{
 		ZoneNetworkEgressCost:     znec,
 		ZoneNetworkEgressCost:     znec,
 		RegionNetworkEgressCost:   rnec,
 		RegionNetworkEgressCost:   rnec,
 		InternetNetworkEgressCost: inec,
 		InternetNetworkEgressCost: inec,
+		NatGatewayEgressCost:      nge,
+		NatGatewayIngressCost:     ngi,
 	}, nil
 	}, nil
 }
 }
 
 

+ 28 - 12
pkg/cloud/provider/providerconfig.go

@@ -26,7 +26,7 @@ const closedSourceConfigMount = "models/"
 // ProviderConfig is a utility class that provides a thread-safe configuration storage/cache for all Provider
 // ProviderConfig is a utility class that provides a thread-safe configuration storage/cache for all Provider
 // implementations
 // implementations
 type ProviderConfig struct {
 type ProviderConfig struct {
-	lock            *sync.Mutex
+	lock            sync.Mutex
 	configManager   *config.ConfigFileManager
 	configManager   *config.ConfigFileManager
 	configFile      *config.ConfigFile
 	configFile      *config.ConfigFile
 	customPricing   *models.CustomPricing
 	customPricing   *models.CustomPricing
@@ -37,7 +37,6 @@ type ProviderConfig struct {
 func NewProviderConfig(configManager *config.ConfigFileManager, fileName string) *ProviderConfig {
 func NewProviderConfig(configManager *config.ConfigFileManager, fileName string) *ProviderConfig {
 	configFile := configManager.ConfigFileAt(coreenv.GetPathFromConfig(fileName))
 	configFile := configManager.ConfigFileAt(coreenv.GetPathFromConfig(fileName))
 	pc := &ProviderConfig{
 	pc := &ProviderConfig{
-		lock:          new(sync.Mutex),
 		configManager: configManager,
 		configManager: configManager,
 		configFile:    configFile,
 		configFile:    configFile,
 		customPricing: nil,
 		customPricing: nil,
@@ -69,10 +68,7 @@ func (pc *ProviderConfig) onConfigFileUpdated(changeType config.ChangeType, data
 			customPricing = DefaultPricing()
 			customPricing = DefaultPricing()
 		}
 		}
 
 
-		pc.customPricing = customPricing
-		if pc.customPricing.SpotGPU == "" {
-			pc.customPricing.SpotGPU = DefaultPricing().SpotGPU // Migration for users without this value set by default.
-		}
+		pc.customPricing = updateDefaultsOnEmpty(customPricing)
 	}
 	}
 }
 }
 
 
@@ -136,10 +132,7 @@ func (pc *ProviderConfig) loadConfig(writeIfNotExists bool) (*models.CustomPrici
 		return DefaultPricing(), err
 		return DefaultPricing(), err
 	}
 	}
 
 
-	pc.customPricing = &customPricing
-	if pc.customPricing.SpotGPU == "" {
-		pc.customPricing.SpotGPU = DefaultPricing().SpotGPU // Migration for users without this value set by default.
-	}
+	pc.customPricing = updateDefaultsOnEmpty(&customPricing)
 
 
 	return pc.customPricing, nil
 	return pc.customPricing, nil
 }
 }
@@ -177,7 +170,7 @@ func (pc *ProviderConfig) Update(updateFunc func(*models.CustomPricing) error) (
 	}
 	}
 
 
 	// Cache Update (possible the ptr already references the cached value)
 	// Cache Update (possible the ptr already references the cached value)
-	pc.customPricing = c
+	pc.customPricing = updateDefaultsOnEmpty(c)
 
 
 	cj, err := json.Marshal(c)
 	cj, err := json.Marshal(c)
 	if err != nil {
 	if err != nil {
@@ -247,10 +240,33 @@ func DefaultPricing() *models.CustomPricing {
 		ZoneNetworkEgress:     "0.01",
 		ZoneNetworkEgress:     "0.01",
 		RegionNetworkEgress:   "0.01",
 		RegionNetworkEgress:   "0.01",
 		InternetNetworkEgress: "0.12",
 		InternetNetworkEgress: "0.12",
+		NatGatewayEgress:      "0.045",
+		NatGatewayIngress:     "0.045",
 		CustomPricesEnabled:   "false",
 		CustomPricesEnabled:   "false",
 	}
 	}
 }
 }
 
 
+// Helper to default fields that may be left unset or empty due to config age
+func updateDefaultsOnEmpty(pricing *models.CustomPricing) *models.CustomPricing {
+	if pricing == nil {
+		return pricing
+	}
+
+	defaultPricing := DefaultPricing()
+
+	if pricing.SpotGPU == "" {
+		pricing.SpotGPU = defaultPricing.SpotGPU // Migration for users without this value set by default.
+	}
+	if pricing.NatGatewayEgress == "" {
+		pricing.NatGatewayEgress = defaultPricing.NatGatewayEgress
+	}
+	if pricing.NatGatewayIngress == "" {
+		pricing.NatGatewayIngress = defaultPricing.NatGatewayIngress
+	}
+
+	return pricing
+}
+
 // Gives the config file name in a full qualified file name
 // Gives the config file name in a full qualified file name
 func filenameInConfigPath(fqfn string) string {
 func filenameInConfigPath(fqfn string) string {
 	_, fileName := gopath.Split(fqfn)
 	_, fileName := gopath.Split(fqfn)
@@ -277,7 +293,7 @@ func ReturnPricingFromConfigs(filename string) (*models.CustomPricing, error) {
 	if err != nil {
 	if err != nil {
 		return &models.CustomPricing{}, fmt.Errorf("ReturnPricingFromConfigs: unable to open file %s with err: %v", providerConfigFile, err)
 		return &models.CustomPricing{}, fmt.Errorf("ReturnPricingFromConfigs: unable to open file %s with err: %v", providerConfigFile, err)
 	}
 	}
-	return defaultPricing, nil
+	return updateDefaultsOnEmpty(defaultPricing), nil
 }
 }
 
 
 func ExtractConfigFromProviders(prov models.Provider) models.ProviderConfig {
 func ExtractConfigFromProviders(prov models.Provider) models.ProviderConfig {

+ 2 - 0
pkg/cloud/scaleway/provider.go

@@ -176,6 +176,8 @@ func (c *Scaleway) NetworkPricing() (*models.Network, error) {
 		ZoneNetworkEgressCost:     0,
 		ZoneNetworkEgressCost:     0,
 		RegionNetworkEgressCost:   0,
 		RegionNetworkEgressCost:   0,
 		InternetNetworkEgressCost: 0,
 		InternetNetworkEgressCost: 0,
+		NatGatewayEgressCost:      0,
+		NatGatewayIngressCost:     0,
 	}, nil
 	}, nil
 }
 }
 
 

+ 12 - 0
pkg/costmodel/allocation.go

@@ -324,6 +324,12 @@ func (cm *CostModel) computeAllocation(start, end time.Time) (*opencost.Allocati
 	resChNetInternetGiB := source.WithGroup(grp, ds.QueryNetInternetGiB(start, end))
 	resChNetInternetGiB := source.WithGroup(grp, ds.QueryNetInternetGiB(start, end))
 	resChNetInternetPricePerGiB := source.WithGroup(grp, ds.QueryNetInternetPricePerGiB(start, end))
 	resChNetInternetPricePerGiB := source.WithGroup(grp, ds.QueryNetInternetPricePerGiB(start, end))
 
 
+	resChNetNatGatewayGiB := source.WithGroup(grp, ds.QueryNetNatGatewayGiB(start, end))
+	resChNetNatGatewayEgressPricePerGiB := source.WithGroup(grp, ds.QueryNetNatGatewayPricePerGiB(start, end))
+
+	resChNetNatGatewayIngressGiB := source.WithGroup(grp, ds.QueryNetNatGatewayIngressGiB(start, end))
+	resChNetNatGatewayIngressPricePerGiB := source.WithGroup(grp, ds.QueryNetNatGatewayIngressPricePerGiB(start, end))
+
 	var resChNodeLabels *source.QueryGroupFuture[source.NodeLabelsResult]
 	var resChNodeLabels *source.QueryGroupFuture[source.NodeLabelsResult]
 	if env.IsAllocationNodeLabelsEnabled() {
 	if env.IsAllocationNodeLabelsEnabled() {
 		resChNodeLabels = source.WithGroup(grp, ds.QueryNodeLabels(start, end))
 		resChNodeLabels = source.WithGroup(grp, ds.QueryNodeLabels(start, end))
@@ -389,6 +395,10 @@ func (cm *CostModel) computeAllocation(start, end time.Time) (*opencost.Allocati
 	resNetRegionPricePerGiB, _ := resChNetRegionPricePerGiB.Await()
 	resNetRegionPricePerGiB, _ := resChNetRegionPricePerGiB.Await()
 	resNetInternetGiB, _ := resChNetInternetGiB.Await()
 	resNetInternetGiB, _ := resChNetInternetGiB.Await()
 	resNetInternetPricePerGiB, _ := resChNetInternetPricePerGiB.Await()
 	resNetInternetPricePerGiB, _ := resChNetInternetPricePerGiB.Await()
+	resNetNatGatewayGiB, _ := resChNetNatGatewayGiB.Await()
+	resNetNatGatewayEgressPricePerGiB, _ := resChNetNatGatewayEgressPricePerGiB.Await()
+	resNetNatGatewayIngressGiB, _ := resChNetNatGatewayIngressGiB.Await()
+	resNetNatGatewayIngressPricePerGiB, _ := resChNetNatGatewayIngressPricePerGiB.Await()
 
 
 	var resNodeLabels []*source.NodeLabelsResult
 	var resNodeLabels []*source.NodeLabelsResult
 	if env.IsAllocationNodeLabelsEnabled() {
 	if env.IsAllocationNodeLabelsEnabled() {
@@ -439,6 +449,8 @@ func (cm *CostModel) computeAllocation(start, end time.Time) (*opencost.Allocati
 	applyNetworkAllocation(podMap, resNetZoneGiB, resNetZonePricePerGiB, podUIDKeyMap, applyCrossZoneNetworkAllocation)
 	applyNetworkAllocation(podMap, resNetZoneGiB, resNetZonePricePerGiB, podUIDKeyMap, applyCrossZoneNetworkAllocation)
 	applyNetworkAllocation(podMap, resNetRegionGiB, resNetRegionPricePerGiB, podUIDKeyMap, applyCrossRegionNetworkAllocation)
 	applyNetworkAllocation(podMap, resNetRegionGiB, resNetRegionPricePerGiB, podUIDKeyMap, applyCrossRegionNetworkAllocation)
 	applyNetworkAllocation(podMap, resNetInternetGiB, resNetInternetPricePerGiB, podUIDKeyMap, applyInternetNetworkAllocation)
 	applyNetworkAllocation(podMap, resNetInternetGiB, resNetInternetPricePerGiB, podUIDKeyMap, applyInternetNetworkAllocation)
+	applyNetworkAllocation(podMap, resNetNatGatewayGiB, resNetNatGatewayEgressPricePerGiB, podUIDKeyMap, applyNatGatewayEgressAllocation)
+	applyNetworkAllocation(podMap, resNetNatGatewayIngressGiB, resNetNatGatewayIngressPricePerGiB, podUIDKeyMap, applyNatGatewayIngressAllocation)
 
 
 	// In the case that a two pods with the same name had different containers,
 	// In the case that a two pods with the same name had different containers,
 	// we will double-count the containers. There is no way to associate each
 	// we will double-count the containers. There is no way to associate each

+ 8 - 0
pkg/costmodel/allocation_helpers.go

@@ -990,6 +990,14 @@ func applyInternetNetworkAllocation(alloc *opencost.Allocation, networkSubCost f
 	alloc.NetworkInternetCost = networkSubCost
 	alloc.NetworkInternetCost = networkSubCost
 }
 }
 
 
+func applyNatGatewayEgressAllocation(alloc *opencost.Allocation, networkSubCost float64) {
+	alloc.NetworkNatGatewayEgressCost = networkSubCost
+}
+
+func applyNatGatewayIngressAllocation(alloc *opencost.Allocation, networkSubCost float64) {
+	alloc.NetworkNatGatewayIngressCost = networkSubCost
+}
+
 func applyNetworkAllocation(podMap map[podKey]*pod, resNetworkGiB []*source.NetworkGiBResult, resNetworkCostPerGiB []*source.NetworkPricePerGiBResult, podUIDKeyMap map[podKey][]podKey, applyCostFunc func(*opencost.Allocation, float64)) {
 func applyNetworkAllocation(podMap map[podKey]*pod, resNetworkGiB []*source.NetworkGiBResult, resNetworkCostPerGiB []*source.NetworkPricePerGiBResult, podUIDKeyMap map[podKey][]podKey, applyCostFunc func(*opencost.Allocation, float64)) {
 	costPerGiBByCluster := map[string]float64{}
 	costPerGiBByCluster := map[string]float64{}
 
 

+ 9 - 5
pkg/costmodel/costmodel.go

@@ -188,7 +188,7 @@ func (cm *CostModel) ComputeCostData(start, end time.Time) (map[string]*CostData
 	}
 	}
 
 
 	// Get metrics data
 	// Get metrics data
-	resRAMUsage, resCPUUsage, resNetZoneRequests, resNetRegionRequests, resNetInternetRequests, err := queryMetrics(mq, start, end)
+	resRAMUsage, resCPUUsage, resNetZoneRequests, resNetRegionRequests, resNetInternetRequests, resNetNatGatewayRequests, resNetNatGatewayIngressRequests, err := queryMetrics(mq, start, end)
 	if err != nil {
 	if err != nil {
 		log.Warnf("ComputeCostData: continuing despite metrics errors: %s", err)
 		log.Warnf("ComputeCostData: continuing despite metrics errors: %s", err)
 	}
 	}
@@ -218,7 +218,7 @@ func (cm *CostModel) ComputeCostData(start, end time.Time) (map[string]*CostData
 		}
 		}
 	}
 	}
 
 
-	networkUsageMap, err := GetNetworkUsageData(resNetZoneRequests, resNetRegionRequests, resNetInternetRequests, clusterID)
+	networkUsageMap, err := GetNetworkUsageData(resNetZoneRequests, resNetRegionRequests, resNetInternetRequests, resNetNatGatewayRequests, resNetNatGatewayIngressRequests, clusterID)
 	if err != nil {
 	if err != nil {
 		log.Warnf("Unable to get Network Cost Data: %s", err.Error())
 		log.Warnf("Unable to get Network Cost Data: %s", err.Error())
 		networkUsageMap = make(map[string]*NetworkUsageData)
 		networkUsageMap = make(map[string]*NetworkUsageData)
@@ -563,7 +563,7 @@ func (cm *CostModel) ComputeCostData(start, end time.Time) (map[string]*CostData
 	return containerNameCost, err
 	return containerNameCost, err
 }
 }
 
 
-func queryMetrics(mq source.MetricsQuerier, start, end time.Time) ([]*source.ContainerMetricResult, []*source.ContainerMetricResult, []*source.NetZoneGiBResult, []*source.NetRegionGiBResult, []*source.NetInternetGiBResult, error) {
+func queryMetrics(mq source.MetricsQuerier, start, end time.Time) ([]*source.ContainerMetricResult, []*source.ContainerMetricResult, []*source.NetZoneGiBResult, []*source.NetRegionGiBResult, []*source.NetInternetGiBResult, []*source.NetNatGatewayGiBResult, []*source.NetNatGatewayIngressGiBResult, error) {
 	grp := source.NewQueryGroup()
 	grp := source.NewQueryGroup()
 
 
 	resChRAMUsage := source.WithGroup(grp, mq.QueryRAMUsageAvg(start, end))
 	resChRAMUsage := source.WithGroup(grp, mq.QueryRAMUsageAvg(start, end))
@@ -571,6 +571,8 @@ func queryMetrics(mq source.MetricsQuerier, start, end time.Time) ([]*source.Con
 	resChNetZoneRequests := source.WithGroup(grp, mq.QueryNetZoneGiB(start, end))
 	resChNetZoneRequests := source.WithGroup(grp, mq.QueryNetZoneGiB(start, end))
 	resChNetRegionRequests := source.WithGroup(grp, mq.QueryNetRegionGiB(start, end))
 	resChNetRegionRequests := source.WithGroup(grp, mq.QueryNetRegionGiB(start, end))
 	resChNetInternetRequests := source.WithGroup(grp, mq.QueryNetInternetGiB(start, end))
 	resChNetInternetRequests := source.WithGroup(grp, mq.QueryNetInternetGiB(start, end))
+	resChNetNatGatewayEgressRequests := source.WithGroup(grp, mq.QueryNetNatGatewayGiB(start, end))
+	resChNetNatGatewayIngressRequests := source.WithGroup(grp, mq.QueryNetNatGatewayIngressGiB(start, end))
 
 
 	// Process metrics query results. Handle errors using ctx.Errors.
 	// Process metrics query results. Handle errors using ctx.Errors.
 	resRAMUsage, _ := resChRAMUsage.Await()
 	resRAMUsage, _ := resChRAMUsage.Await()
@@ -578,6 +580,8 @@ func queryMetrics(mq source.MetricsQuerier, start, end time.Time) ([]*source.Con
 	resNetZoneRequests, _ := resChNetZoneRequests.Await()
 	resNetZoneRequests, _ := resChNetZoneRequests.Await()
 	resNetRegionRequests, _ := resChNetRegionRequests.Await()
 	resNetRegionRequests, _ := resChNetRegionRequests.Await()
 	resNetInternetRequests, _ := resChNetInternetRequests.Await()
 	resNetInternetRequests, _ := resChNetInternetRequests.Await()
+	resNetNatGatewayEgressRequests, _ := resChNetNatGatewayEgressRequests.Await()
+	resNetNatGatewayIngressRequests, _ := resChNetNatGatewayIngressRequests.Await()
 
 
 	// NOTE: The way we currently handle errors and warnings only early returns if there is an error. Warnings
 	// NOTE: The way we currently handle errors and warnings only early returns if there is an error. Warnings
 	// NOTE: will not propagate unless coupled with errors.
 	// NOTE: will not propagate unless coupled with errors.
@@ -595,10 +599,10 @@ func queryMetrics(mq source.MetricsQuerier, start, end time.Time) ([]*source.Con
 
 
 		// ErrorCollection is an collection of errors wrapped in a single error implementation
 		// ErrorCollection is an collection of errors wrapped in a single error implementation
 		// We opt to not return an error for the sake of running as a pure exporter.
 		// We opt to not return an error for the sake of running as a pure exporter.
-		return resRAMUsage, resCPUUsage, resNetZoneRequests, resNetRegionRequests, resNetInternetRequests, grp.Error()
+		return resRAMUsage, resCPUUsage, resNetZoneRequests, resNetRegionRequests, resNetInternetRequests, resNetNatGatewayEgressRequests, resNetNatGatewayIngressRequests, grp.Error()
 	}
 	}
 
 
-	return resRAMUsage, resCPUUsage, resNetZoneRequests, resNetRegionRequests, resNetInternetRequests, nil
+	return resRAMUsage, resCPUUsage, resNetZoneRequests, resNetRegionRequests, resNetInternetRequests, resNetNatGatewayEgressRequests, resNetNatGatewayIngressRequests, nil
 }
 }
 
 
 func findUnmountedPVCostData(clusterMap clusters.ClusterMap, unmountedPVs map[string][]*PersistentVolumeClaimData, namespaceLabelsMapping map[string]map[string]string, namespaceAnnotationsMapping map[string]map[string]string) map[string]*CostData {
 func findUnmountedPVCostData(clusterMap clusters.ClusterMap, unmountedPVs map[string][]*PersistentVolumeClaimData, namespaceLabelsMapping map[string]map[string]string, namespaceAnnotationsMapping map[string]map[string]string) map[string]*CostData {

+ 75 - 51
pkg/costmodel/metrics.go

@@ -118,22 +118,24 @@ func toStringPtr(s string) *string { return &s }
 var metricsInit sync.Once
 var metricsInit sync.Once
 
 
 var (
 var (
-	cpuGv                      *prometheus.GaugeVec
-	ramGv                      *prometheus.GaugeVec
-	gpuGv                      *prometheus.GaugeVec
-	gpuCountGv                 *prometheus.GaugeVec
-	pvGv                       *prometheus.GaugeVec
-	spotGv                     *prometheus.GaugeVec
-	totalGv                    *prometheus.GaugeVec
-	ramAllocGv                 *prometheus.GaugeVec
-	cpuAllocGv                 *prometheus.GaugeVec
-	gpuAllocGv                 *prometheus.GaugeVec
-	pvAllocGv                  *prometheus.GaugeVec
-	networkZoneEgressCostG     prometheus.Gauge
-	networkRegionEgressCostG   prometheus.Gauge
-	networkInternetEgressCostG prometheus.Gauge
-	clusterManagementCostGv    *prometheus.GaugeVec
-	lbCostGv                   *prometheus.GaugeVec
+	cpuGv                         *prometheus.GaugeVec
+	ramGv                         *prometheus.GaugeVec
+	gpuGv                         *prometheus.GaugeVec
+	gpuCountGv                    *prometheus.GaugeVec
+	pvGv                          *prometheus.GaugeVec
+	spotGv                        *prometheus.GaugeVec
+	totalGv                       *prometheus.GaugeVec
+	ramAllocGv                    *prometheus.GaugeVec
+	cpuAllocGv                    *prometheus.GaugeVec
+	gpuAllocGv                    *prometheus.GaugeVec
+	pvAllocGv                     *prometheus.GaugeVec
+	networkZoneEgressCostG        prometheus.Gauge
+	networkRegionEgressCostG      prometheus.Gauge
+	networkInternetEgressCostG    prometheus.Gauge
+	networkNatGatewayEgressCostG  prometheus.Gauge
+	networkNatGatewayIngressCostG prometheus.Gauge
+	clusterManagementCostGv       *prometheus.GaugeVec
+	lbCostGv                      *prometheus.GaugeVec
 )
 )
 
 
 // initCostModelMetrics uses a sync.Once to ensure that these metrics are only created once
 // initCostModelMetrics uses a sync.Once to ensure that these metrics are only created once
@@ -257,6 +259,22 @@ func initCostModelMetrics(clusterInfo clusters.ClusterInfoProvider, metricsConfi
 			toRegisterGauge = append(toRegisterGauge, networkInternetEgressCostG)
 			toRegisterGauge = append(toRegisterGauge, networkInternetEgressCostG)
 		}
 		}
 
 
+		networkNatGatewayEgressCostG = prometheus.NewGauge(prometheus.GaugeOpts{
+			Name: "kubecost_network_nat_gateway_egress_cost",
+			Help: "kubecost_network_nat_gateway_egress_cost Total cost per GB of nat gateway egress.",
+		})
+		if _, disabled := disabledMetrics["kubecost_network_nat_gateway_egress_cost"]; !disabled {
+			toRegisterGauge = append(toRegisterGauge, networkNatGatewayEgressCostG)
+		}
+
+		networkNatGatewayIngressCostG = prometheus.NewGauge(prometheus.GaugeOpts{
+			Name: "kubecost_network_nat_gateway_ingress_cost",
+			Help: "kubecost_network_nat_gateway_ingress_cost Total cost per GB of nat gateway ingress.",
+		})
+		if _, disabled := disabledMetrics["kubecost_network_nat_gateway_ingress_cost"]; !disabled {
+			toRegisterGauge = append(toRegisterGauge, networkNatGatewayIngressCostG)
+		}
+
 		clusterManagementCostGv = prometheus.NewGaugeVec(prometheus.GaugeOpts{
 		clusterManagementCostGv = prometheus.NewGaugeVec(prometheus.GaugeOpts{
 			Name: "kubecost_cluster_management_cost",
 			Name: "kubecost_cluster_management_cost",
 			Help: "kubecost_cluster_management_cost Hourly cost paid as a cluster management fee.",
 			Help: "kubecost_cluster_management_cost Hourly cost paid as a cluster management fee.",
@@ -302,22 +320,24 @@ type CostModelMetricsEmitter struct {
 	Model            *CostModel
 	Model            *CostModel
 
 
 	// Metrics
 	// Metrics
-	CPUPriceRecorder              *prometheus.GaugeVec
-	RAMPriceRecorder              *prometheus.GaugeVec
-	PersistentVolumePriceRecorder *prometheus.GaugeVec
-	GPUPriceRecorder              *prometheus.GaugeVec
-	GPUCountRecorder              *prometheus.GaugeVec
-	PVAllocationRecorder          *prometheus.GaugeVec
-	NodeSpotRecorder              *prometheus.GaugeVec
-	NodeTotalPriceRecorder        *prometheus.GaugeVec
-	RAMAllocationRecorder         *prometheus.GaugeVec
-	CPUAllocationRecorder         *prometheus.GaugeVec
-	GPUAllocationRecorder         *prometheus.GaugeVec
-	ClusterManagementCostRecorder *prometheus.GaugeVec
-	LBCostRecorder                *prometheus.GaugeVec
-	NetworkZoneEgressRecorder     prometheus.Gauge
-	NetworkRegionEgressRecorder   prometheus.Gauge
-	NetworkInternetEgressRecorder prometheus.Gauge
+	CPUPriceRecorder                 *prometheus.GaugeVec
+	RAMPriceRecorder                 *prometheus.GaugeVec
+	PersistentVolumePriceRecorder    *prometheus.GaugeVec
+	GPUPriceRecorder                 *prometheus.GaugeVec
+	GPUCountRecorder                 *prometheus.GaugeVec
+	PVAllocationRecorder             *prometheus.GaugeVec
+	NodeSpotRecorder                 *prometheus.GaugeVec
+	NodeTotalPriceRecorder           *prometheus.GaugeVec
+	RAMAllocationRecorder            *prometheus.GaugeVec
+	CPUAllocationRecorder            *prometheus.GaugeVec
+	GPUAllocationRecorder            *prometheus.GaugeVec
+	ClusterManagementCostRecorder    *prometheus.GaugeVec
+	LBCostRecorder                   *prometheus.GaugeVec
+	NetworkZoneEgressRecorder        prometheus.Gauge
+	NetworkRegionEgressRecorder      prometheus.Gauge
+	NetworkInternetEgressRecorder    prometheus.Gauge
+	NetworkNatGatewayEgressRecorder  prometheus.Gauge
+	NetworkNatGatewayIngressRecorder prometheus.Gauge
 
 
 	// Concurrent Flow Control - Manages the run state of the metric emitter
 	// Concurrent Flow Control - Manages the run state of the metric emitter
 	runState atomic.AtomicRunState
 	runState atomic.AtomicRunState
@@ -351,25 +371,27 @@ func NewCostModelMetricsEmitter(clusterCache clustercache.ClusterCache, provider
 	metrics.InitOpencostTelemetry(metricsConfig)
 	metrics.InitOpencostTelemetry(metricsConfig)
 
 
 	return &CostModelMetricsEmitter{
 	return &CostModelMetricsEmitter{
-		KubeClusterCache:              clusterCache,
-		CloudProvider:                 provider,
-		Model:                         model,
-		CPUPriceRecorder:              cpuGv,
-		RAMPriceRecorder:              ramGv,
-		GPUPriceRecorder:              gpuGv,
-		GPUCountRecorder:              gpuCountGv,
-		PersistentVolumePriceRecorder: pvGv,
-		NodeSpotRecorder:              spotGv,
-		NodeTotalPriceRecorder:        totalGv,
-		RAMAllocationRecorder:         ramAllocGv,
-		CPUAllocationRecorder:         cpuAllocGv,
-		GPUAllocationRecorder:         gpuAllocGv,
-		PVAllocationRecorder:          pvAllocGv,
-		NetworkZoneEgressRecorder:     networkZoneEgressCostG,
-		NetworkRegionEgressRecorder:   networkRegionEgressCostG,
-		NetworkInternetEgressRecorder: networkInternetEgressCostG,
-		ClusterManagementCostRecorder: clusterManagementCostGv,
-		LBCostRecorder:                lbCostGv,
+		KubeClusterCache:                 clusterCache,
+		CloudProvider:                    provider,
+		Model:                            model,
+		CPUPriceRecorder:                 cpuGv,
+		RAMPriceRecorder:                 ramGv,
+		GPUPriceRecorder:                 gpuGv,
+		GPUCountRecorder:                 gpuCountGv,
+		PersistentVolumePriceRecorder:    pvGv,
+		NodeSpotRecorder:                 spotGv,
+		NodeTotalPriceRecorder:           totalGv,
+		RAMAllocationRecorder:            ramAllocGv,
+		CPUAllocationRecorder:            cpuAllocGv,
+		GPUAllocationRecorder:            gpuAllocGv,
+		PVAllocationRecorder:             pvAllocGv,
+		NetworkZoneEgressRecorder:        networkZoneEgressCostG,
+		NetworkRegionEgressRecorder:      networkRegionEgressCostG,
+		NetworkInternetEgressRecorder:    networkInternetEgressCostG,
+		NetworkNatGatewayEgressRecorder:  networkNatGatewayEgressCostG,
+		NetworkNatGatewayIngressRecorder: networkNatGatewayIngressCostG,
+		ClusterManagementCostRecorder:    clusterManagementCostGv,
+		LBCostRecorder:                   lbCostGv,
 	}
 	}
 }
 }
 
 
@@ -474,6 +496,8 @@ func (cmme *CostModelMetricsEmitter) Start() bool {
 				cmme.NetworkZoneEgressRecorder.Set(networkCosts.ZoneNetworkEgressCost)
 				cmme.NetworkZoneEgressRecorder.Set(networkCosts.ZoneNetworkEgressCost)
 				cmme.NetworkRegionEgressRecorder.Set(networkCosts.RegionNetworkEgressCost)
 				cmme.NetworkRegionEgressRecorder.Set(networkCosts.RegionNetworkEgressCost)
 				cmme.NetworkInternetEgressRecorder.Set(networkCosts.InternetNetworkEgressCost)
 				cmme.NetworkInternetEgressRecorder.Set(networkCosts.InternetNetworkEgressCost)
+				cmme.NetworkNatGatewayEgressRecorder.Set(networkCosts.NatGatewayEgressCost)
+				cmme.NetworkNatGatewayIngressRecorder.Set(networkCosts.NatGatewayIngressCost)
 			}
 			}
 
 
 			end := time.Now()
 			end := time.Now()

+ 72 - 9
pkg/costmodel/networkcosts.go

@@ -8,14 +8,16 @@ import (
 	costAnalyzerCloud "github.com/opencost/opencost/pkg/cloud/models"
 	costAnalyzerCloud "github.com/opencost/opencost/pkg/cloud/models"
 )
 )
 
 
-// NetworkUsageVNetworkUsageDataector contains the network usage values for egress network traffic
+// NetworkUsageData contains the network usage values for egress network traffic and nat gateway
 type NetworkUsageData struct {
 type NetworkUsageData struct {
-	ClusterID             string
-	PodName               string
-	Namespace             string
-	NetworkZoneEgress     []*util.Vector
-	NetworkRegionEgress   []*util.Vector
-	NetworkInternetEgress []*util.Vector
+	ClusterID                string
+	PodName                  string
+	Namespace                string
+	NetworkZoneEgress        []*util.Vector
+	NetworkRegionEgress      []*util.Vector
+	NetworkInternetEgress    []*util.Vector
+	NetworkNatGatewayEgress  []*util.Vector
+	NetworkNatGatewayIngress []*util.Vector
 }
 }
 
 
 // NetworkUsageVector contains a network usage vector for egress network traffic
 // NetworkUsageVector contains a network usage vector for egress network traffic
@@ -28,7 +30,14 @@ type NetworkUsageVector struct {
 
 
 // GetNetworkUsageData performs a join of the the results of zone, region, and internet usage queries to return a single
 // GetNetworkUsageData performs a join of the the results of zone, region, and internet usage queries to return a single
 // map containing network costs for each namespace+pod
 // map containing network costs for each namespace+pod
-func GetNetworkUsageData(zr []*source.NetZoneGiBResult, rr []*source.NetRegionGiBResult, ir []*source.NetInternetGiBResult, defaultClusterID string) (map[string]*NetworkUsageData, error) {
+func GetNetworkUsageData(
+	zr []*source.NetZoneGiBResult,
+	rr []*source.NetRegionGiBResult,
+	ir []*source.NetInternetGiBResult,
+	nge []*source.NetNatGatewayGiBResult,
+	ngi []*source.NetNatGatewayIngressGiBResult,
+	defaultClusterID string,
+) (map[string]*NetworkUsageData, error) {
 	zoneNetworkMap, err := getNetworkUsage(zr, defaultClusterID)
 	zoneNetworkMap, err := getNetworkUsage(zr, defaultClusterID)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -44,6 +53,16 @@ func GetNetworkUsageData(zr []*source.NetZoneGiBResult, rr []*source.NetRegionGi
 		return nil, err
 		return nil, err
 	}
 	}
 
 
+	natGatewayEgressNetMap, err := getNetworkUsage(nge, defaultClusterID)
+	if err != nil {
+		return nil, err
+	}
+
+	natGatewayIngressNetMap, err := getNetworkUsage(ngi, defaultClusterID)
+	if err != nil {
+		return nil, err
+	}
+
 	usageData := make(map[string]*NetworkUsageData)
 	usageData := make(map[string]*NetworkUsageData)
 	for k, v := range zoneNetworkMap {
 	for k, v := range zoneNetworkMap {
 		existing, ok := usageData[k]
 		existing, ok := usageData[k]
@@ -90,6 +109,36 @@ func GetNetworkUsageData(zr []*source.NetZoneGiBResult, rr []*source.NetRegionGi
 		existing.NetworkInternetEgress = v.Values
 		existing.NetworkInternetEgress = v.Values
 	}
 	}
 
 
+	for k, v := range natGatewayEgressNetMap {
+		existing, ok := usageData[k]
+		if !ok {
+			usageData[k] = &NetworkUsageData{
+				ClusterID:               v.ClusterID,
+				PodName:                 v.PodName,
+				Namespace:               v.Namespace,
+				NetworkNatGatewayEgress: v.Values,
+			}
+			continue
+		}
+
+		existing.NetworkNatGatewayEgress = v.Values
+	}
+
+	for k, v := range natGatewayIngressNetMap {
+		existing, ok := usageData[k]
+		if !ok {
+			usageData[k] = &NetworkUsageData{
+				ClusterID:                v.ClusterID,
+				PodName:                  v.PodName,
+				Namespace:                v.Namespace,
+				NetworkNatGatewayIngress: v.Values,
+			}
+			continue
+		}
+
+		existing.NetworkNatGatewayIngress = v.Values
+	}
+
 	return usageData, nil
 	return usageData, nil
 }
 }
 
 
@@ -104,12 +153,16 @@ func GetNetworkCost(usage *NetworkUsageData, cloud costAnalyzerCloud.Provider) (
 	zoneCost := pricing.ZoneNetworkEgressCost
 	zoneCost := pricing.ZoneNetworkEgressCost
 	regionCost := pricing.RegionNetworkEgressCost
 	regionCost := pricing.RegionNetworkEgressCost
 	internetCost := pricing.InternetNetworkEgressCost
 	internetCost := pricing.InternetNetworkEgressCost
+	natGatewayEgressCost := pricing.NatGatewayEgressCost
+	natGatewayIngressCost := pricing.NatGatewayIngressCost
 
 
 	zlen := len(usage.NetworkZoneEgress)
 	zlen := len(usage.NetworkZoneEgress)
 	rlen := len(usage.NetworkRegionEgress)
 	rlen := len(usage.NetworkRegionEgress)
 	ilen := len(usage.NetworkInternetEgress)
 	ilen := len(usage.NetworkInternetEgress)
+	ngelen := len(usage.NetworkNatGatewayEgress)
+	ngilen := len(usage.NetworkNatGatewayIngress)
 
 
-	l := max(zlen, rlen, ilen)
+	l := max(zlen, rlen, ilen, ngelen, ngilen)
 	for i := 0; i < l; i++ {
 	for i := 0; i < l; i++ {
 		var cost float64 = 0
 		var cost float64 = 0
 		var timestamp float64
 		var timestamp float64
@@ -129,6 +182,16 @@ func GetNetworkCost(usage *NetworkUsageData, cloud costAnalyzerCloud.Provider) (
 			timestamp = usage.NetworkInternetEgress[i].Timestamp
 			timestamp = usage.NetworkInternetEgress[i].Timestamp
 		}
 		}
 
 
+		if i < ngelen {
+			cost += usage.NetworkNatGatewayEgress[i].Value * natGatewayEgressCost
+			timestamp = usage.NetworkNatGatewayEgress[i].Timestamp
+		}
+
+		if i < ngilen {
+			cost += usage.NetworkNatGatewayIngress[i].Value * natGatewayIngressCost
+			timestamp = usage.NetworkNatGatewayIngress[i].Timestamp
+		}
+
 		results = append(results, &util.Vector{
 		results = append(results, &util.Vector{
 			Value:     cost,
 			Value:     cost,
 			Timestamp: timestamp,
 			Timestamp: timestamp,

+ 618 - 0
pkg/costmodel/networkcosts_test.go

@@ -0,0 +1,618 @@
+package costmodel
+
+import (
+	"io"
+	"testing"
+
+	"github.com/opencost/opencost/core/pkg/clustercache"
+	"github.com/opencost/opencost/core/pkg/source"
+	"github.com/opencost/opencost/core/pkg/util"
+	"github.com/opencost/opencost/pkg/cloud/models"
+)
+
+// mockProvider is a mock implementation of the Provider interface for testing
+type mockProvider struct {
+	network *models.Network
+	err     error
+}
+
+func (m *mockProvider) NetworkPricing() (*models.Network, error) {
+	return m.network, m.err
+}
+
+func (m *mockProvider) GetKey(map[string]string, *clustercache.Node) models.Key {
+	return nil
+}
+
+func (m *mockProvider) PVPricing(models.PVKey) (*models.PV, error) {
+	return nil, nil
+}
+
+func (m *mockProvider) NodePricing(models.Key) (*models.Node, models.PricingMetadata, error) {
+	return nil, models.PricingMetadata{}, nil
+}
+
+func (m *mockProvider) LoadBalancerPricing() (*models.LoadBalancer, error) {
+	return nil, nil
+}
+
+func (m *mockProvider) AllNodePricing() (interface{}, error) {
+	return nil, nil
+}
+
+func (m *mockProvider) ClusterInfo() (map[string]string, error) {
+	return nil, nil
+}
+
+func (m *mockProvider) GetAddresses() ([]byte, error) {
+	return nil, nil
+}
+
+func (m *mockProvider) GetDisks() ([]byte, error) {
+	return nil, nil
+}
+
+func (m *mockProvider) GetOrphanedResources() ([]models.OrphanedResource, error) {
+	return nil, nil
+}
+
+func (m *mockProvider) GpuPricing(map[string]string) (string, error) {
+	return "", nil
+}
+
+func (m *mockProvider) DownloadPricingData() error {
+	return nil
+}
+
+func (m *mockProvider) GetPVKey(*clustercache.PersistentVolume, map[string]string, string) models.PVKey {
+	return nil
+}
+
+func (m *mockProvider) UpdateConfig(io.Reader, string) (*models.CustomPricing, error) {
+	return nil, nil
+}
+
+func (m *mockProvider) UpdateConfigFromConfigMap(map[string]string) (*models.CustomPricing, error) {
+	return nil, nil
+}
+
+func (m *mockProvider) GetConfig() (*models.CustomPricing, error) {
+	return nil, nil
+}
+
+func (m *mockProvider) GetManagementPlatform() (string, error) {
+	return "", nil
+}
+
+func (m *mockProvider) ApplyReservedInstancePricing(map[string]*models.Node) {
+}
+
+func (m *mockProvider) ServiceAccountStatus() *models.ServiceAccountStatus {
+	return nil
+}
+
+func (m *mockProvider) PricingSourceStatus() map[string]*models.PricingSource {
+	return nil
+}
+
+func (m *mockProvider) ClusterManagementPricing() (string, float64, error) {
+	return "", 0.0, nil
+}
+
+func (m *mockProvider) CombinedDiscountForNode(string, bool, float64, float64) float64 {
+	return 0.0
+}
+
+func (m *mockProvider) Regions() []string {
+	return nil
+}
+
+func (m *mockProvider) PricingSourceSummary() interface{} {
+	return nil
+}
+
+// TestGetNetworkUsageData tests the aggregation of NAT Gateway egress and ingress data
+func TestGetNetworkUsageData(t *testing.T) {
+	defaultClusterID := "default-cluster"
+
+	testCases := []struct {
+		name                     string
+		zoneResults              []*source.NetZoneGiBResult
+		regionResults            []*source.NetRegionGiBResult
+		internetResults          []*source.NetInternetGiBResult
+		natGatewayEgressResults  []*source.NetNatGatewayGiBResult
+		natGatewayIngressResults []*source.NetNatGatewayIngressGiBResult
+		expectedKeys             []string
+		validateFunc             func(t *testing.T, result map[string]*NetworkUsageData)
+	}{
+		{
+			name:            "NAT Gateway egress only",
+			zoneResults:     nil,
+			regionResults:   nil,
+			internetResults: nil,
+			natGatewayEgressResults: []*source.NetNatGatewayGiBResult{
+				{
+					Pod:       "pod1",
+					Namespace: "ns1",
+					Cluster:   "cluster1",
+					Data: []*util.Vector{
+						{Value: 10.5, Timestamp: 1000},
+						{Value: 20.3, Timestamp: 2000},
+					},
+				},
+			},
+			natGatewayIngressResults: nil,
+			expectedKeys:             []string{"ns1,pod1,cluster1"},
+			validateFunc: func(t *testing.T, result map[string]*NetworkUsageData) {
+				key := "ns1,pod1,cluster1"
+				if data, ok := result[key]; ok {
+					if len(data.NetworkNatGatewayEgress) != 2 {
+						t.Errorf("expected 2 NAT Gateway egress vectors, got %d", len(data.NetworkNatGatewayEgress))
+					}
+					if data.NetworkNatGatewayEgress[0].Value != 10.5 {
+						t.Errorf("expected first egress value 10.5, got %f", data.NetworkNatGatewayEgress[0].Value)
+					}
+					if len(data.NetworkNatGatewayIngress) != 0 {
+						t.Errorf("expected 0 NAT Gateway ingress vectors, got %d", len(data.NetworkNatGatewayIngress))
+					}
+				} else {
+					t.Errorf("expected key %s not found in result", key)
+				}
+			},
+		},
+		{
+			name:                    "NAT Gateway ingress only",
+			zoneResults:             nil,
+			regionResults:           nil,
+			internetResults:         nil,
+			natGatewayEgressResults: nil,
+			natGatewayIngressResults: []*source.NetNatGatewayIngressGiBResult{
+				{
+					Pod:       "pod2",
+					Namespace: "ns2",
+					Cluster:   "cluster2",
+					Data: []*util.Vector{
+						{Value: 5.2, Timestamp: 1000},
+					},
+				},
+			},
+			expectedKeys: []string{"ns2,pod2,cluster2"},
+			validateFunc: func(t *testing.T, result map[string]*NetworkUsageData) {
+				key := "ns2,pod2,cluster2"
+				if data, ok := result[key]; ok {
+					if len(data.NetworkNatGatewayIngress) != 1 {
+						t.Errorf("expected 1 NAT Gateway ingress vector, got %d", len(data.NetworkNatGatewayIngress))
+					}
+					if data.NetworkNatGatewayIngress[0].Value != 5.2 {
+						t.Errorf("expected ingress value 5.2, got %f", data.NetworkNatGatewayIngress[0].Value)
+					}
+					if len(data.NetworkNatGatewayEgress) != 0 {
+						t.Errorf("expected 0 NAT Gateway egress vectors, got %d", len(data.NetworkNatGatewayEgress))
+					}
+				} else {
+					t.Errorf("expected key %s not found in result", key)
+				}
+			},
+		},
+		{
+			name:            "NAT Gateway egress and ingress for same pod",
+			zoneResults:     nil,
+			regionResults:   nil,
+			internetResults: nil,
+			natGatewayEgressResults: []*source.NetNatGatewayGiBResult{
+				{
+					Pod:       "pod3",
+					Namespace: "ns3",
+					Cluster:   "cluster3",
+					Data: []*util.Vector{
+						{Value: 15.0, Timestamp: 1000},
+					},
+				},
+			},
+			natGatewayIngressResults: []*source.NetNatGatewayIngressGiBResult{
+				{
+					Pod:       "pod3",
+					Namespace: "ns3",
+					Cluster:   "cluster3",
+					Data: []*util.Vector{
+						{Value: 8.5, Timestamp: 1000},
+					},
+				},
+			},
+			expectedKeys: []string{"ns3,pod3,cluster3"},
+			validateFunc: func(t *testing.T, result map[string]*NetworkUsageData) {
+				key := "ns3,pod3,cluster3"
+				if data, ok := result[key]; ok {
+					if len(data.NetworkNatGatewayEgress) != 1 {
+						t.Errorf("expected 1 NAT Gateway egress vector, got %d", len(data.NetworkNatGatewayEgress))
+					}
+					if data.NetworkNatGatewayEgress[0].Value != 15.0 {
+						t.Errorf("expected egress value 15.0, got %f", data.NetworkNatGatewayEgress[0].Value)
+					}
+					if len(data.NetworkNatGatewayIngress) != 1 {
+						t.Errorf("expected 1 NAT Gateway ingress vector, got %d", len(data.NetworkNatGatewayIngress))
+					}
+					if data.NetworkNatGatewayIngress[0].Value != 8.5 {
+						t.Errorf("expected ingress value 8.5, got %f", data.NetworkNatGatewayIngress[0].Value)
+					}
+				} else {
+					t.Errorf("expected key %s not found in result", key)
+				}
+			},
+		},
+		{
+			name: "Mixed network traffic with NAT Gateway",
+			zoneResults: []*source.NetZoneGiBResult{
+				{
+					Pod:       "pod4",
+					Namespace: "ns4",
+					Cluster:   "cluster4",
+					Data: []*util.Vector{
+						{Value: 3.0, Timestamp: 1000},
+					},
+				},
+			},
+			regionResults: []*source.NetRegionGiBResult{
+				{
+					Pod:       "pod4",
+					Namespace: "ns4",
+					Cluster:   "cluster4",
+					Data: []*util.Vector{
+						{Value: 7.0, Timestamp: 1000},
+					},
+				},
+			},
+			internetResults: []*source.NetInternetGiBResult{
+				{
+					Pod:       "pod4",
+					Namespace: "ns4",
+					Cluster:   "cluster4",
+					Data: []*util.Vector{
+						{Value: 12.0, Timestamp: 1000},
+					},
+				},
+			},
+			natGatewayEgressResults: []*source.NetNatGatewayGiBResult{
+				{
+					Pod:       "pod4",
+					Namespace: "ns4",
+					Cluster:   "cluster4",
+					Data: []*util.Vector{
+						{Value: 18.0, Timestamp: 1000},
+					},
+				},
+			},
+			natGatewayIngressResults: []*source.NetNatGatewayIngressGiBResult{
+				{
+					Pod:       "pod4",
+					Namespace: "ns4",
+					Cluster:   "cluster4",
+					Data: []*util.Vector{
+						{Value: 9.0, Timestamp: 1000},
+					},
+				},
+			},
+			expectedKeys: []string{"ns4,pod4,cluster4"},
+			validateFunc: func(t *testing.T, result map[string]*NetworkUsageData) {
+				key := "ns4,pod4,cluster4"
+				if data, ok := result[key]; ok {
+					// Verify all network types are present
+					if len(data.NetworkZoneEgress) != 1 {
+						t.Errorf("expected 1 zone egress vector, got %d", len(data.NetworkZoneEgress))
+					}
+					if len(data.NetworkRegionEgress) != 1 {
+						t.Errorf("expected 1 region egress vector, got %d", len(data.NetworkRegionEgress))
+					}
+					if len(data.NetworkInternetEgress) != 1 {
+						t.Errorf("expected 1 internet egress vector, got %d", len(data.NetworkInternetEgress))
+					}
+					if len(data.NetworkNatGatewayEgress) != 1 {
+						t.Errorf("expected 1 NAT Gateway egress vector, got %d", len(data.NetworkNatGatewayEgress))
+					}
+					if len(data.NetworkNatGatewayIngress) != 1 {
+						t.Errorf("expected 1 NAT Gateway ingress vector, got %d", len(data.NetworkNatGatewayIngress))
+					}
+
+					// Verify values
+					if data.NetworkNatGatewayEgress[0].Value != 18.0 {
+						t.Errorf("expected NAT Gateway egress 18.0, got %f", data.NetworkNatGatewayEgress[0].Value)
+					}
+					if data.NetworkNatGatewayIngress[0].Value != 9.0 {
+						t.Errorf("expected NAT Gateway ingress 9.0, got %f", data.NetworkNatGatewayIngress[0].Value)
+					}
+				} else {
+					t.Errorf("expected key %s not found in result", key)
+				}
+			},
+		},
+		{
+			name:            "Default cluster ID fallback for NAT Gateway",
+			zoneResults:     nil,
+			regionResults:   nil,
+			internetResults: nil,
+			natGatewayEgressResults: []*source.NetNatGatewayGiBResult{
+				{
+					Pod:       "pod5",
+					Namespace: "ns5",
+					Cluster:   "", // Empty cluster ID should use default
+					Data: []*util.Vector{
+						{Value: 5.0, Timestamp: 1000},
+					},
+				},
+			},
+			natGatewayIngressResults: nil,
+			expectedKeys:             []string{"ns5,pod5,default-cluster"},
+			validateFunc: func(t *testing.T, result map[string]*NetworkUsageData) {
+				key := "ns5,pod5,default-cluster"
+				if data, ok := result[key]; ok {
+					if data.ClusterID != "default-cluster" {
+						t.Errorf("expected cluster ID 'default-cluster', got %s", data.ClusterID)
+					}
+				} else {
+					t.Errorf("expected key %s not found in result", key)
+				}
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			result, err := GetNetworkUsageData(
+				tc.zoneResults,
+				tc.regionResults,
+				tc.internetResults,
+				tc.natGatewayEgressResults,
+				tc.natGatewayIngressResults,
+				defaultClusterID,
+			)
+
+			if err != nil {
+				t.Fatalf("unexpected error: %v", err)
+			}
+
+			if len(result) != len(tc.expectedKeys) {
+				t.Errorf("expected %d keys, got %d", len(tc.expectedKeys), len(result))
+			}
+
+			for _, key := range tc.expectedKeys {
+				if _, ok := result[key]; !ok {
+					t.Errorf("expected key %s not found in result", key)
+				}
+			}
+
+			if tc.validateFunc != nil {
+				tc.validateFunc(t, result)
+			}
+		})
+	}
+}
+
+// TestGetNetworkCost tests the calculation of NAT Gateway costs
+func TestGetNetworkCost(t *testing.T) {
+	testCases := []struct {
+		name           string
+		usage          *NetworkUsageData
+		pricing        *models.Network
+		expectedCost   float64
+		expectedLength int
+	}{
+		{
+			name: "NAT Gateway egress cost only",
+			usage: &NetworkUsageData{
+				ClusterID: "cluster1",
+				PodName:   "pod1",
+				Namespace: "ns1",
+				NetworkNatGatewayEgress: []*util.Vector{
+					{Value: 10.0, Timestamp: 1000}, // 10 GiB
+				},
+			},
+			pricing: &models.Network{
+				NatGatewayEgressCost: 0.05, // $0.05 per GiB
+			},
+			expectedCost:   0.50, // 10 * 0.05 = 0.50
+			expectedLength: 1,
+		},
+		{
+			name: "NAT Gateway ingress cost only",
+			usage: &NetworkUsageData{
+				ClusterID: "cluster1",
+				PodName:   "pod1",
+				Namespace: "ns1",
+				NetworkNatGatewayIngress: []*util.Vector{
+					{Value: 20.0, Timestamp: 1000}, // 20 GiB
+				},
+			},
+			pricing: &models.Network{
+				NatGatewayIngressCost: 0.02, // $0.02 per GiB
+			},
+			expectedCost:   0.40, // 20 * 0.02 = 0.40
+			expectedLength: 1,
+		},
+		{
+			name: "NAT Gateway egress and ingress costs",
+			usage: &NetworkUsageData{
+				ClusterID: "cluster1",
+				PodName:   "pod1",
+				Namespace: "ns1",
+				NetworkNatGatewayEgress: []*util.Vector{
+					{Value: 10.0, Timestamp: 1000},
+				},
+				NetworkNatGatewayIngress: []*util.Vector{
+					{Value: 5.0, Timestamp: 1000},
+				},
+			},
+			pricing: &models.Network{
+				NatGatewayEgressCost:  0.05, // $0.05 per GiB
+				NatGatewayIngressCost: 0.02, // $0.02 per GiB
+			},
+			expectedCost:   0.60, // (10 * 0.05) + (5 * 0.02) = 0.50 + 0.10 = 0.60
+			expectedLength: 1,
+		},
+		{
+			name: "Mixed network costs with NAT Gateway",
+			usage: &NetworkUsageData{
+				ClusterID: "cluster1",
+				PodName:   "pod1",
+				Namespace: "ns1",
+				NetworkZoneEgress: []*util.Vector{
+					{Value: 5.0, Timestamp: 1000},
+				},
+				NetworkRegionEgress: []*util.Vector{
+					{Value: 8.0, Timestamp: 1000},
+				},
+				NetworkInternetEgress: []*util.Vector{
+					{Value: 12.0, Timestamp: 1000},
+				},
+				NetworkNatGatewayEgress: []*util.Vector{
+					{Value: 15.0, Timestamp: 1000},
+				},
+				NetworkNatGatewayIngress: []*util.Vector{
+					{Value: 10.0, Timestamp: 1000},
+				},
+			},
+			pricing: &models.Network{
+				ZoneNetworkEgressCost:     0.01,
+				RegionNetworkEgressCost:   0.02,
+				InternetNetworkEgressCost: 0.09,
+				NatGatewayEgressCost:      0.05,
+				NatGatewayIngressCost:     0.02,
+			},
+			expectedCost:   2.24, // (5*0.01) + (8*0.02) + (12*0.09) + (15*0.05) + (10*0.02) = 0.05 + 0.16 + 1.08 + 0.75 + 0.20 = 2.24
+			expectedLength: 1,
+		},
+		{
+			name: "Multiple time points with NAT Gateway",
+			usage: &NetworkUsageData{
+				ClusterID: "cluster1",
+				PodName:   "pod1",
+				Namespace: "ns1",
+				NetworkNatGatewayEgress: []*util.Vector{
+					{Value: 10.0, Timestamp: 1000},
+					{Value: 15.0, Timestamp: 2000},
+					{Value: 20.0, Timestamp: 3000},
+				},
+				NetworkNatGatewayIngress: []*util.Vector{
+					{Value: 5.0, Timestamp: 1000},
+					{Value: 8.0, Timestamp: 2000},
+					{Value: 12.0, Timestamp: 3000},
+				},
+			},
+			pricing: &models.Network{
+				NatGatewayEgressCost:  0.05,
+				NatGatewayIngressCost: 0.02,
+			},
+			expectedLength: 3,
+		},
+		{
+			name: "Zero NAT Gateway costs",
+			usage: &NetworkUsageData{
+				ClusterID: "cluster1",
+				PodName:   "pod1",
+				Namespace: "ns1",
+				NetworkNatGatewayEgress: []*util.Vector{
+					{Value: 100.0, Timestamp: 1000},
+				},
+				NetworkNatGatewayIngress: []*util.Vector{
+					{Value: 50.0, Timestamp: 1000},
+				},
+			},
+			pricing: &models.Network{
+				NatGatewayEgressCost:  0.0,
+				NatGatewayIngressCost: 0.0,
+			},
+			expectedCost:   0.0,
+			expectedLength: 1,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			provider := &mockProvider{
+				network: tc.pricing,
+			}
+
+			result, err := GetNetworkCost(tc.usage, provider)
+			if err != nil {
+				t.Fatalf("unexpected error: %v", err)
+			}
+
+			if len(result) != tc.expectedLength {
+				t.Errorf("expected %d result vectors, got %d", tc.expectedLength, len(result))
+			}
+
+			if tc.expectedLength > 0 {
+				totalCost := 0.0
+				for _, v := range result {
+					totalCost += v.Value
+				}
+
+				if tc.expectedCost > 0 {
+					if diff := totalCost - tc.expectedCost; diff > 0.001 || diff < -0.001 {
+						t.Errorf("expected total cost %f, got %f", tc.expectedCost, totalCost)
+					}
+				}
+			}
+		})
+	}
+}
+
+// TestGetNetworkCost_NATGatewayMisalignedVectors tests NAT Gateway cost calculation with different vector lengths
+func TestGetNetworkCost_NATGatewayMisalignedVectors(t *testing.T) {
+	usage := &NetworkUsageData{
+		ClusterID: "cluster1",
+		PodName:   "pod1",
+		Namespace: "ns1",
+		NetworkZoneEgress: []*util.Vector{
+			{Value: 5.0, Timestamp: 1000},
+			{Value: 6.0, Timestamp: 2000},
+		},
+		NetworkNatGatewayEgress: []*util.Vector{
+			{Value: 10.0, Timestamp: 1000},
+			{Value: 15.0, Timestamp: 2000},
+			{Value: 20.0, Timestamp: 3000}, // Extra NAT Gateway data point
+		},
+		NetworkNatGatewayIngress: []*util.Vector{
+			{Value: 5.0, Timestamp: 1000}, // Only one ingress data point
+		},
+	}
+
+	pricing := &models.Network{
+		ZoneNetworkEgressCost: 0.01,
+		NatGatewayEgressCost:  0.05,
+		NatGatewayIngressCost: 0.02,
+	}
+
+	provider := &mockProvider{
+		network: pricing,
+	}
+
+	result, err := GetNetworkCost(usage, provider)
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+
+	// Should have 3 result vectors (max of all vector lengths including NAT Gateway)
+	if len(result) != 3 {
+		t.Errorf("expected 3 result vectors (max of all vectors including NAT Gateway), got %d", len(result))
+	}
+
+	// First vector: zone (5*0.01) + natEgress (10*0.05) + natIngress (5*0.02) = 0.05 + 0.50 + 0.10 = 0.65
+	expectedFirst := (5.0 * 0.01) + (10.0 * 0.05) + (5.0 * 0.02)
+	if diff := result[0].Value - expectedFirst; diff > 0.001 || diff < -0.001 {
+		t.Errorf("expected first vector cost %f, got %f", expectedFirst, result[0].Value)
+	}
+
+	// Second vector: zone (6*0.01) + natEgress (15*0.05) = 0.06 + 0.75 = 0.81
+	// (no NAT ingress for second time point)
+	expectedSecond := (6.0 * 0.01) + (15.0 * 0.05)
+	if diff := result[1].Value - expectedSecond; diff > 0.001 || diff < -0.001 {
+		t.Errorf("expected second vector cost %f, got %f", expectedSecond, result[1].Value)
+	}
+
+	// Third vector: only natEgress (20*0.05) = 1.00
+	// (no zone, region, internet, or NAT ingress for third time point)
+	expectedThird := 20.0 * 0.05
+	if diff := result[2].Value - expectedThird; diff > 0.001 || diff < -0.001 {
+		t.Errorf("expected third vector cost %f, got %f", expectedThird, result[2].Value)
+	}
+}