Shën. përkth.: Operatorët (operators) janë softuer të ndihmës për Kubernetes, të caktuar për të automatizuar ekzekutimin e veprimeve rutinore mbi objektet e klasterit në ngjarje të caktuara. Ne tashmë kemi shkruar për operatorët në , ku kemi diskutuar idetë themelore dhe parimet e funksionimit të tyre. Por nëse ai material ishte më shumë një pikëpamje nga jashtë e shfrytëzimit të komponentëve të gatshëm për Kubernetes, tani po ofrohet përkthimi i një artikulli të ri — ky është një shikim nga zhvilluesi/ingjineri DevOps, i shqetësuar për realizimin e një operatori të ri.

Ky postim me një shembull nga jeta reale vendosa ta shkruaj pas përpjekjeve të mia për të gjetur dokumentacionin mbi krijimin e një operatori për Kubernetes, që kaloi përmes studimit të kodit.
Shembulli që do të përshkruhet është: në klasterin tonë Kubernetes, çdo Namespace paraqet një ambient-lojë për ndonjë ekip, dhe ne do t'i kufizojmë qasjen atyre në mënyrë që ekipet të mund të luajnë vetëm në ambjentet e tyre të lojës.
Të arrihet e dëshiruara mund të bëhet përmes caktimit të një grupi përdoruesish, të cilit i jepet RoleBinding për një Namespace и ClusterRole me të drejta redaktimi. 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ë )
Krijimi i tillë RoleBinding mund të bëhet edhe manualisht, por pasi të kalosh shifrën njëqind të hapësirave të emrave, kjo bëhet një veprim i lodhshëm. Pikërisht këtu ndihmojnë operatorët e Kubernetes — ata lejojnë automatizimin e krijimit të resurseve Kubernetes, bazuar në ndryshimet në resurset. Në rastin tonë, ne do të duam të krijojmë RoleBinding kur krijojmë Namespace.
Së pari do të përcaktojmë funksionin main, i cili realizon konfigurimin e kërkuar për të nisur operatorin dhe pastaj thërret veprimin e operatorit:
(Shën. përkth.: këtu dhe më tej komentet në kod janë përkthyer në shqip. Më tej, hapësirat janë rregulluar në hapësira në vend të [të rekomanduarave në Go] tabave thjesht për lehtësimin e lexueshmërisë brenda formatimit të Habr-së. Pas çdo listimi janë dhënë lidhjet në origjinal në GitHub, ku janë ruajtur komentet dhe tabat në anglisht.)
func main() {
// Vendosim daljen e logeve në STDOUT konsolë
log.SetOutput(os.Stdout)
sigs := make(chan os.Signal, 1) // Krijojmë një kanal për të pranuar sinjalet nga OS
stop := make(chan struct{}) // Krijojmë një kanal për të pranuar sinjalin e 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 që përfundimi i tyre të bëhet
wg := &sync.WaitGroup{}
runOutsideCluster := flag.Bool("run-outside-cluster", false, "Vendosni këtë flamur kur po funksiononi jashtë grumbullit.")
flag.Parse()
// Krijojmë clientset për të komunikuar me grumbullin Kubernetes
clientset, err := newClientSet(*runOutsideCluster)
if err != nil {
panic(err.Error())
}
controller.NewNamespaceController(clientset).Run(stop, wg)
<-sigs // Prisni sinjalet (deri sa të marrë një sinjal nuk ndodh asgjë tjetër)
log.Printf("Duke u mbyllur...")
close(stop) // I themi goroutines të ndalojnë
wg.Wait() // Pritni që gjithçka të jetë ndaluar
}(, në )
Ne po bëjmë si më poshtë:
- Konfiguroni trajtuesit e sinjaleve specifike të sistemit operativ për të nxitur një mbyllje të saktë (graceful) të operatorit.
- Përdorni
WaitGroup, për të ndaluar saktësisht të gjitha goroutines para se të mbyllet aplikacioni. - Sigurojmë qasje në grumbull duke krijuar
clientset. - Po e nisim
NamespaceController, ku do të vendoset e gjithë logjika jonë.
Tani është e nevojshme një bazë 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 ne po konfiguroni SharedIndexInformer, i cili do të presë në mënyrë efektive (duke përdorur cache) ndryshimet në hapësirat e emrave (për më shumë rreth informers lexoni artikullin "» — shënim i përkthyesit.). Pas kësaj ne lidhim EventHandler me informerin, kështu që kur një hapësirë emri shtohet (Namespace) funksioni createRoleBinding.
Hapi tjetër është të përcaktojmë 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ështimi për të krijuar Role Binding: %s", err.Error()))
} else {
log.Println(fmt.Sprintf("Krijuar AD RoleBinding për Namespace: %s", roleBinding.Name))
}
}(, në )
Ne e marrim hapësirën e emrit si obj dhe e kthejmë atë në një objekt Namespace. Pastaj përcaktojmë RoleBinding, duke u bazuar në atë që është përmendur në skedarin YAML të përmendur në fillim, duke përdorur objektin e ofruar Namespace dhe krijojmë RoleBinding. Në fund, regjistrojmë nëse krijimi kaloi me sukses.
Funksioni i fundit që duhet përcaktuar ë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 po themi WaitGroup, që do ta nisim goroutine-n dhe më pas do të thërrasim namespaceInformer, i cili ishte përcaktuar më parë. Kur të merret sinjali i ndaljes, ai do të përfundojë funksionin, duke raportuar WaitGroup, që nuk ekzekutohet më, dhe ky funksion do të përfundojë punën e tij.
Informacionin në lidhje me ndërtimin dhe nisjen e këtij operatori në klasterin Kubernetes mund ta gjeni në .
Në këtë pikë, operatori që krijon RoleBinding kur shfaqet Namespace në klasterin Kubernetes, është gati.
Burimi: habr.com
