Op. prev.: Operaterji (operators) so pomožno programsko opremo za Kubernetes, namenjeno avtomatizaciji izvajanja rutinskih nalog nad objekti gruči ob določenih dogodkih. O operaterjih smo že pisali v , kjer smo predstavili temeljne ideje in načela njihovega delovanja. Medtem ko je bilo tisto gradivo bolj pogled na uporabo pripravljenih komponent za Kubernetes, je ta prevod novega članka že vidik razvijalca/DevOps inženirja, ki se ukvarja z implementacijo novega operaterja.

Ta objava z resničnim primerom je nastala po mojih poskusih iskanja dokumentacije za ustvarjanje operaterja za Kubernetes, ki so minili skozi raziskovanje kode.
Primer, ki ga bomo opisali, je: v naši Kubernetes gruči vsak Namespace predstavlja peščeno okolje neke ekipe, in želeli smo omejiti dostop do njih, da bi ekipe lahko igrale le v svojih peščenjakih.
Želenega lahko dosežemo z dodelitvijo uporabniku skupine, ki ima RoleBinding do specifičnih Namespace in ClusterRole z pravico do urejanja. YAML predstavitev bo izgledala tako:
---
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 )
Tak operater lahko ustvarite tudi ročno, vendar postane to naporno, ko število prostorov imen preseže sto. Tu pridejo v pomoč operaterji Kubernetes — omogočajo avtomatizacijo ustvarjanja virov Kubernetes, temelječ na spremembah virov. V našem primeru želimo ustvariti RoleBinding ob ustvarjanju RoleBinding Najprej določimo funkcijo Namespace.
, ki izvede zahtevano nastavitev za zagon operaterja in nato pokliče dejanje operaterja: main: tukaj in naprej so komentarji v kodi prevedeni v slovenščino. Poleg tega so zamiki popravljeni na presledke namesto [priporočanih v Go] tabulatorjev izključno z namenom boljše berljivosti v okviru oblikovanja Habr-a. Po vsakem seznamu so navedene povezave do izvirnika na GitHubu, kjer so ohranjeni angleški komentarji in tabulatorji.)
(Op. prev.:
func main() {
// Nastavimo izhod logov na konzolni STDOUT
log.SetOutput(os.Stdout)
sigs := make(chan os.Signal, 1) // Ustvarimo kanal za prejemanje signalov OS
stop := make(chan struct{}) // Ustvarimo kanal za prejemanje stop signala
// Registriramo prejemanje SIGTERM v kanalu sigs
signal.Notify(sigs, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)
// Goroutines lahko same dodajajo sebe v WaitGroup,
// da čakamo na končanje njihovega izvajanja
wg := &sync.WaitGroup{}
runOutsideCluster := flag.Bool("run-outside-cluster", false, "Nastavite to oznako, ko delate zunaj klasterja.")
flag.Parse()
// Ustvarimo clientset za interakcijo s klasterjem Kubernetes
clientset, err := newClientSet(*runOutsideCluster)
if err != nil {
panic(err.Error())
}
controller.NewNamespaceController(clientset).Run(stop, wg)
<-sigs // Čakamo na signale (dokler ne dobimo signala se nič ne dogaja)
log.Printf("Zaprti...\n")
close(stop) // Povemo goroutines, naj se ustavijo
wg.Wait() // Čakamo, da je vse ustavljeno
}(, v )
Naredimo naslednje:
- Nastavljamo obdelovalec določenih signalov operacijskega sistema, da sproži pravilno (graceful) zapiranje delovanja operatorja.
- Uporabljamo
WaitGroup, da pravilno ustavimo vse goroutines pred zaprtjem aplikacije. - Omogočamo dostop do klasterja s kreiranjem
clientset. - Začnemo
NamespaceController, v katerem bo vsa naša logika.
Zdaj potrebujemo osnovo za logiko, in v našem primeru je to omenjeni 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 )
Tukaj nastavljamo SharedIndexInformer, ki bo učinkovito (z uporabo predpomnilnika) čakal na spremembe v imenskih prostorih (več o informers lahko preberete v članku „» — op. prev.). Po tem povežemo EventHandler z informerjem, s čimer ob dodajanju imenskega prostora (Namespace) sprožimo funkcijo createRoleBinding.
Naslednji korak — definirati to funkcijo 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("Pri ustvarjanju Role Bindinga je prišlo do napake: %s", err.Error()))
} else {
log.Println(fmt.Sprintf("Ustvarjen AD RoleBinding za Namespace: %s", roleBinding.Name))
}
}(, v )
Dostopamo do imenskega prostora kot obj in ga predmet Namespace. Nato določimo RoleBinding, na podlagi omenjenega YAML-dokumenta, z uporabo zagotovljenega predmeta Namespace in ustvarimo RoleBinding. Nazadnje, zabeležimo, ali je bilo ustvarjanje uspešno.
Zadnja funkcija, ki jo je treba določiti, je Zaženi:
// 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 )
Tukaj govorimo o tem WaitGroup, da bomo zagnali goroutine in nato poklicali namespaceInformer, ki je bil vnaprej določen. Ko prejmemo signal zaustavitve, bo funkcija končala svoje delo in obvestila WaitGroup, da se več ne izvaja, in ta funkcija bo končala svoje delo.
Informacije o sestavljanju in zagonu tega operaterja v Kubernetes klastru najdete v .
Tu operater, ki ustvarja RoleBinding ob pojavu Namespace v Kubernetes klastru, je pripravljen.
Izvor: habr.com
