随着Kubernetes日趋成熟与稳定,网易云将其列为最重要的基础设施,成为云计算的底座。在此背景之下,PaaS团队的目标自然也将适配K8S列为了其首要支持目标。但是在实际落地过程中,我们遇到了很多问题,本文将分享一下网易在PaaS服务在迁移上K8S过程中的问题和我们的应对之道。
本文由作者授权发布,未经许可,请勿转载。
作者:蒋文康,网易杭州研究院工程师
调研
众所周知,Kubernetes处理无状态应用最为合适,Pod的短生命周期特性和无数据本地存储都是为无状态应用而生的,但是有状态应用是否也适合上K8S呢?最开始我们对此是持怀疑态度的,直到发现Operator的存在。Operator是由coreos公司提出的一个概念,定位为一种打包、部署和管理Kubernetes应用的一种方法。
Operator的工作原理并不复杂,如下图所示:
Operator通过Watch ApiServer来捕获CR的请求,然后创建对应的资源来“组装”成我们需要的集群。CR所属的子资源的状态也通过ApiServer反馈给Operator,Operator来做相应的协调处理,整个过程清晰明了。
原理不复杂,关键在于Operator的开发模式与我们以前做PaaS管控的逻辑出入是很大的,主要表现在以下两个方面:
1.基于声明式的开发模式,而不是命令式。
2.假定资源的状态变化是高频的,而不是低频的。
对我们冲击最大的就是声明式的开发方式,它强调一开始就把最终的结果描述清楚,不管过程;而我们已经的开发模式基本上都是命令式的,由用户通过一些列的Restful“命令”来组装成他需要的集群。
声明式的优势:
1.强调结果,而不是过程
2.对使用者友好
3.简化开发工作
缺点也很明显:
1.性能。针对某些大规模的场景,声明式可能没有命令式处理高效。
但是对于PaaS服务的管控端而言,性能不是主要指标,稳定性和可维护性更加重要。所以声明式的开发方式应该是对PaaS的管控端开发更加友好。
另外,还一个显著的区别在于,相比于传统的VM架构,Kubernetes集群上的资源似乎更加“不稳定”。我们分析发现,这种不稳定性来源于多个方面:
2.混部。一个Kubernet集群可以承载的负载类型非常多,业务容器、PaaS容器和大数据相关的容器都可以跑在一个K8S集群中。虽然可以通过给Node打标签,配合一些调度策略将不通的业务隔离开,但出于提高利用率的考虑,又不会隔离的非常严格。这样就可能造成资源的抢占,从而带来风险。
3.Docker较弱的隔离性。Docker是共性操作系统内容,基于Namespace的隔离还不够完善,可能带来潜在风险。
复杂的Controller关系。Kubernetes的一大优势在于其高度的自治性,这种自治性通过各自各样的Controller来完成,同时Controller之间又存在着隐含的层级关系。
这种“不稳定”因素的存在迫使我们在设计之初就要考虑服务的自愈能力。实际上,K8S已经提供了非常强大的自愈能力,我们更多要考虑的是让PaaS如何利用这些能力。
在调研过程中,我们发现其实现在已经有非常多的PaaS服务迁移上了Kubernetes,awesome-operators上有众多开源的Operator项目,有官方的也有第三方开源的。这进一步坚定了我们选择Operator的决心。
标准化
在研发过程中我们发现了一个很大的问题,在于各个PaaS服务的管控端都是每个PaaS团队各自开发,基本上没有代码复用。有一两个团队出于自己代码维护性地角度方便,开发了一些SDK包来封装与IaaS之间的交互,但这些SDK包并没有经过良好的设计和抽象,导致其很难在其他PaaS之间共享。这样带来的一个直接问题就是研发效率低下。一个PaaS服务从立项、开发、测试和上线大概需要2~3个月的时间,这还不包括对接各种横向服务,如计费、SAM、资源池等。另外一个问题在于,管控端质量取决于PaaS人员的水平,一些共性的问题反复发生,没有集中性的技术手段来规避这些问题。
为了避免重蹈覆辙,在PaaS on K8S立项之初我们就强调通过标准化规范来约束PaaS Operator,通过“中台”小组来解决PaaS共性的问题。(这里的中台应该叫PaaS公共服务更为合适)
我们在充分调研的情况下,结合Redis和Kafka迁移上K8S的实际项目经验,制定了一套标准化的规范。该规范包含以下几个方面:
1.文档规范:必须包含需求文档、概要设计和详细设计,尤其是需求文档,一定是要从业务方的痛点出发,说明该项目的价值和意义。
2.设计规范:包括设计原则,最佳实践和CRD的详细设计要求。
3.开发规范:使用Go为开发语言,OperatorSDK为开发框架,Api设计遵循社区规范。
4.高可用要求:必须容忍单节点异常,多机房部署要容忍单机房异常。
5.部署规范:所有的Operator都需要发布到网易轻舟微服务应用商店;镜像制作要使用统一维护的基础镜像,禁止使用latest标签;统一使用脚本管理系统来分发宿主机上的管理脚本等。
6.运维规范:定义Operator和人工运维的边界;日常巡检的内容;应急预案的要求。
7.测试规范:由QA团队制定,包含单元测试、e2e测试、功能场景测试、数据面稳定性测试、管控面和数据面异常测试、性能测试等。
8.监控报警:基础的监控指标、数据面的采集指标和采集方式、管控面的采集指标和采集方式、报警的规范和原则。
目前标准化的内容已经涵盖非功能性80%的内容,功能性大概40%左右,可以说做到了PaaS研发过程的基本覆盖了。有些内容很难通过标准化的规范来约束,我们通过白皮书的形式来进行说明,进一步覆盖实际开发过程中的可能遇到的问题。
扩展
PaaS迁移上K8S的过程中遇到了很多问题,我们发现原生的K8S的能力还不能满足我们的需求。为此,我们和K8S团队合作,将我们的需求统一化,由K8S团队来实现这些扩展能力。
调度
PaaS服务的调度与无状态应用还是存在很大的差别,主要在于PaaS对高可用的要求更高。比如,我们希望Redis Cluster不超过1/3的Pod分布到同一个Node上,不超过1/2的Pod分布到同一个机房里面等等。我们将这种扩展调度的需求放置到一个configmap里面,由K8S来实现统一的扩展调度器。
apiVersion: v1
kind: ConfigMap
metadata:
name: config-map-name
namespace: cluster-namespace #实例集群所在的ns
data:
cluster-size: 100 //集群大小
max-pod-on-node: 33 //单节点最多能调度的Pod(包含)
max-pod-in-zone: 50 //Zone内最多调度Pod(包含)
available-zones: cn-east-1a,cn-east-1b,cn-east-1c //必须调度的可用区
1.max-pod-on-node就可以限制整个集群在单个Node上调度Pod的数量
2.max-pod-in-zone可限制单个机房内调度Pod的数量
3.available-zone用来实现AZ的MUST IN语言,从而满足多机房特性
MUST IN语义的实现其实比较困难,这与K8S的调度机制有关。K8S是基于Pod进行调度,通过预选和优选来过滤出符合条件的Node。然而,PaaS服务很多时候需要有“全局视野”,局部调度最优但不代表全局调度就是最优的。
我们Redis Cluster采用了多个StatefulSet来管理分片,每个StatefulSet的0号Pod为主分片,1号Pod为复制分片,要求多机房的情况下主分片和复制分片不能在同一个机房。如果由K8S随机调度,那么有可能就会发生死锁问题,可调度的Node越少发生死锁的概率就会越高。下图详细说明发生这一问题的过程,已经目前我们的解决方法。
但是这种解决方式存在局限性和特殊性,并不能应用于所有的情况。问题的核心还是在于PaaS调度需要有“全局视野”,我们需要提前规划好Pod的分布情况,但是现有K8S的调度机制限制了这方面的能力,除非我们自定义PaaS的“专属调度器”。
本地盘
很多PaaS服务都有存储的需求,这也有状态服务的一大特点。云原生架构推荐我们存储架构分离,但是这需要强大的网络支持,所以本地盘还是相对现实靠谱的方案。K8S原生对本地盘的支持不太好,主要在于Local PV需要手工管理和维护,为止K8S团队帮助我们开发了基于LVM的本地盘管理插件。
管理员只需要按如下方式来申明可用的盘和机器信息即可。
apiVersion: node.netease.com/v1
kind: LocalStorage
metadata:
name: ls
spec:
disks:
- /dev/sde
node: 172.24.5.4
storageClass: localstorage-class
vg: k8svg
status:
allocatable: 676475Mi
capacity: 762491Mi
phase: Active
通过自定义storageclass的方式给我们提供了相当高的灵活的和扩展性,可以满足共享盘和独立盘的功能,只需要建立不同storageclass即可。
IP保持
IP保持K8S团队开发的扩展能力,它是在Pod重建以后还能保持以前分配的IP,从使用方式上表现得更像VM一样。实际上这种使用方式是违反K8S的设计理念的,但是出于调试、运维、查看日志等需求,业务方希望能保持POD的IP。我们使用这一功能是因为我们遇到以下的场景。
Redis集群中某个Pod挂掉以后,原先分配给该Pod的IP可能被其他集群复用,造成元信息混乱,客户端有可能连接到一个另外一个集群的Pod,造成访问异常。
这是一个合理的场景,如果PaaS的节点之间或者Client与服务节点之间缺少认证机制,仅靠IP地址来建立信任关系,确实会存在上诉的场景。但IP被错误的Pod复用的概率取决于可分配IP池的大小,如果IP池足够大,出现这种概率的情况还是极低的。
目前我们还没有找到一种相对简单的方式来规避该问题,但我们还是不推荐使用该功能。
产品化
PaaS开发的Operator都将集成到轻舟平台的应用商店。标准化规范要求Operator统一使用OperatorSDK进行开发,它自动化生成CSV文件,而轻舟应用商店通过Operator Lifecyc Manager来统一管理和运维Operator,两者可以无缝对接,部署难度大大降低。
同时前端界面是一套,无须为PaaS服务做定制化开发,前端研发效率大大提高,使用方式也更加统一。业务方使用也是非常友好的。业务只需要在应用市场订阅所需要的Operator即可使用到PaaS服务。当然,在产品化集成方面,Operator离一个完整的产品还存在差距,但为了尽量剥离这些商业化逻辑,让PaaS Operator更加纯粹,我们正在开发Operator Assistor这样的统一适配组件,来满足权限、配额、计量计费的产品逻辑。
总结
从今年8月开始做Operator,到年底已经上线了3个Operator(Redis Cluster、Kafka、Zookeeper),我们的研发速度大大提高。从大家的反馈来看也十分积极。经统计,编写Operator所需要的代码比起此前的管控代码减少了80%以上,可用性提高的同时,运维成本还降低了很多。由于今年还未大规模部署,预计明年基于Operator托管的PaaS服务将遍地开花,我们人均的运维规模将大幅提升。