面向 K8s 设计误区
K8s 设计模式
自定义资源
Operator

通俗的理解
使用 SDK 创建一个新的 Operator 项目; 通过添加自定义资源(CRD)定义新的资源 API; 指定使用 SDK API 来 watch 的资源; 自定义 Controller 实现 K8s 协调(reconcile)逻辑;
有了锤子,看到的只有钉子
一切设计皆 YAML; 一切皆合一; 一切皆终态; 一切交互皆 cr。
误区1:一切设计皆 YAML
案例
先根据已知的数据初步整理一个大而全的 YAML,做一下初步的分类,例如应用大概包含基础信息,依赖服务,运维逻辑,监控采集等,每个分类做一个子部分。 开会讨论具体的内容是否能满足要求,结果每次开会都难以形成共识。
因为总是有新的需求满足不了,在讨论A时,就有人提到 B、C、D,不断有新的需求; 每个部分的属性非常难统一,因为不同的实现属性差异较大; 理解不一致,相同名字但使用时每个人的理解也不同;
由于工期很紧,只能临时妥协,做一个中间态,后面再进一步优化。 后续优化升级,相同的流程再来一遍,还是很难形成共识。
apiVersion: apps.mwops.alibaba-inc.com/v1alpha1kind: AppDefinitionmetadata:labels:app: "A"name: A-1.0 //chart-name+chart-versionnamespace: kubeonespec:appName: A //chart-nameversion: 1.0 //chart-versiontype: apps.mwops.alibaba-inc.com/v1alpha1.argo-helmworkloadSettings: //注 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.ipvalue: ${resources:fileserver_edas.ip}- name: DB_NAMEvalueFromConfigMap:name: ${resources:rds-resource.cm-name}expr: ${database}- name: DB_PASSWORDvalueFromSecret:name: ${instancename}-rds-secretexpr: ${password}- name: object-storage-endpointvalue: ${resources:object-storage.endpoint}- name: object-storage-usernamevalueFromSecret:name: ${resources:object-storage.secret-name}expr: ${username}- name: object-storage-passwordvalueFromSecret:name: ${resources:object-storage.secret-name}expr: ${password}- name: redis-endpointvalue: ${resources:redis.endpoint}- name: redis-passwordvalue: ${resources:redis.password}resources:- name: tolerationstype: apps.mwops.alibaba-inc.com/tolerationsparameterValues:- name: keyvalue: "sigma.ali/is-ecs"- name: keyvalue: "sigma.ali/resource-pool"- name: namespace-resourcetype: apps.mwops.alibaba-inc.com/v1alpha1.namespaceparameterValues:- name: namevalue: edas- name: fileserver-edastype: apps.mwops.alibaba-inc.com/v1alpha1.database.vipparameterValues:- name: portvalue: 21,80,8080,5000- name: src_portvalue: 21,80,8080,5000- name: typevalue: ClusterIP- name: check_typevalue: ""- name: urivalue: ""- name: ipvalue: ""- name: test-dbtype: apps.mwops.alibaba-inc.com/v1alpha1.database.mysqlhaparameterValues:- name: namevalue: test-db- name: uservalue: test-user- name: passwordvalue: test-passwd- name: secretvalue: test-db-mysqlha-secret- name: service-slbtype: apps.mwops.alibaba-inc.com/v1alpha1.slbmode: post-createparameterValues:- name: servicevalue: "serviceA"- name: annotationsvalue: "app:a,version:1.0"- name: external-ipvalue:- name: service-resource2type: apps.mwops.alibaba-inc.com/v1alpha1.serviceparameterValues:- name: second-domainvalue: edas.console- name: portsvalue: "80:80"- name: selectorsvalue: "app:a,version:1.0"- name: typevalue: "loadbalance"- name: service-dnstype: apps.mwops.alibaba-inc.com/v1alpha1.dnsparameterValues:- name: domainvalue: edas.server.${global:domain}- name: vipvalue: ${resources:service-resource2.EXTERNAL-IP}- name: dns-resourcetype: apps.mwops.alibaba-inc.com/v1alpha1.dnsparameterValues:- name: domainvalue: edas.console.${global:domain}- name: vipvalue: “127.0.0.1”- name: cni-resourcetype: apps.mwops.alibaba-inc.com/v1alpha1.cniparameterValues:- name: countvalue: 4- name: ip_listvalue:- name: object-storagetype: apps.mwops.alibaba-inc.com/v1alpha1.objectStorage.minioparameterValues:- name: namespacevalue: test-ns- name: usernamevalue: test-user- name: passwordvalue: test-password- name: storage-capacityvalue: 20Gi- name: secret-namevalue: minio-my-store-access-keys- name: endpointvalue: minio-instance-external-service- name: redistype: apps.mwops.alibaba-inc.com/v1alpha1.database.redisparameterValues:- name: cpuvalue: 500m- name: memoryvalue: 128Mi- name: passwordvalue: i_am_a_password- name: storage-capacityvalue: 20Gi- name: endpointvalue: redis-redis-cluster- name: accesskeytype: apps.mwops.alibaba-inc.com/v1alpha1.accesskeyparameterValues:- name: namevalue: default- name: userNamevalue: ecs_test@aliyun.comexposes:- name: dnsvalue: ${resources:dns-resource.domain}- name: db-endpointvalueFromConfigmap:name: ${resources:rds-resource.cm-name}expr: ${endpoint}:3306/${database}- name: ip_listvalue: ${resources:cni-resource.ip_list}- name: object-storage-endpointvalue: ${resources:object-storage.endpoint}.${resource:namespace-resource.name}- name: object-storage-usernamevalueFromSecret:name: ${resources:object-storage.secret-name}expr: ${username}- name: object-storage-passwordvalueFromSecret:name: ${resources:object-storage.secret-name}expr: ${password}- name: redis-endpointvalue: ${resources:redis.endpoint}.${resource:namespace-resource.name}- name: redis-passwordvalue: ${resources:redis.password}
反思
适用范围
1、适用场景
2、不适用场景
UML 用例图; 梳理用户故事; 基于用户故事对齐 Domain Object,确定关键的业务对象以及对象间关系; 需要 Operator 化的对象,每个对象描述为一个 CRD,当然 CRD 缺乏接口、继承等面向对象的能力,可以通过其他方式曲线表达; 不需要 Operator 化的对象,直接编写 Controller;
误区2:一切皆合一
案例
apiVersion: apps.mwops.alibaba-inc.com/v1alpha1kind: AppDefinitionmetadata:labels:app: "WordPress"name: WordPress-1.0 //chart-name+chart-versionnamespace: kubeonespec:appName: WordPress //chart-nameversion: 1.0 //chart-versiontype: apps.mwops.alibaba-inc.com/v1alpha1.argo-helmparameterValues: //注 parameterValues标识业务属性- name: "enableTenant"value: "1"- name: "CPU"value: "1"- name: "MEM"value: "2Gi"- name: "jvm"value: "flag;gc"- name: replicasvalue: 3- name: connectstringvalueFromConfigMap:name: ${resources:test-db.exposes.connectstring}expr: ${connectstring}- name: db_user_namevalueFromSecret:....resources:- name: test-db //创建一个新的DBtype: apps.mwops.alibaba-inc.com/v1alpha1.database.mysqlhaparameterValues:- name: cpuvalue: 2- name: memoryvalue: 4G- name: storagevalue: 20Gi- name: usernamevalue: myusername- name: passwordvalue: i_am_a_password- name: dbnamevalue: wordPressexposes:- name: connectstring- name: username- name: passwordexposes:- name: dnsvalue: ...
反思
apiVersion: apps.mwops.alibaba-inc.com/v1alpha1kind: AppDefinitionmetadata:labels:app: "WordPress"name: WordPress-1.0 //chart-name+chart-versionnamespace: kubeonespec:appName: WordPress //chart-nameversion: 1.0 //chart-versionname: WordPress-testtype: apps.mwops.alibaba-inc.com/v1alpha1.argo-helmparameterValues: //注 parameterValues标识业务属性- ....resources:- name: test-db-secretvalue: "wordPress1Secret" //引用已有的secretexposes:- name: dnsvalue: ...
apiVersion: argoproj.io/v1alpha1kind: Workflowmetadata:generateName: wordPress-spec:templates:- name: wordPresssteps:# 创建db- - name: wordpress-dbtemplate: wordpress-dbarguments:parameters: [{name: wordpress-db1}]# 创建应用- - name:template: wordpressarguments:parameters: [{db-sercet: wordpress-db1}]
编排是一次性的配置,编排文件下发一次之后,后续操作都是操作单个对象,例如:变更时,只会单独变更 wordPress,或单独变更 wordPressDB,而不会一次性同时变更 2 个对象。 单独变更应用时,是否需要下发整个终态 YAML,这个要根据实际情况进行设计,值得大家思考。后面会提出针对整个应用生命周期状态机的设计,里面有详细的解释。
适用范围
1、适用场景
2、不适用场景
误区3:一切皆终态
案例
反思
1、命令式编程
第一步,创建一个存储结果的集合变量 results;
第二步,遍历这个数字集合 collection;
第三步,一个一个地判断每个数字是不是大于 5,如果是就将这个数字添加到结果集合变量 results 中。
List results = new List();foreach(var num in collection){if (num > 5)results.Add(num);}
2、声明式编程
命令式编程:命令“机器”如何去做事情(how),这样不管你想要的是什么(what),它都会按照你的命令实现。
声明式编程:告诉“机器”你想要的是什么(what),让机器想出如何去做(how)。


适用范围
1、适用场景
2、不适用场景
误区4:一切交互皆 cr
案例
调用一个 http 接口或 function,需要下发一个 cr;
应用 crud 都下发完整 cr;
反思
案例1:是否所有的逻辑都需要下发一个 cr?
通过 API 传入 cr,cr 保存到 etcd;
触发 informer;
controller 接收到对应的事件,触发逻辑;
更新 cr 状态;
清理 cr,否则会占用 etcd 存储;

案例2:

适用范围
1、适用场景
2、不适用场景
高频的服务调用,无需持久化的数据。
复杂状态机的驱动。
总结
揭秘 Kubernetes Operator: http://www.dockone.io/article/8769 声明式编程和命令式编程有什么区别 : https://www.zhihu.com/question/22285830 如何在 Kubernetes 中编写自定义控制器: https://www.sohu.com/a/363619791_198222
- END -
「技术分享」某种程度上,是让作者和读者,不那么孤独的东西。欢迎关注我的微信公众号:「Kirito的技术分享」
评论
