rollback.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  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 kubectl
  14. import (
  15. "bytes"
  16. "fmt"
  17. "sort"
  18. appsv1 "k8s.io/api/apps/v1"
  19. corev1 "k8s.io/api/core/v1"
  20. apiequality "k8s.io/apimachinery/pkg/api/equality"
  21. "k8s.io/apimachinery/pkg/api/meta"
  22. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  23. "k8s.io/apimachinery/pkg/runtime"
  24. "k8s.io/apimachinery/pkg/runtime/schema"
  25. "k8s.io/apimachinery/pkg/types"
  26. "k8s.io/apimachinery/pkg/util/json"
  27. "k8s.io/apimachinery/pkg/util/strategicpatch"
  28. "k8s.io/client-go/kubernetes"
  29. kapps "k8s.io/kubernetes/pkg/kubectl/apps"
  30. "k8s.io/kubernetes/pkg/kubectl/scheme"
  31. deploymentutil "k8s.io/kubernetes/pkg/kubectl/util/deployment"
  32. )
  33. const (
  34. rollbackSuccess = "rolled back"
  35. rollbackSkipped = "skipped rollback"
  36. )
  37. // Rollbacker provides an interface for resources that can be rolled back.
  38. type Rollbacker interface {
  39. Rollback(obj runtime.Object, updatedAnnotations map[string]string, toRevision int64, dryRun bool) (string, error)
  40. }
  41. type RollbackVisitor struct {
  42. clientset kubernetes.Interface
  43. result Rollbacker
  44. }
  45. func (v *RollbackVisitor) VisitDeployment(elem kapps.GroupKindElement) {
  46. v.result = &DeploymentRollbacker{v.clientset}
  47. }
  48. func (v *RollbackVisitor) VisitStatefulSet(kind kapps.GroupKindElement) {
  49. v.result = &StatefulSetRollbacker{v.clientset}
  50. }
  51. func (v *RollbackVisitor) VisitDaemonSet(kind kapps.GroupKindElement) {
  52. v.result = &DaemonSetRollbacker{v.clientset}
  53. }
  54. func (v *RollbackVisitor) VisitJob(kind kapps.GroupKindElement) {}
  55. func (v *RollbackVisitor) VisitPod(kind kapps.GroupKindElement) {}
  56. func (v *RollbackVisitor) VisitReplicaSet(kind kapps.GroupKindElement) {}
  57. func (v *RollbackVisitor) VisitReplicationController(kind kapps.GroupKindElement) {}
  58. func (v *RollbackVisitor) VisitCronJob(kind kapps.GroupKindElement) {}
  59. // RollbackerFor returns an implementation of Rollbacker interface for the given schema kind
  60. func RollbackerFor(kind schema.GroupKind, c kubernetes.Interface) (Rollbacker, error) {
  61. elem := kapps.GroupKindElement(kind)
  62. visitor := &RollbackVisitor{
  63. clientset: c,
  64. }
  65. err := elem.Accept(visitor)
  66. if err != nil {
  67. return nil, fmt.Errorf("error retrieving rollbacker for %q, %v", kind.String(), err)
  68. }
  69. if visitor.result == nil {
  70. return nil, fmt.Errorf("no rollbacker has been implemented for %q", kind)
  71. }
  72. return visitor.result, nil
  73. }
  74. type DeploymentRollbacker struct {
  75. c kubernetes.Interface
  76. }
  77. func (r *DeploymentRollbacker) Rollback(obj runtime.Object, updatedAnnotations map[string]string, toRevision int64, dryRun bool) (string, error) {
  78. if toRevision < 0 {
  79. return "", revisionNotFoundErr(toRevision)
  80. }
  81. accessor, err := meta.Accessor(obj)
  82. if err != nil {
  83. return "", fmt.Errorf("failed to create accessor for kind %v: %s", obj.GetObjectKind(), err.Error())
  84. }
  85. name := accessor.GetName()
  86. namespace := accessor.GetNamespace()
  87. // TODO: Fix this after kubectl has been removed from core. It is not possible to convert the runtime.Object
  88. // to the external appsv1 Deployment without round-tripping through an internal version of Deployment. We're
  89. // currently getting rid of all internal versions of resources. So we specifically request the appsv1 version
  90. // here. This follows the same pattern as for DaemonSet and StatefulSet.
  91. deployment, err := r.c.AppsV1().Deployments(namespace).Get(name, metav1.GetOptions{})
  92. if err != nil {
  93. return "", fmt.Errorf("failed to retrieve Deployment %s: %v", name, err)
  94. }
  95. rsForRevision, err := deploymentRevision(deployment, r.c, toRevision)
  96. if err != nil {
  97. return "", err
  98. }
  99. if dryRun {
  100. return printTemplate(&rsForRevision.Spec.Template)
  101. }
  102. if deployment.Spec.Paused {
  103. return "", fmt.Errorf("you cannot rollback a paused deployment; resume it first with 'kubectl rollout resume deployment/%s' and try again", name)
  104. }
  105. // Skip if the revision already matches current Deployment
  106. if equalIgnoreHash(&rsForRevision.Spec.Template, &deployment.Spec.Template) {
  107. return fmt.Sprintf("%s (current template already matches revision %d)", rollbackSkipped, toRevision), nil
  108. }
  109. // remove hash label before patching back into the deployment
  110. delete(rsForRevision.Spec.Template.Labels, appsv1.DefaultDeploymentUniqueLabelKey)
  111. // compute deployment annotations
  112. annotations := map[string]string{}
  113. for k := range annotationsToSkip {
  114. if v, ok := deployment.Annotations[k]; ok {
  115. annotations[k] = v
  116. }
  117. }
  118. for k, v := range rsForRevision.Annotations {
  119. if !annotationsToSkip[k] {
  120. annotations[k] = v
  121. }
  122. }
  123. // make patch to restore
  124. patchType, patch, err := getDeploymentPatch(&rsForRevision.Spec.Template, annotations)
  125. if err != nil {
  126. return "", fmt.Errorf("failed restoring revision %d: %v", toRevision, err)
  127. }
  128. // Restore revision
  129. if _, err = r.c.AppsV1().Deployments(namespace).Patch(name, patchType, patch); err != nil {
  130. return "", fmt.Errorf("failed restoring revision %d: %v", toRevision, err)
  131. }
  132. return rollbackSuccess, nil
  133. }
  134. // equalIgnoreHash returns true if two given podTemplateSpec are equal, ignoring the diff in value of Labels[pod-template-hash]
  135. // We ignore pod-template-hash because:
  136. // 1. The hash result would be different upon podTemplateSpec API changes
  137. // (e.g. the addition of a new field will cause the hash code to change)
  138. // 2. The deployment template won't have hash labels
  139. func equalIgnoreHash(template1, template2 *corev1.PodTemplateSpec) bool {
  140. t1Copy := template1.DeepCopy()
  141. t2Copy := template2.DeepCopy()
  142. // Remove hash labels from template.Labels before comparing
  143. delete(t1Copy.Labels, appsv1.DefaultDeploymentUniqueLabelKey)
  144. delete(t2Copy.Labels, appsv1.DefaultDeploymentUniqueLabelKey)
  145. return apiequality.Semantic.DeepEqual(t1Copy, t2Copy)
  146. }
  147. // annotationsToSkip lists the annotations that should be preserved from the deployment and not
  148. // copied from the replicaset when rolling a deployment back
  149. var annotationsToSkip = map[string]bool{
  150. corev1.LastAppliedConfigAnnotation: true,
  151. deploymentutil.RevisionAnnotation: true,
  152. deploymentutil.RevisionHistoryAnnotation: true,
  153. deploymentutil.DesiredReplicasAnnotation: true,
  154. deploymentutil.MaxReplicasAnnotation: true,
  155. appsv1.DeprecatedRollbackTo: true,
  156. }
  157. // getPatch returns a patch that can be applied to restore a Deployment to a
  158. // previous version. If the returned error is nil the patch is valid.
  159. func getDeploymentPatch(podTemplate *corev1.PodTemplateSpec, annotations map[string]string) (types.PatchType, []byte, error) {
  160. // Create a patch of the Deployment that replaces spec.template
  161. patch, err := json.Marshal([]interface{}{
  162. map[string]interface{}{
  163. "op": "replace",
  164. "path": "/spec/template",
  165. "value": podTemplate,
  166. },
  167. map[string]interface{}{
  168. "op": "replace",
  169. "path": "/metadata/annotations",
  170. "value": annotations,
  171. },
  172. })
  173. return types.JSONPatchType, patch, err
  174. }
  175. func deploymentRevision(deployment *appsv1.Deployment, c kubernetes.Interface, toRevision int64) (revision *appsv1.ReplicaSet, err error) {
  176. _, allOldRSs, newRS, err := deploymentutil.GetAllReplicaSets(deployment, c.AppsV1())
  177. if err != nil {
  178. return nil, fmt.Errorf("failed to retrieve replica sets from deployment %s: %v", deployment.Name, err)
  179. }
  180. allRSs := allOldRSs
  181. if newRS != nil {
  182. allRSs = append(allRSs, newRS)
  183. }
  184. var (
  185. latestReplicaSet *appsv1.ReplicaSet
  186. latestRevision = int64(-1)
  187. previousReplicaSet *appsv1.ReplicaSet
  188. previousRevision = int64(-1)
  189. )
  190. for _, rs := range allRSs {
  191. if v, err := deploymentutil.Revision(rs); err == nil {
  192. if toRevision == 0 {
  193. if latestRevision < v {
  194. // newest one we've seen so far
  195. previousRevision = latestRevision
  196. previousReplicaSet = latestReplicaSet
  197. latestRevision = v
  198. latestReplicaSet = rs
  199. } else if previousRevision < v {
  200. // second newest one we've seen so far
  201. previousRevision = v
  202. previousReplicaSet = rs
  203. }
  204. } else if toRevision == v {
  205. return rs, nil
  206. }
  207. }
  208. }
  209. if toRevision > 0 {
  210. return nil, revisionNotFoundErr(toRevision)
  211. }
  212. if previousReplicaSet == nil {
  213. return nil, fmt.Errorf("no rollout history found for deployment %q", deployment.Name)
  214. }
  215. return previousReplicaSet, nil
  216. }
  217. type DaemonSetRollbacker struct {
  218. c kubernetes.Interface
  219. }
  220. func (r *DaemonSetRollbacker) Rollback(obj runtime.Object, updatedAnnotations map[string]string, toRevision int64, dryRun bool) (string, error) {
  221. if toRevision < 0 {
  222. return "", revisionNotFoundErr(toRevision)
  223. }
  224. accessor, err := meta.Accessor(obj)
  225. if err != nil {
  226. return "", fmt.Errorf("failed to create accessor for kind %v: %s", obj.GetObjectKind(), err.Error())
  227. }
  228. ds, history, err := daemonSetHistory(r.c.AppsV1(), accessor.GetNamespace(), accessor.GetName())
  229. if err != nil {
  230. return "", err
  231. }
  232. if toRevision == 0 && len(history) <= 1 {
  233. return "", fmt.Errorf("no last revision to roll back to")
  234. }
  235. toHistory := findHistory(toRevision, history)
  236. if toHistory == nil {
  237. return "", revisionNotFoundErr(toRevision)
  238. }
  239. if dryRun {
  240. appliedDS, err := applyDaemonSetHistory(ds, toHistory)
  241. if err != nil {
  242. return "", err
  243. }
  244. return printPodTemplate(&appliedDS.Spec.Template)
  245. }
  246. // Skip if the revision already matches current DaemonSet
  247. done, err := daemonSetMatch(ds, toHistory)
  248. if err != nil {
  249. return "", err
  250. }
  251. if done {
  252. return fmt.Sprintf("%s (current template already matches revision %d)", rollbackSkipped, toRevision), nil
  253. }
  254. // Restore revision
  255. if _, err = r.c.AppsV1().DaemonSets(accessor.GetNamespace()).Patch(accessor.GetName(), types.StrategicMergePatchType, toHistory.Data.Raw); err != nil {
  256. return "", fmt.Errorf("failed restoring revision %d: %v", toRevision, err)
  257. }
  258. return rollbackSuccess, nil
  259. }
  260. // daemonMatch check if the given DaemonSet's template matches the template stored in the given history.
  261. func daemonSetMatch(ds *appsv1.DaemonSet, history *appsv1.ControllerRevision) (bool, error) {
  262. patch, err := getDaemonSetPatch(ds)
  263. if err != nil {
  264. return false, err
  265. }
  266. return bytes.Equal(patch, history.Data.Raw), nil
  267. }
  268. // getPatch returns a strategic merge patch that can be applied to restore a Daemonset to a
  269. // previous version. If the returned error is nil the patch is valid. The current state that we save is just the
  270. // PodSpecTemplate. We can modify this later to encompass more state (or less) and remain compatible with previously
  271. // recorded patches.
  272. func getDaemonSetPatch(ds *appsv1.DaemonSet) ([]byte, error) {
  273. dsBytes, err := json.Marshal(ds)
  274. if err != nil {
  275. return nil, err
  276. }
  277. var raw map[string]interface{}
  278. err = json.Unmarshal(dsBytes, &raw)
  279. if err != nil {
  280. return nil, err
  281. }
  282. objCopy := make(map[string]interface{})
  283. specCopy := make(map[string]interface{})
  284. // Create a patch of the DaemonSet that replaces spec.template
  285. spec := raw["spec"].(map[string]interface{})
  286. template := spec["template"].(map[string]interface{})
  287. specCopy["template"] = template
  288. template["$patch"] = "replace"
  289. objCopy["spec"] = specCopy
  290. patch, err := json.Marshal(objCopy)
  291. return patch, err
  292. }
  293. type StatefulSetRollbacker struct {
  294. c kubernetes.Interface
  295. }
  296. // toRevision is a non-negative integer, with 0 being reserved to indicate rolling back to previous configuration
  297. func (r *StatefulSetRollbacker) Rollback(obj runtime.Object, updatedAnnotations map[string]string, toRevision int64, dryRun bool) (string, error) {
  298. if toRevision < 0 {
  299. return "", revisionNotFoundErr(toRevision)
  300. }
  301. accessor, err := meta.Accessor(obj)
  302. if err != nil {
  303. return "", fmt.Errorf("failed to create accessor for kind %v: %s", obj.GetObjectKind(), err.Error())
  304. }
  305. sts, history, err := statefulSetHistory(r.c.AppsV1(), accessor.GetNamespace(), accessor.GetName())
  306. if err != nil {
  307. return "", err
  308. }
  309. if toRevision == 0 && len(history) <= 1 {
  310. return "", fmt.Errorf("no last revision to roll back to")
  311. }
  312. toHistory := findHistory(toRevision, history)
  313. if toHistory == nil {
  314. return "", revisionNotFoundErr(toRevision)
  315. }
  316. if dryRun {
  317. appliedSS, err := applyRevision(sts, toHistory)
  318. if err != nil {
  319. return "", err
  320. }
  321. return printPodTemplate(&appliedSS.Spec.Template)
  322. }
  323. // Skip if the revision already matches current StatefulSet
  324. done, err := statefulsetMatch(sts, toHistory)
  325. if err != nil {
  326. return "", err
  327. }
  328. if done {
  329. return fmt.Sprintf("%s (current template already matches revision %d)", rollbackSkipped, toRevision), nil
  330. }
  331. // Restore revision
  332. if _, err = r.c.AppsV1().StatefulSets(sts.Namespace).Patch(sts.Name, types.StrategicMergePatchType, toHistory.Data.Raw); err != nil {
  333. return "", fmt.Errorf("failed restoring revision %d: %v", toRevision, err)
  334. }
  335. return rollbackSuccess, nil
  336. }
  337. var appsCodec = scheme.Codecs.LegacyCodec(appsv1.SchemeGroupVersion)
  338. // applyRevision returns a new StatefulSet constructed by restoring the state in revision to set. If the returned error
  339. // is nil, the returned StatefulSet is valid.
  340. func applyRevision(set *appsv1.StatefulSet, revision *appsv1.ControllerRevision) (*appsv1.StatefulSet, error) {
  341. clone := set.DeepCopy()
  342. patched, err := strategicpatch.StrategicMergePatch([]byte(runtime.EncodeOrDie(appsCodec, clone)), revision.Data.Raw, clone)
  343. if err != nil {
  344. return nil, err
  345. }
  346. err = json.Unmarshal(patched, clone)
  347. if err != nil {
  348. return nil, err
  349. }
  350. return clone, nil
  351. }
  352. // statefulsetMatch check if the given StatefulSet's template matches the template stored in the given history.
  353. func statefulsetMatch(ss *appsv1.StatefulSet, history *appsv1.ControllerRevision) (bool, error) {
  354. patch, err := getStatefulSetPatch(ss)
  355. if err != nil {
  356. return false, err
  357. }
  358. return bytes.Equal(patch, history.Data.Raw), nil
  359. }
  360. // getStatefulSetPatch returns a strategic merge patch that can be applied to restore a StatefulSet to a
  361. // previous version. If the returned error is nil the patch is valid. The current state that we save is just the
  362. // PodSpecTemplate. We can modify this later to encompass more state (or less) and remain compatible with previously
  363. // recorded patches.
  364. func getStatefulSetPatch(set *appsv1.StatefulSet) ([]byte, error) {
  365. str, err := runtime.Encode(appsCodec, set)
  366. if err != nil {
  367. return nil, err
  368. }
  369. var raw map[string]interface{}
  370. if err := json.Unmarshal([]byte(str), &raw); err != nil {
  371. return nil, err
  372. }
  373. objCopy := make(map[string]interface{})
  374. specCopy := make(map[string]interface{})
  375. spec := raw["spec"].(map[string]interface{})
  376. template := spec["template"].(map[string]interface{})
  377. specCopy["template"] = template
  378. template["$patch"] = "replace"
  379. objCopy["spec"] = specCopy
  380. patch, err := json.Marshal(objCopy)
  381. return patch, err
  382. }
  383. // findHistory returns a controllerrevision of a specific revision from the given controllerrevisions.
  384. // It returns nil if no such controllerrevision exists.
  385. // If toRevision is 0, the last previously used history is returned.
  386. func findHistory(toRevision int64, allHistory []*appsv1.ControllerRevision) *appsv1.ControllerRevision {
  387. if toRevision == 0 && len(allHistory) <= 1 {
  388. return nil
  389. }
  390. // Find the history to rollback to
  391. var toHistory *appsv1.ControllerRevision
  392. if toRevision == 0 {
  393. // If toRevision == 0, find the latest revision (2nd max)
  394. sort.Sort(historiesByRevision(allHistory))
  395. toHistory = allHistory[len(allHistory)-2]
  396. } else {
  397. for _, h := range allHistory {
  398. if h.Revision == toRevision {
  399. // If toRevision != 0, find the history with matching revision
  400. return h
  401. }
  402. }
  403. }
  404. return toHistory
  405. }
  406. // printPodTemplate converts a given pod template into a human-readable string.
  407. func printPodTemplate(specTemplate *corev1.PodTemplateSpec) (string, error) {
  408. podSpec, err := printTemplate(specTemplate)
  409. if err != nil {
  410. return "", err
  411. }
  412. return fmt.Sprintf("will roll back to %s", podSpec), nil
  413. }
  414. func revisionNotFoundErr(r int64) error {
  415. return fmt.Errorf("unable to find specified revision %v in history", r)
  416. }
  417. // TODO: copied from daemon controller, should extract to a library
  418. type historiesByRevision []*appsv1.ControllerRevision
  419. func (h historiesByRevision) Len() int { return len(h) }
  420. func (h historiesByRevision) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
  421. func (h historiesByRevision) Less(i, j int) bool {
  422. return h[i].Revision < h[j].Revision
  423. }