k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/controller/deployment/util/deployment_util.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package util 18 19 import ( 20 "context" 21 "fmt" 22 "math" 23 "sort" 24 "strconv" 25 "strings" 26 "time" 27 28 apps "k8s.io/api/apps/v1" 29 v1 "k8s.io/api/core/v1" 30 apiequality "k8s.io/apimachinery/pkg/api/equality" 31 "k8s.io/apimachinery/pkg/api/meta" 32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 "k8s.io/apimachinery/pkg/labels" 34 "k8s.io/apimachinery/pkg/runtime" 35 "k8s.io/apimachinery/pkg/types" 36 intstrutil "k8s.io/apimachinery/pkg/util/intstr" 37 "k8s.io/apimachinery/pkg/util/wait" 38 appsclient "k8s.io/client-go/kubernetes/typed/apps/v1" 39 appslisters "k8s.io/client-go/listers/apps/v1" 40 "k8s.io/klog/v2" 41 "k8s.io/kubernetes/pkg/controller" 42 labelsutil "k8s.io/kubernetes/pkg/util/labels" 43 "k8s.io/utils/integer" 44 ) 45 46 const ( 47 // RevisionAnnotation is the revision annotation of a deployment's replica sets which records its rollout sequence 48 RevisionAnnotation = "deployment.kubernetes.io/revision" 49 // RevisionHistoryAnnotation maintains the history of all old revisions that a replica set has served for a deployment. 50 RevisionHistoryAnnotation = "deployment.kubernetes.io/revision-history" 51 // DesiredReplicasAnnotation is the desired replicas for a deployment recorded as an annotation 52 // in its replica sets. Helps in separating scaling events from the rollout process and for 53 // determining if the new replica set for a deployment is really saturated. 54 DesiredReplicasAnnotation = "deployment.kubernetes.io/desired-replicas" 55 // MaxReplicasAnnotation is the maximum replicas a deployment can have at a given point, which 56 // is deployment.spec.replicas + maxSurge. Used by the underlying replica sets to estimate their 57 // proportions in case the deployment has surge replicas. 58 MaxReplicasAnnotation = "deployment.kubernetes.io/max-replicas" 59 60 // RollbackRevisionNotFound is not found rollback event reason 61 RollbackRevisionNotFound = "DeploymentRollbackRevisionNotFound" 62 // RollbackTemplateUnchanged is the template unchanged rollback event reason 63 RollbackTemplateUnchanged = "DeploymentRollbackTemplateUnchanged" 64 // RollbackDone is the done rollback event reason 65 RollbackDone = "DeploymentRollback" 66 67 // Reasons for deployment conditions 68 // 69 // Progressing: 70 71 // ReplicaSetUpdatedReason is added in a deployment when one of its replica sets is updated as part 72 // of the rollout process. 73 ReplicaSetUpdatedReason = "ReplicaSetUpdated" 74 // FailedRSCreateReason is added in a deployment when it cannot create a new replica set. 75 FailedRSCreateReason = "ReplicaSetCreateError" 76 // NewReplicaSetReason is added in a deployment when it creates a new replica set. 77 NewReplicaSetReason = "NewReplicaSetCreated" 78 // FoundNewRSReason is added in a deployment when it adopts an existing replica set. 79 FoundNewRSReason = "FoundNewReplicaSet" 80 // NewRSAvailableReason is added in a deployment when its newest replica set is made available 81 // ie. the number of new pods that have passed readiness checks and run for at least minReadySeconds 82 // is at least the minimum available pods that need to run for the deployment. 83 NewRSAvailableReason = "NewReplicaSetAvailable" 84 // TimedOutReason is added in a deployment when its newest replica set fails to show any progress 85 // within the given deadline (progressDeadlineSeconds). 86 TimedOutReason = "ProgressDeadlineExceeded" 87 // PausedDeployReason is added in a deployment when it is paused. Lack of progress shouldn't be 88 // estimated once a deployment is paused. 89 PausedDeployReason = "DeploymentPaused" 90 // ResumedDeployReason is added in a deployment when it is resumed. Useful for not failing accidentally 91 // deployments that paused amidst a rollout and are bounded by a deadline. 92 ResumedDeployReason = "DeploymentResumed" 93 // 94 // Available: 95 96 // MinimumReplicasAvailable is added in a deployment when it has its minimum replicas required available. 97 MinimumReplicasAvailable = "MinimumReplicasAvailable" 98 // MinimumReplicasUnavailable is added in a deployment when it doesn't have the minimum required replicas 99 // available. 100 MinimumReplicasUnavailable = "MinimumReplicasUnavailable" 101 ) 102 103 // NewDeploymentCondition creates a new deployment condition. 104 func NewDeploymentCondition(condType apps.DeploymentConditionType, status v1.ConditionStatus, reason, message string) *apps.DeploymentCondition { 105 return &apps.DeploymentCondition{ 106 Type: condType, 107 Status: status, 108 LastUpdateTime: metav1.Now(), 109 LastTransitionTime: metav1.Now(), 110 Reason: reason, 111 Message: message, 112 } 113 } 114 115 // GetDeploymentCondition returns the condition with the provided type. 116 func GetDeploymentCondition(status apps.DeploymentStatus, condType apps.DeploymentConditionType) *apps.DeploymentCondition { 117 for i := range status.Conditions { 118 c := status.Conditions[i] 119 if c.Type == condType { 120 return &c 121 } 122 } 123 return nil 124 } 125 126 // SetDeploymentCondition updates the deployment to include the provided condition. If the condition that 127 // we are about to add already exists and has the same status and reason then we are not going to update. 128 func SetDeploymentCondition(status *apps.DeploymentStatus, condition apps.DeploymentCondition) { 129 currentCond := GetDeploymentCondition(*status, condition.Type) 130 if currentCond != nil && currentCond.Status == condition.Status && currentCond.Reason == condition.Reason { 131 return 132 } 133 // Do not update lastTransitionTime if the status of the condition doesn't change. 134 if currentCond != nil && currentCond.Status == condition.Status { 135 condition.LastTransitionTime = currentCond.LastTransitionTime 136 } 137 newConditions := filterOutCondition(status.Conditions, condition.Type) 138 status.Conditions = append(newConditions, condition) 139 } 140 141 // RemoveDeploymentCondition removes the deployment condition with the provided type. 142 func RemoveDeploymentCondition(status *apps.DeploymentStatus, condType apps.DeploymentConditionType) { 143 status.Conditions = filterOutCondition(status.Conditions, condType) 144 } 145 146 // filterOutCondition returns a new slice of deployment conditions without conditions with the provided type. 147 func filterOutCondition(conditions []apps.DeploymentCondition, condType apps.DeploymentConditionType) []apps.DeploymentCondition { 148 var newConditions []apps.DeploymentCondition 149 for _, c := range conditions { 150 if c.Type == condType { 151 continue 152 } 153 newConditions = append(newConditions, c) 154 } 155 return newConditions 156 } 157 158 // ReplicaSetToDeploymentCondition converts a replica set condition into a deployment condition. 159 // Useful for promoting replica set failure conditions into deployments. 160 func ReplicaSetToDeploymentCondition(cond apps.ReplicaSetCondition) apps.DeploymentCondition { 161 return apps.DeploymentCondition{ 162 Type: apps.DeploymentConditionType(cond.Type), 163 Status: cond.Status, 164 LastTransitionTime: cond.LastTransitionTime, 165 LastUpdateTime: cond.LastTransitionTime, 166 Reason: cond.Reason, 167 Message: cond.Message, 168 } 169 } 170 171 // SetDeploymentRevision updates the revision for a deployment. 172 func SetDeploymentRevision(deployment *apps.Deployment, revision string) bool { 173 updated := false 174 175 if deployment.Annotations == nil { 176 deployment.Annotations = make(map[string]string) 177 } 178 if deployment.Annotations[RevisionAnnotation] != revision { 179 deployment.Annotations[RevisionAnnotation] = revision 180 updated = true 181 } 182 183 return updated 184 } 185 186 // MaxRevision finds the highest revision in the replica sets 187 func MaxRevision(logger klog.Logger, allRSs []*apps.ReplicaSet) int64 { 188 max := int64(0) 189 for _, rs := range allRSs { 190 if v, err := Revision(rs); err != nil { 191 // Skip the replica sets when it failed to parse their revision information 192 logger.V(4).Info("Couldn't parse revision for replica set, deployment controller will skip it when reconciling revisions", "replicaSet", klog.KObj(rs), "err", err) 193 } else if v > max { 194 max = v 195 } 196 } 197 return max 198 } 199 200 // LastRevision finds the second max revision number in all replica sets (the last revision) 201 func LastRevision(logger klog.Logger, allRSs []*apps.ReplicaSet) int64 { 202 max, secMax := int64(0), int64(0) 203 for _, rs := range allRSs { 204 if v, err := Revision(rs); err != nil { 205 // Skip the replica sets when it failed to parse their revision information 206 logger.V(4).Info("Couldn't parse revision for replica set, deployment controller will skip it when reconciling revisions", "replicaSet", klog.KObj(rs), "err", err) 207 } else if v >= max { 208 secMax = max 209 max = v 210 } else if v > secMax { 211 secMax = v 212 } 213 } 214 return secMax 215 } 216 217 // Revision returns the revision number of the input object. 218 func Revision(obj runtime.Object) (int64, error) { 219 acc, err := meta.Accessor(obj) 220 if err != nil { 221 return 0, err 222 } 223 v, ok := acc.GetAnnotations()[RevisionAnnotation] 224 if !ok { 225 return 0, nil 226 } 227 return strconv.ParseInt(v, 10, 64) 228 } 229 230 // SetNewReplicaSetAnnotations sets new replica set's annotations appropriately by updating its revision and 231 // copying required deployment annotations to it; it returns true if replica set's annotation is changed. 232 func SetNewReplicaSetAnnotations(ctx context.Context, deployment *apps.Deployment, newRS *apps.ReplicaSet, newRevision string, exists bool, revHistoryLimitInChars int) bool { 233 logger := klog.FromContext(ctx) 234 // First, copy deployment's annotations (except for apply and revision annotations) 235 annotationChanged := copyDeploymentAnnotationsToReplicaSet(deployment, newRS) 236 // Then, update replica set's revision annotation 237 if newRS.Annotations == nil { 238 newRS.Annotations = make(map[string]string) 239 } 240 oldRevision, ok := newRS.Annotations[RevisionAnnotation] 241 // The newRS's revision should be the greatest among all RSes. Usually, its revision number is newRevision (the max revision number 242 // of all old RSes + 1). However, it's possible that some of the old RSes are deleted after the newRS revision being updated, and 243 // newRevision becomes smaller than newRS's revision. We should only update newRS revision when it's smaller than newRevision. 244 245 oldRevisionInt, err := strconv.ParseInt(oldRevision, 10, 64) 246 if err != nil { 247 if oldRevision != "" { 248 logger.Info("Updating replica set revision OldRevision not int", "err", err) 249 return false 250 } 251 //If the RS annotation is empty then initialise it to 0 252 oldRevisionInt = 0 253 } 254 newRevisionInt, err := strconv.ParseInt(newRevision, 10, 64) 255 if err != nil { 256 logger.Info("Updating replica set revision NewRevision not int", "err", err) 257 return false 258 } 259 if oldRevisionInt < newRevisionInt { 260 newRS.Annotations[RevisionAnnotation] = newRevision 261 annotationChanged = true 262 logger.V(4).Info("Updating replica set revision", "replicaSet", klog.KObj(newRS), "newRevision", newRevision) 263 } 264 // If a revision annotation already existed and this replica set was updated with a new revision 265 // then that means we are rolling back to this replica set. We need to preserve the old revisions 266 // for historical information. 267 if ok && oldRevisionInt < newRevisionInt { 268 revisionHistoryAnnotation := newRS.Annotations[RevisionHistoryAnnotation] 269 oldRevisions := strings.Split(revisionHistoryAnnotation, ",") 270 if len(oldRevisions[0]) == 0 { 271 newRS.Annotations[RevisionHistoryAnnotation] = oldRevision 272 } else { 273 totalLen := len(revisionHistoryAnnotation) + len(oldRevision) + 1 274 // index for the starting position in oldRevisions 275 start := 0 276 for totalLen > revHistoryLimitInChars && start < len(oldRevisions) { 277 totalLen = totalLen - len(oldRevisions[start]) - 1 278 start++ 279 } 280 if totalLen <= revHistoryLimitInChars { 281 oldRevisions = append(oldRevisions[start:], oldRevision) 282 newRS.Annotations[RevisionHistoryAnnotation] = strings.Join(oldRevisions, ",") 283 } else { 284 logger.Info("Not appending revision due to revision history length limit reached", "revisionHistoryLimit", revHistoryLimitInChars) 285 } 286 } 287 } 288 // If the new replica set is about to be created, we need to add replica annotations to it. 289 if !exists && SetReplicasAnnotations(newRS, *(deployment.Spec.Replicas), *(deployment.Spec.Replicas)+MaxSurge(*deployment)) { 290 annotationChanged = true 291 } 292 return annotationChanged 293 } 294 295 var annotationsToSkip = map[string]bool{ 296 v1.LastAppliedConfigAnnotation: true, 297 RevisionAnnotation: true, 298 RevisionHistoryAnnotation: true, 299 DesiredReplicasAnnotation: true, 300 MaxReplicasAnnotation: true, 301 apps.DeprecatedRollbackTo: true, 302 } 303 304 // skipCopyAnnotation returns true if we should skip copying the annotation with the given annotation key 305 // TODO: How to decide which annotations should / should not be copied? 306 // 307 // See https://github.com/kubernetes/kubernetes/pull/20035#issuecomment-179558615 308 func skipCopyAnnotation(key string) bool { 309 return annotationsToSkip[key] 310 } 311 312 // copyDeploymentAnnotationsToReplicaSet copies deployment's annotations to replica set's annotations, 313 // and returns true if replica set's annotation is changed. 314 // Note that apply and revision annotations are not copied. 315 func copyDeploymentAnnotationsToReplicaSet(deployment *apps.Deployment, rs *apps.ReplicaSet) bool { 316 rsAnnotationsChanged := false 317 if rs.Annotations == nil { 318 rs.Annotations = make(map[string]string) 319 } 320 for k, v := range deployment.Annotations { 321 // newRS revision is updated automatically in getNewReplicaSet, and the deployment's revision number is then updated 322 // by copying its newRS revision number. We should not copy deployment's revision to its newRS, since the update of 323 // deployment revision number may fail (revision becomes stale) and the revision number in newRS is more reliable. 324 if _, exist := rs.Annotations[k]; skipCopyAnnotation(k) || (exist && rs.Annotations[k] == v) { 325 continue 326 } 327 rs.Annotations[k] = v 328 rsAnnotationsChanged = true 329 } 330 return rsAnnotationsChanged 331 } 332 333 // SetDeploymentAnnotationsTo sets deployment's annotations as given RS's annotations. 334 // This action should be done if and only if the deployment is rolling back to this rs. 335 // Note that apply and revision annotations are not changed. 336 func SetDeploymentAnnotationsTo(deployment *apps.Deployment, rollbackToRS *apps.ReplicaSet) { 337 deployment.Annotations = getSkippedAnnotations(deployment.Annotations) 338 for k, v := range rollbackToRS.Annotations { 339 if !skipCopyAnnotation(k) { 340 deployment.Annotations[k] = v 341 } 342 } 343 } 344 345 func getSkippedAnnotations(annotations map[string]string) map[string]string { 346 skippedAnnotations := make(map[string]string) 347 for k, v := range annotations { 348 if skipCopyAnnotation(k) { 349 skippedAnnotations[k] = v 350 } 351 } 352 return skippedAnnotations 353 } 354 355 // FindActiveOrLatest returns the only active or the latest replica set in case there is at most one active 356 // replica set. If there are more active replica sets, then we should proportionally scale them. 357 func FindActiveOrLatest(newRS *apps.ReplicaSet, oldRSs []*apps.ReplicaSet) *apps.ReplicaSet { 358 if newRS == nil && len(oldRSs) == 0 { 359 return nil 360 } 361 362 sort.Sort(sort.Reverse(controller.ReplicaSetsByCreationTimestamp(oldRSs))) 363 allRSs := controller.FilterActiveReplicaSets(append(oldRSs, newRS)) 364 365 switch len(allRSs) { 366 case 0: 367 // If there is no active replica set then we should return the newest. 368 if newRS != nil { 369 return newRS 370 } 371 return oldRSs[0] 372 case 1: 373 return allRSs[0] 374 default: 375 return nil 376 } 377 } 378 379 // GetDesiredReplicasAnnotation returns the number of desired replicas 380 func GetDesiredReplicasAnnotation(logger klog.Logger, rs *apps.ReplicaSet) (int32, bool) { 381 return getIntFromAnnotation(logger, rs, DesiredReplicasAnnotation) 382 } 383 384 func getMaxReplicasAnnotation(logger klog.Logger, rs *apps.ReplicaSet) (int32, bool) { 385 return getIntFromAnnotation(logger, rs, MaxReplicasAnnotation) 386 } 387 388 func getIntFromAnnotation(logger klog.Logger, rs *apps.ReplicaSet, annotationKey string) (int32, bool) { 389 annotationValue, ok := rs.Annotations[annotationKey] 390 if !ok { 391 return int32(0), false 392 } 393 intValue, err := strconv.Atoi(annotationValue) 394 if err != nil { 395 logger.V(2).Info("Could not convert the value with annotation key for the replica set", "annotationValue", annotationValue, "annotationKey", annotationKey, "replicaSet", klog.KObj(rs)) 396 return int32(0), false 397 } 398 return int32(intValue), true 399 } 400 401 // SetReplicasAnnotations sets the desiredReplicas and maxReplicas into the annotations 402 func SetReplicasAnnotations(rs *apps.ReplicaSet, desiredReplicas, maxReplicas int32) bool { 403 updated := false 404 if rs.Annotations == nil { 405 rs.Annotations = make(map[string]string) 406 } 407 desiredString := fmt.Sprintf("%d", desiredReplicas) 408 if hasString := rs.Annotations[DesiredReplicasAnnotation]; hasString != desiredString { 409 rs.Annotations[DesiredReplicasAnnotation] = desiredString 410 updated = true 411 } 412 maxString := fmt.Sprintf("%d", maxReplicas) 413 if hasString := rs.Annotations[MaxReplicasAnnotation]; hasString != maxString { 414 rs.Annotations[MaxReplicasAnnotation] = maxString 415 updated = true 416 } 417 return updated 418 } 419 420 // ReplicasAnnotationsNeedUpdate return true if ReplicasAnnotations need to be updated 421 func ReplicasAnnotationsNeedUpdate(rs *apps.ReplicaSet, desiredReplicas, maxReplicas int32) bool { 422 if rs.Annotations == nil { 423 return true 424 } 425 desiredString := fmt.Sprintf("%d", desiredReplicas) 426 if hasString := rs.Annotations[DesiredReplicasAnnotation]; hasString != desiredString { 427 return true 428 } 429 maxString := fmt.Sprintf("%d", maxReplicas) 430 if hasString := rs.Annotations[MaxReplicasAnnotation]; hasString != maxString { 431 return true 432 } 433 return false 434 } 435 436 // MaxUnavailable returns the maximum unavailable pods a rolling deployment can take. 437 func MaxUnavailable(deployment apps.Deployment) int32 { 438 if !IsRollingUpdate(&deployment) || *(deployment.Spec.Replicas) == 0 { 439 return int32(0) 440 } 441 // Error caught by validation 442 _, maxUnavailable, _ := ResolveFenceposts(deployment.Spec.Strategy.RollingUpdate.MaxSurge, deployment.Spec.Strategy.RollingUpdate.MaxUnavailable, *(deployment.Spec.Replicas)) 443 if maxUnavailable > *deployment.Spec.Replicas { 444 return *deployment.Spec.Replicas 445 } 446 return maxUnavailable 447 } 448 449 // MinAvailable returns the minimum available pods of a given deployment 450 func MinAvailable(deployment *apps.Deployment) int32 { 451 if !IsRollingUpdate(deployment) { 452 return int32(0) 453 } 454 return *(deployment.Spec.Replicas) - MaxUnavailable(*deployment) 455 } 456 457 // MaxSurge returns the maximum surge pods a rolling deployment can take. 458 func MaxSurge(deployment apps.Deployment) int32 { 459 if !IsRollingUpdate(&deployment) { 460 return int32(0) 461 } 462 // Error caught by validation 463 maxSurge, _, _ := ResolveFenceposts(deployment.Spec.Strategy.RollingUpdate.MaxSurge, deployment.Spec.Strategy.RollingUpdate.MaxUnavailable, *(deployment.Spec.Replicas)) 464 return maxSurge 465 } 466 467 // GetProportion will estimate the proportion for the provided replica set using 1. the current size 468 // of the parent deployment, 2. the replica count that needs be added on the replica sets of the 469 // deployment, and 3. the total replicas added in the replica sets of the deployment so far. 470 func GetProportion(logger klog.Logger, rs *apps.ReplicaSet, d apps.Deployment, deploymentReplicasToAdd, deploymentReplicasAdded int32) int32 { 471 if rs == nil || *(rs.Spec.Replicas) == 0 || deploymentReplicasToAdd == 0 || deploymentReplicasToAdd == deploymentReplicasAdded { 472 return int32(0) 473 } 474 475 rsFraction := getReplicaSetFraction(logger, *rs, d) 476 allowed := deploymentReplicasToAdd - deploymentReplicasAdded 477 478 if deploymentReplicasToAdd > 0 { 479 // Use the minimum between the replica set fraction and the maximum allowed replicas 480 // when scaling up. This way we ensure we will not scale up more than the allowed 481 // replicas we can add. 482 return min(rsFraction, allowed) 483 } 484 // Use the maximum between the replica set fraction and the maximum allowed replicas 485 // when scaling down. This way we ensure we will not scale down more than the allowed 486 // replicas we can remove. 487 return max(rsFraction, allowed) 488 } 489 490 // getReplicaSetFraction estimates the fraction of replicas a replica set can have in 491 // 1. a scaling event during a rollout or 2. when scaling a paused deployment. 492 func getReplicaSetFraction(logger klog.Logger, rs apps.ReplicaSet, d apps.Deployment) int32 { 493 // If we are scaling down to zero then the fraction of this replica set is its whole size (negative) 494 if *(d.Spec.Replicas) == int32(0) { 495 return -*(rs.Spec.Replicas) 496 } 497 498 deploymentReplicas := *(d.Spec.Replicas) + MaxSurge(d) 499 annotatedReplicas, ok := getMaxReplicasAnnotation(logger, &rs) 500 if !ok { 501 // If we cannot find the annotation then fallback to the current deployment size. Note that this 502 // will not be an accurate proportion estimation in case other replica sets have different values 503 // which means that the deployment was scaled at some point but we at least will stay in limits 504 // due to the min-max comparisons in getProportion. 505 annotatedReplicas = d.Status.Replicas 506 } 507 508 // We should never proportionally scale up from zero which means rs.spec.replicas and annotatedReplicas 509 // will never be zero here. 510 newRSsize := (float64(*(rs.Spec.Replicas) * deploymentReplicas)) / float64(annotatedReplicas) 511 return integer.RoundToInt32(newRSsize) - *(rs.Spec.Replicas) 512 } 513 514 // RsListFromClient returns an rsListFunc that wraps the given client. 515 func RsListFromClient(c appsclient.AppsV1Interface) RsListFunc { 516 return func(namespace string, options metav1.ListOptions) ([]*apps.ReplicaSet, error) { 517 rsList, err := c.ReplicaSets(namespace).List(context.TODO(), options) 518 if err != nil { 519 return nil, err 520 } 521 var ret []*apps.ReplicaSet 522 for i := range rsList.Items { 523 ret = append(ret, &rsList.Items[i]) 524 } 525 return ret, err 526 } 527 } 528 529 // TODO: switch RsListFunc and podListFunc to full namespacers 530 531 // RsListFunc returns the ReplicaSet from the ReplicaSet namespace and the List metav1.ListOptions. 532 type RsListFunc func(string, metav1.ListOptions) ([]*apps.ReplicaSet, error) 533 534 // podListFunc returns the PodList from the Pod namespace and the List metav1.ListOptions. 535 type podListFunc func(string, metav1.ListOptions) (*v1.PodList, error) 536 537 // ListReplicaSets returns a slice of RSes the given deployment targets. 538 // Note that this does NOT attempt to reconcile ControllerRef (adopt/orphan), 539 // because only the controller itself should do that. 540 // However, it does filter out anything whose ControllerRef doesn't match. 541 func ListReplicaSets(deployment *apps.Deployment, getRSList RsListFunc) ([]*apps.ReplicaSet, error) { 542 // TODO: Right now we list replica sets by their labels. We should list them by selector, i.e. the replica set's selector 543 // should be a superset of the deployment's selector, see https://github.com/kubernetes/kubernetes/issues/19830. 544 namespace := deployment.Namespace 545 selector, err := metav1.LabelSelectorAsSelector(deployment.Spec.Selector) 546 if err != nil { 547 return nil, err 548 } 549 options := metav1.ListOptions{LabelSelector: selector.String()} 550 all, err := getRSList(namespace, options) 551 if err != nil { 552 return nil, err 553 } 554 // Only include those whose ControllerRef matches the Deployment. 555 owned := make([]*apps.ReplicaSet, 0, len(all)) 556 for _, rs := range all { 557 if metav1.IsControlledBy(rs, deployment) { 558 owned = append(owned, rs) 559 } 560 } 561 return owned, nil 562 } 563 564 // ListPods returns a list of pods the given deployment targets. 565 // This needs a list of ReplicaSets for the Deployment, 566 // which can be found with ListReplicaSets(). 567 // Note that this does NOT attempt to reconcile ControllerRef (adopt/orphan), 568 // because only the controller itself should do that. 569 // However, it does filter out anything whose ControllerRef doesn't match. 570 func ListPods(deployment *apps.Deployment, rsList []*apps.ReplicaSet, getPodList podListFunc) (*v1.PodList, error) { 571 namespace := deployment.Namespace 572 selector, err := metav1.LabelSelectorAsSelector(deployment.Spec.Selector) 573 if err != nil { 574 return nil, err 575 } 576 options := metav1.ListOptions{LabelSelector: selector.String()} 577 all, err := getPodList(namespace, options) 578 if err != nil { 579 return all, err 580 } 581 // Only include those whose ControllerRef points to a ReplicaSet that is in 582 // turn owned by this Deployment. 583 rsMap := make(map[types.UID]bool, len(rsList)) 584 for _, rs := range rsList { 585 rsMap[rs.UID] = true 586 } 587 owned := &v1.PodList{Items: make([]v1.Pod, 0, len(all.Items))} 588 for i := range all.Items { 589 pod := &all.Items[i] 590 controllerRef := metav1.GetControllerOf(pod) 591 if controllerRef != nil && rsMap[controllerRef.UID] { 592 owned.Items = append(owned.Items, *pod) 593 } 594 } 595 return owned, nil 596 } 597 598 // EqualIgnoreHash returns true if two given podTemplateSpec are equal, ignoring the diff in value of Labels[pod-template-hash] 599 // We ignore pod-template-hash because: 600 // 1. The hash result would be different upon podTemplateSpec API changes 601 // (e.g. the addition of a new field will cause the hash code to change) 602 // 2. The deployment template won't have hash labels 603 func EqualIgnoreHash(template1, template2 *v1.PodTemplateSpec) bool { 604 t1Copy := template1.DeepCopy() 605 t2Copy := template2.DeepCopy() 606 // Remove hash labels from template.Labels before comparing 607 delete(t1Copy.Labels, apps.DefaultDeploymentUniqueLabelKey) 608 delete(t2Copy.Labels, apps.DefaultDeploymentUniqueLabelKey) 609 return apiequality.Semantic.DeepEqual(t1Copy, t2Copy) 610 } 611 612 // FindNewReplicaSet returns the new RS this given deployment targets (the one with the same pod template). 613 func FindNewReplicaSet(deployment *apps.Deployment, rsList []*apps.ReplicaSet) *apps.ReplicaSet { 614 sort.Sort(controller.ReplicaSetsByCreationTimestamp(rsList)) 615 for i := range rsList { 616 if EqualIgnoreHash(&rsList[i].Spec.Template, &deployment.Spec.Template) { 617 // In rare cases, such as after cluster upgrades, Deployment may end up with 618 // having more than one new ReplicaSets that have the same template as its template, 619 // see https://github.com/kubernetes/kubernetes/issues/40415 620 // We deterministically choose the oldest new ReplicaSet. 621 return rsList[i] 622 } 623 } 624 // new ReplicaSet does not exist. 625 return nil 626 } 627 628 // FindOldReplicaSets returns the old replica sets targeted by the given Deployment, with the given slice of RSes. 629 // Note that the first set of old replica sets doesn't include the ones with no pods, and the second set of old replica sets include all old replica sets. 630 func FindOldReplicaSets(deployment *apps.Deployment, rsList []*apps.ReplicaSet) ([]*apps.ReplicaSet, []*apps.ReplicaSet) { 631 var requiredRSs []*apps.ReplicaSet 632 var allRSs []*apps.ReplicaSet 633 newRS := FindNewReplicaSet(deployment, rsList) 634 for _, rs := range rsList { 635 // Filter out new replica set 636 if newRS != nil && rs.UID == newRS.UID { 637 continue 638 } 639 allRSs = append(allRSs, rs) 640 if *(rs.Spec.Replicas) != 0 { 641 requiredRSs = append(requiredRSs, rs) 642 } 643 } 644 return requiredRSs, allRSs 645 } 646 647 // SetFromReplicaSetTemplate sets the desired PodTemplateSpec from a replica set template to the given deployment. 648 func SetFromReplicaSetTemplate(deployment *apps.Deployment, template v1.PodTemplateSpec) *apps.Deployment { 649 deployment.Spec.Template.ObjectMeta = template.ObjectMeta 650 deployment.Spec.Template.Spec = template.Spec 651 deployment.Spec.Template.ObjectMeta.Labels = labelsutil.CloneAndRemoveLabel( 652 deployment.Spec.Template.ObjectMeta.Labels, 653 apps.DefaultDeploymentUniqueLabelKey) 654 return deployment 655 } 656 657 // GetReplicaCountForReplicaSets returns the sum of Replicas of the given replica sets. 658 func GetReplicaCountForReplicaSets(replicaSets []*apps.ReplicaSet) int32 { 659 totalReplicas := int32(0) 660 for _, rs := range replicaSets { 661 if rs != nil { 662 totalReplicas += *(rs.Spec.Replicas) 663 } 664 } 665 return totalReplicas 666 } 667 668 // GetActualReplicaCountForReplicaSets returns the sum of actual replicas of the given replica sets. 669 func GetActualReplicaCountForReplicaSets(replicaSets []*apps.ReplicaSet) int32 { 670 totalActualReplicas := int32(0) 671 for _, rs := range replicaSets { 672 if rs != nil { 673 totalActualReplicas += rs.Status.Replicas 674 } 675 } 676 return totalActualReplicas 677 } 678 679 // GetReadyReplicaCountForReplicaSets returns the number of ready pods corresponding to the given replica sets. 680 func GetReadyReplicaCountForReplicaSets(replicaSets []*apps.ReplicaSet) int32 { 681 totalReadyReplicas := int32(0) 682 for _, rs := range replicaSets { 683 if rs != nil { 684 totalReadyReplicas += rs.Status.ReadyReplicas 685 } 686 } 687 return totalReadyReplicas 688 } 689 690 // GetAvailableReplicaCountForReplicaSets returns the number of available pods corresponding to the given replica sets. 691 func GetAvailableReplicaCountForReplicaSets(replicaSets []*apps.ReplicaSet) int32 { 692 totalAvailableReplicas := int32(0) 693 for _, rs := range replicaSets { 694 if rs != nil { 695 totalAvailableReplicas += rs.Status.AvailableReplicas 696 } 697 } 698 return totalAvailableReplicas 699 } 700 701 // IsRollingUpdate returns true if the strategy type is a rolling update. 702 func IsRollingUpdate(deployment *apps.Deployment) bool { 703 return deployment.Spec.Strategy.Type == apps.RollingUpdateDeploymentStrategyType 704 } 705 706 // DeploymentComplete considers a deployment to be complete once all of its desired replicas 707 // are updated and available, and no old pods are running. 708 func DeploymentComplete(deployment *apps.Deployment, newStatus *apps.DeploymentStatus) bool { 709 return newStatus.UpdatedReplicas == *(deployment.Spec.Replicas) && 710 newStatus.Replicas == *(deployment.Spec.Replicas) && 711 newStatus.AvailableReplicas == *(deployment.Spec.Replicas) && 712 newStatus.ObservedGeneration >= deployment.Generation 713 } 714 715 // DeploymentProgressing reports progress for a deployment. Progress is estimated by comparing the 716 // current with the new status of the deployment that the controller is observing. More specifically, 717 // when new pods are scaled up or become ready or available, or old pods are scaled down, then we 718 // consider the deployment is progressing. 719 func DeploymentProgressing(deployment *apps.Deployment, newStatus *apps.DeploymentStatus) bool { 720 oldStatus := deployment.Status 721 722 // Old replicas that need to be scaled down 723 oldStatusOldReplicas := oldStatus.Replicas - oldStatus.UpdatedReplicas 724 newStatusOldReplicas := newStatus.Replicas - newStatus.UpdatedReplicas 725 726 return (newStatus.UpdatedReplicas > oldStatus.UpdatedReplicas) || 727 (newStatusOldReplicas < oldStatusOldReplicas) || 728 newStatus.ReadyReplicas > deployment.Status.ReadyReplicas || 729 newStatus.AvailableReplicas > deployment.Status.AvailableReplicas 730 } 731 732 // used for unit testing 733 var nowFn = func() time.Time { return time.Now() } 734 735 // DeploymentTimedOut considers a deployment to have timed out once its condition that reports progress 736 // is older than progressDeadlineSeconds or a Progressing condition with a TimedOutReason reason already 737 // exists. 738 func DeploymentTimedOut(ctx context.Context, deployment *apps.Deployment, newStatus *apps.DeploymentStatus) bool { 739 if !HasProgressDeadline(deployment) { 740 return false 741 } 742 743 // Look for the Progressing condition. If it doesn't exist, we have no base to estimate progress. 744 // If it's already set with a TimedOutReason reason, we have already timed out, no need to check 745 // again. 746 condition := GetDeploymentCondition(*newStatus, apps.DeploymentProgressing) 747 if condition == nil { 748 return false 749 } 750 // If the previous condition has been a successful rollout then we shouldn't try to 751 // estimate any progress. Scenario: 752 // 753 // * progressDeadlineSeconds is smaller than the difference between now and the time 754 // the last rollout finished in the past. 755 // * the creation of a new ReplicaSet triggers a resync of the Deployment prior to the 756 // cached copy of the Deployment getting updated with the status.condition that indicates 757 // the creation of the new ReplicaSet. 758 // 759 // The Deployment will be resynced and eventually its Progressing condition will catch 760 // up with the state of the world. 761 if condition.Reason == NewRSAvailableReason { 762 return false 763 } 764 if condition.Reason == TimedOutReason { 765 return true 766 } 767 logger := klog.FromContext(ctx) 768 // Look at the difference in seconds between now and the last time we reported any 769 // progress or tried to create a replica set, or resumed a paused deployment and 770 // compare against progressDeadlineSeconds. 771 from := condition.LastUpdateTime 772 now := nowFn() 773 delta := time.Duration(*deployment.Spec.ProgressDeadlineSeconds) * time.Second 774 timedOut := from.Add(delta).Before(now) 775 776 logger.V(4).Info("Deployment timed out from last progress check", "deployment", klog.KObj(deployment), "timeout", timedOut, "from", from, "now", now) 777 return timedOut 778 } 779 780 // NewRSNewReplicas calculates the number of replicas a deployment's new RS should have. 781 // When one of the followings is true, we're rolling out the deployment; otherwise, we're scaling it. 782 // 1) The new RS is saturated: newRS's replicas == deployment's replicas 783 // 2) Max number of pods allowed is reached: deployment's replicas + maxSurge == all RSs' replicas 784 func NewRSNewReplicas(deployment *apps.Deployment, allRSs []*apps.ReplicaSet, newRS *apps.ReplicaSet) (int32, error) { 785 switch deployment.Spec.Strategy.Type { 786 case apps.RollingUpdateDeploymentStrategyType: 787 // Check if we can scale up. 788 maxSurge, err := intstrutil.GetScaledValueFromIntOrPercent(deployment.Spec.Strategy.RollingUpdate.MaxSurge, int(*(deployment.Spec.Replicas)), true) 789 if err != nil { 790 return 0, err 791 } 792 // Find the total number of pods 793 currentPodCount := GetReplicaCountForReplicaSets(allRSs) 794 maxTotalPods := *(deployment.Spec.Replicas) + int32(maxSurge) 795 if currentPodCount >= maxTotalPods { 796 // Cannot scale up. 797 return *(newRS.Spec.Replicas), nil 798 } 799 // Scale up. 800 scaleUpCount := maxTotalPods - currentPodCount 801 // Do not exceed the number of desired replicas. 802 scaleUpCount = min(scaleUpCount, *(deployment.Spec.Replicas)-*(newRS.Spec.Replicas)) 803 return *(newRS.Spec.Replicas) + scaleUpCount, nil 804 case apps.RecreateDeploymentStrategyType: 805 return *(deployment.Spec.Replicas), nil 806 default: 807 return 0, fmt.Errorf("deployment type %v isn't supported", deployment.Spec.Strategy.Type) 808 } 809 } 810 811 // IsSaturated checks if the new replica set is saturated by comparing its size with its deployment size. 812 // Both the deployment and the replica set have to believe this replica set can own all of the desired 813 // replicas in the deployment and the annotation helps in achieving that. All pods of the ReplicaSet 814 // need to be available. 815 func IsSaturated(deployment *apps.Deployment, rs *apps.ReplicaSet) bool { 816 if rs == nil { 817 return false 818 } 819 desiredString := rs.Annotations[DesiredReplicasAnnotation] 820 desired, err := strconv.Atoi(desiredString) 821 if err != nil { 822 return false 823 } 824 return *(rs.Spec.Replicas) == *(deployment.Spec.Replicas) && 825 int32(desired) == *(deployment.Spec.Replicas) && 826 rs.Status.AvailableReplicas == *(deployment.Spec.Replicas) 827 } 828 829 // WaitForObservedDeployment polls for deployment to be updated so that deployment.Status.ObservedGeneration >= desiredGeneration. 830 // Returns error if polling timesout. 831 func WaitForObservedDeployment(getDeploymentFunc func() (*apps.Deployment, error), desiredGeneration int64, interval, timeout time.Duration) error { 832 // TODO: This should take clientset.Interface when all code is updated to use clientset. Keeping it this way allows the function to be used by callers who have client.Interface. 833 return wait.PollImmediate(interval, timeout, func() (bool, error) { 834 deployment, err := getDeploymentFunc() 835 if err != nil { 836 return false, err 837 } 838 return deployment.Status.ObservedGeneration >= desiredGeneration, nil 839 }) 840 } 841 842 // ResolveFenceposts resolves both maxSurge and maxUnavailable. This needs to happen in one 843 // step. For example: 844 // 845 // 2 desired, max unavailable 1%, surge 0% - should scale old(-1), then new(+1), then old(-1), then new(+1) 846 // 1 desired, max unavailable 1%, surge 0% - should scale old(-1), then new(+1) 847 // 2 desired, max unavailable 25%, surge 1% - should scale new(+1), then old(-1), then new(+1), then old(-1) 848 // 1 desired, max unavailable 25%, surge 1% - should scale new(+1), then old(-1) 849 // 2 desired, max unavailable 0%, surge 1% - should scale new(+1), then old(-1), then new(+1), then old(-1) 850 // 1 desired, max unavailable 0%, surge 1% - should scale new(+1), then old(-1) 851 func ResolveFenceposts(maxSurge, maxUnavailable *intstrutil.IntOrString, desired int32) (int32, int32, error) { 852 surge, err := intstrutil.GetScaledValueFromIntOrPercent(intstrutil.ValueOrDefault(maxSurge, intstrutil.FromInt32(0)), int(desired), true) 853 if err != nil { 854 return 0, 0, err 855 } 856 unavailable, err := intstrutil.GetScaledValueFromIntOrPercent(intstrutil.ValueOrDefault(maxUnavailable, intstrutil.FromInt32(0)), int(desired), false) 857 if err != nil { 858 return 0, 0, err 859 } 860 861 if surge == 0 && unavailable == 0 { 862 // Validation should never allow the user to explicitly use zero values for both maxSurge 863 // maxUnavailable. Due to rounding down maxUnavailable though, it may resolve to zero. 864 // If both fenceposts resolve to zero, then we should set maxUnavailable to 1 on the 865 // theory that surge might not work due to quota. 866 unavailable = 1 867 } 868 869 return int32(surge), int32(unavailable), nil 870 } 871 872 // HasProgressDeadline checks if the Deployment d is expected to surface the reason 873 // "ProgressDeadlineExceeded" when the Deployment progress takes longer than expected time. 874 func HasProgressDeadline(d *apps.Deployment) bool { 875 return d.Spec.ProgressDeadlineSeconds != nil && *d.Spec.ProgressDeadlineSeconds != math.MaxInt32 876 } 877 878 // HasRevisionHistoryLimit checks if the Deployment d is expected to keep a specified number of 879 // old replicaSets. These replicaSets are mainly kept with the purpose of rollback. 880 // The RevisionHistoryLimit can start from 0 (no retained replicasSet). When set to math.MaxInt32, 881 // the Deployment will keep all revisions. 882 func HasRevisionHistoryLimit(d *apps.Deployment) bool { 883 return d.Spec.RevisionHistoryLimit != nil && *d.Spec.RevisionHistoryLimit != math.MaxInt32 884 } 885 886 // GetDeploymentsForReplicaSet returns a list of Deployments that potentially 887 // match a ReplicaSet. Only the one specified in the ReplicaSet's ControllerRef 888 // will actually manage it. 889 // Returns an error only if no matching Deployments are found. 890 func GetDeploymentsForReplicaSet(deploymentLister appslisters.DeploymentLister, rs *apps.ReplicaSet) ([]*apps.Deployment, error) { 891 if len(rs.Labels) == 0 { 892 return nil, fmt.Errorf("no deployments found for ReplicaSet %v because it has no labels", rs.Name) 893 } 894 895 // TODO: MODIFY THIS METHOD so that it checks for the podTemplateSpecHash label 896 dList, err := deploymentLister.Deployments(rs.Namespace).List(labels.Everything()) 897 if err != nil { 898 return nil, err 899 } 900 901 var deployments []*apps.Deployment 902 for _, d := range dList { 903 selector, err := metav1.LabelSelectorAsSelector(d.Spec.Selector) 904 if err != nil { 905 // This object has an invalid selector, it does not match the replicaset 906 continue 907 } 908 // If a deployment with a nil or empty selector creeps in, it should match nothing, not everything. 909 if selector.Empty() || !selector.Matches(labels.Set(rs.Labels)) { 910 continue 911 } 912 deployments = append(deployments, d) 913 } 914 915 if len(deployments) == 0 { 916 return nil, fmt.Errorf("could not find deployments set for ReplicaSet %s in namespace %s with labels: %v", rs.Name, rs.Namespace, rs.Labels) 917 } 918 919 return deployments, nil 920 } 921 922 // ReplicaSetsByRevision sorts a list of ReplicaSet by revision, using their creation timestamp or name as a tie breaker. 923 // By using the creation timestamp, this sorts from old to new replica sets. 924 type ReplicaSetsByRevision []*apps.ReplicaSet 925 926 func (o ReplicaSetsByRevision) Len() int { return len(o) } 927 func (o ReplicaSetsByRevision) Swap(i, j int) { o[i], o[j] = o[j], o[i] } 928 func (o ReplicaSetsByRevision) Less(i, j int) bool { 929 revision1, err1 := Revision(o[i]) 930 revision2, err2 := Revision(o[j]) 931 if err1 != nil || err2 != nil || revision1 == revision2 { 932 return controller.ReplicaSetsByCreationTimestamp(o).Less(i, j) 933 } 934 return revision1 < revision2 935 }