25

网易PaaS On K8S实践 | Michael-J

 4 years ago
source link: https://michael-j.net/2019/12/24/%E7%BD%91%E6%98%93PaaS-On-K8S%E5%AE%9E%E8%B7%B5/?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

网易PaaS On K8S实践

2019-12-24

| 云计算

| 0 Comments

| 476

3,306

|

12

随着Kubernetes日趋成熟与稳定,网易云将其列为最重要的基础设施,成为云计算的底座。在此背景之下,PaaS团队的目标自然也将适配K8S列为了其首要支持目标。但是在实际落地过程中,我们遇到了很多问题,本文将分享一下网易在PaaS服务在迁移上K8S过程中的问题和我们的应对之道。

随着Kubernetes日趋成熟与稳定,网易云将其列为最重要的基础设施,成为云计算的底座。在此背景之下,PaaS团队的目标自然也将适配K8S列为了其首要支持目标。但是在实际落地过程中,我们遇到了很多问题,本文将分享一下网易在PaaS服务在迁移上K8S过程中的问题和我们的应对之道。

众所周知,Kubernetes处理无状态应用最为合适,Pod的短生命周期特性和无数据本地存储都是为无状态应用而生的,但是有状态应用是否也适合上K8S呢?最开始我们对此是持怀疑态度的,直到发现Operator的存在。Operator是由coreos公司提出的一个概念,定位为一种打包、部署和管理Kubernetes应用的一种方法。关于Operator诞生的历史,可以参见这篇博文:OpenShift全力拥抱Operator:Kubernetes运维自动化背后的战争

Operator的工作原理并不复杂,如下图所示:

operator-principle.png

Operator通过Watch ApiServer来捕获CR的请求,然后创建对应的资源来“组装”成我们需要的集群。CR所属的子资源的状态也通过ApiServer反馈给Operator,Operator来做相应的协调处理,整个过程清晰明了。

原理不复杂,关键在于Operator的开发模式与我们以前做PaaS管控的逻辑出入是很大的,主要表现在以下两个方面:

  1. 基于声明式的开发模式,而不是命令式。
  2. 假定资源的状态变化是高频的,而不是低频的。

对我们冲击最大的就是声明式的开发方式,它强调一开始就把最终的结果描述清楚,不管过程;而我们已经的开发模式基本上都是命令式的,由用户通过一些列的Restful“命令”来组装成他需要的集群。

声明式的优势:

  • 强调结果,而不是过程
  • 对使用者友好
  • 简化开发工作

缺点也很明显:

  • 性能。针对某些大规模的场景,声明式可能没有命令式处理高效。

但是对于PaaS服务的管控端而言,性能不是主要指标,稳定性和可维护性更加重要。所以声明式的开发方式应该是对PaaS的管控端开发更加友好。

另外,还一个显著的区别在于,相比于传统的VM架构,Kubernetes集群上的资源似乎更加“不稳定”。我们分析发现,这种不稳定性来源于多个方面:

  • 混部。一个Kubernet集群可以承载的负载类型非常多,业务容器、PaaS容器和大数据相关的容器都可以跑在一个K8S集群中。虽然可以通过给Node打标签,配合一些调度策略将不通的业务隔离开,但出于提高利用率的考虑,又不会隔离的非常严格。这样就可能造成资源的抢占,从而带来风险。
  • Docker较弱的隔离性。Docker是共性操作系统内容,基于Namespace的隔离还不够完善,可能带来潜在风险。
  • 复杂的Controller关系。Kubernetes的一大优势在于其高度的自治性,这种自治性通过各自各样的Controller来完成,同时Controller之间又存在着隐含的层级关系。

这种“不稳定”因素的存在迫使我们在设计之初就要考虑服务的自愈能力。实际上,K8S已经提供了非常强大的自愈能力,我们更多要考虑的是让PaaS如何利用这些能力。

在调研过程中,我们发现其实现在已经有非常多的PaaS服务迁移上了Kubernetes,awesome-operators上有众多开源的Operator项目,有官方的也有第三方开源的。这进一步坚定了我们选择Operator的决心。

在云1.0和2.0的研发过程中我们发现了一个很大的问题,在于各个PaaS服务的管控端都是每个PaaS团队各自开发,基本上没有代码复用。有一两个团队出于自己代码维护性地角度方便,开发了一些SDK包来封装与IaaS之间的交互,但这些SDK包并没有经过良好的设计和抽象,导致其很难在其他PaaS之间共享。这样带来的一个直接问题就是研发效率低下。一个PaaS服务从立项、开发、测试和上线大概需要2~3个月的时间,这还不包括对接各种横向服务,如计费、SAM、资源池等。另外一个问题在于,管控端质量取决于PaaS人员的水平,一些共性的问题反复发生,没有集中性的技术手段来规避这些问题。

为了避免重蹈覆辙,在PaaS on K8S立项之初我们就强调通过标准化规范来约束PaaS Operator,通过“中台”小组来解决PaaS共性的问题。(这里的中台应该叫PaaS公共服务更为合适)

我们在充分调研的情况下,结合Redis和Kafka迁移上K8S的实际项目经验,制定了一套标准化的规范。该规范包含以下几个方面:

  • 文档规范:必须包含需求文档、概要设计和详细设计,尤其是需求文档,一定是要从业务方的痛点出发,说明该项目的价值和意义。
  • 设计规范:包括设计原则,最佳实践和CRD的详细设计要求。
  • 开发规范:使用Go为开发语言,OperatorSDK为开发框架,Api设计遵循社区规范
  • 高可用要求:必须容忍单节点异常,多机房部署要容忍单机房异常。
  • 部署规范:所有的Operator都需要发布到轻舟应用商店;镜像制作要使用统一维护的基础镜像,禁止使用latest标签;统一使用脚本管理系统来分发宿主机上的管理脚本等。
  • 运维规范:定义Operator和人工运维的边界;日常巡检的内容;应急预案的要求。
  • 测试规范:由QA团队制定,包含单元测试、e2e测试、功能场景测试、数据面稳定性测试、管控面和数据面异常测试、性能测试等。
  • 监控报警:基础的监控指标、数据面的采集指标和采集方式、管控面的采集指标和采集方式、报警的规范和原则。

目前标准化的内容已经涵盖非功能性80%的内容,功能性大概40%左右,可以说做到了PaaS研发过程的基本覆盖了。有些内容很难通过标准化的规范来约束,我们通过白皮书的形式来进行说明,进一步覆盖实际开发过程中的可能遇到的问题。

PaaS迁移上K8S的过程中遇到了很多问题,我们发现原生的K8S的能力还不能满足我们的需求。为此,我们和K8S团队合作,将我们的需求统一化,由K8S团队来实现这些扩展能力。

PaaS服务的调度与无状态应用还是存在很大的差别,主要在于PaaS对高可用的要求更高。比如,我们希望Redis Cluster不超过1/3的Pod分布到同一个Node上,不超过1/2的Pod分布到同一个机房里面等等。我们将这种扩展调度的需求放置到一个configmap里面,由K8S来实现统一的扩展调度器。

1
2
3
4
5
6
7
8
9
10
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 //必须调度的可用区
  • max-pod-on-node就可以限制整个集群在单个Node上调度Pod的数量
  • max-pod-in-zone可限制单个机房内调度Pod的数量
  • available-zone用来实现AZ的MUST IN语言,从而满足多机房特性

MUST IN语义的实现其实比较困难,这与K8S的调度机制有关。K8S是基于Pod进行调度,通过预选和优选来过滤出符合条件的Node。然而,PaaS服务很多时候需要有“全局视野”,局部调度最优但不代表全局调度就是最优的。

我们Redis Cluster采用了多个StatefulSet来管理分片,每个StatefulSet的0号Pod为主分片,1号Pod为复制分片,要求多机房的情况下主分片和复制分片不能在同一个机房。如果由K8S随机调度,那么有可能就会发生死锁问题,可调度的Node越少发生死锁的概率就会越高。下图详细说明发生这一问题的过程,已经目前我们的解决方法。

pod-schedule-deadlock.jpg

但是这种解决方式存在局限性和特殊性,并不能应用于所有的情况。问题的核心还是在于PaaS调度需要有“全局视野”,我们需要提前规划好Pod的分布情况,但是现有K8S的调度机制限制了这方面的能力,除非我们自定义PaaS的“专属调度器”。

很多PaaS服务都有存储的需求,这也有状态服务的一大特点。云原生架构推荐我们存储架构分离,但是这需要强大的网络支持,所以本地盘还是相对现实靠谱的方案。K8S原生对本地盘的支持不太好,主要在于Local PV需要手工管理和维护,为止K8S团队帮助我们开发了基于LVM的本地盘管理插件。

管理员只需要按如下方式来申明可用的盘和机器信息即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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保持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这样的统一适配组件,来满足权限、配额、计量计费的产品逻辑。

operator-shop.png

Operator应用商店

从今年8月开始做Operator,到年底已经上线了3个Operator(Redis Cluster、Kafka、Zookeeper),我们的研发速度大大提高。从大家的反馈来看也十分积极。经统计,编写Operator所需要的代码比起1.0和2.0的管控代码大概减少了80%以上,可用性提高的同时,运维成本还降低了很多。由于今年还未大规模部署,预计明年基于Operator托管的PaaS服务将遍地开花,我们人均的运维规模将大幅提升。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK