Notă. transl.: Operatorii sunt software auxiliare pentru Kubernetes, concepute pentru a automatiza execuția acțiunilor de rutină pe obiectele cluster atunci când apar anumite evenimente. Am scris deja despre operatori în
Am decis să scriu această postare cu un exemplu din viața reală după încercările mele de a găsi documentație despre crearea unui operator pentru Kubernetes, care a trecut prin studierea codului.
Exemplul care va fi descris este următorul: în clusterul nostru Kubernetes, fiecare Namespace
reprezintă mediul sandbox al unei echipe și am vrut să limităm accesul la acestea, astfel încât echipele să poată juca doar în propriile lor sandbox.
Puteți obține ceea ce doriți alocand unui utilizator un grup care are RoleBinding
la specific Namespace
и ClusterRole
cu drepturi de editare. Reprezentarea YAML va arăta astfel:
---
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
Creeaza una RoleBinding
O puteți face manual, dar după ce treceți marcajul sutei de spații de nume, devine o sarcină plictisitoare. Acesta este locul în care operatorii Kubernetes sunt la îndemână - vă permit să automatizați crearea resurselor Kubernetes pe baza modificărilor aduse resurselor. În cazul nostru dorim să creăm RoleBinding
în timp ce creează Namespace
.
În primul rând, să definim funcția main
care face configurarea necesară pentru a rula instrucțiunea și apoi apelează acțiunea instrucțiunii:
(Notă. transl.: aici și mai jos comentariile din cod sunt traduse în rusă. În plus, indentarea a fost corectată la spații în loc de file [recomandat în Go] numai în scopul unei mai bune lizibilități în aspectul Habr. După fiecare listă există link-uri către originalul de pe GitHub, unde sunt stocate comentariile și file-urile în limba engleză.)
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() // Ожидаем, что все остановлено
}
(
Facem următoarele:
- Configuram un handler pentru semnalele specifice ale sistemului de operare pentru a provoca terminarea gratioasa a operatorului.
- Folosim
WaitGroup
pentru a opri cu grație toate goroutinele înainte de a termina aplicația. - Oferim acces la cluster prin creare
clientset
. - Lansa
NamespaceController
, în care se va localiza toată logica noastră.
Acum avem nevoie de o bază pentru logică, iar în cazul nostru aceasta este cea menționată 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
}
(
Aici configuram SharedIndexInformer
, care va aștepta efectiv (folosind un cache) modificări în spațiile de nume (citiți mai multe despre informatori în articolul „EventHandler
la informator, astfel încât atunci când adăugați un spațiu de nume (Namespace
) este numită funcția createRoleBinding
.
Următorul pas este definirea acestei funcții 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))
}
}
(
Obținem spațiul de nume ca obj
și convertiți-l într-un obiect Namespace
. Apoi definim RoleBinding
, pe baza fișierului YAML menționat la început, folosind obiectul furnizat Namespace
și creând RoleBinding
. În cele din urmă, înregistrăm dacă crearea a avut succes.
Ultima funcție care trebuie definită este 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
}
(
Aici vorbim WaitGroup
că lansăm goroutina și apoi sunăm namespaceInformer
, care a fost definit anterior. Când sosește semnalul de oprire, acesta va încheia funcția, informează WaitGroup
, care nu mai este executată, iar această funcție va ieși.
Informații despre construirea și rularea acestei instrucțiuni pe un cluster Kubernetes pot fi găsite în
Asta este pentru operatorul care creează RoleBinding
cand Namespace
în clusterul Kubernetes, gata.
Sursa: www.habr.com