于梦琦 / 美国谷歌软件工程师
嘉宾介绍:
美国谷歌 Kubernetes/Google Container Engine(GKE)组核心成员,主要从事CLI(kubectl)开发以及配置管理的研究与开发。 本科和硕士分别毕业于上海交通大学和 UCSD
大家好!我是来自谷歌的于梦琦。今天我来给大家介绍一下 —— Kubernetes 配置管理中的最佳方法。
我们先来说一下 Kubernetes 中几个重要的概念:第一个要说也是最重要的就是 Declarative,Declarative 的意思是申明的、陈述的,与之相反的是 Imperative,Imperative 意思是命令式的。首先说一下 Kubernetes 设计完全是按照 Declarative 设计的。但为了初学者的学习方便,也是支持 Imperative。Declarative 对 Kubernetes 的功能非常重要,比如说它的自育性、自治能力都完全是依赖于 Declarative。第二个概念是 Level-triggered,Edge-triggered 。第三个概念是异步的。这些概念在后面会详细的介绍。
首先看一下 Declarative 和 Imperative 的区别和对比。Declarative 的定义是用户设定期望的状态,系统会知道它需要执行什么操作,来达到期望的状态。比如说我们看下面的例子。用户期望的状态是三个,系统观察到的状态是两个。于是系统就会知道创建一个新的来达到最终状态是三个。而对于 Imperative,需要用户告诉系统需要做什么。比如说用户说创建一个新的 Container,系统才会创建一个新的 Container。
大部分的 Kubernetes API 都有 Spec 和 Status 两个 Field。Spec 是让用户写入期望的状态,系统可以通过 Spec 读出用户的期望。Status 是系统写入观察到的状态,用户可以从中读出系统当前是什么状态。
这两个 Field 对于 Reconciliation Loop 是非常重要的,接下来看一下什么叫 Reconcile?
Reconcile 中文意思是 “调和”,“和解” 的意思,简单的说就是它不断使系统当的状态,向用户期望的状态移动。比如说右边的例子,用户期望的 Replica 是三个,Controller 通过 Watch 发现期望的状态是 3 个,但实际观测到的 Replica 的是 2 个,所以它就会 Create 一个新的 Pod。然后 Controller 会继续 Watch 这些 Pod,当它发现 Create 完成了,就会更新 Status 到 3 个,使 Status 和 Spec 达到一致的状态。
接下来再来说一下 Level–triggered 和 Edge-triggered 的区别。首先说一下 Kubernetes 是 Level-triggered。先看一下右边的例子。最上面是用户期望的状态变化。中间的是某一个 Edge-triggered Controller 的变化。最下面是 Level-triggered 的 Controller 的状态。在第二条虚线这个时刻,用户希望更新应用的版本,从 V1 到 V2,然而在第一条虚线和第三条虚线中间,我们的 Controller 死了。
对于 Edge-triggered 的 Controller 就会错过这个用户的更新请求,状态就没有变化。对于我们的 Level-triggered 的 Controller 就会在重新启动的时候发现用户的请求变化,就会更新版本到 V2。在这个时候用户想 Scale 他的应用的 Replica 到 3 个,这时候我们可以看到 Edge-triggered 的 Controller 就会直接 Scale V1,最终达到一个错误的状态。Level-triggered 的 Controller 就会最终达到正确的状态。
Kubectl 的 Commands 可以被分为三类:Imperative Commands、Imperative Object Configuration 和 Declarative Object Configuration。
第一类是叫做 Imperative Commands。Imperative 的 Commands 主要就是通过命令行的 Flag 直接进行 Imperative 的操作,Create 或者是 delete 之类的。例子是 Kubectl run, Expose,或者是 Create deployment,它就会把底层对于对象配置的操作对用户隐藏了起来。
接下来我们再来看一下它们优缺点的对比。Edge-triggered 的 Controller 优点就是直接、简单,易于实现。缺点就是如果错过了一个 Edge,可能就会导致一个最终的错误的状态。Level-triggered Controller 的优点就是不用担心这个,Edge-triggered Controller 另外一个缺点是不会采用直接的路径达到当前的状态,比如说当前的版本是 V1,用户想要更新到 V2,紧接着用户发现了什么问题,马上想更新到 V3。对一个 Edge-triggered 的 Controller 就会完成 V2 的更新,然后再去更新 V3。而如果对一个 Level-triggered 的 Controller 会在接收到 V3 的更新请求的时候直接开始更新,而不会等完成 V2 再更新 V3。
第二类要说的是 Imperative 对象配置,Object configuration 的意思是一个对象可以被定义为 yaml 或者 json 的格式存储在文件中,我们叫它 Object Configuration。Imperative Object Configuration 是直接对 Object Configuration 进行 Imperative 的操作,比如说 Kubectl create, Replace,Delete。
然后说一下第一类和第二类的对比。第一种 Imperative 的 Commands 优点是简单直接易学易记,缺点是没有办法使用 Change review,就是先更改再去批准流程,而且没有办法使用 Audittrails。Audit trails 的意思就是审计追踪又可以叫做 Audit log,它会记录系统的各种变化。第三个缺点是没法提供一个模板。相对于 Imperative Commands, Imperative Object Configuration 的优点是可以进行版本控制,比如说可以用 Git,也可以用 Change review 这个流程,也可以用 Autid trails,它的确定就是需要用户对于对象的 Schema 有一个基本的理解,另一个缺点就是需要用户写 yaml 文件。
第三类是 Kubectl 的 Commands 是 Declarative 的对象配置。它也是直接对 Object Configuration 进行操作的。用户不需要指定要 Create,Update 还是Delete,Commands 直接会帮你决定谁用哪一种。例子就是 ubectl apply –prune-f。
接下来对比第二类和第三类的区别。Imperative Object Configuration 对于 Declarative 的优点就是简单一些,稍微易理解一些。但它的缺点就是对于一个目录的配置文件只能选择同一种操作。它的第二个缺点就是其他用户的更新,必须被反应在配置文件中,不然其他用户的更新就会在下一次的更新中被丢掉。
接下来说一下 Declarative Object Configuration的优点。它的优点就是其他用户的更新可以被保留下来,比如说我的 Autoscaler 帮你管理的 Replica 这个 Field,你管理其他的 Field,你们之间就不会有冲突。第二个优点是对同一个目录下的对象可以有不同的操作,可以是 Create、Patch、Delete,缺点就是这个行为稍微难理解一些,需要时间来学习。
接下来说一下配置的管理。先从简单的说,如果是一个新手用户,我们建议他从最开始的 Imperative Commands 开始用起、学起,接下来可以尝试使用 Imperative Object Configuration。而对于比较高级的用户,我们推荐用 Declarative Object Configuration 来管理他的配置,同时要使用版本控制和 Change review 的流程。
对于新手用户来说,Kubernetes API 就是存放期望状态唯一的地方,它能支持一定数量的版本历史,但无法支持很长的历史,也不支持跨对象的历史。
对于高阶用户,我们推荐将期望的状态存在版本控制中,好处就是可以支持跨对象的历史。比如说有多个 Deployment,或者是其他 API 的 Resource,他们之间有互相的依赖关系。我们可以在整个系统在一个好的状态下,把整个 Repo 做一个 Tag。如果我们在后面的生产中遇到重大的问题需要回滚,可以直接回滚到之前的好的状态的 Tag 上。
另外是推荐使用 Autoreconciliation。你把配置存放到 Git Repo 中,然后有一个工具可以自动的把配置应用到 Kubernetes 集群上。这里有一个 BOX 做的开源项目,就是 Kube-applier.
随后,于梦琦老师在现场做了一个Demo, 感兴趣的同学,可以戳“阅读原文”观看。
Declarative APP Management,这是我们正在开发的一个工作流程,比之前的只多了前面的一个步骤。最下面的东西相当是一个应用商店,后面的三个步骤基本上是一样的。有了这个应用商店,可以从中找到通用的应用,比如说找一个 Redis、Cassandra,然后可以把这个配置的 Repo Fork 下来,可以做一些你想要的改变,到时候会有一些工具帮助你做改变。
比如说所有的 Resource 前面都加 Label,或者是加上一些名字前缀,后面一步可选择就是可以创建一个 Overlay,前三步可以到一个 Base 的 Layer,第四步可以把自定义的 Ovrelay 应用到前面三步产生的 Base。可以把这个 Qverlay 想象成一个 Patch。比如说有 3 个不同的 Production,Staging 和开发的环境,就有三个不同的 Overlay,要应用到相同的 Base 上,可以拿到三个不同的 Configuration,可以应用于三个不同的环境。后面步骤是一样的。
再简单说一下 Package Management。现在 Kubernetes 中的 Package Management有一个 Helm。一些应用被写成了 Chart 的格式,可以用 Helm 跑起来。但现在遇到的挑战,就是很难在此之中找到平衡,既使我们有足够多的参数满足所有的用户的需求,又能使这个应用不会因为参数过多导致难以维护,所以我们想推前面的 Declarative Application Management。下面是一个 Jenkins 的例子说明参数多过导致的难以维护。这是一个 Recap,有 Declarative 和 Imperative,还有 Spec 和 Status,还有 Commands 和 Configuration Management。
Q1:我有两个问题,刚才介绍了一个 Level-Triggered 是怎么实现的?第二个问题是一个应用商店,不知道应用在什么场景下?我们通常也是拉一些 Redis,取得一些镜像,做一些更改,跑一些 Pod 之类的。
A:先说下你的第二个问题,我们的应用商店想说的是关于某一个应用的 Kubernetes 应用商店,是配置文件,不是说这些 Image,你可以 Fork 这个 Repo,它可以直接跑起来,也可以做自定义的修改。
Q2:就是已经配置好的?
A:它是完整的配置文件。有很多 yaml 文件。
Q3:第一个问题是 Level Trap Kubernetes 怎么实现的,现在 Kubernetes 是这样做的是吗?
A:基本上是。现在 Kubernetes 每个 Controller 会有一个 Local Store。它会不断把变量拿过来,定期做 sync,sync 就会拿到用户期望的状态,它会不断的向 API Server请求。
Q4:我想提一下问题是刚才你用 Git 替换它的版本,这就会产生一个问题,如果用灰度发布或者是其他的发布,我们都是一节一节的提上去,这样 Git 全部提上去,不会产生很大的危险吗?
A:我觉得你应该可以这样做,比如说要一步一步,第一步做一个 Commit,再第二步做一个 Commit,最后进行发布,如果发布成功了,Monitor 了一段时间,发现没有问题可以打一个 Tag,如果中间有问题可以 Roll back 到最初的版本。
Q5: 你现在写了一个脚本,Git update 的形式,把一个版本跟踪上去,后端有一个服务不断的查询是否有更新,更新了之后把自己的服务更新上去,用自己的 Git Control Apply?这样就产生一个问题,我的商业服务比较多,这样不能产生灰度的更新,就是一部分一部分地更新。
A:我这个脚本只是用简单的例子想说明概念,这个脚本只是用最简单的 Kubectl Apply 来做的。你可以自己写一些自定义的 Apply 的工具,你规定它只会看到新的 Tag 才会更新,普通的 Push 不会每次更新,可以做这样的自定义的修改。你说的分布式的更新,比如说这个 Region 有一个对应的 Applier,可以根据你的规则只更新这个区域,不同的区域有不同的更新规则,我觉得应该可以这样做。
Q6: 等于说这些过程需要完成,而不是 Kubernetes 后期有这样相应的东西支撑?
A:后期应该会有,在 Slides 放了一个例子,就是 Auto Applier,这是在美国的一个叫做 BOX 的公司应用在生产环境的工具,就是自动的把这个配置应用到他们的集群。这是一个开源的工具。
Q7: 申明式要做很多比对工作,会不会影响 Kubernetes 扩展性的规模?如果集群的Pod 数量以及资源数量比较多,会对它的性能会造成很大的影响,有想过达到最上马的最大规模数吗?因为申明式需要做很多的比对,而且要不停的做比对,会不会对造成很大的影响,就像刚才说的 Controller 挂了,再重新起来,还要把这个配置做比对,再发现目标的 Design 的数目和当前数目的差异,再去做调整。
A:我觉得,如果跑非常非常多的 Pod,有可能会成为一个瓶颈,但目前 Kubernetes Native 的原生 API 都是这样做的。你想提的问题是你们写一个自定义的 API,比如说一个 PPR,一个 CRD,要写一个自定义的 Controller,会担心不断的比对,会影响它的性能,对吗?
Q8:或者说整个 Kubernetes 能支持的 Pod 的最大数量,或者说整个集群最大的规模数目,会不会在这个点会成为瓶颈?
A:目前 Kubernetes 是保证支持 5000 个 Node,具体能跑多少个 Pod 不是很确定,但目前不会成为瓶颈问题。它做了一个小的优化,每个 Controller 都有一个 Cache,会不断的变化,定期 Sync,不会因为死了就错过了一个用户的请求。
谷歌大神详解 Kubernetes 配置管理最佳方法
内容来源:2017 年 10 月 15 日,美国谷歌软件工程师于梦琦在 “首届Kubernetes 中国用户大会” 进行的《Kubernetes中应用的管理与实践》为主题的演讲分享。「K8sMeetup 中国社区」经演讲者审阅授权发布。