O que Todo Dev Deve Saber sobre Kustomize em Kubernetes: Overlays, Patches e Bases Reutilizáveis Já leu

Introdução ao Kustomize: Por que Precisamos Dele Kubernetes é poderoso, mas gerenciar manifests YAML em múltiplos ambientes é um pesadelo. Quando você tem um deployment que funciona em desenvolvimento, produção e testes, manter três arquivos YAML separados leva a inconsistências, duplicação de código e erros humanos difíceis de rastrear. É aqui que Kustomize entra em cena. Kustomize é uma ferramenta nativa do kubectl (integrada desde a versão 1.14) que permite organizar, customizar e reutilizar manifestos Kubernetes sem usar templates ou linguagens especiais. Em vez de substituir valores com Helm ou processar templates, Kustomize trabalha com bases (manifests padrão) e overlays (variações para ambientes específicos), usando patches para aplicar mudanças cirúrgicas. Isso mantém seu código declarativo, versionável e fácil de debugar. Arquitetura Fundamental: Bases, Overlays e Patches O Conceito de Bases Uma base é um conjunto completo e autossuficiente de manifestos Kubernetes que define sua aplicação. Ela contém tudo que a aplicação precisa: Deployments, Services, ConfigMaps, Secrets, etc. A base é

Introdução ao Kustomize: Por que Precisamos Dele

Kubernetes é poderoso, mas gerenciar manifests YAML em múltiplos ambientes é um pesadelo. Quando você tem um deployment que funciona em desenvolvimento, produção e testes, manter três arquivos YAML separados leva a inconsistências, duplicação de código e erros humanos difíceis de rastrear. É aqui que Kustomize entra em cena.

Kustomize é uma ferramenta nativa do kubectl (integrada desde a versão 1.14) que permite organizar, customizar e reutilizar manifestos Kubernetes sem usar templates ou linguagens especiais. Em vez de substituir valores com Helm ou processar templates, Kustomize trabalha com bases (manifests padrão) e overlays (variações para ambientes específicos), usando patches para aplicar mudanças cirúrgicas. Isso mantém seu código declarativo, versionável e fácil de debugar.

Arquitetura Fundamental: Bases, Overlays e Patches

O Conceito de Bases

Uma base é um conjunto completo e autossuficiente de manifestos Kubernetes que define sua aplicação. Ela contém tudo que a aplicação precisa: Deployments, Services, ConfigMaps, Secrets, etc. A base é pensada para ser o estado "padrão" da sua aplicação, sem customizações específicas de ambiente.

Considere uma aplicação web simples. Sua base ficaria assim:

base/
├── kustomization.yaml
├── deployment.yaml
├── service.yaml
└── configmap.yaml

Arquivo base/deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
  labels:
    app: webapp
spec:
  replicas: 2
  selector:
    matchLabels:
      app: webapp
  template:
    metadata:
      labels:
        app: webapp
    spec:
      containers:
      - name: webapp
        image: myapp:latest
        ports:
        - containerPort: 8080
        resources:
          requests:
            memory: "256Mi"
            cpu: "100m"
          limits:
            memory: "512Mi"
            cpu: "500m"

Arquivo base/service.yaml:

apiVersion: v1
kind: Service
metadata:
  name: webapp
spec:
  selector:
    app: webapp
  ports:
  - port: 80
    targetPort: 8080
  type: ClusterIP

Arquivo base/configmap.yaml:

apiVersion: v1
kind: ConfigMap
metadata:
  name: webapp-config
data:
  LOG_LEVEL: "info"
  DATABASE_TIMEOUT: "30s"

Arquivo base/kustomization.yaml:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - deployment.yaml
  - service.yaml
  - configmap.yaml

commonLabels:
  version: "1.0"
  managed-by: kustomize

O arquivo kustomization.yaml é o coração da base. Ele lista todos os recursos que fazem parte da aplicação e define configurações comuns como labels que serão aplicados a todos os recursos automaticamente.

O Conceito de Overlays

Um overlay é uma variação da base para um ambiente específico. Enquanto a base define o comportamento padrão, overlays definem ajustes: mais réplicas para produção, diferentes limites de recursos, URLs diferentes, etc. Um overlay é uma pasta que sempre contém um kustomization.yaml e referencia a base.

A estrutura fica assim:

.
├── base/
│   ├── kustomization.yaml
│   ├── deployment.yaml
│   ├── service.yaml
│   └── configmap.yaml
├── overlays/
│   ├── dev/
│   │   ├── kustomization.yaml
│   │   └── patches/
│   │       └── deployment.yaml
│   ├── staging/
│   │   ├── kustomization.yaml
│   │   └── patches/
│   │       └── deployment.yaml
│   └── prod/
│       ├── kustomization.yaml
│       └── patches/
│           └── deployment.yaml

Um overlay nunca duplica a base. Ele referencia a base e aplica mudanças através de patches. Arquivo overlays/prod/kustomization.yaml:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

bases:
  - ../../base

patchesStrategicMerge:
  - patches/deployment.yaml

replicas:
  - name: webapp
    count: 5

commonLabels:
  environment: prod

Patches: Aplicando Mudanças Cirúrgicas

Um patch é uma mudança incremental em um recurso. Em vez de reescrever o manifesto inteiro, você especifica exatamente o que muda. Kustomize suporta diferentes tipos de patches: strategic merge patches, JSON patches e patches com comentários.

Arquivo overlays/prod/patches/deployment.yaml (strategic merge patch):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
spec:
  replicas: 5
  template:
    spec:
      containers:
      - name: webapp
        image: myapp:v2.1.0
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "1Gi"
            cpu: "1000m"
        env:
        - name: LOG_LEVEL
          value: "warn"

Quando você aplica este overlay, Kustomize faz um merge inteligente: mantém tudo da base (Service, ConfigMap, labels comuns) e sobrescreve apenas os campos do Deployment que você especificou. O resultado é uma aplicação de produção com 5 réplicas, imagem versão 2.1.0 e recursos aumentados, sem duplicar nenhum código.

Exemplos Práticos: Do Conceito à Aplicação

Exemplo Completo: Aplicação Multi-Ambiente

Vamos construir uma aplicação real com três ambientes. Começamos criando a estrutura:

mkdir -p kustomize-demo/base kustomize-demo/overlays/{dev,staging,prod}
cd kustomize-demo

Arquivo base/kustomization.yaml:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: default

resources:
  - deployment.yaml
  - service.yaml
  - configmap.yaml

commonLabels:
  app: myapp
  managed: "true"

namePrefix: myapp-

commonAnnotations:
  kustomize.config.k8s.io/version: "4.5.7"

Arquivo base/deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 2
  selector:
    matchLabels:
      tier: backend
  template:
    metadata:
      labels:
        tier: backend
    spec:
      containers:
      - name: api
        image: myapp-api:1.0.0
        imagePullPolicy: IfNotPresent
        ports:
        - name: http
          containerPort: 8080
        env:
        - name: DATABASE_HOST
          valueFrom:
            configMapKeyRef:
              name: config
              key: db_host
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits:
            cpu: 500m
            memory: 512Mi
        livenessProbe:
          httpGet:
            path: /health
            port: http
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: http
          initialDelaySeconds: 10
          periodSeconds: 5

Arquivo base/service.yaml:

apiVersion: v1
kind: Service
metadata:
  name: api
spec:
  type: ClusterIP
  ports:
  - name: http
    port: 80
    targetPort: http
  selector:
    tier: backend

Arquivo base/configmap.yaml:

apiVersion: v1
kind: ConfigMap
metadata:
  name: config
data:
  db_host: "postgres.default.svc.cluster.local"
  cache_ttl: "300"
  log_level: "info"

Agora os overlays. Arquivo overlays/dev/kustomization.yaml:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: dev

bases:
  - ../../base

replicas:
  - name: api
    count: 1

patchesStrategicMerge:
  - patches/deployment.yaml

commonLabels:
  environment: development

namePrefix: dev-

Arquivo overlays/dev/patches/deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  template:
    spec:
      containers:
      - name: api
        image: myapp-api:latest
        imagePullPolicy: Always
        resources:
          requests:
            cpu: 50m
            memory: 64Mi
          limits:
            cpu: 200m
            memory: 256Mi
        env:
        - name: DEBUG
          value: "true"

Arquivo overlays/prod/kustomization.yaml:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: production

bases:
  - ../../base

replicas:
  - name: api
    count: 5

patchesStrategicMerge:
  - patches/deployment.yaml
  - patches/service.yaml

commonLabels:
  environment: production

namePrefix: prod-

Arquivo overlays/prod/patches/deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  template:
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - myapp
              topologyKey: kubernetes.io/hostname
      containers:
      - name: api
        image: myapp-api:2.0.0
        resources:
          requests:
            cpu: 500m
            memory: 512Mi
          limits:
            cpu: 2
            memory: 2Gi
        env:
        - name: DATABASE_HOST
          value: "prod-postgres.production.svc.cluster.local"

Arquivo overlays/prod/patches/service.yaml:

apiVersion: v1
kind: Service
metadata:
  name: api
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
spec:
  type: LoadBalancer

Para ver o resultado final sem aplicar, use:

# Visualizar manifests de desenvolvimento
kubectl kustomize overlays/dev

# Visualizar manifests de produção
kubectl kustomize overlays/prod

# Aplicar em desenvolvimento
kubectl apply -k overlays/dev

# Aplicar em produção
kubectl apply -k overlays/prod

Técnicas Avançadas: Vars e ConfigMap Generators

Além de patches simples, Kustomize oferece funcionalidades avançadas. Use vars para substituir valores referenciados e generators para criar ConfigMaps e Secrets dinamicamente.

Arquivo base/kustomization.yaml (versão expandida):

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: default

resources:
  - deployment.yaml
  - service.yaml

configMapGenerator:
  - name: app-config
    literals:
    - app.name=myapp
    - app.version=1.0.0
    files:
    - config.properties

secretGenerator:
  - name: app-secrets
    envs:
    - secrets.env

vars:
  - name: IMAGE_TAG
    objref:
      kind: ConfigMap
      name: app-config
      apiVersion: v1
    fieldref:
      fieldpath: data.image_tag

commonLabels:
  app: myapp

Quando você usa configMapGenerator, Kustomize adiciona um hash ao final do nome (ex: app-config-4h67f2b9) automaticamente, garantindo que mudanças triggerem um rollout de pods. Isso é muito mais eficiente do que patches manuais.

Fluxo de Trabalho Profissional: Organização e Manutenção

Estrutura de Projeto Escalável

Em projetos grandes, você terá múltiplas aplicações. Organize assim:

infra/
├── apps/
│   ├── webapp/
│   │   ├── base/
│   │   └── overlays/
│   │       ├── dev/
│   │       ├── staging/
│   │       └── prod/
│   ├── api/
│   │   ├── base/
│   │   └── overlays/
│   │       ├── dev/
│   │       ├── staging/
│   │       └── prod/
│   └── worker/
│       ├── base/
│       └── overlays/
│           ├── dev/
│           ├── staging/
│           └── prod/
└── clusters/
    ├── dev-cluster/
    │   └── kustomization.yaml
    ├── staging-cluster/
    │   └── kustomization.yaml
    └── prod-cluster/
        └── kustomization.yaml

Arquivo clusters/prod-cluster/kustomization.yaml:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

bases:
  - ../../apps/webapp/overlays/prod
  - ../../apps/api/overlays/prod
  - ../../apps/worker/overlays/prod

namespace: production

commonLabels:
  cluster: prod-cluster
  managed-by: kustomize

Isso permite que uma CI/CD simples execute kubectl apply -k clusters/prod-cluster/ e toda a produção seja atualizada de forma consistente.

Validação e Testes

Antes de aplicar manifests, valide-os:

# Validar sintaxe YAML
kubectl kustomize overlays/prod | kubectl apply --dry-run=client -f -

# Validar contra um cluster específico
kubectl apply -k overlays/prod --dry-run=server -f -

# Usar ferramentas como kubeval
kubectl kustomize overlays/prod | kubeval --strict

# Usar kube-score para boas práticas
kubectl kustomize overlays/prod | kube-score score -

Em um pipeline CI/CD, você rodaria esses comandos antes de fazer merge para garantir que nenhuma configuração inválida entre na base de código.

Versionamento e Mudanças Gradativas

Ao atualizar imagens ou configurações, Kustomize se integra perfeitamente com git. Faça mudanças em um branch, valide com kubectl kustomize, e após merge em main, seu CD pipeline aplica automaticamente. Como todos os manifests finais são gerados, você tem histórico completo de mudanças.

Para mudanças gradativas (canary deployments), combine Kustomize com Flagger ou Argo Rollouts. Kustomize cuida da configuração base, e essas ferramentas gerenciam o rollout inteligente.

Conclusão

Kustomize resolve um problema fundamental em operações Kubernetes: como manter múltiplos ambientes sem duplicar código ou usar templates complexos. Os três conceitos-chave que você deve dominar são: (1) Bases como fonte única de verdade com manifests completos e autossuficientes, (2) Overlays como variações leves para ambientes específicos que referenciam a base sem duplicação, e (3) Patches como modificações cirúrgicas que aplicam mudanças apenas nos campos necessários. Ao estruturar seus projetos dessa forma, você ganha legibilidade, reutilização e controle de versão natural através do git, tudo sem deixar o mundo declarativo do Kubernetes.

Referências


Artigos relacionados