Pozn. prekl.: Operátori (operators) sú pomocný softvér pre Kubernetes, ktorý má za cieľ automatizovať vykonávanie rutinných úkonov nad objektmi klastru pri určitých udalostiach. Už sme písali o operadoroch v , kde sme sa venovali základným myšlienkam a princípom ich fungovania. Ale zatiaľ čo ten materiál bol skôr pohľadom z hľadiska prevádzky hotových komponentov pre Kubernetes, tak tento preklad novej článku je už pohľadom vývojára/DevOps inžiniera, ktorého zaujíma implementácia nového operátora.

Tento príspevok s príkladom z reálneho života som sa rozhodol napísať po svojich pokusoch nájsť dokumentáciu na vytvorenie operátora pre Kubernetes, ktoré prešli cez štúdium kódu.
Príklad, ktorý bude popísaný, je nasledujúci: v našom klastri Kubernetes každý Namespace predstavuje sandboxové prostredie nejakého tímu, a my sme chceli obmedziť prístup k nim tak, aby tímy mohli hrať len vo svojich pieskoviskách.
Dosiahnutie želaného je možné priradením užívateľovi skupiny, ktorá má RoleBinding k konkrétnym Namespace a ClusterRole s právom na úpravy. YAML predstavenie bude vyzerať takto:
---
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(, v )
Vytvoriť takýto RoleBinding môžete aj manuálne, ale po prekročení hranice sto priestorov názvov sa to stáva únavnou činnosťou. Práve tu pomáhajú operátori Kubernetes — umožňujú automatizovať vytváranie zdrojov Kubernetes na základe zmien v zdrojoch. V našom prípade chceme vytvárať RoleBinding pri vytváraní Namespace.
Najprv určíme funkciu main, ktorá vykonáva požadované nastavenie pre spustenie operátora a následne vyvoláva akciu operátora:
(Pozn. prekl.: tu a ďalej sú komentáre v kóde preložené do slovenčiny. Okrem toho, odsadenia sú opravené na medzery namiesto [odporúčaných v Go] tabulátorov výlučne za účelom lepšej čitateľnosti v rámci rozloženia Habra. Po každom výpise sú uvedené odkazy na originál na GitHub, kde sú zachované anglické komentáre a tabulátory.)
func main() {
// Nastavíme výstup logov do konzolového STDOUT
log.SetOutput(os.Stdout)
sigs := make(chan os.Signal, 1) // Vytvoríme kanál na prijímanie signálov OS
stop := make(chan struct{}) // Vytvoríme kanál na prijímanie signálu na zastavenie
// Registrujeme prijímanie SIGTERM do kanála sigs
signal.Notify(sigs, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)
// Gorutiny sa môžu samy pridávať do WaitGroup,
// aby čakala na dokončenie ich vykonania
wg := &sync.WaitGroup{}
runOutsideCluster := flag.Bool("run-outside-cluster", false, "Nastavte toto flag pri spúšťaní mimo klastra.")
flag.Parse()
// Vytvoríme clientset na interakciu s klastrom Kubernetes
clientset, err := newClientSet(*runOutsideCluster)
if err != nil {
panic(err.Error())
}
controller.NewNamespaceController(clientset).Run(stop, wg)
<-sigs // Čakáme na signály (do prijatia signálu sa viac nič nedeje)
log.Printf("Zatvárame...")
close(stop) // Hovoríme gorutinám, aby sa zastavili
wg.Wait() // Čakáme, kým je všetko zastavené
}(, v )
Robíme nasledovné:
- Nastavujeme spracovateľa konkrétnych signálov operačného systému, aby sme vyvolali korektné (graceful) ukončenie prevádzky operátora.
- Používame
WaitGroup, aby sme korektne zastavili všetky gorutiny pred ukončením aplikácie. - Poskytujeme prístup k klastru vytvorením
clientset. - Spúšťame
NamespaceController, v ktorom sa nachádza celá naša logika.
Teraz potrebujeme základ pre logiku, a v našom prípade to je spomínaný 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
}(, v )
Tu nastavujeme SharedIndexInformer, ktorý efektívne (využívajúc cache) čaká na zmeny v názvových priestoroch (viac o informeroch čítajte v článku „» — pozn. prekl.). Potom pripojíme EventHandler k informeru, vďaka čomu sa pri pridávaní názvového priestoru (Namespace) vyvolá funkcia createRoleBinding.
Ďalší krok — definovať túto funkciu 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("Nepodarilo sa vytvoriť Role Binding: %s", err.Error()))
} else {
log.Println(fmt.Sprintf("Vytvorený AD RoleBinding pre Namespace: %s", roleBinding.Name))
}
}(, v )
Získavame názvový priestor ako obj a pretransformujeme ho na objekt NamespacePotom určíme RoleBinding, na základe toho, čo bolo uvedené na začiatku v YAML súbore, pomocou poskytnutého objektu Namespace a vytvoríme RoleBinding. Nakoniec logujeme, či bolo vytvorenie úspešné.
Posledná funkcia, ktorú je potrebné určiť, je Spustiť:
// 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
}(, v )
Tu hovoríme WaitGroup, že spustíme goroutine a následne voláme namespaceInformer, ktorý bol predtým definovaný. Keď príde signál na zastavenie, dokončí funkciu a oznámi WaitGroup, že sa už nevykonáva, a táto funkcia ukončí svoju činnosť.
Informácie o zostavení a spustení tohto operátora v Kubernetese nájdete v .
Na tomto operátore, ktorý vytvára RoleBinding po objavení sa Namespace v Kubernetese, je pripravený.
Zdroj: habr.com
