Skriver en operator til Kubernetes i Golang

Bem. overs.: Operatører (operators) er hjælpeprogrammer til Kubernetes, der har til formål at automatisere udførelsen af rutineopgaver på klyngeobjekter ved bestemte hændelser. Vi har allerede skrevet om operatører i denne artikel, hvor vi diskuterede de grundlæggende ideer og principper for deres funktion. Men mens det materiale var mere et perspektiv fra driftssiden af de færdige komponenter til Kubernetes, så er den foreslåede oversættelse af den nye artikel allerede en udvikler-/DevOps-ingenørs forståelse, der er bekymret over implementeringen af en ny operator.

Skriver en operator til Kubernetes i Golang

Jeg besluttede at skrive dette indlæg med et eksempel fra det virkelige liv efter mine forsøg på at finde dokumentation til oprettelse af en operator til Kubernetes, som gik gennem kodestudie.

Eksemplet, der vil blive beskrevet, er som følger: I vores Kubernetes-klynge repræsenterer hver Namespace et sandkasse-miljø for et bestemt team, og vi ønskede at begrænse adgangen til dem, så teamerne kun kunne lege i deres egne sandkasser.

Det ønskede resultat kan opnås ved at tildele brugeren en gruppe, der har RoleBinding til specifikke Namespace og ClusterRole med redigeringsretter. YAML-repræsentationen vil se således ud:

---
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.yaml, i raw)

Sådan en RoleBinding kan også oprettes manuelt, men efter at have nået over et hundrede navneområder bliver det en trættende opgave. Netop her hjælper Kubernetes-operatører — de muliggør automatisering af oprettelse af Kubernetes-ressourcer baseret på ændringer i ressourcerne. I vores tilfælde ønsker vi at oprette RoleBinding ved oprettelsen af Namespace.

Først definerer vi en funktion main, som udfører den nødvendige konfiguration for at starte operatøren og derefter kalder operatørens handling:

(Bem. overs.: Her og fremover er kommentarerne i koden oversat til dansk. Desuden er indrykninger korrigeret til mellemrum i stedet for [de anbefalede i Go] tabulatortasterne udelukkende med henblik på bedre læsbarhed i forbindelse med udformningen af Habr. Efter hver liste er der links til originalen på GitHub, hvor de engelsksprogede kommentarer og tabulatortasterne er bevaret.)

func main() {
  // Sætter logudskriften til konsolens STDOUT
  log.SetOutput(os.Stdout)

  sigs := make(chan os.Signal, 1) // Opretter en kanal til at modtage OS-signaler
  stop := make(chan struct{})     // Opretter en kanal til at modtage stop-signal

  // Registrerer modtagelsen af SIGTERM i kanalen sigs
  signal.Notify(sigs, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) 

  // Goroutines kan selv tilføje sig til WaitGroup,
  // så deres udførelse venter vi på
  wg := &sync.WaitGroup{} 

  runOutsideCluster := flag.Bool("run-outside-cluster", false, "Sæt dette flag, når du kører uden for klyngen.")
  flag.Parse()
  // Opretter clientset til interaktion med Kubernetes-klyngen
  clientset, err := newClientSet(*runOutsideCluster)

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

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

  <-sigs // Venter på signaler (intet sker indtil der modtages et signal)
  log.Printf("Afslutter...")

  close(stop) // Fortæller goroutines at stoppe
  wg.Wait()   // Venter på at alt er stoppet
}

(main.go, i raw)

Vi gør følgende:

  1. Vi konfigurerer håndtereren for specifikke signalsystemer for at kalde korrekt (graceful) afslutning af operatøren.
  2. Vi bruger WaitGroup, for korrekt at stoppe alle goroutines før afslutning af applikationen.
  3. Vi giver adgang til klyngen ved at oprette clientset.
  4. Starting NamespaceController, hvor al vores logik vil være placeret.

Nu er der brug for en grundfor logikken, og i vores tilfælde er det den nævnte 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.go, i raw)

Her konfigurerer vi SharedIndexInformer, som effektivt (ved brug af cache) venter på ændringer i navnerum (læs mere om informers i artiklen "Hvordan fungerer Kubernetes-scheduler egentlig?» — ft. overs.). Herefter tilslutter vi EventHandler til informeren, hvilket gør at ved tilføjelse af navnerum (Namespace) kaldes funktionen createRoleBinding.

Næste skridt er at definere denne funktion 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("Fejl ved oprettelse af Role Binding: %s", err.Error()))
  } else {
    log.Println(fmt.Sprintf("Oprettet AD RoleBinding for Namespace: %s", roleBinding.Name))
  }
}

(controller.go, i raw)

Vi får navnerummet som obj og konverterer det til et objekt Namespace. Herefter definerer vi RoleBinding, baseret på det nævnte YAML-dokument, ved at bruge det givne objekt Namespace og opretter RoleBinding. Til sidst logger vi, om oprettelsen var vellykket.

Den sidste funktion, der skal defineres, er Kør:

// 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.go, i raw)

Her siger vi WaitGroup, at vi vil starte en goroutine og derefter kalde namespaceInformer, som blev defineret tidligere. Når stoppesignalet modtages, afslutter det funktionen, rapporterer WaitGroup, at den ikke længere kører, og denne funktion slutter sit arbejde.

Information om opbygningen og kørsel af denne operator i et Kubernetes-kluster kan findes i repository på GitHub.

Så er denne operator, der opretter RoleBinding ved ankomsten Namespace i Kubernetes-klusteret, klar.

Kilde: habr.com

Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere 🔥 Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere | ProHoster