Skip to content

Commit 0c3ebb0

Browse files
Merge pull request #6313 from kiryl-filatau:feature/kubernetes-scale-to-1-gpu
PiperOrigin-RevId: 859584009
2 parents 99a91b4 + 5597d07 commit 0c3ebb0

File tree

5 files changed

+116
-13
lines changed

5 files changed

+116
-13
lines changed

perfkitbenchmarker/container_service/kubernetes_cluster.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ def _ModifyPodSpecPlacementYaml(
189189
del name
190190
node_selectors = self.GetNodeSelectors(machine_type)
191191
if node_selectors:
192-
pod_spec_yaml['nodeSelector'].update(node_selectors)
192+
pod_spec_yaml.setdefault('nodeSelector', {}).update(node_selectors)
193193

194194
@property
195195
def _ingress_manifest_path(self) -> str:
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
apiVersion: karpenter.sh/v1
2+
kind: NodePool
3+
metadata:
4+
name: {{ gpu_nodepool_name | default('gpu') }}
5+
spec:
6+
disruption:
7+
consolidateAfter: {{ gpu_consolidate_after | default('1m') }}
8+
consolidationPolicy: {{ gpu_consolidation_policy | default('WhenEmptyOrUnderutilized') }}
9+
limits:
10+
cpu: {{ gpu_nodepool_cpu_limit | default(1000) }}
11+
template:
12+
metadata:
13+
labels:
14+
pkb_nodepool: {{ gpu_nodepool_label | default('gpu') }}
15+
spec:
16+
nodeClassRef:
17+
group: karpenter.k8s.aws
18+
kind: EC2NodeClass
19+
name: {{ karpenter_ec2nodeclass_name | default('default') }}
20+
requirements:
21+
- key: kubernetes.io/arch
22+
operator: In
23+
values: {{ gpu_arch | default(['amd64']) }}
24+
- key: kubernetes.io/os
25+
operator: In
26+
values: {{ gpu_os | default(['linux']) }}
27+
- key: karpenter.sh/capacity-type
28+
operator: In
29+
values: {{ gpu_capacity_types | default(['on-demand']) }}
30+
- key: karpenter.k8s.aws/instance-category
31+
operator: In
32+
values: {{ gpu_instance_categories | default(['g']) }}
33+
- key: karpenter.k8s.aws/instance-family
34+
operator: In
35+
values: {{ gpu_instance_families | default(['g6','g6e']) }}
36+
taints:
37+
- key: {{ gpu_taint_key | default('nvidia.com/gpu') }}
38+
effect: NoSchedule

perfkitbenchmarker/data/container/kubernetes_scale/kubernetes_scale.yaml.j2

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ spec:
2020
command: {{ Command }}
2121
{%- endif %}
2222
resources:
23+
requests:
24+
cpu: {{ CpuRequest }}
25+
memory: {{ MemoryRequest }}
26+
ephemeral-storage: {{ EphemeralStorageRequest }}
27+
{%- if NvidiaGpuRequest %}
28+
nvidia.com/gpu: {{ NvidiaGpuRequest }}
29+
{%- endif %}
2330
limits:
2431
cpu: {{ CpuRequest }}
2532
memory: {{ MemoryRequest }}
@@ -53,3 +60,8 @@ spec:
5360
operator: "Exists"
5461
effect: "NoExecute"
5562
tolerationSeconds: {{ PodTimeout }}
63+
{%- if GpuTaintKey %}
64+
- key: {{ GpuTaintKey }}
65+
operator: Exists
66+
effect: NoSchedule
67+
{%- endif %}

perfkitbenchmarker/linux_benchmarks/kubernetes_scale_benchmark.py

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,16 +89,48 @@ def GetConfig(user_config):
8989
return config
9090

9191

92+
def _IsEksKarpenterAwsGpu(cluster: container_service.KubernetesCluster) -> bool:
93+
return bool(
94+
virtual_machine.GPU_COUNT.value
95+
and FLAGS.cloud.lower() == 'aws'
96+
and getattr(cluster, 'CLUSTER_TYPE', None) == 'Karpenter'
97+
)
98+
99+
100+
def _EnsureEksKarpenterGpuNodepool(
101+
cluster: container_service.KubernetesCluster,
102+
) -> None:
103+
"""Ensures a GPU NodePool exists for EKS Karpenter before applying workloads."""
104+
if not _IsEksKarpenterAwsGpu(cluster):
105+
return
106+
cluster.ApplyManifest(
107+
'container/kubernetes_scale/aws-gpu-nodepool.yaml.j2',
108+
gpu_nodepool_name='gpu',
109+
gpu_nodepool_label='gpu',
110+
karpenter_ec2nodeclass_name='default',
111+
gpu_instance_categories=['g'],
112+
gpu_instance_families=['g6', 'g6e'],
113+
gpu_capacity_types=['on-demand'],
114+
gpu_arch=['amd64'],
115+
gpu_os=['linux'],
116+
gpu_taint_key='nvidia.com/gpu',
117+
gpu_consolidate_after='1m',
118+
gpu_consolidation_policy='WhenEmptyOrUnderutilized',
119+
gpu_nodepool_cpu_limit=1000,
120+
)
121+
122+
92123
def Prepare(bm_spec: benchmark_spec.BenchmarkSpec):
93124
"""Sets additional spec attributes."""
94125
bm_spec.always_call_cleanup = True
126+
assert bm_spec.container_cluster
127+
_EnsureEksKarpenterGpuNodepool(bm_spec.container_cluster)
95128

96129

97130
def _GetRolloutCreationTime(rollout_name: str) -> int:
98131
"""Returns the time when the rollout was created."""
99132
out, _, _ = container_service.RunRetryableKubectlCommand([
100-
'rollout',
101-
'history',
133+
'get',
102134
rollout_name,
103135
'-o',
104136
'jsonpath={.metadata.creationTimestamp}',
@@ -122,6 +154,7 @@ def Run(bm_spec: benchmark_spec.BenchmarkSpec) -> list[sample.Sample]:
122154
assert bm_spec.container_cluster
123155
cluster = bm_spec.container_cluster
124156
assert isinstance(cluster, container_service.KubernetesCluster)
157+
cluster: container_service.KubernetesCluster = cluster
125158

126159
# Warm up the cluster by creating a single pod. This compensates for
127160
# differences between Standard & Autopilot, where Standard already has 1 node
@@ -180,8 +213,10 @@ def ScaleUpPods(
180213
max_wait_time = _GetScaleTimeout()
181214
resource_timeout = max_wait_time + 60 * 5 # 5 minutes after waiting to avoid
182215
# pod delete events from polluting data collection.
183-
yaml_docs = cluster.ConvertManifestToYamlDicts(
184-
MANIFEST_TEMPLATE,
216+
217+
is_eks_karpenter_aws_gpu = _IsEksKarpenterAwsGpu(cluster)
218+
219+
manifest_kwargs = dict(
185220
Name='kubernetes-scaleup',
186221
Replicas=num_new_pods,
187222
CpuRequest=CPUS_PER_POD.value,
@@ -192,12 +227,26 @@ def ScaleUpPods(
192227
EphemeralStorageRequest='10Mi',
193228
RolloutTimeout=max_wait_time,
194229
PodTimeout=resource_timeout,
230+
Cloud=FLAGS.cloud.lower(),
231+
GpuTaintKey=None,
232+
)
233+
234+
# GpuTaintKey is still needed for tolerations in the yaml template
235+
if is_eks_karpenter_aws_gpu:
236+
manifest_kwargs['GpuTaintKey'] = 'nvidia.com/gpu'
237+
238+
yaml_docs = cluster.ConvertManifestToYamlDicts(
239+
MANIFEST_TEMPLATE,
240+
**manifest_kwargs,
195241
)
242+
243+
# Use ModifyPodSpecPlacementYaml to add nodeSelectors via GetNodeSelectors()
196244
cluster.ModifyPodSpecPlacementYaml(
197245
yaml_docs,
198246
'kubernetes-scaleup',
199247
cluster.default_nodepool.machine_type,
200248
)
249+
201250
resource_names = cluster.ApplyYaml(yaml_docs)
202251

203252
assert resource_names
@@ -366,7 +415,7 @@ def GetStatusConditionsForResourceType(
366415
def ConvertToEpochTime(timestamp: str) -> int:
367416
"""Converts a timestamp to epoch time."""
368417
# Example: 2024-11-08T23:44:36Z
369-
return parser.parse(timestamp).timestamp()
418+
return int(parser.parse(timestamp).timestamp())
370419

371420

372421
def ParseStatusChanges(

perfkitbenchmarker/providers/aws/elastic_kubernetes_service.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -313,9 +313,7 @@ def _ingress_manifest_path(self) -> str:
313313
"""The path to the ingress manifest template file."""
314314
return 'container/ingress.yaml.j2'
315315

316-
def _WaitForIngress(
317-
self, name: str, namespace: str, port: int
318-
) -> str:
316+
def _WaitForIngress(self, name: str, namespace: str, port: int) -> str:
319317
"""Waits for an Ingress resource to be deployed to the cluster."""
320318
del port
321319
self.WaitForResource(
@@ -1283,10 +1281,16 @@ def ResizeNodePool(
12831281

12841282
def GetNodeSelectors(self, machine_type: str | None = None) -> dict[str, str]:
12851283
"""Gets the node selectors section of a yaml for the provider."""
1286-
machine_family = util.GetMachineFamily(machine_type)
1287-
if machine_family:
1288-
return {'karpenter.k8s.aws/instance-family': machine_family}
1289-
return {}
1284+
selectors = {}
1285+
# If GPU is requested, use the GPU nodepool
1286+
if virtual_machine.GPU_TYPE.value:
1287+
selectors['karpenter.sh/nodepool'] = 'gpu'
1288+
else:
1289+
# Otherwise, use instance-family selector if machine_type is specified
1290+
machine_family = util.GetMachineFamily(machine_type)
1291+
if machine_family:
1292+
selectors['karpenter.k8s.aws/instance-family'] = machine_family
1293+
return selectors
12901294

12911295
def GetNodePoolNames(self) -> list[str]:
12921296
"""Gets node pool names for the cluster.

0 commit comments

Comments
 (0)