provisioning_test.go•10.2 kB
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package scale_test
import (
"context"
"strconv"
"time"
"github.com/samber/lo"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/labels"
karpv1 "sigs.k8s.io/karpenter/pkg/apis/v1"
"sigs.k8s.io/karpenter/pkg/test"
v1 "github.com/aws/karpenter-provider-aws/pkg/apis/v1"
"github.com/aws/karpenter-provider-aws/test/pkg/debug"
"github.com/aws/karpenter-provider-aws/test/pkg/environment/aws"
. "github.com/onsi/ginkgo/v2"
)
const testGroup = "provisioning"
var _ = Describe("Provisioning", Label(debug.NoWatch), Label(debug.NoEvents), func() {
var nodePool *karpv1.NodePool
var nodeClass *v1.EC2NodeClass
var deployment *appsv1.Deployment
var selector labels.Selector
var dsCount int
BeforeEach(func() {
nodeClass = env.DefaultEC2NodeClass()
nodePool = env.DefaultNodePool(nodeClass)
nodePool.Spec.Limits = nil
nodePool.Spec.Disruption.Budgets = []karpv1.Budget{{
Nodes: "70%",
}}
test.ReplaceRequirements(nodePool, karpv1.NodeSelectorRequirementWithMinValues{
NodeSelectorRequirement: corev1.NodeSelectorRequirement{
Key: v1.LabelInstanceHypervisor,
Operator: corev1.NodeSelectorOpIn,
Values: []string{"nitro"},
},
})
deployment = test.Deployment(test.DeploymentOptions{
PodOptions: test.PodOptions{
ResourceRequirements: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("10m"),
corev1.ResourceMemory: resource.MustParse("50Mi"),
},
},
TerminationGracePeriodSeconds: lo.ToPtr[int64](0),
},
})
selector = labels.SelectorFromSet(deployment.Spec.Selector.MatchLabels)
// Get the DS pod count and use it to calculate the DS pod overhead
dsCount = env.GetDaemonSetCount(nodePool)
})
It("should scale successfully on a node-dense scale-up", Label(debug.NoEvents), func(_ context.Context) {
// Disable Prefix Delegation for the node-dense scale-up to not exhaust the IPs
// This is required because of the number of Warm ENIs that will be created and the number of IPs
// that will be allocated across this large number of nodes, despite the fact that the ENI CIDR space will
// be extremely under-utilized
env.ExpectPrefixDelegationDisabled()
DeferCleanup(func() {
env.ExpectPrefixDelegationEnabled()
})
replicasPerNode := 1
expectedNodeCount := 500
replicas := replicasPerNode * expectedNodeCount
deployment.Spec.Replicas = lo.ToPtr[int32](int32(replicas))
// Hostname anti-affinity to require one pod on each node
deployment.Spec.Template.Spec.Affinity = &corev1.Affinity{
PodAntiAffinity: &corev1.PodAntiAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []corev1.PodAffinityTerm{
{
LabelSelector: deployment.Spec.Selector,
TopologyKey: corev1.LabelHostname,
},
},
},
}
By("waiting for the deployment to deploy all of its pods")
env.ExpectCreated(deployment)
env.EventuallyExpectPendingPodCount(selector, replicas)
env.MeasureProvisioningDurationFor(func() {
By("kicking off provisioning by applying the nodePool and nodeClass")
env.ExpectCreated(nodePool, nodeClass)
env.EventuallyExpectCreatedNodeClaimCount("==", expectedNodeCount)
env.EventuallyExpectCreatedNodeCount("==", expectedNodeCount)
env.EventuallyExpectInitializedNodeCount("==", expectedNodeCount)
env.EventuallyExpectHealthyPodCount(selector, replicas)
}, map[string]string{
aws.TestCategoryDimension: testGroup,
aws.TestNameDimension: "node-dense",
aws.ProvisionedNodeCountDimension: strconv.Itoa(expectedNodeCount),
aws.DeprovisionedNodeCountDimension: strconv.Itoa(0),
aws.PodDensityDimension: strconv.Itoa(replicasPerNode),
})
}, SpecTimeout(time.Minute*30))
It("should scale successfully on a node-dense scale-up with minValues in the NodePool requirement", Label(debug.NoEvents), func(_ context.Context) {
// Disable Prefix Delegation for the node-dense scale-up to not exhaust the IPs
// This is required because of the number of Warm ENIs that will be created and the number of IPs
// that will be allocated across this large number of nodes, despite the fact that the ENI CIDR space will
// be extremely under-utilized
env.ExpectPrefixDelegationDisabled()
DeferCleanup(func() {
env.ExpectPrefixDelegationEnabled()
})
replicasPerNode := 1
expectedNodeCount := 500
replicas := replicasPerNode * expectedNodeCount
deployment.Spec.Replicas = lo.ToPtr[int32](int32(replicas))
// Hostname anti-affinity to require one pod on each node
deployment.Spec.Template.Spec.Affinity = &corev1.Affinity{
PodAntiAffinity: &corev1.PodAntiAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []corev1.PodAffinityTerm{
{
LabelSelector: deployment.Spec.Selector,
TopologyKey: corev1.LabelHostname,
},
},
},
}
test.ReplaceRequirements(nodePool, karpv1.NodeSelectorRequirementWithMinValues{
// minValues is restricted to 30 to have enough instance types to be sent to launch API and not make this test flaky.
NodeSelectorRequirement: corev1.NodeSelectorRequirement{
Key: corev1.LabelInstanceTypeStable,
Operator: corev1.NodeSelectorOpExists,
},
MinValues: lo.ToPtr(30),
})
By("waiting for the deployment to deploy all of its pods")
env.ExpectCreated(deployment)
env.EventuallyExpectPendingPodCount(selector, replicas)
env.MeasureProvisioningDurationFor(func() {
By("kicking off provisioning by applying the nodePool and nodeClass")
env.ExpectCreated(nodePool, nodeClass)
env.EventuallyExpectCreatedNodeClaimCount("==", expectedNodeCount)
env.EventuallyExpectCreatedNodeCount("==", expectedNodeCount)
env.EventuallyExpectInitializedNodeCount("==", expectedNodeCount)
env.EventuallyExpectHealthyPodCount(selector, replicas)
}, map[string]string{
aws.TestCategoryDimension: testGroup,
aws.TestNameDimension: "node-dense-min-val",
aws.ProvisionedNodeCountDimension: strconv.Itoa(expectedNodeCount),
aws.DeprovisionedNodeCountDimension: strconv.Itoa(0),
aws.PodDensityDimension: strconv.Itoa(replicasPerNode),
})
}, SpecTimeout(time.Minute*30))
It("should scale successfully on a pod-dense scale-up", func(_ context.Context) {
replicasPerNode := 110
maxPodDensity := replicasPerNode + dsCount
expectedNodeCount := 60
replicas := replicasPerNode * expectedNodeCount
deployment.Spec.Replicas = lo.ToPtr[int32](int32(replicas))
nodeClass.Spec.Kubelet = &v1.KubeletConfiguration{
MaxPods: lo.ToPtr[int32](int32(maxPodDensity)),
}
test.ReplaceRequirements(nodePool, karpv1.NodeSelectorRequirementWithMinValues{
// With Prefix Delegation enabled, .large instances can have 434 pods.
NodeSelectorRequirement: corev1.NodeSelectorRequirement{
Key: v1.LabelInstanceSize,
Operator: corev1.NodeSelectorOpIn,
Values: []string{"large"},
},
},
)
env.MeasureProvisioningDurationFor(func() {
By("waiting for the deployment to deploy all of its pods")
env.ExpectCreated(deployment)
env.EventuallyExpectPendingPodCount(selector, replicas)
By("kicking off provisioning by applying the nodePool and nodeClass")
env.ExpectCreated(nodePool, nodeClass)
env.EventuallyExpectHealthyPodCount(selector, replicas)
}, map[string]string{
aws.TestCategoryDimension: testGroup,
aws.TestNameDimension: "pod-dense",
aws.ProvisionedNodeCountDimension: strconv.Itoa(expectedNodeCount),
aws.DeprovisionedNodeCountDimension: strconv.Itoa(0),
aws.PodDensityDimension: strconv.Itoa(replicasPerNode),
})
}, SpecTimeout(time.Minute*30))
It("should scale successfully on a pod-dense scale-up with minValues in the NodePool requirement", func(_ context.Context) {
replicasPerNode := 110
maxPodDensity := replicasPerNode + dsCount
expectedNodeCount := 60
replicas := replicasPerNode * expectedNodeCount
deployment.Spec.Replicas = lo.ToPtr[int32](int32(replicas))
nodeClass.Spec.Kubelet = &v1.KubeletConfiguration{
MaxPods: lo.ToPtr[int32](int32(maxPodDensity)),
}
test.ReplaceRequirements(nodePool,
karpv1.NodeSelectorRequirementWithMinValues{
// With Prefix Delegation enabled, .large instances can have 434 pods.
NodeSelectorRequirement: corev1.NodeSelectorRequirement{
Key: v1.LabelInstanceSize,
Operator: corev1.NodeSelectorOpIn,
Values: []string{"large"},
},
},
karpv1.NodeSelectorRequirementWithMinValues{
// minValues is restricted to 30 to have enough instance types to be sent to launch API and not make this test flaky.
NodeSelectorRequirement: corev1.NodeSelectorRequirement{
Key: corev1.LabelInstanceTypeStable,
Operator: corev1.NodeSelectorOpExists,
},
MinValues: lo.ToPtr(30),
},
)
env.MeasureProvisioningDurationFor(func() {
By("waiting for the deployment to deploy all of its pods")
env.ExpectCreated(deployment)
env.EventuallyExpectPendingPodCount(selector, replicas)
By("kicking off provisioning by applying the nodePool and nodeClass")
env.ExpectCreated(nodePool, nodeClass)
env.EventuallyExpectHealthyPodCount(selector, replicas)
}, map[string]string{
aws.TestCategoryDimension: testGroup,
aws.TestNameDimension: "pod-dense-min-val",
aws.ProvisionedNodeCountDimension: strconv.Itoa(expectedNodeCount),
aws.DeprovisionedNodeCountDimension: strconv.Itoa(0),
aws.PodDensityDimension: strconv.Itoa(replicasPerNode),
})
}, SpecTimeout(time.Minute*30))
})