Skip to content

Commit 1acc8c2

Browse files
committed
Disable GPU resource processor for nodes using DRA for accelerator attachment
1 parent 20f76e9 commit 1acc8c2

File tree

9 files changed

+91
-25
lines changed

9 files changed

+91
-25
lines changed

cluster-autoscaler/cloudprovider/cloud_provider.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,14 @@ const (
9898

9999
// GpuConfig contains the label, type and the resource name for a GPU.
100100
type GpuConfig struct {
101-
Label string
102-
Type string
103-
ResourceName apiv1.ResourceName
101+
Label string
102+
Type string
103+
ExtendedResourceName apiv1.ResourceName
104+
DraDriverName string
105+
}
106+
107+
func (gpu *GpuConfig) ExposedViaDra() bool {
108+
return gpu.DraDriverName != ""
104109
}
105110

106111
// CloudProvider contains configuration info and functions for interacting with
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package gce
2+
3+
import apiv1 "k8s.io/api/core/v1"
4+
5+
const (
6+
// DraGPUDriver name of the driver used to expose NVIDIA GPU resources
7+
DraGPUDriver = "gpu.nvidia.com"
8+
// DraGPULabel is the label added to nodes with GPU resource exposed via DRA.
9+
DraGPULabel = "cloud.google.com/gke-gpu-dra-driver"
10+
)
11+
12+
// GpuDraDriverEnabled checks whether GPU driver is enabled on the node
13+
func GpuDraDriverEnabled(node *apiv1.Node) bool {
14+
return node.Labels[DraGPULabel] == "true"
15+
}

cluster-autoscaler/cloudprovider/gce/gce_cloud_provider.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,20 @@ func (gce *GceCloudProvider) GetAvailableGPUTypes() map[string]struct{} {
8282
}
8383

8484
// GetNodeGpuConfig returns the label, type and resource name for the GPU added to node. If node doesn't have
85-
// any GPUs, it returns nil.
85+
// any GPUs, it returns nil. If node has GPU attached using DRA - populates the according field in GpuConfig
8686
func (gce *GceCloudProvider) GetNodeGpuConfig(node *apiv1.Node) *cloudprovider.GpuConfig {
87-
return gpu.GetNodeGPUFromCloudProvider(gce, node)
87+
gpuConfig := gpu.GetNodeGPUFromCloudProvider(gce, node)
88+
89+
// If GPU devices are exposed using DRA - extended resource
90+
// won't be present in the node alloctable or capacity
91+
// so we overwrite extended resource name as it won't ever
92+
// be there
93+
if GpuDraDriverEnabled(node) {
94+
gpuConfig.DraDriverName = DraGPUDriver
95+
gpuConfig.ExtendedResourceName = ""
96+
}
97+
98+
return gpuConfig
8899
}
89100

90101
// NodeGroups returns all node groups configured for this cloud provider.

cluster-autoscaler/cloudprovider/kwok/kwok_provider_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,7 @@ func TestGetNodeGpuConfig(t *testing.T) {
486486
l := p.GetNodeGpuConfig(nodeWithGPU)
487487
assert.NotNil(t, l)
488488
assert.Equal(t, "k8s.amazonaws.com/accelerator", l.Label)
489-
assert.Equal(t, gpu.ResourceNvidiaGPU, string(l.ResourceName))
489+
assert.Equal(t, gpu.ResourceNvidiaGPU, string(l.ExtendedResourceName))
490490
assert.Equal(t, "nvidia-tesla-k80", l.Type)
491491

492492
nodeWithNoAllocatableGPU := &apiv1.Node{
@@ -499,7 +499,7 @@ func TestGetNodeGpuConfig(t *testing.T) {
499499
l = p.GetNodeGpuConfig(nodeWithNoAllocatableGPU)
500500
assert.NotNil(t, l)
501501
assert.Equal(t, "k8s.amazonaws.com/accelerator", l.Label)
502-
assert.Equal(t, gpu.ResourceNvidiaGPU, string(l.ResourceName))
502+
assert.Equal(t, gpu.ResourceNvidiaGPU, string(l.ExtendedResourceName))
503503
assert.Equal(t, "nvidia-tesla-k80", l.Type)
504504

505505
nodeWithNoGPULabel := &apiv1.Node{
@@ -515,7 +515,7 @@ func TestGetNodeGpuConfig(t *testing.T) {
515515
l = p.GetNodeGpuConfig(nodeWithNoGPULabel)
516516
assert.NotNil(t, l)
517517
assert.Equal(t, "k8s.amazonaws.com/accelerator", l.Label)
518-
assert.Equal(t, gpu.ResourceNvidiaGPU, string(l.ResourceName))
518+
assert.Equal(t, gpu.ResourceNvidiaGPU, string(l.ExtendedResourceName))
519519
assert.Equal(t, "", l.Type)
520520

521521
}

cluster-autoscaler/processors/customresources/gpu_processor.go

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,15 @@ func (p *GpuCustomResourcesProcessor) FilterOutNodesWithUnreadyResources(context
4242
newReadyNodes := make([]*apiv1.Node, 0)
4343
nodesWithUnreadyGpu := make(map[string]*apiv1.Node)
4444
for _, node := range readyNodes {
45+
if gpuExposedViaDra(context, node) {
46+
newReadyNodes = append(newReadyNodes, node)
47+
continue
48+
}
49+
4550
_, hasGpuLabel := node.Labels[context.CloudProvider.GPULabel()]
4651
gpuAllocatable, hasGpuAllocatable := node.Status.Allocatable[gpu.ResourceNvidiaGPU]
4752
directXAllocatable, hasDirectXAllocatable := node.Status.Allocatable[gpu.ResourceDirectX]
48-
// We expect node to have GPU based on label, but it doesn't show up
49-
// on node object. Assume the node is still not fully started (installing
50-
// GPU drivers).
51-
if hasGpuLabel && ((!hasGpuAllocatable || gpuAllocatable.IsZero()) && (!hasDirectXAllocatable || directXAllocatable.IsZero())) {
53+
if hasGpuLabel && ((!hasGpuAllocatable || gpuAllocatable.IsZero()) && (!hasDirectXAllocatable || directXAllocatable.IsZero())) && !gpuExposedViaDra(context, node) {
5254
klog.V(3).Infof("Overriding status of node %v, which seems to have unready GPU",
5355
node.Name)
5456
nodesWithUnreadyGpu[node.Name] = kubernetes.GetUnreadyNodeCopy(node, kubernetes.ResourceUnready)
@@ -70,13 +72,17 @@ func (p *GpuCustomResourcesProcessor) FilterOutNodesWithUnreadyResources(context
7072
// GetNodeResourceTargets returns mapping of resource names to their targets.
7173
// This includes resources which are not yet ready to use and visible in kubernetes.
7274
func (p *GpuCustomResourcesProcessor) GetNodeResourceTargets(context *context.AutoscalingContext, node *apiv1.Node, nodeGroup cloudprovider.NodeGroup) ([]CustomResourceTarget, errors.AutoscalerError) {
73-
gpuTarget, err := p.GetNodeGpuTarget(context.CloudProvider.GPULabel(), node, nodeGroup)
75+
if gpuExposedViaDra(context, node) {
76+
return []CustomResourceTarget{}, nil
77+
}
78+
79+
gpuTarget, err := p.getNodeGpuTarget(context.CloudProvider.GPULabel(), node, nodeGroup)
7480
return []CustomResourceTarget{gpuTarget}, err
7581
}
7682

77-
// GetNodeGpuTarget returns the gpu target of a given node. This includes gpus
83+
// getNodeGpuTarget returns the gpu target of a given node. This includes gpus
7884
// that are not ready to use and visible in kubernetes.
79-
func (p *GpuCustomResourcesProcessor) GetNodeGpuTarget(GPULabel string, node *apiv1.Node, nodeGroup cloudprovider.NodeGroup) (CustomResourceTarget, errors.AutoscalerError) {
85+
func (p *GpuCustomResourcesProcessor) getNodeGpuTarget(GPULabel string, node *apiv1.Node, nodeGroup cloudprovider.NodeGroup) (CustomResourceTarget, errors.AutoscalerError) {
8086
gpuLabel, found := node.Labels[GPULabel]
8187
if !found {
8288
return CustomResourceTarget{}, nil
@@ -121,3 +127,15 @@ func (p *GpuCustomResourcesProcessor) GetNodeGpuTarget(GPULabel string, node *ap
121127
// CleanUp cleans up processor's internal structures.
122128
func (p *GpuCustomResourcesProcessor) CleanUp() {
123129
}
130+
131+
func gpuExposedViaDra(ctx *context.AutoscalingContext, node *apiv1.Node) bool {
132+
gpuConfig := ctx.CloudProvider.GetNodeGpuConfig(node)
133+
if gpuConfig == nil {
134+
return false
135+
}
136+
137+
// Devices attached through DRA are not using node allocatable
138+
// to confirm their attachment, assume that node is ready
139+
// and will be checked in the separate processor
140+
return gpuConfig.ExposedViaDra()
141+
}

cluster-autoscaler/processors/customresources/gpu_processor_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
apiv1 "k8s.io/api/core/v1"
2626
"k8s.io/apimachinery/pkg/api/resource"
2727
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28+
"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/gce"
2829
testprovider "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/test"
2930
"k8s.io/autoscaler/cluster-autoscaler/context"
3031
"k8s.io/autoscaler/cluster-autoscaler/utils/gpu"
@@ -152,13 +153,28 @@ func TestFilterOutNodesWithUnreadyResources(t *testing.T) {
152153
}
153154
expectedReadiness[nodeNoGpuUnready.Name] = false
154155

156+
nodeGPUReadyDra := &apiv1.Node{
157+
ObjectMeta: metav1.ObjectMeta{
158+
Name: "nodeGPUViaDra",
159+
Labels: map[string]string{
160+
gce.DraGPULabel: "true",
161+
},
162+
CreationTimestamp: metav1.NewTime(start),
163+
},
164+
Status: apiv1.NodeStatus{
165+
Conditions: []apiv1.NodeCondition{readyCondition},
166+
},
167+
}
168+
expectedReadiness[nodeGPUReadyDra.Name] = true
169+
155170
initialReadyNodes := []*apiv1.Node{
156171
nodeGpuReady,
157172
nodeGpuUnready,
158173
nodeGpuUnready2,
159174
nodeDirectXReady,
160175
nodeDirectXUnready,
161176
nodeNoGpuReady,
177+
nodeGPUReadyDra,
162178
}
163179
initialAllNodes := []*apiv1.Node{
164180
nodeGpuReady,
@@ -168,6 +184,7 @@ func TestFilterOutNodesWithUnreadyResources(t *testing.T) {
168184
nodeDirectXUnready,
169185
nodeNoGpuReady,
170186
nodeNoGpuUnready,
187+
nodeGPUReadyDra,
171188
}
172189

173190
processor := GpuCustomResourcesProcessor{}

cluster-autoscaler/simulator/utilization/info.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,14 @@ type Info struct {
4949
// returns the individual cpu, memory and gpu utilization.
5050
func Calculate(nodeInfo *framework.NodeInfo, skipDaemonSetPods, skipMirrorPods, draEnabled bool, gpuConfig *cloudprovider.GpuConfig, currentTime time.Time) (utilInfo Info, err error) {
5151
if gpuConfig != nil {
52-
gpuUtil, err := CalculateUtilizationOfResource(nodeInfo, gpuConfig.ResourceName, skipDaemonSetPods, skipMirrorPods, currentTime)
52+
gpuUtil, err := CalculateUtilizationOfResource(nodeInfo, gpuConfig.ExtendedResourceName, skipDaemonSetPods, skipMirrorPods, currentTime)
5353
if err != nil {
54-
klog.V(3).Infof("node %s has unready GPU resource: %s", nodeInfo.Node().Name, gpuConfig.ResourceName)
54+
klog.V(3).Infof("node %s has unready GPU resource: %s", nodeInfo.Node().Name, gpuConfig.ExtendedResourceName)
5555
// Return 0 if GPU is unready. This will guarantee we can still scale down a node with unready GPU.
56-
return Info{GpuUtil: 0, ResourceName: gpuConfig.ResourceName, Utilization: 0}, nil
56+
return Info{GpuUtil: 0, ResourceName: gpuConfig.ExtendedResourceName, Utilization: 0}, nil
5757
}
5858
// Skips cpu and memory utilization calculation for node with GPU.
59-
return Info{GpuUtil: gpuUtil, ResourceName: gpuConfig.ResourceName, Utilization: gpuUtil}, err
59+
return Info{GpuUtil: gpuUtil, ResourceName: gpuConfig.ExtendedResourceName, Utilization: gpuUtil}, err
6060
}
6161

6262
if draEnabled && len(nodeInfo.LocalResourceSlices) > 0 {

cluster-autoscaler/simulator/utilization/info_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ func TestCalculateWithDynamicResources(t *testing.T) {
343343
nodeInfo: nodeInfoGpuAndDra,
344344
gpuConfig: gpuConfig,
345345
draEnabled: true,
346-
wantUtilInfo: Info{Utilization: 0, ResourceName: gpuConfig.ResourceName},
346+
wantUtilInfo: Info{Utilization: 0, ResourceName: gpuConfig.ExtendedResourceName},
347347
},
348348
{
349349
testName: "DRA slices and claims present, DRA enabled, error while calculating DRA util -> error returned",
@@ -371,9 +371,9 @@ func getGpuConfigFromNode(node *apiv1.Node) *cloudprovider.GpuConfig {
371371
gpuAllocatable, hasGpuAllocatable := node.Status.Allocatable[gpu.ResourceNvidiaGPU]
372372
if hasGpuLabel || (hasGpuAllocatable && !gpuAllocatable.IsZero()) {
373373
return &cloudprovider.GpuConfig{
374-
Label: gpuLabel,
375-
Type: gpuType,
376-
ResourceName: gpu.ResourceNvidiaGPU,
374+
Label: gpuLabel,
375+
Type: gpuType,
376+
ExtendedResourceName: gpu.ResourceNvidiaGPU,
377377
}
378378
}
379379
return nil

cluster-autoscaler/utils/gpu/gpu.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ func GetGpuInfoForMetrics(gpuConfig *cloudprovider.GpuConfig, availableGPUTypes
5656
if gpuConfig == nil {
5757
return "", MetricsNoGPU
5858
}
59-
resourceName := gpuConfig.ResourceName
59+
resourceName := gpuConfig.ExtendedResourceName
6060
capacity, capacityFound := node.Status.Capacity[resourceName]
6161
// There is no label value, fallback to generic solution
6262
if gpuConfig.Type == "" && capacityFound && !capacity.IsZero() {
@@ -116,7 +116,7 @@ func PodRequestsGpu(pod *apiv1.Pod) bool {
116116
func GetNodeGPUFromCloudProvider(provider cloudprovider.CloudProvider, node *apiv1.Node) *cloudprovider.GpuConfig {
117117
gpuLabel := provider.GPULabel()
118118
if NodeHasGpu(gpuLabel, node) {
119-
return &cloudprovider.GpuConfig{Label: gpuLabel, Type: node.Labels[gpuLabel], ResourceName: ResourceNvidiaGPU}
119+
return &cloudprovider.GpuConfig{Label: gpuLabel, Type: node.Labels[gpuLabel], ExtendedResourceName: ResourceNvidiaGPU}
120120
}
121121
return nil
122122
}

0 commit comments

Comments
 (0)