Skriver en operatør for Kubernetes i Golang

Bemærk. overs.: Operatører er hjælpesoftware til Kubernetes, designet til at automatisere udførelsen af ​​rutinehandlinger på klyngeobjekter, når visse hændelser opstår. Vi har allerede skrevet om operatører i denne artikel, hvor de talte om de grundlæggende ideer og principper i deres arbejde. Men hvis dette materiale mere var et syn fra siden af ​​at betjene færdige komponenter til Kubernetes, så er oversættelsen af ​​den nye artikel, der nu foreslås, allerede visionen for en udvikler/DevOps-ingeniør, der er forundret over implementeringen af ​​en ny operatør.

Skriver en operatør for 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 om oprettelse af en operatør til Kubernetes, som gik gennem at studere koden.

Eksemplet, der vil blive beskrevet, er dette: i vores Kubernetes-klynge, hver Namespace repræsenterer et holds sandkassemiljø, og vi ønskede at begrænse adgangen til dem, så hold kun kunne spille i deres egne sandkasser.

Du kan opnå det, du ønsker, ved at tildele en bruger en gruppe, der har RoleBinding til specifik Namespace и ClusterRole med redigeringsrettigheder. YAML-repræsentationen vil se sådan 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

(rollebinding.yamlI )

Skab en RoleBinding Du kan gøre det manuelt, men efter at have krydset de hundrede navneområder, bliver det en kedelig opgave. Det er her, Kubernetes-operatører er nyttige - de giver dig mulighed for at automatisere oprettelsen af ​​Kubernetes-ressourcer baseret på ændringer af ressourcer. I vores tilfælde ønsker vi at skabe RoleBinding mens du skaber Namespace.

Først og fremmest, lad os definere funktionen mainsom udfører den nødvendige opsætning for at køre sætningen og derefter kalder sætningshandlingen:

(Bemærk. overs.: her og nedenfor er kommentarerne i koden oversat til russisk. Derudover er indrykket blevet rettet til mellemrum i stedet for [anbefalet i Go] faner udelukkende med henblik på bedre læsbarhed i Habr-layoutet. Efter hver liste er der links til originalen på GitHub, hvor engelsksprogede kommentarer og faner er gemt.)

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.goI )

Vi gør følgende:

  1. Vi konfigurerer en handler til specifikke operativsystemsignaler for at forårsage en yndefuld opsigelse af operatøren.
  2. Vi bruger WaitGroupfor yndefuldt at stoppe alle goroutiner, før applikationen afsluttes.
  3. Vi giver adgang til klyngen ved at oprette clientset.
  4. Lad os starte NamespaceController, hvori al vores logik vil være placeret.

Nu mangler vi et grundlag for logik, 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.goI )

Her konfigurerer vi SharedIndexInformer, som effektivt (ved hjælp af en cache) vil vente på ændringer i navnerum (læs mere om informanter i artiklen "Hvordan fungerer Kubernetes-planlæggeren egentlig?"- ca. oversættelse). Herefter forbinder vi EventHandler til informanten, så når du tilføjer et navneområde (Namespace) funktion kaldes createRoleBinding.

Det næste trin 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("Failed to create Role Binding: %s", err.Error()))
  } else {
    log.Println(fmt.Sprintf("Created AD RoleBinding for Namespace: %s", roleBinding.Name))
  }
}

(controller.goI )

Vi får navnerummet som obj og konvertere det til et objekt Namespace. Så definerer vi RoleBinding, baseret på YAML-filen nævnt i begyndelsen, ved hjælp af det medfølgende objekt Namespace og skabe RoleBinding. Til sidst logger vi, om oprettelsen lykkedes.

Den sidste funktion, der skal defineres, er 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.goI )

Her snakker vi WaitGroupat vi starter goroutinen og derefter kalder namespaceInformer, som tidligere er defineret. Når stopsignalet kommer, vil det afslutte funktionen, oplys WaitGroup, som ikke længere udføres, og denne funktion afsluttes.

Information om at bygge og køre denne erklæring på en Kubernetes-klynge kan findes i repositories på GitHub.

Det er det for operatøren, der skaber RoleBinding hvornår Namespace i Kubernetes-klyngen, klar.

Kilde: www.habr.com

Tilføj en kommentar