Conceitos Fundamentais
No ecossistema do Kubernetes, a API é baseada em REST. Os recursos são definidos por três componentes principais:
Grupo, Versão e Recurso (GVR)
# O identificador de um recurso é composto por:
# Grupo: define o namespace da API, como 'batch' para jobs.
# Versão: estágio de maturidade, como v1, v1beta1.
# Recurso: a instância concreta, como 'pods' ou 'jobs'.
# Exemplo de consulta:
kubectl api-resources | grep jobs
Grupo, Versão e Tipo (GVK)
# O GVK mapeia um tipo Go para um recurso Kubernetes.
# Ao criar um CRD, o GVK é definido no manifesto.
kubectl get customresourcedefinitions mycrds.example.com -o jsonpath='{.spec.group}/{.spec.versions[*].name}'
Scheme
O Scheme no controller-runtime mantém o mapeaemnto entre GVKs e structs Go, permitindo a serialização e deserialização de objetos.
Desenvolvimenot com Kubebuilder
Configuração do Ambiente
# Instale as ferramentas necessárias
go install sigs.k8s.io/controller-tools/cmd/controller-gen@latest
curl -sSL https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH) -o kubebuilder
chmod +x kubebuilder && mv kubebuilder /usr/local/bin/
Inicialização do Projeto
# Crie um diretório para o projeto e inicialize o módulo Go
mkdir meu-operador && cd meu-operador
go mod init github.com/exemplo/meu-operador
# Gere a estrutura básica do operador
kubebuilder init --domain exemplo.com
# Adicione um novo recurso customizado (API)
kubebuilder create api --group controle --version v1alpha1 --kind Agendamento
Estrutura do Projeto
Ao inicializar, o Kubebuilder gera uma estrutura padrão com diretórios como cmd/, config/, e api/. O diretório config/ contém manifests para RBAC, CRDs e webhooks, enquanto api/ armazena os tipos definidos pelo usuário.
Definição de APIs
Os tipos de recursos são definidos em arquivos Go. Os campos devem usar tags JSON e anotações para validação.
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// Especificação desejada para o recurso Agendamento
type AgendamentoSpec struct {
// Cronograma de execução
// +kubebuilder:validation:Pattern=`^(\*|\d+)(\/\d+)?(\s+(\*|\d+)(\/\d+)?){4}$`
ExpressaoCron string `json:"expressaoCron"`
// Template do Job a ser criado
TemplateJob BatchV1.JobTemplateSpec `json:"templateJob"`
// Limite de tempo para início
// +kubebuilder:validation:Minimum=0
LimiteInicioSeg *int64 `json:"limiteInicioSeg,omitempty"`
}
// Status observado do recurso
type AgendamentoStatus struct {
UltimoAgendamento *metav1.Time `json:"ultimoAgendamento,omitempty"`
JobsAtivos []string `json:"jobsAtivos,omitempty"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
type Agendamento struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec AgendamentoSpec `json:"spec,omitempty"`
Status AgendamentoStatus `json:"status,omitempty"`
}
Imlpementação do Controlador
O controlador contém a lógica de reconciliação para alinhar o estado atual com o estado desejado.
package controller
import (
"context"
"time"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
v1alpha1 "github.com/exemplo/meu-operador/api/v1alpha1"
)
// Reconcilhador para o recurso Agendamento
type AgendamentoReconciler struct {
client.Client
}
func (r *AgendamentoReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
var agendamento v1alpha1.Agendamento
// Obter o recurso atual
if err := r.Get(ctx, req.NamespacedName, &agendamento); err != nil {
logger.Error(err, "Falha ao buscar Agendamento")
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// Lógica de reconciliação aqui
// Atualizar status, gerenciar Jobs, etc.
logger.Info("Reconciliação concluída", "recurso", agendamento.Name)
return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
}
func (r *AgendamentoReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&v1alpha1.Agendamento{}).
Complete(r)
}
Webhooks
Webhooks permitem interceptar requisições de API para validação ou mutação.
# Gerar scaffold para webhook
kubebuilder create webhook --group controle --version v1alpha1 --kind Agendamento --defaulting --programmatic-validation
# Exemplo de anotação no tipo Go
// +kubebuilder:webhook:path=/mutate-controle-v1alpha1-agendamento,mutating=true,failurePolicy=fail,groups=controle,resources=agendamentos,verbs=create;update,versions=v1alpha1,name=magendamento.kb.io
type Agendamento struct { ... }
Interagindo com a API via Client-Go
Clientes Disponíveis
- RESTClient: cliente base para requisições HTTP.
- Clientset: cliente tipado para recursos internos do Kubernetes.
- DynamicClient: cliente para recursos arbitários, incluindo CRDs.
Exemplo com Clientset
package main
import (
"context"
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)
func main() {
config, err := clientcmd.BuildConfigFromFlags("", "")
if err != nil {
panic(err.Error())
}
cli, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err.Error())
}
// Listar todos os Pods no namespace default
pods, err := cli.CoreV1().Pods("default").List(context.TODO(), metav1.ListOptions{})
if err != nil {
panic(err.Error())
}
for _, pod := range pods.Items {
fmt.Printf("Pod: %s\n", pod.Name)
}
}
Usando Dynamic Client para CRDs
package main
import (
"context"
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/tools/clientcmd"
)
func main() {
config, err := clientcmd.BuildConfigFromFlags("", "")
if err != nil {
panic(err.Error())
}
dynClient, err := dynamic.NewForConfig(config)
if err != nil {
panic(err.Error())
}
gvr := schema.GroupVersionResource{
Group: "controle.exemplo.com",
Version: "v1alpha1",
Resource: "agendamentos",
}
// Criar um novo recurso Agendamento
novoAgendamento := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "controle.exemplo.com/v1alpha1",
"kind": "Agendamento",
"metadata": map[string]interface{}{
"name": "meu-agendamento",
"namespace": "default",
},
"spec": map[string]interface{}{
"expressaoCron": "*/5 * * * *",
},
},
}
resultado, err := dynClient.Resource(gvr).Namespace("default").Create(context.TODO(), novoAgendamento, metav1.CreateOptions{})
if err != nil {
panic(err.Error())
}
fmt.Printf("Recurso criado: %s\n", resultado.GetName())
}
Projeto Prático: Monitoramento de Sub-rede
Este exemplo demonstra um operador que calcula o uso de IPs em uma sub-rede definida via CRD.
Definição do Recurso SubRede
package v1alpha1
type SubRedeSpec struct {
CIDRs []string `json:"cidrs"`
}
type SubRedeStatus struct {
CIDRsUtilizados []string `json:"cidrsUtilizados,omitempty"`
TotalIPs int `json:"totalIPs,omitempty"`
IPsUtilizados int `json:"ipsUtilizados,omitempty"`
IPsDisponiveis int `json:"ipsDisponiveis,omitempty"`
Gateways []string `json:"gateways,omitempty"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Total",type=integer,JSONPath=".status.totalIPs"
type SubRede struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec SubRedeSpec `json:"spec,omitempty"`
Status SubRedeStatus `json:"status,omitempty"`
}
Lógica do Controlador
package controller
import (
"context"
"net"
"time"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
v1alpha1 "github.com/exemplo/meu-operador/api/v1alpha1"
)
type SubRedeReconciler struct {
client.Client
}
func (r *SubRedeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
var rede v1alpha1.SubRede
if err := r.Get(ctx, req.NamespacedName, &rede); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// Calcular estatísticas da sub-rede
totalIPs, gateways := calcularIPs(rede.Spec.CIDRs)
rede.Status.TotalIPs = totalIPs
rede.Status.Gateways = gateways
// A lógica real buscaria pods para contar IPs utilizados
rede.Status.IPsUtilizados = 0
rede.Status.IPsDisponiveis = totalIPs
if err := r.Status().Update(ctx, &rede); err != nil {
logger.Error(err, "Falha ao atualizar status")
return ctrl.Result{}, err
}
return ctrl.Result{RequeueAfter: 60 * time.Second}, nil
}
func calcularIPs(cidrs []string) (int, []string) {
var total int
var gws []string
for _, cidr := range cidrs {
_, ipNet, _ := net.ParseCIDR(cidr)
bits, size := ipNet.Mask.Size()
total += 1<<uint :="ipNet.IP" broadcast="" complete="" ctrl.manager="" ctrl.newcontrollermanagedby="" da="" de="" desconta="" e="" endere="" error="" for="" func="" gateway="" gw="" gw.string="" gws="append(gws," ip="" o="" primeiro="" rede="" return="" setupwithmanager="" sub-rede="" total=""></uint>