Pisanje operatora za Kubernetes u Golangu

Bilješka. prev.: Operatori su pomoćni softver za Kubernetes, dizajniran za automatiziranje izvršavanja rutinskih radnji na objektima klastera kada se dogode određeni događaji. Već smo pisali o operaterima u ovaj članak, gdje su govorili o temeljnim idejama i načelima svog rada. Ali ako je taj materijal bio više pogled sa strane upravljanja gotovim komponentama za Kubernetes, tada je prijevod novog članka koji je sada predložen već vizija programera/DevOps inženjera zbunjenog implementacijom novog operatora.

Pisanje operatora za Kubernetes u Golangu

Odlučio sam napisati ovaj post s primjerom iz stvarnog života nakon mojih pokušaja da pronađem dokumentaciju o stvaranju operatora za Kubernetes, što je prošlo kroz proučavanje koda.

Primjer koji će biti opisan je sljedeći: u našem Kubernetes klasteru, svaki Namespace predstavlja timsko sandbox okruženje i htjeli smo im ograničiti pristup tako da timovi mogu igrati samo u svojim sandboxovima.

Možete postići ono što želite tako da korisniku dodijelite grupu koja ima RoleBinding na konkretne Namespace и ClusterRole s pravom uređivanja. YAML prikaz će izgledati ovako:

---
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.yamlU sirov)

Stvori jedan RoleBinding Možete to učiniti ručno, ali nakon što prijeđete granicu od stotinu imenskih prostora, to postaje dosadan zadatak. Tu Kubernetes operatori dolaze od koristi—omogućuju vam da automatizirate stvaranje Kubernetes resursa na temelju promjena resursa. U našem slučaju želimo stvarati RoleBinding pri stvaranju Namespace.

Prije svega, definirajmo funkciju mainkoji izvodi potrebnu postavku za izvođenje naredbe i zatim poziva radnju naredbe:

(Bilješka. prev.: ovdje i ispod su komentari u kodu prevedeni na ruski. Osim toga, uvlačenje je ispravljeno na razmake umjesto [preporučeno u Go] karticama isključivo u svrhu bolje čitljivosti unutar Habr izgleda. Nakon svakog popisa nalaze se poveznice na izvornik na GitHubu, gdje su pohranjeni komentari i kartice na engleskom 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()   // Ожидаем, что все остановлено
}

(glavni.idiU sirov)

Radimo sljedeće:

  1. Konfiguriramo rukovatelja za specifične signale operacijskog sustava kako bismo uzrokovali graciozno prekidanje operatora.
  2. Koristimo WaitGroupkako biste elegantno zaustavili sve goroutine prije prekida aplikacije.
  3. Pristup klasteru omogućujemo stvaranjem clientset.
  4. Pokreni NamespaceController, u kojem će biti smještena sva naša logika.

Sada nam treba osnova za logiku, au našem slučaju to je spomenuta 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
}

(kontrolor.krenutiU sirov)

Ovdje konfiguriramo SharedIndexInformer, koji će učinkovito (pomoću predmemorije) čekati promjene u imenskim prostorima (više o doušnicima pročitajte u članku “Kako zapravo radi Kubernetes planer?"- cca. prijevod). Nakon ovoga se spajamo EventHandler u informator, tako da prilikom dodavanja imenskog prostora (Namespace) poziva se funkcija createRoleBinding.

Sljedeći korak je definiranje ove 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))
  }
}

(kontrolor.krenutiU sirov)

Dobivamo imenski prostor kao obj i pretvoriti ga u objekt Namespace. Zatim definiramo RoleBinding, na temelju YAML datoteke spomenute na početku, koristeći navedeni objekt Namespace i stvaranje RoleBinding. Na kraju, bilježimo je li stvaranje bilo uspješno.

Posljednja funkcija koju 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
}

(kontrolor.krenutiU sirov)

Evo razgovaramo WaitGroupda pokrenemo goroutine i zatim pozovemo namespaceInformer, koji je prethodno definiran. Kada stigne signal za zaustavljanje, prekinut će funkciju, informirajte WaitGroup, koji se više ne izvršava, a ova funkcija će izaći.

Informacije o izgradnji i pokretanju ove izjave na Kubernetes klasteru mogu se pronaći u spremišta na GitHubu.

To je sve za operatera koji stvara RoleBinding kada Namespace u klasteru Kubernetes, spreman.

Izvor: www.habr.com

Dodajte komentar