Nota. traducir: Los operadores son software auxiliar para Kubernetes, diseñados para automatizar la ejecución de acciones rutinarias en objetos del clúster cuando ocurren ciertos eventos. Ya hemos escrito sobre operadores en
Decidí escribir esta publicación con un ejemplo de la vida real después de mis intentos de encontrar documentación sobre la creación de un operador para Kubernetes, que consistió en estudiar el código.
El ejemplo que se describirá es este: en nuestro clúster de Kubernetes, cada Namespace
representa el entorno sandbox de un equipo y queríamos limitar el acceso a ellos para que los equipos solo pudieran jugar en sus propios sandboxes.
Puedes lograr lo que deseas asignando a un usuario un grupo que tenga RoleBinding
a específico Namespace
и ClusterRole
con derechos de edición. La representación YAML se verá así:
---
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
(
Crea uno RoleBinding
Puedes hacerlo manualmente, pero después de cruzar la marca de los cien espacios de nombres, se convierte en una tarea tediosa. Aquí es donde los operadores de Kubernetes resultan útiles: le permiten automatizar la creación de recursos de Kubernetes en función de los cambios en los recursos. En nuestro caso queremos crear RoleBinding
mientras creaba Namespace
.
Primero que nada, definamos la función. main
que realiza la configuración requerida para ejecutar la declaración y luego llama a la acción de la declaración:
(Nota. traducir: aquí y debajo los comentarios del código están traducidos al ruso. Además, la sangría se ha corregido a espacios en lugar de pestañas [recomendadas en Go] únicamente con el fin de mejorar la legibilidad dentro del diseño de Habr. Después de cada listado hay enlaces al original en GitHub, donde se almacenan pestañas y comentarios en inglés).
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() // Ожидаем, что все остановлено
}
(
Hacemos lo siguiente:
- Configuramos un controlador para señales específicas del sistema operativo para provocar una terminación elegante del operador.
- Uso
WaitGroup
para detener con gracia todas las gorutinas antes de finalizar la aplicación. - Proporcionamos acceso al clúster creando
clientset
. - Lanzamos
NamespaceController
, en el que se ubicará toda nuestra lógica.
Ahora necesitamos una base para la lógica, y en nuestro caso esta es la mencionada. 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
}
(
Aquí configuramos SharedIndexInformer
, que efectivamente (usando un caché) esperará cambios en los espacios de nombres (lea más sobre informantes en el artículo “EventHandler
al informador, de modo que al agregar un espacio de nombres (Namespace
) la función se llama createRoleBinding
.
El siguiente paso es definir esta función. 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))
}
}
(
Obtenemos el espacio de nombres como obj
y convertirlo en un objeto Namespace
. Entonces definimos RoleBinding
, basado en el archivo YAML mencionado al principio, usando el objeto proporcionado Namespace
y creando RoleBinding
. Finalmente, registramos si la creación fue exitosa.
La última función a definir es 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
}
(
aquí estamos hablando WaitGroup
que lanzamos la gorutina y luego llamamos namespaceInformer
, que ha sido previamente definido. Cuando llegue la señal de stop finalizará la función, informar WaitGroup
, que ya no se ejecuta y esta función saldrá.
Puede encontrar información sobre cómo crear y ejecutar esta declaración en un clúster de Kubernetes en
Eso es todo para el operador que crea RoleBinding
cuando Namespace
en el cluster de Kubernetes, listo.
Fuente: habr.com