Objetivo da Aula
Ao concluir esta aula, você será capaz de:
Construir um agente LLM que diagnostica e opera workloads Kubernetes via mock kubectl
Implementar ajustar_hpa() que atualiza CLUSTER_STATE['hpa'] corretamente
Implementar gerar_manifest_ingress() que produz YAML válido com annotations nginx
Analisar o padrão “inspecionar antes de agir” em agentes de operações
Conectar o loop de diagnóstico ReAct com as 6 tools do agente K8s
Por que isso importa
Kubernetes é o runtime padrão para workloads em nuvem, mas sua operação exige memorizar dezenas de comandos (kubectl get, kubectl describe, kubectl logs, kubectl rollout). Um agente LLM que opera K8s converte linguagem natural em ações específicas — “O auth-service está com CrashLoopBackOff, diagnostique” → inspeção automática de pods, logs e HPA → fix recomendado.
O caso de uso é real: equipes de SRE usam agentes para triagem inicial de alertas, identificação de causa raiz e ajuste de configurações sem intervenção manual para incidentes P2/P3.
Conceitos Fundamentais
Estado do Cluster como Dict Python
O lab usa um CLUSTER_STATE dict que replica a estrutura do cluster real:
CLUSTER_STATE = {
'namespaces': ['default', 'prod', 'staging', 'monitoring'],
'deployments': {
'prod': [
{'name': 'api-gateway', 'replicas': 3, 'ready': 3, ...},
{'name': 'auth-service', 'replicas': 2, 'ready': 1, ...}, # ← ready < replicas
],
},
'pods': {
'prod': [
{'name': 'auth-service-abc12', 'status': 'CrashLoopBackOff', 'restarts': 15},
],
},
'hpa': {
'prod': [
{'name': 'api-gateway', 'min_replicas': 2, 'max_replicas': 10, 'current': 3, 'cpu_target': 70},
],
},
}As tools kubectl_get, kubectl_scale, kubectl_apply, kubectl_logs operam sobre esse estado — simulando o comportamento real do kubectl.
TODO 1: ajustar_hpa(deployment, namespace, min_replicas, max_replicas, cpu_target_percent)
def ajustar_hpa(deployment: str, namespace: str = 'prod', min_replicas: int = 2,
max_replicas: int = 10, cpu_target_percent: int = 70) -> str:
hpas = CLUSTER_STATE['hpa'].get(namespace, [])
# busca HPA existente para o deployment
hpa_existente = next((h for h in hpas if h['name'] == deployment), None)
if hpa_existente:
# atualiza HPA existente
hpa_existente['min_replicas'] = min_replicas
hpa_existente['max_replicas'] = max_replicas
hpa_existente['cpu_target'] = cpu_target_percent
return (
f'HPA {deployment} atualizado: '
f'min={min_replicas} max={max_replicas} cpu={cpu_target_percent}%'
)
else:
# cria novo HPA para o deployment
hpas.append({
'name': deployment,
'min_replicas': min_replicas,
'max_replicas': max_replicas,
'current': min_replicas,
'cpu_target': cpu_target_percent,
})
CLUSTER_STATE['hpa'][namespace] = hpas
return (
f'HPA criado para {deployment}: '
f'min={min_replicas} max={max_replicas} cpu={cpu_target_percent}%'
)Por que mutar CLUSTER_STATE em vez de retornar só a string? Porque o agente pode consultar listar_recursos(recurso='hpa') em seguida — e precisa ver o estado atualizado. A mutação do dict mantém consistência entre tool calls dentro do mesmo loop.
TODO 2: gerar_manifest_ingress(servico, host, namespace, porta, tls)
def gerar_manifest_ingress(servico: str, host: str, namespace: str = 'prod',
porta: int = 80, tls: bool = True) -> str:
manifest = f"""apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {servico}-ingress
namespace: {namespace}
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/ssl-redirect: "{'true' if tls else 'false'}"
nginx.ingress.kubernetes.io/proxy-connect-timeout: "30"
nginx.ingress.kubernetes.io/proxy-send-timeout: "60"
nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
spec:
rules:
- host: {host}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {servico}
port:
number: {porta}"""
if tls:
manifest += f"""
tls:
- hosts:
- {host}
secretName: {servico}-tls-cert"""
return manifestO manifest gerado é YAML válido compatível com kubectl apply. A tool aplicar_manifest no lab chama kubectl_apply(manifest) que retorna dry-run confirmando o tamanho do manifest.
Aprofundamento Técnico
Padrão “Inspecionar Antes de Agir”
O system prompt do agente define:
system='Você é um SRE especialista em Kubernetes. Diagnostique e resolva problemas usando as tools. Sempre inspecione antes de agir.'Na prática, isso gera o seguinte trace:
1. listar_recursos(recurso='pods', namespace='prod')
→ [auth-service-abc12: CrashLoopBackOff, restarts=15]
2. obter_logs(pod='auth-service-abc12')
→ 'FATAL: OOM: kill process 1234 (node) score 900'
3. listar_recursos(recurso='deployments', namespace='prod')
→ [auth-service: replicas=2, ready=1, memory_request='128Mi']
4. [decide: OOMKilled por limite de memória muito baixo]
5. ajustar_hpa(deployment='auth-service', min_replicas=3, max_replicas=6, cpu_target_percent=60)
→ HPA criado para auth-service: min=3 max=6 cpu=60%
6. end_turn: "Causa raiz: OOMKilled por memory_request=128Mi insuficiente..."O agente não age imediatamente — inspeciona primeiro. Isso evita o anti-padrão de escalar sem entender a causa.
Por que ready < replicas é Indicador de Problema
No CLUSTER_STATE:
{'name': 'auth-service', 'replicas': 2, 'ready': 1, ...}
ready: 1 com replicas: 2 significa que 1 pod está em estado não-Running (CrashLoopBackOff, OOMKilled, Pending). O agente que entende K8s vai imediatamente buscar os pods desse deployment e checar os logs.Ingress com TLS: Secrets Separados
O manifest gerado referencia {servico}-tls-cert como secret. Em produção, você precisa:
# Criar secret com certificado
kubectl create secret tls auth-service-tls-cert \
--cert=tls.crt \
--key=tls.key \
-n prod
# Ou usar cert-manager (automático)
kubectl apply -f cert-manager-issuer.yamlNo lab, o dry-run aceita o manifest sem verificar se o secret existe.
Exemplos Anotados
Exemplo 1: Diagnóstico Completo de OOMKilled
resultado = agente_k8s(
'O auth-service em prod está com pods em CrashLoopBackOff. '
'Diagnostique a causa e sugira o fix.'
)
# Trace esperado:
# → listar_recursos(recurso='pods', namespace='prod')
# ← [{'name': 'auth-service-abc12', 'status': 'CrashLoopBackOff', 'restarts': 15}]
# → obter_logs(pod='auth-service-abc12')
# ← 'FATAL: OOM: kill process 1234 (node) score 900'
# → listar_recursos(recurso='deployments', namespace='prod')
# ← [{'name': 'auth-service', 'memory_request': '128Mi'}]
# end_turn: "Causa: OOMKilled. memory_request=128Mi insuficiente. Recomendo 256Mi."Exemplo 2: TODO 1 e TODO 2 em Ação
# TODO 1: ajustar_hpa com criação no CLUSTER_STATE
ajustar_hpa('auth-service', 'prod', min_replicas=3, max_replicas=8, cpu_target_percent=60)
# → CLUSTER_STATE['hpa']['prod'] agora contém auth-service
# → 'HPA criado para auth-service: min=3 max=8 cpu=60%'
# Confirmação: listar HPA após ajuste
hpas = kubectl_get('hpa', 'prod')
print([h['name'] for h in hpas])
# → ['api-gateway', 'auth-service'] ← auth-service agora aparece
# TODO 2: gerar Ingress YAML
manifest = gerar_manifest_ingress('auth-service', 'auth.empresa.com', 'prod', 443, True)
print(manifest)
# → YAML completo com annotations nginx e bloco TLSPadrões e Armadilhas
Padrões
Padrão 1: Mutar CLUSTER_STATE para consistência entre tools
# Na ajustar_hpa: mutação direta do dict
hpa_existente['min_replicas'] = min_replicas
# Próxima tool listar_recursos(recurso='hpa') verá o estado atualizadoPadrão 2: next((d for d in lista if d['name'] == nome), None) — busca por nome
hpa = next((h for h in hpas if h['name'] == deployment), None)
if hpa:
# update
else:
# createPadrão idiomático Python para buscar em lista de dicts — mais legível que filter + index.
Padrão 3: YAML com f-string multiline
manifest = f"""apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {servico}-ingress"""f-strings multilinha geram YAML exato. Cuidado com a indentação — espaços no f-string viram espaços no YAML.
Armadilhas
⚠️ Armadilha 1: Criar HPA sem verificar se deployment existe
# Antes de criar HPA, verificar se o deployment existe
dep = kubectl_get('deployments', namespace, deployment)
if not dep:
return f'Deployment {deployment} não encontrado em {namespace}'⚠️ Armadilha 2: YAML com indentação inconsistente
# RUIM: indentação mista spaces/tabs vai quebrar kubectl apply
manifest = f"""apiVersion: networking.k8s.io/v1
\tkind: Ingress""" # ← tab!
# CORRETO: sempre 2 ou 4 espaços, consistente
manifest = f"""apiVersion: networking.k8s.io/v1
kind: Ingress"""⚠️ Armadilha 3: Loop do agente sem limite vai consumir tokens infinitamente
for _ in range(8): # ← SEMPRE ter limite
response = client.messages.create(...)
if response.stop_reason == 'end_turn':
breakO starter usa range(8). Sem limite, um agente em loop (tool retorna erro → agente retry → mesmo erro) nunca termina.
Se não for realizar o laboratório, pule para o próximo capítulo.
Ponte para o Lab
2 TODOs no starter.py:
TODO 1 — na função ajustar_hpa:
if nome == 'ajustar_hpa':
dep = args['deployment']
ns = args.get('namespace', 'prod')
min_r = args.get('min_replicas', 2)
max_r = args.get('max_replicas', 10)
cpu = args.get('cpu_target_percent', 70)
hpas = CLUSTER_STATE['hpa'].setdefault(ns, [])
hpa = next((h for h in hpas if h['name'] == dep), None)
if hpa:
hpa.update({'min_replicas': min_r, 'max_replicas': max_r, 'cpu_target': cpu})
return f'HPA {dep} atualizado: min={min_r} max={max_r} cpu={cpu}%'
else:
hpas.append({'name': dep, 'min_replicas': min_r, 'max_replicas': max_r,
'current': min_r, 'cpu_target': cpu})
return f'HPA criado para {dep}: min={min_r} max={max_r} cpu={cpu}%'TODO 2 — na função gerar_manifest_ingress:
if nome == 'gerar_manifest_ingress':
return gerar_manifest_ingress(
args['servico'], args['host'],
args.get('namespace', 'prod'),
args.get('porta', 80),
args.get('tls', True)
)(Onde gerar_manifest_ingress é a função com o f-string YAML mostrada na seção de TODOs acima)
Rode com python starter.py e observe o agente diagnosticar o CrashLoopBackOff e ajustar o HPA automaticamente.
Agora você está pronto para o lab.