Opmerking. vert.: Operators zijn hulpsoftware voor Kubernetes, ontworpen om de uitvoering van routinematige acties op clusterobjecten te automatiseren wanneer bepaalde gebeurtenissen plaatsvinden. We hebben al over operators geschreven in
Ik besloot dit bericht te schrijven met een voorbeeld uit de praktijk na mijn pogingen om documentatie te vinden over het maken van een operator voor Kubernetes, waarbij ik de code had bestudeerd.
Het voorbeeld dat zal worden beschreven is dit: in ons Kubernetes-cluster elk Namespace
vertegenwoordigt de sandbox-omgeving van een team en we wilden de toegang daartoe beperken, zodat teams alleen in hun eigen sandbox konden spelen.
U kunt bereiken wat u wilt door een gebruiker een groep toe te wijzen die dat wel heeft RoleBinding
tot specifiek Namespace
и ClusterRole
met bewerkingsrechten. De YAML-weergave ziet er als volgt uit:
---
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
(
Creëer er een RoleBinding
Je kunt het handmatig doen, maar als je de grens van honderd naamruimten overschrijdt, wordt het een vervelende klus. Dit is waar Kubernetes-operators van pas komen: ze stellen u in staat de creatie van Kubernetes-resources te automatiseren op basis van wijzigingen in resources. In ons geval willen we creëren RoleBinding
tijdens het creëren Namespace
.
Laten we eerst de functie definiëren main
die de vereiste instellingen uitvoert om de instructie uit te voeren en vervolgens de instructieactie aanroept:
(Opmerking. vert.: hier en hieronder zijn de opmerkingen in de code in het Russisch vertaald. Bovendien is de inspringing gecorrigeerd naar spaties in plaats van [aanbevolen in Go]-tabbladen, uitsluitend met het oog op een betere leesbaarheid binnen de Habr-lay-out. Na elke vermelding staan links naar het origineel op GitHub, waar Engelstalige opmerkingen en tabbladen worden opgeslagen.)
func main() {
// Устанавливаем вывод логов в консольный STDOUT
log.SetOutput(os.Stdout)
sigs := make(chan os.Signal, 1) // Создаем канал для получения сигналов ОС
stop := make(chan struct{}) // Создаем канал для получения стоп-сигнала
// Регистрируем получение SIGTERM в канале sigs
signal.Notify(sigs, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)
// Goroutines могут сами добавлять себя в WaitGroup,
// чтобы завершения их выполнения дожидались
wg := &sync.WaitGroup{}
runOutsideCluster := flag.Bool("run-outside-cluster", false, "Set this flag when running outside of the cluster.")
flag.Parse()
// Создаем clientset для взаимодействия с кластером Kubernetes
clientset, err := newClientSet(*runOutsideCluster)
if err != nil {
panic(err.Error())
}
controller.NewNamespaceController(clientset).Run(stop, wg)
<-sigs // Ждем сигналов (до получения сигнала более ничего не происходит)
log.Printf("Shutting down...")
close(stop) // Говорим goroutines остановиться
wg.Wait() // Ожидаем, что все остановлено
}
Wij doen het volgende:
- We configureren een handler voor specifieke besturingssysteemsignalen om een correcte beëindiging van de operator te veroorzaken.
- We gebruiken
WaitGroup
om alle goroutines netjes te stoppen voordat u de applicatie beëindigt. - Wij bieden toegang tot het cluster door het creëren van
clientset
. - Lancering
NamespaceController
, waarin al onze logica zich zal bevinden.
Nu hebben we een basis voor logica nodig, en in ons geval is dit degene die genoemd wordt 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
}
(
Hier configureren we SharedIndexInformer
, die effectief (met behulp van een cache) zal wachten op wijzigingen in naamruimten (lees meer over informanten in het artikel “EventHandler
aan de informant, zodat bij het toevoegen van een naamruimte (Namespace
) functie wordt aangeroepen createRoleBinding
.
De volgende stap is het definiëren van deze functie 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("Failed to create Role Binding: %s", err.Error()))
} else {
log.Println(fmt.Sprintf("Created AD RoleBinding for Namespace: %s", roleBinding.Name))
}
}
(
We krijgen de naamruimte als obj
en converteer het naar een object Namespace
. Dan definiëren wij RoleBinding
, gebaseerd op het in het begin genoemde YAML-bestand, met behulp van het opgegeven object Namespace
en creëren RoleBinding
. Ten slotte loggen we of de creatie succesvol is verlopen.
De laatste functie die moet worden gedefinieerd is 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
}
(
Hier zijn we aan het praten WaitGroup
dat we de goroutine lanceren en vervolgens bellen namespaceInformer
, die eerder is gedefinieerd. Wanneer het stopsignaal arriveert, wordt de functie beëindigd en wordt geïnformeerd WaitGroup
, dat niet langer wordt uitgevoerd, en deze functie wordt afgesloten.
Informatie over het bouwen en uitvoeren van deze verklaring op een Kubernetes-cluster vindt u in
Dat is het voor de operator die creëert RoleBinding
wanneer Namespace
in het Kubernetes-cluster, klaar.
Bron: www.habr.com