Nota. trad.: Gli operatori sono software ausiliario per Kubernetes, progettati per automatizzare l'esecuzione di azioni di routine sugli oggetti del cluster quando si verificano determinati eventi. Abbiamo già scritto degli operatori in
Ho deciso di scrivere questo post con un esempio di vita reale dopo i miei tentativi di trovare documentazione sulla creazione di un operatore per Kubernetes, che sono passati attraverso lo studio del codice.
L'esempio che verrà descritto è questo: nel nostro cluster Kubernetes, ciascuno Namespace
rappresenta l'ambiente sandbox di una squadra e volevamo limitarne l'accesso in modo che le squadre potessero giocare solo nei propri sandbox.
Puoi ottenere ciò che desideri assegnando a un utente un gruppo che ha RoleBinding
a specifico Namespace
и ClusterRole
con diritti di modifica. La rappresentazione YAML sarà simile a questa:
---
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
Crearne uno RoleBinding
Puoi farlo manualmente, ma dopo aver superato il limite dei cento spazi dei nomi, diventa un compito noioso. È qui che gli operatori Kubernetes tornano utili: ti consentono di automatizzare la creazione di risorse Kubernetes in base alle modifiche alle risorse. Nel nostro caso vogliamo creare RoleBinding
durante la creazione Namespace
.
Prima di tutto definiamo la funzione main
che esegue la configurazione richiesta per eseguire l'istruzione e quindi chiama l'azione dell'istruzione:
(Nota. trad.: qui e sotto i commenti nel codice sono tradotti in russo. Inoltre, il rientro è stato corretto in spazi anziché in schede [consigliato in Go] al solo scopo di una migliore leggibilità all'interno del layout Habr. Dopo ogni elenco sono presenti collegamenti all'originale su GitHub, dove sono archiviati commenti e schede in lingua inglese.)
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() // Ожидаем, что все остановлено
}
Facciamo quanto segue:
- Configuriamo un gestore per segnali specifici del sistema operativo per causare la terminazione regolare dell'operatore.
- Noi usiamo
WaitGroup
per interrompere con garbo tutte le goroutine prima di terminare l'applicazione. - Forniamo l'accesso al cluster creando
clientset
. - Lanciamo
NamespaceController
, in cui si troverà tutta la nostra logica.
Ora abbiamo bisogno di una base logica, e nel nostro caso è quella menzionata 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
}
(
Qui configuriamo SharedIndexInformer
, che effettivamente (utilizzando una cache) attenderà le modifiche negli spazi dei nomi (leggi di più sugli informatori nell’articolo “EventHandler
all'informatore, in modo che quando si aggiunge uno spazio dei nomi (Namespace
) viene chiamata la funzione createRoleBinding
.
Il passo successivo è definire questa funzione 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))
}
}
(
Otteniamo lo spazio dei nomi come obj
e convertirlo in un oggetto Namespace
. Quindi definiamo RoleBinding
, basato sul file YAML menzionato all'inizio, utilizzando l'oggetto fornito Namespace
e creare RoleBinding
. Infine, registriamo se la creazione ha avuto successo.
L'ultima funzione da definire è 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
}
(
Qui stiamo parlando WaitGroup
che lanciamo la goroutine e poi chiamiamo namespaceInformer
, che è stato precedentemente definito. Quando arriva il segnale di arresto, la funzione terminerà, informa WaitGroup
, che non viene più eseguito, e questa funzione uscirà.
È possibile trovare informazioni sulla creazione e l'esecuzione di questa istruzione su un cluster Kubernetes in
Questo è tutto per l'operatore che crea RoleBinding
all'apparenza Namespace
nel cluster Kubernetes, pronto.
Fonte: habr.com