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
Ou via manifesto:
apiVersion: v1
kind: Namespace
metadata:
name: aplicacao-prod
Listando os namespaces existentes:
$kubectl get namespaces
ou
$kubectl get ns
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
apipodem 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-proddeixam 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
Ou via manifesto YAML:
apiVersion: v1
kind: Secret
metadata:
name: aplicacao-secret
namespace: aplicacao-prod
type: Opaque
data:
DB_PASSWORD: bWluaGFzZW5oYQ==
API_KEY: YWJjMTIz
Os valores no campo
dataprecisam 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
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
Quando as imagens de uma aplicação estão armazenadas em um registro privado, seja um registro próprio, seja um plano pago do Docker Hub ou de outro provedor, Kubernetes precisa de credenciais para conseguir baixar (pull) essas imagens ao criar os pods. É para isso que existe o imagePullSecret, comumente criado com o nome regcred.
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:
Existem duas formas de criar esse secret: via linha de comando (mais rápida) ou via arquivo YAML (mais indicada quando você precisa versionar ou automatizar essa criação).
Opção 1: criando via linha de comando
kubectl create secret docker-registry regcred \
-n aplicacao-prod \
--docker-server=registry.suaempresa.com \
--docker-username=<deploy-token-username> \
--docker-password=<deploy-token-password> \
--docker-email=seu-email@suaempresa.com
Onde:
-
-n namespace: namespace onde o secret será criado (o secret fica restrito a esse namespace); -
--docker-server: endereço do registro privado; -
--docker-username/--docker-password: credenciais de acesso; -
--docker-email: e-mail associado à conta (obrigatório pelo comando, ainda que não seja sempre usado na prática).
Opção 2: criando via arquivo YAML
Se você prefere (ou precisa) versionar essa configuração, é possível montar o secret manualmente como um arquivo YAML.
1. Crie um arquivo config.json com as credenciais do registro:
{
"auths": {
"registry.suaempresa.com": {
"username": "<deploy-token-username>",
"password": "<deploy-token-password>",
"email": "seu-email@suaempresa.com"
}
}
}
2. Converta o arquivo para base64, já que é assim que o Kubernetes espera o conteúdo do secret:
$ cat config.json | base64 -w 0
3. Crie o arquivo regcred.yaml, colando o resultado do comando acima no campo .dockerconfigjson:
apiVersion: v1
kind: Secret
metadata:
name: regcred
namespace: namespace
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: ewoJImF1dGhzIjogewoJCSJyZWdpc3RyeS5zdWFlbXByZXNhLmNvbSI6IHsKICAgICAgInVzZXJuYW1lIjogIjxkZXBsb3ktdG9rZW4tdXNlcm5hbWU+IiwKICAgICAgInBhc3N3b3JkIjogIjxkZXBsb3ktdG9rZW4tcGFzc3dvcmQ+IiwKICAgICAgImVtYWlsIjogInNldS1lbWFpbEBzdWFlbXByZXNhLmNvbSIKICAgIH0KCX0KfQo=
Nunca versione (Git) um arquivo
regcred.yamlcom credenciais reais - mesmo estando em base64, isso não é criptografia, apenas uma codificação: qualquer pessoa com acesso ao arquivo consegue decodificar e recuperar a senha original. Trate esse valor como um segredo sensível de verdade, usando ferramentas como Sealed Secrets, Vault ou variáveis de ambiente do seu pipeline de CI/CD.
4. Aplique o secret no cluster:
$ kubectl apply -f regcred.yaml
Usando o regcred em um Deployment
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
O campo imagePullSecrets aponta para o secret regcred criado anteriormente, e ele precisa estar no mesmo namespace do Pod ou Deployment que vai usá-lo.
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
-
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
-
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 bitsetuid).
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"
-
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).
100mde 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
Os três objetos trabalham juntos:
- O Deployment roda a aplicação com 2 réplicas, autenticando no registry privado via
regcrede injetando configurações sensíveis viaaplicacao-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.













