k8s源码解析 - apply命令的实现

create与apply

kubectl create 和apply命令的区别如下:
在这里插入图片描述

(1)kubectl create:kubectl create命令可创建新资源。如果再次运行该命令,则会抛出错误,因为资源名称在名称空间中应该是唯一的。根据yaml文件生成新的资源,所以要求yaml文件中的配置必须是完整的。

(2)kubectl apply:将配置应用于资源。 如果资源不在那里,那么它将被创建。 kubectl apply命令可以第二次运行,因为它只是应用如下所示的配置。 在这种情况下,配置没有改变。 所以,资源没有改变。

(3)kubectl apply:根据配置文件里面列出来的内容,可只更新现有的资源配置。所以yaml文件的内容可以只写需要升级的属性。

(4)kubectl replace:使用配置文件或stdin来替换资源。支持JSON和YAML格式。如果替换当前资源,则必须提供完整的资源规范。


Server-Side-Apply

Kubernetes 是一个声明式的资源管理系统。用户在本地定义期望的状态,然后通过 kubectl apply 去跟更新当前集群状态中被用户指定的那一部分。然而它远没有听起来那么简单。原来的 kubectl apply 是基于客户端实现的。Apply 的时候不能简单地替换掉单个资源的整体状态,因为还有其他人也会去更改资源,比如 controllers、admissions、webhooks。那么怎样保证在改一个资源的同时,不会覆盖掉别人的改动呢?

three way merge:用户把 last applied state 存在 Pod annotations 里,在下次 apply 的时候根据 (最新状态,last applied,用户指定状态) 做 3 way diff,然后生成 patch 发送到 APIServer。但是这样做还是有问题!Apply 的初衷是让个体去指定哪些资源字段归他管理。但是原有实现既不能阻止不同个体之间互相篡改字段,也没有在冲突发生时告知用户和解决。举个例子, CoreOS 产品里自带的 controller 和用户都会去更改 Node 对象的一些特殊 labels,结果出现冲突,导致集群出故障了只能派人去修。

而现在我们终于迎来了胜利的曙光——那就是服务器端 apply。APIServer 会做 diff 和 merge 操作,很多原本易碎的现象都得到了解决。更重要的是,相比于原来用 last-applied annotations,服务器端 apply 新提供了一种声明式 API (叫 ManagedFields) 来明确指定谁管理哪些资源字段。而当发生冲突时,比如 kubectl 和 controller 都改同一个字段时,非 Admin(管理员)的请求会返回错误并且提示去解决。1.6后的新版本将跟踪并管理所有新Kubernetes对象的字段变更,确保用户及时了解哪些资源在何时进行过更改。这样一来,不同组件同时更改同一资源将会变得更加安全可靠。使用服务器端Apply实现的合并策略通常提供了更稳定的对象生命周期。服务器端Apply试图根据谁管理字段来合并字段,而不是仅仅根据值来否决字段。这样做的目的是通过减少意外干扰,使多个参与者更新同一个对象更容易、更稳定。


apply命令的底层实现

1、获取需要apply的资源objs, 发送到服务端(k8s的apiserver)。 因为一个yaml文件中可有多个资源创建,因此需要循环遍历objs进行apply。 关于如何使用户输入的yaml文件转换为资源的obj输入到apply的run函数中,请参考下一章节。

//代码路径 /k8s.io/kubectl/pkg/cmd/apply/apply.go
// Run executes the `apply` command.
func (o *ApplyOptions) Run() error {
....
	// Generates the objects using the resource builder if they have not
	// already been stored by calling "SetObjects()" in the pre-processor.
	infos, err := o.GetObjects()
	if err != nil {
		return err
	}
	if len(infos) == 0 {
		return fmt.Errorf("no objects passed to apply")
	}
	for _, info := range infos {

		o.MarkNamespaceVisited(info)

		if err := o.Recorder.Record(info.Object); err != nil {
			klog.V(4).Infof("error recording current command: %v", err)
		}

2、若为ServerSideApply时, 直接patch数据。

		if o.ServerSideApply {
			// Send the full object to be applied on the server side.
			data, err := runtime.Encode(unstructured.UnstructuredJSONScheme, info.Object)
			if err != nil {
				return cmdutil.AddSourceToErr("serverside-apply", info.Source, err)
			}

			options := metav1.PatchOptions{
				Force:        &o.ForceConflicts,
				FieldManager: o.FieldManager,
			}

2.1、patch之前可以先 dryrun 进行校验,没问题再真正执行,dryrun 会将需要做的步骤模拟执行并输出,不会真正记录操作。dry中每个阶段均正常运行,最后一个存储阶段除外。运行准入控制器以检查请求是否有效,所做的更改不会持久保存到存储中,但是将被持久保存的最终对象以及正常状态代码仍会返回给用户。

2.2、若为ServerSideApply时, 由资源映射器mapping和客户端client构建helper(Helper提供了检索或改变RESTful资源的方法)。调用helper的Patch方法通过向apiServer发送patch请求更新资源对象并返回。判断服务端apply命令是否可执行。

			helper := resource.NewHelper(info.Client, info.Mapping)
			if o.DryRunStrategy == cmdutil.DryRunServer {
				if err := o.DryRunVerifier.HasSupport(info.Mapping.GroupVersionKind); err != nil {
					return err
				}
				helper.DryRun(true)
			}
			
			obj, err := helper.Patch(
				info.Namespace,
				info.Name,
				types.ApplyPatchType,
				data,
				&options,
			)
			if err != nil {
				if isIncompatibleServerError(err) {
					err = fmt.Errorf("Server-side apply not available on the server: (%v)", err)
				}

3、若不为ServerSideApply时。查询需要apply的obj资源名(kind-ns-name),是否在k8s中已经创建。apply命令用来判断是更新资源还是创建资源。资源是新创建的,跳过three-way merge,直接创建即可。 如下是创建资源:

		if err := info.Get(); err != nil {
			if !errors.IsNotFound(err) {
				return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving current configuration of:\n%s\nfrom server for:", info.String()), info.Source, err)
			}

			// Create the resource if it doesn't exist
			// First, update the annotation used by kubectl apply
			if err := util.CreateApplyAnnotation(info.Object, unstructured.UnstructuredJSONScheme); err != nil {
				return cmdutil.AddSourceToErr("creating", info.Source, err)
			}

			if o.DryRunStrategy != cmdutil.DryRunClient {
				// Then create the resource and skip the three-way merge
				helper := resource.NewHelper(info.Client, info.Mapping)
				if o.DryRunStrategy == cmdutil.DryRunServer {
					if err := o.DryRunVerifier.HasSupport(info.Mapping.GroupVersionKind); err != nil {
						return cmdutil.AddSourceToErr("creating", info.Source, err)
					}
					helper.DryRun(true)
				}
				obj, err := helper.Create(info.Namespace, true, info.Object)
				if err != nil {
					return cmdutil.AddSourceToErr("creating", info.Source, err)
				}
				info.Refresh(obj, true)
			}

			if err := o.MarkObjectVisited(info); err != nil {
				return err
			}

			if o.shouldPrintObject() {
				continue
			}

			printer, err := o.ToPrinter("created")
			if err != nil {
				return err
			}
			if err = printer.PrintObj(info.Object, o.Out); err != nil {
				return err
			}
			continue
		}

4.、apply命令更新资源,采用three way merge的策略用patch方法对资源进行更新

		if err := o.MarkObjectVisited(info); err != nil {
			return err
		}

		if o.DryRunStrategy != cmdutil.DryRunClient {
			metadata, _ := meta.Accessor(info.Object)
			annotationMap := metadata.GetAnnotations()
			if _, ok := annotationMap[corev1.LastAppliedConfigAnnotation]; !ok {
				fmt.Fprintf(o.ErrOut, warningNoLastAppliedConfigAnnotation, o.cmdBaseName)
			}

			patcher, err := newPatcher(o, info)
			if err != nil {
				return err
			}
			patchBytes, patchedObject, err := patcher.Patch(info.Object, modified, info.Source, info.Namespace, info.Name, o.ErrOut)
			if err != nil {
				return cmdutil.AddSourceToErr(fmt.Sprintf("applying patch:\n%s\nto:\n%v\nfor:", patchBytes, info), info.Source, err)
			}

			info.Refresh(patchedObject, true)

			if string(patchBytes) == "{}" && !o.shouldPrintObject() {
				printer, err := o.ToPrinter("unchanged")
				if err != nil {
					return err
				}
				if err = printer.PrintObj(info.Object, o.Out); err != nil {
					return err
				}
				continue
			}
		}

		if o.shouldPrintObject() {
			continue
		}

		printer, err := o.ToPrinter("configured")
		if err != nil {
			return err
		}
		if err = printer.PrintObj(info.Object, o.Out); err != nil {
			return err
		}
	}

	if o.PostProcessorFn != nil {
		klog.V(4).Infof("Running apply post-processor function")
		if err := o.PostProcessorFn(); err != nil {
			return err
		}
	}

	return nil
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值