Please enable JavaScript.
Coggle requires JavaScript to display documents.
Operator Development (Part. 2) - Coggle Diagram
Operator Development (Part. 2)
Webhooks
Os webhooks são um tipo de resource que permite especificar o comportamento personalizado em torno das requisições recebidas.
Tipos
Admission Webhook
Mutating Webhooks
: interceptam requisições de entrada com base em determinadas condições que você especifica e altera a requisição de alguma forma
Validating Webhooks
: funciona de forma semelhante, mas aceita ou rejeita a requisição em vez de alterá-la.
Authorization Webhook
O apiserver por padrão não se autentica nos webhooks. No entanto, se você quiser autenticar os clientes, poderá configurar o apiserver para usar autenticação básica, token de portador ou um certificado para se autenticar nos webhooks. Não existe um implementação externa de um authorization webhook no Kubernetes.
CRD Conversion Webhook
São um tipo de
mutating webhook
associado às Custom Resource Definitions
Eles permitem que diferentes versões de API de um recurso sejam convertidas entre si.
Intercepta requisições de entrada para criar versões v1alpha1 do seu recurso e as converte automaticamente para criar versões v1beta1
Prática
Se você deseja implementar admission webhook para seu CRD, a única coisa que você precisa fazer é implementar a interface
Defaulter
(Adminission) e/ou
Validator
(Validating). O Kubebuilder cuida do resto para você, como
Criando o servidor webhook.
Garantindo que o servidor foi adicionado no manager.
Criando handlers para seus webhooks.
Registrando cada handler com um caminho em seu servidor.
Admission Webhook
$ operator-sdk create webhook --group cache --version v1alpha1 --kind Memcached --defaulting --programmatic-validation
Após criar o webhook ele monta
api/<version>/<kind>_webhook.go
. Além de toda estrutura com o certmanager
Perceba que
--defaulting
é devido a criação de um webhook mutating (método Default), enquanto o
--pragmatic-validation
é pq irá criar um webhook validating também
A ideia de usar Default() como método de admission é pq ele irá modificar o spec e seu uso padrão é dar um valor "default" a specs que não são definidas no CR.
No caso da validation, teremos uma interface chamada
webhook.Validator
e 3 métodos. Espera-se que os métodos
ValidateCreate()
,
ValidateUpdate()
e
ValidateDelete()
validem seu receptor na criação, atualização e exclusão, respectivamente.
Se você quiser executar os webhooks localmente, terá que gerar certificados para servir os webhooks e colocá-los no diretório correto (/tmp/k8s-webhook-server/serving-certs/tls.{crt,key}, por padrão
Se você não estiver executando um API Server local, também precisará descobrir como fazer proxy do tráfego do cluster remoto para o servidor de webhook local. Por esse motivo, geralmente recomendamos desabilitar os webhooks ao fazer seu ciclo de teste de execução de código local.
make run ENABLE_WEBHOOKS=false
Conversion Webhook
$ operator-sdk create webhook --group cache --version v1beta1 --kind Memcached --conversion
A lógica do
--validation
é a mesma que vimos anteriormente. Mas não confundir com
--pragmatic-validation
Esse interface implementa o modelo
hub-and-spokes
, onde define uma versão de API como hub e todas as outras versões devem implmentar métodos que convertem para a versão do hub. Então na prática temos um método hub() da versão atual e spoke() que irá implementar a mudança.
Como já vimos isso não é nada mais que um mutanting webhook, que inclusive irá definir o default de diferentes versões. A diferença é conceitual com hub-and-spokes. Qualquer versão diferente da atual irá implementar o mesmo método e ainda podemos trabalhar no hub modificando ele também.
Scope
Um operador com escopo de namespace observa e gerencia recursos em um único Namespace, enquanto um operador com escopo de cluster observa e gerencia recursos em todo o cluster.
Um operador deve ter escopo de cluster se observar recursos que podem ser criados em qualquer Namespace. Um operador deve ter escopo de namespace se for destinado a ser implantado de forma flexível.
Escopo de namespace permite atualizações desacopladas, isolamento de namespace para falhas e monitoramento e diferentes definições de API.
Um Manager é inicializado sem nenhuma opção de namespace especificada, será do escopo cluster, esse é o default. Isso ocorre na definição de
ctrl.NewManager
. Também é possível ser multi namespaced, adicionando uma lista. Lembre que se o CR for criado em outro namespace ele não será gerenciado.
CRD Scope
Custom Resource Definitions (CRDs) contêm um campo de escopo que determina se o Custom Resource (CR) resultante tem escopo de cluster ou namespace.
Um autor de operador pode usar um CRD com escopo de namespace para restringir o acesso a um CR a determinados namespaces ou ter diferentes versões de CRs acessíveis em namespaces diferentes. Como alternativa, um autor de operador pode desejar um CRD com escopo de cluster para que todos os namespaces tenham visibilidade e acesso a CRs.
$ operator-sdk create api --group cache --version v1alpha1 --kind Memcached --resource=true --controller=true --namespaced=false
--> CRD com escopo de cluster
Manage CR status (Conditions)
Um pattern frequentemente usado é incluir Conditions no status de recursos personalizados. Uma Condition representa as últimas observações disponíveis do estado de um objeto.
Conditions são um mecanismo de extensão que permite que ferramentas e outros controllers coletem informações resumidas sobre recursos sem a necessidade de entender detalhes de status específicos do recurso.
As Conditions devem complementar informações mais detalhadas sobre o status observado de um objeto escrito por um controller, em vez de substituí-lo. Por exemplo, a condition "Available" de um deployment pode ser determinado examinando readyReplicas, replicas e outras propriedades do deployment. No entanto, a condition "Available" permite que outros componentes evitem duplicar a lógica de disponibilidade do controller de deployment.
Cada condition contém um Status e um Type. Também pode conter uma Reason e uma Message.
Phase vs Conditions
A phase de nível superior é um estado agregado que responde a algumas perguntas do usuário, como meu pod está em um estado terminal? Mas tem lacunas, pois o estado real está contido nas conditions. O array de conditions é um conjunto de tipos (Ready, PodScheduled…) com um status (True, False ou Unknown) que compõem o ‘estado computado’ de um Pod a qualquer momento. Como veremos mais adiante, o estado é quase sempre partial (conditions abertas).
Conditions estão entrando em desuso, sendo cada vez mais incorporado objetos no status e reduzindo o uso de conditions.
Handle Cleanup on Deletion (Finalizer)
Os operadores podem criar objetos como parte de seu dever operacional. O acúmulo de objetos pode consumir recursos desnecessários, tornar a API mais lenta e sobrecarregar a interface do usuário. Como tal, é importante que os operadores mantenham uma boa higiene e limpem os recursos quando não forem necessários.
Internal Resources
Um exemplo típico de limpeza correta de recursos é a implementação de Jobs. Quando um job é criado, um ou vários pods são criados como recursos filho. Quando um job é excluído, os pods associados também são excluídos.
Este é um padrão muito comum facilmente alcançado definindo uma referência de proprietário do objeto pai (Job) para o objeto filho (Pod).
ctrl.SetControllerReference(job, pod, r.Scheme)
Observe que o comportamento padrão para a exclusão em cascata é a propagação em segundo plano, o que significa que as solicitações de exclusão para objetos filho ocorrem após a solicitação para excluir o objeto pai.
External Resources
Às vezes, recursos externos ou recursos que não pertencem a um recurso personalizado fazem parte de um recurso e a deleção não é imediata.
Uma solicitação de exclusão de um objeto com um Finalizer se torna uma atualização durante a qual um carimbo de data e hora de exclusão é definido; o objeto não é excluído enquanto o Finalizer estiver presente.
O loop de reconciliação do controller do recurso personalizado precisará verificar se o carimbo de data e hora de exclusão está definido, executar a(s) operação(ões) de limpeza externa e, em seguida, remover o Finalizer para permitir o garbage collector do objeto.
Finalizers são condições que devem ser atendidas antes que um recurso possa ser excluído. Quando uma exclusão é solicitada em um recurso finalizado, o recurso é bloqueado para alterações até que as condições sejam atendidas
Resumindo, ao adicionar
metadata.finalizers
o recurso não será destruido nem atualizado. Ele receberá campos como deletionGracePeriodSeconds e deletionTimestamp e ficará parado até que o finalizer seja retirado.
Lifecycle Finalizer
Adiciona campo metadata.finalizer ao recurso
Executa o comando de deleção. Nesse momento a API Server observa o campo finalizer e inicia modificações no objeto.
Modifica o objeto para adicionar um campo metadata.deletionTimestamp com a hora em que você iniciou a exclusão.
Impede que o objeto seja removido até que seu campo metadata.finalizers esteja vazio.
O controller que gerencia esse finalizer percebe a atualização do objeto definindo o metadata.deletionTimestamp, indicando que a exclusão do objeto foi solicitada.
O controller então tenta satisfazer os requisitos dos finalizers especificados para esse recurso. Cada vez que uma condição do finalizer é satisfeita, o controller remove essa chave do campo de finalizers do recurso.
Quando o campo de finalizers é esvaziado, um objeto com um conjunto de campos deletionTimestamp é excluído automaticamente. Você também pode usar finalizers para evitar a exclusão de recursos não gerenciados.