Pišemo operator za Kubernetes u Golangu

Bilješka. transl.: Operatori su pomoćni softver za Kubernetes dizajniran za automatizaciju izvršavanja rutinskih radnji na objektima klastera tokom određenih događaja. Već smo pisali o operaterima u ovaj članakgdje su govorili o osnovnim idejama i principima svog rada. Ali ako je taj materijal više bio pogled sa strane rada gotovih komponenti za Kubernetes, onda je prijevod novog članka koji je sada predložen već vizija programera / DevOps inženjera, zbunjenog implementacijom novog operatera .

Pišemo operator za Kubernetes u Golangu

Odlučio sam da napišem ovaj post sa primjerom iz stvarnog života nakon što sam pokušao da pronađem dokumentaciju o kreiranju operatora za Kubernetes, prošao kroz proučavanje koda.

Primjer koji će biti opisan je sljedeći: u našem Kubernetes klasteru, svaki Namespace predstavlja okruženje sa sandboxom tima, a mi smo željeli ograničiti pristup njima tako da timovi mogu igrati samo u svojim sandboxovima.

Možete postići ono što želite dodjeljivanjem korisnika grupi koja ima RoleBinding na specific Namespace и ClusterRole sa pravima uređivanja. YAML reprezentacija ć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.yaml, in sirov)

Kreirajte takve RoleBinding možete to učiniti ručno, ali kada dobijete više od stotinu imenskih prostora, to postaje dosadan zadatak. Tu su Kubernetes operateri korisni – oni vam omogućavaju da automatizujete kreiranje Kubernetes resursa na osnovu promena u resursima. U našem slučaju želimo da stvaramo RoleBinding prilikom stvaranja Namespace.

Prije svega, definirajmo funkciju main, koji vrši potrebnu postavku za pokretanje naredbe, a zatim poziva akciju naredbe:

(Bilješka. transl.: u daljem tekstu, komentari u kodu su prevedeni na ruski. Osim toga, uvlačenje je ispravljeno za razmake umjesto [preporučeno u Go] karticama isključivo u svrhu bolje čitljivosti u okviru Habrovog izgleda. Nakon svake liste, postoje veze do originala na GitHub-u, gdje se čuvaju 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()   // Ожидаем, что все остановлено
}

(main.go, in sirov)

Radimo sljedeće:

  1. Postavili smo rukovaoce za specifične signale operativnog sistema kako bismo naveli operatera da graciozno izađe.
  2. Koristimo WaitGroupda graciozno zaustavite sve gorrutine prije nego što se aplikacija završi.
  3. Omogućavamo pristup klasteru kreiranjem clientset.
  4. Pokreni NamespaceController, u kojem će se nalaziti sva naša logika.

Sada nam je potrebna osnova za logiku, a u našem slučaju to je pomenuto 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, in sirov)

Evo postavljamo SharedIndexInformer, koji će efikasno (koristeći keš memoriju) čekati promjene imenskog prostora (više o informatorima pročitajte u članku "Kako zapravo radi Kubernetes planer?"- cca. transl.). Nakon toga se povezujemo EventHandler informatoru, zahvaljujući kojem, prilikom dodavanja imenskog prostora (Namespace) funkcija se poziva 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))
  }
}

(controller.go, in sirov)

Dobijamo imenski prostor kao obj i pretvoriti ga u objekat Namespace. Onda definišemo RoleBinding, na osnovu YAML datoteke spomenute na početku, koristeći dani objekt Namespace i stvaranje RoleBinding. Na kraju evidentiramo da li je kreiranje 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
}

(controller.go, in sirov)

Evo razgovaramo WaitGroupda pokrenemo goroutinu i zatim pozovemo namespaceInformerkoji je prethodno definisan. Kada dođe stop signal, on će prekinuti funkciju, obavijestite WaitGroup, koji se više ne izvršava, a ova funkcija će izaći.

Informacije o izgradnji i pokretanju ove izjave na Kubernetes klasteru možete pronaći na spremišta na GitHub-u.

Na ovu izjavu, koja stvara RoleBinding kada Namespace u Kubernetes klasteru, spreman.

izvor: www.habr.com

Dodajte komentar