用 Golang 為 Kubernetes 寫一個操作符

筆記。 翻譯。:Operator 是 Kubernetes 的輔助軟體,旨在當某些事件發生時,自動對叢集物件執行例行操作。 我們已經寫過關於運算符的文章 這篇文章,他們談論了他們工作的基本想法和原則。 但如果這些材料更多的是從操作 Kubernetes 的現成組件的角度來看,那麼現在提出的新文章的翻譯已經是一個開發人員/DevOps 工程師對新操作符的實現感到困惑的願景。

用 Golang 為 Kubernetes 寫一個操作符

在我嘗試尋找有關為 Kubernetes 建立操作符的文件並研究了程式碼之後,我決定用一個現實生活中的範例來寫這篇文章。

將要描述的例子是這樣的:在我們的 Kubernetes 叢集中,每個 Namespace 代表團隊的沙盒環境,我們希望限制對它們的訪問,以便團隊只能在自己的沙盒中進行遊戲。

您可以透過為使用者指派一個具有以下權限的群組來實現您想要的目的 RoleBinding 到具體的 Namespace и ClusterRole 具有編輯權。 YAML 表示如下:

---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: kubernetes-team-1
  namespace: team-1
subjects:
- kind: Group
  name: kubernetes-team-1
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: edit
apiGroup: rbac.authorization.k8s.io

(角色綁定.yaml)

創建一個 RoleBinding 您可以手動完成此操作,但在超過一百個命名空間標記後,它就變成了一項乏味的任務。 這就是 Kubernetes Operator 派上用場的地方——它們允許您根據資源的變更自動建立 Kubernetes 資源。 在我們的例子中,我們想要創建 RoleBinding 在創作時 Namespace.

首先我們來定義一下函數 main它執行運行語句所需的設置,然後調用語句操作:

(筆記。 翻譯。:這裡和下面程式碼中的註解已翻譯成俄語。 此外,縮排已被更正為空格,而不是 [Go 中推薦的] 選項卡,僅僅是為了 Habr 佈局中更好的可讀性。 每個列表後面都有 GitHub 上原始版本的鏈接,其中存儲了英語註釋和選項卡。)

func main() {
  // Устанавливаем вывод логов в консольный STDOUT
  log.SetOutput(os.Stdout)

  sigs := make(chan os.Signal, 1) // Создаем канал для получения сигналов ОС
  stop := make(chan struct{})     // Создаем канал для получения стоп-сигнала

  // Регистрируем получение SIGTERM в канале sigs
  signal.Notify(sigs, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) 

  // Goroutines могут сами добавлять себя в WaitGroup,
 // чтобы завершения их выполнения дожидались
  wg := &sync.WaitGroup{} 

  runOutsideCluster := flag.Bool("run-outside-cluster", false, "Set this flag when running outside of the cluster.")
  flag.Parse()
  // Создаем clientset для взаимодействия с кластером Kubernetes
  clientset, err := newClientSet(*runOutsideCluster)

  if err != nil {
    panic(err.Error())
  }

  controller.NewNamespaceController(clientset).Run(stop, wg)

  <-sigs // Ждем сигналов (до получения сигнала более ничего не происходит)
  log.Printf("Shutting down...")

  close(stop) // Говорим goroutines остановиться
  wg.Wait()   // Ожидаем, что все остановлено
}

(主程序)

我們執行以下操作:

  1. 我們為特定作業系統訊號配置一個處理程序,以導致操作員正常終止。
  2. 我們用 WaitGroup在終止應用程式之前優雅地停止所有 goroutine。
  3. 我們透過創建來提供對集群的訪問 clientset.
  4. 發射 NamespaceController,我們所有的邏輯都將位於其中。

現在我們需要一個邏輯基礎,在我們的例子中,這就是提到的 NamespaceController:

// NamespaceController следит через Kubernetes API за изменениями
// в пространствах имен и создает RoleBinding для конкретного namespace.
type NamespaceController struct {
  namespaceInformer cache.SharedIndexInformer
  kclient           *kubernetes.Clientset
}

// NewNamespaceController создает новый NewNamespaceController
func NewNamespaceController(kclient *kubernetes.Clientset) *NamespaceController {
  namespaceWatcher := &NamespaceController{}

  // Создаем информер для слежения за Namespaces
  namespaceInformer := cache.NewSharedIndexInformer(
    &cache.ListWatch{
      ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
        return kclient.Core().Namespaces().List(options)
      },
      WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
        return kclient.Core().Namespaces().Watch(options)
      },
    },
    &v1.Namespace{},
    3*time.Minute,
    cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
  )

  namespaceInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
    AddFunc: namespaceWatcher.createRoleBinding,
  })

  namespaceWatcher.kclient = kclient
  namespaceWatcher.namespaceInformer = namespaceInformer

  return namespaceWatcher
}

(控制器.go)

這裡我們配置一下 SharedIndexInformer,這將有效地(使用快取)等待命名空間中的更改 (閱讀文章中有關告密者的更多資訊“Kubernetes 調度程序實際上是如何工作的?“- 約。 翻譯)。 之後我們連接 EventHandler 通知者,以便在新增命名空間時(Namespace) 函數被調用 createRoleBinding.

下一步是定義這個函數 createRoleBinding:

func (c *NamespaceController) createRoleBinding(obj interface{}) {
  namespaceObj := obj.(*v1.Namespace)
  namespaceName := namespaceObj.Name

  roleBinding := &v1beta1.RoleBinding{
    TypeMeta: metav1.TypeMeta{
      Kind:       "RoleBinding",
      APIVersion: "rbac.authorization.k8s.io/v1beta1",
    },
    ObjectMeta: metav1.ObjectMeta{
      Name:      fmt.Sprintf("ad-kubernetes-%s", namespaceName),
      Namespace: namespaceName,
    },
    Subjects: []v1beta1.Subject{
      v1beta1.Subject{
        Kind: "Group",
        Name: fmt.Sprintf("ad-kubernetes-%s", namespaceName),
      },
    },
    RoleRef: v1beta1.RoleRef{
      APIGroup: "rbac.authorization.k8s.io",
        Kind:     "ClusterRole",
        Name:     "edit",
    },
  }

  _, err := c.kclient.Rbac().RoleBindings(namespaceName).Create(roleBinding)

  if err != nil {
    log.Println(fmt.Sprintf("Failed to create Role Binding: %s", err.Error()))
  } else {
    log.Println(fmt.Sprintf("Created AD RoleBinding for Namespace: %s", roleBinding.Name))
  }
}

(控制器.go)

我們得到的命名空間為 obj 並將其轉換為對象 Namespace。 然後我們定義 RoleBinding,基於開頭提到的YAML文件,使用提供的對象 Namespace 並創造 RoleBinding。 最後我們記錄一下創建是否成功。

最後定義的函數是 Run:

// Run запускает процесс ожидания изменений в пространствах имён
// и действия в соответствии с этими изменениями.
func (c *NamespaceController) Run(stopCh <-chan struct{}, wg *sync.WaitGroup) {
  // Когда эта функция завершена, пометим как выполненную
  defer wg.Done()

  // Инкрементируем wait group, т.к. собираемся вызвать goroutine
  wg.Add(1)

  // Вызываем goroutine
  go c.namespaceInformer.Run(stopCh)

  // Ожидаем получения стоп-сигнала
  <-stopCh
}

(控制器.go)

我們在這裡談論 WaitGroup我們啟動 goroutine 然後調用 namespaceInformer,之前已定義。 當停止訊號到來時,將結束該功能,通知 WaitGroup,不再執行,該函數將退出。

有關在 Kubernetes 叢集上建置和運行此語句的信息,請參閱 GitHub 上的存儲庫.

這就是建立操作符的操作 RoleBinding 什麼時候 Namespace 在 Kubernetes 叢集中,準備就緒。

來源: www.habr.com

添加評論