Skriuwen fan in operator foar Kubernetes yn Golang

Noat. transl.: Operators binne auxiliary software foar Kubernetes, ûntworpen om de útfiering fan routine aksjes op klusterobjekten te automatisearjen as bepaalde eveneminten foarkomme. Wy hawwe al skreaun oer operators yn dit artikel, dêr't se praat oer de fûnemintele ideeën en prinsipes fan harren wurk. Mar as dat materiaal mear in werjefte wie fan 'e kant fan it operearjen fan klearmakke komponinten foar Kubernetes, dan is de oersetting fan it nije artikel no foarsteld al de fyzje fan in ûntwikkelder / DevOps-yngenieur dy't fernuvere is troch de ymplemintaasje fan in nije operator.

Skriuwen fan in operator foar Kubernetes yn Golang

Ik besleat dit berjocht te skriuwen mei in foarbyld fan 'e echte libben nei myn besykjen om dokumintaasje te finen oer it meitsjen fan in operator foar Kubernetes, dy't gie troch it bestudearjen fan de koade.

It foarbyld dat beskreaun wurdt is dit: yn ús Kubernetes-kluster, elk Namespace fertsjintwurdiget de sânboxomjouwing fan in team, en wy woenen de tagong ta har beheine, sadat teams allinich yn har eigen sânboxen koene spielje.

Jo kinne berikke wat jo wolle troch in brûker in groep ta te jaan dy't hat RoleBinding nei spesifyk Namespace и ClusterRole mei bewurkingsrjochten. De YAML-fertsjintwurdiging sil der sa útsjen:

---
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, yn rau)

Meitsje ien RoleBinding Jo kinne it mei de hân dwaan, mar nei it oerstekken fan de hûndert nammeromten, wurdt it in ferfeelsume taak. Dit is wêr't Kubernetes-operators goed fan pas komme - se kinne jo it oanmeitsjen fan Kubernetes-boarnen automatisearje op basis fan feroarings oan boarnen. Yn ús gefal wolle wy meitsje RoleBinding wylst it meitsjen Namespace.

Lit ús earst de funksje definiearje maindy't de fereaske opset docht om de ferklearring út te fieren en dan de ferklearringaksje opropt:

(Noat. transl.: hjir en ûnder de opmerkings yn 'e koade binne oerset yn it Russysk. Derneist is de ynspringing korrizjearre nei spaasjes ynstee fan [oanrikkemandearre yn Go] ljeppers allinich foar it doel fan bettere lêsberens binnen de Habr-opmaak. Nei elke fermelding binne d'r keppelings nei it orizjineel op GitHub, wêr't Ingelsktalige opmerkings en ljeppers wurde opslein.)

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.go, yn rau)

Wy dogge it folgjende:

  1. Wy konfigurearje in handler foar spesifike bestjoeringssysteem sinjalen foar in feroarsaakje sierlike beëiniging fan de operator.
  2. Wy brûke WaitGroupom alle goroutines sierlik te stopjen foardat de applikaasje beëinige wurdt.
  3. Wy jouwe tagong ta it kluster troch te meitsjen clientset.
  4. Launch NamespaceController, wêryn al ús logika lizze sil.

No hawwe wy in basis nedich foar logika, en yn ús gefal is dit de neamde 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, yn rau)

Hjir konfigurearje wy SharedIndexInformer, dy't effektyf (mei in cache) wachtsje op feroaringen yn nammeromten (lês mear oer ynformanten yn it artikel "Hoe wurket de Kubernetes-planner eins?"- ca. oersetting). Nei dit wy ferbine EventHandler oan de ynformateur, sadat by it tafoegjen fan in nammeromte (Namespace) funksje wurdt neamd createRoleBinding.

De folgjende stap is om dizze funksje te definiearjen 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.go, yn rau)

Wy krije de nammeromte as obj en konvertearje it nei in objekt Namespace. Dan definiearje wy RoleBinding, basearre op it YAML-bestân neamd oan it begjin, mei it opjûne objekt Namespace en oanmeitsjen RoleBinding. As lêste, loggen wy oft de skepping wie suksesfol.

De lêste funksje om te definiearjen 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.go, yn rau)

Hjir prate wy WaitGroupdat wy lansearje de goroutine en dan belje namespaceInformer, dy't earder definiearre is. As it stopsinjaal komt, sil de funksje einigje, ynformearje WaitGroup, dy't net mear útfierd wurdt, en dizze funksje sil ôfslute.

Ynformaasje oer it bouwen en útfieren fan dizze ferklearring op in Kubernetes-kluster is te finen yn repositories op GitHub.

Dat is it foar de operator dy't skept RoleBinding wannear Namespace yn de Kubernetes kluster, klear.

Boarne: www.habr.com

Add a comment