Escribir un operador para Kubernetes en Golang

Nota. traducir: Los operadores son software auxiliar para Kubernetes, diseñados para automatizar la ejecución de acciones rutinarias en objetos del clúster cuando ocurren ciertos eventos. Ya hemos escrito sobre operadores en este artículo, donde hablaron sobre las ideas y principios fundamentales de su trabajo. Pero si ese material era más bien una visión desde el punto de vista de la operación de componentes listos para usar para Kubernetes, entonces la traducción del nuevo artículo ahora propuesto ya es la visión de un desarrollador/ingeniero de DevOps desconcertado por la implementación de un nuevo operador.

Escribir un operador para Kubernetes en Golang

Decidí escribir esta publicación con un ejemplo de la vida real después de mis intentos de encontrar documentación sobre la creación de un operador para Kubernetes, que consistió en estudiar el código.

El ejemplo que se describirá es este: en nuestro clúster de Kubernetes, cada Namespace representa el entorno sandbox de un equipo y queríamos limitar el acceso a ellos para que los equipos solo pudieran jugar en sus propios sandboxes.

Puedes lograr lo que deseas asignando a un usuario un grupo que tenga RoleBinding a específico Namespace и ClusterRole con derechos de edición. La representación YAML se verá así:

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

(vinculación de roles.yamlEn crudo)

Crea uno RoleBinding Puedes hacerlo manualmente, pero después de cruzar la marca de los cien espacios de nombres, se convierte en una tarea tediosa. Aquí es donde los operadores de Kubernetes resultan útiles: le permiten automatizar la creación de recursos de Kubernetes en función de los cambios en los recursos. En nuestro caso queremos crear RoleBinding mientras creaba Namespace.

Primero que nada, definamos la función. mainque realiza la configuración requerida para ejecutar la declaración y luego llama a la acción de la declaración:

(Nota. traducir: aquí y debajo los comentarios del código están traducidos al ruso. Además, la sangría se ha corregido a espacios en lugar de pestañas [recomendadas en Go] únicamente con el fin de mejorar la legibilidad dentro del diseño de Habr. Después de cada listado hay enlaces al original en GitHub, donde se almacenan pestañas y comentarios en inglés).

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()   // Ожидаем, что все остановлено
}

(principal.irEn crudo)

Hacemos lo siguiente:

  1. Configuramos un controlador para señales específicas del sistema operativo para provocar una terminación elegante del operador.
  2. Uso WaitGrouppara detener con gracia todas las gorutinas antes de finalizar la aplicación.
  3. Proporcionamos acceso al clúster creando clientset.
  4. Lanzamos NamespaceController, en el que se ubicará toda nuestra lógica.

Ahora necesitamos una base para la lógica, y en nuestro caso esta es la mencionada. 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
}

(controlador.goEn crudo)

Aquí configuramos SharedIndexInformer, que efectivamente (usando un caché) esperará cambios en los espacios de nombres (lea más sobre informantes en el artículo “¿Cómo funciona realmente el programador de Kubernetes?"- aprox. traducción). Después de esto nos conectamos EventHandler al informador, de modo que al agregar un espacio de nombres (Namespace) la función se llama createRoleBinding.

El siguiente paso es definir esta función. 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))
  }
}

(controlador.goEn crudo)

Obtenemos el espacio de nombres como obj y convertirlo en un objeto Namespace. Entonces definimos RoleBinding, basado en el archivo YAML mencionado al principio, usando el objeto proporcionado Namespace y creando RoleBinding. Finalmente, registramos si la creación fue exitosa.

La última función a definir es 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
}

(controlador.goEn crudo)

aquí estamos hablando WaitGroupque lanzamos la gorutina y luego llamamos namespaceInformer, que ha sido previamente definido. Cuando llegue la señal de stop finalizará la función, informar WaitGroup, que ya no se ejecuta y esta función saldrá.

Puede encontrar información sobre cómo crear y ejecutar esta declaración en un clúster de Kubernetes en repositorios en GitHub.

Eso es todo para el operador que crea RoleBinding cuando Namespace en el cluster de Kubernetes, listo.

Fuente: habr.com

Añadir un comentario