Kubernetes operatora rakstīšana Golangā

Piezīme. tulk.: operatori ir Kubernetes palīgprogrammatūra, kas paredzēta, lai automatizētu parasto darbību izpildi klastera objektos, kad notiek noteikti notikumi. Mēs jau rakstījām par operatoriem Šis raksts, kurā tika runāts par sava darba pamatidejām un principiem. Bet, ja šis materiāls vairāk bija skats no Kubernetes gatavu komponentu darbības puses, tad tagad piedāvātā jaunā raksta tulkojums jau ir izstrādātāja/DevOps inženiera redzējums, kurš ir neizpratnē par jauna operatora ieviešanu.

Kubernetes operatora rakstīšana Golangā

Es nolēmu uzrakstīt šo ziņu ar piemēru no reālās dzīves pēc tam, kad mēģināju atrast dokumentāciju par Kubernetes operatora izveidi, kas tika veikta koda pētīšanā.

Piemērs, kas tiks aprakstīts, ir šāds: mūsu Kubernetes klasterī katrs Namespace ir komandas smilškastes vide, un mēs vēlējāmies ierobežot piekļuvi tām, lai komandas varētu spēlēt tikai savās smilšu kastēs.

Varat sasniegt to, ko vēlaties, piešķirot lietotājam grupu, kurai ir RoleBinding uz konkrētu Namespace и ClusterRole ar rediģēšanas tiesībām. YAML attēlojums izskatīsies šādi:

---
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

(lomu saistīšana.yamlUz neapstrādāts)

Izveidot vienu RoleBinding To var izdarīt manuāli, taču pēc simts nosaukumvietu atzīmes šķērsošanas tas kļūst par nogurdinošu uzdevumu. Šeit noder Kubernetes operatori — tie ļauj automatizēt Kubernetes resursu izveidi, pamatojoties uz izmaiņām resursos. Mūsu gadījumā mēs vēlamies radīt RoleBinding veidojot Namespace.

Vispirms definēsim funkciju mainkas veic nepieciešamo iestatījumu, lai palaistu priekšrakstu, un pēc tam izsauc paziņojuma darbību:

(Piezīme. tulk.: šeit un zemāk komentāri kodā ir tulkoti krievu valodā. Turklāt atkāpe ir koriģēta uz atstarpēm, nevis [ieteicams lietotnē Go] cilnēm, lai nodrošinātu labāku lasāmību Habr izkārtojumā. Pēc katra ieraksta ir saites uz oriģinālu vietnē GitHub, kur tiek glabāti komentāri un cilnes angļu valodā.)

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()   // Ожидаем, что все остановлено
}

(galvenais.goUz neapstrādāts)

Mēs veicam šādas darbības:

  1. Mēs konfigurējam apdarinātāju konkrētiem operētājsistēmas signāliem, lai izraisītu operatora graciozu pārtraukšanu.
  2. Mēs izmantojam WaitGrouplai graciozi apturētu visas gorutīnas pirms pieteikuma pārtraukšanas.
  3. Mēs nodrošinām piekļuvi klasterim, izveidojot clientset.
  4. Palaist NamespaceController, kurā atradīsies visa mūsu loģika.

Tagad mums ir vajadzīgs pamats loģikai, un mūsu gadījumā tas ir minēts 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.goUz neapstrādāts)

Šeit mēs konfigurējam SharedIndexInformer, kas efektīvi (izmantojot kešatmiņu) gaidīs izmaiņas nosaukumvietās (vairāk par informatoriem lasiet rakstā “Kā patiesībā darbojas Kubernetes plānotājs?"- apm. tulkojums). Pēc tam mēs savienojam EventHandler ziņotājam, lai, pievienojot nosaukumvietu (Namespace) tiek izsaukta funkcija createRoleBinding.

Nākamais solis ir definēt šo funkciju 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.goUz neapstrādāts)

Mēs iegūstam nosaukumvietu kā obj un pārvērst to par objektu Namespace. Tad mēs definējam RoleBinding, pamatojoties uz sākumā minēto YAML failu, izmantojot norādīto objektu Namespace un radot RoleBinding. Visbeidzot mēs reģistrējam, vai izveide bija veiksmīga.

Pēdējā definējamā funkcija ir 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.goUz neapstrādāts)

Šeit mēs runājam WaitGroupka mēs palaižam gorutīnu un pēc tam zvanām namespaceInformer, kas ir definēts iepriekš. Kad pienāk apstāšanās signāls, tas beigs funkciju, informē WaitGroup, kas vairs netiek izpildīts, un šī funkcija tiks aizvērta.

Informāciju par šī paziņojuma izveidi un izpildi Kubernetes klasterī var atrast GitHub krātuves.

Tas ir paredzēts operatoram, kurš rada RoleBinding kad Namespace Kubernetes klasterī, gatavs.

Avots: www.habr.com

Pievieno komentāru