面向K8s设计误区 K8s设计模式 有了锤子,看到的只有钉子 误区1 一切设计皆yaml

简介: K8s 取其精华去其糟粕,是我们程序员应该做的事情。

 

Kubernetes是一个具有普遍意义的容器编排工具,它提供了一套基于容器构建分布式系统的基础依赖,其意义等同于Linux在操作系统中的地位,可以认为是分布式的操作系统。

 

自定义资源

 

版本被引入。

 

RBAC、安全功能。用户可以开发自定义控制器来感知或者操作自定义资源的变化。

 

Operator

 

在自定义资源基础上,如何实现自定义资源创建或更新时的逻辑行为,K8s Operator提供了相应的开发框架。Operator通过扩展Kubernetes定义Custom Controller,list/watch 对应的自定义资源,在对应资源发生变化时,触发自定义的逻辑。

 

Operator 开发者可以像使用原生 API 进行应用管理一样,通过声明式的方式定义一组业务应用的期望终态,并且根据业务应用的自身特点进行相应控制器逻辑编写,以此完成对应用运行时刻生命周期的管理并持续维护与期望终态的一致性。

通俗的理解

 

CRD是K8s标准化的资源扩展能力,以java为例,int、long、Map、Object是java内置的类,用户可以自定义Class实现类的扩展,CRD就是K8s中的自定义类,CR就是对应类的一个instance。

 

Operator模式 = 自定义类 + 观察者模式,Operator模式让大家编写K8s的扩展变得非常简单快捷,逐渐成为面向K8s设计的标准。

 

Operator提供了标准化的设计流程:

 

  1. 使用 SDK 创建一个新的 Operator 项目
  2. 通过添加自定义资源(CRD)定义新的资源 API
  3. 指定使用 SDK API 来 watch 的资源
  4. 自定义Controller实现K8s协调(reconcile)逻辑

有了锤子,看到的只有钉子

 

我们团队(KubeOne团队)一直在致力于解决复杂中间件应用如何部署到K8s,自然也是Operator模式的践行者。经历了近2年的开发,初步解决了中间件在各个环境K8s的部署,当前中间也走了很多弯路,踩了很多坑。

 

KubeOne内核也经历3个大版本的迭代,前2次开发过程基本都是follow Operator标准开发流程进行开发设计。遵循一个标准的、典型的Operator的设计过程,看上去一切都是这么的完美,但是每次设计都非常痛苦,践行Operator模式之后,最值得反思和借鉴的就是”有了锤子,看到的只有钉子“,简单总结一下就是4个一切:

 

  1. 一切设计皆yaml
  2. 一切皆合一
  3. 一切皆终态
  4. 一切交互皆cr

 

误区1 一切设计皆yaml

 

K8s的API是yaml格式,Operator设计流程也是让大家首先定义crd,所以团队开始设计时直接采用了yaml格式。

 

案例

 

根据标准化流程,团队面向yaml设计流程大体如下:

 

  1. 先根据已知的数据初步整理一个大而全的yaml,做一下初步的分类,例如应用大概包含基础信息,依赖服务,运维逻辑,监控采集等,每个分类做一个子部分
  2. 开会讨论具体的内容是否能满足要求,结果每次开会都难以形成共识
  • 因为总是有新的需求满足不了,在讨论A时,就有人提到B、C、D,不断有新的需求
  • 每个部分的属性非常难统一,因为不同的实现属性差异较大
  • 理解不一致,相同名字但使用时每个人的理解也不同
  1. 由于工期很紧,只能临时妥协,做一个中间态,后面再进一步优化
  2. 后续优化升级,相同的流程再来一遍,还是很难形成共识

 

这是第2个版本的设计:

apiVersion: apps.mwops.alibaba-inc.com/v1alpha1
kind: AppDefinition
metadata:
  labels:
    app: "A"
  name: A-1.0 //chart-name+chart-version
  namespace: kubeone
spec:
  appName: A  //chart-name
  version: 1.0 //chart-version
  type: apps.mwops.alibaba-inc.com/v1alpha1.argo-helm
  workloadSettings:   //注 workloadSettings 标识type应该使用的属性
    - name: "deployToK8SName"
      value: ""
    - name: "deployToNamespace"
      value: ${resources:namespace-resource.name}
  parameterValues:   //注 parameterValues标识业务属性
    - name: "enableTenant"
      value: "1"
    - name: "CPU"
      value: "1"
    - name: "MEM"
      value: "2Gi"
    - name: "jvm"
      value: "flag;gc"
    - name: vip.fileserver-edas.ip
      value: ${resources:fileserver_edas.ip}
    - name: DB_NAME
      valueFromConfigMap:
        name: ${resources:rds-resource.cm-name}
        expr: ${database}
    - name: DB_PASSWORD
      valueFromSecret:
          name: ${instancename}-rds-secret
          expr: ${password}
    - name: object-storage-endpoint
      value: ${resources:object-storage.endpoint}
    - name: object-storage-username
      valueFromSecret:
          name: ${resources:object-storage.secret-name}
          expr: ${username}
    - name: object-storage-password
      valueFromSecret:
          name: ${resources:object-storage.secret-name}
          expr: ${password}
    - name: redis-endpoint
      value: ${resources:redis.endpoint}
    - name: redis-password
      value: ${resources:redis.password}
  resources:
      - name: tolerations
        type: apps.mwops.alibaba-inc.com/tolerations
        parameterValues:
           - name: key
             value: "sigma.ali/is-ecs"
           - name: key
             value: "sigma.ali/resource-pool"
      - name: namespace-resource
        type: apps.mwops.alibaba-inc.com/v1alpha1.namespace
        parameterValues:
          - name: name
            value: edas
      - name: fileserver-edas
        type: apps.mwops.alibaba-inc.com/v1alpha1.database.vip
        parameterValues:
          - name: port
            value: 21,80,8080,5000
          - name: src_port
            value: 21,80,8080,5000
          - name: type
            value: ClusterIP
          - name: check_type
            value: ""
          - name: uri
            value: ""
          - name: ip
            value: ""
      - name: test-db
        type: apps.mwops.alibaba-inc.com/v1alpha1.database.mysqlha