好吧,这时间差不多了我刚才记错时间了我以为是2.50,是2.45那我们现在开始吧今天我跟大家分享的是K8SAPI的一些设计规范以及如果我们自己去扩展K8SAPI,应该去注意一些什么内容然后我先做个自我介绍吧我是华为云华为云专职的开源公司目前在维护K8S还有Kamada的项目还是购中央变成的突出作者正是因为我在维护Kamada项目过程中我深切地感受到很多企业里边去定义的一些CRD往往是一些CRD它定义的并不是那么规范那往往就是说这些开发者会向我咨询说我们的CRD能不能跟Kamada兼容能不能跟Kamada兼容这时候我一看CRD的设计它有一些问题导致可能跟Kamada没法很好的工作所以说我就是说结合我自己的经验因为Kamada项目里面也会定义也会设计一些自己的API所以说结合我自己的经历来做这次分享所以我想我也想了解一下在做的各位谁在就是曾经去设计过自己的API设计API包括你用K8s的聚合API或者是CRD设计过API的同学请举下手看一下好还有几位好那我觉得今天没有设计过API的话也可以因为里面的很多的内容都是来自K8s的API我们以K8s的API举例这儿共产党也会带大家去了解一下K8sAPI的设计是怎么样的好那我今天会简单的讲一下K8sAPI的情况还有K8sAPI如何去扩展的机制以及两种机制之间的不同还有一些接下来就是对K8sAPI的设计的约定还有我自己的会融入我自己的一些经验好那K8s我觉得是一个学习美肯很高的一个项目那其中呢就是有它的种类繁多的API那最早的话K8s的API并没有去分组大家知道所以说到了后面API种多的时候变多的时候它对API进行了一个分组那即便说分组也容纳不了更多的这种需求所以说API没有办法全部都在K8里边去做实现那怎么办K8提供了扩展的机制用户可以基于K8去做自己的扩展那这里的API的版本这里有一个概念就是API的一个版本API版本Alpha, Beta还有Stable分别代表不同的成熟度一般企业里边比较激进的可能会从Beta开始用然后更慎重的可能会只用StableAlpha一般是变动会比较频繁的一种那说到扩展API那扩展API的话K8是提供两种机制我们从从它眼镜的一个时间训练来看最早的话是引入是通过聚合API的方式然后接着才会有CRD但是发展到现在我们可以看到CRD的生命力感觉更强实现的次数会更多但是AA仍然是一个很重要的一个能力那它的区别就是AA的话它的开发和运为的成本会比CRD更高CRD可能来得更便捷然后AA的方式聚合API的方式它的定制能力更强定制能力更强我们这里说的定制能力可能相比较的话CRD它的Subresource它可能只有Scale跟Status两种资源但是我们在AA里面可以任意地定制这里举一个例子如果我们用AA来扩展K8S就好像在一个火车一个火车上去增加一个车厢去来实现你的能力它的缺点就是说你需要去按照火车的设计标准去打造你的车厢然后进行装潢最后组装相当于挂在API Subresource的一个组件CRD更像是火车原来车厢里面的一个卡座你只需要去放一个你自己的座椅就这么简单但是同时它带来的限制就是说能力会受限这个绝大多数情况下可能CRD就已经满足所以接下来内容我们说API的设计其实这两种机制都有可能会遇到都适用我们就从一个API的最前线的例子开始我们会从API Version看的一直到data这样展开来介绍我左边的就是一个大家比较熟悉的这种deployment的一个压谋这里面有一个API Version和CAN的如果我们在设计自己的API的时候API Version怎么去定义这里你需要注意的有三点K8S是有一个reserved一个API Group那就是说微空的代表是核心的那种API Group组这个是K8S Reserved的那其他的像单字节的单字节的词还有说以K8S.io的这后坠的域名都是K8 Reserved的大家不要用但是如果定义的话可以按照自己的项目或者是组织来定义自己的域名说到CAN的CAN的的话很多同学就是说在设计自己的API的时候可能也会用到自己的CAN的那这里有一个概念就是CAN的跟resource到底有什么区别CAN的跟resource到底有什么区别我的理解是CAN的更像是一种举个例子可能大家比较好理解比如说我手里的这台手机大家知道这个型号可能是Mate60对吧这种Mate60可能是它的一个用户能够普遍能够接受的一个名字但是它在运营商系统里边注册的有可能是一个卫星通信中端什么C什么之类的一个型号对吧所以说CAN的更像是更方便用户记忆的一个名字然后resource名字更像是在内部系统处理的过程中所用到的名字好接下来我们看一下MataData其实MataData里边在我们设计的时候并不会去去一般的话我没有见过任何人会去改MataData的内容因为这里边就是有几个关键的关键的字段可以大家可以就是有些人就是不会没有对这些字段有一个很好的理解那在后面的应用可能也会也会受限那这里边比较重要的比如resource versionresource version实际上是一个它每一个字元在ETCD里边的这种存储的一个版本号每一次这种整个字元任意的一次变更哪怕是一个制服的变更都会导致resource version的递增下面这个generation是表示的是一个spec的变更次数这个过程中像status的变化就不会去触发generation的上升递增那label and annotationlabel and annotation很多人也是搞不清楚这里边的区别那label更主要是用于说对字元的一个分类规类然后呢会经常会用于系统的这种减速比如说label selector然后呢它的它的限制就是说它的长度它的value只是会有会有个限制然后 annotation更像是一种解释更像是一种解释所以它的长度会比较长然后 find a letterfind a letter更像是find a letter就是一种我在删除字元之前我需要得到某个控制器的同意的情况下控制器可以增加一个 find a letter这个就就有点像说是要去处理某个比如说去处理某个人去去去杀头之前必须要经过谁同意一样的意思好 那个还有一个 owner reference表示的是资源对象之间的从属关系从属关系它K8s提供默认的这种GC控制器它可以保证在你的主资源删除之后然后从属的资源能够被自动地清理这个在我们实现的时候也会用的比较频繁好 接下来是一个两个大块就是SpikeSpike has statedSpike呢 其实我们说K8s是生命式的使用的是生命式的API那生命式所体现的生命的内容到底是什么就是你desired的一个state就是所有的内容都应该出现在Spike里面这个过程中我也见到有些人设计的它的CRD它的desired一些期望的一些配置是出现在Status里面这个是不对的然后在Spike里面包括什么内容一部分绝大部分是用户所提供的另外一部分是系统默认设置的还有一部分是被第三方组建所管理的这个第三方组建管理比如说POD的NodeName这个字段它其实是被调度器所管理的 对吧那Status就是表示说这个对象 这个字件对象当前的一些状态这个状态包括你的状态的一些字段还有Condition接下来我们会聊Status字段GainCondition到底什么区别然后还可能包括part of theSpikeValue我SpikeValue也有可能出现在Status这个出现的时候往往是对Spike内容的一个重复我们举一个例子Diplomint里面有一个observed generation当然这个例子里面我们现在说Diplomint里面有replicant它其实就是对应的Spike里面的还有observed generation实际上对应的是metadata的内容它出现Status的实际是有一定的用途的就是让它的控制器能够纪录我所recancel的这个资源对象到底是哪一个版本的资源对象好 我很多同学就是说它不会分不太清楚Condition跟Status到底什么区别我们先说ConditionCondition的话它是对一个资源对象一个high-level的资源状态的表示High-level怎么理解我的一个资源对象Status可能有很多的字段这些字段我可以说控制器可以根据这些字段进行推导出来一个用户可以理解的一个最终状态比如说Diplomint的availableavailable的状态它可能是参考的各种当前的replicant以更新的replicant这样的信息来推导出来一个available状态这个状态主要是用于被第三方系统第三方工具进行读取进行判断同时同时第三方的工具也可以在不改动API的情况下向资源对象去追加新的状态这个是API级的一个扩展机制那之前的话之前大家之前的k8s版本大家可以看到很多资源对象它的Status里的condition都是自己定义的那现在k8s的资源对象慢慢地统一层一个叫Metave E的conditionMetave E的condition所以说后面的API设计都应该去使用Metave E那这样的话所有的API所有的资源对象的condition都是一样的你任何系统去识别都没有任何的障碍那这里面condition的几个几个关键的字段类型 还有状态还最近更新时间read the message我们接下来看一下一个资源对象的类型就是说你需要定义成一个人类可读的一种类型然后一般通常来讲都是大脱风的表示然后这里有一个需要着重强调的就是说定义类型的时候尽量去少用一些这种double negative的词比如说我定义一个资源的状态是failed的因为为什么因为在condition里边还有一个status叫True False还有unknown如果你定一个negative的词那就会出现一个failed等于false这样的话是需要一种逻辑上的转换那它比如说failed跟ready相比那ready可能更优一点另外这个type一般就是需要定义成一个形容词比如说deploy的可能会更优于deploying那个状态刚才提到这种每一个condition都会有一个True false还有unknownunknown的默认情况下缺省的情况下就是代表unknownunknown的一个最最标准的用法就是说我的一个控制器首次去recast out一个资源对象如果没有如果还没有得出是True or false之前先标几个unknown但是一般情况下就会了解一般的控制器都不会去这样去做所以说unknown and默认就是false那个这里的就reasonreason跟message的区别reason主要是用于机器识别吧相当于机机的一个状态然后message的更像是更用于表示是人类可读的这种这种更多的信息好那接下来另外一个关于status has back的一个一个事情就是说大家在定义自己的CRD的时候一定要尽量去尝试定义subresource就像把status单独独立出来这样的话用户再去变更它的spark的时候就不会去把status的内容充掉接下来API设计的关于类型选择因为我们也需要去在API里面比如说新增自己的自断这里面就设计到给每个字段去选一个什么样的类型类型有可能会包含勾语言的build in的类型还有自定义类型这个里面就是说尽量少用就是每一个字类型的定义应该给它有一个meaningful的名字这个我其实跟我们在coding的时候去定义名字是一样的就保持就是三个原则第一要清晰清晰的表达它的意思第二你还要简洁第三还不能有奇异对吧比如说你的自断曲直的话也不要用这种123这样的序列你可以给它取一个不同的名字然后设计到整形的时候因为int类型它在不同的系统里边可能有不同的长短的解释所以说尽量用int32或者是int64那这里也有人问我只需要去用int16或者int8行不行其实也可以但是这个区别并不是很大因为你节省的仅仅是以两个字节另外就是对浮电型因为浮电型两个浮电型你没有办法准确地定义出来是否相等所以说浮电型也是尽量少用还有在定义一些自断的类型比如说布尔型的时候要格外注意一定要想我将来除了处放肆以外还有没有可能出现第三种如果有可能那你当前选择的布尔就会阻碍了后面的眼镜那关于产量的定义那产量的话是像产量主要是用于说自断的取值定义一个自断的取值范围同时我们说再定义一些你的组建的flagflag的取值也可以也是用于这个产量的一个规范应该用这种托风大托风的方式当然在k8s里边也有反例出现当然这个是一种历史原因但是不推荐的做法就是比如说kubelite的参数也出现了全小写的方式这是历史原因接下来看sound name还有category我们知道在k8s里边很多资源都有一个sound name然后再用category用sound name显得就是说可以更便捷那我们在自己设计API的时候可能也会去给它去定义一些更简短的名字但是这里需要注意的点就是说像k8s在匹配短名字跟长命字之间它长名字可能就是优先级会更高要举个例子如果你像k8s的service它有一个sound name叫SVC如果你定义一个API给它取得名字API的名字比如说单数的名字也叫SVC那你如果用coup controlguideSVC的时候可能就是会把k8sSVC给屏蔽掉那category的实际上也是说对你API的一种分类比如说你可以把你自己定义的API全部都归入到一个组比如说常见的这种凹那这样的话coup control guide凹就可以看到所有的资源好 那这sound name跟category怎么实现怎么实现你像我们这里说beauty in resource什么是beauty in resource就是说k8s的API都是beauty in resource那如果我们扩展k8sAPI用的是a的方式用的是g和API的方式实际上也是beauty in那beauty in的话你需要去为你的API实现一个新的一个rest of the jackal叫sound name跟categories那如果是crdcrd的话你就比较简单你就可以打上一个在你的API里面打上一个标签去指定sound name那再用相应的damp就是说crd的自动生成工具它能帮你生成crd的那个配置好那我们在设计API的时候还有一个经常需要去思考的就是说我每一个字段我需要是require的还是说optional的这个也是一个很伤脑筋的事情那我们先说在k8s的规范是怎么样那k8s我们可以看到k8s的很多API它的字段它并没有并没有一个显示的optional或require的这么一个构园的tag但是我们在设计API的时候推荐这么做为什么因为k8s早期的话并没有提供这个optional加require的这两个tag而是通过显示的隐示的一个json tag叫Omit Empty的一个tag所以现在的规范的做法是如果你的字段是一个optional的那它应该有一个optional的一个tag然后呢如果你的自定义类型是你的类型是自定义的比如说你自定义了一个struck的结构那你尽量使用指针如果是购义员内置的类型比如说mypossess那这种的话就无所谓因为它的默认的领值是new那这种optional的一般还会带上一个确设就是Omit Empty的一个tag那相对应的require的field它就会有一个显示的require的一个comments一般的话也不会是一个指针的类型同时也不会有一个json的缺省的tag好 那这里还有一个在定义API字段的时候就考虑如何去识别一个字段用户填的用户指定了值但是指定了值是空这两种case因为在你后端的实现的过程中可能你读到了一个字段的值它可能就是空这个时候你的定义也是一个默认为空这个时候你怎么知道用户到底有没有填比如说一个int32类型默认值是0用户填的是0还是没填这个时候就可以使用指针类型进去别因为指针如果它填的也是这种本身的这种领指也是能够区别出来好 那另外一个讨论的是设置default值的问题我们在设计字段的时候也会考虑设计default值那为什么需要设置default值呢因为default它可以减化用户的配置可以让用户去只配置一些关键字段然后就可以工作的这么一个便利方式但是它存储在etcd固化下来它所有的字段都是代表的是一个全状态的一个就是把所有的default值都会设置的一个状态这样有什么好处就是对你的后端系统的话你不用去猜不用去做各种的判断比如说我去后端形态实现去default某个值是否设置如果没有的话我怎么样有的话怎么样就少了这种的逻辑判断另外对用户来说它可以通过再一次比如说把资源盖得出来进行查看它可以看到资源的一个全部的一个状态这里需要注意的一个点就是我们在用KBS的API的时候KBSAPI的这个字段默认值有可能会随着版本之间有所变化那对我们的启示是什么我们在生产上用的时候是不是应该尽可能地去显示的指定你想要指即便它也是默认值那为什么说这样显示的指定默认值因为默认值设定它需要遵循说它不可以去改变你以前设置的值它往往是说为指定的情况下设置默认值这里面包括还包括像一个map中去增加你的新的值像你的slash里面新增值好 这里一个我思考的问题就是说你的Spark中的内容竟然是用户提供的我们的控制器能不能改经常有人就是说Spark内容是用户提供的你的控制器不应该改但是我的思考跟实践就是说可以改 但要慎重要少改一定要改的话要在用户知情的情况下去做这样的改动比如说比如说我的HPAHPA去改Department of Replicate那这个时候其实用户有预期它相当于Department of Replicate交给HPA进行管理好 那设置Default只有一三种方式就这种Static Default就是像你实现聚合API的时候可以通过实现REST的API去设置Default的值然后往往比较常见的是Admission Controller就是我们通过一个Webhook来实现设置Meringue值另外一种比较少见但是也非常有用的就是这种Operator的这种方式我一个Operator可能是用于安装一个组件但这个时候我没有一个Webhook怎么办我的控制器也可以进行设置Meringue值Operator Reference刚才提到了它表示的是资源的一个从属关系一般的话命名就会带上一个Ref的一个后坠一问次主要是提供历史的信息其实也遵循我们刚才命名规则清晰简洁 无奇异刚才讲的大部分都是K8s的我们所理解常规的那种API实际上API的范畴远不止于此像Label Annotation Events Metrics其实都是API的范畴这里有一个抛出一个问题就是说我适不适合通过Label来引入新的功能这个时候一般来说这种做法可能会在使用开源软件的时候我需要在上面去叠加一个新的能力我可以用这种方式但是如果是这个项目就是你自己做或者CLD就是你自己定义那我建议直接把它体现在Spec中去因为在K8s早期实践过这个方向我的新增的Feature都在Annotation里边先定义然后觉得可靠了我再把它移到Spec但是后面都全面地契用了这种方式好 最后跟大家分享一些感触就是我在做项目的时候很多人就是说在聊方案的时候就是说一说这个方案怎么怎么样就是说K8s就这么做的总是说K8s就这么做的但实际上K8s有它的好的绝大部分是好的但是也并不一定完全好也不一定完全适用于自己所以说K8s并不是圣经在设计API的时候可以去多去参考K8s的API但是还要结合自己的实际情况同时也不要怕犯错因为API的话可以眼睛你可以去考虑后面的兼容好 我今天的内容就这么多谢谢大家好 那个有没有什么问题谢谢我的问题就是你刚提到有Alpha Beta和Stable版本吗对如果比如说我在spec里面加了一个property比如说以前这个propertyAlpha是没有的现在加到了Beta里面这个是正常的design嘛因为我看到比如说GateKeeper他们那个OPPAGateKeeperNetProject他们新加了一个property把这个property同时加加了Alpha和Beta的版本的那个definition里面就是 真是问题我觉得这是个很好的问题我来重复一下它的问题就是我的问题实际上包含了两个第一个我在往一个API里增加一个新的字段的时候我需不需要去演进我的API比如说从Alpha演进到Beta对吧好 那第二个问题是我的API从Alpha演进到Beta我对Beta做变更做新经增加字段我觉得是很正常 对吧但是同时再回来把Alpha给也增加进去这个是不是一个正常的我觉得这是个很好的问题我觉得K8S也没有也没有很好的解决这个问题为什么这么说因为严格意义上来讲你增加一个字段你需要对你的API做一次演进比如说从Alpha演进到Beta或者从Beta演进到Stable你需要做一次演进但是K8S你看它这几年的做法它的唯一的API它的Stable的API因为它短期没有演进到V2的这种计划所以它的API里面也会新增一些字段但是新增的会比较谨慎所以说这里的衡量点是什么你新加的字段会不会破坏你的兼容性如果不会的话其实这个问题都不是很大为什么为什么不是很大因为你没演进一次API然后其实的代价是比较大的你需要让你的用户也需要跟着去迁移虽然用户的迁移工作量可能不是很大但是也需要去迁移所以说这样也回答了第二个问题就是说它在Alpha里面再加一个合不合适我觉得如果是不破坏兼容性的情况下是可以接受的好 谢谢好 还有没有问题好 那如果没有问题那就提前一祝大家中秋国庆愉快乐好 谢谢大家