Noter. trad.: Les opérateurs sont des logiciels auxiliaires pour Kubernetes, conçus pour automatiser l'exécution d'actions de routine sur les objets du cluster lorsque certains événements se produisent. Nous avons déjà parlé des opérateurs dans
J'ai décidé d'écrire cet article avec un exemple concret après mes tentatives de recherche de documentation sur la création d'un opérateur pour Kubernetes, qui sont passées par l'étude du code.
L'exemple qui va être décrit est le suivant : dans notre cluster Kubernetes, chaque Namespace
représente l'environnement sandbox d'une équipe, et nous voulions en limiter l'accès afin que les équipes ne puissent jouer que dans leur propre sandbox.
Vous pouvez réaliser ce que vous voulez en attribuant à un utilisateur un groupe qui a RoleBinding
à spécifique Namespace
и ClusterRole
avec droits d'édition. La représentation YAML ressemblera à ceci :
---
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
(
Créer une RoleBinding
Vous pouvez le faire manuellement, mais après avoir franchi la barre des cent espaces de noms, cela devient une tâche fastidieuse. C'est là que les opérateurs Kubernetes s'avèrent utiles : ils vous permettent d'automatiser la création de ressources Kubernetes en fonction des modifications apportées aux ressources. Dans notre cas, nous voulons créer RoleBinding
en créant Namespace
.
Tout d'abord, définissons la fonction main
qui effectue la configuration requise pour exécuter l'instruction, puis appelle l'action de l'instruction :
(Noter. trad.: ici et ci-dessous les commentaires dans le code sont traduits en russe. De plus, l'indentation a été corrigée en espaces au lieu des onglets [recommandés dans Go] uniquement dans le but d'une meilleure lisibilité dans la mise en page Habr. Après chaque liste, il y a des liens vers l'original sur GitHub, où les commentaires et les onglets en anglais sont stockés.)
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() // Ожидаем, что все остановлено
}
Nous procédons comme suit :
- Nous configurons un gestionnaire pour des signaux spécifiques du système d'exploitation afin de provoquer la résiliation en douceur de l'opérateur.
- Nous utilisons
WaitGroup
pour arrêter gracieusement toutes les goroutines avant de terminer l'application. - Nous donnons accès au cluster en créant
clientset
. - Run
NamespaceController
, dans lequel se situera toute notre logique.
Nous avons maintenant besoin d'une base logique, et dans notre cas, c'est celle mentionnée 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
}
(
Ici, nous configurons SharedIndexInformer
, qui attendra effectivement (à l'aide d'un cache) les changements dans les espaces de noms (en savoir plus sur les informateurs dans l'article «EventHandler
à l'informateur, de sorte que lors de l'ajout d'un espace de noms (Namespace
) la fonction est appelée createRoleBinding
.
La prochaine étape consiste à définir cette fonction 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))
}
}
(
Nous obtenons l'espace de noms comme obj
et convertissez-le en objet Namespace
. Ensuite, nous définissons RoleBinding
, basé sur le fichier YAML mentionné au début, en utilisant l'objet fourni Namespace
et créer RoleBinding
. Enfin, nous enregistrons si la création a réussi.
La dernière fonction à définir est 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
}
(
Ici, nous parlons WaitGroup
qu'on lance la goroutine puis qu'on appelle namespaceInformer
, qui a été défini précédemment. Lorsque le signal d'arrêt arrive, il mettra fin à la fonction, informera WaitGroup
, qui n'est plus exécuté, et cette fonction se terminera.
Des informations sur la création et l'exécution de cette instruction sur un cluster Kubernetes sont disponibles dans
Voilà pour l'opérateur qui crée RoleBinding
lorsque Namespace
dans le cluster Kubernetes, prêt.
Source: habr.com