Observação. trad.: Operadores são softwares auxiliares do Kubernetes, projetados para automatizar a execução de ações de rotina em objetos de cluster quando ocorrem determinados eventos. Já escrevemos sobre operadores em
Decidi escrever este post com um exemplo da vida real após minhas tentativas de encontrar documentação sobre a criação de um operador para Kubernetes, que passou pelo estudo do código.
O exemplo que será descrito é este: em nosso cluster Kubernetes, cada Namespace
representa o ambiente sandbox de uma equipe, e queríamos limitar o acesso a eles para que as equipes pudessem jogar apenas em seus próprios sandboxes.
Você pode conseguir o que deseja atribuindo a um usuário um grupo que tenha RoleBinding
para específico Namespace
и ClusterRole
com direitos de edição. A representação YAML ficará assim:
---
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
(
Crie um RoleBinding
Você pode fazer isso manualmente, mas depois de ultrapassar a marca dos cem namespaces, torna-se uma tarefa tediosa. É aqui que os operadores do Kubernetes são úteis: eles permitem automatizar a criação de recursos do Kubernetes com base em alterações nos recursos. No nosso caso, queremos criar RoleBinding
ao criar Namespace
.
Primeiro de tudo, vamos definir a função main
que faz a configuração necessária para executar a instrução e depois chama a ação da instrução:
(Observação. trad.: aqui e abaixo os comentários no código são traduzidos para o russo. Além disso, o recuo foi corrigido para espaços em vez de guias [recomendadas em Go] apenas para fins de melhor legibilidade no layout Habr. Após cada listagem, há links para o original no GitHub, onde são armazenados comentários e guias em 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() // Ожидаем, что все остановлено
}
(
Fazemos o seguinte:
- Configuramos um manipulador para sinais específicos do sistema operacional para causar o encerramento normal do operador.
- Nós usamos
WaitGroup
para interromper normalmente todas as goroutines antes de encerrar o aplicativo. - Fornecemos acesso ao cluster criando
clientset
. - Lançamos
NamespaceController
, no qual toda a nossa lógica estará localizada.
Agora precisamos de uma base para a lógica, e no nosso 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
}
(
Aqui nós configuramos SharedIndexInformer
, que irá efetivamente (usando um cache) aguardar mudanças nos namespaces (leia mais sobre informantes no artigo “EventHandler
ao informante, para que ao adicionar um namespace (Namespace
) a função é chamada createRoleBinding
.
O próximo passo é definir esta função 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 namespace como obj
e convertê-lo em um objeto Namespace
. Então definimos RoleBinding
, com base no arquivo YAML mencionado no início, usando o objeto fornecido Namespace
e criando RoleBinding
. Finalmente, registramos se a criação foi bem-sucedida.
A última função a ser definida é 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
}
(
Aqui estamos conversando WaitGroup
que lançamos a goroutine e depois chamamos namespaceInformer
, que foi previamente definido. Ao chegar o sinal de parada encerrará a função, informe WaitGroup
, que não é mais executado, e esta função será encerrada.
Informações sobre como construir e executar esta instrução em um cluster Kubernetes podem ser encontradas em
É isso para o operador que cria RoleBinding
após a aparência Namespace
no cluster Kubernetes, pronto.
Fonte: habr.com