溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

面向K8s設(shè)計(jì)誤區(qū)是怎樣的

發(fā)布時(shí)間:2021-12-16 10:16:05 來源:億速云 閱讀:165 作者:柒染 欄目:云計(jì)算

這期內(nèi)容當(dāng)中小編將會給大家?guī)碛嘘P(guān) 面向K8s設(shè)計(jì)誤區(qū)是怎樣的,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

K8s 設(shè)計(jì)模式


Kubernetes 是一個具有普遍意義的容器編排工具,它提供了一套基于容器構(gòu)建分布式系統(tǒng)的基礎(chǔ)依賴,其意義等同于 Linux 在操作系統(tǒng)中的地位,可以認(rèn)為是分布式的操作系統(tǒng)。

自定義資源

K8s 提供了 Pod、Service、Volume 等一系列基礎(chǔ)資源定義,為了更好提供擴(kuò)展性,CRD 功能是在 1.7 版本被引入。


用戶可以根據(jù)自己的需求添加自定義的 Kubernetes 對象資源(CRD)。值得注意的是,這里用戶自己添加的 Kubernetes 對象資源都是 native 的都是一等公民,和 Kubernetes 中自帶的、原生的那些 Pod、Deployment 是同樣的對象資源。在 Kubernetes 的 API Server 看來,它們都是存在于 etcd 中的一等資源。

同時(shí),自定義資源和原生內(nèi)置的資源一樣,都可以用 kubectl  來去創(chuàng)建、查看,也享有 RBAC、安全功能。用戶可以開發(fā)自定義控制器來感知或者操作自定義資源的變化。

Operator

在自定義資源基礎(chǔ)上,如何實(shí)現(xiàn)自定義資源創(chuàng)建或更新時(shí)的邏輯行為,K8s Operator 提供了相應(yīng)的開發(fā)框架。Operator 通過擴(kuò)展 Kubernetes 定義 Custom Controller,list/watch 對應(yīng)的自定義資源,在對應(yīng)資源發(fā)生變化時(shí),觸發(fā)自定義的邏輯。


Operator 開發(fā)者可以像使用原生 API 進(jìn)行應(yīng)用管理一樣,通過聲明式的方式定義一組業(yè)務(wù)應(yīng)用的期望終態(tài),并且根據(jù)業(yè)務(wù)應(yīng)用的自身特點(diǎn)進(jìn)行相應(yīng)控制器邏輯編寫,以此完成對應(yīng)用運(yùn)行時(shí)刻生命周期的管理并持續(xù)維護(hù)與期望終態(tài)的一致性。

通俗的理解

CRD 是 K8s 標(biāo)準(zhǔn)化的資源擴(kuò)展能力,以 Java 為例,int、long、Map、Object 是 Java 內(nèi)置的類,用戶可以自定義 Class 實(shí)現(xiàn)類的擴(kuò)展,CRD 就是 K8s 中的自定義類,CR 就是對應(yīng)類的一個 instance。


Operator 模式 = 自定義類 + 觀察者模式,Operator 模式讓大家編寫 K8s 的擴(kuò)展變得非常簡單快捷,逐漸成為面向 K8s 設(shè)計(jì)的標(biāo)準(zhǔn)。
 

Operator 提供了標(biāo)準(zhǔn)化的設(shè)計(jì)流程:

  1. 使用 SDK 創(chuàng)建一個新的 Operator 項(xiàng)目;

  2. 通過添加自定義資源(CRD)定義新的資源 API;

  3. 指定使用 SDK API 來 watch 的資源;

  4. 自定義 Controller 實(shí)現(xiàn) K8s 協(xié)調(diào)(reconcile)邏輯;


有了錘子,看到的只有釘子


我們團(tuán)隊(duì)(KubeOne 團(tuán)隊(duì))一直在致力于解決復(fù)雜中間件應(yīng)用如何部署到 K8s,自然也是 Operator 模式的踐行者。經(jīng)歷了近 2 年的開發(fā),初步解決了中間件在各個環(huán)境 K8s 的部署,當(dāng)前中間也走了很多彎路,踩了很多坑。

KubeOne 內(nèi)核也經(jīng)歷 3 個大版本的迭代,前 2 次開發(fā)過程基本都是 follow Operator 標(biāo)準(zhǔn)開發(fā)流程進(jìn)行開發(fā)設(shè)計(jì)。遵循一個標(biāo)準(zhǔn)的、典型的 Operator 的設(shè)計(jì)過程,看上去一切都是這么的完美,但是每次設(shè)計(jì)都非常痛苦,踐行 Operator 模式之后,最值得反思和借鑒的就是”有了錘子,看到的只有釘子,簡單總結(jié)一下就是 4 個一切:

  1. 一切設(shè)計(jì)皆 YAML;

  2. 一切皆合一;

  3. 一切皆終態(tài);

  4. 一切交互皆 cr。

誤區(qū)1:一切設(shè)計(jì)皆 YAML


K8s 的 API 是 YAML 格式,Operator 設(shè)計(jì)流程也是讓大家首先定義 CRD,所以團(tuán)隊(duì)開始設(shè)計(jì)時(shí)直接采用了 YAML 格式。

案例

根據(jù)標(biāo)準(zhǔn)化流程,團(tuán)隊(duì)面向 YAML 設(shè)計(jì)流程大體如下:

1、先根據(jù)已知的數(shù)據(jù)初步整理一個大而全的 YAML,做一下初步的分類,例如應(yīng)用大概包含基礎(chǔ)信息,依賴服務(wù),運(yùn)維邏輯,監(jiān)控采集等,每個分類做一個子部分。

2、開會討論具體的內(nèi)容是否能滿足要求,結(jié)果每次開會都難以形成共識。

  • 因?yàn)榭偸怯行碌男枨鬂M足不了,在討論A時(shí),就有人提到 B、C、D,不斷有新的需求;

  • 每個部分的屬性非常難統(tǒng)一,因?yàn)椴煌膶?shí)現(xiàn)屬性差異較大;

  • 理解不一致,相同名字但使用時(shí)每個人的理解也不同; 

3、由于工期很緊,只能臨時(shí)妥協(xié),做一個中間態(tài),后面再進(jìn)一步優(yōu)化。 

4、后續(xù)優(yōu)化升級,相同的流程再來一遍,還是很難形成共識。


這是第 2 個版本的設(shè)計(jì):

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 標(biāo)識type應(yīng)該使用的屬性
    - name: "deployToK8SName"
      value: ""
    - name: "deployToNamespace"
      value: ${resources:namespace-resource.name}
  parameterValues:   //注 parameterValues標(biāo)識業(yè)務(wù)屬性
    - 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
        parameterValues:
          - name: name
            value: test-db
          - name: user
            value: test-user
          - name: password
            value: test-passwd
          - name: secret
            value: test-db-mysqlha-secret
      - name: service-slb
        type: apps.mwops.alibaba-inc.com/v1alpha1.slb
        mode: post-create
        parameterValues:
          - name: service
            value: "serviceA"
          - name: annotations
            value: "app:a,version:1.0"
          - name: external-ip
            value: 
      - name: service-resource2
        type: apps.mwops.alibaba-inc.com/v1alpha1.service
        parameterValues: 
          - name: second-domain
            value: edas.console
          - name: ports
            value: "80:80"
          - name: selectors
            value: "app:a,version:1.0"
          - name: type
            value: "loadbalance"
      - name: service-dns
        type: apps.mwops.alibaba-inc.com/v1alpha1.dns
        parameterValues:
          - name: domain
            value: edas.server.${global:domain}
          - name: vip
            value: ${resources:service-resource2.EXTERNAL-IP}
      - name: dns-resource
        type: apps.mwops.alibaba-inc.com/v1alpha1.dns
        parameterValues:
          - name: domain
            value: edas.console.${global:domain}
          - name: vip
            value: “127.0.0.1”
      - name: cni-resource
        type: apps.mwops.alibaba-inc.com/v1alpha1.cni
        parameterValues:
          - name: count
            value: 4
          - name: ip_list
            value: 
      - name: object-storage
        type: apps.mwops.alibaba-inc.com/v1alpha1.objectStorage.minio
        parameterValues:
          - name: namespace
            value: test-ns
          - name: username
            value: test-user
          - name: password
            value: test-password
          - name: storage-capacity
            value: 20Gi
          - name: secret-name
            value: minio-my-store-access-keys
          - name: endpoint
            value: minio-instance-external-service
      - name: redis
        type: apps.mwops.alibaba-inc.com/v1alpha1.database.redis
        parameterValues:
          - name: cpu
            value: 500m
          - name: memory
            value: 128Mi
          - name: password
            value: i_am_a_password
          - name: storage-capacity
            value: 20Gi
          - name: endpoint
            value: redis-redis-cluster 
      - name: accesskey
        type: apps.mwops.alibaba-inc.com/v1alpha1.accesskey
        parameterValues:
          - name: name
            value: default
          - name: userName
            value: ecs_test@aliyun.com
  exposes:
    - name: dns
      value: ${resources:dns-resource.domain}
    - name: db-endpoint
      valueFromConfigmap:
        name: ${resources:rds-resource.cm-name}
        expr: ${endpoint}:3306/${database}
    - name: ip_list
      value: ${resources:cni-resource.ip_list}
    - name: object-storage-endpoint
      value: ${resources:object-storage.endpoint}.${resource:namespace-resource.name}
    - 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}.${resource:namespace-resource.name}
    - name: redis-password
      value: ${resources:redis.password}

反思

這樣的痛苦難以用語言表達(dá),感覺一切都脫離了掌控,沒有統(tǒng)一的判斷標(biāo)準(zhǔn),設(shè)計(jì)標(biāo)準(zhǔn),公說公有理婆說婆有理,內(nèi)容一直加,字段一直改。事不過三,第三次設(shè)計(jì)時(shí),我們集體討論反思為什么這么難形成共識?為什么每個人理解不同?為什么總是在改?


結(jié)論很一致,沒有面向 YAML 的設(shè)計(jì),只有面向?qū)ο蟮脑O(shè)計(jì),設(shè)計(jì)語言也只有 UML,只有這些歷經(jīng)考驗(yàn)、成熟的設(shè)計(jì)方法論,才是最簡單也是最高效的。


從上面那個一個巨大無比的 YAML 大家可以體會我們設(shè)計(jì)的復(fù)雜,但是這還是不是最痛苦的。最痛苦的是大家拋棄了原有的設(shè)計(jì)流程及設(shè)計(jì)語言,試圖使用一個開放的 Map 來描述一切。當(dāng)設(shè)計(jì)沒有對象,也沒有關(guān)系,只剩下 Map 里一個個屬性,也就無所謂對錯,也無所謂優(yōu)劣。最后爭來爭去,最后不過是再加一個字段,爭了一個寂寞。

適用范圍

那 Operator 先設(shè)計(jì) CRD,再開發(fā) controller 的方式不正確嗎? 答案:部分正確。

1、適用場景

與 Java Class 相同,簡單對象不需要經(jīng)過復(fù)雜的設(shè)計(jì)流程,直接設(shè)計(jì) YAML 簡單高效。

2、不適用場景

在設(shè)計(jì)一個復(fù)雜的體系時(shí),例如:應(yīng)用管理,包含多個對象且對象之間有復(fù)雜的關(guān)系,有復(fù)雜的用戶故事,UML 和面向?qū)ο蟮脑O(shè)計(jì)就顯得非常重要。
設(shè)計(jì)時(shí)只考慮 UML 和領(lǐng)域語言,設(shè)計(jì)完成后,CRD 可以認(rèn)為是 Java 的 Class,或者是數(shù)據(jù)庫的表結(jié)構(gòu),只是最終要實(shí)現(xiàn)時(shí)的一種選擇。而且有很多對象不需要持久化,也不需要通過 Operator 機(jī)制觸發(fā)對應(yīng)的邏輯,就不需要設(shè)計(jì) CRD,而是直接實(shí)現(xiàn)一個 controller 即可。


YAML 是接口或 Class 聲明的一種格式化表達(dá),常規(guī) YAML 要盡可能小,盡可能職責(zé)單一,盡可能抽象。復(fù)雜的 YAML 是對簡單 CRD 資源的一種編排結(jié)果,提供類似一站式資源配套方案。


在第 3 個版本及 PaaS-Core 設(shè)計(jì)時(shí),我們就采取了如下的流程:

  1. UML 用例圖;

  2. 梳理用戶故事;

  3. 基于用戶故事對齊 Domain Object,確定關(guān)鍵的業(yè)務(wù)對象以及對象間關(guān)系;

  4. 需要 Operator 化的對象,每個對象描述為一個 CRD,當(dāng)然 CRD 缺乏接口、繼承等面向?qū)ο蟮哪芰?,可以通過其他方式曲線表達(dá);

  5. 不需要 Operator 化的對象,直接編寫 Controller;

誤區(qū)2:一切皆合一


為了保證一個應(yīng)用的終態(tài),或者為了使用 gitops 管理一個應(yīng)用,是否應(yīng)該把應(yīng)用相關(guān)的內(nèi)容都放入一個 CRD 或一個 IAC 文件?根據(jù) gitops 設(shè)計(jì),每次變更時(shí)需要下發(fā)整個文件?

案例

案例1: 應(yīng)用 WordPress,需要依賴一個 MySQL,終態(tài)如何定義?

apiVersion: apps.mwops.alibaba-inc.com/v1alpha1kind: AppDefinitionmetadata: labels: app: "WordPress" name: WordPress-1.0 //chart-name+chart-version namespace: kubeonespec: appName: WordPress //chart-name version: 1.0 //chart-version type: apps.mwops.alibaba-inc.com/v1alpha1.argo-helm parameterValues: //注 parameterValues標(biāo)識業(yè)務(wù)屬性 - name: "enableTenant" value: "1" - name: "CPU" value: "1" - name: "MEM" value: "2Gi" - name: "jvm" value: "flag;gc" - name: replicas value: 3 - name: connectstring valueFromConfigMap: name: ${resources:test-db.exposes.connectstring} expr: ${connectstring} - name: db_user_name valueFromSecret: .... resources: - name: test-db //創(chuàng)建一個新的DB type: apps.mwops.alibaba-inc.com/v1alpha1.database.mysqlha parameterValues: - name: cpu value: 2 - name: memory value: 4G - name: storage value: 20Gi  - name: username value: myusername - name: password value: i_am_a_password - name: dbname value: wordPress exposes: - name: connectstring - name: username - name: password exposes: - name: dns value: ...


上方的代碼是 wordPress 應(yīng)用的終態(tài)嗎?這個文件包含了應(yīng)用所需要的 DB 的定義和應(yīng)用的定義,只要一次下發(fā)就可以先創(chuàng)建對應(yīng)的數(shù)據(jù)庫,再把應(yīng)用拉起。
案例2:每次變更時(shí),直接修改整個 yaml 的部分內(nèi)容,修改后直接下發(fā)到 K8s,引起不必要的變更。例如:要從 3 個節(jié)點(diǎn)擴(kuò)容到 5 個節(jié)點(diǎn),修改上面 YAML 文件的 replicas 之后,需要下發(fā)整個 YAML。整個下發(fā)的 YAML 經(jīng)過二次解析成底層的 StatefulSet 或 Deployment,解析邏輯升級后,可能會產(chǎn)生不符合預(yù)期的變化,導(dǎo)致所有 Pod 重建。

反思

先回答第一個問題,上方 YAML 文件不是應(yīng)用的終態(tài),而是一個編排,此編排包含了 DB 的定義和應(yīng)用的定義。應(yīng)用的終態(tài)只應(yīng)該包含自己必須的依賴引用,而不包含依賴是如何創(chuàng)建的。因?yàn)檫@個依賴引用可以是新創(chuàng)建的,也可以是一個已有的,也可以是手工填寫的,依賴如何創(chuàng)建與應(yīng)用終態(tài)無關(guān)。

apiVersion: apps.mwops.alibaba-inc.com/v1alpha1
kind: AppDefinition
metadata:
  labels:
    app: "WordPress"
  name: WordPress-1.0 //chart-name+chart-version
  namespace: kubeone
spec:
  appName: WordPress  //chart-name
  version: 1.0 //chart-version
  name: WordPress-test
  type: apps.mwops.alibaba-inc.com/v1alpha1.argo-helm
  parameterValues:   //注 parameterValues標(biāo)識業(yè)務(wù)屬性
    - ....
  resources:
    - name: test-db-secret
        value: "wordPress1Secret">

創(chuàng)建一個應(yīng)用,就不能先創(chuàng)建 db,再創(chuàng)建應(yīng)用嗎?


可以的,多個對象之間依賴是通過編排實(shí)現(xiàn)的。編排有單個應(yīng)用創(chuàng)建的編排,也有一個復(fù)雜站點(diǎn)創(chuàng)建的編排。以 Argo 為例:

apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: wordPress-
spec:
  templates:
  - name: wordPress
    steps:
    # 創(chuàng)建db
    - - name: wordpress-db
      template: wordpress-db
      arguments:
         parameters: [{name: wordpress-db1}]
  # 創(chuàng)建應(yīng)用
     - - name: 
     template: wordpress
     arguments:
        parameters: [{db-sercet: wordpress-db1}]

針對第 2 個案例,是否每次交互都需要下發(fā)全部完整的 YAML?
 

答案: 

  1. 編排是一次性的配置,編排文件下發(fā)一次之后,后續(xù)操作都是操作單個對象,例如:變更時(shí),只會單獨(dú)變更 wordPress,或單獨(dú)變更 wordPressDB,而不會一次性同時(shí)變更 2 個對象。 

  2. 單獨(dú)變更應(yīng)用時(shí),是否需要下發(fā)整個終態(tài) YAML,這個要根據(jù)實(shí)際情況進(jìn)行設(shè)計(jì),值得大家思考。后面會提出針對整個應(yīng)用生命周期狀態(tài)機(jī)的設(shè)計(jì),里面有詳細(xì)的解釋。

適用范圍

1、適用場景

CRD 或 IAC 定義時(shí),單個對象的終態(tài)只應(yīng)該包含自身及對依賴的引用。與面向?qū)ο蟮脑O(shè)計(jì)相同,我們不應(yīng)該把所有類的定義都放到一個 Class 里面。

2、不適用場景
多個對象要一次性創(chuàng)建,并且需要按照順序創(chuàng)建,存在依賴關(guān)系,需要通過編排層實(shí)現(xiàn)。

誤區(qū)3:一切皆終態(tài)


體驗(yàn)了 K8s 的終態(tài)化之后,大家在設(shè)計(jì)時(shí)言必稱終態(tài),仿佛不能用上終態(tài)設(shè)計(jì),不下發(fā)一個 YAML 聲明對象的終態(tài)就是落伍,就是上一代的設(shè)計(jì)。

案例
案例1:應(yīng)用編排
還是以 WordPress 為例,將 WordPressDB 和 WordPress 放在一起進(jìn)行部署,先部署 DB,再創(chuàng)建應(yīng)用。示例 YAML 同上。
 

案例2:應(yīng)用發(fā)布
應(yīng)用第一次部署及后續(xù)的升級直接下發(fā)一個完整的應(yīng)用 YAML,系統(tǒng)會自動幫你到達(dá)終態(tài)。但為了能夠細(xì)粒度控制發(fā)布的流程,努力在 Deployment 或 StatefulSet 上下功夫,進(jìn)行 partition 的控制,試圖在終態(tài)里增加一點(diǎn)點(diǎn)的交互性。

反思

說到終態(tài),必然要提到命令式、聲明式編程,終態(tài)其實(shí)就是聲明式最終的執(zhí)行結(jié)果。我們先回顧一下命令式、終態(tài)式編程。

1、命令式編程
命令式編程的主要思想是關(guān)注計(jì)算機(jī)執(zhí)行的步驟,即一步一步告訴計(jì)算機(jī)先做什么再做什么。
比如:如果你想在一個數(shù)字集合 collection(變量名) 中篩選大于 5 的數(shù)字,你需要這樣告訴計(jì)算機(jī):

  • 第一步,創(chuàng)建一個存儲結(jié)果的集合變量 results;

  • 第二步,遍歷這個數(shù)字集合 collection;

  • 第三步,一個一個地判斷每個數(shù)字是不是大于 5,如果是就將這個數(shù)字添加到結(jié)果集合變量 results 中。

代碼實(shí)現(xiàn)如下:

List results = new List();
foreach(var num in collection)
{
if (num > 5)
results.Add(num);
}


很明顯,這個樣子的代碼是很常見的一種,不管你用的是 C、C++ 還是 C#、Java、Javascript、BASIC、Python、Ruby 等,你都可以以這個方式寫。

2、聲明式編程

聲明式編程是以數(shù)據(jù)結(jié)構(gòu)的形式來表達(dá)程序執(zhí)行的邏輯。它的主要思想是告訴計(jì)算機(jī)應(yīng)該做什么,但不指定具體要怎么做。
 

SQL 語句就是最明顯的一種聲明式編程的例子,例如:


SELECT * FROM collection WHERE num > 5


除了 SQL,網(wǎng)頁編程中用到的 HTML 和 CSS 也都屬于聲明式編程。


通過觀察聲明式編程的代碼我們可以發(fā)現(xiàn)它有一個特點(diǎn)是它不需要創(chuàng)建變量用來存儲數(shù)據(jù)。
另一個特點(diǎn)是它不包含循環(huán)控制的代碼如 for, while。
換言之:

  • 命令式編程:命令“機(jī)器”如何去做事情(how),這樣不管你想要的是什么(what),它都會按照你的命令實(shí)現(xiàn)。

  • 聲明式編程:告訴“機(jī)器”你想要的是什么(what),讓機(jī)器想出如何去做(how)。

當(dāng)接口越是在表達(dá)“要什么”,就是越聲明式;越是在表達(dá)“要怎樣”,就是越命令式。SQL就是在表達(dá)要什么(數(shù)據(jù)),而不是表達(dá)怎么弄出我要的數(shù)據(jù),所以它就很“聲明式”。


簡單的說,接口的表述方式越接近人類語言——詞匯的串行連接(一個詞匯實(shí)際上是一個概念)——就越“聲明式”;越接近計(jì)算機(jī)語言——“順序+分支+循環(huán)”的操作流程——就越“命令式”。


越是聲明式,意味著下層要做更多的東西,或者說能力越強(qiáng),也意味著效率的損失。越是命令式,意味著上層對下層有更多的操作空間,可以按照自己特定的需求要求下層按照某種方式來處理。


簡單的講,Imperative Programming Language (命令式語言)一般都有 control flow, 并且具有可以和其他設(shè)備進(jìn)行交互的能力。而 Declarative Programming language (聲明式語言) 一般做不到這些。
基于以上的分析,編排或工作流本質(zhì)是一個流程

性控制的過程,一般是一次性的過程,無需強(qiáng)行終態(tài)化,而且建站編排執(zhí)行結(jié)束后,不能保持終態(tài),因?yàn)楹罄m(xù)會根據(jù)單個應(yīng)用進(jìn)行發(fā)布和升級。案例1是一個典型的編排,只是一次性的創(chuàng)建了 2 個對象 DB 和應(yīng)用的終態(tài)。


應(yīng)用發(fā)布其實(shí)是通過一個發(fā)布單或工作流,控制 2 個不同版本的應(yīng)用節(jié)點(diǎn)和流量的終態(tài)化的過程,不應(yīng)該是應(yīng)用終態(tài)的一部分,而是一個獨(dú)立的控制流程。


適用范圍

聲明式或終態(tài)設(shè)計(jì)。

1、適用場景

無過多交互,無需關(guān)注底層實(shí)現(xiàn)的場景,即把聲明提供給系統(tǒng)后,系統(tǒng)會自動化達(dá)到聲明所要求的狀態(tài),而不需要人為干預(yù)。

2、不適用場景

一次性的流程編排,有頻繁交互的控制流程。
命令式和聲明式本就是 2 種互補(bǔ)的編程模式,就像有了面向?qū)ο笾?,有人就鄙視面向過程的編程,現(xiàn)在有了聲明式,就開始鄙視命令式編程,那一屋!

誤區(qū)4:一切交互皆 cr


因?yàn)?K8s 的 API 交互只能通過 YAML,導(dǎo)致大家的設(shè)計(jì)都以 cr 為中心,所有的交互都設(shè)計(jì)為下發(fā)一個 cr,通過 watch cr 觸發(fā)對應(yīng)的邏輯。

案例

  • 調(diào)用一個 http 接口或 function,需要下發(fā)一個 cr;

  • 應(yīng)用 crud 都下發(fā)完整 cr;

反思

案例1:是否所有的邏輯都需要下發(fā)一個 cr?
下發(fā) cr 其實(shí)做了比較多的事情,流程很長,效率并不高,流程如下:

  • 通過 API 傳入 cr,cr 保存到 etcd;

  • 觸發(fā) informer;

  • controller 接收到對應(yīng)的事件,觸發(fā)邏輯;

  • 更新 cr 狀態(tài);

  • 清理 cr,否則會占用 etcd 存儲;


如果需要頻繁的調(diào)用對應(yīng)的接口,盡量通過 sdk 直接調(diào)用。

案例2:

K8s 對 YAML 操作命令有 create、apply、patch、delete、get 等,但一個應(yīng)用的生命周期狀態(tài)機(jī)不只是這幾個命令可以涵蓋,我們比較一下應(yīng)用狀態(tài)機(jī)(上)和 YAML 狀態(tài)機(jī)(下):


不同的有狀態(tài)應(yīng)用,在收到不同的指令,需要觸發(fā)不同的邏輯,例如:MQ 在收到 stop 指令時(shí),需要先停寫,檢查數(shù)據(jù)是否消費(fèi)完成。如果只是通過 YAML 狀態(tài)機(jī)是無法涵蓋應(yīng)用狀態(tài)機(jī)相關(guān)的 event,所以我們必須打破下發(fā) cr 的模式。對于應(yīng)用來說,理想的交互方式是通過 event driven 應(yīng)用狀態(tài)機(jī)的變化,狀態(tài)發(fā)生變換時(shí)觸發(fā)對應(yīng)的邏輯。

適用范圍

1、適用場景

需要持久化,保持終態(tài)的數(shù)據(jù)。

2、不適用場景
  • 高頻的服務(wù)調(diào)用,無需持久化的數(shù)據(jù)。

  • 復(fù)雜狀態(tài)機(jī)的驅(qū)動。

上述就是小編為大家分享的 面向K8s設(shè)計(jì)誤區(qū)是怎樣的了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

k8s
AI