Een operator schrijven voor Kubernetes in Golang

Opmerking. vert.: Operators zijn hulpsoftware voor Kubernetes, ontworpen om de uitvoering van routinematige acties op clusterobjecten te automatiseren wanneer bepaalde gebeurtenissen plaatsvinden. We hebben al over operators geschreven in dit artikel, waar ze spraken over de fundamentele ideeën en principes van hun werk. Maar als dat materiaal meer een weergave was van de kant van het bedienen van kant-en-klare componenten voor Kubernetes, dan is de vertaling van het nieuwe artikel dat nu wordt voorgesteld al de visie van een ontwikkelaar/DevOps-ingenieur die verbaasd is over de implementatie van een nieuwe operator.

Een operator schrijven voor Kubernetes in Golang

Ik besloot dit bericht te schrijven met een voorbeeld uit de praktijk na mijn pogingen om documentatie te vinden over het maken van een operator voor Kubernetes, waarbij ik de code had bestudeerd.

Het voorbeeld dat zal worden beschreven is dit: in ons Kubernetes-cluster elk Namespace vertegenwoordigt de sandbox-omgeving van een team en we wilden de toegang daartoe beperken, zodat teams alleen in hun eigen sandbox konden spelen.

U kunt bereiken wat u wilt door een gebruiker een groep toe te wijzen die dat wel heeft RoleBinding tot specifiek Namespace и ClusterRole met bewerkingsrechten. De YAML-weergave ziet er als volgt uit:

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

(rolbinding.yamlIn rauw)

Creëer er een RoleBinding Je kunt het handmatig doen, maar als je de grens van honderd naamruimten overschrijdt, wordt het een vervelende klus. Dit is waar Kubernetes-operators van pas komen: ze stellen u in staat de creatie van Kubernetes-resources te automatiseren op basis van wijzigingen in resources. In ons geval willen we creëren RoleBinding tijdens het creëren Namespace.

Laten we eerst de functie definiëren maindie de vereiste instellingen uitvoert om de instructie uit te voeren en vervolgens de instructieactie aanroept:

(Opmerking. vert.: hier en hieronder zijn de opmerkingen in de code in het Russisch vertaald. Bovendien is de inspringing gecorrigeerd naar spaties in plaats van [aanbevolen in Go]-tabbladen, uitsluitend met het oog op een betere leesbaarheid binnen de Habr-lay-out. Na elke vermelding staan ​​links naar het origineel op GitHub, waar Engelstalige opmerkingen en tabbladen worden opgeslagen.)

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

(hoofd.goIn rauw)

Wij doen het volgende:

  1. We configureren een handler voor specifieke besturingssysteemsignalen om een ​​correcte beëindiging van de operator te veroorzaken.
  2. We gebruiken WaitGroupom alle goroutines netjes te stoppen voordat u de applicatie beëindigt.
  3. Wij bieden toegang tot het cluster door het creëren van clientset.
  4. Lancering NamespaceController, waarin al onze logica zich zal bevinden.

Nu hebben we een basis voor logica nodig, en in ons geval is dit degene die genoemd wordt 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.goIn rauw)

Hier configureren we SharedIndexInformer, die effectief (met behulp van een cache) zal wachten op wijzigingen in naamruimten (lees meer over informanten in het artikel “Hoe werkt de Kubernetes-planner eigenlijk?"- ca. vert.). Hierna maken we verbinding EventHandler aan de informant, zodat bij het toevoegen van een naamruimte (Namespace) functie wordt aangeroepen createRoleBinding.

De volgende stap is het definiëren van deze functie 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.goIn rauw)

We krijgen de naamruimte als obj en converteer het naar een object Namespace. Dan definiëren wij RoleBinding, gebaseerd op het in het begin genoemde YAML-bestand, met behulp van het opgegeven object Namespace en creëren RoleBinding. Ten slotte loggen we of de creatie succesvol is verlopen.

De laatste functie die moet worden gedefinieerd is 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.goIn rauw)

Hier zijn we aan het praten WaitGroupdat we de goroutine lanceren en vervolgens bellen namespaceInformer, die eerder is gedefinieerd. Wanneer het stopsignaal arriveert, wordt de functie beëindigd en wordt geïnformeerd WaitGroup, dat niet langer wordt uitgevoerd, en deze functie wordt afgesloten.

Informatie over het bouwen en uitvoeren van deze verklaring op een Kubernetes-cluster vindt u in opslagplaatsen op GitHub.

Dat is het voor de operator die creëert RoleBinding wanneer Namespace in het Kubernetes-cluster, klaar.

Bron: www.habr.com

Voeg een reactie