Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion pkg/clusteragent/autoscaling/workload/controller_vertical.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,13 @@ func (u *verticalController) sync(ctx context.Context, podAutoscaler *datadoghq.
podsPerDirectOwner[pod.Owners[0].ID] = podsPerDirectOwner[pod.Owners[0].ID] + 1
}

// Get the live pod UIDs, prune pod operations for pods that are no longer in the live set.
livePodUIDs := make(map[string]struct{}, len(pods))
for _, pod := range pods {
livePodUIDs[pod.EntityID.ID] = struct{}{}
}
autoscalerInternal.PrunePodOperations(recommendationID, livePodUIDs)

// Classify each non-terminating pod by resize status so we can set scaled replicas
// (completed pods count) accurately. Pass this slice to syncInternal to avoid
// a duplicate call to getPodResizeStatus.
Expand All @@ -135,6 +142,10 @@ func (u *verticalController) sync(ctx context.Context, podAutoscaler *datadoghq.
if pod.DeletionTimestamp != nil {
continue
}
if autoscalerInternal.HasPendingOperation(pod.EntityID.ID, recommendationID) {
podsByResizeStatus[PodResizeStatusEvicting] = append(podsByResizeStatus[PodResizeStatusEvicting], classifiedPod{pod: pod})
continue
}
status, ltt := getPodResizeStatus(pod, recommendationID)
podsByResizeStatus[status] = append(podsByResizeStatus[status], classifiedPod{pod: pod, lastTransitionTime: ltt})
}
Expand Down Expand Up @@ -276,6 +287,7 @@ func (u *verticalController) syncInternal(
if result == evictor.Evicted {
evictedThisSync++
autoscalerInternal.InPlaceEvictionSuccessInc()
autoscalerInternal.TrackPodOperation(cp.pod.EntityID.ID, recommendationID)
}
if result == evictor.PDBLockedOrThrottle || result == evictor.Skipped {
pdbBlocked = true
Expand Down Expand Up @@ -303,8 +315,14 @@ func (u *verticalController) syncInternal(
eventType = corev1.EventTypeWarning
reason = model.FailedToEvictEventReason
}
inFlight := len(podsByResizeStatus[PodResizeStatusEvicting])
remaining := int(int32(len(toEvict)) - evictedThisSync - failedEvictions)
suffix := fmt.Sprintf("%d remaining", remaining)
if inFlight > 0 {
suffix += fmt.Sprintf(", %d in-flight", inFlight)
}
u.eventRecorder.Eventf(podAutoscaler, eventType, reason,
"In-place resize eviction: %s (%d pods pending)", strings.Join(parts, ", "), len(toEvict))
"In-place resize eviction: %s (%s)", strings.Join(parts, ", "), suffix)
}

// Terminating pods are excluded from podsByResizeStatus, so summing all bucket lengths
Expand All @@ -314,6 +332,7 @@ func (u *verticalController) syncInternal(
totalActive += len(bucket)
}
if len(podsByResizeStatus[PodResizeStatusCompleted]) == totalActive {
autoscalerInternal.ClearPodOperations()
if lastAction := autoscalerInternal.VerticalLastAction(); lastAction != nil &&
lastAction.Type == datadoghqcommon.DatadogPodAutoscalerResizeTriggeredVerticalActionType {
u.eventRecorder.Eventf(podAutoscaler, corev1.EventTypeNormal, model.ResizeSuccessfulEventReason,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ const (
PodResizeStatusError
PodResizeStatusInfeasible
PodResizeStatusDeferred
// PodResizeStatusEvicting marks pods with an accepted eviction pending termination.
PodResizeStatusEvicting
)

// classifiedPod pairs a pod with the LastTransitionTime of the condition that
Expand Down
38 changes: 38 additions & 0 deletions pkg/clusteragent/autoscaling/workload/model/pod_autoscaler.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,10 @@ type PodAutoscalerInternal struct {
// customRecommenderConfiguration holds the configuration for custom recommenders,
// Parsed from annotations on the autoscaler
customRecommenderConfiguration *RecommenderConfiguration

// submittedPodOps maps pod UID to the recommendationID of the last accepted patch or eviction,
// used to suppress redundant API calls during the informer-cache lag window.
submittedPodOps map[string]string
}

// NewPodAutoscalerInternal creates a new PodAutoscalerInternal from a Kubernetes CR
Expand Down Expand Up @@ -901,6 +905,40 @@ func (p *PodAutoscalerInternal) InPlaceResizeCompletedInc() {
p.inPlaceResizeCompletedCount++
}

// TrackPodOperation records that a patch or eviction for podUID was accepted by the API server.
func (p *PodAutoscalerInternal) TrackPodOperation(podUID, recommendationID string) {
if p.submittedPodOps == nil {
p.submittedPodOps = make(map[string]string)
}
p.submittedPodOps[podUID] = recommendationID
}

// HasPendingOperation reports whether podUID has a tracked operation for the current recommendationID.
func (p *PodAutoscalerInternal) HasPendingOperation(podUID, recommendationID string) bool {
if p.submittedPodOps == nil {
return false
}
return p.submittedPodOps[podUID] == recommendationID
}

// ClearPodOperations drops all tracked pod operations; called on resize completion.
func (p *PodAutoscalerInternal) ClearPodOperations() {
p.submittedPodOps = nil
}

// PrunePodOperations removes entries whose stored recommendationID no longer matches
// the current one, and entries whose pod UID have been removed from the livePodUIDs map.
func (p *PodAutoscalerInternal) PrunePodOperations(recommendationID string, livePodUIDs map[string]struct{}) {
for uid, rid := range p.submittedPodOps {
if rid != recommendationID {
delete(p.submittedPodOps, uid)
}
if _, ok := livePodUIDs[uid]; !ok {
delete(p.submittedPodOps, uid)
}
}
}

// CurrentReplicas returns the current number of PODs for the targetRef
func (p *PodAutoscalerInternal) CurrentReplicas() *int32 {
return p.currentReplicas
Expand Down
Loading