sync.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. /*
  2. Copyright 2016 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package deployment
  14. import (
  15. "fmt"
  16. "reflect"
  17. "sort"
  18. "strconv"
  19. apps "k8s.io/api/apps/v1"
  20. "k8s.io/api/core/v1"
  21. "k8s.io/apimachinery/pkg/api/errors"
  22. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  23. "k8s.io/klog"
  24. "k8s.io/kubernetes/pkg/controller"
  25. deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util"
  26. labelsutil "k8s.io/kubernetes/pkg/util/labels"
  27. )
  28. // syncStatusOnly only updates Deployments Status and doesn't take any mutating actions.
  29. func (dc *DeploymentController) syncStatusOnly(d *apps.Deployment, rsList []*apps.ReplicaSet) error {
  30. newRS, oldRSs, err := dc.getAllReplicaSetsAndSyncRevision(d, rsList, false)
  31. if err != nil {
  32. return err
  33. }
  34. allRSs := append(oldRSs, newRS)
  35. return dc.syncDeploymentStatus(allRSs, newRS, d)
  36. }
  37. // sync is responsible for reconciling deployments on scaling events or when they
  38. // are paused.
  39. func (dc *DeploymentController) sync(d *apps.Deployment, rsList []*apps.ReplicaSet) error {
  40. newRS, oldRSs, err := dc.getAllReplicaSetsAndSyncRevision(d, rsList, false)
  41. if err != nil {
  42. return err
  43. }
  44. if err := dc.scale(d, newRS, oldRSs); err != nil {
  45. // If we get an error while trying to scale, the deployment will be requeued
  46. // so we can abort this resync
  47. return err
  48. }
  49. // Clean up the deployment when it's paused and no rollback is in flight.
  50. if d.Spec.Paused && getRollbackTo(d) == nil {
  51. if err := dc.cleanupDeployment(oldRSs, d); err != nil {
  52. return err
  53. }
  54. }
  55. allRSs := append(oldRSs, newRS)
  56. return dc.syncDeploymentStatus(allRSs, newRS, d)
  57. }
  58. // checkPausedConditions checks if the given deployment is paused or not and adds an appropriate condition.
  59. // These conditions are needed so that we won't accidentally report lack of progress for resumed deployments
  60. // that were paused for longer than progressDeadlineSeconds.
  61. func (dc *DeploymentController) checkPausedConditions(d *apps.Deployment) error {
  62. if !deploymentutil.HasProgressDeadline(d) {
  63. return nil
  64. }
  65. cond := deploymentutil.GetDeploymentCondition(d.Status, apps.DeploymentProgressing)
  66. if cond != nil && cond.Reason == deploymentutil.TimedOutReason {
  67. // If we have reported lack of progress, do not overwrite it with a paused condition.
  68. return nil
  69. }
  70. pausedCondExists := cond != nil && cond.Reason == deploymentutil.PausedDeployReason
  71. needsUpdate := false
  72. if d.Spec.Paused && !pausedCondExists {
  73. condition := deploymentutil.NewDeploymentCondition(apps.DeploymentProgressing, v1.ConditionUnknown, deploymentutil.PausedDeployReason, "Deployment is paused")
  74. deploymentutil.SetDeploymentCondition(&d.Status, *condition)
  75. needsUpdate = true
  76. } else if !d.Spec.Paused && pausedCondExists {
  77. condition := deploymentutil.NewDeploymentCondition(apps.DeploymentProgressing, v1.ConditionUnknown, deploymentutil.ResumedDeployReason, "Deployment is resumed")
  78. deploymentutil.SetDeploymentCondition(&d.Status, *condition)
  79. needsUpdate = true
  80. }
  81. if !needsUpdate {
  82. return nil
  83. }
  84. var err error
  85. d, err = dc.client.AppsV1().Deployments(d.Namespace).UpdateStatus(d)
  86. return err
  87. }
  88. // getAllReplicaSetsAndSyncRevision returns all the replica sets for the provided deployment (new and all old), with new RS's and deployment's revision updated.
  89. //
  90. // rsList should come from getReplicaSetsForDeployment(d).
  91. //
  92. // 1. Get all old RSes this deployment targets, and calculate the max revision number among them (maxOldV).
  93. // 2. Get new RS this deployment targets (whose pod template matches deployment's), and update new RS's revision number to (maxOldV + 1),
  94. // only if its revision number is smaller than (maxOldV + 1). If this step failed, we'll update it in the next deployment sync loop.
  95. // 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.
  96. //
  97. // Note that currently the deployment controller is using caches to avoid querying the server for reads.
  98. // This may lead to stale reads of replica sets, thus incorrect deployment status.
  99. func (dc *DeploymentController) getAllReplicaSetsAndSyncRevision(d *apps.Deployment, rsList []*apps.ReplicaSet, createIfNotExisted bool) (*apps.ReplicaSet, []*apps.ReplicaSet, error) {
  100. _, allOldRSs := deploymentutil.FindOldReplicaSets(d, rsList)
  101. // Get new replica set with the updated revision number
  102. newRS, err := dc.getNewReplicaSet(d, rsList, allOldRSs, createIfNotExisted)
  103. if err != nil {
  104. return nil, nil, err
  105. }
  106. return newRS, allOldRSs, nil
  107. }
  108. const (
  109. // limit revision history length to 100 element (~2000 chars)
  110. maxRevHistoryLengthInChars = 2000
  111. )
  112. // Returns a replica set that matches the intent of the given deployment. Returns nil if the new replica set doesn't exist yet.
  113. // 1. Get existing new RS (the RS that the given deployment targets, whose pod template is the same as deployment's).
  114. // 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.
  115. // 3. If there's no existing new RS and createIfNotExisted is true, create one with appropriate revision number (maxOldRevision + 1) and replicas.
  116. // Note that the pod-template-hash will be added to adopted RSes and pods.
  117. func (dc *DeploymentController) getNewReplicaSet(d *apps.Deployment, rsList, oldRSs []*apps.ReplicaSet, createIfNotExisted bool) (*apps.ReplicaSet, error) {
  118. existingNewRS := deploymentutil.FindNewReplicaSet(d, rsList)
  119. // Calculate the max revision number among all old RSes
  120. maxOldRevision := deploymentutil.MaxRevision(oldRSs)
  121. // Calculate revision number for this new replica set
  122. newRevision := strconv.FormatInt(maxOldRevision+1, 10)
  123. // Latest replica set exists. We need to sync its annotations (includes copying all but
  124. // annotationsToSkip from the parent deployment, and update revision, desiredReplicas,
  125. // and maxReplicas) and also update the revision annotation in the deployment with the
  126. // latest revision.
  127. if existingNewRS != nil {
  128. rsCopy := existingNewRS.DeepCopy()
  129. // Set existing new replica set's annotation
  130. annotationsUpdated := deploymentutil.SetNewReplicaSetAnnotations(d, rsCopy, newRevision, true, maxRevHistoryLengthInChars)
  131. minReadySecondsNeedsUpdate := rsCopy.Spec.MinReadySeconds != d.Spec.MinReadySeconds
  132. if annotationsUpdated || minReadySecondsNeedsUpdate {
  133. rsCopy.Spec.MinReadySeconds = d.Spec.MinReadySeconds
  134. return dc.client.AppsV1().ReplicaSets(rsCopy.ObjectMeta.Namespace).Update(rsCopy)
  135. }
  136. // Should use the revision in existingNewRS's annotation, since it set by before
  137. needsUpdate := deploymentutil.SetDeploymentRevision(d, rsCopy.Annotations[deploymentutil.RevisionAnnotation])
  138. // If no other Progressing condition has been recorded and we need to estimate the progress
  139. // of this deployment then it is likely that old users started caring about progress. In that
  140. // case we need to take into account the first time we noticed their new replica set.
  141. cond := deploymentutil.GetDeploymentCondition(d.Status, apps.DeploymentProgressing)
  142. if deploymentutil.HasProgressDeadline(d) && cond == nil {
  143. msg := fmt.Sprintf("Found new replica set %q", rsCopy.Name)
  144. condition := deploymentutil.NewDeploymentCondition(apps.DeploymentProgressing, v1.ConditionTrue, deploymentutil.FoundNewRSReason, msg)
  145. deploymentutil.SetDeploymentCondition(&d.Status, *condition)
  146. needsUpdate = true
  147. }
  148. if needsUpdate {
  149. var err error
  150. if d, err = dc.client.AppsV1().Deployments(d.Namespace).UpdateStatus(d); err != nil {
  151. return nil, err
  152. }
  153. }
  154. return rsCopy, nil
  155. }
  156. if !createIfNotExisted {
  157. return nil, nil
  158. }
  159. // new ReplicaSet does not exist, create one.
  160. newRSTemplate := *d.Spec.Template.DeepCopy()
  161. podTemplateSpecHash := controller.ComputeHash(&newRSTemplate, d.Status.CollisionCount)
  162. newRSTemplate.Labels = labelsutil.CloneAndAddLabel(d.Spec.Template.Labels, apps.DefaultDeploymentUniqueLabelKey, podTemplateSpecHash)
  163. // Add podTemplateHash label to selector.
  164. newRSSelector := labelsutil.CloneSelectorAndAddLabel(d.Spec.Selector, apps.DefaultDeploymentUniqueLabelKey, podTemplateSpecHash)
  165. // Create new ReplicaSet
  166. newRS := apps.ReplicaSet{
  167. ObjectMeta: metav1.ObjectMeta{
  168. // Make the name deterministic, to ensure idempotence
  169. Name: d.Name + "-" + podTemplateSpecHash,
  170. Namespace: d.Namespace,
  171. OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(d, controllerKind)},
  172. Labels: newRSTemplate.Labels,
  173. },
  174. Spec: apps.ReplicaSetSpec{
  175. Replicas: new(int32),
  176. MinReadySeconds: d.Spec.MinReadySeconds,
  177. Selector: newRSSelector,
  178. Template: newRSTemplate,
  179. },
  180. }
  181. allRSs := append(oldRSs, &newRS)
  182. newReplicasCount, err := deploymentutil.NewRSNewReplicas(d, allRSs, &newRS)
  183. if err != nil {
  184. return nil, err
  185. }
  186. *(newRS.Spec.Replicas) = newReplicasCount
  187. // Set new replica set's annotation
  188. deploymentutil.SetNewReplicaSetAnnotations(d, &newRS, newRevision, false, maxRevHistoryLengthInChars)
  189. // Create the new ReplicaSet. If it already exists, then we need to check for possible
  190. // hash collisions. If there is any other error, we need to report it in the status of
  191. // the Deployment.
  192. alreadyExists := false
  193. createdRS, err := dc.client.AppsV1().ReplicaSets(d.Namespace).Create(&newRS)
  194. switch {
  195. // We may end up hitting this due to a slow cache or a fast resync of the Deployment.
  196. case errors.IsAlreadyExists(err):
  197. alreadyExists = true
  198. // Fetch a copy of the ReplicaSet.
  199. rs, rsErr := dc.rsLister.ReplicaSets(newRS.Namespace).Get(newRS.Name)
  200. if rsErr != nil {
  201. return nil, rsErr
  202. }
  203. // If the Deployment owns the ReplicaSet and the ReplicaSet's PodTemplateSpec is semantically
  204. // deep equal to the PodTemplateSpec of the Deployment, it's the Deployment's new ReplicaSet.
  205. // Otherwise, this is a hash collision and we need to increment the collisionCount field in
  206. // the status of the Deployment and requeue to try the creation in the next sync.
  207. controllerRef := metav1.GetControllerOf(rs)
  208. if controllerRef != nil && controllerRef.UID == d.UID && deploymentutil.EqualIgnoreHash(&d.Spec.Template, &rs.Spec.Template) {
  209. createdRS = rs
  210. err = nil
  211. break
  212. }
  213. // Matching ReplicaSet is not equal - increment the collisionCount in the DeploymentStatus
  214. // and requeue the Deployment.
  215. if d.Status.CollisionCount == nil {
  216. d.Status.CollisionCount = new(int32)
  217. }
  218. preCollisionCount := *d.Status.CollisionCount
  219. *d.Status.CollisionCount++
  220. // Update the collisionCount for the Deployment and let it requeue by returning the original
  221. // error.
  222. _, dErr := dc.client.AppsV1().Deployments(d.Namespace).UpdateStatus(d)
  223. if dErr == nil {
  224. klog.V(2).Infof("Found a hash collision for deployment %q - bumping collisionCount (%d->%d) to resolve it", d.Name, preCollisionCount, *d.Status.CollisionCount)
  225. }
  226. return nil, err
  227. case err != nil:
  228. msg := fmt.Sprintf("Failed to create new replica set %q: %v", newRS.Name, err)
  229. if deploymentutil.HasProgressDeadline(d) {
  230. cond := deploymentutil.NewDeploymentCondition(apps.DeploymentProgressing, v1.ConditionFalse, deploymentutil.FailedRSCreateReason, msg)
  231. deploymentutil.SetDeploymentCondition(&d.Status, *cond)
  232. // We don't really care about this error at this point, since we have a bigger issue to report.
  233. // TODO: Identify which errors are permanent and switch DeploymentIsFailed to take into account
  234. // these reasons as well. Related issue: https://github.com/kubernetes/kubernetes/issues/18568
  235. _, _ = dc.client.AppsV1().Deployments(d.Namespace).UpdateStatus(d)
  236. }
  237. dc.eventRecorder.Eventf(d, v1.EventTypeWarning, deploymentutil.FailedRSCreateReason, msg)
  238. return nil, err
  239. }
  240. if !alreadyExists && newReplicasCount > 0 {
  241. dc.eventRecorder.Eventf(d, v1.EventTypeNormal, "ScalingReplicaSet", "Scaled up replica set %s to %d", createdRS.Name, newReplicasCount)
  242. }
  243. needsUpdate := deploymentutil.SetDeploymentRevision(d, newRevision)
  244. if !alreadyExists && deploymentutil.HasProgressDeadline(d) {
  245. msg := fmt.Sprintf("Created new replica set %q", createdRS.Name)
  246. condition := deploymentutil.NewDeploymentCondition(apps.DeploymentProgressing, v1.ConditionTrue, deploymentutil.NewReplicaSetReason, msg)
  247. deploymentutil.SetDeploymentCondition(&d.Status, *condition)
  248. needsUpdate = true
  249. }
  250. if needsUpdate {
  251. _, err = dc.client.AppsV1().Deployments(d.Namespace).UpdateStatus(d)
  252. }
  253. return createdRS, err
  254. }
  255. // scale scales proportionally in order to mitigate risk. Otherwise, scaling up can increase the size
  256. // of the new replica set and scaling down can decrease the sizes of the old ones, both of which would
  257. // have the effect of hastening the rollout progress, which could produce a higher proportion of unavailable
  258. // replicas in the event of a problem with the rolled out template. Should run only on scaling events or
  259. // when a deployment is paused and not during the normal rollout process.
  260. func (dc *DeploymentController) scale(deployment *apps.Deployment, newRS *apps.ReplicaSet, oldRSs []*apps.ReplicaSet) error {
  261. // If there is only one active replica set then we should scale that up to the full count of the
  262. // deployment. If there is no active replica set, then we should scale up the newest replica set.
  263. if activeOrLatest := deploymentutil.FindActiveOrLatest(newRS, oldRSs); activeOrLatest != nil {
  264. if *(activeOrLatest.Spec.Replicas) == *(deployment.Spec.Replicas) {
  265. return nil
  266. }
  267. _, _, err := dc.scaleReplicaSetAndRecordEvent(activeOrLatest, *(deployment.Spec.Replicas), deployment)
  268. return err
  269. }
  270. // If the new replica set is saturated, old replica sets should be fully scaled down.
  271. // This case handles replica set adoption during a saturated new replica set.
  272. if deploymentutil.IsSaturated(deployment, newRS) {
  273. for _, old := range controller.FilterActiveReplicaSets(oldRSs) {
  274. if _, _, err := dc.scaleReplicaSetAndRecordEvent(old, 0, deployment); err != nil {
  275. return err
  276. }
  277. }
  278. return nil
  279. }
  280. // There are old replica sets with pods and the new replica set is not saturated.
  281. // We need to proportionally scale all replica sets (new and old) in case of a
  282. // rolling deployment.
  283. if deploymentutil.IsRollingUpdate(deployment) {
  284. allRSs := controller.FilterActiveReplicaSets(append(oldRSs, newRS))
  285. allRSsReplicas := deploymentutil.GetReplicaCountForReplicaSets(allRSs)
  286. allowedSize := int32(0)
  287. if *(deployment.Spec.Replicas) > 0 {
  288. allowedSize = *(deployment.Spec.Replicas) + deploymentutil.MaxSurge(*deployment)
  289. }
  290. // Number of additional replicas that can be either added or removed from the total
  291. // replicas count. These replicas should be distributed proportionally to the active
  292. // replica sets.
  293. deploymentReplicasToAdd := allowedSize - allRSsReplicas
  294. // The additional replicas should be distributed proportionally amongst the active
  295. // replica sets from the larger to the smaller in size replica set. Scaling direction
  296. // drives what happens in case we are trying to scale replica sets of the same size.
  297. // In such a case when scaling up, we should scale up newer replica sets first, and
  298. // when scaling down, we should scale down older replica sets first.
  299. var scalingOperation string
  300. switch {
  301. case deploymentReplicasToAdd > 0:
  302. sort.Sort(controller.ReplicaSetsBySizeNewer(allRSs))
  303. scalingOperation = "up"
  304. case deploymentReplicasToAdd < 0:
  305. sort.Sort(controller.ReplicaSetsBySizeOlder(allRSs))
  306. scalingOperation = "down"
  307. }
  308. // Iterate over all active replica sets and estimate proportions for each of them.
  309. // The absolute value of deploymentReplicasAdded should never exceed the absolute
  310. // value of deploymentReplicasToAdd.
  311. deploymentReplicasAdded := int32(0)
  312. nameToSize := make(map[string]int32)
  313. for i := range allRSs {
  314. rs := allRSs[i]
  315. // Estimate proportions if we have replicas to add, otherwise simply populate
  316. // nameToSize with the current sizes for each replica set.
  317. if deploymentReplicasToAdd != 0 {
  318. proportion := deploymentutil.GetProportion(rs, *deployment, deploymentReplicasToAdd, deploymentReplicasAdded)
  319. nameToSize[rs.Name] = *(rs.Spec.Replicas) + proportion
  320. deploymentReplicasAdded += proportion
  321. } else {
  322. nameToSize[rs.Name] = *(rs.Spec.Replicas)
  323. }
  324. }
  325. // Update all replica sets
  326. for i := range allRSs {
  327. rs := allRSs[i]
  328. // Add/remove any leftovers to the largest replica set.
  329. if i == 0 && deploymentReplicasToAdd != 0 {
  330. leftover := deploymentReplicasToAdd - deploymentReplicasAdded
  331. nameToSize[rs.Name] = nameToSize[rs.Name] + leftover
  332. if nameToSize[rs.Name] < 0 {
  333. nameToSize[rs.Name] = 0
  334. }
  335. }
  336. // TODO: Use transactions when we have them.
  337. if _, _, err := dc.scaleReplicaSet(rs, nameToSize[rs.Name], deployment, scalingOperation); err != nil {
  338. // Return as soon as we fail, the deployment is requeued
  339. return err
  340. }
  341. }
  342. }
  343. return nil
  344. }
  345. func (dc *DeploymentController) scaleReplicaSetAndRecordEvent(rs *apps.ReplicaSet, newScale int32, deployment *apps.Deployment) (bool, *apps.ReplicaSet, error) {
  346. // No need to scale
  347. if *(rs.Spec.Replicas) == newScale {
  348. return false, rs, nil
  349. }
  350. var scalingOperation string
  351. if *(rs.Spec.Replicas) < newScale {
  352. scalingOperation = "up"
  353. } else {
  354. scalingOperation = "down"
  355. }
  356. scaled, newRS, err := dc.scaleReplicaSet(rs, newScale, deployment, scalingOperation)
  357. return scaled, newRS, err
  358. }
  359. func (dc *DeploymentController) scaleReplicaSet(rs *apps.ReplicaSet, newScale int32, deployment *apps.Deployment, scalingOperation string) (bool, *apps.ReplicaSet, error) {
  360. sizeNeedsUpdate := *(rs.Spec.Replicas) != newScale
  361. annotationsNeedUpdate := deploymentutil.ReplicasAnnotationsNeedUpdate(rs, *(deployment.Spec.Replicas), *(deployment.Spec.Replicas)+deploymentutil.MaxSurge(*deployment))
  362. scaled := false
  363. var err error
  364. if sizeNeedsUpdate || annotationsNeedUpdate {
  365. rsCopy := rs.DeepCopy()
  366. *(rsCopy.Spec.Replicas) = newScale
  367. deploymentutil.SetReplicasAnnotations(rsCopy, *(deployment.Spec.Replicas), *(deployment.Spec.Replicas)+deploymentutil.MaxSurge(*deployment))
  368. rs, err = dc.client.AppsV1().ReplicaSets(rsCopy.Namespace).Update(rsCopy)
  369. if err == nil && sizeNeedsUpdate {
  370. scaled = true
  371. dc.eventRecorder.Eventf(deployment, v1.EventTypeNormal, "ScalingReplicaSet", "Scaled %s replica set %s to %d", scalingOperation, rs.Name, newScale)
  372. }
  373. }
  374. return scaled, rs, err
  375. }
  376. // cleanupDeployment is responsible for cleaning up a deployment ie. retains all but the latest N old replica sets
  377. // where N=d.Spec.RevisionHistoryLimit. Old replica sets are older versions of the podtemplate of a deployment kept
  378. // around by default 1) for historical reasons and 2) for the ability to rollback a deployment.
  379. func (dc *DeploymentController) cleanupDeployment(oldRSs []*apps.ReplicaSet, deployment *apps.Deployment) error {
  380. if !deploymentutil.HasRevisionHistoryLimit(deployment) {
  381. return nil
  382. }
  383. // Avoid deleting replica set with deletion timestamp set
  384. aliveFilter := func(rs *apps.ReplicaSet) bool {
  385. return rs != nil && rs.ObjectMeta.DeletionTimestamp == nil
  386. }
  387. cleanableRSes := controller.FilterReplicaSets(oldRSs, aliveFilter)
  388. diff := int32(len(cleanableRSes)) - *deployment.Spec.RevisionHistoryLimit
  389. if diff <= 0 {
  390. return nil
  391. }
  392. sort.Sort(controller.ReplicaSetsByCreationTimestamp(cleanableRSes))
  393. klog.V(4).Infof("Looking to cleanup old replica sets for deployment %q", deployment.Name)
  394. for i := int32(0); i < diff; i++ {
  395. rs := cleanableRSes[i]
  396. // Avoid delete replica set with non-zero replica counts
  397. if rs.Status.Replicas != 0 || *(rs.Spec.Replicas) != 0 || rs.Generation > rs.Status.ObservedGeneration || rs.DeletionTimestamp != nil {
  398. continue
  399. }
  400. klog.V(4).Infof("Trying to cleanup replica set %q for deployment %q", rs.Name, deployment.Name)
  401. if err := dc.client.AppsV1().ReplicaSets(rs.Namespace).Delete(rs.Name, nil); err != nil && !errors.IsNotFound(err) {
  402. // Return error instead of aggregating and continuing DELETEs on the theory
  403. // that we may be overloading the api server.
  404. return err
  405. }
  406. }
  407. return nil
  408. }
  409. // syncDeploymentStatus checks if the status is up-to-date and sync it if necessary
  410. func (dc *DeploymentController) syncDeploymentStatus(allRSs []*apps.ReplicaSet, newRS *apps.ReplicaSet, d *apps.Deployment) error {
  411. newStatus := calculateStatus(allRSs, newRS, d)
  412. if reflect.DeepEqual(d.Status, newStatus) {
  413. return nil
  414. }
  415. newDeployment := d
  416. newDeployment.Status = newStatus
  417. _, err := dc.client.AppsV1().Deployments(newDeployment.Namespace).UpdateStatus(newDeployment)
  418. return err
  419. }
  420. // calculateStatus calculates the latest status for the provided deployment by looking into the provided replica sets.
  421. func calculateStatus(allRSs []*apps.ReplicaSet, newRS *apps.ReplicaSet, deployment *apps.Deployment) apps.DeploymentStatus {
  422. availableReplicas := deploymentutil.GetAvailableReplicaCountForReplicaSets(allRSs)
  423. totalReplicas := deploymentutil.GetReplicaCountForReplicaSets(allRSs)
  424. unavailableReplicas := totalReplicas - availableReplicas
  425. // If unavailableReplicas is negative, then that means the Deployment has more available replicas running than
  426. // desired, e.g. whenever it scales down. In such a case we should simply default unavailableReplicas to zero.
  427. if unavailableReplicas < 0 {
  428. unavailableReplicas = 0
  429. }
  430. status := apps.DeploymentStatus{
  431. // TODO: Ensure that if we start retrying status updates, we won't pick up a new Generation value.
  432. ObservedGeneration: deployment.Generation,
  433. Replicas: deploymentutil.GetActualReplicaCountForReplicaSets(allRSs),
  434. UpdatedReplicas: deploymentutil.GetActualReplicaCountForReplicaSets([]*apps.ReplicaSet{newRS}),
  435. ReadyReplicas: deploymentutil.GetReadyReplicaCountForReplicaSets(allRSs),
  436. AvailableReplicas: availableReplicas,
  437. UnavailableReplicas: unavailableReplicas,
  438. CollisionCount: deployment.Status.CollisionCount,
  439. }
  440. // Copy conditions one by one so we won't mutate the original object.
  441. conditions := deployment.Status.Conditions
  442. for i := range conditions {
  443. status.Conditions = append(status.Conditions, conditions[i])
  444. }
  445. if availableReplicas >= *(deployment.Spec.Replicas)-deploymentutil.MaxUnavailable(*deployment) {
  446. minAvailability := deploymentutil.NewDeploymentCondition(apps.DeploymentAvailable, v1.ConditionTrue, deploymentutil.MinimumReplicasAvailable, "Deployment has minimum availability.")
  447. deploymentutil.SetDeploymentCondition(&status, *minAvailability)
  448. } else {
  449. noMinAvailability := deploymentutil.NewDeploymentCondition(apps.DeploymentAvailable, v1.ConditionFalse, deploymentutil.MinimumReplicasUnavailable, "Deployment does not have minimum availability.")
  450. deploymentutil.SetDeploymentCondition(&status, *noMinAvailability)
  451. }
  452. return status
  453. }
  454. // isScalingEvent checks whether the provided deployment has been updated with a scaling event
  455. // by looking at the desired-replicas annotation in the active replica sets of the deployment.
  456. //
  457. // rsList should come from getReplicaSetsForDeployment(d).
  458. // podMap should come from getPodMapForDeployment(d, rsList).
  459. func (dc *DeploymentController) isScalingEvent(d *apps.Deployment, rsList []*apps.ReplicaSet) (bool, error) {
  460. newRS, oldRSs, err := dc.getAllReplicaSetsAndSyncRevision(d, rsList, false)
  461. if err != nil {
  462. return false, err
  463. }
  464. allRSs := append(oldRSs, newRS)
  465. for _, rs := range controller.FilterActiveReplicaSets(allRSs) {
  466. desired, ok := deploymentutil.GetDesiredReplicasAnnotation(rs)
  467. if !ok {
  468. continue
  469. }
  470. if desired != *(d.Spec.Replicas) {
  471. return true, nil
  472. }
  473. }
  474. return false, nil
  475. }