Obs. övers.: Operatörer (operators) är hjälpprogram för Kubernetes som syftar till att automatisera utförandet av rutinuppgifter på klusterobjekt vid vissa händelser. Vi har redan skrivit om operatörer i , där vi berättade om grundläggande idéer och principer för deras funktion. Men om det materialet var mer en synvinkel från sidan av driften av färdiga komponenter för Kubernetes, så är den här översättningen av en ny artikel en utvecklares/DevOps-ingenjörs perspektiv, som funderar på att implementera en ny operatör.

Detta inlägg med ett verkligt exempel beslutade jag att skriva efter mina försök att hitta dokumentation för att skapa en operatör för Kubernetes, vilket ledde till att jag studerade koden.
Exemplet som ska beskrivs är så här: i vårt Kubernetes-kluster representerar varje Namespace en sandlådemiljö för ett team, och vi ville begränsa åtkomsten till dem så att teamen bara kan leka i sina egna sandlådor.
Det önskade kan uppnås genom att tilldela en användare en grupp som har RoleBinding till specifika Namespace och ClusterRole med redigeringsrättigheter. YAML-representationen kommer att se ut så här:
---
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(, i )
En sådan kan skapas manuellt, men efter att ha passerat hundra namnområden blir det en tröttsam uppgift. Här hjälper Kubernetes-operatörerna — de gör det möjligt att automatisera skapandet av Kubernetes-resurser baserat på förändringar i resurser. I vårt fall vill vi skapa RoleBinding vid skapandet av RoleBinding Först definierar vi funktionen Namespace.
, som utför den nödvändiga inställningen för att starta operatören och sedan anropar operatörens åtgärd: main: här och framöver har kommentarer i koden översatts till svenska. Dessutom har indragningar rättats till mellanslag istället för [de rekommenderade i Go] tabbar, enbart för bättre läsbarhet inom ramen för Hubbs design. Efter varje listning finns länkar till originalet på GitHub, där de engelska kommentarerna och tabbarna är bevarade.)
(Obs. övers.: здесь и далее комментарии в коде переведены на русский язык. Кроме того, отступы исправлены на пробелы вместо [рекомендуемых в Go] табов исключительно с целью лучшей читаемости в рамках вёрстки Хабра. После каждого листинга приведены ссылки на оригинал на GitHub, где сохранены англоязычные комментарии и табы.)
func main() {
// Ställer in loggutdata till konsolens STDOUT
log.SetOutput(os.Stdout)
sigs := make(chan os.Signal, 1) // Skapar en kanal för att ta emot OS-signaler
stop := make(chan struct{}) // Skapar en kanal för att ta emot stopp-signal
// Registrera mottagande av SIGTERM i kanalen sigs
signal.Notify(sigs, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)
// Goroutines kan själva lägga till sig i WaitGroup,
// så att deras avslutning väntas
wg := &sync.WaitGroup{}
runOutsideCluster := flag.Bool("run-outside-cluster", false, "Sätt denna flagga vid körning utanför klustret.")
flag.Parse()
// Skapar clientset för interaktion med Kubernetes-klustret
clientset, err := newClientSet(*runOutsideCluster)
if err != nil {
panic(err.Error())
}
controller.NewNamespaceController(clientset).Run(stop, wg)
<-sigs // Väntar på signaler (ingenting händer förrän signal mottas)
log.Printf("Stänger ner...")
close(stop) // Ber goroutines att stoppa
wg.Wait() // Väntar på att allt ska stoppas
}(, i )
Vi gör följande:
- Vi konfigurerar hanterare för specifika operativsystemsignaler för att korrekt initiera (graceful) nedstängning av operatören.
- Används
WaitGroup, för att korrekt stoppa alla goroutines innan programmet avslutas. - Vi ger åtkomst till klustret genom att skapa
clientset. - Starta
NamespaceController, där hela vår logik kommer att placeras.
Nu behöver vi ett fundament för logiken, och i vårt fall är det den nämnda 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
}(, i )
Här ställer vi in SharedIndexInformer, som effektivt (med hjälp av cache) kommer att vänta på förändringar i namnrymder (läs mer om informers i artikeln "» — anm. övers.). Därefter kopplar vi EventHandler till informern, vilket gör att när en namnrymd läggs till (Namespace), anropas funktionen createRoleBinding.
Nästa steg är att definiera denna funktion 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("Misslyckades med att skapa Role Binding: %s", err.Error()))
} else {
log.Println(fmt.Sprintf("Skapade AD RoleBinding för namnrymd: %s", roleBinding.Name))
}
}(, i )
Vi får namnrymden som obj och omvandlar den till ett objekt Namespace. Därefter definierar vi RoleBinding, baserat på det som nämndes i den inledande YAML-filen, med hjälp av det tillhandahållna objektet Namespace och skapar RoleBinding. Slutligen loggar vi huruvida skapandet lyckades.
Den sista funktionen som behöver definieras är 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
}(, i )
Här talar vi om WaitGroup, att vi kommer att köra en goroutine och sedan anropa namespaceInformer, som har fördefinierats. När stopp-signalen kommer in, kommer den att avsluta funktionen, meddelar WaitGroup, att den inte längre körs, och denna funktion avslutar sitt arbete.
Information om att bygga och köra denna operator i Kubernetes-klustret finns i .
Här är operatören som skapar RoleBinding vid närvaron av Namespace i Kubernetes-klustret, redo.
Källa: habr.com
