» » Пишем оператора для Kubernetes на Golang

 

Пишем оператора для Kubernetes на Golang

Автор: admin от 12-09-2017, 09:15, посмотрело: 266

[i]Прим. перев.: Операторы (operators) — это вспомогательное ПО для Kubernetes, призванное автоматизировать выполнение рутинных действий над объектами кластера при определённых событиях. Мы уже писали об операторах в этой статье, где рассказывали об основополагающих идеях и принципах их работы. Но если тот материал был скорее взглядом со стороны эксплуатации готовых компонентов для Kubernetes, то предлагаемый теперь перевод новой статьи — это уже видение разработчика/DevOps-инженера, озадаченного реализацией нового оператора.[/i]



Пишем оператора для Kubernetes на Golang


Этот пост с примером из реальной жизни я решил написать после своих попыток найти документацию по созданию оператора для Kubernetes, прошедших через изучение кода.



Пример, который будет описан, таков: в нашем кластере Kubernetes каждый Namespace представляет окружение-песочницу какой-то команды, и мы хотели ограничить доступ к ним так, чтобы команды могли играть только в своих песочницах.rolebinding.yaml, в raw)[/i]



Создавать такой RoleBinding можно и вручную, но после преодоления отметки в сотню пространств имён это становится утомительным занятием. Как раз здесь помогают операторы Kubernetes — они позволяют автоматизировать создание ресурсов Kubernetes, основываясь на изменениях в ресурсах. В нашем случае мы хотим создавать RoleBinding при создании Namespace.



Первым делом определим функцию main, которая выполняет требуемую настройку для запуска оператора и затем вызывает действие оператора:



[i](Прим. перев.: здесь и далее комментарии в коде переведены на русский язык. Кроме того, отступы исправлены на пробелы вместо [рекомендуемых в Go] табов исключительно с целью лучшей читаемости в рамках вёрстки Хабры. После каждого листинга приведены ссылки на оригинал на GitHub, где сохранены англоязычные комментарии и табы.)[/i]



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


[i](main.go, в raw)[/i]



Мы делаем следующее:




  1. Настраиваем обработчика конкретных сигналов операционной системы, чтобы вызвать корректное (graceful) завершение работы оператора.

  2. Используем WaitGroup, чтобы корректно остановить все функции Go перед завершением работы приложения.

  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
}


[i](controller.go, в raw)[/i]



Здесь мы настраиваем SharedIndexInformer, который будет эффективно (используя кэш) ожидать изменений в пространствах имён [i](подробнее об informers читайте в статье «Как на самом деле работает планировщик Kubernetes?» — прим. перев.)[/i]. После этого мы подключаем 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))
  }
}


[i](controller.go, в raw)[/i]



Мы получаем пространство имён как obj и преобразуем его в объект Namespace. Затем определяем RoleBinding, основываясь на упомянутом в начале YAML-файле, используя предоставленный объект Namespace и создавая RoleBinding. Наконец, логируем, успешно ли прошло создание.



Последняя функция, которую необходимо определить, — Run:



// Run запускает процесс ожидания изменений в пространствах имён
// и действия в соответствии с этими изменениями.
func (c *NamespaceController) Run(stopCh <-chan struct{}, wg *sync.WaitGroup) {
  // Когда эта функция завершена, пометим функцию go как выполненную
  defer wg.Done()

  // Инкрементируем wait group, т.к. собираемся вызвать функцию go
  wg.Add(1)

  // Вызываем функцию go
  go c.namespaceInformer.Run(stopCh)

  // Ожидаем получения стоп-сигнала
  <-stopCh
}


[i](controller.go, в raw)[/i]



Здесь мы говорим WaitGroup, что запустим функцию go и затем вызываем namespaceInformer, который был предварительно определён. Когда поступит сигнал остановки, он завершит функцию go, сообщит WaitGroup, что больше не выполняется, и эта функция завершит свою работу.



Информацию о сборке и запуске этого оператора в кластере Kubernetes можно найти в репозитории на GitHub.



На этом оператор, который создаёт RoleBinding при появлении Namespace в кластере Kubernetes, готов.

Источник: Хабрахабр

Категория: Программирование

Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь.
Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

Добавление комментария

Имя:*
E-Mail:
Комментарий:
Полужирный Наклонный текст Подчеркнутый текст Зачеркнутый текст | Выравнивание по левому краю По центру Выравнивание по правому краю | Вставка смайликов Выбор цвета | Скрытый текст Вставка цитаты Преобразовать выбранный текст из транслитерации в кириллицу Вставка спойлера
Введите два слова, показанных на изображении: *