Nota. transl.: Os operadores son software auxiliar para Kubernetes, deseñados para automatizar a execución de accións rutineiras en obxectos do clúster cando ocorren certos eventos. Xa escribimos sobre os operadores en
Decidín escribir esta publicación cun exemplo da vida real despois dos meus intentos de atopar documentación sobre a creación dun operador para Kubernetes, que pasou por estudar o código.
O exemplo que se describirá é este: no noso clúster de Kubernetes, cada un Namespace
representa o entorno sandbox dun equipo e queriamos limitar o acceso a eles para que os equipos só puidesen xogar nos seus propios sandbox.
Podes conseguir o que queiras asignando a un usuario un grupo que teña RoleBinding
a específico Namespace
и ClusterRole
con dereitos de edición. A representación de YAML terá o seguinte aspecto:
---
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 un RoleBinding
Podes facelo manualmente, pero despois de cruzar a marca de cen espazos de nomes, convértese nunha tarefa tediosa. Aquí é onde os operadores de Kubernetes resultan útiles: permítenche automatizar a creación de recursos de Kubernetes en función dos cambios nos recursos. No noso caso queremos crear RoleBinding
ao crear Namespace
.
En primeiro lugar, imos definir a función main
que fai a configuración necesaria para executar a instrución e logo chama a acción da instrución:
(Nota. transl.: aquí e abaixo os comentarios do código están traducidos ao ruso. Ademais, a sangría foi corrixida a espazos en lugar das pestanas [recomendado en Ir] só co propósito de mellorar a lexibilidade no deseño Habr. Despois de cada listaxe hai ligazóns ao orixinal en GitHub, onde se almacenan comentarios e pestanas 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() // Ожидаем, что все остановлено
}
(
Facemos o seguinte:
- Configuramos un controlador para sinais específicos do sistema operativo para provocar a terminación graciosa do operador.
- Usamos
WaitGroup
para deter con gracia todas as goroutines antes de finalizar a aplicación. - Ofrecemos acceso ao clúster mediante a creación
clientset
. - Lanzamento
NamespaceController
, no que se situará toda a nosa lóxica.
Agora necesitamos unha base para a lóxica, e no noso caso esta é a 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 unha caché) agardará por cambios nos espazos de nomes (ler máis sobre informantes no artigo "EventHandler
ao informante, de xeito que ao engadir un espazo de nomes (Namespace
) chámase función createRoleBinding
.
O seguinte paso é 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))
}
}
(
Obtemos o espazo de nomes como obj
e convertelo nun obxecto Namespace
. Despois definimos RoleBinding
, baseado no ficheiro YAML mencionado ao principio, utilizando o obxecto proporcionado Namespace
e creando RoleBinding
. Finalmente, rexistramos se a creación foi exitosa.
A última función a definir é 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 a falar WaitGroup
que lancemos a goroutine e despois chamemos namespaceInformer
, que foi previamente definido. Cando chegue o sinal de parada, finalizará a función, informa WaitGroup
, que xa non se executa, e esta función sairá.
Pódese atopar información sobre a creación e execución desta declaración nun clúster de Kubernetes en
Iso é para o operador que crea RoleBinding
cando Namespace
no clúster de Kubernetes, listo.
Fonte: www.habr.com