Operator SDK: criando operadores em Go ou Ansible
1. Fundamentos de Operadores no Kubernetes
Operadores são extensões do Kubernetes que automatizam tarefas complexas de gerenciamento de aplicações. Eles implementam o padrão de reconciliação (Controller Pattern), onde um loop contínuo observa o estado atual do cluster e o compara com o estado desejado definido em um Recurso Customizado (CR).
A diferença fundamental entre operadores, controllers e CRDs pode ser resumida assim:
- CRD (Custom Resource Definition): define um novo tipo de recurso no Kubernetes (ex: MyApp, Database)
- Controller: implementa a lógica de reconciliação para um recurso específico
- Operador: é a combinação de um CRD + Controller + lógica de negócio completa
Casos de uso comuns incluem: gerenciamento de StatefulSets complexos (como bancos de dados), backups automatizados, upgrades com zero downtime e instalação/configuração de software empresarial.
2. Introdução ao Operator SDK
O Operator SDK é um framework que facilita a criação de operadores Kubernetes. Atualmente na versão v1 (substituindo a v0.x), ele oferece scaffolds para Go, Ansible e Helm.
A estrutura de um projeto criado com o SDK inclui:
meu-operator/
├── api/ # definições da CRD
├── controllers/ # lógica de reconciliação
├── config/ # manifests Kubernetes (RBAC, CRDs, deploy)
├── Dockerfile
├── Makefile
└── main.go
O fluxo de desenvolvimento segue: scaffold inicial → implementação da lógica → build da imagem → deploy no cluster.
3. Criando um Operador com Go (Golang)
Para criar um operador Go, execute:
operator-sdk init --domain=example.com --repo=github.com/user/meu-operator
operator-sdk create api --group=app --version=v1 --kind=MyApp --resource --controller
A lógica principal fica na função Reconcile:
func (r *MyAppReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := log.FromContext(ctx)
// Buscar o CR
var myApp appv1.MyApp
if err := r.Get(ctx, req.NamespacedName, &myApp); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// Definir estado desejado
desiredReplicas := myApp.Spec.Replicas
// Criar ou atualizar Deployment filho
deploy := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: myApp.Name + "-deploy",
Namespace: myApp.Namespace,
},
Spec: appsv1.DeploymentSpec{
Replicas: &desiredReplicas,
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "app",
Image: myApp.Spec.Image,
},
},
},
},
},
}
// Aplicar controle de ownership
if err := controllerutil.SetControllerReference(&myApp, deploy, r.Scheme); err != nil {
return ctrl.Result{}, err
}
// Criar ou atualizar
_, err := controllerutil.CreateOrUpdate(ctx, r.Client, deploy, func() error {
deploy.Spec.Replicas = &desiredReplicas
return nil
})
// Atualizar status do CR
myApp.Status.ReadyReplicas = deploy.Status.ReadyReplicas
r.Status().Update(ctx, &myApp)
return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
}
Este exemplo cria um operador que gerencia uma aplicação web escalável, mantendo um Deployment sincronizado com o CR.
4. Criando um Operador com Ansible
Para operadores Ansible, o scaffold é:
operator-sdk init --domain=example.com --plugins=ansible
operator-sdk create api --group=db --version=v1 --kind=Database --generate-playbook
O arquivo watches.yaml mapeia CRDs para roles Ansible:
---
- version: v1
group: db.example.com
kind: Database
role: /opt/ansible/roles/database
A estrutura de roles Ansible segue o padrão:
roles/database/
├── defaults/main.yml # valores padrão
├── tasks/main.yml # tarefas principais
├── templates/ # templates Jinja2
└── vars/main.yml # variáveis
Exemplo de tarefa para configurar MySQL:
---
- name: Criar diretório de dados
file:
path: "/data/{{ meta.name }}"
state: directory
- name: Deploy do StatefulSet MySQL
k8s:
state: present
definition:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: "{{ meta.name }}-mysql"
namespace: "{{ meta.namespace }}"
spec:
replicas: "{{ spec.replicas | default(1) }}"
selector:
matchLabels:
app: mysql
template:
spec:
containers:
- name: mysql
image: "{{ spec.image | default('mysql:8.0') }}"
env:
- name: MYSQL_ROOT_PASSWORD
value: "{{ spec.rootPassword }}"
Vantagens do Ansible: menor curva de aprendizado, reuso de playbooks existentes. Limitações: performance inferior ao Go, menor controle sobre reconciliação.
5. Testes, Validação e Debug de Operadores
Testes unitários com envtest:
func TestMyAppController(t *testing.T) {
// Configurar ambiente de teste
cfg, ctx := setupTestEnv(t)
defer ctx.Cancel()
k8sClient, err := client.New(cfg, client.Options{})
require.NoError(t, err)
// Criar CR de teste
myApp := &appv1.MyApp{
ObjectMeta: metav1.ObjectMeta{
Name: "test-app",
Namespace: "default",
},
Spec: appv1.MyAppSpec{
Replicas: 3,
Image: "nginx:latest",
},
}
err = k8sClient.Create(ctx, myApp)
require.NoError(t, err)
// Verificar se o Deployment foi criado
deploy := &appsv1.Deployment{}
err = k8sClient.Get(ctx, types.NamespacedName{
Name: "test-app-deploy", Namespace: "default",
}, deploy)
require.NoError(t, err)
assert.Equal(t, int32(3), *deploy.Spec.Replicas)
}
Para debug local, use make run e monitore os logs:
kubectl logs deployment/meu-operator-controller-manager -n meu-operator-system
operator-sdk scorecard --namespace meu-operator-system
6. Build, Deploy e Gerenciamento do Ciclo de Vida
Dockerfile multi-estágio para operador Go:
# Build stage
FROM golang:1.21 AS builder
WORKDIR /workspace
COPY go.mod go.mod
COPY go.sum go.sum
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o manager main.go
# Runtime stage
FROM gcr.io/distroless/static:nonroot
WORKDIR /
COPY --from=builder /workspace/manager .
USER 65532:65532
ENTRYPOINT ["/manager"]
Deploy via OLM (Operator Lifecycle Manager):
operator-sdk generate kustomize manifests
make bundle
operator-sdk bundle validate ./bundle
operator-sdk run bundle quay.io/user/meu-operator:v0.1.0
Para monitoramento, adicione métricas Prometheus:
import (
"sigs.k8s.io/controller-runtime/pkg/metrics"
"github.com/prometheus/client_golang/prometheus"
)
var (
appReplicas = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "myapp_replicas_total",
Help: "Total replicas managed by operator",
},
[]string{"name", "namespace"},
)
)
func init() {
metrics.Registry.MustRegister(appReplicas)
}
7. Boas Práticas e Padrões Avançados
Tratamento de erros com retry e backoff:
func (r *MyAppReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
if err := r.syncDeployment(ctx, req); err != nil {
// Retry com backoff exponencial
return ctrl.Result{RequeueAfter: time.Duration(rand.Intn(10)) * time.Second}, nil
}
return ctrl.Result{}, nil
}
Finalizers para limpeza:
const myAppFinalizer = "finalizer.example.com"
func (r *MyAppReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
myApp := &appv1.MyApp{}
r.Get(ctx, req.NamespacedName, myApp)
if !myApp.DeletionTimestamp.IsZero() {
if controllerutil.ContainsFinalizer(myApp, myAppFinalizer) {
// Limpar recursos externos
controllerutil.RemoveFinalizer(myApp, myAppFinalizer)
r.Update(ctx, myApp)
}
return ctrl.Result{}, nil
}
controllerutil.AddFinalizer(myApp, myAppFinalizer)
r.Update(ctx, myApp)
// ... lógica principal
}
RBAC mínimo:
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: meu-operator-role
rules:
- apiGroups: ["app.example.com"]
resources: ["myapps", "myapps/status", "myapps/finalizers"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
8. Conclusão e Próximos Passos
O fluxo completo de criação de um operador envolve: definir um CRD → implementar o controller com lógica de reconciliação → construir e deployar o operador no cluster. O Operator SDK abstrai grande parte da complexidade, permitindo focar na lógica de negócio.
Para aprofundamento, explore integrações com Knative (serverless), Kubeflow (ML pipelines) e sistemas de batch processing. A comunidade Kubernetes oferece vasto material para evoluir seus operadores.
Referências
- Operator SDK Documentation — Documentação oficial completa sobre instalação, scaffolds e deploy de operadores
- Kubebuilder Book — Guia detalhado sobre construção de operadores Go com controller-runtime
- Ansible Operator Tutorial — Tutorial oficial para criação de operadores Ansible passo a passo
- Operator Pattern no Kubernetes Docs — Conceitos fundamentais sobre o padrão de operadores no Kubernetes
- Awesome Operators — Lista curada de operadores de exemplo e ferramentas da comunidade