Megjegyzés fordítás.: Az operátorok (operators) segédprogramok a Kubernetes számára, amelynek célja a rutinfeladatok automatizálása a klaszter objektumainak kezelése során bizonyos események hatására. Már írtunk az operátorokról a , ahol a működésük alapelveiről és koncepcióiról beszéltünk. De míg az előző anyag inkább a kész komponensek üzemeltetésére fókuszált, a mostani cikk fordítása már egy fejlesztő/DevOps mérnök nézőpontja, akit az új operátor megvalósítása foglalkoztat.

Ezt a bejegyzést egy valós példával azért írtam, mert próbáltam dokumentációt találni a Kubernetes operátor létrehozásáról, ami a kód tanulmányozásával indult.
A példa, amit itt bemutatok, a következő: a Kubernetes klaszterünkben minden Namespace egy-egy csapat homokozó környezetét jelenti, és szerettük volna korlátozni a hozzáférést úgy, hogy a csapatok csak a saját homokozóikban tudjanak játszani.
A kívánt cél elérhető, ha a felhasználónak egy csoportot rendelünk, amelynek RoleBinding jogosultságai vannak Namespace és ClusterRole szerkesztési joggal. A YAML-ábrázolás így fog kinézni:
---
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(, in )
Ilyen RoleBinding manuálisan is létrehozható, de miután átlépjük a száz namespace határt, ez unalmas tevékenységgé válik. Itt jönnek a Kubernetes operátorok segítségére, mivel lehetővé teszik a Kubernetes erőforrások automatikus létrehozását az erőforrások változásai alapján. Az esetünkben azt szeretnénk, hogy RoleBinding a létrehozás során Namespace.
Először határozzuk meg a mainfunkciót, amely végrehajtja a szükséges beállításokat az operátor indításához, majd meghívja az operátor műveletet:
(Megjegyzés fordítás.: itt és a továbbiakban a kódban található megjegyzések oroszra lettek fordítva. Ezen kívül a behúzások a [Go-ban ajánlott] tabok helyett szóközökre lettek cserélve kizárólag a Habra struktúráján belüli jobb olvashatóság érdekében. Minden listázás után hivatkozásokat adunk meg az eredetire a GitHubon, ahol megmaradtak az angol nyelvű megjegyzések és tabok.
func main() {
// Beállítjuk a naplózás kimenetét a konzol STDOUT-ra
log.SetOutput(os.Stdout)
sigs := make(chan os.Signal, 1) // Létrehozunk egy csatornát az OS jelek fogadására
stop := make(chan struct{}) // Létrehozunk egy csatornát a leállító jel fogadására
// Regisztráljuk a SIGTERM jel fogadását a sigs csatornára
signal.Notify(sigs, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)
// A Goroutine-ok önállóan hozzáadhatják magukat a WaitGroup-hoz,
// hogy várakozzunk a végrehajtásuk befejezésére
wg := &sync.WaitGroup{}
runOutsideCluster := flag.Bool("run-outside-cluster", false, "Ezt a zászlót állítsuk be, ha a klaszteren kívül futunk.")
flag.Parse()
// Létrehozzuk a clientset-et a Kubernetes klaszterrel való interakcióhoz
clientset, err := newClientSet(*runOutsideCluster)
if err != nil {
panic(err.Error())
}
controller.NewNamespaceController(clientset).Run(stop, wg)
<-sigs // Várjuk a jeleket (a jel megérkezéséig semmi más nem történik)
log.Printf("Leállás...")
close(stop) // Megmondjuk a goroutine-oknak, hogy álljanak meg
wg.Wait() // Várunk, hogy minden leálljon
}(, in )
A következőt tesszük:
- Beállítjuk a konkrét operációs rendszer jeleinek feldolgozóját, hogy megfelelő (graceful) leállítást hívjunk meg az operátor számára.
- Használjuk
WaitGroup, hogy megfelelően leállítsuk az összes goroutine-t az alkalmazás leállítása előtt. - Hozzáférést biztosítunk a klaszterhez az
clientset. - Indítás
NamespaceController, ahol az összes logikánk elhelyezkedik.
Most szükség van a logika alapjára, és esetünkben ez a fent említett 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
}(, in )
Itt beállítjuk a SharedIndexInformer, amely hatékonyan (cache használatával) várja a névtérbeli változásokat (további információ a informerekről olvasható a "» — megjegyzés). Ezután csatlakoztatjuk az EventHandler az informálóhoz, így névtér hozzáadásakor (Namespace) meghívódik a createRoleBinding.
Következő lépés — definiálni ezt a funkciót func (c *NamespaceController) createRoleBinding(obj interface{}) { namespaceObj := obj.(*v1.Namespace) namespaceName := namespaceObj.NameroleBinding := &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("A Role Binding létrehozása sikertelen: %s", err.Error())) } else { log.Println(fmt.Sprintf("AD RoleBinding létrehozva a névtérhez: %s", roleBinding.Name)) } } createRoleBinding:
A névteret úgy kapjuk meg, mint(, in )
Névteret kapunk, mint obj és átalakítjuk objektummá Namespace. Ezután meghatározzuk RoleBinding, az elején említett YAML-fájl alapján, a megadott objektum használatával Namespace és létrehozva RoleBinding. Végül naplózzuk, hogy sikeres volt-e a létrehozás.
Az utolsó funkció, amelyet meg kell határozni, a 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
}(, in )
Itt arról beszélünk WaitGroup, hogy elindítjuk a goroutine-t, majd meghívjuk namespaceInformer, amelyet előre definiáltunk. Amikor megérkezik a leállító jel, befejezi a funkciót, és jelzi WaitGroup, hogy már nem fut, és ez a funkció befejezi a működését.
Az operátor építéséről és futtatásáról a Kubernetes klaszterben az alábbiakban található .
Ezzel az operátor, amely létrehozza RoleBinding a Kubernetes klaszterben való megjelenéskor, készen áll. Namespace Megjegyzés: Az operátorok olyan segédprogramok a Kuberneteshez, amelyek célja, hogy automatizálják a klaszter objektumain végzendő rutinfeladatok végrehajtását bizonyos események hatására.
Forrás: habr.com
