1. Conceitos Fundamentais
O que é Kubernetes
Kubernetes (K8s) é uma plataforma open-source para orquestração de containers. Ele automatiza implantação, escalonamento e gerenciamento de aplicações containerizadas.
Arquitetura do Cluster
┌─────────────────────────────────────────────────────────┐
│ CONTROL PLANE │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐ │
│ │ kube- │ │ kube- │ │ kube- │ │ etcd │ │
│ │ apiserver│ │controller│ │scheduler │ │ │ │
│ └──────────┘ └──────────┘ └──────────┘ └────────┘ │
└─────────────────────────────────────────────────────────┘
│ │ │
┌──────────┴────┐ ┌────────┴──────┐ ┌─────┴─────────┐
│ WORKER NODE │ │ WORKER NODE │ │ WORKER NODE │
│ ┌───────────┐ │ │ ┌───────────┐ │ │ ┌───────────┐ │
│ │ kubelet │ │ │ │ kubelet │ │ │ │ kubelet │ │
│ │ kube-proxy│ │ │ │ kube-proxy│ │ │ │ kube-proxy│ │
│ │ runtime │ │ │ │ runtime │ │ │ │ runtime │ │
│ └───────────┘ │ │ └───────────┘ │ │ └───────────┘ │
└───────────────┘ └───────────────┘ └───────────────┘
Componentes do Control Plane
| Componente | Função |
|---|
| kube-apiserver | Ponto de entrada para todas as operações (REST API). Valida e processa requests |
| etcd | Banco de dados chave-valor distribuído. Armazena todo o estado do cluster |
| kube-scheduler | Decide em qual Node um Pod será executado com base em recursos e constraints |
| kube-controller-manager | Roda controllers (Deployment, ReplicaSet, Node, etc.) em loop de reconciliação |
| cloud-controller-manager | Integra com APIs de cloud (AWS, GCP, Azure) para LoadBalancer, volumes, etc. |
Componentes do Worker Node
| Componente | Função |
|---|
| kubelet | Agente que garante que os containers estão rodando conforme spec do Pod |
| kube-proxy | Mantém regras de rede (iptables/ipvs) para roteamento de Services |
| Container Runtime | Executa containers (containerd, CRI-O, Docker) |
Objetos Principais
Namespace — Isolamento lógico de recursos dentro do cluster
Pod — Menor unidade executável: um ou mais containers
ReplicaSet — Garante N réplicas de um Pod rodando
Deployment — Gerencia ReplicaSets com rollout/rollback
StatefulSet — Deployment com identidade estável (banco de dados, etc.)
DaemonSet — Um Pod por Node (monitoring agents, log collectors)
Job — Execução até conclusão (batch processing)
CronJob — Job agendado com expressão cron
Service — Exposição estável de Pods via selector
Ingress — Roteamento HTTP/HTTPS para Services
ConfigMap — Configurações não sensíveis em chave-valor
Secret — Dados sensíveis codificados em base64
PersistentVolume — Storage provisionado no cluster
PVC — Requisição de storage por um Pod
ServiceAccount — Identidade para Pods interagirem com a API
Namespaces Built-in
kubectl get namespaces
# NAME STATUS AGE
# default Active 30d # recursos sem namespace explícito
# kube-system Active 30d # componentes internos do K8s
# kube-public Active 30d # recursos públicos (leitura por todos)
# kube-node-lease Active 30d # heartbeats dos nodes
2. kubectl Essencial
Configuração e Contextos
# Ver config atual
kubectl config view
kubectl config current-context
# Listar contextos disponíveis
kubectl config get-contexts
# Trocar de contexto (cluster)
kubectl config use-context prod-cluster
kubectl config use-context staging-cluster
# Definir namespace padrão para o contexto atual
kubectl config set-context --current --namespace=meu-namespace
# Instalar kubectx + kubens (facilita troca de contexto/namespace)
# brew install kubectx
kubectx prod-cluster
kubens meu-namespace
get — Listar Recursos
# Sintaxe: kubectl get <recurso> [nome] [flags]
# Listar todos os pods no namespace atual
kubectl get pods
# Listar com mais informações (IP, Node, status detalhado)
kubectl get pods -o wide
# Listar em todos os namespaces
kubectl get pods --all-namespaces
kubectl get pods -A
# Listar em namespace específico
kubectl get pods -n kube-system
# Output em YAML (ver spec completo)
kubectl get pod meu-pod -o yaml
# Output em JSON
kubectl get pod meu-pod -o json
# Output customizado com jsonpath
kubectl get pods -o jsonpath='{.items[*].metadata.name}'
# Listar com labels
kubectl get pods --show-labels
# Filtrar por label
kubectl get pods -l app=frontend
kubectl get pods -l app=frontend,env=prod
# Watch (atualização em tempo real)
kubectl get pods -w
# Listar múltiplos recursos de uma vez
kubectl get pods,services,deployments
# Listar todos os recursos de um namespace
kubectl get all -n meu-namespace
# Listar nodes do cluster
kubectl get nodes
kubectl get nodes -o wide
describe — Detalhes e Eventos
# Ver detalhes completos de um recurso (inclui Events — muito útil para debug)
kubectl describe pod meu-pod
kubectl describe node worker-1
kubectl describe service meu-service
kubectl describe deployment meu-deploy
kubectl describe ingress meu-ingress
# Descrever todos os pods com label específica
kubectl describe pods -l app=backend
apply / create / delete — Gerenciar Recursos
# Aplicar manifest (cria ou atualiza — idempotente, PREFERIDO)
kubectl apply -f deployment.yaml
kubectl apply -f ./k8s/ # aplica todos os arquivos do diretório
kubectl apply -f ./k8s/ --recursive # aplica recursivamente
kubectl apply -f https://raw.githubusercontent.com/.../manifest.yaml
# Criar recurso (falha se já existe)
kubectl create -f pod.yaml
kubectl create namespace producao
kubectl create configmap minha-config --from-literal=chave=valor
# Deletar recursos
kubectl delete -f deployment.yaml
kubectl delete pod meu-pod
kubectl delete pods -l app=frontend
kubectl delete namespace staging # deleta tudo no namespace
kubectl delete pod meu-pod --force --grace-period=0 # forçar deleção imediata
exec — Executar Comandos em Containers
# Shell interativo no container
kubectl exec -it meu-pod -- /bin/bash
kubectl exec -it meu-pod -- /bin/sh # quando bash não disponível
# Container específico em Pod multi-container
kubectl exec -it meu-pod -c sidecar-container -- /bin/bash
# Executar comando único (sem TTY)
kubectl exec meu-pod -- ls /app
kubectl exec meu-pod -- env
kubectl exec meu-pod -- cat /etc/config/app.yaml
# Namespace específico
kubectl exec -it meu-pod -n producao -- /bin/bash
logs — Ver Logs
# Logs do pod (container principal)
kubectl logs meu-pod
# Seguir logs em tempo real
kubectl logs -f meu-pod
# Container específico
kubectl logs meu-pod -c meu-container
# Últimas N linhas
kubectl logs meu-pod --tail=100
# Logs dos últimos 30 minutos
kubectl logs meu-pod --since=30m
# Logs de pod anterior (após crash/restart)
kubectl logs meu-pod --previous
kubectl logs meu-pod -p
# Logs de todos os pods com label
kubectl logs -l app=backend --all-containers=true
# Timestamps nos logs
kubectl logs meu-pod --timestamps
port-forward — Acesso Local
# Encaminhar porta local para porta do pod
kubectl port-forward pod/meu-pod 8080:80
# Agora: curl localhost:8080 → pod:80
# Via Service
kubectl port-forward service/meu-service 8080:80
# Via Deployment
kubectl port-forward deployment/meu-deploy 8080:80
# Múltiplas portas
kubectl port-forward pod/meu-pod 8080:80 9090:9090
# Bind em todas as interfaces (acessível na rede)
kubectl port-forward pod/meu-pod 8080:80 --address=0.0.0.0
cp — Copiar Arquivos
# Copiar do pod para local
kubectl cp meu-pod:/app/logs/app.log ./app.log
kubectl cp meu-pod:/etc/config/ ./config-backup/
# Copiar do local para pod
kubectl cp ./config.yaml meu-pod:/app/config/config.yaml
# Container específico
kubectl cp meu-pod:/app/data ./data -c meu-container
# Namespace específico
kubectl cp meu-pod:/app/log.txt ./log.txt -n producao
top — Monitorar Recursos
# Uso de CPU/memória dos nodes
kubectl top nodes
# Uso de CPU/memória dos pods
kubectl top pods
kubectl top pods -n kube-system
kubectl top pods --all-namespaces
kubectl top pods --sort-by=cpu
kubectl top pods --sort-by=memory
# Pod específico com containers
kubectl top pod meu-pod --containers
rollout — Gerenciar Rollouts
# Status do rollout
kubectl rollout status deployment/meu-deploy
kubectl rollout status deployment/meu-deploy -w # aguardar conclusão
# Histórico de rollouts
kubectl rollout history deployment/meu-deploy
# Ver detalhes de uma revisão específica
kubectl rollout history deployment/meu-deploy --revision=3
# Desfazer último rollout (rollback)
kubectl rollout undo deployment/meu-deploy
# Rollback para revisão específica
kubectl rollout undo deployment/meu-deploy --to-revision=2
# Pausar rollout (zero-downtime maintenance)
kubectl rollout pause deployment/meu-deploy
# Retomar rollout pausado
kubectl rollout resume deployment/meu-deploy
# Forçar rollout (restart sem mudar imagem)
kubectl rollout restart deployment/meu-deploy
scale — Escalar Réplicas
# Escalar deployment para N réplicas
kubectl scale deployment meu-deploy --replicas=5
kubectl scale deployment meu-deploy --replicas=0 # "desligar" sem deletar
# Escalar apenas se réplicas atuais batem com expectativa
kubectl scale deployment meu-deploy --replicas=3 --current-replicas=2
# Escalar múltiplos deployments
kubectl scale deployment frontend backend --replicas=3
# Escalar ReplicaSet
kubectl scale replicaset meu-rs --replicas=4
edit / patch — Editar Recursos
# Abrir editor (usa $EDITOR ou vi por padrão)
kubectl edit deployment meu-deploy
kubectl edit configmap minha-config
# Patch estratégico (merge parcial)
kubectl patch deployment meu-deploy -p '{"spec":{"replicas":3}}'
# Patch com arquivo
kubectl patch deployment meu-deploy --patch-file=patch.yaml
# Patch de substituição total (replace)
kubectl patch deployment meu-deploy --type=merge -p '{"spec":{"replicas":5}}'
# Patch JSON (path específico)
kubectl patch pod meu-pod --type='json' \
-p='[{"op": "replace", "path": "/spec/containers/0/image", "value":"nginx:1.25"}]'
Comandos Úteis Adicionais
# Ver API resources disponíveis
kubectl api-resources
kubectl api-resources --namespaced=true
# Verificar permissões do usuário atual
kubectl auth can-i create pods
kubectl auth can-i delete deployments -n producao
kubectl auth can-i '*' '*' # admin check
# Dry-run (simular sem aplicar)
kubectl apply -f deployment.yaml --dry-run=client
kubectl apply -f deployment.yaml --dry-run=server
# Gerar YAML de um recurso imperativo
kubectl create deployment nginx --image=nginx --dry-run=client -o yaml > deploy.yaml
# Diff entre estado atual e novo manifest
kubectl diff -f deployment.yaml
# Explain — documentação de campos
kubectl explain pod.spec.containers
kubectl explain deployment.spec.strategy
# Eventos do namespace
kubectl get events --sort-by='.lastTimestamp'
kubectl get events -n meu-namespace
# Forçar deleção de namespace preso em Terminating
kubectl get namespace stuck-ns -o json | \
jq '.spec.finalizers = []' | \
kubectl replace --raw /api/v1/namespaces/stuck-ns/finalize -f -
3. Pods
Manifest Completo de Pod
apiVersion: v1
kind: Pod
metadata:
name: meu-app
namespace: producao
labels:
app: meu-app
version: "1.2.0"
env: prod
annotations:
description: "Pod de exemplo com configurações completas"
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
spec:
# Node onde o pod vai rodar (opcional)
nodeName: worker-1
# Afinidade de node por labels
nodeSelector:
kubernetes.io/os: linux
node-type: high-memory
# Restart policy (Always | OnFailure | Never)
restartPolicy: Always
# Tempo para terminar graciosamente (padrão: 30s)
terminationGracePeriodSeconds: 60
# Service Account para acesso à API do K8s
serviceAccountName: meu-service-account
# Não montar token automático do service account
automountServiceAccountToken: false
# Security context no nível do Pod
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 2000
seccompProfile:
type: RuntimeDefault
# Volumes declarados para uso pelos containers
volumes:
- name: config-volume
configMap:
name: minha-config
- name: secret-volume
secret:
secretName: meu-secret
defaultMode: 0400 # permissões dos arquivos
- name: dados-temp
emptyDir: {}
- name: host-logs
hostPath:
path: /var/log/app
type: DirectoryOrCreate
# Init containers rodam antes dos containers principais (em ordem)
initContainers:
- name: wait-for-db
image: busybox:1.36
command: ['sh', '-c', 'until nc -z postgres-service 5432; do sleep 2; done']
- name: run-migrations
image: meu-app:1.2.0
command: ['./migrate.sh']
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-secret
key: url
containers:
- name: app
image: meu-app:1.2.0
imagePullPolicy: IfNotPresent # Always | Never | IfNotPresent
# Portas expostas (informativo, não cria regras de rede)
ports:
- name: http
containerPort: 8080
protocol: TCP
- name: metrics
containerPort: 9090
protocol: TCP
# Security context no nível do container (sobrescreve Pod-level)
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
# Variáveis de ambiente
env:
- name: APP_ENV
value: "production"
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name # metadata do próprio pod
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: CPU_REQUEST
valueFrom:
resourceFieldRef:
containerName: app
resource: requests.cpu
# Todas as chaves de um ConfigMap como env vars
envFrom:
- configMapRef:
name: minha-config
- secretRef:
name: meu-secret
# Montar volumes
volumeMounts:
- name: config-volume
mountPath: /etc/config
readOnly: true
- name: secret-volume
mountPath: /etc/secrets
readOnly: true
- name: dados-temp
mountPath: /tmp
# Recursos: requests (mínimo garantido) e limits (máximo permitido)
resources:
requests:
cpu: "250m" # 0.25 vCPU
memory: "256Mi"
limits:
cpu: "500m" # 0.5 vCPU
memory: "512Mi"
# Liveness probe: se falhar, container é reiniciado
livenessProbe:
httpGet:
path: /health/live
port: 8080
httpHeaders:
- name: Custom-Header
value: liveness
initialDelaySeconds: 30 # aguarda antes da primeira verificação
periodSeconds: 10 # intervalo entre verificações
timeoutSeconds: 5 # timeout de cada verificação
failureThreshold: 3 # falhas consecutivas para reiniciar
successThreshold: 1 # sucessos para considerar saudável
# Readiness probe: se falhar, pod é removido do Service (sem tráfego)
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
successThreshold: 1
# Startup probe: para apps lentos na inicialização
# Desativa liveness/readiness até o app iniciar
startupProbe:
httpGet:
path: /health/live
port: 8080
failureThreshold: 30 # 30 * 10s = 5 min para iniciar
periodSeconds: 10
# Lifecycle hooks
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo 'Container started' >> /tmp/log"]
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 10"] # drenagem de conexões
Pod Multi-Container (Sidecar Pattern)
apiVersion: v1
kind: Pod
metadata:
name: app-com-sidecar
spec:
volumes:
- name: logs-compartilhados
emptyDir: {}
containers:
# Container principal: aplicação
- name: app
image: meu-app:latest
volumeMounts:
- name: logs-compartilhados
mountPath: /var/log/app
# Sidecar: agente de logs (coleta e envia logs da app)
- name: log-shipper
image: fluent/fluent-bit:latest
volumeMounts:
- name: logs-compartilhados
mountPath: /var/log/app
readOnly: true
env:
- name: FLUENTBIT_OUTPUT
value: "elasticsearch"
# Sidecar: proxy (Envoy, Istio, etc.)
- name: envoy-proxy
image: envoyproxy/envoy:v1.28
ports:
- containerPort: 9901 # admin
- containerPort: 10000 # listener
Tipos de Probes
# HTTP GET probe
livenessProbe:
httpGet:
path: /healthz
port: 8080
scheme: HTTPS # HTTP (padrão) ou HTTPS
# TCP Socket probe (conexão na porta)
readinessProbe:
tcpSocket:
port: 5432
# Exec probe (exit code 0 = saudável)
livenessProbe:
exec:
command:
- /bin/sh
- -c
- "redis-cli ping | grep PONG"
# gRPC probe (requer servidor implementar Health protocol)
livenessProbe:
grpc:
port: 50051
service: liveness
4. Deployments
Manifest Completo de Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-api
namespace: producao
labels:
app: backend-api
version: "2.0.0"
spec:
# Número de réplicas desejadas
replicas: 3
# Quantas revisões manter no histórico (para rollback)
revisionHistoryLimit: 10
# Tempo mínimo que pod deve ficar Running antes de ser considerado Available
minReadySeconds: 5
# Seleciona os Pods gerenciados por este Deployment
selector:
matchLabels:
app: backend-api
# Estratégia de atualização
strategy:
type: RollingUpdate # ou Recreate
rollingUpdate:
maxSurge: 1 # pods extras acima do desejado durante update
maxUnavailable: 0 # pods que podem ficar indisponíveis (0 = zero-downtime)
# Template dos pods
template:
metadata:
labels:
app: backend-api # deve bater com selector.matchLabels
version: "2.0.0"
annotations:
# Forçar rollout quando configmap muda (add checksum)
checksum/config: "abc123def456"
spec:
# Anti-afinidade: evita dois pods no mesmo node
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: backend-api
topologyKey: kubernetes.io/hostname
containers:
- name: backend-api
image: meu-registry/backend-api:2.0.0
ports:
- containerPort: 8080
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "1000m"
memory: "1Gi"
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 20
periodSeconds: 5
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
Estratégia Recreate
# Derruba TODOS os pods antigos antes de criar os novos
# Causa downtime — usar apenas quando versões não podem coexistir
strategy:
type: Recreate
Estratégia Blue-Green (manual)
# Blue = versão atual (v1), Green = nova versão (v2)
# 1. Deploy da versão green com label separada
kubectl apply -f deployment-v2.yaml
# 2. Verificar se green está saudável
kubectl rollout status deployment/app-v2
# 3. Mudar o selector do Service para apontar para green
kubectl patch service meu-service -p '{"spec":{"selector":{"version":"v2"}}}'
# 4. Se der problema, reverter o selector
kubectl patch service meu-service -p '{"spec":{"selector":{"version":"v1"}}}'
# 5. Após validação, deletar blue
kubectl delete deployment app-v1
Horizontal Pod Autoscaler (HPA)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: backend-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: backend-api
minReplicas: 2
maxReplicas: 10
metrics:
# Escalar por CPU
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70 # manter CPU média em 70%
# Escalar por memória
- type: Resource
resource:
name: memory
target:
type: AverageValue
averageValue: 400Mi
# Escalar por métrica customizada (requer Prometheus Adapter)
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "100"
# Criar HPA de forma imperativa
kubectl autoscale deployment backend-api --cpu-percent=70 --min=2 --max=10
# Ver status do HPA
kubectl get hpa
kubectl describe hpa backend-hpa
5. Services
ClusterIP (padrão — acesso interno ao cluster)
apiVersion: v1
kind: Service
metadata:
name: backend-service
namespace: producao
spec:
type: ClusterIP # padrão, pode omitir
selector:
app: backend-api # roteia para pods com esta label
ports:
- name: http
port: 80 # porta do Service (usada pelos clientes)
targetPort: 8080 # porta no container
protocol: TCP
- name: metrics
port: 9090
targetPort: 9090
# Acessar ClusterIP de dentro do cluster:
# <service-name>.<namespace>.svc.cluster.local
curl http://backend-service.producao.svc.cluster.local/api/health
# Dentro do mesmo namespace:
curl http://backend-service/api/health
NodePort (acesso externo via porta do Node)
apiVersion: v1
kind: Service
metadata:
name: frontend-nodeport
spec:
type: NodePort
selector:
app: frontend
ports:
- port: 80
targetPort: 3000
nodePort: 30080 # porta no node (range: 30000-32767)
# se omitir nodePort, K8s escolhe automaticamente
# Acessar via IP de qualquer node + nodePort
curl http://<NODE_IP>:30080
LoadBalancer (provisionado pelo cloud provider)
apiVersion: v1
kind: Service
metadata:
name: api-loadbalancer
annotations:
# AWS NLB ao invés de CLB
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
# GCP: IP estático
cloud.google.com/load-balancer-type: "External"
spec:
type: LoadBalancer
selector:
app: api
ports:
- port: 443
targetPort: 8443
# IP externo estático (se disponível)
loadBalancerIP: "34.100.200.50"
ExternalName (alias DNS externo)
# Mapeia um Service para um DNS externo
# Útil para integrar serviços externos como se fossem internos
apiVersion: v1
kind: Service
metadata:
name: banco-externo
namespace: producao
spec:
type: ExternalName
externalName: banco.empresa.com.br # CNAME
Headless Service (sem ClusterIP)
# Sem proxy — DNS retorna IPs dos Pods diretamente
# Necessário para StatefulSets e service discovery direto
apiVersion: v1
kind: Service
metadata:
name: postgres-headless
spec:
clusterIP: None # headless
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432
# DNS resolve para lista de IPs dos pods:
# postgres-headless.namespace.svc.cluster.local → [10.0.0.1, 10.0.0.2, ...]
# Pods do StatefulSet recebem DNS individual:
# postgres-0.postgres-headless.namespace.svc.cluster.local
6. ConfigMaps e Secrets
ConfigMap — Criação
# Criação imperativa — literais
kubectl create configmap app-config \
--from-literal=APP_ENV=production \
--from-literal=LOG_LEVEL=info \
--from-literal=MAX_CONNECTIONS=100
# A partir de arquivo de properties
kubectl create configmap app-config --from-file=app.properties
# A partir de arquivo env
kubectl create configmap app-config --from-env-file=.env
# A partir de diretório (cada arquivo vira uma chave)
kubectl create configmap configs --from-file=./configs/
# Ver o configmap criado
kubectl get configmap app-config -o yaml
ConfigMap — Manifest
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: producao
data:
# Valores simples (chave-valor)
APP_ENV: "production"
LOG_LEVEL: "info"
MAX_CONNECTIONS: "100"
# Arquivo completo como valor (bloco literal YAML)
app.properties: |
server.port=8080
spring.datasource.url=jdbc:postgresql://postgres:5432/mydb
cache.ttl=3600
nginx.conf: |
server {
listen 80;
location / {
proxy_pass http://backend:8080;
}
}
Secret — Criação
# Criação imperativa
kubectl create secret generic db-secret \
--from-literal=username=admin \
--from-literal=password='S3cr3t!P@ss'
# A partir de arquivo (ex: certificado TLS)
kubectl create secret tls meu-tls \
--cert=tls.crt \
--key=tls.key
# Docker registry credentials
kubectl create secret docker-registry registry-credentials \
--docker-server=register.homelab-cloud.com \
--docker-username=deploy \
--docker-password=minhasenha \
--docker-email=deploy@empresa.com
Secret — Manifest
apiVersion: v1
kind: Secret
metadata:
name: db-secret
namespace: producao
type: Opaque # genérico (outros: kubernetes.io/tls, kubernetes.io/dockerconfigjson)
# Valores DEVEM ser base64 encoded
data:
username: YWRtaW4= # echo -n 'admin' | base64
password: UzNjcjN0IQ== # echo -n 'S3cr3t!' | base64
# Alternativa: stringData aceita valores em texto plano (K8s faz o encoding)
stringData:
api-key: "minha-api-key-secreta"
config.yaml: |
database:
host: postgres
password: senha123
Usando ConfigMaps e Secrets em Pods
spec:
volumes:
- name: config-vol
configMap:
name: app-config
# Montar apenas chaves específicas
items:
- key: app.properties
path: app.properties # nome do arquivo no mount
- key: nginx.conf
path: nginx/nginx.conf
- name: secret-vol
secret:
secretName: db-secret
defaultMode: 0400
containers:
- name: app
# Todas as chaves do ConfigMap como env vars
envFrom:
- configMapRef:
name: app-config
- secretRef:
name: db-secret
# Env vars individuais
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
- name: APP_ENV
valueFrom:
configMapKeyRef:
name: app-config
key: APP_ENV
# Montar como arquivos
volumeMounts:
- name: config-vol
mountPath: /etc/app/config
readOnly: true
- name: secret-vol
mountPath: /etc/secrets
readOnly: true
Sealed Secrets (GitOps-friendly)
# Instalar sealed-secrets controller
helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm install sealed-secrets sealed-secrets/sealed-secrets -n kube-system
# Instalar kubeseal CLI
# brew install kubeseal
# Criar SealedSecret (pode commitar no Git!)
kubectl create secret generic db-secret \
--from-literal=password=S3cr3t \
--dry-run=client -o yaml | \
kubeseal --format yaml > sealed-db-secret.yaml
# Aplicar (controller decripta e cria o Secret real)
kubectl apply -f sealed-db-secret.yaml
7. Ingress
nginx-ingress Controller — Instalação
# Via Helm (recomendado)
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--create-namespace \
--set controller.replicaCount=2
Ingress Básico
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: meu-ingress
namespace: producao
annotations:
# Especifica o IngressClass (nginx, traefik, etc.)
kubernetes.io/ingress.class: "nginx"
# Redirect HTTP → HTTPS
nginx.ingress.kubernetes.io/ssl-redirect: "true"
# Tamanho máximo do body
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
# Timeout
nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
nginx.ingress.kubernetes.io/proxy-connect-timeout: "60"
# Rate limiting
nginx.ingress.kubernetes.io/limit-rps: "10"
spec:
ingressClassName: nginx
# TLS
tls:
- hosts:
- app.meusite.com.br
- api.meusite.com.br
secretName: tls-meusite-secret # Secret tipo kubernetes.io/tls
rules:
# Host-based routing
- host: app.meusite.com.br
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 80
- host: api.meusite.com.br
http:
paths:
# Path-based routing
- path: /v1
pathType: Prefix
backend:
service:
name: api-v1-service
port:
number: 8080
- path: /v2
pathType: Prefix
backend:
service:
name: api-v2-service
port:
number: 8080
# Instalar cert-manager
helm repo add jetstack https://charts.jetstack.io
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--set installCRDs=true
# ClusterIssuer para Let's Encrypt
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: ops@empresa.com.br
privateKeySecretRef:
name: letsencrypt-prod-key
solvers:
- http01:
ingress:
class: nginx
---
# Ingress com TLS automático via cert-manager
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
tls:
- hosts:
- app.meusite.com.br
secretName: app-tls-cert # cert-manager cria e renova automaticamente
rules:
- host: app.meusite.com.br
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend
port:
number: 80
Anotações Nginx Comuns
annotations:
# Rewrite de URL
nginx.ingress.kubernetes.io/rewrite-target: /$2
# CORS
nginx.ingress.kubernetes.io/enable-cors: "true"
nginx.ingress.kubernetes.io/cors-allow-origin: "https://app.meusite.com.br"
# Autenticação básica
nginx.ingress.kubernetes.io/auth-type: basic
nginx.ingress.kubernetes.io/auth-secret: basic-auth-secret
nginx.ingress.kubernetes.io/auth-realm: "Área Restrita"
# Whitelist de IPs
nginx.ingress.kubernetes.io/whitelist-source-range: "10.0.0.0/8,192.168.0.0/16"
# Backend protocol
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
# Canary release (10% do tráfego para nova versão)
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "10"
# Session affinity (sticky sessions)
nginx.ingress.kubernetes.io/affinity: "cookie"
nginx.ingress.kubernetes.io/session-cookie-name: "route"
8. Volumes e Storage
emptyDir e hostPath
spec:
volumes:
# emptyDir: criado quando pod inicia, destruído quando pod termina
# Compartilhado entre containers do mesmo pod
- name: temp-storage
emptyDir: {}
- name: temp-memory
emptyDir:
medium: Memory # tmpfs (RAM) — mais rápido mas consome memória
sizeLimit: 512Mi
# hostPath: monta diretório do node no pod
# EVITAR em produção — acopla ao node, problema de segurança
- name: host-logs
hostPath:
path: /var/log/containers
type: Directory # Directory | File | DirectoryOrCreate | FileOrCreate | Socket
PersistentVolume (PV) — Provisionamento Manual
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-dados-postgres
spec:
capacity:
storage: 50Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce # RWO: um node por vez (discos)
# - ReadOnlyMany # ROX: múltiplos nodes leitura
# - ReadWriteMany # RWX: múltiplos nodes leitura/escrita (NFS, EFS)
persistentVolumeReclaimPolicy: Retain # Retain | Recycle | Delete
storageClassName: manual
hostPath: # para dev local (minikube)
path: /data/postgres
PersistentVolumeClaim (PVC)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-postgres-dados
namespace: producao
spec:
accessModes:
- ReadWriteOnce
storageClassName: ssd-premium # deve existir no cluster
resources:
requests:
storage: 20Gi
---
# Usando o PVC em um Pod
spec:
volumes:
- name: postgres-storage
persistentVolumeClaim:
claimName: pvc-postgres-dados
containers:
- name: postgres
image: postgres:16
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
StorageClass — Provisionamento Dinâmico
# StorageClass para AWS EBS (gp3)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ssd-premium
annotations:
storageclass.kubernetes.io/is-default-class: "false"
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer # aguarda pod ser agendado
reclaimPolicy: Delete
allowVolumeExpansion: true
parameters:
type: gp3
iops: "3000"
throughput: "125"
encrypted: "true"
---
# StorageClass para GCP Persistent Disk
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gcp-ssd
provisioner: pd.csi.storage.gke.io
parameters:
type: pd-ssd
replication-type: regional-pd
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
# Verificar StorageClasses disponíveis
kubectl get storageclass
kubectl get sc
# Ver PVs e PVCs
kubectl get pv
kubectl get pvc -A
# Expandir PVC (requer allowVolumeExpansion: true na StorageClass)
kubectl patch pvc meu-pvc -p '{"spec":{"resources":{"requests":{"storage":"50Gi"}}}}'
9. StatefulSets
Quando usar StatefulSet vs Deployment
| Característica | Deployment | StatefulSet |
|---|
| Identidade do Pod | Aleatória | Estável e previsível (pod-0, pod-1, …) |
| Ordem de criação | Simultânea | Sequencial (0 → 1 → 2) |
| Ordem de deleção | Simultânea | Reversa (2 → 1 → 0) |
| Storage | Compartilhado ou sem estado | Storage dedicado por pod |
| DNS | Service genérico | DNS individual por pod |
| Casos de uso | Aplicações stateless | Bancos, Kafka, ZooKeeper, Redis Cluster |
Manifest de StatefulSet — PostgreSQL com Replicação
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: banco
spec:
serviceName: postgres-headless # OBRIGATÓRIO: headless service para DNS
replicas: 3
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
# Init container para configurar réplicas
initContainers:
- name: init-postgres
image: postgres:16
command:
- bash
- "-c"
- |
set -ex
# Determinar se é master ou replica pelo ordinal
[[ $(hostname) =~ -([0-9]+)$ ]] || exit 1
ordinal=${BASH_REMATCH[1]}
if [[ $ordinal -eq 0 ]]; then
echo "Iniciando como PRIMARY"
cp /mnt/config-map/primary.conf /etc/postgresql/postgresql.conf
else
echo "Iniciando como REPLICA"
cp /mnt/config-map/replica.conf /etc/postgresql/postgresql.conf
fi
volumeMounts:
- name: config-map
mountPath: /mnt/config-map
- name: conf
mountPath: /etc/postgresql
containers:
- name: postgres
image: postgres:16
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: password
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
ports:
- containerPort: 5432
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
- name: conf
mountPath: /etc/postgresql
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "2"
memory: "4Gi"
readinessProbe:
exec:
command: ["pg_isready", "-U", "postgres"]
initialDelaySeconds: 10
periodSeconds: 5
volumes:
- name: config-map
configMap:
name: postgres-config
- name: conf
emptyDir: {}
# volumeClaimTemplates: cria um PVC dedicado por pod automaticamente
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: ssd-premium
resources:
requests:
storage: 20Gi
---
# Headless Service obrigatório para StatefulSet
apiVersion: v1
kind: Service
metadata:
name: postgres-headless
namespace: banco
spec:
clusterIP: None
selector:
app: postgres
ports:
- port: 5432
---
# Service regular para acesso ao primary (pod-0)
apiVersion: v1
kind: Service
metadata:
name: postgres-primary
namespace: banco
spec:
selector:
app: postgres
statefulset.kubernetes.io/pod-name: postgres-0
ports:
- port: 5432
# DNS dos pods do StatefulSet:
# postgres-0.postgres-headless.banco.svc.cluster.local
# postgres-1.postgres-headless.banco.svc.cluster.local
# postgres-2.postgres-headless.banco.svc.cluster.local
# Verificar pods e PVCs criados
kubectl get pods -n banco
kubectl get pvc -n banco
# Escalar StatefulSet (cria/destrói em ordem)
kubectl scale statefulset postgres --replicas=5 -n banco
10. DaemonSet, Jobs e CronJobs
DaemonSet — Um Pod por Node
# Casos de uso: log collectors, monitoring agents, network plugins, security scanners
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: node-exporter
namespace: monitoring
spec:
selector:
matchLabels:
app: node-exporter
template:
metadata:
labels:
app: node-exporter
spec:
# Tolerar todos os taints (inclusive master nodes)
tolerations:
- operator: Exists
effect: NoSchedule
# Acesso ao host (necessário para métricas do sistema)
hostNetwork: true
hostPID: true
containers:
- name: node-exporter
image: prom/node-exporter:v1.7.0
ports:
- containerPort: 9100
hostPort: 9100
securityContext:
privileged: true
volumeMounts:
- name: proc
mountPath: /host/proc
readOnly: true
- name: sys
mountPath: /host/sys
readOnly: true
volumes:
- name: proc
hostPath:
path: /proc
- name: sys
hostPath:
path: /sys
Job — Execução Única até Conclusão
# Casos de uso: migrations, processamento batch, backups
apiVersion: batch/v1
kind: Job
metadata:
name: db-migration
namespace: producao
spec:
# Número de conclusões desejadas
completions: 1
# Pods paralelos
parallelism: 1
# Tentativas máximas antes de falhar
backoffLimit: 4
# Deletar job automaticamente após conclusão (TTL em segundos)
ttlSecondsAfterFinished: 3600
# Timeout total
activeDeadlineSeconds: 600
template:
spec:
restartPolicy: OnFailure # Never | OnFailure (obrigatório para Jobs)
containers:
- name: migration
image: meu-app:2.0.0
command: ["./bin/run-migrations.sh"]
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-secret
key: url
---
# Job com paralelismo (work queue)
apiVersion: batch/v1
kind: Job
metadata:
name: batch-processor
spec:
completions: 10 # processar 10 itens
parallelism: 3 # 3 workers em paralelo
completionMode: Indexed # cada pod recebe um índice (JOB_COMPLETION_INDEX)
template:
spec:
restartPolicy: OnFailure
containers:
- name: worker
image: batch-worker:latest
env:
- name: WORKER_INDEX
valueFrom:
fieldRef:
fieldPath: metadata.annotations['batch.kubernetes.io/job-completion-index']
CronJob — Jobs Agendados
apiVersion: batch/v1
kind: CronJob
metadata:
name: backup-diario
namespace: producao
spec:
# Expressão cron: minuto hora dia-mês mês dia-semana
schedule: "0 2 * * *" # todo dia às 02:00 UTC
timezone: "America/Sao_Paulo" # K8s 1.27+
# Política quando job anterior ainda está rodando
concurrencyPolicy: Forbid # Allow | Forbid | Replace
# Histórico de jobs para manter
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
# Iniciar mesmo se perdeu a janela (em segundos)
startingDeadlineSeconds: 300
# Suspender temporariamente
suspend: false
jobTemplate:
spec:
backoffLimit: 2
ttlSecondsAfterFinished: 86400 # 24h
template:
spec:
restartPolicy: OnFailure
containers:
- name: backup
image: backup-tool:latest
command: ["/bin/sh", "-c", "pg_dump $DATABASE_URL | gzip | aws s3 cp - s3://backups/$(date +%Y%m%d).sql.gz"]
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-secret
key: url
# Criar job manual a partir de um CronJob (útil para testar ou executar manualmente)
kubectl create job backup-manual --from=cronjob/backup-diario
# Ver jobs e cronjobs
kubectl get jobs
kubectl get cronjobs
kubectl describe cronjob backup-diario
# Ver logs do job
kubectl logs job/backup-manual
11. RBAC
Conceitos
ServiceAccount — Identidade para Pods (como se autenticar na API do K8s)
Role — Permissões dentro de um namespace
ClusterRole — Permissões em todo o cluster (ou recursos não-namespacados)
RoleBinding — Liga um Role a um usuário/grupo/ServiceAccount (namespace)
ClusterRoleBinding — Liga um ClusterRole a um usuário/grupo/SA (cluster todo)
ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-service-account
namespace: producao
annotations:
# AWS IRSA: associar a uma IAM Role
eks.amazonaws.com/role-arn: arn:aws:iam::123456789:role/minha-role
---
# Usar o ServiceAccount em um Pod
spec:
serviceAccountName: app-service-account
Role e RoleBinding (namespace-scoped)
# Role: define permissões
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: producao
rules:
# Recursos de API
- apiGroups: [""] # "" = core API group
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "replicasets"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list"]
# Restringir a resources específicos pelo nome
resourceNames: ["app-config", "feature-flags"]
---
# RoleBinding: liga Role a um Subject
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: pod-reader-binding
namespace: producao
subjects:
# ServiceAccount
- kind: ServiceAccount
name: app-service-account
namespace: producao
# Usuário
- kind: User
name: developer@empresa.com
# Grupo
- kind: Group
name: developers
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
ClusterRole e ClusterRoleBinding (cluster-wide)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: node-viewer
rules:
# Recursos não-namespacados
- apiGroups: [""]
resources: ["nodes", "namespaces", "persistentvolumes"]
verbs: ["get", "list", "watch"]
# Permissão em tudo (admin)
# - apiGroups: ["*"]
# resources: ["*"]
# verbs: ["*"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: node-viewer-binding
subjects:
- kind: ServiceAccount
name: monitoring-agent
namespace: monitoring
roleRef:
kind: ClusterRole
name: node-viewer
apiGroup: rbac.authorization.k8s.io
# Verificar permissões
kubectl auth can-i get pods --as=system:serviceaccount:producao:app-service-account
kubectl auth can-i '*' '*' --as=admin-user
# Listar todas as permissões de um ServiceAccount
kubectl auth can-i --list --as=system:serviceaccount:producao:app-service-account
# Ver roles e bindings
kubectl get roles -n producao
kubectl get rolebindings -n producao
kubectl get clusterroles
kubectl get clusterrolebindings
# Roles built-in úteis
# cluster-admin: acesso total
# admin: acesso total no namespace
# edit: criar/atualizar recursos (sem RBAC)
# view: somente leitura
kubectl describe clusterrole edit
12. Network Policies
Por padrão: sem isolamento
Sem NetworkPolicy, todos os pods podem se comunicar com qualquer outro pod, em qualquer namespace.
Negar todo tráfego (deny-all)
# Passo 1: negar TODO tráfego de entrada para pods com label app=backend
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-ingress
namespace: producao
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
# Sem regras ingress = nega tudo
Permitir tráfego específico
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-network-policy
namespace: producao
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
- Egress
ingress:
# Permitir do frontend (mesmo namespace)
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
# Permitir do namespace de monitoring
- from:
- namespaceSelector:
matchLabels:
name: monitoring
podSelector:
matchLabels:
app: prometheus
ports:
- protocol: TCP
port: 9090
# Permitir de IP externo específico
- from:
- ipBlock:
cidr: 10.0.0.0/8
except:
- 10.0.1.0/24 # exceto esta sub-rede
egress:
# Permitir acesso ao banco (namespace banco)
- to:
- namespaceSelector:
matchLabels:
name: banco
podSelector:
matchLabels:
app: postgres
ports:
- protocol: TCP
port: 5432
# Permitir DNS (obrigatório se restringir egress)
- to:
- namespaceSelector: {}
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
Isolamento completo de namespace
# Isolar completamente um namespace (deny all ingress/egress)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: isolado
spec:
podSelector: {} # aplica a todos os pods do namespace
policyTypes:
- Ingress
- Egress
13. Helm
Conceitos
Chart — Pacote Helm (conjunto de templates K8s + values)
Release — Instância de um chart instalada no cluster
Repository — Coleção de charts (índice HTTP)
Values — Parâmetros de configuração do chart
Template — Manifests K8s com Go templating
Estrutura de um Chart
meu-chart/
├── Chart.yaml # metadados do chart (nome, versão, descrição)
├── values.yaml # valores padrão (substituíveis no install/upgrade)
├── charts/ # charts dependentes (subcharts)
├── templates/ # templates K8s
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── configmap.yaml
│ ├── _helpers.tpl # funções/partials reutilizáveis
│ ├── NOTES.txt # mensagem pós-install
│ └── tests/
│ └── test-connection.yaml
└── .helmignore # arquivos a ignorar no package
Comandos Essenciais
# Repositórios
helm repo add stable https://charts.helm.sh/stable
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update # atualizar índice de repos
helm repo list # listar repos adicionados
helm repo remove bitnami # remover repo
# Buscar charts
helm search repo postgres
helm search repo nginx --versions # todas as versões
helm search hub redis # buscar no Artifact Hub
# Inspecionar chart antes de instalar
helm show chart bitnami/postgresql
helm show values bitnami/postgresql # ver todos os values
helm show readme bitnami/postgresql
# Instalar
helm install minha-release bitnami/postgresql
helm install minha-release bitnami/postgresql --namespace banco --create-namespace
helm install minha-release ./meu-chart # chart local
# Instalar com values customizados
helm install minha-release bitnami/postgresql \
--set auth.username=myuser \
--set auth.password=mypass \
--set primary.persistence.size=20Gi
# Instalar com arquivo de values
helm install minha-release bitnami/postgresql -f values-prod.yaml
# Combinar múltiplos arquivos de values (último sobrescreve)
helm install minha-release bitnami/postgresql \
-f values-base.yaml \
-f values-prod.yaml \
--set auth.password=senhasecreta
# Upgrade (atualiza release existente)
helm upgrade minha-release bitnami/postgresql -f values-prod.yaml
# Instalar ou atualizar (idempotente)
helm upgrade --install minha-release bitnami/postgresql -f values-prod.yaml
# Rollback
helm rollback minha-release # volta para revisão anterior
helm rollback minha-release 2 # volta para revisão específica
helm history minha-release # ver histórico de revisões
# Ver releases instaladas
helm list
helm list -A # todos os namespaces
helm list -n banco
# Detalhes de uma release
helm status minha-release
# Deletar release
helm uninstall minha-release
helm uninstall minha-release --keep-history # mantém histórico para rollback
# Renderizar templates sem instalar (debug)
helm template minha-release bitnami/postgresql -f values.yaml
helm template minha-release ./meu-chart --debug
# Verificar chart (lint)
helm lint ./meu-chart
helm lint ./meu-chart -f values-prod.yaml
# Empacotar chart
helm package ./meu-chart
helm package ./meu-chart --version 1.0.0
# Download do chart
helm pull bitnami/postgresql
helm pull bitnami/postgresql --untar # extrair
helm pull bitnami/postgresql --version 13.2.1
Chart.yaml Exemplo
apiVersion: v2
name: meu-app
description: Aplicação backend com PostgreSQL
type: application # application | library
version: 1.2.0 # versão do chart (semver)
appVersion: "2.0.0" # versão da aplicação
dependencies:
- name: postgresql
version: "13.x.x"
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled
values.yaml Exemplo
# Número de réplicas
replicaCount: 3
image:
repository: meu-registry/backend-api
tag: "2.0.0"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
ingress:
enabled: true
className: nginx
host: api.meusite.com.br
tls: true
tlsSecretName: api-tls
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
postgresql:
enabled: true
auth:
username: appuser
database: myapp
primary:
persistence:
size: 10Gi
autoscaling:
enabled: false
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 70
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "meu-app.fullname" . }}
labels:
{{- include "meu-app.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "meu-app.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "meu-app.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: 8080
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- if .Values.postgresql.enabled }}
env:
- name: DATABASE_HOST
value: {{ include "meu-app.fullname" . }}-postgresql
{{- end }}
14. Namespaces e Quotas
Namespaces
# Criar namespace
kubectl create namespace staging
kubectl apply -f namespace.yaml
# Ver recursos em um namespace
kubectl get all -n staging
# Deletar namespace (e todos os recursos dentro)
kubectl delete namespace staging
# Definir namespace padrão no contexto
kubectl config set-context --current --namespace=producao
apiVersion: v1
kind: Namespace
metadata:
name: producao
labels:
name: producao
environment: production
team: backend
ResourceQuota — Limitar Consumo por Namespace
apiVersion: v1
kind: ResourceQuota
metadata:
name: quota-producao
namespace: producao
spec:
hard:
# Limitar objetos
pods: "20"
services: "10"
persistentvolumeclaims: "10"
configmaps: "30"
secrets: "30"
# Limitar recursos de compute
requests.cpu: "10" # total de CPU requests
requests.memory: "20Gi" # total de memory requests
limits.cpu: "20" # total de CPU limits
limits.memory: "40Gi" # total de memory limits
# Limitar por storage class
ssd-premium.storageclass.storage.k8s.io/requests.storage: "100Gi"
ssd-premium.storageclass.storage.k8s.io/persistentvolumeclaims: "5"
LimitRange — Defaults e Limites por Container
# Define limites default para containers que não especificam resources
apiVersion: v1
kind: LimitRange
metadata:
name: limitrange-default
namespace: staging
spec:
limits:
- type: Container
# Limits padrão (se container não especificar)
default:
cpu: "500m"
memory: "512Mi"
# Requests padrão
defaultRequest:
cpu: "100m"
memory: "128Mi"
# Máximo permitido por container
max:
cpu: "2"
memory: "2Gi"
# Mínimo permitido por container
min:
cpu: "50m"
memory: "64Mi"
- type: PersistentVolumeClaim
max:
storage: "10Gi"
min:
storage: "1Gi"
# Ver quotas e uso atual
kubectl get resourcequota -n producao
kubectl describe resourcequota quota-producao -n producao
# Ver limitranges
kubectl get limitrange -n staging
kubectl describe limitrange limitrange-default -n staging
Multi-tenant Pattern
# Namespace por time/cliente com labels padronizadas
apiVersion: v1
kind: Namespace
metadata:
name: team-backend
labels:
team: backend
cost-center: "123"
environment: production
---
# ResourceQuota para o time
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-quota
namespace: team-backend
spec:
hard:
requests.cpu: "4"
requests.memory: "8Gi"
limits.cpu: "8"
limits.memory: "16Gi"
pods: "10"
15. Troubleshooting
CrashLoopBackOff
Sintoma: Pod fica reiniciando em loop, STATUS = CrashLoopBackOff
Causa: Container termina com erro imediatamente após iniciar
# 1. Ver por que o container está crashando
kubectl describe pod meu-pod # ver Events e Last State
kubectl logs meu-pod # logs da execução atual
kubectl logs meu-pod --previous # logs da execução anterior (antes do crash)
# 2. Causas comuns e soluções:
# a) Erro na aplicação: verificar logs de stack trace
# b) ConfigMap/Secret ausente ou com chave errada
kubectl get configmap minha-config -o yaml
kubectl get secret meu-secret -o yaml | base64 -d
# c) Entrypoint incorreto ou permissão negada
kubectl exec -it meu-pod -- /bin/sh # se conseguir entrar antes do crash
# d) Probe muito agressiva matando o container
# Aumentar initialDelaySeconds e timeoutSeconds nas probes
# e) OOM: ver se aparece "OOMKilled" no describe
kubectl describe pod meu-pod | grep -A5 "Last State"
# Temporariamente: substituir command para debug
kubectl patch deployment meu-deploy -p '{"spec":{"template":{"spec":{"containers":[{"name":"app","command":["sleep","infinity"]}]}}}}'
kubectl exec -it <novo-pod> -- /bin/bash
# Agora pode inspecionar o container sem crash
ImagePullBackOff / ErrImagePull
Sintoma: Pod não consegue baixar a imagem
STATUS = ImagePullBackOff ou ErrImagePull
# 1. Verificar qual imagem está tentando usar
kubectl describe pod meu-pod | grep "Image:"
# 2. Causas e soluções:
# a) Nome/tag da imagem errado
kubectl set image deployment/meu-deploy app=meu-registry/app:v1.0.0
# b) Registry privado sem credentials
# Criar imagePullSecret
kubectl create secret docker-registry registry-creds \
--docker-server=register.homelab-cloud.com \
--docker-username=usuario \
--docker-password=senha
# Adicionar ao deployment ou ServiceAccount
kubectl patch deployment meu-deploy -p '{"spec":{"template":{"spec":{"imagePullSecrets":[{"name":"registry-creds"}]}}}}'
# Adicionar ao ServiceAccount (aplica a todos os pods que usam o SA)
kubectl patch serviceaccount default -p '{"imagePullSecrets":[{"name":"registry-creds"}]}'
# c) Problema de rede/DNS no node
# Testar conectividade do node
kubectl debug node/worker-1 -it --image=busybox
Pod em Pending (não é agendado)
Sintoma: Pod fica em STATUS = Pending indefinidamente
# 1. Ver eventos (sempre o primeiro passo)
kubectl describe pod meu-pod | tail -20
# 2. Causas e soluções:
# a) Recursos insuficientes no cluster
kubectl describe nodes | grep -A5 "Allocated resources"
kubectl top nodes
# Solução: adicionar nodes ou reduzir requests do pod
# b) PVC não consegue ser bound (pending storage)
kubectl get pvc
kubectl describe pvc meu-pvc
# Ver se StorageClass existe e provisioner está funcionando
# c) Node selector / affinity não satisfeita
kubectl get nodes --show-labels
# Verificar se os nodes têm os labels necessários
# d) Taints no node sem toleration no pod
kubectl describe nodes | grep Taints
# Adicionar toleration ou remover taint:
kubectl taint nodes worker-1 maintenance=true:NoSchedule-
# e) Cotas excedidas no namespace
kubectl describe resourcequota -n meu-namespace
OOMKilled
Sintoma: Container morto por falta de memória
kubectl describe pod: State: Terminated, Reason: OOMKilled
# 1. Confirmar
kubectl describe pod meu-pod | grep -A3 "Last State"
# OOMKilled = processo foi morto pelo kernel por exceder o memory limit
# 2. Ver uso atual de memória
kubectl top pod meu-pod --containers
kubectl top nodes
# 3. Soluções:
# a) Aumentar memory limit
kubectl patch deployment meu-deploy -p '{"spec":{"template":{"spec":{"containers":[{"name":"app","resources":{"limits":{"memory":"1Gi"}}}]}}}}'
# b) Investigar memory leak na aplicação
# Capturar heap dump, analisar com profiler
# c) Verificar se requests != limits (evita problema de over-commit)
# requests = reserva garantida, limits = teto máximo
# Recomendado: requests == limits para workloads críticos (Guaranteed QoS)
Pod Running mas não recebe tráfego
# 1. Verificar endpoints do Service (devem mostrar IPs dos pods)
kubectl get endpoints meu-service
kubectl describe endpoints meu-service
# Se endpoints vazio: problema no selector do Service
kubectl get service meu-service -o yaml | grep -A3 selector
kubectl get pods --show-labels # labels dos pods devem bater
# 2. Testar conectividade direta ao pod
kubectl port-forward pod/meu-pod 8080:8080
curl localhost:8080/health
# 3. Verificar readiness probe (pod pode estar Running mas não Ready)
kubectl get pods # READY deve ser 1/1
kubectl describe pod meu-pod | grep -A10 "Readiness"
# 4. Testar do inside do cluster
kubectl run debug --image=curlimages/curl --rm -it -- \
curl http://meu-service.namespace.svc.cluster.local/health
Problemas de DNS
# Testar resolução DNS de dentro do cluster
kubectl run dns-test --image=busybox --rm -it -- nslookup kubernetes.default
# Verificar CoreDNS
kubectl get pods -n kube-system | grep coredns
kubectl logs -n kube-system -l k8s-app=kube-dns
# Formato do DNS interno:
# <service>.<namespace>.svc.cluster.local
# <pod-ip-com-hifens>.<namespace>.pod.cluster.local
# Testar conectividade com service
kubectl run debug --image=nicolaka/netshoot --rm -it -- \
curl http://meu-service.producao.svc.cluster.local:8080/health
16. Boas Práticas
Labels e Annotations
# Labels recomendadas (Kubernetes standard labels)
metadata:
labels:
app.kubernetes.io/name: backend-api # nome da aplicação
app.kubernetes.io/instance: backend-api-prod # instância única
app.kubernetes.io/version: "2.0.0" # versão da app
app.kubernetes.io/component: api # componente (frontend, backend, database)
app.kubernetes.io/part-of: sistema-financeiro # sistema ao qual pertence
app.kubernetes.io/managed-by: helm # ferramenta que gerencia
# Annotations: metadados para ferramentas (não usados como seletores)
annotations:
deployment.kubernetes.io/revision: "5"
description: "API REST do sistema financeiro"
contact: "time-backend@empresa.com.br"
docs: "https://wiki.empresa.com.br/backend-api"
Resource Limits Obrigatórios
# SEMPRE definir resources para todos os containers em produção
# Sem limits: um container pode consumir todos os recursos do node
resources:
requests:
cpu: "250m" # valor que o scheduler usa para planejar alocação
memory: "256Mi" # garante que o pod terá esses recursos disponíveis
limits:
cpu: "500m" # container nunca usa mais que isso
memory: "512Mi" # se ultrapassar: OOMKilled
# Quality of Service (QoS) classes:
# Guaranteed: requests == limits (maior prioridade, nunca evicted antes)
# Burstable: requests < limits
# BestEffort: sem requests/limits (menor prioridade, primeiro a ser evicted)
Non-root Containers
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
securityContext:
allowPrivilegeEscalation: false # impede sudo/setuid
readOnlyRootFilesystem: true # filesystem imutável
capabilities:
drop: ["ALL"] # remover todas as capabilities Linux
add: [] # adicionar apenas o necessário
# MAU — tag 'latest' pode mudar silenciosamente
image: meu-app:latest
# BOM — tag imutável garante reprodutibilidade
image: meu-app:2.0.0
# MELHOR — digest garante exatamente o mesmo layer
image: meu-app@sha256:abc123def456...
Liveness vs Readiness vs Startup
# Startup probe: usar quando app demora para iniciar (>30s)
# Enquanto startup não passa, liveness/readiness são desativados
startupProbe:
httpGet:
path: /health/startup
port: 8080
failureThreshold: 30 # 30 tentativas
periodSeconds: 10 # a cada 10s = até 5 minutos
# Readiness probe: controla se pod recebe tráfego (Service)
# Falha = remove do endpoint (zero downtime durante sobrecarga temporária)
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 3
# Liveness probe: controla se pod é reiniciado
# Usar apenas para detectar deadlocks/estados irrecuperáveis
# NÃO deve falhar por dependências externas (BD lento, etc.)
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 3
PodDisruptionBudget — Alta Disponibilidade
# Garante número mínimo de pods durante manutenção
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: backend-pdb
spec:
# Mínimo de pods disponíveis (pode ser número ou percentagem)
minAvailable: 2
# OU máximo de pods indisponíveis
# maxUnavailable: 1
selector:
matchLabels:
app: backend-api
Checklist de Deploy em Produção
[ ] Imagem com tag versionada (não latest)
[ ] Resource requests e limits definidos
[ ] Liveness e readiness probes configuradas
[ ] Non-root user no securityContext
[ ] readOnlyRootFilesystem: true (ou justificativa)
[ ] Secrets em Secret objects (não ConfigMaps ou env plain)
[ ] Pelo menos 2 réplicas para HA
[ ] PodDisruptionBudget criado
[ ] HPA configurado para workloads variáveis
[ ] Pod anti-affinity para distribuir réplicas entre nodes
[ ] Labels padrão (app.kubernetes.io/*) em todos os recursos
[ ] NetworkPolicy restritiva (deny-all + allow específico)
[ ] ResourceQuota no namespace
[ ] Rollout strategy com maxUnavailable: 0 para zero-downtime
[ ] RBAC mínimo necessário (principle of least privilege)
[ ] Logging estruturado em JSON para integração com ELK/Loki
Comandos de Debug Rápido
# Pod temporário de debug no cluster
kubectl run debug \
--image=nicolaka/netshoot \
--rm -it \
--restart=Never \
-- bash
# Debug de um pod existente (K8s 1.23+)
kubectl debug meu-pod -it --image=busybox --share-processes --copy-to=debug-pod
# Copiar pod existente com novo comando para inspeção
kubectl debug meu-pod -it \
--image=ubuntu \
--copy-to=meu-pod-debug \
--container=app \
-- bash
# Ver todos os eventos ordenados por tempo
kubectl get events --sort-by='.lastTimestamp' -A
# Forçar rollout (reiniciar todos os pods de um deployment)
kubectl rollout restart deployment/meu-deploy
# Ver uso de recursos por namespace
kubectl top pods -A --sort-by=memory
# Listar todos os containers de todos os pods
kubectl get pods -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{range .spec.containers[*]}{.image}{"\n"}{end}{end}' -A
# Verificar se há pods em estado ruim
kubectl get pods -A | grep -v Running | grep -v Completed