Bem. overs.: Operatører (operators) er hjælpeprogrammer til Kubernetes, der har til formål at automatisere udførelsen af rutineopgaver på klyngeobjekter ved bestemte hændelser. Vi har allerede skrevet om operatører i , hvor vi diskuterede de grundlæggende ideer og principper for deres funktion. Men mens det materiale var mere et perspektiv fra driftssiden af de færdige komponenter til Kubernetes, så er den foreslåede oversættelse af den nye artikel allerede en udvikler-/DevOps-ingenørs forståelse, der er bekymret over implementeringen af en ny operator.

Jeg besluttede at skrive dette indlæg med et eksempel fra det virkelige liv efter mine forsøg på at finde dokumentation til oprettelse af en operator til Kubernetes, som gik gennem kodestudie.
Eksemplet, der vil blive beskrevet, er som følger: I vores Kubernetes-klynge repræsenterer hver Namespace et sandkasse-miljø for et bestemt team, og vi ønskede at begrænse adgangen til dem, så teamerne kun kunne lege i deres egne sandkasser.
Det ønskede resultat kan opnås ved at tildele brugeren en gruppe, der har RoleBinding til specifikke Namespace og ClusterRole med redigeringsretter. YAML-repræsentationen vil se således ud:
---
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(, i )
Sådan en RoleBinding kan også oprettes manuelt, men efter at have nået over et hundrede navneområder bliver det en trættende opgave. Netop her hjælper Kubernetes-operatører — de muliggør automatisering af oprettelse af Kubernetes-ressourcer baseret på ændringer i ressourcerne. I vores tilfælde ønsker vi at oprette RoleBinding ved oprettelsen af Namespace.
Først definerer vi en funktion main, som udfører den nødvendige konfiguration for at starte operatøren og derefter kalder operatørens handling:
(Bem. overs.: Her og fremover er kommentarerne i koden oversat til dansk. Desuden er indrykninger korrigeret til mellemrum i stedet for [de anbefalede i Go] tabulatortasterne udelukkende med henblik på bedre læsbarhed i forbindelse med udformningen af Habr. Efter hver liste er der links til originalen på GitHub, hvor de engelsksprogede kommentarer og tabulatortasterne er bevaret.)
func main() {
// Sætter logudskriften til konsolens STDOUT
log.SetOutput(os.Stdout)
sigs := make(chan os.Signal, 1) // Opretter en kanal til at modtage OS-signaler
stop := make(chan struct{}) // Opretter en kanal til at modtage stop-signal
// Registrerer modtagelsen af SIGTERM i kanalen sigs
signal.Notify(sigs, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)
// Goroutines kan selv tilføje sig til WaitGroup,
// så deres udførelse venter vi på
wg := &sync.WaitGroup{}
runOutsideCluster := flag.Bool("run-outside-cluster", false, "Sæt dette flag, når du kører uden for klyngen.")
flag.Parse()
// Opretter clientset til interaktion med Kubernetes-klyngen
clientset, err := newClientSet(*runOutsideCluster)
if err != nil {
panic(err.Error())
}
controller.NewNamespaceController(clientset).Run(stop, wg)
<-sigs // Venter på signaler (intet sker indtil der modtages et signal)
log.Printf("Afslutter...")
close(stop) // Fortæller goroutines at stoppe
wg.Wait() // Venter på at alt er stoppet
}(, i )
Vi gør følgende:
- Vi konfigurerer håndtereren for specifikke signalsystemer for at kalde korrekt (graceful) afslutning af operatøren.
- Vi bruger
WaitGroup, for korrekt at stoppe alle goroutines før afslutning af applikationen. - Vi giver adgang til klyngen ved at oprette
clientset. - Starting
NamespaceController, hvor al vores logik vil være placeret.
Nu er der brug for en grundfor logikken, og i vores tilfælde er det den nævnte 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
}(, i )
Her konfigurerer vi SharedIndexInformer, som effektivt (ved brug af cache) venter på ændringer i navnerum (læs mere om informers i artiklen "» — ft. overs.). Herefter tilslutter vi EventHandler til informeren, hvilket gør at ved tilføjelse af navnerum (Namespace) kaldes funktionen createRoleBinding.
Næste skridt er at definere denne funktion 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("Fejl ved oprettelse af Role Binding: %s", err.Error()))
} else {
log.Println(fmt.Sprintf("Oprettet AD RoleBinding for Namespace: %s", roleBinding.Name))
}
}(, i )
Vi får navnerummet som obj og konverterer det til et objekt Namespace. Herefter definerer vi RoleBinding, baseret på det nævnte YAML-dokument, ved at bruge det givne objekt Namespace og opretter RoleBinding. Til sidst logger vi, om oprettelsen var vellykket.
Den sidste funktion, der skal defineres, er Kør:
// 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
}(, i )
Her siger vi WaitGroup, at vi vil starte en goroutine og derefter kalde namespaceInformer, som blev defineret tidligere. Når stoppesignalet modtages, afslutter det funktionen, rapporterer WaitGroup, at den ikke længere kører, og denne funktion slutter sit arbejde.
Information om opbygningen og kørsel af denne operator i et Kubernetes-kluster kan findes i .
Så er denne operator, der opretter RoleBinding ved ankomsten Namespace i Kubernetes-klusteret, klar.
Kilde: habr.com
