Notera. transl.: Operatörer är hjälpprogram för Kubernetes, utformad för att automatisera utförandet av rutinåtgärder på klusterobjekt när vissa händelser inträffar. Vi har redan skrivit om operatörer i
Jag bestämde mig för att skriva det här inlägget med ett exempel från verkligheten efter mina försök att hitta dokumentation om att skapa en operatör för Kubernetes, som gick igenom koden.
Exemplet som kommer att beskrivas är detta: i vårt Kubernetes-kluster, var och en Namespace
representerar ett lags sandlådemiljö, och vi ville begränsa tillgången till dem så att lag bara kunde spela i sina egna sandlådor.
Du kan uppnå vad du vill genom att tilldela en användare en grupp som har RoleBinding
till specifikt Namespace
и 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
(
Skapa en RoleBinding
Du kan göra det manuellt, men efter att ha passerat hundra namnutrymmen blir det en tråkig uppgift. Det är här Kubernetes-operatörerna kommer till nytta – de låter dig automatisera skapandet av Kubernetes-resurser baserat på ändringar av resurser. I vårt fall vill vi skapa RoleBinding
medan du skapar Namespace
.
Först av allt, låt oss definiera funktionen main
som gör den nödvändiga inställningen för att köra satsen och sedan anropar satsåtgärden:
(Notera. transl.: här och nedan är kommentarerna i koden översatta till ryska. Dessutom har indraget korrigerats till mellanslag istället för [rekommenderas i Go]-flikar enbart i syfte att förbättra läsbarheten inom Habr-layouten. Efter varje listning finns det länkar till originalet på GitHub, där engelskspråkiga kommentarer och flikar lagras.)
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() // Ожидаем, что все остановлено
}
Vi gör följande:
- Vi konfigurerar en hanterare för specifika operativsystemsignaler för att orsaka graciös uppsägning av operatören.
- Vi använder
WaitGroup
att graciöst stoppa alla goroutiner innan du avslutar applikationen. - Vi ger tillgång till klustret genom att skapa
clientset
. - Vi lanserar
NamespaceController
, där all vår logik kommer att finnas.
Nu behöver vi en grund för logik, och i vårt fall är det den som nämns 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
}
Här konfigurerar vi SharedIndexInformer
, som effektivt (med hjälp av en cache) väntar på ändringar i namnutrymmen (läs mer om informatörer i artikeln "EventHandler
till informatören, så att när du lägger till ett namnområde (Namespace
) funktionen anropas 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("Failed to create Role Binding: %s", err.Error()))
} else {
log.Println(fmt.Sprintf("Created AD RoleBinding for Namespace: %s", roleBinding.Name))
}
}
Vi får namnutrymmet som obj
och konvertera det till ett objekt Namespace
. Sedan definierar vi RoleBinding
, baserat på YAML-filen som nämndes i början, med hjälp av det angivna objektet Namespace
och skapande RoleBinding
. Slutligen loggar vi om skapandet lyckades.
Den sista funktionen som ska 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
}
Här pratar vi WaitGroup
att vi startar goroutinen och sedan ringer namespaceInformer
, som tidigare har definierats. När stoppsignalen kommer kommer den att avsluta funktionen, informera WaitGroup
, som inte längre körs, och den här funktionen avslutas.
Information om att bygga och köra detta uttalande på ett Kubernetes-kluster finns i
Det är det för operatören som skapar RoleBinding
när Namespace
i Kubernetes-klustret, redo.
Källa: will.com