mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-31 05:40:42 +00:00 
			
		
		
		
	1. MaxInt32 has the same meaning as unset, for compatibility 2. Deployment controller treats MaxInt32 the same as unset (nil)
		
			
				
	
	
		
			537 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			537 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
| Copyright 2016 The Kubernetes Authors.
 | |
| 
 | |
| 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 deployment
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"reflect"
 | |
| 	"sort"
 | |
| 	"strconv"
 | |
| 
 | |
| 	"github.com/golang/glog"
 | |
| 	apps "k8s.io/api/apps/v1"
 | |
| 	"k8s.io/api/core/v1"
 | |
| 	"k8s.io/apimachinery/pkg/api/errors"
 | |
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | |
| 	"k8s.io/kubernetes/pkg/controller"
 | |
| 	deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util"
 | |
| 	labelsutil "k8s.io/kubernetes/pkg/util/labels"
 | |
| )
 | |
| 
 | |
| // syncStatusOnly only updates Deployments Status and doesn't take any mutating actions.
 | |
| func (dc *DeploymentController) syncStatusOnly(d *apps.Deployment, rsList []*apps.ReplicaSet) error {
 | |
| 	newRS, oldRSs, err := dc.getAllReplicaSetsAndSyncRevision(d, rsList, false)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	allRSs := append(oldRSs, newRS)
 | |
| 	return dc.syncDeploymentStatus(allRSs, newRS, d)
 | |
| }
 | |
| 
 | |
| // sync is responsible for reconciling deployments on scaling events or when they
 | |
| // are paused.
 | |
| func (dc *DeploymentController) sync(d *apps.Deployment, rsList []*apps.ReplicaSet) error {
 | |
| 	newRS, oldRSs, err := dc.getAllReplicaSetsAndSyncRevision(d, rsList, false)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if err := dc.scale(d, newRS, oldRSs); err != nil {
 | |
| 		// If we get an error while trying to scale, the deployment will be requeued
 | |
| 		// so we can abort this resync
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	// Clean up the deployment when it's paused and no rollback is in flight.
 | |
| 	if d.Spec.Paused && getRollbackTo(d) == nil {
 | |
| 		if err := dc.cleanupDeployment(oldRSs, d); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	allRSs := append(oldRSs, newRS)
 | |
| 	return dc.syncDeploymentStatus(allRSs, newRS, d)
 | |
| }
 | |
| 
 | |
| // checkPausedConditions checks if the given deployment is paused or not and adds an appropriate condition.
 | |
| // These conditions are needed so that we won't accidentally report lack of progress for resumed deployments
 | |
| // that were paused for longer than progressDeadlineSeconds.
 | |
| func (dc *DeploymentController) checkPausedConditions(d *apps.Deployment) error {
 | |
| 	if !deploymentutil.HasProgressDeadline(d) {
 | |
| 		return nil
 | |
| 	}
 | |
| 	cond := deploymentutil.GetDeploymentCondition(d.Status, apps.DeploymentProgressing)
 | |
| 	if cond != nil && cond.Reason == deploymentutil.TimedOutReason {
 | |
| 		// If we have reported lack of progress, do not overwrite it with a paused condition.
 | |
| 		return nil
 | |
| 	}
 | |
| 	pausedCondExists := cond != nil && cond.Reason == deploymentutil.PausedDeployReason
 | |
| 
 | |
| 	needsUpdate := false
 | |
| 	if d.Spec.Paused && !pausedCondExists {
 | |
| 		condition := deploymentutil.NewDeploymentCondition(apps.DeploymentProgressing, v1.ConditionUnknown, deploymentutil.PausedDeployReason, "Deployment is paused")
 | |
| 		deploymentutil.SetDeploymentCondition(&d.Status, *condition)
 | |
| 		needsUpdate = true
 | |
| 	} else if !d.Spec.Paused && pausedCondExists {
 | |
| 		condition := deploymentutil.NewDeploymentCondition(apps.DeploymentProgressing, v1.ConditionUnknown, deploymentutil.ResumedDeployReason, "Deployment is resumed")
 | |
| 		deploymentutil.SetDeploymentCondition(&d.Status, *condition)
 | |
| 		needsUpdate = true
 | |
| 	}
 | |
| 
 | |
| 	if !needsUpdate {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	var err error
 | |
| 	d, err = dc.client.AppsV1().Deployments(d.Namespace).UpdateStatus(d)
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| // getAllReplicaSetsAndSyncRevision returns all the replica sets for the provided deployment (new and all old), with new RS's and deployment's revision updated.
 | |
| //
 | |
| // rsList should come from getReplicaSetsForDeployment(d).
 | |
| //
 | |
| // 1. Get all old RSes this deployment targets, and calculate the max revision number among them (maxOldV).
 | |
| // 2. Get new RS this deployment targets (whose pod template matches deployment's), and update new RS's revision number to (maxOldV + 1),
 | |
| //    only if its revision number is smaller than (maxOldV + 1). If this step failed, we'll update it in the next deployment sync loop.
 | |
| // 3. Copy new RS's revision number to deployment (update deployment's revision). If this step failed, we'll update it in the next deployment sync loop.
 | |
| //
 | |
| // Note that currently the deployment controller is using caches to avoid querying the server for reads.
 | |
| // This may lead to stale reads of replica sets, thus incorrect deployment status.
 | |
| func (dc *DeploymentController) getAllReplicaSetsAndSyncRevision(d *apps.Deployment, rsList []*apps.ReplicaSet, createIfNotExisted bool) (*apps.ReplicaSet, []*apps.ReplicaSet, error) {
 | |
| 	_, allOldRSs := deploymentutil.FindOldReplicaSets(d, rsList)
 | |
| 
 | |
| 	// Get new replica set with the updated revision number
 | |
| 	newRS, err := dc.getNewReplicaSet(d, rsList, allOldRSs, createIfNotExisted)
 | |
| 	if err != nil {
 | |
| 		return nil, nil, err
 | |
| 	}
 | |
| 
 | |
| 	return newRS, allOldRSs, nil
 | |
| }
 | |
| 
 | |
| // Returns a replica set that matches the intent of the given deployment. Returns nil if the new replica set doesn't exist yet.
 | |
| // 1. Get existing new RS (the RS that the given deployment targets, whose pod template is the same as deployment's).
 | |
| // 2. If there's existing new RS, update its revision number if it's smaller than (maxOldRevision + 1), where maxOldRevision is the max revision number among all old RSes.
 | |
| // 3. If there's no existing new RS and createIfNotExisted is true, create one with appropriate revision number (maxOldRevision + 1) and replicas.
 | |
| // Note that the pod-template-hash will be added to adopted RSes and pods.
 | |
| func (dc *DeploymentController) getNewReplicaSet(d *apps.Deployment, rsList, oldRSs []*apps.ReplicaSet, createIfNotExisted bool) (*apps.ReplicaSet, error) {
 | |
| 	existingNewRS := deploymentutil.FindNewReplicaSet(d, rsList)
 | |
| 
 | |
| 	// Calculate the max revision number among all old RSes
 | |
| 	maxOldRevision := deploymentutil.MaxRevision(oldRSs)
 | |
| 	// Calculate revision number for this new replica set
 | |
| 	newRevision := strconv.FormatInt(maxOldRevision+1, 10)
 | |
| 
 | |
| 	// Latest replica set exists. We need to sync its annotations (includes copying all but
 | |
| 	// annotationsToSkip from the parent deployment, and update revision, desiredReplicas,
 | |
| 	// and maxReplicas) and also update the revision annotation in the deployment with the
 | |
| 	// latest revision.
 | |
| 	if existingNewRS != nil {
 | |
| 		rsCopy := existingNewRS.DeepCopy()
 | |
| 
 | |
| 		// Set existing new replica set's annotation
 | |
| 		annotationsUpdated := deploymentutil.SetNewReplicaSetAnnotations(d, rsCopy, newRevision, true)
 | |
| 		minReadySecondsNeedsUpdate := rsCopy.Spec.MinReadySeconds != d.Spec.MinReadySeconds
 | |
| 		if annotationsUpdated || minReadySecondsNeedsUpdate {
 | |
| 			rsCopy.Spec.MinReadySeconds = d.Spec.MinReadySeconds
 | |
| 			return dc.client.AppsV1().ReplicaSets(rsCopy.ObjectMeta.Namespace).Update(rsCopy)
 | |
| 		}
 | |
| 
 | |
| 		// Should use the revision in existingNewRS's annotation, since it set by before
 | |
| 		needsUpdate := deploymentutil.SetDeploymentRevision(d, rsCopy.Annotations[deploymentutil.RevisionAnnotation])
 | |
| 		// If no other Progressing condition has been recorded and we need to estimate the progress
 | |
| 		// of this deployment then it is likely that old users started caring about progress. In that
 | |
| 		// case we need to take into account the first time we noticed their new replica set.
 | |
| 		cond := deploymentutil.GetDeploymentCondition(d.Status, apps.DeploymentProgressing)
 | |
| 		if deploymentutil.HasProgressDeadline(d) && cond == nil {
 | |
| 			msg := fmt.Sprintf("Found new replica set %q", rsCopy.Name)
 | |
| 			condition := deploymentutil.NewDeploymentCondition(apps.DeploymentProgressing, v1.ConditionTrue, deploymentutil.FoundNewRSReason, msg)
 | |
| 			deploymentutil.SetDeploymentCondition(&d.Status, *condition)
 | |
| 			needsUpdate = true
 | |
| 		}
 | |
| 
 | |
| 		if needsUpdate {
 | |
| 			var err error
 | |
| 			if d, err = dc.client.AppsV1().Deployments(d.Namespace).UpdateStatus(d); err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 		}
 | |
| 		return rsCopy, nil
 | |
| 	}
 | |
| 
 | |
| 	if !createIfNotExisted {
 | |
| 		return nil, nil
 | |
| 	}
 | |
| 
 | |
| 	// new ReplicaSet does not exist, create one.
 | |
| 	newRSTemplate := *d.Spec.Template.DeepCopy()
 | |
| 	podTemplateSpecHash := controller.ComputeHash(&newRSTemplate, d.Status.CollisionCount)
 | |
| 	newRSTemplate.Labels = labelsutil.CloneAndAddLabel(d.Spec.Template.Labels, apps.DefaultDeploymentUniqueLabelKey, podTemplateSpecHash)
 | |
| 	// Add podTemplateHash label to selector.
 | |
| 	newRSSelector := labelsutil.CloneSelectorAndAddLabel(d.Spec.Selector, apps.DefaultDeploymentUniqueLabelKey, podTemplateSpecHash)
 | |
| 
 | |
| 	// Create new ReplicaSet
 | |
| 	newRS := apps.ReplicaSet{
 | |
| 		ObjectMeta: metav1.ObjectMeta{
 | |
| 			// Make the name deterministic, to ensure idempotence
 | |
| 			Name:            d.Name + "-" + podTemplateSpecHash,
 | |
| 			Namespace:       d.Namespace,
 | |
| 			OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(d, controllerKind)},
 | |
| 			Labels:          newRSTemplate.Labels,
 | |
| 		},
 | |
| 		Spec: apps.ReplicaSetSpec{
 | |
| 			Replicas:        new(int32),
 | |
| 			MinReadySeconds: d.Spec.MinReadySeconds,
 | |
| 			Selector:        newRSSelector,
 | |
| 			Template:        newRSTemplate,
 | |
| 		},
 | |
| 	}
 | |
| 	allRSs := append(oldRSs, &newRS)
 | |
| 	newReplicasCount, err := deploymentutil.NewRSNewReplicas(d, allRSs, &newRS)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	*(newRS.Spec.Replicas) = newReplicasCount
 | |
| 	// Set new replica set's annotation
 | |
| 	deploymentutil.SetNewReplicaSetAnnotations(d, &newRS, newRevision, false)
 | |
| 	// Create the new ReplicaSet. If it already exists, then we need to check for possible
 | |
| 	// hash collisions. If there is any other error, we need to report it in the status of
 | |
| 	// the Deployment.
 | |
| 	alreadyExists := false
 | |
| 	createdRS, err := dc.client.AppsV1().ReplicaSets(d.Namespace).Create(&newRS)
 | |
| 	switch {
 | |
| 	// We may end up hitting this due to a slow cache or a fast resync of the Deployment.
 | |
| 	case errors.IsAlreadyExists(err):
 | |
| 		alreadyExists = true
 | |
| 
 | |
| 		// Fetch a copy of the ReplicaSet.
 | |
| 		rs, rsErr := dc.rsLister.ReplicaSets(newRS.Namespace).Get(newRS.Name)
 | |
| 		if rsErr != nil {
 | |
| 			return nil, rsErr
 | |
| 		}
 | |
| 
 | |
| 		// If the Deployment owns the ReplicaSet and the ReplicaSet's PodTemplateSpec is semantically
 | |
| 		// deep equal to the PodTemplateSpec of the Deployment, it's the Deployment's new ReplicaSet.
 | |
| 		// Otherwise, this is a hash collision and we need to increment the collisionCount field in
 | |
| 		// the status of the Deployment and requeue to try the creation in the next sync.
 | |
| 		controllerRef := metav1.GetControllerOf(rs)
 | |
| 		if controllerRef != nil && controllerRef.UID == d.UID && deploymentutil.EqualIgnoreHash(&d.Spec.Template, &rs.Spec.Template) {
 | |
| 			createdRS = rs
 | |
| 			err = nil
 | |
| 			break
 | |
| 		}
 | |
| 
 | |
| 		// Matching ReplicaSet is not equal - increment the collisionCount in the DeploymentStatus
 | |
| 		// and requeue the Deployment.
 | |
| 		if d.Status.CollisionCount == nil {
 | |
| 			d.Status.CollisionCount = new(int32)
 | |
| 		}
 | |
| 		preCollisionCount := *d.Status.CollisionCount
 | |
| 		*d.Status.CollisionCount++
 | |
| 		// Update the collisionCount for the Deployment and let it requeue by returning the original
 | |
| 		// error.
 | |
| 		_, dErr := dc.client.AppsV1().Deployments(d.Namespace).UpdateStatus(d)
 | |
| 		if dErr == nil {
 | |
| 			glog.V(2).Infof("Found a hash collision for deployment %q - bumping collisionCount (%d->%d) to resolve it", d.Name, preCollisionCount, *d.Status.CollisionCount)
 | |
| 		}
 | |
| 		return nil, err
 | |
| 	case err != nil:
 | |
| 		msg := fmt.Sprintf("Failed to create new replica set %q: %v", newRS.Name, err)
 | |
| 		if deploymentutil.HasProgressDeadline(d) {
 | |
| 			cond := deploymentutil.NewDeploymentCondition(apps.DeploymentProgressing, v1.ConditionFalse, deploymentutil.FailedRSCreateReason, msg)
 | |
| 			deploymentutil.SetDeploymentCondition(&d.Status, *cond)
 | |
| 			// We don't really care about this error at this point, since we have a bigger issue to report.
 | |
| 			// TODO: Identify which errors are permanent and switch DeploymentIsFailed to take into account
 | |
| 			// these reasons as well. Related issue: https://github.com/kubernetes/kubernetes/issues/18568
 | |
| 			_, _ = dc.client.AppsV1().Deployments(d.Namespace).UpdateStatus(d)
 | |
| 		}
 | |
| 		dc.eventRecorder.Eventf(d, v1.EventTypeWarning, deploymentutil.FailedRSCreateReason, msg)
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	if !alreadyExists && newReplicasCount > 0 {
 | |
| 		dc.eventRecorder.Eventf(d, v1.EventTypeNormal, "ScalingReplicaSet", "Scaled up replica set %s to %d", createdRS.Name, newReplicasCount)
 | |
| 	}
 | |
| 
 | |
| 	needsUpdate := deploymentutil.SetDeploymentRevision(d, newRevision)
 | |
| 	if !alreadyExists && deploymentutil.HasProgressDeadline(d) {
 | |
| 		msg := fmt.Sprintf("Created new replica set %q", createdRS.Name)
 | |
| 		condition := deploymentutil.NewDeploymentCondition(apps.DeploymentProgressing, v1.ConditionTrue, deploymentutil.NewReplicaSetReason, msg)
 | |
| 		deploymentutil.SetDeploymentCondition(&d.Status, *condition)
 | |
| 		needsUpdate = true
 | |
| 	}
 | |
| 	if needsUpdate {
 | |
| 		_, err = dc.client.AppsV1().Deployments(d.Namespace).UpdateStatus(d)
 | |
| 	}
 | |
| 	return createdRS, err
 | |
| }
 | |
| 
 | |
| // scale scales proportionally in order to mitigate risk. Otherwise, scaling up can increase the size
 | |
| // of the new replica set and scaling down can decrease the sizes of the old ones, both of which would
 | |
| // have the effect of hastening the rollout progress, which could produce a higher proportion of unavailable
 | |
| // replicas in the event of a problem with the rolled out template. Should run only on scaling events or
 | |
| // when a deployment is paused and not during the normal rollout process.
 | |
| func (dc *DeploymentController) scale(deployment *apps.Deployment, newRS *apps.ReplicaSet, oldRSs []*apps.ReplicaSet) error {
 | |
| 	// If there is only one active replica set then we should scale that up to the full count of the
 | |
| 	// deployment. If there is no active replica set, then we should scale up the newest replica set.
 | |
| 	if activeOrLatest := deploymentutil.FindActiveOrLatest(newRS, oldRSs); activeOrLatest != nil {
 | |
| 		if *(activeOrLatest.Spec.Replicas) == *(deployment.Spec.Replicas) {
 | |
| 			return nil
 | |
| 		}
 | |
| 		_, _, err := dc.scaleReplicaSetAndRecordEvent(activeOrLatest, *(deployment.Spec.Replicas), deployment)
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	// If the new replica set is saturated, old replica sets should be fully scaled down.
 | |
| 	// This case handles replica set adoption during a saturated new replica set.
 | |
| 	if deploymentutil.IsSaturated(deployment, newRS) {
 | |
| 		for _, old := range controller.FilterActiveReplicaSets(oldRSs) {
 | |
| 			if _, _, err := dc.scaleReplicaSetAndRecordEvent(old, 0, deployment); err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 		}
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	// There are old replica sets with pods and the new replica set is not saturated.
 | |
| 	// We need to proportionally scale all replica sets (new and old) in case of a
 | |
| 	// rolling deployment.
 | |
| 	if deploymentutil.IsRollingUpdate(deployment) {
 | |
| 		allRSs := controller.FilterActiveReplicaSets(append(oldRSs, newRS))
 | |
| 		allRSsReplicas := deploymentutil.GetReplicaCountForReplicaSets(allRSs)
 | |
| 
 | |
| 		allowedSize := int32(0)
 | |
| 		if *(deployment.Spec.Replicas) > 0 {
 | |
| 			allowedSize = *(deployment.Spec.Replicas) + deploymentutil.MaxSurge(*deployment)
 | |
| 		}
 | |
| 
 | |
| 		// Number of additional replicas that can be either added or removed from the total
 | |
| 		// replicas count. These replicas should be distributed proportionally to the active
 | |
| 		// replica sets.
 | |
| 		deploymentReplicasToAdd := allowedSize - allRSsReplicas
 | |
| 
 | |
| 		// The additional replicas should be distributed proportionally amongst the active
 | |
| 		// replica sets from the larger to the smaller in size replica set. Scaling direction
 | |
| 		// drives what happens in case we are trying to scale replica sets of the same size.
 | |
| 		// In such a case when scaling up, we should scale up newer replica sets first, and
 | |
| 		// when scaling down, we should scale down older replica sets first.
 | |
| 		var scalingOperation string
 | |
| 		switch {
 | |
| 		case deploymentReplicasToAdd > 0:
 | |
| 			sort.Sort(controller.ReplicaSetsBySizeNewer(allRSs))
 | |
| 			scalingOperation = "up"
 | |
| 
 | |
| 		case deploymentReplicasToAdd < 0:
 | |
| 			sort.Sort(controller.ReplicaSetsBySizeOlder(allRSs))
 | |
| 			scalingOperation = "down"
 | |
| 		}
 | |
| 
 | |
| 		// Iterate over all active replica sets and estimate proportions for each of them.
 | |
| 		// The absolute value of deploymentReplicasAdded should never exceed the absolute
 | |
| 		// value of deploymentReplicasToAdd.
 | |
| 		deploymentReplicasAdded := int32(0)
 | |
| 		nameToSize := make(map[string]int32)
 | |
| 		for i := range allRSs {
 | |
| 			rs := allRSs[i]
 | |
| 
 | |
| 			// Estimate proportions if we have replicas to add, otherwise simply populate
 | |
| 			// nameToSize with the current sizes for each replica set.
 | |
| 			if deploymentReplicasToAdd != 0 {
 | |
| 				proportion := deploymentutil.GetProportion(rs, *deployment, deploymentReplicasToAdd, deploymentReplicasAdded)
 | |
| 
 | |
| 				nameToSize[rs.Name] = *(rs.Spec.Replicas) + proportion
 | |
| 				deploymentReplicasAdded += proportion
 | |
| 			} else {
 | |
| 				nameToSize[rs.Name] = *(rs.Spec.Replicas)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Update all replica sets
 | |
| 		for i := range allRSs {
 | |
| 			rs := allRSs[i]
 | |
| 
 | |
| 			// Add/remove any leftovers to the largest replica set.
 | |
| 			if i == 0 && deploymentReplicasToAdd != 0 {
 | |
| 				leftover := deploymentReplicasToAdd - deploymentReplicasAdded
 | |
| 				nameToSize[rs.Name] = nameToSize[rs.Name] + leftover
 | |
| 				if nameToSize[rs.Name] < 0 {
 | |
| 					nameToSize[rs.Name] = 0
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			// TODO: Use transactions when we have them.
 | |
| 			if _, _, err := dc.scaleReplicaSet(rs, nameToSize[rs.Name], deployment, scalingOperation); err != nil {
 | |
| 				// Return as soon as we fail, the deployment is requeued
 | |
| 				return err
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (dc *DeploymentController) scaleReplicaSetAndRecordEvent(rs *apps.ReplicaSet, newScale int32, deployment *apps.Deployment) (bool, *apps.ReplicaSet, error) {
 | |
| 	// No need to scale
 | |
| 	if *(rs.Spec.Replicas) == newScale {
 | |
| 		return false, rs, nil
 | |
| 	}
 | |
| 	var scalingOperation string
 | |
| 	if *(rs.Spec.Replicas) < newScale {
 | |
| 		scalingOperation = "up"
 | |
| 	} else {
 | |
| 		scalingOperation = "down"
 | |
| 	}
 | |
| 	scaled, newRS, err := dc.scaleReplicaSet(rs, newScale, deployment, scalingOperation)
 | |
| 	return scaled, newRS, err
 | |
| }
 | |
| 
 | |
| func (dc *DeploymentController) scaleReplicaSet(rs *apps.ReplicaSet, newScale int32, deployment *apps.Deployment, scalingOperation string) (bool, *apps.ReplicaSet, error) {
 | |
| 
 | |
| 	sizeNeedsUpdate := *(rs.Spec.Replicas) != newScale
 | |
| 
 | |
| 	annotationsNeedUpdate := deploymentutil.ReplicasAnnotationsNeedUpdate(rs, *(deployment.Spec.Replicas), *(deployment.Spec.Replicas)+deploymentutil.MaxSurge(*deployment))
 | |
| 
 | |
| 	scaled := false
 | |
| 	var err error
 | |
| 	if sizeNeedsUpdate || annotationsNeedUpdate {
 | |
| 		rsCopy := rs.DeepCopy()
 | |
| 		*(rsCopy.Spec.Replicas) = newScale
 | |
| 		deploymentutil.SetReplicasAnnotations(rsCopy, *(deployment.Spec.Replicas), *(deployment.Spec.Replicas)+deploymentutil.MaxSurge(*deployment))
 | |
| 		rs, err = dc.client.AppsV1().ReplicaSets(rsCopy.Namespace).Update(rsCopy)
 | |
| 		if err == nil && sizeNeedsUpdate {
 | |
| 			scaled = true
 | |
| 			dc.eventRecorder.Eventf(deployment, v1.EventTypeNormal, "ScalingReplicaSet", "Scaled %s replica set %s to %d", scalingOperation, rs.Name, newScale)
 | |
| 		}
 | |
| 	}
 | |
| 	return scaled, rs, err
 | |
| }
 | |
| 
 | |
| // cleanupDeployment is responsible for cleaning up a deployment ie. retains all but the latest N old replica sets
 | |
| // where N=d.Spec.RevisionHistoryLimit. Old replica sets are older versions of the podtemplate of a deployment kept
 | |
| // around by default 1) for historical reasons and 2) for the ability to rollback a deployment.
 | |
| func (dc *DeploymentController) cleanupDeployment(oldRSs []*apps.ReplicaSet, deployment *apps.Deployment) error {
 | |
| 	if deployment.Spec.RevisionHistoryLimit == nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	// Avoid deleting replica set with deletion timestamp set
 | |
| 	aliveFilter := func(rs *apps.ReplicaSet) bool {
 | |
| 		return rs != nil && rs.ObjectMeta.DeletionTimestamp == nil
 | |
| 	}
 | |
| 	cleanableRSes := controller.FilterReplicaSets(oldRSs, aliveFilter)
 | |
| 
 | |
| 	diff := int32(len(cleanableRSes)) - *deployment.Spec.RevisionHistoryLimit
 | |
| 	if diff <= 0 {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	sort.Sort(controller.ReplicaSetsByCreationTimestamp(cleanableRSes))
 | |
| 	glog.V(4).Infof("Looking to cleanup old replica sets for deployment %q", deployment.Name)
 | |
| 
 | |
| 	for i := int32(0); i < diff; i++ {
 | |
| 		rs := cleanableRSes[i]
 | |
| 		// Avoid delete replica set with non-zero replica counts
 | |
| 		if rs.Status.Replicas != 0 || *(rs.Spec.Replicas) != 0 || rs.Generation > rs.Status.ObservedGeneration || rs.DeletionTimestamp != nil {
 | |
| 			continue
 | |
| 		}
 | |
| 		glog.V(4).Infof("Trying to cleanup replica set %q for deployment %q", rs.Name, deployment.Name)
 | |
| 		if err := dc.client.AppsV1().ReplicaSets(rs.Namespace).Delete(rs.Name, nil); err != nil && !errors.IsNotFound(err) {
 | |
| 			// Return error instead of aggregating and continuing DELETEs on the theory
 | |
| 			// that we may be overloading the api server.
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // syncDeploymentStatus checks if the status is up-to-date and sync it if necessary
 | |
| func (dc *DeploymentController) syncDeploymentStatus(allRSs []*apps.ReplicaSet, newRS *apps.ReplicaSet, d *apps.Deployment) error {
 | |
| 	newStatus := calculateStatus(allRSs, newRS, d)
 | |
| 
 | |
| 	if reflect.DeepEqual(d.Status, newStatus) {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	newDeployment := d
 | |
| 	newDeployment.Status = newStatus
 | |
| 	_, err := dc.client.AppsV1().Deployments(newDeployment.Namespace).UpdateStatus(newDeployment)
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| // calculateStatus calculates the latest status for the provided deployment by looking into the provided replica sets.
 | |
| func calculateStatus(allRSs []*apps.ReplicaSet, newRS *apps.ReplicaSet, deployment *apps.Deployment) apps.DeploymentStatus {
 | |
| 	availableReplicas := deploymentutil.GetAvailableReplicaCountForReplicaSets(allRSs)
 | |
| 	totalReplicas := deploymentutil.GetReplicaCountForReplicaSets(allRSs)
 | |
| 	unavailableReplicas := totalReplicas - availableReplicas
 | |
| 	// If unavailableReplicas is negative, then that means the Deployment has more available replicas running than
 | |
| 	// desired, e.g. whenever it scales down. In such a case we should simply default unavailableReplicas to zero.
 | |
| 	if unavailableReplicas < 0 {
 | |
| 		unavailableReplicas = 0
 | |
| 	}
 | |
| 
 | |
| 	status := apps.DeploymentStatus{
 | |
| 		// TODO: Ensure that if we start retrying status updates, we won't pick up a new Generation value.
 | |
| 		ObservedGeneration:  deployment.Generation,
 | |
| 		Replicas:            deploymentutil.GetActualReplicaCountForReplicaSets(allRSs),
 | |
| 		UpdatedReplicas:     deploymentutil.GetActualReplicaCountForReplicaSets([]*apps.ReplicaSet{newRS}),
 | |
| 		ReadyReplicas:       deploymentutil.GetReadyReplicaCountForReplicaSets(allRSs),
 | |
| 		AvailableReplicas:   availableReplicas,
 | |
| 		UnavailableReplicas: unavailableReplicas,
 | |
| 		CollisionCount:      deployment.Status.CollisionCount,
 | |
| 	}
 | |
| 
 | |
| 	// Copy conditions one by one so we won't mutate the original object.
 | |
| 	conditions := deployment.Status.Conditions
 | |
| 	for i := range conditions {
 | |
| 		status.Conditions = append(status.Conditions, conditions[i])
 | |
| 	}
 | |
| 
 | |
| 	if availableReplicas >= *(deployment.Spec.Replicas)-deploymentutil.MaxUnavailable(*deployment) {
 | |
| 		minAvailability := deploymentutil.NewDeploymentCondition(apps.DeploymentAvailable, v1.ConditionTrue, deploymentutil.MinimumReplicasAvailable, "Deployment has minimum availability.")
 | |
| 		deploymentutil.SetDeploymentCondition(&status, *minAvailability)
 | |
| 	} else {
 | |
| 		noMinAvailability := deploymentutil.NewDeploymentCondition(apps.DeploymentAvailable, v1.ConditionFalse, deploymentutil.MinimumReplicasUnavailable, "Deployment does not have minimum availability.")
 | |
| 		deploymentutil.SetDeploymentCondition(&status, *noMinAvailability)
 | |
| 	}
 | |
| 
 | |
| 	return status
 | |
| }
 | |
| 
 | |
| // isScalingEvent checks whether the provided deployment has been updated with a scaling event
 | |
| // by looking at the desired-replicas annotation in the active replica sets of the deployment.
 | |
| //
 | |
| // rsList should come from getReplicaSetsForDeployment(d).
 | |
| // podMap should come from getPodMapForDeployment(d, rsList).
 | |
| func (dc *DeploymentController) isScalingEvent(d *apps.Deployment, rsList []*apps.ReplicaSet) (bool, error) {
 | |
| 	newRS, oldRSs, err := dc.getAllReplicaSetsAndSyncRevision(d, rsList, false)
 | |
| 	if err != nil {
 | |
| 		return false, err
 | |
| 	}
 | |
| 	allRSs := append(oldRSs, newRS)
 | |
| 	for _, rs := range controller.FilterActiveReplicaSets(allRSs) {
 | |
| 		desired, ok := deploymentutil.GetDesiredReplicasAnnotation(rs)
 | |
| 		if !ok {
 | |
| 			continue
 | |
| 		}
 | |
| 		if desired != *(d.Spec.Replicas) {
 | |
| 			return true, nil
 | |
| 		}
 | |
| 	}
 | |
| 	return false, nil
 | |
| }
 |