deployment.go 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  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. "sort"
  16. "strconv"
  17. appsv1 "k8s.io/api/apps/v1"
  18. corev1 "k8s.io/api/core/v1"
  19. apiequality "k8s.io/apimachinery/pkg/api/equality"
  20. "k8s.io/apimachinery/pkg/api/meta"
  21. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  22. "k8s.io/apimachinery/pkg/runtime"
  23. intstrutil "k8s.io/apimachinery/pkg/util/intstr"
  24. appsclient "k8s.io/client-go/kubernetes/typed/apps/v1"
  25. )
  26. const (
  27. // RevisionAnnotation is the revision annotation of a deployment's replica sets which records its rollout sequence
  28. RevisionAnnotation = "deployment.kubernetes.io/revision"
  29. // RevisionHistoryAnnotation maintains the history of all old revisions that a replica set has served for a deployment.
  30. RevisionHistoryAnnotation = "deployment.kubernetes.io/revision-history"
  31. // DesiredReplicasAnnotation is the desired replicas for a deployment recorded as an annotation
  32. // in its replica sets. Helps in separating scaling events from the rollout process and for
  33. // determining if the new replica set for a deployment is really saturated.
  34. DesiredReplicasAnnotation = "deployment.kubernetes.io/desired-replicas"
  35. // MaxReplicasAnnotation is the maximum replicas a deployment can have at a given point, which
  36. // is deployment.spec.replicas + maxSurge. Used by the underlying replica sets to estimate their
  37. // proportions in case the deployment has surge replicas.
  38. MaxReplicasAnnotation = "deployment.kubernetes.io/max-replicas"
  39. // RollbackRevisionNotFound is not found rollback event reason
  40. RollbackRevisionNotFound = "DeploymentRollbackRevisionNotFound"
  41. // RollbackTemplateUnchanged is the template unchanged rollback event reason
  42. RollbackTemplateUnchanged = "DeploymentRollbackTemplateUnchanged"
  43. // RollbackDone is the done rollback event reason
  44. RollbackDone = "DeploymentRollback"
  45. // TimedOutReason is added in a deployment when its newest replica set fails to show any progress
  46. // within the given deadline (progressDeadlineSeconds).
  47. TimedOutReason = "ProgressDeadlineExceeded"
  48. )
  49. // GetDeploymentCondition returns the condition with the provided type.
  50. func GetDeploymentCondition(status appsv1.DeploymentStatus, condType appsv1.DeploymentConditionType) *appsv1.DeploymentCondition {
  51. for i := range status.Conditions {
  52. c := status.Conditions[i]
  53. if c.Type == condType {
  54. return &c
  55. }
  56. }
  57. return nil
  58. }
  59. // Revision returns the revision number of the input object.
  60. func Revision(obj runtime.Object) (int64, error) {
  61. acc, err := meta.Accessor(obj)
  62. if err != nil {
  63. return 0, err
  64. }
  65. v, ok := acc.GetAnnotations()[RevisionAnnotation]
  66. if !ok {
  67. return 0, nil
  68. }
  69. return strconv.ParseInt(v, 10, 64)
  70. }
  71. // GetAllReplicaSets returns the old and new replica sets targeted by the given Deployment. It gets PodList and
  72. // ReplicaSetList from client interface. Note that the first set of old replica sets doesn't include the ones
  73. // with no pods, and the second set of old replica sets include all old replica sets. The third returned value
  74. // is the new replica set, and it may be nil if it doesn't exist yet.
  75. func GetAllReplicaSets(deployment *appsv1.Deployment, c appsclient.AppsV1Interface) ([]*appsv1.ReplicaSet, []*appsv1.ReplicaSet, *appsv1.ReplicaSet, error) {
  76. rsList, err := listReplicaSets(deployment, rsListFromClient(c))
  77. if err != nil {
  78. return nil, nil, nil, err
  79. }
  80. oldRSes, allOldRSes := findOldReplicaSets(deployment, rsList)
  81. newRS := findNewReplicaSet(deployment, rsList)
  82. return oldRSes, allOldRSes, newRS, nil
  83. }
  84. // RsListFromClient returns an rsListFunc that wraps the given client.
  85. func rsListFromClient(c appsclient.AppsV1Interface) rsListFunc {
  86. return func(namespace string, options metav1.ListOptions) ([]*appsv1.ReplicaSet, error) {
  87. rsList, err := c.ReplicaSets(namespace).List(options)
  88. if err != nil {
  89. return nil, err
  90. }
  91. var ret []*appsv1.ReplicaSet
  92. for i := range rsList.Items {
  93. ret = append(ret, &rsList.Items[i])
  94. }
  95. return ret, err
  96. }
  97. }
  98. // TODO: switch this to full namespacers
  99. type rsListFunc func(string, metav1.ListOptions) ([]*appsv1.ReplicaSet, error)
  100. // listReplicaSets returns a slice of RSes the given deployment targets.
  101. // Note that this does NOT attempt to reconcile ControllerRef (adopt/orphan),
  102. // because only the controller itself should do that.
  103. // However, it does filter out anything whose ControllerRef doesn't match.
  104. func listReplicaSets(deployment *appsv1.Deployment, getRSList rsListFunc) ([]*appsv1.ReplicaSet, error) {
  105. // TODO: Right now we list replica sets by their labels. We should list them by selector, i.e. the replica set's selector
  106. // should be a superset of the deployment's selector, see https://github.com/kubernetes/kubernetes/issues/19830.
  107. namespace := deployment.Namespace
  108. selector, err := metav1.LabelSelectorAsSelector(deployment.Spec.Selector)
  109. if err != nil {
  110. return nil, err
  111. }
  112. options := metav1.ListOptions{LabelSelector: selector.String()}
  113. all, err := getRSList(namespace, options)
  114. if err != nil {
  115. return nil, err
  116. }
  117. // Only include those whose ControllerRef matches the Deployment.
  118. owned := make([]*appsv1.ReplicaSet, 0, len(all))
  119. for _, rs := range all {
  120. if metav1.IsControlledBy(rs, deployment) {
  121. owned = append(owned, rs)
  122. }
  123. }
  124. return owned, nil
  125. }
  126. // EqualIgnoreHash returns true if two given podTemplateSpec are equal, ignoring the diff in value of Labels[pod-template-hash]
  127. // We ignore pod-template-hash because:
  128. // 1. The hash result would be different upon podTemplateSpec API changes
  129. // (e.g. the addition of a new field will cause the hash code to change)
  130. // 2. The deployment template won't have hash labels
  131. func equalIgnoreHash(template1, template2 *corev1.PodTemplateSpec) bool {
  132. t1Copy := template1.DeepCopy()
  133. t2Copy := template2.DeepCopy()
  134. // Remove hash labels from template.Labels before comparing
  135. delete(t1Copy.Labels, appsv1.DefaultDeploymentUniqueLabelKey)
  136. delete(t2Copy.Labels, appsv1.DefaultDeploymentUniqueLabelKey)
  137. return apiequality.Semantic.DeepEqual(t1Copy, t2Copy)
  138. }
  139. // FindNewReplicaSet returns the new RS this given deployment targets (the one with the same pod template).
  140. func findNewReplicaSet(deployment *appsv1.Deployment, rsList []*appsv1.ReplicaSet) *appsv1.ReplicaSet {
  141. sort.Sort(replicaSetsByCreationTimestamp(rsList))
  142. for i := range rsList {
  143. if equalIgnoreHash(&rsList[i].Spec.Template, &deployment.Spec.Template) {
  144. // In rare cases, such as after cluster upgrades, Deployment may end up with
  145. // having more than one new ReplicaSets that have the same template as its template,
  146. // see https://github.com/kubernetes/kubernetes/issues/40415
  147. // We deterministically choose the oldest new ReplicaSet.
  148. return rsList[i]
  149. }
  150. }
  151. // new ReplicaSet does not exist.
  152. return nil
  153. }
  154. // replicaSetsByCreationTimestamp sorts a list of ReplicaSet by creation timestamp, using their names as a tie breaker.
  155. type replicaSetsByCreationTimestamp []*appsv1.ReplicaSet
  156. func (o replicaSetsByCreationTimestamp) Len() int { return len(o) }
  157. func (o replicaSetsByCreationTimestamp) Swap(i, j int) { o[i], o[j] = o[j], o[i] }
  158. func (o replicaSetsByCreationTimestamp) Less(i, j int) bool {
  159. if o[i].CreationTimestamp.Equal(&o[j].CreationTimestamp) {
  160. return o[i].Name < o[j].Name
  161. }
  162. return o[i].CreationTimestamp.Before(&o[j].CreationTimestamp)
  163. }
  164. // // FindOldReplicaSets returns the old replica sets targeted by the given Deployment, with the given slice of RSes.
  165. // // 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.
  166. func findOldReplicaSets(deployment *appsv1.Deployment, rsList []*appsv1.ReplicaSet) ([]*appsv1.ReplicaSet, []*appsv1.ReplicaSet) {
  167. var requiredRSs []*appsv1.ReplicaSet
  168. var allRSs []*appsv1.ReplicaSet
  169. newRS := findNewReplicaSet(deployment, rsList)
  170. for _, rs := range rsList {
  171. // Filter out new replica set
  172. if newRS != nil && rs.UID == newRS.UID {
  173. continue
  174. }
  175. allRSs = append(allRSs, rs)
  176. if *(rs.Spec.Replicas) != 0 {
  177. requiredRSs = append(requiredRSs, rs)
  178. }
  179. }
  180. return requiredRSs, allRSs
  181. }
  182. // ResolveFenceposts resolves both maxSurge and maxUnavailable. This needs to happen in one
  183. // step. For example:
  184. //
  185. // 2 desired, max unavailable 1%, surge 0% - should scale old(-1), then new(+1), then old(-1), then new(+1)
  186. // 1 desired, max unavailable 1%, surge 0% - should scale old(-1), then new(+1)
  187. // 2 desired, max unavailable 25%, surge 1% - should scale new(+1), then old(-1), then new(+1), then old(-1)
  188. // 1 desired, max unavailable 25%, surge 1% - should scale new(+1), then old(-1)
  189. // 2 desired, max unavailable 0%, surge 1% - should scale new(+1), then old(-1), then new(+1), then old(-1)
  190. // 1 desired, max unavailable 0%, surge 1% - should scale new(+1), then old(-1)
  191. func ResolveFenceposts(maxSurge, maxUnavailable *intstrutil.IntOrString, desired int32) (int32, int32, error) {
  192. surge, err := intstrutil.GetValueFromIntOrPercent(intstrutil.ValueOrDefault(maxSurge, intstrutil.FromInt(0)), int(desired), true)
  193. if err != nil {
  194. return 0, 0, err
  195. }
  196. unavailable, err := intstrutil.GetValueFromIntOrPercent(intstrutil.ValueOrDefault(maxUnavailable, intstrutil.FromInt(0)), int(desired), false)
  197. if err != nil {
  198. return 0, 0, err
  199. }
  200. if surge == 0 && unavailable == 0 {
  201. // Validation should never allow the user to explicitly use zero values for both maxSurge
  202. // maxUnavailable. Due to rounding down maxUnavailable though, it may resolve to zero.
  203. // If both fenceposts resolve to zero, then we should set maxUnavailable to 1 on the
  204. // theory that surge might not work due to quota.
  205. unavailable = 1
  206. }
  207. return int32(surge), int32(unavailable), nil
  208. }