Shkruajmë operator për Kubernetes në Golang

Shën. përkth.: Operatorët (operators) janë softuer të ndihmës për Kubernetes, të caktuar për të automatizuar ekzekutimin e veprimeve rutinore mbi objektet e klasterit në ngjarje të caktuara. Ne tashmë kemi shkruar për operatorët në këto artikuj, ku kemi diskutuar idetë themelore dhe parimet e funksionimit të tyre. Por nëse ai material ishte më shumë një pikëpamje nga jashtë e shfrytëzimit të komponentëve të gatshëm për Kubernetes, tani po ofrohet përkthimi i një artikulli të ri — ky është një shikim nga zhvilluesi/ingjineri DevOps, i shqetësuar për realizimin e një operatori të ri.

Shkruajmë operator për Kubernetes në Golang

Ky postim me një shembull nga jeta reale vendosa ta shkruaj pas përpjekjeve të mia për të gjetur dokumentacionin mbi krijimin e një operatori për Kubernetes, që kaloi përmes studimit të kodit.

Shembulli që do të përshkruhet është: në klasterin tonë Kubernetes, çdo Namespace paraqet një ambient-lojë për ndonjë ekip, dhe ne do t'i kufizojmë qasjen atyre në mënyrë që ekipet të mund të luajnë vetëm në ambjentet e tyre të lojës.

Të arrihet e dëshiruara mund të bëhet përmes caktimit të një grupi përdoruesish, të cilit i jepet RoleBinding për një Namespace и ClusterRole me të drejta redaktimi. Përfaqësimi YAML do të duket kështu:

---
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, në raw)

Krijimi i tillë RoleBinding mund të bëhet edhe manualisht, por pasi të kalosh shifrën njëqind të hapësirave të emrave, kjo bëhet një veprim i lodhshëm. Pikërisht këtu ndihmojnë operatorët e Kubernetes — ata lejojnë automatizimin e krijimit të resurseve Kubernetes, bazuar në ndryshimet në resurset. Në rastin tonë, ne do të duam të krijojmë RoleBinding kur krijojmë Namespace.

Së pari do të përcaktojmë funksionin main, i cili realizon konfigurimin e kërkuar për të nisur operatorin dhe pastaj thërret veprimin e operatorit:

(Shën. përkth.: këtu dhe më tej komentet në kod janë përkthyer në shqip. Më tej, hapësirat janë rregulluar në hapësira në vend të [të rekomanduarave në Go] tabave thjesht për lehtësimin e lexueshmërisë brenda formatimit të Habr-së. Pas çdo listimi janë dhënë lidhjet në origjinal në GitHub, ku janë ruajtur komentet dhe tabat në anglisht.)

func main() {
  // Vendosim daljen e logeve në STDOUT konsolë
  log.SetOutput(os.Stdout)

  sigs := make(chan os.Signal, 1) // Krijojmë një kanal për të pranuar sinjalet nga OS
  stop := make(chan struct{})     // Krijojmë një kanal për të pranuar sinjalin e ndalimit

  // Regjistrojmë marrjen e SIGTERM në kanalin sigs
  signal.Notify(sigs, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) 

  // Goroutines mund të shtojnë vetveten në WaitGroup,
  // për të pritur që përfundimi i tyre të bëhet
  wg := &sync.WaitGroup{} 

  runOutsideCluster := flag.Bool("run-outside-cluster", false, "Vendosni këtë flamur kur po funksiononi jashtë grumbullit.")
  flag.Parse()
  // Krijojmë clientset për të komunikuar me grumbullin Kubernetes
  clientset, err := newClientSet(*runOutsideCluster)

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

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

  <-sigs // Prisni sinjalet (deri sa të marrë një sinjal nuk ndodh asgjë tjetër)
  log.Printf("Duke u mbyllur...")

  close(stop) // I themi goroutines të ndalojnë
  wg.Wait()   // Pritni që gjithçka të jetë ndaluar
}

(main.go, në raw)

Ne po bëjmë si më poshtë:

  1. Konfiguroni trajtuesit e sinjaleve specifike të sistemit operativ për të nxitur një mbyllje të saktë (graceful) të operatorit.
  2. Përdorni WaitGroup, për të ndaluar saktësisht të gjitha goroutines para se të mbyllet aplikacioni.
  3. Sigurojmë qasje në grumbull duke krijuar clientset.
  4. Po e nisim NamespaceController, ku do të vendoset e gjithë logjika jonë.

Tani është e nevojshme një bazë për logjikën, dhe në rastin tonë kjo është e përmendur 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, në raw)

Këtu ne po konfiguroni SharedIndexInformer, i cili do të presë në mënyrë efektive (duke përdorur cache) ndryshimet në hapësirat e emrave (për më shumë rreth informers lexoni artikullin "Si funksionon realisht planifikuesi Kubernetes?» — shënim i përkthyesit.). Pas kësaj ne lidhim EventHandler me informerin, kështu që kur një hapësirë emri shtohet (Namespace) funksioni createRoleBinding.

Hapi tjetër është të përcaktojmë këtë funksion 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("Dështimi për të krijuar Role Binding: %s", err.Error()))
  } else {
    log.Println(fmt.Sprintf("Krijuar AD RoleBinding për Namespace: %s", roleBinding.Name))
  }
}

(controller.go, në raw)

Ne e marrim hapësirën e emrit si obj dhe e kthejmë atë në një objekt Namespace. Pastaj përcaktojmë RoleBinding, duke u bazuar në atë që është përmendur në skedarin YAML të përmendur në fillim, duke përdorur objektin e ofruar Namespace dhe krijojmë RoleBinding. Në fund, regjistrojmë nëse krijimi kaloi me sukses.

Funksioni i fundit që duhet përcaktuar është Ekzekuto:

// 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, në raw)

Këtu po themi WaitGroup, që do ta nisim goroutine-n dhe më pas do të thërrasim namespaceInformer, i cili ishte përcaktuar më parë. Kur të merret sinjali i ndaljes, ai do të përfundojë funksionin, duke raportuar WaitGroup, që nuk ekzekutohet më, dhe ky funksion do të përfundojë punën e tij.

Informacionin në lidhje me ndërtimin dhe nisjen e këtij operatori në klasterin Kubernetes mund ta gjeni në repozytorin në GitHub.

Në këtë pikë, operatori që krijon RoleBinding kur shfaqet Namespace në klasterin Kubernetes, është gati.

Burimi: habr.com

Купить надежный хостинг для сайтов с защитой от DDoS, VPS VDS серверы 🔥 Купить надежный хостинг для сайтов с защитой от DDoS, VPS VDS серверы | ProHoster