用 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 集群中,准备就绪。

来源: habr.com

添加评论