ソースを参照

Update isSpot

Signed-off-by: Sean Holcomb <seanholcomb@gmail.com>
Sean Holcomb 1 ヶ月 前
コミット
553c85c6ff

+ 0 - 2
core/pkg/model/kubemodel/node.go

@@ -14,8 +14,6 @@ type Node struct {
 	ProviderID           string             `json:"providerId"`
 	ProviderID           string             `json:"providerId"`
 	Name                 string             `json:"name"`
 	Name                 string             `json:"name"`
 	Labels               map[string]string  `json:"labels"`
 	Labels               map[string]string  `json:"labels"`
-	InstanceType         string             `json:"instanceType"`
-	Preemptible          bool               `json:"preemptible"` // TODO unpopulated
 	ResourceCapacities   ResourceQuantities `json:"resourceCapacities"`
 	ResourceCapacities   ResourceQuantities `json:"resourceCapacities"`
 	ResourcesAllocatable ResourceQuantities `json:"resourcesAllocatable"`
 	ResourcesAllocatable ResourceQuantities `json:"resourcesAllocatable"`
 	FileSystem           FileSystem         `json:"fileSystem"`
 	FileSystem           FileSystem         `json:"fileSystem"`

+ 40 - 0
core/pkg/util/compat.go

@@ -8,6 +8,22 @@ import (
 
 
 // See https://kubernetes.io/docs/reference/labels-annotations-taints/
 // See https://kubernetes.io/docs/reference/labels-annotations-taints/
 
 
+// Spot/preemptible node label constants across cloud providers.
+const (
+	// KarpenterCapacityTypeLabel is set by Karpenter on AWS and GCP.
+	KarpenterCapacityTypeLabel = "karpenter.sh/capacity-type"
+
+	// GKE native labels
+	GKEPreemptibleLabel = "cloud.google.com/gke-preemptible"
+	GKESpotLabel        = "cloud.google.com/gke-spot"
+
+	// Azure VMSS priority label
+	AzureSpotLabel = "kubernetes.azure.com/scalesetpriority"
+
+	// Oracle OKE preemptible label
+	OCIPreemptibleLabel = "oci.oraclecloud.com/oke-is-preemptible"
+)
+
 func GetZone(labels map[string]string) (string, bool) {
 func GetZone(labels map[string]string) (string, bool) {
 	if _, ok := labels[v1.LabelTopologyZone]; ok { // Label as of 1.17
 	if _, ok := labels[v1.LabelTopologyZone]; ok { // Label as of 1.17
 		return labels[v1.LabelTopologyZone], true
 		return labels[v1.LabelTopologyZone], true
@@ -58,6 +74,30 @@ func GetArchType(labels map[string]string) (string, bool) {
 	}
 	}
 }
 }
 
 
+// IsPreemptible returns true if the node labels indicate a spot or preemptible
+// instance. It covers GKE (preemptible + Spot VMs), AWS/GCP via Karpenter,
+// Azure VMSS spot, and Oracle OKE preemptible nodes.
+// this function does not currently support user set `SpotLabel` and SpotLabelValue`
+// we could add this here via environment variables
+func IsPreemptible(labels map[string]string) bool {
+	if labels[GKEPreemptibleLabel] == "true" {
+		return true
+	}
+	if labels[GKESpotLabel] == "true" {
+		return true
+	}
+	if labels[KarpenterCapacityTypeLabel] == "spot" {
+		return true
+	}
+	if labels[AzureSpotLabel] == "spot" {
+		return true
+	}
+	if labels[OCIPreemptibleLabel] == "true" {
+		return true
+	}
+	return false
+}
+
 func PrivateIPCheck(ip string) bool {
 func PrivateIPCheck(ip string) bool {
 	ipAddress := net.ParseIP(ip)
 	ipAddress := net.ParseIP(ip)
 	return ipAddress.IsPrivate()
 	return ipAddress.IsPrivate()

+ 81 - 0
core/pkg/util/compat_test.go

@@ -4,6 +4,87 @@ import (
 	"testing"
 	"testing"
 )
 )
 
 
+func TestIsPreemptible(t *testing.T) {
+	tests := map[string]struct {
+		labels map[string]string
+		want   bool
+	}{
+		"gke preemptible": {
+			labels: map[string]string{"cloud.google.com/gke-preemptible": "true"},
+			want:   true,
+		},
+		"gke spot": {
+			labels: map[string]string{"cloud.google.com/gke-spot": "true"},
+			want:   true,
+		},
+		"karpenter spot": {
+			labels: map[string]string{"karpenter.sh/capacity-type": "spot"},
+			want:   true,
+		},
+		"karpenter on-demand": {
+			labels: map[string]string{"karpenter.sh/capacity-type": "on-demand"},
+			want:   false,
+		},
+		"azure spot": {
+			labels: map[string]string{"kubernetes.azure.com/scalesetpriority": "spot"},
+			want:   true,
+		},
+		"azure regular": {
+			labels: map[string]string{"kubernetes.azure.com/scalesetpriority": "regular"},
+			want:   false,
+		},
+		"oci preemptible": {
+			labels: map[string]string{"oci.oraclecloud.com/oke-is-preemptible": "true"},
+			want:   true,
+		},
+		"on-demand node": {
+			labels: map[string]string{"kubernetes.io/arch": "amd64"},
+			want:   false,
+		},
+		"empty labels": {
+			labels: map[string]string{},
+			want:   false,
+		},
+		"nil labels": {
+			labels: nil,
+			want:   false,
+		},
+	}
+	for name, tt := range tests {
+		t.Run(name, func(t *testing.T) {
+			if got := IsPreemptible(tt.labels); got != tt.want {
+				t.Errorf("IsPreemptible() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestPrivateIPCheck(t *testing.T) {
+	tests := map[string]struct {
+		ip   string
+		want bool
+	}{
+		"RFC1918 10.x":        {ip: "10.0.0.1", want: true},
+		"RFC1918 172.16.x":    {ip: "172.16.0.1", want: true},
+		"RFC1918 172.31.x":    {ip: "172.31.255.255", want: true},
+		"RFC1918 192.168.x":   {ip: "192.168.1.1", want: true},
+		"public IPv4":         {ip: "8.8.8.8", want: false},
+		"loopback":            {ip: "127.0.0.1", want: false},
+		"private IPv6 fc00::": {ip: "fc00::1", want: true},
+		"private IPv6 fd00::": {ip: "fd00::1", want: true},
+		"public IPv6":         {ip: "2001:4860:4860::8888", want: false},
+		"invalid IP":          {ip: "not-an-ip", want: false},
+		"empty string":        {ip: "", want: false},
+	}
+	for name, tt := range tests {
+		t.Run(name, func(t *testing.T) {
+			if got := PrivateIPCheck(tt.ip); got != tt.want {
+				t.Errorf("PrivateIPCheck(%q) = %v, want %v", tt.ip, got, tt.want)
+			}
+		})
+	}
+}
+
 func TestGetArchType(t *testing.T) {
 func TestGetArchType(t *testing.T) {
 	type args struct {
 	type args struct {
 		labels map[string]string
 		labels map[string]string

+ 0 - 14
pkg/kubemodel/kubemodel.go

@@ -197,7 +197,6 @@ func (km *KubeModel) computeNodes(kms *kubemodel.KubeModelSet, start, end time.T
 	nodeInfoResultFuture := source.WithGroup(grp, metrics.QueryNodeInfo(start, end))
 	nodeInfoResultFuture := source.WithGroup(grp, metrics.QueryNodeInfo(start, end))
 	nodeUptimeResultFuture := source.WithGroup(grp, metrics.QueryNodeUptime(start, end))
 	nodeUptimeResultFuture := source.WithGroup(grp, metrics.QueryNodeUptime(start, end))
 	nodeLabelsResultFuture := source.WithGroup(grp, metrics.QueryNodeLabels(start, end))
 	nodeLabelsResultFuture := source.WithGroup(grp, metrics.QueryNodeLabels(start, end))
-	nodeIsSpotResultFuture := source.WithGroup(grp, metrics.QueryNodeIsSpot(start, end))
 	nodeResourceCapacitiesFuture := source.WithGroup(grp, metrics.QueryNodeResourceCapacities(start, end))
 	nodeResourceCapacitiesFuture := source.WithGroup(grp, metrics.QueryNodeResourceCapacities(start, end))
 	nodeResourcesAllocatableFuture := source.WithGroup(grp, metrics.QueryNodeResourcesAllocatable(start, end))
 	nodeResourcesAllocatableFuture := source.WithGroup(grp, metrics.QueryNodeResourcesAllocatable(start, end))
 
 
@@ -213,7 +212,6 @@ func (km *KubeModel) computeNodes(kms *kubemodel.KubeModelSet, start, end time.T
 			UID:                  res.UID,
 			UID:                  res.UID,
 			ProviderID:           res.ProviderID,
 			ProviderID:           res.ProviderID,
 			Name:                 res.Node,
 			Name:                 res.Node,
-			InstanceType:         res.InstanceType,
 			ResourceCapacities:   make(kubemodel.ResourceQuantities),
 			ResourceCapacities:   make(kubemodel.ResourceQuantities),
 			ResourcesAllocatable: make(kubemodel.ResourceQuantities),
 			ResourcesAllocatable: make(kubemodel.ResourceQuantities),
 		}
 		}
@@ -253,18 +251,6 @@ func (km *KubeModel) computeNodes(kms *kubemodel.KubeModelSet, start, end time.T
 		node.ResourcesAllocatable.Set(resource, unit, kubemodel.StatAvg, value)
 		node.ResourcesAllocatable.Set(resource, unit, kubemodel.StatAvg, value)
 	}
 	}
 
 
-	nodeIsSpotResult, _ := nodeIsSpotResultFuture.Await()
-	for _, res := range nodeIsSpotResult {
-		node, ok := nodeMap[res.UID]
-		if !ok {
-			log.Warnf("node with UID '%s' has not been initialized to add spot status", res.UID)
-			continue
-		}
-		if len(res.Data) > 0 {
-			node.Preemptible = res.Data[0].Value > 0
-		}
-	}
-
 	nodeLabelsResult, _ := nodeLabelsResultFuture.Await()
 	nodeLabelsResult, _ := nodeLabelsResultFuture.Await()
 	for _, res := range nodeLabelsResult {
 	for _, res := range nodeLabelsResult {
 		node, ok := nodeMap[res.UID]
 		node, ok := nodeMap[res.UID]