Voltar para o blog

Como desenhar sandboxes efêmeros para agentes de IA no Kubernetes

Um agente que analisa repositórios, executa testes ou transforma arquivos precisa de ferramentas com efeitos reais: shell, interpretadores, acesso a dados e chamadas de rede. Neste projeto, a premissa de segurança é que o código executado pode ser hostil, mesmo quando a solicitação original é legítima.

A proposta é separar planejamento e execução. O agente solicita uma tarefa; uma plataforma confiável decide onde executá-la, quais capacidades conceder e quando encerrar o ambiente. O agente não recebe liberdade para criar infraestrutura arbitrária.

O objetivo não é apenas criar Pods rapidamente. É construir um serviço de execução com limites verificáveis de identidade, rede, recursos, duração e persistência.

1. O que caracteriza um sandbox efêmero

Neste artigo, um sandbox é um ambiente descartável criado para uma execução ou sessão, com política explícita e armazenamento de trabalho temporário. O modelo de linguagem pode permanecer fora dele: quem precisa do isolamento é, principalmente, o executor de ferramentas.

Um namespace organiza recursos e políticas, mas não constitui sozinho uma fronteira forte contra código hostil. Contêineres convencionais compartilham o kernel do nó; por isso, a escolha do runtime precisa acompanhar o modelo de ameaça. (kubernetes.io)

Adote três propriedades como requisitos de projeto:

  • Efemeridade: toda execução tem prazo, responsável e procedimento de destruição.
  • Autoridade limitada: o executor só acessa dados e operações necessários à tarefa.
  • Estado externo verificável: resultados aceitos, auditoria e estado operacional sobrevivem à exclusão do ambiente.

2. Comece pelo modelo de ameaça

Antes do YAML, documente ativos, adversários e consequências. Para uma plataforma que executa código de repositórios externos, proponho o seguinte contrato:

Ameaça consideradaControle proposto
Leitura de credenciaisNenhuma credencial administrativa no executor
ExfiltraçãoSaída bloqueada por padrão e gateway com autorização
Consumo ilimitadoQuotas, limites, prazo e concorrência por tenant
Contaminação entre tarefasWorkspace novo e nenhum volume gravável compartilhado
Ações externas indevidasFerramentas mediadas, idempotência e aprovação para operações sensíveis

Trate arquivos, dependências, respostas de ferramentas e saída do agente como conteúdo não confiável. A política de infraestrutura deve permanecer fora do alcance desses dados.

Para clientes mutuamente não confiáveis ou exigências regulatórias elevadas, considere clusters e contas de cloud separados, além do isolamento de runtime. A documentação de multitenancy distingue isolamento por namespace de modelos com separação mais forte. (kubernetes.io)

3. Arquitetura de referência

Fluxo proposto:

Cliente → API de execução → autorização e orçamento → fila → controller → namespace + políticas → Job → runtime isolado → resultado validado → limpeza

Separe dois planos:

  • Plano de controle confiável: autenticação, autorização, fila, reconciliação, auditoria e emissão de capacidades temporárias.
  • Plano de execução não confiável: processo do agente ou ferramenta, arquivos de entrada e workspace.

O controller é um componente da plataforma, não uma API fictícia do Kubernetes. Ele pode usar recursos nativos; uma CustomResourceDefinition, ou CRD, é opcional quando houver necessidade de uma API própria de sessões.

Objetos e responsabilidades

  • Job: acompanha uma tarefa finita. Não equivale a uma garantia de execução exatamente uma vez; o código precisa tolerar execuções duplicadas. (kubernetes.io)
  • RuntimeClass: seleciona uma configuração de runtime previamente instalada nos nós. (kubernetes.io)
  • ServiceAccount: representa a identidade Kubernetes do workload. Desabilitar a montagem automática do token evita disponibilizá-lo quando não é necessário. (kubernetes.io)
  • ResourceQuota: limita consumo agregado e quantidade de objetos no namespace; não reserva capacidade física. (kubernetes.io)
  • NetworkPolicy: controla tráfego selecionado quando a implementação de rede oferece suporte. (kubernetes.io)

Para sessões interativas, proponho um Pod com protocolo de sessão e prazo externo. Não trate um Deployment que recria processos indefinidamente como substituto automático de uma sessão descartável.

4. Escolha o isolamento antes de otimizar o startup

O gVisor implementa uma interface semelhante à do Linux em um kernel de aplicação em espaço de usuário. O Kata Containers utiliza máquinas virtuais leves e virtualização de hardware. São alternativas diferentes, com requisitos de compatibilidade que devem ser avaliados para o workload. (gvisor.dev)

AlternativaCritério de decisão proposto
Contêiner convencionalCódigo confiável, risco aceito e controles complementares
gVisorAvaliar para execução não confiável sem exigir uma VM tradicional por tarefa
Kata ContainersAvaliar quando a fronteira baseada em virtualização for um requisito
VM ou cluster dedicadoPriorizar quando a separação justificar operação e capacidade adicionais

Não escolha por um benchmark genérico. Teste chamadas de sistema, bibliotecas nativas, filesystem, rede, inicialização e necessidades de GPU com suas próprias tarefas.

RuntimeClass de exemplo

O manifesto pressupõe que o handler runsc já foi instalado e configurado no runtime dos nós. O label sandbox-runtime: gvisor é uma convenção local administrada pela plataforma.

apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: agent-gvisor
handler: runsc
scheduling:
  nodeSelector:
    sandbox-runtime: gvisor

Esse objeto seleciona o handler; não instala o gVisor. Adapte nome, handler e seleção de nós. Em produção, configure também overhead com valores medidos quando aplicável, para que o custo adicional do runtime participe do dimensionamento. (kubernetes.io)

Recomendo um pool dedicado, políticas de agendamento administradas pela plataforma e bloqueio de alterações desses controles pelo executor. Separação de agendamento complementa, mas não substitui, isolamento de segurança.

5. Exemplo completo: execução offline e descartável

O exemplo abaixo é didático: executa um comando inofensivo, sem rede, para validar o envelope operacional. Ele não implementa download de entradas, inferência nem upload de artefatos.

Pré-requisitos: runtime anterior funcional, nós compatíveis e plugin de rede com enforcement de NetworkPolicy. A plataforma deve criar e verificar o baseline antes de admitir código não confiável; a aceitação de um objeto pela API não demonstra, sozinha, que o dataplane já aplica a política.

apiVersion: v1
kind: Namespace
metadata:
  name: agent-run-7f3a
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/enforce-version: latest
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: sandbox
  namespace: agent-run-7f3a
automountServiceAccountToken: false
---
apiVersion: v1
kind: ResourceQuota
metadata:
  name: sandbox-budget
  namespace: agent-run-7f3a
spec:
  hard:
    pods: '2'
    count/jobs.batch: '2'
    requests.cpu: '1'
    limits.cpu: '2'
    requests.memory: 512Mi
    limits.memory: 1Gi
    requests.ephemeral-storage: 512Mi
    limits.ephemeral-storage: 1Gi
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny
  namespace: agent-run-7f3a
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress
---
apiVersion: batch/v1
kind: Job
metadata:
  name: agent-task
  namespace: agent-run-7f3a
spec:
  backoffLimit: 0
  activeDeadlineSeconds: 300
  ttlSecondsAfterFinished: 600
  template:
    metadata:
      labels:
        app: agent-sandbox
    spec:
      runtimeClassName: agent-gvisor
      serviceAccountName: sandbox
      automountServiceAccountToken: false
      restartPolicy: Never
      terminationGracePeriodSeconds: 10
      securityContext:
        runAsNonRoot: true
        runAsUser: 10000
        runAsGroup: 10000
        fsGroup: 10000
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: executor
          image: busybox:1.37.0
          command:
            - sh
            - -c
          args:
            - |
              set -eu
              echo sandbox-ready > /workspace/result.txt
              cat /workspace/result.txt
          workingDir: /workspace
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop:
                - ALL
          resources:
            requests:
              cpu: 250m
              memory: 128Mi
              ephemeral-storage: 128Mi
            limits:
              cpu: '1'
              memory: 512Mi
              ephemeral-storage: 512Mi
          volumeMounts:
            - name: workspace
              mountPath: /workspace
            - name: tmp
              mountPath: /tmp
      volumes:
        - name: workspace
          emptyDir:
            sizeLimit: 256Mi
        - name: tmp
          emptyDir:
            sizeLimit: 64Mi

O que adaptar e por quê

O perfil restricted exige controles de segurança de Pod; ele não impõe toda a arquitetura proposta. readOnlyRootFilesystem é uma escolha adicional. Em produção, fixe a versão do perfil na versão Kubernetes validada, em vez de deixar latest acompanhar mudanças automaticamente. (kubernetes.io)

backoffLimit: 0 evita retries de falhas pelo Job. activeDeadlineSeconds limita a duração ativa do Job, mas não representa o prazo completo desde a entrada na fila nem uma garantia física instantânea de desligamento. (kubernetes.io)

O Time to Live, ou TTL, de 600 segundos começa após a conclusão ou falha. Ele permite a limpeza do Job e seus dependentes, mas não remove automaticamente o namespace e todos os recursos independentes. (kubernetes.io)

O emptyDir acompanha a vida do Pod e não é armazenamento durável. Seu sizeLimit não deve ser interpretado como reserva de disco; monitore pressão de armazenamento no nó. (kubernetes.io)

Requests orientam o agendamento; limites de CPU podem causar throttling, e limites de memória podem resultar em encerramento por falta de memória. Ajuste também armazenamento efêmero para workspace e logs. (kubernetes.io)

A quota é por namespace: o orçamento global e por tenant continua sendo responsabilidade da plataforma. Substitua o namespace fixo por identificadores únicos. Para produção, use imagem aprovada por digest, dependências pré-instaladas e validação de assinatura conforme sua política.

6. Rede, identidade e ferramentas mediadas

O manifesto não autoriza DNS nem acesso externo. NetworkPolicies são aditivas: outra política permissiva pode ampliar o tráfego permitido. A API padrão não oferece autorização por domínio HTTP, e existem particularidades de tráfego com nós e implementação de rede. Valide explicitamente metadata de cloud e destinos internos; não trate o YAML como prova suficiente de bloqueio. (kubernetes.io) Para dimensionar IPs, DNS, conexões e egress desse tráfego mediado, veja Como dimensionar a rede do Kubernetes para sandboxes efêmeros de agentes de IA.

Para tarefas conectadas, recomendo liberar somente um gateway privado autenticado. Ele deve autorizar destinos e métodos, limitar bytes e tempo, validar redirecionamentos e rejeitar destinos internos indevidos. Se DNS for necessário, use resolução controlada e considere o risco de exfiltração pelo próprio DNS.

Não coloque uma chave ampla de modelo, GitHub ou cloud no processo não confiável. Prefira capacidades curtas, restritas a uma execução, ou chamadas mediadas por serviços confiáveis.

Role-Based Access Control, ou RBAC, deve separar controller e executor. Permissões para criar workloads podem permitir acesso indireto a Secrets e identidades; não aceite manifests arbitrários enviados pelo agente. (kubernetes.io)

No Amazon Elastic Kubernetes Service, ou Amazon EKS, EKS Pod Identity associa uma função de Identity and Access Management, ou IAM, a uma ServiceAccount. Neste desenho, prefira conceder essa identidade ao serviço confiável de artefatos, não ao sandbox. (docs.aws.amazon.com)

Use Transport Layer Security, ou TLS, entre serviços; mantenha rotação e expiração de credenciais. Secrets Kubernetes precisam de proteção de acesso e criptografia em repouso: codificação base64 não equivale a criptografia. (kubernetes.io)

7. Ciclo de vida e recuperação

Proponho registrar os estados queued → provisioning → running → collecting → terminating → cleaned, com caminhos de falha e cancelamento. São estados da aplicação, não fases nativas de Pod.

O controller deve reconciliar estado desejado e observado, usando chave de idempotência por execução. Um reconciliador independente procura ambientes vencidos e recursos órfãos.

Antes de marcar sucesso, exija confirmação durável do resultado. O serviço de artefatos deve impor tamanho máximo, validar caminhos, rejeitar extrações inseguras e tratar arquivos produzidos como não confiáveis. Não dependa de copiar arquivos depois que o Pod já foi removido.

A coleta de lixo Kubernetes utiliza relações de propriedade, com restrições de escopo. Um Job namespaced não deve ser usado como proprietário de um namespace, que é cluster-scoped. Planeje a remoção explícita do namespace e de recursos externos. (kubernetes.io)

Em partições de rede, apagar um objeto na API não prova que o processo deixou de existir no nó. Para operações sensíveis, combine prazo com credenciais expirantes, revogação no gateway e fencing do nó antes de considerar a execução fisicamente encerrada. (kubernetes.io)

8. Cenário corporativo: validar uma alteração de código

Problema: uma equipe quer que agentes analisem pull requests sem executar scripts externos dentro dos runners corporativos privilegiados.

Solução proposta: manter o planejamento fora do sandbox e criar uma execução isolada por tentativa de validação.

Implementação: um serviço confiável prepara um snapshot do repositório, sem credenciais; o executor recebe somente os arquivos necessários e dependências aprovadas. Testes produzem um relatório. Outro serviço valida o artefato e publica o comentário na plataforma de código.

Resultado esperado: reduzir o alcance de uma execução comprometida e facilitar auditoria e reprodução. Isso é uma expectativa arquitetural, não um benchmark ou relato de resultado medido.

Use fixtures sintéticas para PostgreSQL ou Redis quando os testes precisarem desses serviços. Evite reutilizar bancos de produção ou caches graváveis entre clientes. Uma aprovação humana deve continuar separada da execução quando a ação envolver deploy, exclusão ou alteração sensível.

9. Produção: disponibilidade, capacidade e custos

Minha recomendação é manter múltiplas réplicas da API e dos gateways, distribuição entre zonas e reconciliação idempotente. Para controllers com escrita concorrente, adote eleição de líder ou coordenação equivalente. Proteja o banco de estado com backup e testes de restauração.

Aplique readiness, startup e liveness probes aos serviços persistentes conforme sua função. O Job curto do exemplo não precisa de endpoint HTTP artificial: conclusão, deadline e progresso externo são os sinais relevantes. Probes possuem semânticas diferentes e não devem ser adicionadas mecanicamente. (kubernetes.io)

Use PodDisruptionBudget nos serviços adequados, lembrando que ele limita determinadas interrupções voluntárias, não elimina falhas involuntárias. Não o trate como garantia de conclusão de um Job. (kubernetes.io)

Capacity planning

Como exemplo hipotético, 60 tarefas por minuto com duração média de dois minutos implicam aproximadamente 120 execuções simultâneas em regime estável. Com requests de 250m e 128Mi, isso representa 30 CPUs e 15Gi de memória solicitadas, antes de overhead e componentes da plataforma.

Dimensione também rajadas, percentis de duração, imagens, disco, endereços IP e objetos na API. Imponha limites de concorrência antes de criar milhares de Pods pendentes.

Autoscaling de nós considera necessidades de agendamento e restrições dos Pods. Ele complementa, mas não substitui, controle de admissão e capacidade mínima para absorver a inicialização. (kubernetes.io)

Para FinOps, acompanhe custo por tarefa aceita e por tarefa concluída. Inclua capacidade ociosa, egress, armazenamento, logs e retries. Minha política sugerida é reservar capacidade estável para serviços críticos e avaliar Spot apenas para tarefas recuperáveis, com prazo que comporte interrupções.

Prefira gateways compartilhados a um load balancer por sandbox. Use caches imutáveis e identificados por conteúdo; não reaproveite ambientes usados entre tenants apenas para reduzir latência.

10. Observabilidade e troubleshooting

OpenTelemetry oferece sinais como métricas, logs e traces. Instrumente a plataforma para correlacionar admissão, provisionamento, execução e limpeza. (opentelemetry.io)

Proponho acompanhar:

  • latência de fila e tempo até o primeiro comando;
  • throughput, taxa de sucesso e falhas por categoria;
  • CPU, memória, disco, throttling e reinicializações;
  • duração da coleta e idade dos ambientes expirados;
  • recusas de política, tráfego negado e saturação do gateway.

Defina objetivos de nível de serviço, ou SLOs, separados para disponibilidade da API, início da execução e limpeza. Use identificadores de execução em logs e traces, evitando uma série temporal por execução. Não registre prompts completos ou credenciais por padrão.

Para investigação, os comandos abaixo consultam estado, eventos e logs. Execute-os com uma identidade operacional autorizada, não dentro do sandbox. (kubernetes.io)

kubectl get runtimeclass agent-gvisor
kubectl get nodes -l sandbox-runtime=gvisor
kubectl get jobs,pods -n agent-run-7f3a
kubectl describe job agent-task -n agent-run-7f3a
kubectl describe resourcequota sandbox-budget -n agent-run-7f3a
kubectl get networkpolicy -n agent-run-7f3a
kubectl logs job/agent-task -n agent-run-7f3a
kubectl get events -n agent-run-7f3a --sort-by=.metadata.creationTimestamp

Use este roteiro operacional, adaptando a correção à evidência coletada:

SintomaPossível causaInvestigaçãoCorreção proposta
Pod em PendingCapacidade ou seleção de nós incompatívelEventos e labels dos nósAjustar pool, requests ou seleção
FailedCreatePodSandBoxHandler ausente ou falha no runtimeEventos e logs do runtime no nóCorrigir instalação; não remover isolamento como atalho
Job sem PodQuota ou admissão recusando criaçãoDescrição do Job e eventosCorrigir orçamento ou template
OOMKilledPico acima do orçamentoEstado terminado e métricasReduzir concorrência ou revisar limite
Downloads falhamEgress bloqueado intencionalmentePolíticas e logs do gatewayUsar dependências pré-instaladas ou rota aprovada
Namespace em TerminatingFinalizer ou recurso dependente pendenteCondições do namespace e recursos restantesRecuperar o controller responsável antes de remover finalizers

11. Desenvolvimento, manutenção e critérios de aceitação

Em desenvolvimento, uma réplica e observabilidade simplificada podem bastar para arquivos sintéticos. Isso não justifica executar conteúdo hostil sem isolamento.

Em produção, exija redundância do plano de controle, limites, alertas, trilha de auditoria, backup do estado durável e testes de recuperação. Não faça backup indiscriminado dos workspaces descartáveis: retenha somente o necessário conforme a política de dados.

Gerencie templates e políticas por GitOps e integração e entrega contínuas, ou CI/CD. Deixe o controller gerenciar execuções efêmeras, sem um reconciliador GitOps recriando Jobs já limpos.

Faça upgrades em um pool canário com testes de compatibilidade, isolamento, latência e recuperação. Para rollback, direcione novas tarefas à combinação anterior de imagem, runtime e política validada; não tente transformar um Job concluído em uma execução anterior.

Antes da liberação, recomendo testes que comprovem:

  1. Acesso negado ao cluster, metadata e destinos não autorizados.
  2. Impossibilidade de escolher ServiceAccounts, volumes ou privilégios arbitrários.
  3. Encerramento e revogação após timeout ou cancelamento.
  4. Recuperação após morte do controller e perda de nó.
  5. Ausência de arquivos entre execuções e limpeza de recursos órfãos.
  6. Idempotência de publicações e outras ações externas.

Evite credenciais hardcoded, Docker socket, hostPath, privilégios amplos, saída irrestrita e ausência de orçamento. Não confunda ttlSecondsAfterFinished com limpeza completa nem retries com processamento exatamente uma vez.

12. Quando utilizar — e quando simplificar

Esta arquitetura faz sentido quando há execução recorrente de código não confiável, múltiplos usuários, necessidade de auditoria e equipe capaz de operar a plataforma.

Para automações pequenas e confiáveis, um worker restrito pode ser suficiente. Para fronteiras regulatórias rígidas, uma VM dedicada ou ambiente separado pode ser mais apropriado. Um serviço gerenciado de execução também merece avaliação, considerando isolamento documentado, localização dos dados, limites, custos e dependência do fornecedor.

A decisão deve equilibrar risco e capacidade operacional, não apenas aproveitar um cluster existente.

Resumo prático

  • Separe decisão confiável de execução não confiável.
  • Escolha isolamento pelo modelo de ameaça.
  • Limite identidade, rede, recursos, duração e concorrência.
  • Persista resultados aceitos antes da limpeza.
  • Trate falhas, duplicação e expiração como parte do contrato.
  • Teste os controles no dataplane, não apenas a validade do YAML.

Conclusão

Sandboxes efêmeros devem ser tratados como um produto de Platform Engineering: uma interface simples para consumidores, sustentada por políticas e operação rigorosas.

DevOps fornece entrega reproduzível; SRE, ou Site Reliability Engineering, transforma disponibilidade, recuperação e limpeza em objetivos verificáveis; a arquitetura cloud estabelece fronteiras de identidade e capacidade. O Kubernetes coordena esses mecanismos, mas a segurança depende do contrato completo entre autorização, runtime, rede e ciclo de vida.

Quer aplicar isso no seu ambiente?

Agende uma conversa de 30 minutos sem compromisso. Eu olho o seu cenário e digo por onde começar.

Agendar uma conversa

Receba os novos artigos por e-mail

Um e-mail quando sair artigo novo sobre infraestrutura, backend e IA. Sem spam, cancele quando quiser.