@@ -18,9 +18,11 @@ package instance
1818
1919import (
2020 "context"
21+ "crypto/rand"
2122 "errors"
2223 "fmt"
2324 "math"
25+ "math/big"
2426 "net/http"
2527 "strings"
2628 "time"
@@ -31,6 +33,7 @@ import (
3133 "github.com/patrickmn/go-cache"
3234 "github.com/samber/lo"
3335 "go.uber.org/multierr"
36+ "golang.org/x/time/rate"
3437 corev1 "k8s.io/api/core/v1"
3538 "sigs.k8s.io/controller-runtime/pkg/log"
3639 karpv1 "sigs.k8s.io/karpenter/pkg/apis/v1"
@@ -69,16 +72,17 @@ type DefaultProvider struct {
6972 imageFamilyResolver imagefamily.Resolver
7073 vSwitchProvider vswitch.Provider
7174 ackProvider ack.Provider
75+ createLimiter * rate.Limiter
7276}
7377
74- func NewDefaultProvider (region string , ecsClient * ecsclient.Client ,
78+ func NewDefaultProvider (ctx context. Context , region string , ecsClient * ecsclient.Client ,
7579 imageFamilyResolver imagefamily.Resolver , vSwitchProvider vswitch.Provider ,
7680 ackProvider ack.Provider ) * DefaultProvider {
7781 p := & DefaultProvider {
78- ecsClient : ecsClient ,
79- region : region ,
80- instanceCache : cache .New (instanceCacheExpiration , instanceCacheExpiration ),
81-
82+ ecsClient : ecsClient ,
83+ region : region ,
84+ instanceCache : cache .New (instanceCacheExpiration , instanceCacheExpiration ),
85+ createLimiter : rate . NewLimiter ( rate . Limit ( 1 ), options . FromContext ( ctx ). APGCreationQPS ),
8286 imageFamilyResolver : imageFamilyResolver ,
8387 vSwitchProvider : vSwitchProvider ,
8488 ackProvider : ackProvider ,
@@ -90,6 +94,11 @@ func NewDefaultProvider(region string, ecsClient *ecsclient.Client,
9094func (p * DefaultProvider ) Create (ctx context.Context , nodeClass * v1alpha1.ECSNodeClass , nodeClaim * karpv1.NodeClaim ,
9195 instanceTypes []* cloudprovider.InstanceType ,
9296) (* Instance , error ) {
97+ // Wait for rate limiter
98+ if err := p .createLimiter .Wait (ctx ); err != nil {
99+ log .FromContext (ctx ).Error (err , "rate limit exceeded" )
100+ return nil , fmt .Errorf ("rate limit exceeded: %w" , err )
101+ }
93102 schedulingRequirements := scheduling .NewNodeSelectorRequirementsWithMinValues (nodeClaim .Spec .Requirements ... )
94103 // Only filter the instances if there are no minValues in the requirement.
95104 if ! schedulingRequirements .HasMinValues () {
@@ -107,7 +116,6 @@ func (p *DefaultProvider) Create(ctx context.Context, nodeClass *v1alpha1.ECSNod
107116
108117 return NewInstanceFromProvisioningGroup (launchInstance , createAutoProvisioningGroupRequest , p .region ), nil
109118}
110-
111119func (p * DefaultProvider ) Get (ctx context.Context , id string ) (* Instance , error ) {
112120 if instance , ok := p .instanceCache .Get (id ); ok {
113121 return instance .(* Instance ), nil
@@ -370,6 +378,7 @@ func (p *DefaultProvider) launchInstance(ctx context.Context, nodeClass *v1alpha
370378 }
371379
372380 runtime := & util.RuntimeOptions {}
381+
373382 resp , err := p .ecsClient .CreateAutoProvisioningGroupWithOptions (createAutoProvisioningGroupRequest , runtime )
374383 if err != nil {
375384 return nil , nil , fmt .Errorf ("creating auto provisioning group, %w" , err )
@@ -470,7 +479,7 @@ func (p *DefaultProvider) getProvisioningGroup(ctx context.Context, nodeClass *v
470479 break
471480 }
472481
473- vSwitchID := p .getVSwitchID (instanceType , zonalVSwitchs , requirements , capacityType )
482+ vSwitchID := p .getVSwitchID (instanceType , zonalVSwitchs , requirements , capacityType , nodeClass . Spec . VSwitchSelectionPolicy )
474483 if vSwitchID == "" {
475484 return nil , errors .New ("vSwitchID not found" )
476485 }
@@ -570,23 +579,29 @@ func (p *DefaultProvider) checkODFallback(nodeClaim *karpv1.NodeClaim, instanceT
570579}
571580
572581func (p * DefaultProvider ) getVSwitchID (instanceType * cloudprovider.InstanceType ,
573- zonalVSwitchs map [string ]* vswitch.VSwitch , reqs scheduling.Requirements , capacityType string ) string {
582+ zonalVSwitchs map [string ]* vswitch.VSwitch , reqs scheduling.Requirements , capacityType string , vSwitchSelectionPolicy string ) string {
574583 cheapestVSwitchID := ""
575584 cheapestPrice := math .MaxFloat64
576585
586+ if capacityType == karpv1 .CapacityTypeOnDemand || vSwitchSelectionPolicy == v1alpha1 .VSwitchSelectionPolicyBalanced {
587+ // For on-demand, randomly select a zone's vswitch
588+ zoneIDs := lo .Keys (zonalVSwitchs )
589+ if len (zoneIDs ) > 0 {
590+ randomIndex , _ := rand .Int (rand .Reader , big .NewInt (int64 (len (zoneIDs ))))
591+ return zonalVSwitchs [zoneIDs [randomIndex .Int64 ()]].ID
592+ }
593+ }
594+
577595 // For different AZ, the spot price may differ. So we need to get the cheapest vSwitch in the zone
578596 for i := range instanceType .Offerings {
579597 if reqs .Compatible (instanceType .Offerings [i ].Requirements , scheduling .AllowUndefinedWellKnownLabels ) != nil {
580598 continue
581599 }
600+
582601 vswitch , ok := zonalVSwitchs [instanceType .Offerings [i ].Requirements .Get (corev1 .LabelTopologyZone ).Any ()]
583602 if ! ok {
584603 continue
585604 }
586- if capacityType == karpv1 .CapacityTypeOnDemand {
587- return vswitch .ID
588- }
589-
590605 if instanceType .Offerings [i ].Price < cheapestPrice {
591606 cheapestVSwitchID = vswitch .ID
592607 cheapestPrice = instanceType .Offerings [i ].Price
0 commit comments