- PVSM.RU - https://www.pvsm.ru -
Прим. перев.: Операторы (operators) — это вспомогательное ПО для Kubernetes, призванное автоматизировать выполнение рутинных действий над объектами кластера при определённых событиях. Мы уже писали об операторах в этой статье [1], где рассказывали об основополагающих идеях и принципах их работы. Но если тот материал был скорее взглядом со стороны эксплуатации готовых компонентов для Kubernetes, то предлагаемый теперь перевод новой статьи — это уже видение разработчика/DevOps-инженера, озадаченного реализацией нового оператора.
Этот пост с примером из реальной жизни я решил написать после своих попыток найти документацию по созданию оператора для 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 [2], в raw [3])
Создавать такой RoleBinding
можно и вручную, но после преодоления отметки в сотню пространств имён это становится утомительным занятием. Как раз здесь помогают операторы Kubernetes — они позволяют автоматизировать создание ресурсов Kubernetes, основываясь на изменениях в ресурсах. В нашем случае мы хотим создавать RoleBinding
при создании Namespace
.
Первым делом определим функцию main
, которая выполняет требуемую настройку для запуска оператора и затем вызывает действие оператора:
(Прим. перев.: здесь и далее комментарии в коде переведены на русский язык. Кроме того, отступы исправлены на пробелы вместо [рекомендуемых в Go] табов исключительно с целью лучшей читаемости в рамках вёрстки Хабры. После каждого листинга приведены ссылки на оригинал на 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() // Ожидаем, что все остановлено
}
Мы делаем следующее:
WaitGroup
, чтобы корректно остановить все функции Go перед завершением работы приложения.clientset
.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
}
(controller.go [6], в raw [7])
Здесь мы настраиваем SharedIndexInformer
, который будет эффективно (используя кэш) ожидать изменений в пространствах имён (подробнее об informers читайте в статье «Как на самом деле работает планировщик Kubernetes? [8]» — прим. перев.). После этого мы подключаем 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))
}
}
(controller.go [9], в raw [10])
Мы получаем пространство имён как 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
}
(controller.go [11], в raw [12])
Здесь мы говорим WaitGroup
, что запустим функцию go и затем вызываем namespaceInformer
, который был предварительно определён. Когда поступит сигнал остановки, он завершит функцию go, сообщит WaitGroup
, что больше не выполняется, и эта функция завершит свою работу.
Информацию о сборке и запуске этого оператора в кластере Kubernetes можно найти в репозитории на GitHub [13].
На этом оператор, который создаёт RoleBinding
при появлении Namespace
в кластере Kubernetes, готов.
Автор: shurup
Источник [14]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/open-source/263684
Ссылки в тексте:
[1] этой статье: https://habrahabr.ru/company/flant/blog/326414/
[2] rolebinding.yaml: https://gist.github.com/treacher/db476d33562363718743182e81ed87c1#file-rolebinding-yaml
[3] raw: https://gist.github.com/treacher/db476d33562363718743182e81ed87c1/raw/a4313fe4b3b48f2495c08d6eab7551ffa59c2ee2/rolebinding.yaml
[4] main.go: https://gist.github.com/treacher/f43489316983015423b2d15ddb1cf4d0#file-main-go
[5] raw: https://gist.github.com/treacher/f43489316983015423b2d15ddb1cf4d0/raw/716f1185449f710d5498d057f72e336481892a63/main.go
[6] controller.go: https://gist.github.com/treacher/b38c7383cebe01f57730f1f8956784fb#file-controller-go
[7] raw: https://gist.github.com/treacher/b38c7383cebe01f57730f1f8956784fb/raw/f766f6565b13e30e127b1fa86ad9533219581be0/controller.go
[8] Как на самом деле работает планировщик Kubernetes?: https://habrahabr.ru/company/flant/blog/335552/
[9] controller.go: https://gist.github.com/treacher/fe1a1f8cece6e9dd1c5ce63aa8e2cc67#file-controller-go
[10] raw: https://gist.github.com/treacher/fe1a1f8cece6e9dd1c5ce63aa8e2cc67/raw/93808f949655486c31cc29a240828e4443043238/controller.go
[11] controller.go: https://gist.github.com/treacher/193752d47f7f70150e28bfec4bcd00aa#file-controller-go
[12] raw: https://gist.github.com/treacher/193752d47f7f70150e28bfec4bcd00aa/raw/898beea3626c471f71ba4dc9584f8401d39a7ee8/controller.go
[13] репозитории на GitHub: https://github.com/treacher/namespace-rolebinding-operator
[14] Источник: https://habrahabr.ru/post/337698/
Нажмите здесь для печати.