Escrivim un operador per a Kubernetes a Golang

Nota. transl.: Els operadors són programari auxiliar per a Kubernetes dissenyat per automatitzar l'execució d'accions rutinàries en objectes de clúster durant determinats esdeveniments. Ja hem escrit sobre els operadors a aquest articleon van parlar de les idees i principis fonamentals del seu treball. Però si aquest material era més aviat una visió des del costat de l'operació de components ja fets per a Kubernetes, aleshores la traducció del nou article que ara es proposa ja és la visió d'un desenvolupador/enginyer DevOps, desconcertat per la implementació d'un nou operador. .

Escrivim un operador per a Kubernetes a Golang

Vaig decidir escriure aquesta publicació amb un exemple de la vida real després que els meus intents de trobar documentació sobre la creació d'un operador per a Kubernetes, van passar per l'estudi del codi.

L'exemple que es descriu és el següent: al nostre clúster de Kubernetes, cadascun Namespace representa l'entorn sandbox d'un equip, i volíem restringir-hi l'accés perquè els equips només puguin jugar als seus propis sandbox.

Pots aconseguir el que vulguis assignant un usuari a un grup que tingui RoleBinding a concret Namespace и ClusterRole amb drets d'edició. La representació de YAML tindrà aquest aspecte:

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

(rolebinding.yamla cru)

Crear tal RoleBinding podeu fer-ho manualment, però un cop supereu el centenar d'espais de noms, esdevé una tasca tediosa. Aquí és on els operadors de Kubernetes són útils: us permeten automatitzar la creació de recursos de Kubernetes en funció dels canvis en els recursos. En el nostre cas, volem crear RoleBinding mentre es crea Namespace.

Primer de tot, anem a definir la funció main, que fa la configuració necessària per executar la instrucció i després invoca l'acció de la instrucció:

(Nota. transl.: d'ara endavant, els comentaris del codi es tradueixen al rus. A més, el sagnat es corregeix per als espais en comptes de les pestanyes [recomanat a Go] només amb la finalitat d'una millor llegibilitat en el marc del disseny de Habr. Després de cada llistat, hi ha enllaços a l'original a GitHub, on es guarden els comentaris i les pestanyes en anglè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()   // Ожидаем, что все остановлено
}

(main.goa cru)

Fem el següent:

  1. Hem configurat un controlador per a senyals específics del sistema operatiu perquè l'operador surti amb gràcia.
  2. Fem servir WaitGroupper aturar amb gràcia totes les goroutines abans que finalitzi l'aplicació.
  3. Proporcionem accés al clúster mitjançant la creació clientset.
  4. Llançament NamespaceController, en el qual s'ubicarà tota la nostra lògica.

Ara necessitem una base per a la lògica, i en el nostre cas és l'esmentat 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
}

(controller.goa cru)

Aquí estem fixant SharedIndexInformer, que de manera eficient (utilitzant la memòria cau) esperarà els canvis d'espai de noms (llegiu més sobre informadors a l'article "Com funciona realment el programador de Kubernetes?"- aprox. transl.). Després d'això ens connectem EventHandler a l'informador, gràcies al qual, en afegir un espai de noms (Namespace) s'anomena la funció createRoleBinding.

El següent pas és definir aquesta funció 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))
  }
}

(controller.goa cru)

Obtenim l'espai de noms com obj i convertir-lo en un objecte Namespace. Després definim RoleBinding, basat en el fitxer YAML esmentat al principi, utilitzant l'objecte proporcionat Namespace i creant RoleBinding. Finalment, registrem si la creació va tenir èxit.

L'última funció a definir és − 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
}

(controller.goa cru)

Aquí estem parlant WaitGroupque comencem la goroutina i després cridem namespaceInformerque s'ha definit prèviament. Quan arribi un senyal de parada, finalitzarà la funció, informa WaitGroup, que ja no s'està executant, i aquesta funció sortirà.

Podeu trobar informació sobre com crear i executar aquesta declaració en un clúster de Kubernetes a repositoris a GitHub.

En aquesta declaració, que crea RoleBinding Quan Namespace en un clúster de Kubernetes, llest.

Font: www.habr.com

Afegeix comentari