mozak.tech Engenharia de IA Corporativa 10%

Parte I — Operações e Infraestrutura Potencializadas por IA

1.3 — Diagnóstico e Operação de Kubernetes com Agentes de IA (Kubernetes + IA)

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 manifest

O 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.yaml

No 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 TLS

Padrõ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 atualizado

Padrã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:

    # create

Padrã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':

        break

O starter usa range(8). Sem limite, um agente em loop (tool retorna erro → agente retry → mesmo erro) nunca termina.

⚗ Laboratório prático — mozak.tech
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.