DEV Community

Celso Nery
Celso Nery

Posted on

Montando um Cluster Kubernetes On-Premise — Parte 7: Recursos Essenciais para Produção

🇺🇸 English version here.

Montando um Cluster Kubernetes On-Premise — Parte 7: Recursos Essenciais para Produção

Nas partes anteriores desta série, aprendemos a estrutura básica dos recursos do kubernetes e manifestos YAML. Mas existe uma distância entre "um Deployment que funciona" e "um Deployment para produção". Neste artigo, vou abordar recursos que considero indispensáveis nesse segundo cenário: Namespaces, Secrets, autenticação com registries privados (regcred), RollingUpdate, SecurityContext e Requests/Limits. No final, juntamos tudo em um exemplo real e completo de Deployment.

Namespace

Um Namespace é uma forma de dividir logicamente um único cluster físico em múltiplos "clusters virtuais" — útil para separar ambientes (produção, homologação, desenvolvimento) ou aplicações diferentes, evitando que cresçam misturadas em um espaço só.

Criando um namespace:

$ kubectl create namespace aplicacao-prod

ou 

$ kubectl create ns aplicacao-prod
Enter fullscreen mode Exit fullscreen mode

Ou via manifesto:

apiVersion: v1
kind: Namespace
metadata:
  name: aplicacao-prod
Enter fullscreen mode Exit fullscreen mode

Listando os namespaces existentes:

$kubectl get namespaces

ou

$kubectl get ns
Enter fullscreen mode Exit fullscreen mode

A partir daí, praticamente todo objeto criado (Deployments, Services, Secrets) pode — e geralmente deve-se declarar em qual namespace ele vive, através do campo metadata.namespace ou a flag -n se for via linha de comando. Isso traz benefícios importantes:

  • Isolamento de nomes: dois Deployments chamados api podem coexistir tranquilamente, desde que estejam em namespaces diferentes;
  • Controle de acesso (RBAC): é possível restringir o que um usuário ou uma aplicação pode fazer, escopado a um namespace específico;
  • Organização: comandos como kubectl get pods -n aplicacao-prod deixam claro o que pertence a qual ambiente ou aplicação.

Se você não especificar um namespace, o Kubernetes usa o namespace default — o que funciona para testes rápidos, mas não é recomendado para ambientes reais, onde múltiplas aplicações compartilham o cluster.

Secrets

Secrets armazenam dados sensíveis — senhas, tokens, chaves de API — de forma um pouco mais protegida que um ConfigMap comum (o conteúdo fica codificado em base64, e o acesso pode ser restrito via RBAC).

Criando um Secret via linha de comando, a partir de valores literais:

kubectl create secret generic aplicacao-secret \
  --namespace=aplicacao-prod \
  --from-literal=DB_PASSWORD=minhasenha \
  --from-literal=API_KEY=abc123
Enter fullscreen mode Exit fullscreen mode

Ou via manifesto YAML:

apiVersion: v1
kind: Secret
metadata:
  name: aplicacao-secret
  namespace: aplicacao-prod
type: Opaque
data:
  DB_PASSWORD: bWluaGFzZW5oYQ==
  API_KEY: YWJjMTIz
Enter fullscreen mode Exit fullscreen mode

Os valores no campo data precisam estar em base64 (echo -n 'minhasenha' | base64). Isso não é criptografia — apenas uma codificação — então trate o arquivo YAML do Secret com o mesmo cuidado que trataria um arquivo de senhas em texto puro. Nunca versione (Git) um Secret com valores reais sem alguma camada adicional de proteção, como Sealed Secrets ou um cofre de segredos (Vault).

Consumindo o Secret na aplicação

A forma mais comum é injetar todo o conteúdo do Secret como variáveis de ambiente, usando envFrom:

containers:
  - name: aplicacao
    image: minhaempresa/api:1.0.0
    envFrom:
      - secretRef:
          name: aplicacao-secret
Enter fullscreen mode Exit fullscreen mode

Dessa forma, cada chave do Secret (DB_PASSWORD, API_KEY) vira automaticamente uma variável de ambiente dentro do container, sem precisar listá-las uma a uma.

Regcred — Credenciais para Registry Privado

Se suas imagens são publicadas em um registry privado hospedado no GitLab por exemplo (GitLab Container Registry), o Kubernetes precisa de credenciais para autenticar o pull dessas imagens.

O jeito mais seguro é usar um Deploy Token do GitLab (em vez da sua senha pessoal) — ele pode ser restrito a permissão somente leitura (read_registry) e escopado a um projeto ou grupo específico.

Criando o secret com o Deploy Token:

kubectl create secret docker-registry regcred \
  --namespace=aplicacao-prod \
  --docker-server=registry.gitlab.com \
  --docker-username=<deploy-token-username> \
  --docker-password=<deploy-token-password> \
  --docker-email=seu-email@empresa.com
Enter fullscreen mode Exit fullscreen mode

Depois, referencie o secret no Deployment através de imagePullSecrets:

spec:
  imagePullSecrets:
    - name: regcred
  containers:
    - name: aplicacao
      image: registry.gitlab.com/organizacao/aplicacao:v1.0.0
Enter fullscreen mode Exit fullscreen mode

Prefira sempre um Deploy Token em vez de credenciais pessoais de usuário — se o token vazar ou precisar ser revogado, isso não afeta sua conta pessoal no GitLab, e o escopo de acesso já vem limitado por padrão.

RollingUpdate

Por padrão, um Deployment já usa a estratégia RollingUpdate ao atualizar a versão de uma aplicação — substituindo os pods antigos pelos novos de forma gradual, sem downtime completo. Mas os parâmetros exatos desse processo podem (e devem) ser ajustados:

spec:
  minReadySeconds: 10
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
Enter fullscreen mode Exit fullscreen mode
  • maxSurge: quantos pods a mais do que o número de réplicas desejado podem ser criados temporariamente durante a atualização (aqui, 1 pod extra);
  • maxUnavailable: quantos pods podem ficar indisponíveis simultaneamente durante a atualização (aqui, no máximo 1);
  • minReadySeconds: tempo mínimo (em segundos) que um novo pod precisa ficar "pronto" (passando nos health checks) antes de ser considerado disponível — isso evita que o rollout avance rápido demais com pods que subiram mas ainda não estabilizaram.

Esses valores devem ser calibrados de acordo com a criticidade da aplicação: valores mais conservadores (maxUnavailable: 0) garantem zero indisponibilidade durante o deploy, ao custo de exigir mais capacidade temporária no cluster (via maxSurge).

Já presenciei problemas com downtime em aplicações java, pelo fato da nova réplica demorar a ficar disponível.

SecurityContext

O securityContext define restrições de segurança sobre como um Pod ou container pode rodar — reduzindo a superfície de ataque caso a aplicação seja comprometida. Ele pode ser definido tanto no nível do Pod (aplicado a todos os containers) quanto no nível do container (mais específico, sobrepõe o do Pod).

spec:
  securityContext:
    runAsUser: 5000
    runAsGroup: 5000
  containers:
    - name: aplicacao
      securityContext:
        allowPrivilegeEscalation: false
Enter fullscreen mode Exit fullscreen mode
  • runAsUser / runAsGroup (nível Pod): força os processos a rodarem com um UID/GID específico, em vez de root — reduz o dano potencial caso um invasor consiga executar código dentro do container;
  • allowPrivilegeEscalation: false (nível container): impede que um processo dentro do container obtenha mais privilégios do que os que já possui (por exemplo, através de binários com bit setuid).

Outras opções comuns que vale considerar, dependendo da aplicação:

  • readOnlyRootFilesystem: true: torna o sistema de arquivos raiz do container somente leitura, forçando qualquer escrita a ir para volumes explicitamente montados;
  • capabilities.drop: ["ALL"]: remove todas as capabilities do Linux por padrão, adicionando de volta apenas as estritamente necessárias.

Requests / Limits

Sem definir requests e limits, um container pode, teoricamente, consumir todos os recursos disponíveis no nó onde está rodando — prejudicando outras aplicações no mesmo cluster (ou até derrubando o próprio nó).

resources:
  requests:
    memory: "128Mi"
    cpu: "100m"
  limits:
    memory: "512Mi"
    cpu: "1000m"
Enter fullscreen mode Exit fullscreen mode
  • requests: a quantidade de recursos que o Kubernetes reserva para o container ao decidir em qual nó agendá-lo. O scheduler só coloca o pod em um nó que tenha essa quantidade disponível;
  • limits: o teto máximo que o container pode consumir. Ultrapassar o limite de memória resulta no container sendo finalizado (OOMKilled); ultrapassar o limite de CPU resulta em throttling (o container é desacelerado, mas não finalizado).

100m de CPU significa 100 milicores, ou seja, 0.1 de um núcleo. Definir requests realistas (baseados no consumo observado da aplicação) é importante — requests muito altos desperdiçam capacidade do cluster; requests muito baixos podem levar a mais pods do que o nó realmente suporta rodando simultaneamente.

Exemplo de Deployment de uma aplicação real

Juntando tudo o que vimos nesta parte: namespace dedicado, secrets, autenticação com registry privado, rolling update calibrado, securityContext restritivo e requests/limits definidos — mais o Service e o HorizontalPodAutoscaler já vistos em partes anteriores:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: aplicacao
  namespace: aplicacao-prod
spec:
  minReadySeconds: 10
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  replicas: 2
  selector:
    matchLabels:
      app: aplicacao
  template:
    metadata:
      labels:
        app: aplicacao
    spec:
      securityContext:
        runAsUser: 5000
        runAsGroup: 5000
      containers:
      - name: aplicacao
        image: registry.suaempresa.com.br/organizacao/api:v1234
        envFrom:
          - secretRef:
              name: aplicacao-secret
        securityContext:
          allowPrivilegeEscalation: false
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "512Mi"
            cpu: "1000m"
        ports:
        - containerPort: 8080
      imagePullSecrets:
        - name: regcred
---
apiVersion: v1
kind: Service
metadata:
  name: aplicacao
  namespace: aplicacao-prod
spec:
  selector:
    app: aplicacao
  ports:
  - protocol: TCP
    port: 8080
    targetPort: 8080
  type: NodePort
---
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: aplicacao-hpa
  namespace: aplicacao-prod
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: aplicacao
  minReplicas: 2
  maxReplicas: 5
  targetCPUUtilizationPercentage: 75
Enter fullscreen mode Exit fullscreen mode

Os três objetos trabalham juntos:

  • O Deployment roda a aplicação com 2 réplicas, autenticando no registry privado via regcred e injetando configurações sensíveis via aplicacao-secret;
  • O Service expõe essas réplicas de forma estável na porta 8080;
  • O HorizontalPodAutoscaler monitora o uso de CPU dessas réplicas e ajusta automaticamente entre 2 e 5, conforme a demanda.

Considerações finais

Esses recursos: namespaces, secrets, autenticação segura com registries, estratégia de rollout calibrada, restrições de segurança e limites de recursos — são o que normalmente separa um manifesto comum de um manifesto para rodar uma aplicação crítica em produção. Vale revisar cada um desses pontos sempre que for promover uma aplicação de homologação para produção.

Top comments (0)