Como usar o Kustomize para gerenciar variações de deploy por ambiente

1. Introdução ao Kustomize e o problema da variação entre ambientes

Manter múltiplos ambientes Kubernetes (dev, staging, production) é um dos maiores desafios na adoção de contêineres. Cada ambiente exige configurações diferentes — número de réplicas, recursos de CPU/memória, variáveis de ambiente, nomes de imagens e secrets. A abordagem ingênua de copiar e colar manifests resulta em duplicação massiva, inconsistências e erros humanos.

O Kustomize resolve esse problema de forma elegante. É uma ferramenta declarativa nativa do kubectl (desde a versão 1.14) que permite personalizar configurações YAML sem usar templates ou linguagens de script. Diferente do Helm, que usa templates Go com lógica condicional, o Kustomize opera puramente com patches e sobreposições, mantendo os manifests originais intactos e legíveis.

As vantagens são claras: simplicidade, ausência de linguagem de template, reutilização de bases comuns e integração direta com kubectl apply -k.

2. Estrutura de diretórios e conceitos fundamentais

A estrutura recomendada para projetos Kustomize segue o padrão base/overlays:

meu-app/
├── base/
│   ├── kustomization.yaml
│   ├── deployment.yaml
│   ├── service.yaml
│   └── configmap.yaml
└── overlays/
    ├── dev/
    │   └── kustomization.yaml
    ├── staging/
    │   └── kustomization.yaml
    └── production/
        └── kustomization.yaml

O arquivo kustomization.yaml é o coração do sistema. Ele declara quais recursos incluir, quais patches aplicar e como gerar ConfigMaps e Secrets.

O Kustomize suporta duas estratégias de patch:
- Strategic Merge Patch: específico do Kubernetes, faz merge inteligente de listas e maps
- JSON Patch 6902: operações padronizadas (add, remove, replace) para alterações precisas

A ordem de resolução é: base → patches estratégicos → patches JSON → geradores → transformers.

3. Criando uma base reutilizável com recursos comuns

A base deve conter apenas o que é comum a todos os ambientes. Exemplo de base/kustomization.yaml:

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

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

commonLabels:
  app: meu-app
  managed-by: kustomize

E o base/deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: meu-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: meu-app
  template:
    metadata:
      labels:
        app: meu-app
    spec:
      containers:
      - name: app
        image: minha-imagem:latest
        ports:
        - containerPort: 8080
        resources:
          requests:
            memory: "64Mi"
            cpu: "100m"
          limits:
            memory: "128Mi"
            cpu: "200m"

Note que usamos valores genéricos. As variações virão nos overlays. O namePrefix e nameSuffix são recursos úteis para evitar conflitos:

# dentro de overlays/dev/kustomization.yaml
namePrefix: dev-
nameSuffix: -v1

Isso transformaria meu-app em dev-meu-app-v1.

4. Personalizando por ambiente com overlays e patches

Cada overlay herda a base e aplica suas próprias modificações. Exemplo para overlays/production/kustomization.yaml:

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

resources:
  - ../../base

patches:
  - patch: |-
      - op: replace
        path: /spec/replicas
        value: 5
      - op: replace
        path: /spec/template/spec/containers/0/resources/requests/cpu
        value: "500m"
      - op: replace
        path: /spec/template/spec/containers/0/resources/limits/cpu
        value: "1000m"
    target:
      kind: Deployment
      name: meu-app

Para dev, usamos patches estratégicos que são mais legíveis:

# overlays/dev/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - ../../base

patchesStrategicMerge:
  - patch-deployment.yaml

E o arquivo overlays/dev/patch-deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: meu-app
spec:
  replicas: 1
  template:
    spec:
      containers:
      - name: app
        image: minha-imagem:dev-latest
        resources:
          requests:
            memory: "32Mi"
            cpu: "50m"

5. Gerenciando ConfigMaps e Secrets específicos por ambiente

O configMapGenerator cria ConfigMaps a partir de arquivos ou literais, evitando duplicação:

# overlays/production/kustomization.yaml
configMapGenerator:
  - name: app-config
    literals:
      - LOG_LEVEL=info
      - DB_HOST=db.production.svc.cluster.local
    files:
      - config/app.properties

Para secrets, usamos secretGenerator:

secretGenerator:
  - name: app-secrets
    literals:
      - DB_PASSWORD=senha-segura
    files:
      - secrets/tls.crt

⚠️ Atenção: nunca versionar secrets em texto plano. Use ferramentas como sops (Mozilla) ou Sealed Secrets (Bitnami) para criptografar os dados antes de commit.

6. Trabalhando com múltiplos clusters e namespaces

Para definir namespaces diferentes por ambiente:

# overlays/production/kustomization.yaml
namespace: production

resources:
  - ../../base

patchesStrategicMerge:
  - patch-namespace.yaml

O patch para alterar namespace em todos os recursos:

# patch-namespace.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: meu-app
  namespace: production

Isso garante isolamento completo entre ambientes, mesmo que compartilhem o mesmo cluster.

Para clusters remotos, combine Kustomize com kubectl:

kubectl apply -k overlays/production --context=prod-cluster

7. Fluxo de trabalho e integração com CI/CD

O comando mais útil é kubectl kustomize para visualizar o YAML final sem aplicar:

kubectl kustomize overlays/production > output.yaml

Em pipelines CI/CD, a integração é direta. Exemplo com GitHub Actions:

# .github/workflows/deploy.yml
- name: Deploy to Production
  run: |
    kubectl apply -k overlays/production --context=prod-cluster

Estratégia por branch:
- Branch main → overlay staging
- Branch release/* → overlay production
- Branch feature/* → overlay dev com prefixo único

Versionamento no Git permite revisar diffs entre ambientes:

git diff HEAD~1 -- overlays/production/

8. Boas práticas, armadilhas comuns e próximos passos

Boas práticas:
- Mantenha a base o mais genérica possível
- Use components para patches reutilizáveis entre overlays
- Prefira patches estratégicos para alterações simples
- Documente cada overlay com comentários no kustomization.yaml

Armadilhas comuns:
- Esquecer de atualizar namePrefix ao criar novo overlay
- Usar patches JSON quando patches estratégicos seriam mais claros
- Ignorar a ordem de aplicação dos patches

Limitações:
- Kustomize não suporta lógica condicional (if/else)
- Para cenários complexos, considere Helm com Kustomize como pós-processador
- Recursos avançados: plugins customizados e transformers permitem extensibilidade

Próximos passos: explore Kustomize Components para criar blocos reutilizáveis de configuração, e Kustomize Plugins para integração com ferramentas externas como Vault ou AWS Secrets Manager.

Referências