Pular para o conteúdo

Operators e CRDs

Avançado18 min de leiturakubernetes

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.

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/v1
kind: CustomResourceDefinition
metadata: { 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.

Janela do terminal
kubectl apply -f crd.yaml
kubectl explain banco.spec # a documentação vem do schema
kubectl get bancos -A

O controller observa eventos do recurso, compara desejo com realidade e age. Três regras não negociáveis:

  • Idempotência. Reconcile roda 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.

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.

Janela do terminal
kubectl get clusterrole -o wide | grep -i operator
kubectl auth can-i --list --as=system:serviceaccount:sistema:banco-operator

Restrinja 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.

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.