@@ -136,7 +136,7 @@ func (n *NodeClaim) Add(pod *corev1.Pod, podData *PodData) error {
136
136
return err
137
137
}
138
138
139
- reservedOfferings , offeringsToRelease , err := n .reserveOfferings (remaining , nodeClaimRequirements )
139
+ reservedOfferings , err := n .reserveOfferings (remaining , nodeClaimRequirements )
140
140
if err != nil {
141
141
return err
142
142
}
@@ -148,13 +148,29 @@ func (n *NodeClaim) Add(pod *corev1.Pod, podData *PodData) error {
148
148
n .Requirements = nodeClaimRequirements
149
149
n .topology .Record (pod , n .NodeClaim .Spec .Taints , nodeClaimRequirements , scheduling .AllowUndefinedWellKnownLabels )
150
150
n .hostPortUsage .Add (pod , hostPorts )
151
+ n .releaseReservedOfferings (n .reservedOfferings , reservedOfferings )
151
152
n .reservedOfferings = reservedOfferings
152
- for _ , o := range offeringsToRelease {
153
- n .reservationManager .Release (n .hostname , o )
154
- }
155
153
return nil
156
154
}
157
155
156
+ // releaseReservedOfferings releases all offerings which are present in the current reserved offerings, but are not
157
+ // present in the updated reserved offerings.
158
+ func (n * NodeClaim ) releaseReservedOfferings (current , updated map [string ]cloudprovider.Offerings ) {
159
+ updatedIDs := sets .New [string ]()
160
+ for _ , ofs := range updated {
161
+ for _ , o := range ofs {
162
+ updatedIDs .Insert (o .ReservationID ())
163
+ }
164
+ }
165
+ for _ , ofs := range current {
166
+ for _ , o := range ofs {
167
+ if ! updatedIDs .Has (o .ReservationID ()) {
168
+ n .reservationManager .Release (n .hostname , o )
169
+ }
170
+ }
171
+ }
172
+ }
173
+
158
174
// reserveOfferings handles the reservation of `karpenter.sh/capacity-type: reserved` offerings, returning the reserved
159
175
// offerings in a map from instance type name to offerings. Additionally, a slice of offerings to release is returned.
160
176
// This is based on the previously reserved offerings which are no longer compatible with the nodeclaim. These should
@@ -163,19 +179,17 @@ func (n *NodeClaim) Add(pod *corev1.Pod, podData *PodData) error {
163
179
func (n * NodeClaim ) reserveOfferings (
164
180
instanceTypes []* cloudprovider.InstanceType ,
165
181
nodeClaimRequirements scheduling.Requirements ,
166
- ) (reservedOfferings map [string ]cloudprovider.Offerings , offeringsToRelease [] * cloudprovider. Offering , err error ) {
167
- compatibleReservedInstanceTypes := sets . New [ string ]()
168
- reservedOfferings = map [string ]cloudprovider.Offerings {}
182
+ ) (map [string ]cloudprovider.Offerings , error ) {
183
+ hasCompatibleOffering := false
184
+ reservedOfferings : = map [string ]cloudprovider.Offerings {}
169
185
for _ , it := range instanceTypes {
170
- hasCompatibleOffering := false
171
186
for _ , o := range it .Offerings {
172
187
if o .CapacityType () != v1 .CapacityTypeReserved || ! o .Available {
173
188
continue
174
189
}
175
190
// Track every incompatible reserved offering for release. Since releasing a reservation is a no-op when there is no
176
191
// reservation for the given host, there's no need to check that a reservation actually exists for the offering.
177
192
if ! nodeClaimRequirements .IsCompatible (o .Requirements , scheduling .AllowUndefinedWellKnownLabels ) {
178
- offeringsToRelease = append (offeringsToRelease , o )
179
193
continue
180
194
}
181
195
hasCompatibleOffering = true
@@ -186,35 +200,24 @@ func (n *NodeClaim) reserveOfferings(
186
200
reservedOfferings [it .Name ] = append (reservedOfferings [it .Name ], o )
187
201
}
188
202
}
189
- if hasCompatibleOffering {
190
- compatibleReservedInstanceTypes .Insert (it .Name )
191
- }
192
203
}
193
204
194
205
if n .reservedOfferingMode == ReservedOfferingModeStrict {
195
206
// If an instance type with a compatible reserved offering exists, but we failed to make any reservations, we should
196
- // fail. We include this check due to the pessimistic nature of instance reservation - we have to reserve each potential
197
- // offering for every nodeclaim, since we don't know what we'll launch with. Without this check we would fall back to
198
- // on-demand / spot even when there's sufficient reserved capacity. This check means we may fail to schedule the pod
199
- // during this scheduling simulation, but with the possibility of success on subsequent simulations.
200
- // Note: while this can occur both on initial creation and on
201
- if len (compatibleReservedInstanceTypes ) != 0 && len (reservedOfferings ) == 0 {
202
- return nil , nil , NewReservedOfferingError (fmt .Errorf ("one or more instance types with compatible reserved offerings are available, but could not be reserved" ))
207
+ // fail. This could occur when all of the capacity for compatible instances has been reserved by previously created
208
+ // nodeclaims. Since we reserve offering pessimistically, i.e. we will reserve any offering that the instance could
209
+ // be launched with, we should fall back and attempt to schedule this pod in a subsequent scheduling simulation once
210
+ // reservation capacity is available again.
211
+ if hasCompatibleOffering && len (reservedOfferings ) == 0 {
212
+ return nil , NewReservedOfferingError (fmt .Errorf ("one or more instance types with compatible reserved offerings are available, but could not be reserved" ))
203
213
}
204
214
// If the nodeclaim previously had compatible reserved offerings, but the additional requirements filtered those out,
205
215
// we should fail to add the pod to this nodeclaim.
206
216
if len (n .reservedOfferings ) != 0 && len (reservedOfferings ) == 0 {
207
- return nil , nil , NewReservedOfferingError (fmt .Errorf ("satisfying updated nodeclaim constraints would remove all compatible reserved offering options" ))
208
- }
209
- }
210
- // Ensure we release any offerings for instance types which are no longer compatible with nodeClaimRequirements
211
- for instanceName , offerings := range n .reservedOfferings {
212
- if compatibleReservedInstanceTypes .Has (instanceName ) {
213
- continue
217
+ return nil , NewReservedOfferingError (fmt .Errorf ("satisfying updated nodeclaim constraints would remove all compatible reserved offering options" ))
214
218
}
215
- offeringsToRelease = append (offeringsToRelease , offerings ... )
216
219
}
217
- return reservedOfferings , offeringsToRelease , nil
220
+ return reservedOfferings , nil
218
221
}
219
222
220
223
func (n * NodeClaim ) Destroy () {
0 commit comments