Ние пишем оператор за Kubernetes в Golang

Забележка. превод: Операторите са спомагателен софтуер за Kubernetes, предназначен да автоматизира изпълнението на рутинни действия върху клъстерни обекти по време на определени събития. Вече писахме за операторите в тази статиякъдето разказаха за основните идеи и принципи на тяхната работа. Но ако този материал беше по-скоро поглед от страна на работата на готови компоненти за Kubernetes, тогава преводът на новата статия, предложена сега, вече е визията на разработчик / DevOps инженер, озадачен от внедряването на нов оператор .

Ние пишем оператор за Kubernetes в Golang

Реших да напиша тази публикация с пример от реалния живот, след като опитите ми да намеря документация за създаване на оператор за Kubernetes преминаха през проучването на кода.

Примерът, който ще бъде описан, е следният: в нашия клъстер Kubernetes всеки Namespace представлява пясъчната среда на отбора и искахме да ограничим достъпа до тях, така че отборите да могат да играят само в собствените си пясъчни кутии.

Можете да постигнете това, което искате, като присвоите потребител към група, която има RoleBinding към конкретни Namespace и ClusterRole с права за редакция. YAML представянето ще изглежда така:

---
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, Аз Н суров)

Създайте такива RoleBinding можете да го направите ръчно, но след като получите над сто пространства от имена, това се превръща в досадна задача. Това е мястото, където операторите на Kubernetes са полезни - те ви позволяват да автоматизирате създаването на ресурси на Kubernetes въз основа на промени в ресурсите. В нашия случай искаме да създаваме RoleBinding докато създавате Namespace.

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

(Забележка. превод: по-нататък коментарите в кода са преведени на руски език. В допълнение, отстъпът е коригиран за интервали вместо [препоръчани в Go] раздели единствено с цел по-добра четливост в рамките на оформлението на Habr. След всеки списък има връзки към оригинала в GitHub, където се запазват коментари и раздели на английски език.)

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, Аз Н суров)

Правим следното:

  1. Настроихме манипулатор за специфични сигнали на операционната система, за да накара оператора да излезе грациозно.
  2. употреба WaitGroupза грациозно спиране на всички goroutines преди приложението да приключи.
  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, който ефективно (използвайки кеша) ще изчака промени в пространството на имената (прочетете повече за информаторите в статията "Как всъщност работи планировчикът на Kubernetes?"- прибл. превод). След това се свързваме 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, въз основа на YAML файла, споменат в началото, използвайки предоставения обект 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че стартираме goroutine и след това извикваме namespaceInformerкойто е дефиниран предварително. При постъпване на стоп сигнал това ще прекрати функцията, информират WaitGroup, който вече не се изпълнява и тази функция ще излезе.

Информация за изграждането и изпълнението на този оператор на Kubernetes клъстер може да бъде намерена на хранилища в GitHub.

На това твърдение, което създава RoleBinding кога Namespace в клъстер Kubernetes, готов.

Източник: www.habr.com

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