自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一篇帶你kubebuilder 實(shí)戰(zhàn): CRUD

開發(fā) 項(xiàng)目管理
這篇文章我們實(shí)現(xiàn)了一個(gè) NodePool 的 Operator 用來控制節(jié)點(diǎn)以及對(duì)應(yīng)的 RuntimeClass,除了基本的 CURD 之外我們還學(xué)習(xí)了預(yù)刪除和 OwnerReference 的使用方式。

[[398943]]

在前兩天的文章當(dāng)中我們搭建好了本地的 K8s 開發(fā)環(huán)境,并且了解了 kubebuilder 的基本使用方法,今天就從我之前遇到的一個(gè)真實(shí)需求出發(fā)完整的寫一個(gè) Operator

需求分析

背景

在 K8s 運(yùn)行的過程當(dāng)中我們發(fā)現(xiàn)總是存在一些業(yè)務(wù)由于安全,可用性等各種各樣的原因需要跑在一些獨(dú)立的節(jié)點(diǎn)池上,這些節(jié)點(diǎn)池里面可能再劃分一些小的節(jié)點(diǎn)池。

雖然我們可以使用 Taint,Label對(duì)節(jié)點(diǎn)進(jìn)行劃分,使用 nodeSelector 和 tolerations讓 Pod 跑在指定的節(jié)點(diǎn)上,但是這樣主要會(huì)有兩個(gè)問題:

  • 一個(gè)是管理上不方便,在實(shí)際的使用過程中我們會(huì)發(fā)現(xiàn)存在錯(cuò)配漏配的情況 雖然在 v1.16 之后也可以使用 RuntimeClass來簡(jiǎn)化 pod 的配置,但是 RuntimClass 并不和節(jié)點(diǎn)進(jìn)行關(guān)聯(lián)[^1]
  • 另一個(gè)就是拓展需求不好實(shí)現(xiàn),例如我們想要的某個(gè)節(jié)點(diǎn)屬于網(wǎng)段或者當(dāng)節(jié)點(diǎn)加入這個(gè)節(jié)點(diǎn)池自動(dòng)開墻等

需求

1.對(duì)應(yīng)用來說我們可以在創(chuàng)建或者更新應(yīng)用時(shí)便捷的選擇的對(duì)應(yīng)的節(jié)點(diǎn)池,默認(rèn)情況下不需要進(jìn)行選擇

2.對(duì)于節(jié)點(diǎn)池來說

  • 一個(gè)節(jié)點(diǎn)池可能有多個(gè)節(jié)點(diǎn),并且一個(gè)節(jié)點(diǎn)也可能同時(shí)屬于多個(gè)節(jié)點(diǎn)池
  • 不同節(jié)點(diǎn)池的標(biāo)簽、污點(diǎn)信息可能不同
  • 后續(xù)可以支持不同節(jié)點(diǎn)池的機(jī)型、安全組或者防火墻策略不同等
  • MVP 版本支持標(biāo)簽、污點(diǎn)即可

方案設(shè)計(jì)

節(jié)點(diǎn)池資源如下

  1. apiVersion: nodes.lailin.xyz/v1 
  2. kind: NodePool 
  3. metadata: 
  4.   name: test 
  5. spec: 
  6.   taints: 
  7.    - key: node-pool.lailin.xyz 
  8.      value: test 
  9.      effect: NoSchedule 
  10.   labels: 
  11.    node-pool.lailin.xyz/test: "" 

節(jié)點(diǎn)和節(jié)點(diǎn)池之間的映射如何建立?

  • 我們可以利用 node-role.kubernetes.io/xxx=""標(biāo)簽和節(jié)點(diǎn)池建立映射
  • xxx 和節(jié)點(diǎn)池的name相對(duì)應(yīng)
  • 使用這個(gè)標(biāo)簽的好處是,使用 kubectl get no可以很方便的看到節(jié)點(diǎn)屬于哪個(gè)節(jié)點(diǎn)池
  1. NAME                 STATUS                     ROLES                  AGE    VERSION 
  2. kind-control-plane   Ready,SchedulingDisabled   control-plane,master   2d2h   v1.20.2 

Pod 和節(jié)點(diǎn)池之間的映射如何建立?

  • 我們可以復(fù)用 RuntimeClass對(duì)象,當(dāng)創(chuàng)建一個(gè) NodePool 對(duì)象的時(shí)候我們就創(chuàng)建一個(gè)對(duì)應(yīng)的 RuntimeClass 對(duì)象,然后在 Pod 中只需要加上 runtimeClassName: myclass 就可以了

注: 對(duì)于 MVP 版本來說其實(shí)我們不需要使用自定義資源,只需要通過標(biāo)簽和 RuntimeClass 結(jié)合就能滿足需求,但是這里為了展示一個(gè)完整的流程,我們使用了自定義資源

開發(fā)

創(chuàng)建項(xiàng)目

  1. # 初始化項(xiàng)目 
  2. kubebuilder init --repo github.com/mohuishou/blog-code/k8s-operator/03-node-pool-operator --domain lailin.xyz --skip-go-version-check 
  3.  
  4. # 創(chuàng)建 api 
  5. kubebuilder create api --group nodes --version v1 --kind NodePool 

定義對(duì)象

  1. // NodePoolSpec 節(jié)點(diǎn)池 
  2. type NodePoolSpec struct { 
  3.  // Taints 污點(diǎn) 
  4.  Taints []v1.Taint `json:"taints,omitempty"
  5.  
  6.  // Labels 標(biāo)簽 
  7.  Labels map[string]string `json:"labels,omitempty"

創(chuàng)建

我們實(shí)現(xiàn) Reconcile 函數(shù),req會(huì)返回當(dāng)前變更的對(duì)象的 Namespace和Name信息,有這兩個(gè)信息,我們就可以獲取到這個(gè)對(duì)象了,所以我們的操作就是

1.獲取 NodePool 對(duì)象

2.通過 NodePool 對(duì)象生成對(duì)應(yīng)的 Label 查找是否已經(jīng)存在對(duì)應(yīng)的 Label 的 Node

  • 如果存在,就給對(duì)應(yīng)的 Node 加上對(duì)應(yīng)的 Taint 和 Label
  • 如果不存在就跳過

3.通過 NodePool 生成對(duì)應(yīng)的 RuntimeClass ,查找是否已經(jīng)存在對(duì)應(yīng)的 RuntimeClass

  • 如果不存在就新建
  • 存在就跳過
  1. func (r *NodePoolReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 
  2.  _ = r.Log.WithValues("nodepool", req.NamespacedName) 
  3.  // 獲取對(duì)象 
  4.  pool := &nodesv1.NodePool{} 
  5.  if err := r.Get(ctx, req.NamespacedName, pool); err != nil { 
  6.   return ctrl.Result{}, err 
  7.  } 
  8.  
  9.  var nodes corev1.NodeList 
  10.  
  11.  // 查看是否存在對(duì)應(yīng)的節(jié)點(diǎn),如果存在那么就給這些節(jié)點(diǎn)加上數(shù)據(jù) 
  12.  err := r.List(ctx, &nodes, &client.ListOptions{LabelSelector: pool.NodeLabelSelector()}) 
  13.  if client.IgnoreNotFound(err) != nil { 
  14.   return ctrl.Result{}, err 
  15.  } 
  16.  
  17.  if len(nodes.Items) > 0 { 
  18.   r.Log.Info("find nodes, will merge data""nodes", len(nodes.Items)) 
  19.   for _, n := range nodes.Items { 
  20.    n := n 
  21.    err := r.Patch(ctx, pool.Spec.ApplyNode(n), client.Merge) 
  22.    if err != nil { 
  23.     return ctrl.Result{}, err 
  24.    } 
  25.   } 
  26.  } 
  27.  
  28.  var runtimeClass v1beta1.RuntimeClass 
  29.  err = r.Get(ctx, client.ObjectKeyFromObject(pool.RuntimeClass()), &runtimeClass) 
  30.  if client.IgnoreNotFound(err) != nil { 
  31.   return ctrl.Result{}, err 
  32.  } 
  33.  
  34.  // 如果不存在創(chuàng)建一個(gè)新的 
  35.  if runtimeClass.Name == "" { 
  36.   err = r.Create(ctx, pool.RuntimeClass()) 
  37.   if err != nil { 
  38.    return ctrl.Result{}, err 
  39.   } 
  40.  } 
  41.  
  42.  return ctrl.Result{}, nil 

更新

相信聰明的你已經(jīng)發(fā)現(xiàn)上面的創(chuàng)建邏輯存在很多的問題

1.如果 NodePool 對(duì)象更新,Node 是否更新對(duì)應(yīng)的 Taint 和Label

  • 如果 NodePool 刪除了一個(gè) Label 或Taint對(duì)應(yīng) Node 的Label或Taint 是否需要?jiǎng)h除,怎么刪除?

2.如果 NodePool 對(duì)象更新,RuntimeClass是否更新,如何更新

我們 MVP 版本實(shí)現(xiàn)可以簡(jiǎn)單一些,我們約定,所有屬于 NodePool 的節(jié)點(diǎn) Tanit 和Label信息都應(yīng)該由 NodePool管理,key 包含 kubernetes 標(biāo)簽污點(diǎn)除外

  1. func (r *NodePoolReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 
  2.   // .... 
  3.    
  4.  if len(nodes.Items) > 0 { 
  5.   r.Log.Info("find nodes, will merge data""nodes", len(nodes.Items)) 
  6.   for _, n := range nodes.Items { 
  7.    n := n 
  8.  
  9.    // 更新節(jié)點(diǎn)的標(biāo)簽和污點(diǎn)信息 
  10. +   err := r.Update(ctx, pool.Spec.ApplyNode(n)) 
  11. -   err := r.Patch(ctx, pool.Spec.ApplyNode(n), client.Merge) 
  12.    if err != nil { 
  13.     return ctrl.Result{}, err 
  14.    } 
  15.   } 
  16.  } 
  17.  
  18.  //... 
  19.  
  20.  // 如果存在則更新 
  21. + err = r.Client.Patch(ctx, pool.RuntimeClass(), client.Merge) 
  22. + if err != nil { 
  23. +  return ctrl.Result{}, err 
  24. + } 
  25.  
  26.  return ctrl.Result{}, err 

ApplyNode 方法如下所示,主要是修改節(jié)點(diǎn)的標(biāo)簽和污點(diǎn)信息

  1. // ApplyNode 生成 Node 結(jié)構(gòu),可以用于 Patch 數(shù)據(jù) 
  2. func (s *NodePoolSpec) ApplyNode(node corev1.Node) *corev1.Node { 
  3.  // 除了節(jié)點(diǎn)池的標(biāo)簽之外,我們只保留 k8s 的相關(guān)標(biāo)簽 
  4.  // 注意:這里的邏輯如果一個(gè)節(jié)點(diǎn)只能屬于一個(gè)節(jié)點(diǎn)池 
  5.  nodeLabels := map[string]string{} 
  6.  for k, v := range node.Labels { 
  7.   if strings.Contains(k, "kubernetes") { 
  8.    nodeLabels[k] = v 
  9.   } 
  10.  } 
  11.  
  12.  for k, v := range s.Labels { 
  13.   nodeLabels[k] = v 
  14.  } 
  15.  node.Labels = nodeLabels 
  16.  
  17.  // 污點(diǎn)同理 
  18.  var taints []corev1.Taint 
  19.  for _, taint := range node.Spec.Taints { 
  20.   if strings.Contains(taint.Key"kubernetes") { 
  21.    taints = append(taints, taint) 
  22.   } 
  23.  } 
  24.  
  25.  node.Spec.Taints = append(taints, s.Taints...) 
  26.  return &node 
  27. }  

我們使用 make run將服務(wù)跑起來測(cè)試一下

首先我們準(zhǔn)備一份 NodePool 的 CRD,使用 kubectl apply -f config/samples/ 部署一下

  1. apiVersion: nodes.lailin.xyz/v1 
  2. kind: NodePool 
  3. metadata: 
  4.   name: master 
  5. spec: 
  6.   taints: 
  7.     - key: node-pool.lailin.xyz 
  8.       value: master 
  9.       effect: NoSchedule 
  10.   labels: 
  11.     "node-pool.lailin.xyz/master""8" 
  12.     "node-pool.lailin.xyz/test""2" 
  13.   handler: runc 

部署之后可以獲取到節(jié)點(diǎn)的標(biāo)簽

  1. labels: 
  2.     beta.kubernetes.io/arch: amd64 
  3.     beta.kubernetes.io/os: linux 
  4.     kubernetes.io/arch: amd64 
  5.     kubernetes.io/hostname: kind-control-plane 
  6.     kubernetes.io/os: linux 
  7.     node-pool.lailin.xyz/master: "8" 
  8.     node-pool.lailin.xyz/test: "2" 
  9.     node-role.kubernetes.io/control-plane: "" 
  10.     node-role.kubernetes.io/master: "" 

以及 RuntimeClass

  1. apiVersion: node.k8s.io/v1 
  2.   handler: runc 
  3.   kind: RuntimeClass 
  4.   scheduling: 
  5.     nodeSelector: 
  6.       node-pool.lailin.xyz/master: "8" 
  7.       node-pool.lailin.xyz/test: "2" 
  8.     tolerations: 
  9.     - effect: NoSchedule 
  10.       key: node-pool.lailin.xyz 
  11.       operator: Equal 
  12.       value: master 

我們更新一下 NodePool

  1. apiVersion: nodes.lailin.xyz/v1 
  2. kind: NodePool 
  3. metadata: 
  4.   name: master 
  5. spec: 
  6.   taints: 
  7.     - key: node-pool.lailin.xyz 
  8.       value: master 
  9.       effect: NoSchedule 
  10.   labels: 
  11. +    "node-pool.lailin.xyz/master""10" 
  12. -    "node-pool.lailin.xyz/master""8" 
  13. -    "node-pool.lailin.xyz/test""2" 
  14.   handler: runc 

可以看到 RuntimeClass

  1. scheduling: 
  2.   nodeSelector: 
  3.     node-pool.lailin.xyz/master: "10" 
  4.   tolerations: 
  5.   - effect: NoSchedule 
  6.     key: node-pool.lailin.xyz 
  7.     operator: Equal 
  8.     value: master 

和節(jié)點(diǎn)對(duì)應(yīng)的標(biāo)簽信息都有了相應(yīng)的變化

  1. labels: 
  2.    beta.kubernetes.io/arch: amd64 
  3.    beta.kubernetes.io/os: linux 
  4.    kubernetes.io/arch: amd64 
  5.    kubernetes.io/hostname: kind-control-plane 
  6.    kubernetes.io/os: linux 
  7.    node-pool.lailin.xyz/master: "10" 
  8.    node-role.kubernetes.io/control-plane: "" 
  9.    node-role.kubernetes.io/master: "" 

預(yù)刪除: Finalizers

我們可以直接使用 kubectl delete NodePool name刪除對(duì)應(yīng)的對(duì)象,但是這樣可以發(fā)現(xiàn)一個(gè)問題,就是 NodePool 創(chuàng)建的 RuntimeClass 以及其維護(hù)的 Node Taint Labels 等信息都沒有被清理。

當(dāng)我們想要再刪除一個(gè)對(duì)象的時(shí)候,清理一寫想要清理的信息時(shí),我們就可以使用 Finalizers 特性,執(zhí)行預(yù)刪除的操作。

k8s 的資源對(duì)象當(dāng)中存在一個(gè) Finalizers字段,這個(gè)字段是一個(gè)字符串列表,當(dāng)執(zhí)行刪除資源對(duì)象操作的時(shí)候,k8s 會(huì)先更新 DeletionTimestamp 時(shí)間戳,然后會(huì)去檢查 Finalizers是否為空,如果為空才會(huì)執(zhí)行刪除邏輯。所以我們就可以利用這個(gè)特性執(zhí)行一些預(yù)刪除的操作。注意:預(yù)刪除必須是冪等的

  1. func (r *NodePoolReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 
  2.  _ = r.Log.WithValues("nodepool", req.NamespacedName) 
  3.  // ...... 
  4.  
  5. + // 進(jìn)入預(yù)刪除流程 
  6. + if !pool.DeletionTimestamp.IsZero() { 
  7. +  return ctrl.Result{}, r.nodeFinalizer(ctx, pool, nodes.Items) 
  8. + } 
  9.  
  10. + // 如果刪除時(shí)間戳為空說明現(xiàn)在不需要?jiǎng)h除該數(shù)據(jù),我們將 nodeFinalizer 加入到資源中 
  11. + if !containsString(pool.Finalizers, nodeFinalizer) { 
  12. +  pool.Finalizers = append(pool.Finalizers, nodeFinalizer) 
  13. +  if err := r.Client.Update(ctx, pool); err != nil { 
  14. +   return ctrl.Result{}, err 
  15. +  } 
  16. + } 
  17.  
  18.  // ...... 

預(yù)刪除的邏輯如下

  1. // 節(jié)點(diǎn)預(yù)刪除邏輯 
  2. func (r *NodePoolReconciler) nodeFinalizer(ctx context.Context, pool *nodesv1.NodePool, nodes []corev1.Node) error { 
  3.  // 不為空就說明進(jìn)入到預(yù)刪除流程 
  4.  for _, n := range nodes { 
  5.   n := n 
  6.  
  7.   // 更新節(jié)點(diǎn)的標(biāo)簽和污點(diǎn)信息 
  8.   err := r.Update(ctx, pool.Spec.CleanNode(n)) 
  9.   if err != nil { 
  10.    return err 
  11.   } 
  12.  } 
  13.  
  14.  // 預(yù)刪除執(zhí)行完畢,移除 nodeFinalizer 
  15.  pool.Finalizers = removeString(pool.Finalizers, nodeFinalizer) 
  16.  return r.Client.Update(ctx, pool) 

我們執(zhí)行 kubectl delete NodePool master 然后再獲取節(jié)點(diǎn)信息可以發(fā)現(xiàn),除了 kubernetes 的標(biāo)簽其他 NodePool 附加的標(biāo)簽都已經(jīng)被刪除掉了

  1. labels: 
  2.      beta.kubernetes.io/arch: amd64 
  3.      beta.kubernetes.io/os: linux 
  4.      kubernetes.io/arch: amd64 
  5.      kubernetes.io/hostname: kind-control-plane 
  6.      kubernetes.io/os: linux 
  7.      node-role.kubernetes.io/control-plane: "" 
  8.      node-role.kubernetes.io/master: "" 

OwnerReference

我們上面使用 Finalizer 的時(shí)候只處理了 Node 的相關(guān)數(shù)據(jù),沒有處理 RuntimeClass,能不能用相同的方式進(jìn)行處理呢?當(dāng)然是可以的,但是不夠優(yōu)雅。

對(duì)于這種一一映射或者是附帶創(chuàng)建出來的資源,更好的方式是在子資源的 OwnerReference 上加上對(duì)應(yīng)的 id,這樣我們刪除對(duì)應(yīng)的 NodePool 的時(shí)候所有 OwnerReference 是這個(gè)對(duì)象的對(duì)象都會(huì)被刪除掉,就不用我們自己對(duì)這些邏輯進(jìn)行處理了。

  1. func (r *NodePoolReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 
  2.  //... 
  3.  
  4.  // 如果不存在創(chuàng)建一個(gè)新的 
  5.  if runtimeClass.Name == "" { 
  6. +  runtimeClass = pool.RuntimeClass() 
  7. +  err = ctrl.SetControllerReference(pool, runtimeClass, r.Scheme) 
  8. +  if err != nil { 
  9. +   return ctrl.Result{}, err 
  10. +  } 
  11. +  err = r.Create(ctx, runtimeClass) 
  12. -  err = r.Create(ctx, pool.RuntimeClass()) 
  13.   return ctrl.Result{}, err 
  14.  } 
  15.  
  16.  // ... 

在創(chuàng)建的時(shí)候使用 controllerutil.SetOwnerReference 設(shè)置一下 OwnerReference 即可,然后我們?cè)僭囋噭h除就可以發(fā)現(xiàn) RuntimeClass 也一并被刪除了。

注意,RuntimeClass 是一個(gè)集群級(jí)別的資源,我們最開始創(chuàng)建的 NodePool 是 Namespace 級(jí)別的,直接運(yùn)行會(huì)報(bào)錯(cuò),因?yàn)? Cluster 級(jí)別的 OwnerReference 不允許是 Namespace 的資源。

這個(gè)需要在 api/v1/nodepool_types.go 添加一行注釋,指定為 Cluster 級(jí)別

  1. //+kubebuilder:object:root=true 
  2. +//+kubebuilder:resource:scope=Cluster 
  3. //+kubebuilder:subresource:status 
  4.  
  5. // NodePool is the Schema for the nodepools API 
  6. type NodePool struct { 

修改之后我們需要先執(zhí)行 make uninstall 然后再執(zhí)行 make install

總結(jié)

回顧一下,這篇文章我們實(shí)現(xiàn)了一個(gè) NodePool 的 Operator 用來控制節(jié)點(diǎn)以及對(duì)應(yīng)的 RuntimeClass,除了基本的 CURD 之外我們還學(xué)習(xí)了預(yù)刪除和 OwnerReference 的使用方式。之前在 kubectl delete 某個(gè)資源的時(shí)候有時(shí)候會(huì)卡住,這個(gè)其實(shí)是因?yàn)樵趫?zhí)行預(yù)刪除的操作,可能本來也比較慢,也有可能是預(yù)刪除的時(shí)候返回了錯(cuò)誤導(dǎo)致的。

下一篇我們一起來為我們的 Operator 加上 Event 和 Status。

參考文獻(xiàn)

[^1]: 容器運(yùn)行時(shí)類(Runtime Class):

https://kubernetes.io/zh/docs/concepts/containers/runtime-class/

[^2]: kubebuilder 進(jìn)階使用:

https://zhuanlan.zhihu.com/p/144978395

[^3]: kubebuilder2.0學(xué)習(xí)筆記——搭建和使用

https://segmentfault.com/a/1190000020338350

[^4]: KiND - How I Wasted a Day Loading Local Docker Images:

https://iximiuz.com/en/posts/kubernetes-kind-load-docker-image/

 

責(zé)任編輯:姜華 來源: mohuishou
相關(guān)推薦

2021-05-16 10:52:58

kubebuilderstatus event

2021-05-18 05:40:27

kubebuilderwebhook進(jìn)階

2021-05-17 05:51:31

KubeBuilderOperator測(cè)試

2021-05-08 09:02:48

KubeBuilderOperatork8s

2021-05-20 06:57:16

RabbitMQ開源消息

2023-04-20 08:00:00

ES搜索引擎MySQL

2021-06-16 08:28:25

unary 方法函數(shù)技術(shù)

2022-03-10 08:31:51

REST接口規(guī)范設(shè)計(jì)Restful架構(gòu)

2025-01-17 07:00:00

2022-02-24 07:56:42

開發(fā)Viteesbuild

2022-02-21 09:44:45

Git開源分布式

2023-05-12 08:19:12

Netty程序框架

2021-06-30 00:20:12

Hangfire.NET平臺(tái)

2021-07-28 10:02:54

建造者模式代碼

2022-04-08 08:32:40

mobx狀態(tài)管理庫(kù)redux

2021-07-14 08:24:23

TCPIP 通信協(xié)議

2021-08-11 07:02:21

npm包管理器工具

2020-11-27 08:02:41

Promise

2021-08-02 06:34:55

Redis刪除策略開源

2021-11-08 08:42:44

CentOS Supervisor運(yùn)維
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)