Shën. përk.: Operatorët (operators) janë software ndihmës për Kubernetes, të krijuara për të automatizuar ekzekutimin e veprimeve rutinë mbi objektet e klastra në momente të caktuara. Ne kemi shkruar tashmë për operatorët në , ku folëm mbi idetë dhe parimet themelore të funksionimit të tyre. Por nëse ai material ishte më shumë një perspektivë mbi përdorimin e komponentëve të gatshëm për Kubernetes, ky përkthim i artikullit të ri është vizion i një zhvilluesi/ingjiner DevOps, të ndodhur me zbatimin e një operatori të ri.

Ky post me një shembull nga jeta reale e kam vendosur ta shkruaj pas përpjekjeve të mia për të gjetur dokumentacionin mbi krijimin e operatorëve për Kubernetes, që kaluan përmes studimit të kodit.
Shembulli që do të përshkruhet është: në klastern tonë Kubernetes secili Namespace predimon një ambient-kopsht të një ekipi, dhe ne dëshironim të kufizojmë aksesin në to në mënyrë që ekipet të mund të luajnë vetëm në kopshtet e tyre.
Arrihet e dëshiruara duke i caktuar përdoruesit një grupi, të cilit i janë RoleBinding në lidhje me Namespace dhe ClusterRole me të drejtën për të redaktuar. Përfaqësimi YAML do të duket kështu:
---
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(, në )
Një të tillë RoleBinding mund të krijohet dhe manualisht, por pas tejkalimit të shenjës njëqind hapësirash emrash bëhet një detyrë e mërzitshme. Këtu ndihmojnë operatorët Kubernetes — ata lejojnë automatizimin e krijimit të burimeve Kubernetes, bazuar në ndryshimet në burimet. Në rastin tonë, ne duam të krijojmë RoleBinding në krijimin e Namespace.
Së pari do të përcaktojmë funksionin main, i cili kryen konfigurimin e kërkuar për të nisur operatorin dhe më pas thërret veprimin e operatorit:
(Shën. përk.: këtu dhe më pas komentet në kod janë përkthyer në rusisht. Për më tepër, hapësirat janë rregulluar me hapësira në vend të [të rekomanduarave në Go] tab-e, vetëm për qëllim më të mirë lexueshmërie në kuadër të shfaqjes së Habra. Pas çdo listimi, janë përfshirë lidhjet në origjinalin në GitHub, ku janë ruajtur komentet në anglisht dhe tab-et.)
func main() {
// Përcaktojmë outputin e logut në STDOUT konsolë
log.SetOutput(os.Stdout)
sigs := make(chan os.Signal, 1) // Krijojmë një kanal për marrjen e sinjaleve OS
stop := make(chan struct{}) // Krijojmë një kanal për marrjen e sinjalit të ndalimit
// Regjistrojmë marrjen e SIGTERM në kanalin sigs
signal.Notify(sigs, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)
// Goroutines mund të shtojnë vetveten në WaitGroup,
// për të pritur për përfundimin e tyre
wg := &sync.WaitGroup{}
runOutsideCluster := flag.Bool("run-outside-cluster", false, "Vendosni këtë flamur kur ekzekutoni jashtë klastri.")
flag.Parse()
// Krijojmë clientset për ndërveprim me klasterin Kubernetes
clientset, err := newClientSet(*runOutsideCluster)
if err != nil {
panic(err.Error())
}
controller.NewNamespaceController(clientset).Run(stop, wg)
<-sigs // Presim sinjale (deri në marrjen e sinjalit, asgjë tjetër nuk ndodh)
log.Printf("Po mbyllet...")
close(stop) // I thërrasim goroutines të ndalen
wg.Wait() // Pritëm që të gjitha të ndalen
}(, në )
Ne bëjmë si më poshtë:
- Konfigurojmë menaxherin e sinjaleve specifike të sistemit operativ, për të thirrur një mbyllje të duhur (graceful) të operatorit.
- Përdorim
WaitGroup, për të ndaluar siç duhet të gjitha goroutines përpara përfundimit të aplikacionit. - Sigurojmë aksesin në klaster duke krijuar
clientset. - Nisni
NamespaceController, ku do të vendoset e gjithë logjika jonë.
Tani na nevojitet baza për logjikën, dhe në rastin tonë kjo është e përmendur 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
}(, në )
Këtu konfigurajmë SharedIndexInformer, i cili do të presë në mënyrë efikase (duke përdorur caching) ndryshimet në hapësirat e emrave (lexoni më shumë rreth informers në artikullin “» — shën. transl.). Pas kësaj ne lidhemi EventHandler me informerin, duke bërë që kur shtohet një hapësirë emërtimi (Namespace) të përmbyset funksioni createRoleBinding.
Hapi tjetër — përcaktoni këtë funksion 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("Dështoi të krijojë Role Binding: %s", err.Error()))
} else {
log.Println(fmt.Sprintf("Krijuar AD RoleBinding për Namespace: %s", roleBinding.Name))
}
}(, në )
Ne marrim hapësirën e emrit si obj dhe e kthejmë atë në një objekt Namespace. Pas kësaj përcaktojmë RoleBinding, duke u bazuar në YAML-in përmendur në fillim, duke përdorur objektin e dhënë Namespace dhe duke krijuar RoleBinding. Në fund, e regjistrojmë nëse krijimi shkoi me sukses.
Funksioni i fundit që duhet të përcaktohet është Ekzekuto:
// 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
}(, në )
Këtu ne themi WaitGroup, se do të nisim një goroutine dhe më pas thërrasim namespaceInformer, i cili ishte përcaktuar më parë. Kur të vijë sinjali i ndaljes, ai do ta përfundojë funksionin, do të raportojë WaitGroup, se nuk po ekzekutohet më, dhe ky funksion do ta përfundojë punën e tij.
Informacionin mbi ndërtimin dhe ekzekutimin e këtij operatori në klasterin Kubernetes mund ta gjeni në .
Në këtë operator, i cili krijon RoleBinding me shfaqjen e Namespace në klasterin Kubernetes, është gati.
Burimi: habr.com
