/*
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 (
"context"
"fmt"
"strconv"
apps "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util"
)
// rollback the deployment to the specified revision. In any case cleanup the rollback spec.
func (dc *DeploymentController) rollback(ctx context.Context, d *apps.Deployment, rsList []*apps.ReplicaSet) error {
logger := klog.FromContext(ctx)
newRS, allOldRSs, err := dc.getAllReplicaSetsAndSyncRevision(ctx, d, rsList, true)
if err != nil {
return err
}
//该函数用于回滚部署到指定的修订版本,并在任何情况下清理回滚规范。
//函数首先从上下文中获取日志记录器,
//然后通过调用getAllReplicaSetsAndSyncRevision方法获取新的副本集、所有旧的副本集,并同步修订版本。
//如果获取过程中出现错误,则返回该错误。
allRSs := append(allOldRSs, newRS)
rollbackTo := getRollbackTo(d)
// If rollback revision is 0, rollback to the last revision
if rollbackTo.Revision == 0 {
if rollbackTo.Revision = deploymentutil.LastRevision(logger, allRSs); rollbackTo.Revision == 0 {
// If we still can't find the last revision, gives up rollback
dc.emitRollbackWarningEvent(d, deploymentutil.RollbackRevisionNotFound, "Unable to find last revision.")
// Gives up rollback
return dc.updateDeploymentAndClearRollbackTo(ctx, d)
}
}
for _, rs := range allRSs {
v, err := deploymentutil.Revision(rs)
if err != nil {
logger.V(4).Info("Unable to extract revision from deployment's replica set", "replicaSet", klog.KObj(rs), "err", err)
continue
}
if v == rollbackTo.Revision {
logger.V(4).Info("Found replica set with desired revision", "replicaSet", klog.KObj(rs), "revision", v)
// rollback by copying podTemplate.Spec from the replica set
// revision number will be incremented during the next getAllReplicaSetsAndSyncRevision call
// no-op if the spec matches current deployment's podTemplate.Spec
performedRollback, err := dc.rollbackToTemplate(ctx, d, rs)
if performedRollback && err == nil {
dc.emitRollbackNormalEvent(d, fmt.Sprintf("Rolled back deployment %q to revision %d", d.Name, rollbackTo.Revision))
}
return err
//该函数是一个Go语言函数,它实现了回滚(rollback)部署的逻辑。
//函数首先将一个新副本集(newRS)添加到所有旧副本集(allOldRSs)中,然后根据提供的参数d获取回滚到的修订版本号(revision number) 。
//如果回滚到的修订版本号为0,则将其回滚到最后一个修订版本号。
//如果无法找到最后一个修订版本号,则放弃回滚并发出警告事件。
//然后,函数遍历所有副本集,并尝试从每个副本集中提取修订版本号。
//如果找到与回滚到的修订版本号匹配的副本集,则通过复制该副本集的podTemplate.Spec来执行回滚操作。
//如果回滚操作成功,则发出正常事件。函数的最后返回值是可能产生的错误。
}
}
dc.emitRollbackWarningEvent(d, deploymentutil.RollbackRevisionNotFound, "Unable to find the revision to rollback to.")
// Gives up rollback
return dc.updateDeploymentAndClearRollbackTo(ctx, d)
}
//该函数是DeploymentController的一个方法,用于回滚部署到指定的修订版本。
//它首先尝试获取所有复制集并同步修订版本,然后根据回滚到的修订版本执行回滚操作。
//如果回滚修订版本为0,则回滚到最后一个修订版本。
//它遍历所有复制集,找到具有所需修订版本的复制集,并从该复制集复制podTemplate.Spec来执行回滚。
//如果找不到要回滚的修订版本,则放弃回滚并清除回滚信息。
// rollbackToTemplate compares the templates of the provided deployment and replica set and
// updates the deployment with the replica set template in case they are different. It also
// cleans up the rollback spec so subsequent requeues of the deployment won't end up in here.
func (dc *DeploymentController) rollbackToTemplate(ctx context.Context, d *apps.Deployment, rs *apps.ReplicaSet) (bool, error) {
logger := klog.FromContext(ctx)
performedRollback := false
if !deploymentutil.EqualIgnoreHash(&d.Spec.Template, &rs.Spec.Template) {
logger.V(4).Info("Rolling back deployment to old template spec", "deployment", klog.KObj(d), "templateSpec", rs.Spec.Template.Spec)
deploymentutil.SetFromReplicaSetTemplate(d, rs.Spec.Template)
// set RS (the old RS we'll rolling back to) annotations back to the deployment;
// otherwise, the deployment's current annotations (should be the same as current new RS) will be copied to the RS after the rollback.
//
// For example,
// A Deployment has old RS1 with annotation {change-cause:create}, and new RS2 {change-cause:edit}.
// Note that both annotations are copied from Deployment, and the Deployment should be annotated {change-cause:edit} as well.
// Now, rollback Deployment to RS1, we should update Deployment's pod-template and also copy annotation from RS1.
// Deployment is now annotated {change-cause:create}, and we have new RS1 {change-cause:create}, old RS2 {change-cause:edit}.
//
// If we don't copy the annotations back from RS to deployment on rollback, the Deployment will stay as {change-cause:edit},
// and new RS1 becomes {change-cause:edit} (copied from deployment after rollback), old RS2 {change-cause:edit}, which is not correct.
deploymentutil.SetDeploymentAnnotationsTo(d, rs)
performedRollback = true
} else {
logger.V(4).Info("Rolling back to a revision that contains the same template as current deployment, skipping rollback...", "deployment", klog.KObj(d))
eventMsg := fmt.Sprintf("The rollback revision contains the same template as current deployment %q", d.Name)
dc.emitRollbackWarningEvent(d, deploymentutil.RollbackTemplateUnchanged, eventMsg)
}
return performedRollback, dc.updateDeploymentAndClearRollbackTo(ctx, d)
}
//该函数用于将部署(deployment)的模板(template)与复制集(replica set)的模板进行比较,
//并在两者不相同的情况下,将部署的模板更新为复制集的模板。
//同时,它还会清理回滚规格,以避免后续重新排队导致回滚。
//函数返回一个布尔值和一个错误,表示是否执行了回滚操作和操作是否出错。
//具体步骤如下:
//1. 获取日志记录器。
//2. 初始化执行了回滚操作的标志为false。
//3. 如果部署的模板与复制集的模板不相同,则执行回滚操作:
//- 记录回滚操作的日志。
//- 将部署的模板设置为复制集的模板。
//- 将复制集的注解设置回部署;否则,部署的当前注解(应与当前新复制集相同)将被复制到回滚后的复制集。
//4. 如果部署的模板与复制集的模板相同,则记录跳过回滚的日志,并发出回滚警告事件。
//5. 返回执行了回滚操作的标志和更新部署并清除回滚规格的结果。
func (dc *DeploymentController) emitRollbackWarningEvent(d *apps.Deployment, reason, message string) {
dc.eventRecorder.Eventf(d, v1.EventTypeWarning, reason, message)
}
func (dc *DeploymentController) emitRollbackNormalEvent(d *apps.Deployment, message string) {
dc.eventRecorder.Eventf(d, v1.EventTypeNormal, deploymentutil.RollbackDone, message)
}
// updateDeploymentAndClearRollbackTo sets .spec.rollbackTo to nil and update the input deployment
// It is assumed that the caller will have updated the deployment template appropriately (in case
// we want to rollback).
func (dc *DeploymentController) updateDeploymentAndClearRollbackTo(ctx context.Context, d *apps.Deployment) error {
logger := klog.FromContext(ctx)
logger.V(4).Info("Cleans up rollbackTo of deployment", "deployment", klog.KObj(d))
setRollbackTo(d, nil)
_, err := dc.client.AppsV1().Deployments(d.Namespace).Update(ctx, d, metav1.UpdateOptions{})
return err
}
//该函数是Go语言编写的,用于更新输入的Deployment对象,并将其.spec.rollbackTo设置为nil。
//函数首先从上下文中获取日志记录器,然后使用日志记录器记录清理Deployment的rollbackTo的信息。
//接下来,它调用setRollbackTo函数将rollbackTo设置为nil。
//最后,它使用client来更新Deployment对象,并返回可能的错误。
// TODO: Remove this when extensions/v1beta1 and apps/v1beta1 Deployment are dropped.
func getRollbackTo(d *apps.Deployment) *extensions.RollbackConfig {
// Extract the annotation used for round-tripping the deprecated RollbackTo field.
revision := d.Annotations[apps.DeprecatedRollbackTo]
if revision == "" {
return nil
}
revision64, err := strconv.ParseInt(revision, 10, 64)
if err != nil {
// If it's invalid, ignore it.
return nil
}
return &extensions.RollbackConfig{
Revision: revision64,
}
}
//该函数用于获取一个代表回滚配置的RollbackConfig对象。
//它从传入的Deployment对象的注解中提取回滚版本号(revision),并将其转换为RollbackConfig对象。
//如果注解不存在或格式无效,则返回nil。
// TODO: Remove this when extensions/v1beta1 and apps/v1beta1 Deployment are dropped.
func setRollbackTo(d *apps.Deployment, rollbackTo *extensions.RollbackConfig) {
if rollbackTo == nil {
delete(d.Annotations, apps.DeprecatedRollbackTo)
return
}
if d.Annotations == nil {
d.Annotations = make(map[string]string)
}
d.Annotations[apps.DeprecatedRollbackTo] = strconv.FormatInt(rollbackTo.Revision, 10)
}
//该函数用于设置Go语言中的Deployment对象的回滚配置。
//它根据传入的rollbackTo参数,将回滚的版本信息保存在Deployment的注解(annotations)中。
//如果rollbackTo为nil,则删除该注解;
//否则,创建或更新该注解,值为回滚的修订版本号。