1.背景在KubeNest这个云原生应用发布平台中,traits代表与应用相关的运维动作。例如,DNStraits用于解决不同环境下的问题。修改运维应用主要工作负载的DNS配置。KubeNest中已经有DNStrait等10多个trait,并且随着业务需求的增加,数量还在不断增加。在原有的KubeNest技术中,每个trait所需要的运维逻辑都是通过Operator来实现的。随着Traits的不断增加,Traits的开发和运维也存在一些问题:不必要的重构:在一个应用发布过程中,可能会涉及到多个Traits。这些特征由Operator实现以修改工作负载。每次修改都会导致pod重构。在实际生产过程中,pod重构要尽量避免高昂的开发成本:一个trait的新开发需要通过创建operator应用来创建。实现,虽然可以使用kubebuidler开发框架来简化开发,但还是需要几天的时间才能完成,需要开发者了解Operaotr的开发机制。对于有一定语言功底的开发者,运维成本高:trait太多,一旦涉及到公共逻辑代码修改时(比如给status增加一个字段),需要修改n个trait项目,同时需要升级m个集群,维护成本会是o(n^2)资源浪费:每个trait是一个单独的应用,应用部署最低配置为1核1G多副本。但是内部执行简单的转换逻辑只需要100M左右,所以这些traits实际上造成了很大的资源浪费。代码不规范:在trait共建方面,KubeNest只规定了输入输出标准,用户可以自定义trait开发,也容易因为代码不规范导致bug不一致:面对相同的输入,Operator代码中不正确的逻辑可能会导致输出数据顺序不同,这种不一致很容易导致pod重建。比如更改容忍顺序会导致pod重建,这是无效的。为了解决这些问题,我们不仅要规范性状的输入和输出,还要进一步优化性状的开发。2、在KubeOne的旧架构中,traits不直接操作工作负载。这些traits中90%都是patch类型的traits,大部分traits的逻辑只是简单的逻辑转换。下面介绍老补丁类型的traits实现架构。1.基于TraitOperator的patchclasstrait具体架构如下:如上图所示,traitOperator接受traitCR进行运维逻辑处理,然后直接将运维动作打补丁到工作负载上以YAML片段的形式。traitCR包含两个部分数据:applicationdata:用户不需要关心但Operator逻辑处理需要的应用相关数据,存在于metadata的注解数据中,比如apiVersion和kindofworkload,申请时需要的。用户数据:屏蔽k8s白屏显示traitCR的spec数据中的用户数据。下面用tolerationtrait来说明:tolerationtraitCR(用户选择底层资源)apiVersion:apps.kubeone.alibaba-inc.com/v1kind:TolerationInjectormetadata:annotations:kubeone.ali/workload-api-version:apps。kruise.io/v1alpha1#applicationdatakubeone.ali/workload-kind:StatefulSet#applicationdata...#userdataspec:parameters:sigma.ali/is-ecs:"true"sigma.ali/resource-pool:"example"theCR表示用户希望将pod放置在标记为sigma.ali/is-ecs:"true"和sigma.ali/resource-pool:"example"的节点上。将tolerationtrait生成的YAML分片根据用户在traitCR中的输入转化为YAML分片,然后将YAML分片直接patch到工作负载上,完成运维操作。#YAML片段apiVersion:apps.kruise.io/v1alpha1kind:StatefulSetmetadata:name:sts-examplenamespace:ns-examplespec:template:spec:tolerations:-effect:NoSchedulekey:sigma.ali/resource-poooloperator:Equalvalue:example-effect:NoSchedulekey:sigma.ali/is-ecsoperator:Equalvalue:'true'2.风险点顺序导致重建。原来的statefulset和Openkruisestatefulset中,YAML内容的顺序不一样,也会导致重启。因此,在旧的架构中,trait除了要注意输入参数的值外,还需要注意参数的顺序。当参数的顺序不同时,trait生成的YAML片段的顺序也会不同。对工作负载打补丁时,会触发工作负载重启,可能带来pod重构失败的风险。多个应用程序导致重建。在一个发布过程中,可能会有多个trait应用运维操作。这时候多个applyworkload会导致pod被多次rebuild。从生产安全的角度出发,用户希望podrebuild的次数越少越好。3.trait配置开发框架从前面的架构我们可以看出trait算子其实是一个转换器,将用户数据和应用数据映射成一个YAML片段,然后打补丁到工作负载上。原来的convert逻辑是用operator实现的,效率低,浪费资源。现在使用基于配置的转换解决方案来完成特征逻辑开发。1.Trait开发框架从上图可以看出框架分为两部分:Trait转换:Trait实现使用UniversalOperator通过数据+配置模板=YAML片段生成YAMLFragment,YAMLFragment会通过ConfigMap进行存储。应用:将聚合发布期间生成的所有YAML片段,然后将补丁应用于应用程序工作负载,从而避免多次重建。下面重点介绍可配置的“TraitConvert”的设计:traitCR:用户提交的运维动作,包括应用数据(withmetadata)和用户数据(withspec),可以参考示例trait定义oftolerationtraitCR:去operator配置运维逻辑,本质上是一个YAMLname:数据的标签,在模板中,使用name来渲染数据keyRef:数据源,值为以json路径的形式,会根据keyRef从spec中读取数据default:默认值,如果从spec中找不到数据,则使用默认值required:表示是否需要该属性description:属性的描述params:模板中定义了运维逻辑需要的用户数据和维护逻辑每条用户数据的基本属性,每条数据属性包括name,default,keyRef,description,andrequiredtasks:对于可配置的方面扩展,90%的traits可以直接转换,对于不能转换的,需要添加复杂的逻辑。Trait开发人员可以通过任务对其进行自定义。任务将在生成YAML片段之前执行。目前支持的任务类型有shell、job、httptemplate:基于gotemplate的trait模板,结合datarender形成最终的YAMLfragmentUniversalTraitController:核心转换控制器,结合traitCR和traitdefinition生成YAMLfragments2.流程介绍UniversalTraitController会结合traitCR和traitdefinition生成YAMLfragments。具体过程如下:用户数据处理(Merge)。traitCR中有用户数据(userdata)和应用数据(appdata),参数要求在params中指定,merge过程将userdata和params结合输出,记录为mergeddata自定义逻辑处理(TaskRun).Tasks是配置方案的扩展,是用户自定义的逻辑,包括多个shell、http、job、func等方法。这个流程会把合并后的数据和app数据作为task的输入参数,依次执行多个task。任务执行后,会产生新的输出数据,记录为输出数据数据渲染(Render)。App数据、合并数据、输出数据作为finalstate数据,这些数据和模板用gotemplate技术渲染得到YAMLfragment,YAML暂存在Fragment(configMap)中,专注于将要使用的应用数据被YAML片段使用,从安全的角度来看,平台提供给trait开发者的应用数据是有限的。目前只支持以下参数:OrderId:每个版本的orderIdAppName:应用名称WorkloadApiVersion:工作负载的apiVersionWorkloadKind:工作负载的种类Namespace:应用的命名空间CoreNamespace:kubeNest的命名空间,取值isark-systemReplicas:副本数量3.示例apiVersion:core.oam.dev/v1alpha1kind:TraitDefinitionmetadata:name:etcd-secret-injectornamespace:ns-examplespec:...params:-name:END_POINTtype:"string"description:“thisisadescription”默认值:“https://127.0.0.1”需要:falsetasks:-name:etcd-httpkind:http#shell/job/http/funcspec:script:'{{.Params.END_POINT}}/etcd'输出:-name:TOKENdefault:"defaulttoken"-name:KEYdefault:"defaultkey"template:|apiVersion:v1kind:secretmetadata:name:{{.AppName}}namespace:{{.Namespace}}data:token:{{.Outputs.TOKEN|b64dec}}key:{{.Outputs.KEY|b64dec}}从上面从上面可以看出etcd-secret-injector的作用是接受用户输入的etcd的端点,然后转换生成秘钥4.方案对比对比开发Operator开发配置开发开发成本需要掌握Operator开发知识只需要了解开发周期前后YAML编写知识去Operator需要几天时间,只写YAML,半小时左右运维成本每个trait都需要分开部署和稳定性保证当大部分trait成功汇聚到YAML配置后,只需要部署和维护一个UniversaltraitOperator,大大节省了运维成本。每个trait的资源配置是一个单独的Operator应用,最低配置1核1G。并且需要多副本部署,不消耗资源(新增traits只是增加YAML配置)Standardizationonlyinputandoutputstandardization不仅规范了输入输出,也规范了开发过程,可以很好的避免代错非标准代码导致的Bug无可扩展性多种任务类型支持自定义逻辑,具有良好的切面扩展能力稳定性一次部署可以轻松触发多次podrebuild避免多次rebuild资源配置开发给Operator,提供一个通用的trait开发的标准化管理输入和输出。开发者只需要配置YAML,大大缩短了开发周期。同时,trait应用融合,降低运维成本和资源消耗,避免多次重构,保证生产的稳定性。5.小结作为有状态应用的部署运行平台,KubeNest以“一键部署,随处运行”为目标,可以极大地帮助用户提高部署运行效率。Trait配置开发方案已上线KubeNest,并通过双十一验证,有效保障了KubeNest上应用的稳定性。最后总结了trait配置开发的优势:降本增效:该方案通过去除operator应用,有效汇聚资源。同时YAML配置开发大大提高开发效率,降低运维(部署、升级)成本;数据一致性Resilience:Templating保证数据面向最终状态,使得开发无需关注数据的顺序,保证数据的一致性,杜绝乱序导致重启的风险;推动开源:该方案已经在生产层面得到验证,得到了良好的响应,并输出到KubeVela,方便用户自定义开发traits,促进KubeVela开源生态的建设。【本文为专栏作者《阿里巴巴官方技术》原创稿件,转载请联系原作者】点此查看作者更多好文
