<h2>O que são Custom Resource Definitions (CRDs)?</h2>
<p>Custom Resource Definitions são uma forma poderosa de estender a API do Kubernetes, permitindo que você defina novos tipos de recursos personalizados além dos objetos nativos (Pods, Deployments, Services, etc.). Quando você cria uma CRD, está essencialmente ensinando ao Kubernetes a reconhecer e gerenciar um novo tipo de recurso como se fosse um objeto de primeira classe. Isso significa que você pode usar <code>kubectl</code> para criar, listar, atualizar e deletar instâncias do seu recurso personalizado, exatamente como faria com qualquer outro objeto Kubernetes.</p>
<p>A importância das CRDs vai além de uma simples extensão sintática. Elas formam a base para a criação de operadores Kubernetes, ferramentas que automatizam operações complexas em aplicações. Pense em um CRD como um esquema que define a estrutura de dados que sua aplicação precisa armazenar e gerenciar. O Kubernetes fornece a infraestrutura (armazenamento em etcd, API REST, validação, etc.), e você fornece a lógica de negócio através de um controlador que "observa" essas mudanças e as executa.</p>
<h2>Arquitetura e Funcionamento das CRDs</h2>
<h3>Como as CRDs se Integram ao Kubernetes</h3>
<p>Quando você registra uma CRD no cluster, o Kubernetes cria automaticamente novos endpoints de API para aquele recurso. Cada instância de sua CRD é armazenada no etcd, o banco de dados distribuído do Kubernetes. Um controlador (geralmente executado dentro de um Pod) observa essas mudanças através do mecanismo de watch da API Kubernetes e executa a lógica desejada.</p>
<p>O fluxo é simples mas poderoso: você escreve um manifesto YAML descrevendo a estrutura do seu CRD, aplica esse manifesto ao cluster com <code>kubectl apply</code>, e então pode criar instâncias daquele recurso. Um controlador custom que você escreve (ou obtém de um provedor) fica "escutando" essas mudanças e reage de acordo. Essa separação entre definição (CRD) e comportamento (controlador) é o que torna Kubernetes tão extensível.</p>
<h3>Validação e Schema</h3>
<p>As CRDs suportam validação de esquema OpenAPI 3.0, o que significa que você pode definir regras sobre quais campos são obrigatórios, seus tipos de dados, valores mínimos/máximos, e padrões de expressões regulares. Essa validação acontece no servidor, garantindo que dados inválidos nunca sejam armazenados no etcd. Isso protege a integridade dos seus dados e reduz bugs causados por estados inconsistentes.</p>
<p>Você também pode usar validação de regras customizadas através de CEL (Common Expression Language) para lógica mais complexa, permitindo validações que não cabem em regras simples de schema. Por exemplo, você poderia validar que um campo <code>expiryDate</code> seja sempre posterior a <code>creationDate</code>.</p>
<h2>Criando uma CRD Prática: Exemplo Real</h2>
<h3>Definindo a CRD</h3>
<p>Vamos criar um exemplo prático: uma CRD para gerenciar aplicações de banco de dados. Chamaremos de <code>Database</code>. Aqui está a definição completa:</p>
<pre><code class="language-yaml">apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: databases.data.example.com
spec:
group: data.example.com
names:
kind: Database
plural: databases
singular: database
scope: Namespaced
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
description: "Define uma instância de banco de dados PostgreSQL"
properties:
spec:
type: object
required:
- engine
- version
- storage
properties:
engine:
type: string
enum: ["postgresql", "mysql", "mariadb"]
description: "Engine do banco de dados"
version:
type: string
pattern: '^\d+\.\d+\.\d+$'
description: "Versão do banco de dados (ex: 14.2.5)"
storage:
type: string
pattern: '^\d+Gi$'
description: "Tamanho do volume de armazenamento"
backupEnabled:
type: boolean
default: false
connectionPoolSize:
type: integer
minimum: 1
maximum: 100
default: 10
status:
type: object
properties:
phase:
type: string
enum: ["Pending", "Running", "Failed"]
message:
type: string
lastUpdateTime:
type: string
format: date-time</code></pre>
<p>Salve este arquivo como <code>database-crd.yaml</code> e aplique ao cluster:</p>
<pre><code class="language-bash">kubectl apply -f database-crd.yaml</code></pre>
<p>Você pode verificar se a CRD foi registrada com sucesso:</p>
<pre><code class="language-bash">kubectl get crd databases.data.example.com</code></pre>
<h3>Criando Instâncias da CRD</h3>
<p>Agora que a CRD está definida, você pode criar instâncias dela. Aqui está um exemplo de recurso <code>Database</code>:</p>
<pre><code class="language-yaml">apiVersion: data.example.com/v1
kind: Database
metadata:
name: production-db
namespace: default
spec:
engine: postgresql
version: 14.2.5
storage: 100Gi
backupEnabled: true
connectionPoolSize: 50</code></pre>
<p>Salve como <code>database-instance.yaml</code> e aplique:</p>
<pre><code class="language-bash">kubectl apply -f database-instance.yaml</code></pre>
<p>Agora você pode gerenciar sua database como qualquer outro recurso Kubernetes:</p>
<pre><code class="language-bash"># Listar todas as databases
kubectl get databases
Obter detalhes de uma database específica
kubectl describe database production-db
Editar uma database
kubectl edit database production-db
Deletar uma database
kubectl delete database production-db</code></pre>
<h2>Desenvolvendo um Controlador para sua CRD</h2>
<h3>Arquitetura de um Controlador</h3>
<p>Um controlador é um programa que observa mudanças em recursos (eventos de criação, atualização, deleção) e executa lógica em resposta. O controlador usa a API Kubernetes para monitorar o estado desejado (spec) e atualizando o estado observado (status) conforme a lógica é executada. Essa é a essência da reconciliação: o controlador continua tentando fazer com que o estado atual corresponda ao estado desejado.</p>
<p>Existem bibliotecas especializadas para construir controladores, como Operator SDK, Kubebuilder e client-go. Aqui usaremos uma abordagem com Python e a biblioteca <code>kopf</code> (Kubernetes Operator Framework), que é simples e poderosa.</p>
<h3>Implementação em Python com Kopf</h3>
<p>Primeiro, instale as dependências:</p>
<pre><code class="language-bash">pip install kopf pyyaml kubernetes</code></pre>
<p>Aqui está um controlador funcional para nossa CRD <code>Database</code>:</p>
<pre><code class="language-python">import kopf
import kubernetes
from datetime import datetime
@kopf.on.event('data.example.com', 'v1', 'databases')
def monitor_database(event, **kwargs):
"""
Monitora eventos de mudanças em recursos Database
"""
db = event['object']
action = event['type']
name = db['metadata']['name']
namespace = db['metadata']['namespace']
print(f"Evento detectado: {action} na database {namespace}/{name}")
@kopf.on.create('data.example.com', 'v1', 'databases')
def on_database_create(spec, name, namespace, **kwargs):
"""
Executado quando uma nova Database é criada
"""
engine = spec.get('engine')
version = spec.get('version')
storage = spec.get('storage')
print(f"Criando database {name} com engine {engine} v{version}, storage {storage}")
Aqui você colocaria a lógica real de criação:
- Fazer chamadas a APIs externas
- Provisionar recursos no cloud provider
- Configurar credenciais
- etc
return f"Database {name} criada com sucesso"
@kopf.on.update('data.example.com', 'v1', 'databases')
def on_database_update(spec, name, namespace, patch, **kwargs):
"""
Executado quando uma Database existente é atualizada
"""
engine = spec.get('engine')
storage = spec.get('storage')
print(f"Atualizando database {name}: engine={engine}, storage={storage}")
Aplicar mudanças
with kubernetes.client.ApiClient() as api_client:
api_instance = kubernetes.client.CustomObjectsApi(api_client)
Atualizar o status da database
body = {
'status': {
'phase': 'Running',
'message': f'Database atualizada em {datetime.now().isoformat()}',
'lastUpdateTime': datetime.now().isoformat()
}
}
try:
api_instance.patch_namespaced_custom_object(
group='data.example.com',
version='v1',
namespace=namespace,
plural='databases',
name=name,
body=body
)
print(f"Status de {name} atualizado para Running")
except Exception as e:
print(f"Erro ao atualizar status: {e}")
patch['status'] = {
'phase': 'Failed',
'message': str(e)
}
@kopf.on.delete('data.example.com', 'v1', 'databases')
def on_database_delete(name, namespace, **kwargs):
"""
Executado quando uma Database é deletada
"""
print(f"Deletando database {name} em namespace {namespace}")
Aqui você faria limpeza:
- Remover recursos do cloud provider
- Fazer backup
- Liberar credenciais
- etc
return f"Database {name} deletada com sucesso"
@kopf.index('data.example.com', 'v1', 'databases', key=lambda db, **_: db['spec'].get('engine'))
def databases_by_engine(index, **kwargs):
"""
Índice para facilitar busca de databases por engine
"""
pass</code></pre>
<p>Para executar este controlador localmente durante desenvolvimento:</p>
<pre><code class="language-bash">kopf run -A database_controller.py</code></pre>
<p>Para executar em produção dentro do cluster, crie um Deployment:</p>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: Deployment
metadata:
name: database-controller
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: database-controller
template:
metadata:
labels:
app: database-controller
spec:
serviceAccountName: database-controller
containers:
- name: controller
image: my-registry/database-controller:1.0
imagePullPolicy: Always
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: database-controller
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: database-controller
rules:
- apiGroups: ["data.example.com"]
resources: ["databases"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: database-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: database-controller
subjects:
- kind: ServiceAccount
name: database-controller
namespace: default</code></pre>
<h2>Padrões Avançados e Considerações de Produção</h2>
<h3>Versionamento de CRDs</h3>
<p>À medida que sua CRD evolui, você precisa gerenciar versões para manter compatibilidade com clientes antigos. Kubernetes suporta múltiplas versões simultaneamente. Você pode marcar uma como <code>served: true</code> (aceita requisições) e outra como <code>storage: true</code> (usada internamente). Use <code>conversion</code> estratégies para transformar dados entre versões:</p>
<pre><code class="language-yaml">apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: databases.data.example.com
spec:
group: data.example.com
names:
kind: Database
plural: databases
scope: Namespaced
conversion:
strategy: Webhook
webhook:
clientConfig:
service:
name: crd-conversion-webhook
namespace: default
path: "/convert"
caBundle: base64-encoded-ca-cert
conversionReviewVersions: ["v1"]
versions:
- name: v1
served: true
storage: true
schema: {}
- name: v2
served: true
storage: false
schema: {}</code></pre>
<h3>Subcollections e Status</h3>
<p>As CRDs suportam uma seção <code>status</code> separada, que é o padrão Kubernetes para representar o estado observado. Isso é fundamental para operadores, pois a <code>spec</code> representa o estado desejado e <code>status</code> o que realmente está acontecendo:</p>
<pre><code class="language-yaml">apiVersion: data.example.com/v1
kind: Database
metadata:
name: production-db
spec:
engine: postgresql
version: 14.2.5
storage: 100Gi
status:
phase: Running
message: "Database rodando normalmente"
lastUpdateTime: "2024-01-15T10:30:00Z"
readyReplicas: 3
observedGeneration: 2</code></pre>
<h3>RBAC para CRDs</h3>
<p>Sempre defina permissões apropriadas. Usuários diferentes devem ter acesso diferente a diferentes tipos de recurso:</p>
<pre><code class="language-yaml">apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: database-user
namespace: default
rules:
- apiGroups: ["data.example.com"]
resources: ["databases"]
verbs: ["get", "list"]
- apiGroups: ["data.example.com"]
resources: ["databases/status"]
verbs: ["get"]
- apiGroups: ["data.example.com"]
resources: ["databases"]
resourceNames: ["production-db"]
verbs: ["get", "patch"]</code></pre>
<h2>Conclusão</h2>
<p>Neste artigo, você aprendeu que <strong>Custom Resource Definitions são o mecanismo fundamental para estender o Kubernetes</strong>, permitindo criar abstrações de domínio específicas que se integram perfeitamente com a plataforma. Elas não são apenas sintaxe — elas abrem a porta para automação sofisticada através de operadores, que é como sistemas complexos são gerenciados em produção.</p>
<p>Em segundo lugar, compreendemos que <strong>um CRD sem um controlador é apenas armazenamento — o controlador é o que traz a inteligência</strong>. A reconciliação contínua entre estado desejado (spec) e estado observado (status) é o padrão que permite que sistemas auto-curem e se auto-escalem. Isso exige disciplina na separação de responsabilidades e na idempotência das operações.</p>
<p>Por fim, <strong>produção exige atenção a detalhes</strong>: validação de schema rigorosa, versionamento cuidadoso, RBAC apropriado e monitoramento do seu controlador. Comece simples, teste bem, e evolua incrementalmente.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/" target="_blank" rel="noopener noreferrer">Official Kubernetes CRD Documentation</a></li>
<li><a href="https://sdk.operatorframework.io/docs/building-operators/golang/development-guide/" target="_blank" rel="noopener noreferrer">Operator Framework - Best Practices</a></li>
<li><a href="https://kopf.readthedocs.io/" target="_blank" rel="noopener noreferrer">Kopf: Kubernetes Operator Framework in Python</a></li>
<li><a href="https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#validation" target="_blank" rel="noopener noreferrer">OpenAPI 3.0 Schema in Kubernetes</a></li>
<li><a href="https://kubernetes.io/docs/reference/using-api/api-concepts/" target="_blank" rel="noopener noreferrer">Kubernetes API Conventions</a></li>
</ul>
<p><!-- FIM --></p>