Pisanje operaterja za Kubernetes v Golangu

Opomba. prevod: Operatorji so pomožna programska oprema za Kubernetes, zasnovana za avtomatizacijo izvajanja rutinskih dejanj na objektih gruče, ko pride do določenih dogodkov. O operaterjih v smo že pisali ta članek, kjer so spregovorili o temeljnih idejah in načelih svojega delovanja. Če pa je bilo to gradivo bolj pogled s strani delovanja že pripravljenih komponent za Kubernetes, potem je prevod zdaj predlaganega novega članka že vizija razvijalca/inženirja DevOps, ki ga zmede implementacija novega operaterja.

Pisanje operaterja za Kubernetes v Golangu

Za pisanje te objave s primerom iz resničnega življenja sem se odločil, potem ko sem poskušal najti dokumentacijo o ustvarjanju operaterja za Kubernetes, ki je šel skozi preučevanje kode.

Primer, ki bo opisan, je naslednji: v naši gruči Kubernetes vsak Namespace predstavlja okolje peskovnika ekipe in želeli smo omejiti dostop do njih, tako da bi ekipe lahko igrale samo v svojih peskovnikih.

Če želite, lahko dosežete tako, da uporabniku dodelite skupino, ki ima RoleBinding do določenega Namespace и ClusterRole s pravicami urejanja. Predstavitev YAML bo videti takole:

---
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.yamlV surovi)

Ustvarite enega RoleBinding To lahko storite ročno, a ko presežete oznako sto imenskih prostorov, postane dolgočasno opravilo. Tu pridejo prav operaterji Kubernetes – omogočajo vam avtomatizacijo ustvarjanja virov Kubernetes na podlagi sprememb virov. V našem primeru želimo ustvarjati RoleBinding med ustvarjanjem Namespace.

Najprej definirajmo funkcijo mainki izvede zahtevano nastavitev za zagon stavka in nato pokliče dejanje stavka:

(Opomba. prevod: tukaj in spodaj so komentarji v kodi prevedeni v ruščino. Poleg tega je bil zamik popravljen na presledke namesto zavihkov [priporočeno v Go] izključno zaradi boljše berljivosti znotraj postavitve Habr. Za vsakim seznamom so povezave do izvirnika na GitHubu, kjer so shranjeni komentarji in zavihki v angleškem jeziku.)

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.goV surovi)

Delamo naslednje:

  1. Konfiguriramo upravljalnik za določene signale operacijskega sistema, da povzroči elegantno prekinitev operaterja.
  2. Uporaba WaitGroupda elegantno ustavite vse goroutine, preden prekinete aplikacijo.
  3. Dostop do grozda omogočimo z ustvarjanjem clientset.
  4. Kosilo NamespaceController, v katerem se bo nahajala vsa naša logika.

Zdaj potrebujemo osnovo za logiko in v našem primeru je ta omenjena 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
}

(krmilnik.goV surovi)

Tukaj konfiguriramo SharedIndexInformer, ki bo učinkovito (z uporabo predpomnilnika) čakal na spremembe v imenskih prostorih (več o informatorjih preberite v članku “Kako razporejevalnik Kubernetes dejansko deluje?"- pribl. prevod). Po tem se povežemo EventHandler informerju, tako da ob dodajanju imenskega prostora (Namespace) je poklicana funkcija createRoleBinding.

Naslednji korak je definiranje te funkcije 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))
  }
}

(krmilnik.goV surovi)

Imenski prostor dobimo kot obj in ga pretvorite v predmet Namespace. Nato definiramo RoleBinding, ki temelji na datoteki YAML, omenjeni na začetku, z uporabo podanega predmeta Namespace in ustvarjanje RoleBinding. Nazadnje zabeležimo, ali je bilo ustvarjanje uspešno.

Zadnja funkcija, ki jo je treba definirati, je 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
}

(krmilnik.goV surovi)

Tukaj se pogovarjamo WaitGroupda zaženemo goroutine in nato pokličemo namespaceInformer, ki je bil predhodno opredeljen. Ko pride signal za zaustavitev, bo ta končal funkcijo, obvešča WaitGroup, ki se ne izvaja več, in ta funkcija se bo zaprla.

Informacije o gradnji in izvajanju te izjave v gruči Kubernetes lahko najdete v repozitorije na GitHubu.

To je vse za operaterja, ki ustvarja RoleBinding kdaj Namespace v gruči Kubernetes, pripravljeno.

Vir: www.habr.com

Dodaj komentar