Operators e CRDs
Um operator é o runbook do seu time transformado em controlador: em vez de alguém seguir oito passos para promover uma réplica de banco, um processo observa o estado desejado e executa a reconciliação. É a mesma ideia de Arquitetura aplicada ao seu domínio — e por isso herda tanto a robustez quanto os riscos dela.
CustomResourceDefinition: o vocabulário
Seção intitulada “CustomResourceDefinition: o vocabulário”O CRD registra um tipo novo na API. A partir dele, kubectl get, RBAC, admission e
auditoria funcionam para o seu recurso como funcionam para um Deployment.
apiVersion: apiextensions.k8s.io/v1kind: CustomResourceDefinitionmetadata: { name: bancos.dados.exemplo.com }spec: group: dados.exemplo.com scope: Namespaced names: { kind: Banco, plural: bancos, singular: banco, shortNames: [bd] } versions: - name: v1alpha1 served: true storage: true subresources: { status: {} } # separa o que a pessoa pede do que o controller relata schema: openAPIV3Schema: type: object properties: spec: type: object required: [versao, tamanhoGb] properties: versao: { type: string, enum: ["15", "16"] } tamanhoGb: { type: integer, minimum: 10, maximum: 2000 }O schema é a sua primeira linha de validação: campo obrigatório, enum e limite recusam
manifest errado no apply, não às três da manhã. Use subresources.status desde o
início — sem isso, o controller e a pessoa gravam no mesmo lugar e sobrescrevem um ao outro.
kubectl apply -f crd.yamlkubectl explain banco.spec # a documentação vem do schemakubectl get bancos -AO laço de reconciliação
Seção intitulada “O laço de reconciliação”O controller observa eventos do recurso, compara desejo com realidade e age. Três regras não negociáveis:
- Idempotência.
Reconcileroda de novo sem motivo aparente o tempo todo; executar duas vezes precisa dar o mesmo resultado. - Sem estado na memória. O único estado confiável é a API. Reinicie o processo e ele deve reconstruir tudo a partir do cluster.
- Status honesto. Publique
conditions(Ready,Degraded) com motivo legível; é isso que alguém vai ler durante o incidente.
func (r *BancoReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { var banco dadosv1.Banco if err := r.Get(ctx, req.NamespacedName, &banco); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) // apagado: nada a fazer } if err := r.garantirStatefulSet(ctx, &banco); err != nil { return ctrl.Result{RequeueAfter: 30 * time.Second}, err // erro: tenta de novo } return ctrl.Result{}, r.atualizarStatus(ctx, &banco)}Erro retornado provoca nova tentativa com backoff exponencial — não invente laço de retry
próprio. Para limpeza que precisa acontecer antes da remoção (apagar um bucket, liberar
uma licença), use finalizers; e teste o caminho de remoção, porque finalizer com bug
deixa o recurso preso em Terminating para sempre.
Permissão é o risco real
Seção intitulada “Permissão é o risco real”Um operator roda com uma ServiceAccount que costuma poder muito. Um controller com
cluster-admin é uma escalada de privilégio esperando acontecer: quem cria o recurso
customizado passa a mandar, indiretamente, em tudo que o operator sabe fazer.
kubectl get clusterrole -o wide | grep -i operatorkubectl auth can-i --list --as=system:serviceaccount:sistema:banco-operatorRestrinja verbos e recursos ao necessário, prefira escopo de namespace quando der, e trate
o CRD como superfície de ataque: quem tem create em bancos pede 2000 GB se você não
limitou o schema.
Quando não escrever um operator
Seção intitulada “Quando não escrever um operator”Na maioria dos casos você não deve. Se o problema é parametrizar manifests, use Helm ou Kustomize. Se é aplicar o Git no cluster, use GitOps. Se é uma tarefa periódica, um CronJob resolve. Operator próprio se justifica quando existe lógica operacional com estado — failover, backup, upgrade coordenado — e o time consegue mantê-lo por anos. Antes disso, procure um operator maduro do ecossistema e leia o RBAC dele antes de instalar.
Próximo passo: para tráfego, mTLS e canário sem mudar a aplicação, veja Service mesh.