成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

一篇帶你kubebuilder 實戰: CRUD

開發 項目管理
這篇文章我們實現了一個 NodePool 的 Operator 用來控制節點以及對應的 RuntimeClass,除了基本的 CURD 之外我們還學習了預刪除和 OwnerReference 的使用方式。

[[398943]]

在前兩天的文章當中我們搭建好了本地的 K8s 開發環境,并且了解了 kubebuilder 的基本使用方法,今天就從我之前遇到的一個真實需求出發完整的寫一個 Operator

需求分析

背景

在 K8s 運行的過程當中我們發現總是存在一些業務由于安全,可用性等各種各樣的原因需要跑在一些獨立的節點池上,這些節點池里面可能再劃分一些小的節點池。

雖然我們可以使用 Taint,Label對節點進行劃分,使用 nodeSelector 和 tolerations讓 Pod 跑在指定的節點上,但是這樣主要會有兩個問題:

  • 一個是管理上不方便,在實際的使用過程中我們會發現存在錯配漏配的情況 雖然在 v1.16 之后也可以使用 RuntimeClass來簡化 pod 的配置,但是 RuntimClass 并不和節點進行關聯[^1]
  • 另一個就是拓展需求不好實現,例如我們想要的某個節點屬于網段或者當節點加入這個節點池自動開墻等

需求

1.對應用來說我們可以在創建或者更新應用時便捷的選擇的對應的節點池,默認情況下不需要進行選擇

2.對于節點池來說

  • 一個節點池可能有多個節點,并且一個節點也可能同時屬于多個節點池
  • 不同節點池的標簽、污點信息可能不同
  • 后續可以支持不同節點池的機型、安全組或者防火墻策略不同等
  • MVP 版本支持標簽、污點即可

方案設計

節點池資源如下

  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: "" 

節點和節點池之間的映射如何建立?

  • 我們可以利用 node-role.kubernetes.io/xxx=""標簽和節點池建立映射
  • xxx 和節點池的name相對應
  • 使用這個標簽的好處是,使用 kubectl get no可以很方便的看到節點屬于哪個節點池
  1. NAME                 STATUS                     ROLES                  AGE    VERSION 
  2. kind-control-plane   Ready,SchedulingDisabled   control-plane,master   2d2h   v1.20.2 

Pod 和節點池之間的映射如何建立?

  • 我們可以復用 RuntimeClass對象,當創建一個 NodePool 對象的時候我們就創建一個對應的 RuntimeClass 對象,然后在 Pod 中只需要加上 runtimeClassName: myclass 就可以了

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

開發

創建項目

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

定義對象

  1. // NodePoolSpec 節點池 
  2. type NodePoolSpec struct { 
  3.  // Taints 污點 
  4.  Taints []v1.Taint `json:"taints,omitempty"
  5.  
  6.  // Labels 標簽 
  7.  Labels map[string]string `json:"labels,omitempty"

創建

我們實現 Reconcile 函數,req會返回當前變更的對象的 Namespace和Name信息,有這兩個信息,我們就可以獲取到這個對象了,所以我們的操作就是

1.獲取 NodePool 對象

2.通過 NodePool 對象生成對應的 Label 查找是否已經存在對應的 Label 的 Node

  • 如果存在,就給對應的 Node 加上對應的 Taint 和 Label
  • 如果不存在就跳過

3.通過 NodePool 生成對應的 RuntimeClass ,查找是否已經存在對應的 RuntimeClass

  • 如果不存在就新建
  • 存在就跳過
  1. func (r *NodePoolReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 
  2.  _ = r.Log.WithValues("nodepool", req.NamespacedName) 
  3.  // 獲取對象 
  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.  // 查看是否存在對應的節點,如果存在那么就給這些節點加上數據 
  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.  // 如果不存在創建一個新的 
  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 

更新

相信聰明的你已經發現上面的創建邏輯存在很多的問題

1.如果 NodePool 對象更新,Node 是否更新對應的 Taint 和Label

  • 如果 NodePool 刪除了一個 Label 或Taint對應 Node 的Label或Taint 是否需要刪除,怎么刪除?

2.如果 NodePool 對象更新,RuntimeClass是否更新,如何更新

我們 MVP 版本實現可以簡單一些,我們約定,所有屬于 NodePool 的節點 Tanit 和Label信息都應該由 NodePool管理,key 包含 kubernetes 標簽污點除外

  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.    // 更新節點的標簽和污點信息 
  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 方法如下所示,主要是修改節點的標簽和污點信息

  1. // ApplyNode 生成 Node 結構,可以用于 Patch 數據 
  2. func (s *NodePoolSpec) ApplyNode(node corev1.Node) *corev1.Node { 
  3.  // 除了節點池的標簽之外,我們只保留 k8s 的相關標簽 
  4.  // 注意:這里的邏輯如果一個節點只能屬于一個節點池 
  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.  // 污點同理 
  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將服務跑起來測試一下

首先我們準備一份 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 

部署之后可以獲取到節點的標簽

  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 

和節點對應的標簽信息都有了相應的變化

  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: "" 

預刪除: Finalizers

我們可以直接使用 kubectl delete NodePool name刪除對應的對象,但是這樣可以發現一個問題,就是 NodePool 創建的 RuntimeClass 以及其維護的 Node Taint Labels 等信息都沒有被清理。

當我們想要再刪除一個對象的時候,清理一寫想要清理的信息時,我們就可以使用 Finalizers 特性,執行預刪除的操作。

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

  1. func (r *NodePoolReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 
  2.  _ = r.Log.WithValues("nodepool", req.NamespacedName) 
  3.  // ...... 
  4.  
  5. + // 進入預刪除流程 
  6. + if !pool.DeletionTimestamp.IsZero() { 
  7. +  return ctrl.Result{}, r.nodeFinalizer(ctx, pool, nodes.Items) 
  8. + } 
  9.  
  10. + // 如果刪除時間戳為空說明現在不需要刪除該數據,我們將 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.  // ...... 

預刪除的邏輯如下

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

我們執行 kubectl delete NodePool master 然后再獲取節點信息可以發現,除了 kubernetes 的標簽其他 NodePool 附加的標簽都已經被刪除掉了

  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 的時候只處理了 Node 的相關數據,沒有處理 RuntimeClass,能不能用相同的方式進行處理呢?當然是可以的,但是不夠優雅。

對于這種一一映射或者是附帶創建出來的資源,更好的方式是在子資源的 OwnerReference 上加上對應的 id,這樣我們刪除對應的 NodePool 的時候所有 OwnerReference 是這個對象的對象都會被刪除掉,就不用我們自己對這些邏輯進行處理了。

  1. func (r *NodePoolReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 
  2.  //... 
  3.  
  4.  // 如果不存在創建一個新的 
  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.  // ... 

在創建的時候使用 controllerutil.SetOwnerReference 設置一下 OwnerReference 即可,然后我們再試試刪除就可以發現 RuntimeClass 也一并被刪除了。

注意,RuntimeClass 是一個集群級別的資源,我們最開始創建的 NodePool 是 Namespace 級別的,直接運行會報錯,因為 Cluster 級別的 OwnerReference 不允許是 Namespace 的資源。

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

  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 { 

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

總結

回顧一下,這篇文章我們實現了一個 NodePool 的 Operator 用來控制節點以及對應的 RuntimeClass,除了基本的 CURD 之外我們還學習了預刪除和 OwnerReference 的使用方式。之前在 kubectl delete 某個資源的時候有時候會卡住,這個其實是因為在執行預刪除的操作,可能本來也比較慢,也有可能是預刪除的時候返回了錯誤導致的。

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

參考文獻

[^1]: 容器運行時類(Runtime Class):

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

[^2]: kubebuilder 進階使用:

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

[^3]: kubebuilder2.0學習筆記——搭建和使用

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/

 

責任編輯:姜華 來源: mohuishou
相關推薦

2021-05-16 10:52:58

kubebuilderstatus event

2021-05-17 05:51:31

KubeBuilderOperator測試

2021-05-18 05:40:27

kubebuilderwebhook進階

2021-05-08 09:02:48

KubeBuilderOperatork8s

2021-05-20 06:57:16

RabbitMQ開源消息

2023-04-20 08:00:00

ES搜索引擎MySQL

2022-03-10 08:31:51

REST接口規范設計Restful架構

2022-02-24 07:56:42

開發Viteesbuild

2025-01-17 07:00:00

2021-06-16 08:28:25

unary 方法函數技術

2020-11-27 08:02:41

Promise

2023-02-28 23:04:15

2021-11-24 08:51:32

Node.js監聽函數

2021-08-02 06:34:55

Redis刪除策略開源

2021-11-16 14:09:58

Containerd Dockerk8s

2023-04-21 08:11:54

KubernetesPod

2021-11-08 08:42:44

CentOS Supervisor運維

2021-12-15 11:52:34

GPLLinuxGNU

2022-02-21 09:44:45

Git開源分布式

2023-05-12 08:19:12

Netty程序框架
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 欧产日产国产精品v | 精品亚洲第一 | 一区二区三区免费 | 91久久精品国产免费一区 | 亚洲一卡二卡 | 天天弄天天操 | 午夜电影网址 | 成人av一区 | 国产精品一区二区无线 | 国产黄色精品 | 欧美一区二区在线免费观看 | 中文av在线播放 | 日皮视频免费 | 日韩欧美亚洲 | 羞羞视频免费在线 | 亚洲人a| 喷潮网站| 网络毛片 | 国产成人精品一区二区三区在线 | 亚洲一区在线日韩在线深爱 | 蜜桃视频一区二区三区 | 日本中文字幕日韩精品免费 | 国产精品久久国产精品 | 九九久久久| 中文字幕在线第一页 | 成人做爰www免费看 午夜精品久久久久久久久久久久 | 国产精品人人做人人爽 | 久久久亚洲精品视频 | 毛片1| 国产精品久久久久久久久动漫 | 日韩国产免费观看 | 久久久成人免费视频 | 91精品国产欧美一区二区 | 久久久久久蜜桃一区二区 | 国产农村一级国产农村 | 精品久久久久久久久久久久久久 | 中文字幕高清av | 国产h在线| 精久久久久| 色网站在线免费观看 | 美女天天干天天操 |