Skip to content

Commit fe9073b

Browse files
authored
Merge pull request kubernetes#88318 from mborsz/bench
Add BenchmarkSchedulingWaitForFirstConsumerPVs benchmark
2 parents e6c4ac3 + bd8ed0a commit fe9073b

File tree

7 files changed

+198
-16
lines changed

7 files changed

+198
-16
lines changed

test/integration/scheduler_perf/scheduler_bench_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,25 @@ func BenchmarkSchedulingInTreePVs(b *testing.B) {
128128
}
129129
}
130130

131+
// BenchmarkSchedulingWaitForFirstConsumerPVs benchmarks the scheduling rate
132+
// of pods with volumes with VolumeBindingMode set to WaitForFirstConsumer.
133+
func BenchmarkSchedulingWaitForFirstConsumerPVs(b *testing.B) {
134+
tests := []struct{ nodes, existingPods, minPods int }{
135+
{nodes: 500, existingPods: 500, minPods: 1000},
136+
// default 5000 existingPods is a way too much for now
137+
}
138+
basePod := makeBasePod()
139+
testStrategy := testutils.NewCreatePodWithPersistentVolumeWithFirstConsumerStrategy(gceVolumeFactory, basePod)
140+
nodeStrategy := testutils.NewLabelNodePrepareStrategy(v1.LabelZoneFailureDomain, "zone1")
141+
for _, test := range tests {
142+
name := fmt.Sprintf("%vNodes/%vPods", test.nodes, test.existingPods)
143+
b.Run(name, func(b *testing.B) {
144+
nodeStrategies := []testutils.CountToStrategy{{Count: test.nodes, Strategy: nodeStrategy}}
145+
benchmarkScheduling(test.existingPods, test.minPods, nodeStrategies, testStrategy, b)
146+
})
147+
}
148+
}
149+
131150
// BenchmarkSchedulingMigratedInTreePVs benchmarks the scheduling rate of pods with
132151
// in-tree volumes (used via PV/PVC) that are migrated to CSI. CSINode instances exist
133152
// for all nodes and have proper annotation that AWS is migrated.
@@ -557,6 +576,42 @@ func awsVolumeFactory(id int) *v1.PersistentVolume {
557576
}
558577
}
559578

579+
func gceVolumeFactory(id int) *v1.PersistentVolume {
580+
return &v1.PersistentVolume{
581+
ObjectMeta: metav1.ObjectMeta{
582+
Name: fmt.Sprintf("vol-%d", id),
583+
},
584+
Spec: v1.PersistentVolumeSpec{
585+
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany},
586+
Capacity: v1.ResourceList{
587+
v1.ResourceName(v1.ResourceStorage): resource.MustParse("1Gi"),
588+
},
589+
PersistentVolumeReclaimPolicy: v1.PersistentVolumeReclaimRetain,
590+
PersistentVolumeSource: v1.PersistentVolumeSource{
591+
GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{
592+
FSType: "ext4",
593+
PDName: fmt.Sprintf("vol-%d-pvc", id),
594+
},
595+
},
596+
NodeAffinity: &v1.VolumeNodeAffinity{
597+
Required: &v1.NodeSelector{
598+
NodeSelectorTerms: []v1.NodeSelectorTerm{
599+
{
600+
MatchExpressions: []v1.NodeSelectorRequirement{
601+
{
602+
Key: v1.LabelZoneFailureDomain,
603+
Operator: v1.NodeSelectorOpIn,
604+
Values: []string{"zone1"},
605+
},
606+
},
607+
},
608+
},
609+
},
610+
},
611+
},
612+
}
613+
}
614+
560615
func csiVolumeFactory(id int) *v1.PersistentVolume {
561616
return &v1.PersistentVolume{
562617
ObjectMeta: metav1.ObjectMeta{

test/integration/scheduler_perf/util.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,10 @@ func mustSetupScheduler() (util.ShutdownFunc, coreinformers.PodInformer, clients
6161
Burst: 5000,
6262
})
6363
_, podInformer, schedulerShutdown := util.StartScheduler(clientSet)
64+
fakePVControllerShutdown := util.StartFakePVController(clientSet)
6465

6566
shutdownFunc := func() {
67+
fakePVControllerShutdown()
6668
schedulerShutdown()
6769
apiShutdown()
6870
}

test/integration/util/BUILD

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@ go_library(
1414
importpath = "k8s.io/kubernetes/test/integration/util",
1515
deps = [
1616
"//pkg/api/legacyscheme:go_default_library",
17+
"//pkg/controller/volume/persistentvolume/util:go_default_library",
1718
"//pkg/scheduler:go_default_library",
1819
"//staging/src/k8s.io/api/core/v1:go_default_library",
20+
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
1921
"//staging/src/k8s.io/client-go/informers:go_default_library",
2022
"//staging/src/k8s.io/client-go/informers/core/v1:go_default_library",
2123
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
24+
"//staging/src/k8s.io/client-go/tools/cache:go_default_library",
2225
"//staging/src/k8s.io/client-go/tools/events:go_default_library",
2326
"//staging/src/k8s.io/legacy-cloud-providers/gce:go_default_library",
2427
"//test/integration/framework:go_default_library",

test/integration/util/util.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,15 @@ import (
2222
"net/http/httptest"
2323

2424
v1 "k8s.io/api/core/v1"
25+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2526
"k8s.io/client-go/informers"
2627
coreinformers "k8s.io/client-go/informers/core/v1"
2728
clientset "k8s.io/client-go/kubernetes"
29+
"k8s.io/client-go/tools/cache"
2830
"k8s.io/client-go/tools/events"
2931
"k8s.io/klog"
3032
"k8s.io/kubernetes/pkg/api/legacyscheme"
33+
pvutil "k8s.io/kubernetes/pkg/controller/volume/persistentvolume/util"
3134
"k8s.io/kubernetes/pkg/scheduler"
3235
"k8s.io/kubernetes/test/integration/framework"
3336
)
@@ -87,6 +90,48 @@ func StartScheduler(clientSet clientset.Interface) (*scheduler.Scheduler, corein
8790
return sched, podInformer, shutdownFunc
8891
}
8992

93+
// StartFakePVController is a simplified pv controller logic that sets PVC VolumeName and annotation for each PV binding.
94+
// TODO(mborsz): Use a real PV controller here.
95+
func StartFakePVController(clientSet clientset.Interface) ShutdownFunc {
96+
ctx, cancel := context.WithCancel(context.Background())
97+
98+
informerFactory := informers.NewSharedInformerFactory(clientSet, 0)
99+
pvInformer := informerFactory.Core().V1().PersistentVolumes()
100+
101+
syncPV := func(obj *v1.PersistentVolume) {
102+
if obj.Spec.ClaimRef != nil {
103+
claimRef := obj.Spec.ClaimRef
104+
pvc, err := clientSet.CoreV1().PersistentVolumeClaims(claimRef.Namespace).Get(ctx, claimRef.Name, metav1.GetOptions{})
105+
if err != nil {
106+
klog.Errorf("error while getting %v/%v: %v", claimRef.Namespace, claimRef.Name, err)
107+
return
108+
}
109+
110+
if pvc.Spec.VolumeName == "" {
111+
pvc.Spec.VolumeName = obj.Name
112+
metav1.SetMetaDataAnnotation(&pvc.ObjectMeta, pvutil.AnnBindCompleted, "yes")
113+
_, err := clientSet.CoreV1().PersistentVolumeClaims(claimRef.Namespace).Update(ctx, pvc, metav1.UpdateOptions{})
114+
if err != nil {
115+
klog.Errorf("error while getting %v/%v: %v", claimRef.Namespace, claimRef.Name, err)
116+
return
117+
}
118+
}
119+
}
120+
}
121+
122+
pvInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
123+
AddFunc: func(obj interface{}) {
124+
syncPV(obj.(*v1.PersistentVolume))
125+
},
126+
UpdateFunc: func(_, obj interface{}) {
127+
syncPV(obj.(*v1.PersistentVolume))
128+
},
129+
})
130+
131+
informerFactory.Start(ctx.Done())
132+
return ShutdownFunc(cancel)
133+
}
134+
90135
// createScheduler create a scheduler with given informer factory and default name.
91136
func createScheduler(
92137
clientSet clientset.Interface,

test/utils/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ go_library(
3939
"//staging/src/k8s.io/api/auditregistration/v1alpha1:go_default_library",
4040
"//staging/src/k8s.io/api/batch/v1:go_default_library",
4141
"//staging/src/k8s.io/api/core/v1:go_default_library",
42+
"//staging/src/k8s.io/api/storage/v1:go_default_library",
4243
"//staging/src/k8s.io/api/storage/v1beta1:go_default_library",
4344
"//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",
4445
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",

test/utils/create_resources.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import (
2525

2626
apps "k8s.io/api/apps/v1"
2727
batch "k8s.io/api/batch/v1"
28+
storage "k8s.io/api/storage/v1"
29+
2830
"k8s.io/api/core/v1"
2931
apierrors "k8s.io/apimachinery/pkg/api/errors"
3032
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -218,6 +220,23 @@ func CreateServiceWithRetries(c clientset.Interface, namespace string, obj *v1.S
218220
return RetryWithExponentialBackOff(createFunc)
219221
}
220222

223+
func CreateStorageClassWithRetries(c clientset.Interface, obj *storage.StorageClass) error {
224+
if obj == nil {
225+
return fmt.Errorf("Object provided to create is empty")
226+
}
227+
createFunc := func() (bool, error) {
228+
_, err := c.StorageV1().StorageClasses().Create(context.TODO(), obj, metav1.CreateOptions{})
229+
if err == nil || apierrors.IsAlreadyExists(err) {
230+
return true, nil
231+
}
232+
if IsRetryableAPIError(err) {
233+
return false, nil
234+
}
235+
return false, fmt.Errorf("Failed to create object with non-retriable error: %v", err)
236+
}
237+
return RetryWithExponentialBackOff(createFunc)
238+
}
239+
221240
func CreateResourceQuotaWithRetries(c clientset.Interface, namespace string, obj *v1.ResourceQuota) error {
222241
if obj == nil {
223242
return fmt.Errorf("Object provided to create is empty")

test/utils/runners.go

Lines changed: 73 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
apps "k8s.io/api/apps/v1"
2929
batch "k8s.io/api/batch/v1"
3030
v1 "k8s.io/api/core/v1"
31+
storage "k8s.io/api/storage/v1"
3132
storagev1beta1 "k8s.io/api/storage/v1beta1"
3233
apiequality "k8s.io/apimachinery/pkg/api/equality"
3334
apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -1349,35 +1350,50 @@ func CreatePod(client clientset.Interface, namespace string, podCount int, podTe
13491350
return createError
13501351
}
13511352

1352-
func CreatePodWithPersistentVolume(client clientset.Interface, namespace string, claimTemplate *v1.PersistentVolumeClaim, factory volumeFactory, podTemplate *v1.Pod, count int) error {
1353+
func CreatePodWithPersistentVolume(client clientset.Interface, namespace string, claimTemplate *v1.PersistentVolumeClaim, factory volumeFactory, podTemplate *v1.Pod, count int, bindVolume bool) error {
13531354
var createError error
13541355
lock := sync.Mutex{}
13551356
createPodFunc := func(i int) {
13561357
pvcName := fmt.Sprintf("pvc-%d", i)
1357-
1358+
// pvc
1359+
pvc := claimTemplate.DeepCopy()
1360+
pvc.Name = pvcName
13581361
// pv
13591362
pv := factory(i)
1360-
// bind to "pvc-$i"
1361-
pv.Spec.ClaimRef = &v1.ObjectReference{
1362-
Kind: "PersistentVolumeClaim",
1363-
Namespace: namespace,
1364-
Name: pvcName,
1365-
APIVersion: "v1",
1363+
// PVs are cluster-wide resources.
1364+
// Prepend a namespace to make the name globally unique.
1365+
pv.Name = fmt.Sprintf("%s-%s", namespace, pv.Name)
1366+
if bindVolume {
1367+
// bind pv to "pvc-$i"
1368+
pv.Spec.ClaimRef = &v1.ObjectReference{
1369+
Kind: "PersistentVolumeClaim",
1370+
Namespace: namespace,
1371+
Name: pvcName,
1372+
APIVersion: "v1",
1373+
}
1374+
pv.Status.Phase = v1.VolumeBound
1375+
1376+
// bind pvc to "pv-$i"
1377+
// pvc.Spec.VolumeName = pv.Name
1378+
pvc.Status.Phase = v1.ClaimBound
1379+
} else {
1380+
pv.Status.Phase = v1.VolumeAvailable
13661381
}
1367-
pv.Status.Phase = v1.VolumeBound
13681382
if err := CreatePersistentVolumeWithRetries(client, pv); err != nil {
13691383
lock.Lock()
13701384
defer lock.Unlock()
13711385
createError = fmt.Errorf("error creating PV: %s", err)
13721386
return
13731387
}
1388+
// We need to update status separately, as creating persistentvolumes resets status to the default one
1389+
// (so with Status.Phase will be equal to PersistentVolumePhase).
1390+
if _, err := client.CoreV1().PersistentVolumes().UpdateStatus(context.TODO(), pv, metav1.UpdateOptions{}); err != nil {
1391+
lock.Lock()
1392+
defer lock.Unlock()
1393+
createError = fmt.Errorf("error creating PV: %s", err)
1394+
return
1395+
}
13741396

1375-
// pvc
1376-
pvc := claimTemplate.DeepCopy()
1377-
pvc.Name = pvcName
1378-
// bind to "pv-$i"
1379-
pvc.Spec.VolumeName = pv.Name
1380-
pvc.Status.Phase = v1.ClaimBound
13811397
if err := CreatePersistentVolumeClaimWithRetries(client, namespace, pvc); err != nil {
13821398
lock.Lock()
13831399
defer lock.Unlock()
@@ -1446,9 +1462,50 @@ type volumeFactory func(uniqueID int) *v1.PersistentVolume
14461462

14471463
func NewCreatePodWithPersistentVolumeStrategy(claimTemplate *v1.PersistentVolumeClaim, factory volumeFactory, podTemplate *v1.Pod) TestPodCreateStrategy {
14481464
return func(client clientset.Interface, namespace string, podCount int) error {
1449-
return CreatePodWithPersistentVolume(client, namespace, claimTemplate, factory, podTemplate, podCount)
1465+
return CreatePodWithPersistentVolume(client, namespace, claimTemplate, factory, podTemplate, podCount, true /* bindVolume */)
1466+
}
1467+
}
1468+
1469+
func makeUnboundPersistentVolumeClaim(storageClass string) *v1.PersistentVolumeClaim {
1470+
return &v1.PersistentVolumeClaim{
1471+
Spec: v1.PersistentVolumeClaimSpec{
1472+
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany},
1473+
StorageClassName: &storageClass,
1474+
Resources: v1.ResourceRequirements{
1475+
Requests: v1.ResourceList{
1476+
v1.ResourceName(v1.ResourceStorage): resource.MustParse("1Gi"),
1477+
},
1478+
},
1479+
},
1480+
}
1481+
}
1482+
1483+
func NewCreatePodWithPersistentVolumeWithFirstConsumerStrategy(factory volumeFactory, podTemplate *v1.Pod) TestPodCreateStrategy {
1484+
return func(client clientset.Interface, namespace string, podCount int) error {
1485+
volumeBindingMode := storage.VolumeBindingWaitForFirstConsumer
1486+
storageClass := &storage.StorageClass{
1487+
ObjectMeta: metav1.ObjectMeta{
1488+
Name: "storage-class-1",
1489+
},
1490+
Provisioner: "kubernetes.io/gce-pd",
1491+
VolumeBindingMode: &volumeBindingMode,
1492+
}
1493+
claimTemplate := makeUnboundPersistentVolumeClaim(storageClass.Name)
1494+
1495+
if err := CreateStorageClassWithRetries(client, storageClass); err != nil {
1496+
return fmt.Errorf("failed to create storage class: %v", err)
1497+
}
1498+
1499+
factoryWithStorageClass := func(i int) *v1.PersistentVolume {
1500+
pv := factory(i)
1501+
pv.Spec.StorageClassName = storageClass.Name
1502+
return pv
1503+
}
1504+
1505+
return CreatePodWithPersistentVolume(client, namespace, claimTemplate, factoryWithStorageClass, podTemplate, podCount, false /* bindVolume */)
14501506
}
14511507
}
1508+
14521509
func NewSimpleCreatePodStrategy() TestPodCreateStrategy {
14531510
basePod := &v1.Pod{
14541511
ObjectMeta: metav1.ObjectMeta{

0 commit comments

Comments
 (0)