Obs. overs.: Operatører (operators) er programvare som hjelper Kubernetes med å automatisere utførelsen av rutineoppgaver på klusterobjekter ved bestemte hendelser. Vi har allerede skrevet om operatører i , der vi diskuterte de grunnleggende ideene og prinsippene for deres funksjon. Men hvis det materialet var mer et blikk fra utsiden på driften av ferdige komponenter for Kubernetes, er denne oversettelsen av en ny artikkel en utvikler/DevOps-ingeniørs perspektiv som fokuserer på implementeringen av en ny operator.

Jeg bestemte meg for å skrive dette innlegget med et eksempel fra den virkelige verden etter å ha forsøkt å finne dokumentasjon for å lage en operator for Kubernetes, som ble til i prosessen med å studere koden.
Eksempelet som vil bli beskrevet er som følger: I vårt Kubernetes-kluster Namespace representerer et sandkassemiljø for et team, og vi ønsker å begrense tilgangen til dem slik at teamene bare kan leke i sine egne sandkasser.
Dette kan oppnås ved å tildele en bruker en gruppe som har RoleBinding til bestemte Namespace og ClusterRole med redigeringsrettigheter. YAML-representasjonen vil se slik ut:
---
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 )
En slik kan også opprettes manuelt, men etter å ha nådd grensen på hundre namespaces blir dette en slitsom oppgave. Her kommer Kubernetes-operatørene til nytte – de gjør det mulig å automatisere opprettelse av Kubernetes-ressurser basert på endringer i ressursene. I vårt tilfelle ønsker vi å opprette RoleBinding ved opprettelse RoleBinding Først vil vi definere funksjonen Namespace.
, som utfører den nødvendige konfigurasjonen for å kjøre operatøren og deretter kaller operatørens handling: main: her og videre er kommentarene i koden oversatt til norsk. I tillegg er innrykkene rettet til mellomrom i stedet for [anbefalte i Go] tabulatorer utelukkende for bedre lesbarhet i innholdet til Habr. Etter hvert kodeutdrag er det linker til originalen på GitHub, hvor de engelskspråklige kommentarene og tabulatorene er bevart.)
(Obs. overs.: her og videre er kommentarene i koden oversatt til russisk. I tillegg er innrykkene endret fra [anbefalte i Go] til rom for å forbedre lesbarheten i konteksten av Habr-oppsett. Etter hver opplisting er det lenker til originalen på GitHub, hvor de engelskspråklige kommentarene og innrykkene er bevart.)
func main() {
// Setter logging output to console STDOUT
log.SetOutput(os.Stdout)
sigs := make(chan os.Signal, 1) // Create a channel to receive OS signals
stop := make(chan struct{}) // Create a channel to receive a stop signal
// Register to receive SIGTERM in the sigs channel
signal.Notify(sigs, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)
// Goroutines can add themselves to the WaitGroup,
// to wait for their completion
wg := &sync.WaitGroup{}
runOutsideCluster := flag.Bool("run-outside-cluster", false, "Set this flag when running outside of the cluster.")
flag.Parse()
// Create a clientset to interact with the Kubernetes cluster
clientset, err := newClientSet(*runOutsideCluster)
if err != nil {
panic(err.Error())
}
controller.NewNamespaceController(clientset).Run(stop, wg)
<-sigs // Wait for signals (nothing else happens until a signal is received)
log.Printf("Shutting down...")
close(stop) // Tell goroutines to stop
wg.Wait() // Wait for everything to stop
}(, i )
Vi gjer det følgjande:
- Vi set opp handterarar for spesifikke signal frå operativsystemet for å utløysa ein korrekt (graceful) nedstengjing av operatøren.
- Bruker
WaitGroup, for å stoppa alle goroutines korrekt før applikasjonen stenger ned. - Gir tilgang til klusteret ved å opprette
clientset. - Startar
NamespaceController, der all logikken vår vil liggja.
No er grunnlaget for logikken på plass, og i vårt tilfelle er det den nemnte 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 set vi opp SharedIndexInformer, som vil effektivt (ved å bruke cache) vente på endringar i namnrom (les meir om informers i artikkelen «» — anmerkn. overs.). Etter dette koblar vi til EventHandler til informeren, slik at når eit namnrom blir lagt til (Namespace) blir funksjonen createRoleBinding.
Neste steg er å definera denne funksjonen 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))
}
}(, i )
Vi får namnrommet som obj og vi gjør det til et objekt Namespace. Deretter definerer vi RoleBinding, basert på det som er nevnt i YAML-filen ovenfor, ved å bruke det oppgitte objektet Namespace og oppretter RoleBinding. Til slutt logger vi om opprettingen var vellykket.
Den siste funksjonen som må defineres er 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
}(, i )
Her sier vi WaitGroup, at vi starter goroutine og deretter kaller namespaceInformer, som ble definert tidligere. Når stoppesignalet kommer, avslutter den funksjonen, og rapporterer WaitGroup, at den ikke lenger kjører, og denne funksjonen vil avslutte.
Informasjonen om bygging og kjøring av denne operatoren i Kubernetes-klusteret finner du i .
På dette tidspunktet er operatoren som oppretter RoleBinding når det vises Namespace i Kubernetes-klusteret klar.
Kilde: habr.com
