Писање оператора за Кубернетес у Голангу

Белешка. трансл.: Оператори су помоћни софтвер за Кубернетес, дизајниран да аутоматизује извршавање рутинских радњи на објектима кластера када се догоде одређени догађаји. Већ смо писали о оператерима у Овај чланак, где су говорили о темељним идејама и принципима свог рада. Али ако је тај материјал више био поглед са стране рада са готовим компонентама за Кубернетес, онда је сада предложени превод новог чланка већ визија програмера/ДевОпс инжењера збуњеног имплементацијом новог оператера.

Писање оператора за Кубернетес у Голангу

Одлучио сам да напишем овај пост са примером из стварног живота након покушаја да пронађем документацију о креирању оператора за Кубернетес, који је прошао кроз проучавање кода.

Пример који ће бити описан је следећи: у нашем Кубернетес кластеру, сваки Namespace представља окружење пешчаног окружења тима, и желели смо да им ограничимо приступ тако да тимови могу да играју само у својим сандбоковима.

Можете постићи оно што желите тако што ћете доделити кориснику групу која има RoleBinding do specifičnih Namespace и ClusterRole са уређивачким правима. ИАМЛ репрезентација ће изгледати овако:

---
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 Можете то да урадите ручно, али након што пређете стотину именских простора, то постаје досадан задатак. Ово је место где Кубернетес оператери долазе на руку — они вам омогућавају да аутоматизујете креирање Кубернетес ресурса на основу промена ресурса. У нашем случају желимо да стварамо RoleBinding при стварању Namespace.

Пре свега, хајде да дефинишемо функцију mainкоји врши потребно подешавање за покретање наредбе, а затим позива акцију наредбе:

(Белешка. трансл.: овде и испод коментари у коду су преведени на руски. Поред тога, увлачење је исправљено на размаке уместо [препоручено у Го] картицама искључиво у сврху боље читљивости у оквиру Хабр распореда. После сваке листе постоје везе до оригинала на ГитХуб-у, где се чувају коментари и картице на енглеском језику.)

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

(маин.гоУ сиров)

Радимо следеће:

  1. Конфигуришемо руковалац за специфичне сигнале оперативног система да изазове грациозан завршетак оператора.
  2. Користимо WaitGroupда грациозно зауставите све горрутине пре окончања апликације.
  3. Омогућавамо приступ кластеру креирањем clientset.
  4. Лансирање NamespaceController, у коме ће се налазити сва наша логика.

Сада нам треба основа за логику, а у нашем случају је ово поменута 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
}

(контролор.гоУ сиров)

Овде конфигуришемо SharedIndexInformer, који ће ефективно (користећи кеш) чекати промене у именским просторима (више о информаторима прочитајте у чланку „Како заправо функционише Кубернетес планер?"- прибл. превод). Након овога се повезујемо EventHandler у информатор, тако да приликом додавања именског простора (Namespace) функција се позива createRoleBinding.

Следећи корак је дефинисање ове функције 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))
  }
}

(контролор.гоУ сиров)

Добијамо именски простор као obj и претворити га у објекат Namespace. Затим дефинишемо RoleBinding, на основу ИАМЛ датотеке поменуте на почетку, користећи обезбеђени објекат Namespace и стварање RoleBinding. На крају евидентирамо да ли је креирање било успешно.

Последња функција коју треба дефинисати је 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
}

(контролор.гоУ сиров)

Ево ми причамо WaitGroupда покренемо гороутину и затим позовемо namespaceInformer, што је претходно дефинисано. Када стигне сигнал за заустављање, он ће прекинути функцију, обавестите WaitGroup, који се више не извршава, а ова функција ће изаћи.

Информације о изградњи и покретању ове изјаве на Кубернетес кластеру можете пронаћи у спремишта на ГитХуб-у.

То је то за оператера који креира RoleBinding када Namespace у Кубернетес кластеру, спреман.

Извор: ввв.хабр.цом

Додај коментар