Voltar para o blog

GKE Agent Sandbox para Reinforcement Learning: gVisor, warm pools e cold-start

Introdução

Workloads de Reinforcement Learning (RL) e agentes de Inteligência Artificial apresentam um problema diferente do encontrado em aplicações tradicionais.

Uma API convencional executa código conhecido, empacotado e validado durante o pipeline de CI/CD. Em uma plataforma de agentes, o workload pode executar scripts produzidos dinamicamente, bibliotecas escolhidas em runtime, ambientes de simulação e até código gerado por um Large Language Model (LLM).

Isso altera o modelo de ameaça.

Executar esse tipo de código diretamente em um Pod comum significa compartilhar o kernel do nó Kubernetes com outros workloads. Mesmo utilizando namespaces Linux, seccomp e capabilities, ainda existe uma superfície de ataque importante entre container e kernel.

O Google Kubernetes Engine (GKE) Agent Sandbox foi criado justamente para ambientes desse tipo. Ele adiciona gerenciamento de ciclo de vida de sandboxes sobre Kubernetes e utiliza isolamento baseado em gVisor, permitindo criar ambientes efêmeros ou persistentes para agentes, notebooks, código não confiável e aplicações de IA.

O Agent Sandbox é diferente do recurso tradicional GKE Sandbox:

GKE Sandbox
    ↓
gVisor / isolamento de runtime

GKE Agent Sandbox
    ↓
Controller + CRDs + lifecycle management
    ↓
GKE Sandbox / gVisor

Atualmente, o Agent Sandbox utiliza recursos Kubernetes como Sandbox, SandboxTemplate, SandboxWarmPool e SandboxClaim. O modelo de warm pools permite manter ambientes previamente inicializados para reduzir significativamente o tempo percebido de provisionamento.

Neste laboratório vamos montar uma arquitetura semelhante à seguinte:

                        ┌──────────────────────┐
                        │ RL Orchestrator      │
                        │ API / Trainer        │
                        └──────────┬───────────┘
                                   │
                           SandboxClaim
                                   │
                                   ▼
                       ┌──────────────────────┐
                       │ Agent Sandbox        │
                       │ Controller           │
                       └──────────┬───────────┘
                                  │
                    ┌─────────────┴──────────────┐
                    │ SandboxWarmPool            │
                    │ SandboxTemplate            │
                    └─────────────┬──────────────┘
                                  │
                                  ▼
                     ┌─────────────────────────┐
                     │ Pod / RuntimeClass      │
                     │ gVisor                  │
                     └─────────────┬───────────┘
                                   │
                         GKE sandbox node pool
                                   │
                       ┌───────────┴───────────┐
                       │ Gymnasium Environment │
                       │ RL Agent / Python     │
                       └───────────────────────┘

A proposta não é apenas executar CartPole. O objetivo é construir um modelo que possa posteriormente receber ambientes de avaliação, código gerado por agentes e workloads de RL distribuído.

O que é o GKE Agent Sandbox

O GKE Agent Sandbox é uma camada de gerenciamento de ambientes isolados executados em GKE.

Um Sandbox representa essencialmente um ambiente Kubernetes isolado e gerenciado por um controller. O recurso permite controlar identidade, armazenamento, lifecycle e exposição de rede sem obrigar a aplicação que solicita o ambiente a manipular Pods diretamente.

O projeto utiliza Custom Resource Definitions (CRDs) Kubernetes. Entre os principais recursos estão:

Sandbox

Representa o ambiente isolado propriamente dito. É responsável pelo Pod que executará o workload.

SandboxTemplate

Funciona como blueprint. Em vez de repetir a definição completa do Pod cada vez que um agente precisa de ambiente, podemos criar python-rl-template e reutilizá-lo para centenas de sandboxes.

SandboxWarmPool

Mantém uma quantidade determinada de ambientes previamente inicializados. Por exemplo, replicas: 5 significa que o controller tenta manter cinco sandboxes disponíveis para uso imediato.

SandboxClaim

Representa a solicitação de um ambiente. A aplicação solicita "preciso de um sandbox" e o controller associa um Sandbox disponível no warm pool.

A documentação atual do Agent Sandbox recomenda esse modelo quando startup rápido é importante.

Por que isso é interessante para Reinforcement Learning

Um pipeline simplificado de RL pode ser representado assim:

Policy
  │
  ▼
Action
  │
  ▼
Environment
  │
  ▼
Observation + Reward
  │
  ▼
Trainer
  │
  ▼
Nova Policy

O problema surge quando queremos executar milhares de ambientes independentes:

environment-0001
environment-0002
environment-0003
...
environment-5000

Cada ambiente pode executar:

  • Python;
  • simuladores;
  • ferramentas externas;
  • código produzido pelo agente;
  • dependências diferentes;
  • scripts potencialmente não confiáveis.

O Agent Sandbox transforma esses environments em unidades isoladas de infraestrutura:

Trainer
   │
   ├── SandboxClaim → Gym Env 001
   ├── SandboxClaim → Gym Env 002
   ├── SandboxClaim → Gym Env 003
   └── SandboxClaim → Gym Env N

Isso separa claramente o control plane de treinamento da execução dos ambientes.

gVisor e o runtime sandboxed

Containers tradicionais compartilham o kernel Linux do host:

Container
   ↓
Linux namespaces
   ↓
Host Kernel

Com gVisor:

Container
   ↓
gVisor user-space kernel
   ↓
seccomp / namespaces
   ↓
Host Kernel

O gVisor intercepta grande parte das chamadas de sistema e reduz a interação direta entre o processo do container e o kernel do host.

No Kubernetes, isso é selecionado por:

runtimeClassName: gvisor

Em GKE, nós habilitados para Sandbox recebem automaticamente o label:

sandbox.gke.io/runtime: gvisor

e o taint:

sandbox.gke.io/runtime=gvisor:NoSchedule

Esse mecanismo impede que workloads comuns sejam agendados inadvertidamente nos nodes destinados às sandboxes.

Arquitetura do laboratório

Vamos utilizar GKE Standard para deixar explícita a separação entre os node pools:

GKE Cluster
│
├── system-pool
│     ├── controllers
│     ├── observability
│     └── RL orchestrator
│
└── rl-sandbox-pool
      ├── gVisor
      ├── taint sandbox.gke.io/runtime=gvisor
      ├── taint workload=rl-sandbox
      │
      ├── Sandbox 1
      ├── Sandbox 2
      ├── Sandbox 3
      └── Sandbox N

Essa separação é importante porque código não confiável não deveria disputar o mesmo node pool utilizado por controllers ou aplicações críticas.

A própria documentação do GKE alerta que workloads não confiáveis devem ser considerados na estratégia de isolamento mesmo quando gVisor está sendo utilizado.

Criando o cluster GKE

No momento deste artigo, a documentação do GKE utiliza a API v1beta1 do Agent Sandbox em clusters 1.36.3-gke.1767000 ou superiores. Como versões mínimas podem mudar, verifique a documentação antes de criar ambientes permanentes.

Defina:

export PROJECT_ID="$(gcloud config get-value project)"
export CLUSTER_NAME="rl-agent-sandbox"
export LOCATION="us-central1-a"
export CLUSTER_VERSION="1.36.3-gke.1767000"

Crie o cluster:

gcloud beta container clusters create "${CLUSTER_NAME}" \
  --location="${LOCATION}" \
  --cluster-version="${CLUSTER_VERSION}" \
  --num-nodes=1 \
  --machine-type=e2-standard-2 \
  --workload-pool="${PROJECT_ID}.svc.id.goog"

Nesse laboratório mantemos apenas um node inicial para reduzir custos. Em produção, normalmente utilizaríamos cluster regional e múltiplas zonas.

Criando o node pool dedicado às sandboxes

Agora criamos o pool utilizado para RL:

gcloud container node-pools create rl-sandbox-pool \
  --cluster="${CLUSTER_NAME}" \
  --location="${LOCATION}" \
  --machine-type=e2-standard-4 \
  --image-type=cos_containerd \
  --sandbox=type=gvisor \
  --enable-autoscaling \
  --min-nodes=0 \
  --max-nodes=10 \
  --node-labels=workload=rl-sandbox \
  --node-taints=workload=rl-sandbox:NoSchedule

O ponto mais importante é --sandbox=type=gvisor. Para GKE Sandbox, o node image precisa utilizar cos_containerd.

Também adicionamos nosso próprio taint, workload=rl-sandbox:NoSchedule. Portanto, teremos duas barreiras de scheduling:

sandbox.gke.io/runtime=gvisor:NoSchedule
workload=rl-sandbox:NoSchedule

Isso deixa explícita a intenção daquele node pool.

Habilitando o Agent Sandbox

Depois do pool gVisor:

gcloud beta container clusters update "${CLUSTER_NAME}" \
  --location="${LOCATION}" \
  --enable-agent-sandbox

Valide:

gcloud beta container clusters describe "${CLUSTER_NAME}" \
  --location="${LOCATION}" \
  --format="value(addonsConfig.agentSandboxConfig.enabled)"

O resultado esperado é True. Esse é o procedimento atual documentado pelo Google para GKE Standard.

Criando o namespace

apiVersion: v1
kind: Namespace
metadata:
  name: rl-sandbox
  labels:
    workload-type: agent-sandbox
kubectl apply -f namespace.yaml

Em produção, esse namespace também deveria receber políticas específicas de RBAC, NetworkPolicy, ResourceQuota, LimitRange e Pod Security.

SandboxTemplate para RL

Agora criamos o runtime Python:

apiVersion: extensions.agents.x-k8s.io/v1beta1
kind: SandboxTemplate
metadata:
  name: rl-python-runtime
  namespace: rl-sandbox
spec:
  podTemplate:
    metadata:
      labels:
        app: rl-environment
        workload: rl-sandbox
    spec:
      runtimeClassName: gvisor

      automountServiceAccountToken: false

      securityContext:
        runAsNonRoot: true
        runAsUser: 1000

      nodeSelector:
        sandbox.gke.io/runtime: gvisor
        workload: rl-sandbox

      tolerations:
        - key: sandbox.gke.io/runtime
          operator: Equal
          value: gvisor
          effect: NoSchedule

        - key: workload
          operator: Equal
          value: rl-sandbox
          effect: NoSchedule

      containers:
        - name: rl-runtime

          image: us-central1-docker.pkg.dev/PROJECT_ID/rl/rl-runtime:1.0.0

          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop:
                - ALL

          resources:
            requests:
              cpu: "500m"
              memory: "512Mi"

            limits:
              cpu: "2"
              memory: "2Gi"

          volumeMounts:
            - name: tmp
              mountPath: /tmp

      volumes:
        - name: tmp
          emptyDir:
            sizeLimit: 1Gi

      restartPolicy: OnFailure

Essa configuração faz quatro coisas particularmente importantes:

  1. runtimeClassName: gvisor faz o workload utilizar o runtime sandboxed;
  2. nodeSelector com sandbox.gke.io/runtime: gvisor garante que o Pod seja executado no pool correto;
  3. as tolerations permitem que o workload atravesse os taints existentes;
  4. o securityContext impede diversas formas de escalada de privilégio.

O Agent Sandbox possui políticas de admissão que exigem configurações como runAsNonRoot, capabilities.drop: ["ALL"], limites de recursos, gVisor, node selector e toleration. Configurações como privileged, hostNetwork, hostPID, hostIPC, hostPath e capabilities adicionais são proibidas.

Criando um Warm Pool

Agora podemos manter environments prontos:

apiVersion: extensions.agents.x-k8s.io/v1beta1
kind: SandboxWarmPool
metadata:
  name: rl-python-warmpool
  namespace: rl-sandbox
spec:
  replicas: 3

  sandboxTemplateRef:
    name: rl-python-runtime
kubectl apply -f rl-warmpool.yaml
kubectl get sandboxwarmpool -n rl-sandbox

Conceitualmente:

Warm Pool
│
├── ready sandbox
├── ready sandbox
└── ready sandbox

Quando uma aplicação solicitar um ambiente, não será necessário passar novamente por:

schedule
→ image pull
→ container creation
→ Python startup
→ dependency initialization

É exatamente aí que aparece o ganho de cold-start.

Solicitando um sandbox

Uma aplicação pode solicitar um ambiente através de um SandboxClaim:

apiVersion: extensions.agents.x-k8s.io/v1beta1
kind: SandboxClaim
metadata:
  name: rl-session-001
  namespace: rl-sandbox
spec:
  warmPoolRef:
    name: rl-python-warmpool
kubectl apply -f claim.yaml

kubectl get \
  sandboxwarmpool,sandboxclaim,sandbox,pod \
  -n rl-sandbox

O controller associa o claim a um ambiente do warm pool e passa a reconstruir a capacidade disponível em segundo plano. Esse modelo é recomendado pelo GKE para workloads sensíveis a startup latency.

Imagem do runtime

Um requirements.txt simples:

gymnasium[classic-control]
numpy
openai
opentelemetry-api
opentelemetry-sdk
opentelemetry-exporter-otlp-proto-grpc

Dockerfile:

FROM python:3.12-slim

RUN useradd \
    --create-home \
    --uid 1000 \
    sandbox

WORKDIR /app

COPY requirements.txt .

RUN pip install \
    --no-cache-dir \
    -r requirements.txt

COPY app.py .

RUN chown -R sandbox:sandbox /app

USER sandbox

CMD ["python", "app.py"]

Evite rodar pip install ... durante a inicialização do container. Isso piora drasticamente o cold-start, dificulta reprodutibilidade e aumenta dependência de conectividade externa.

Exemplo com Gymnasium

O antigo projeto OpenAI Gym evoluiu e atualmente o ecossistema utiliza principalmente o Gymnasium, mantido pela Farama Foundation.

A interface fundamental continua baseada em:

observation, info = env.reset()

observation, reward, terminated, truncated, info = env.step(action)

Desde as versões modernas da API, terminated e truncated substituem o antigo conceito único de done.

Um exemplo simples:

import gymnasium as gym


env = gym.make("CartPole-v1")

observation, info = env.reset(seed=42)

total_reward = 0.0


for step in range(500):

    action = env.action_space.sample()

    observation, reward, terminated, truncated, info = env.step(action)

    total_reward += reward

    if terminated or truncated:
        break


print({
    "steps": step + 1,
    "reward": total_reward,
})

env.close()

Esse código pode ser executado integralmente dentro do Sandbox.

Exemplo OpenAI + Gymnasium

Também podemos utilizar um modelo como policy experimental.

Esse exemplo é propositalmente didático: não é uma forma econômica de treinar CartPole. Consultar um LLM a cada timestep cria latência e custo consideravelmente maiores que uma policy neural local.

Porém, o padrão é útil quando o environment representa tarefas de software, browsers, shells ou ferramentas complexas.

import os

import gymnasium as gym

from openai import OpenAI


client = OpenAI(
    api_key=os.environ["OPENAI_API_KEY"]
)


env = gym.make("CartPole-v1")

observation, info = env.reset(seed=42)

total_reward = 0


for step in range(50):

    prompt = f"""
Você controla um ambiente CartPole.

Observação:
{observation.tolist()}

As ações possíveis são:

0 = mover para esquerda
1 = mover para direita

Escolha uma única ação.

Responda somente:
0
ou
1
"""

    response = client.responses.create(
        model="gpt-5.6-luna",
        input=prompt,
    )

    result = response.output_text.strip()

    if result not in {"0", "1"}:
        action = env.action_space.sample()
    else:
        action = int(result)

    observation, reward, terminated, truncated, info = env.step(action)

    total_reward += reward

    print(
        {
            "step": step,
            "action": action,
            "reward": reward,
            "total_reward": total_reward,
        }
    )

    if terminated or truncated:
        break


env.close()

A API atual da OpenAI utiliza o padrão client.responses.create(...) através da Responses API. A arquitetura fica:

Sandbox
   │
   ├── Gymnasium environment
   │
   ├── observation
   │
   ▼
OpenAI API
   │
   ├── decision
   ▼
action
   │
   ▼
Gymnasium
   │
   └── reward

Para workloads reais de RL, uma arquitetura mais eficiente normalmente utiliza:

Local Policy Model
        ↓
Environment
        ↓
Rollout Buffer
        ↓
Trainer

e reserva LLMs externos para tarefas como reward evaluation, planning, reasoning, code generation e environment generation.

Um cenário corporativo realista

Imagine uma plataforma interna desenvolvendo agentes capazes de corrigir aplicações automaticamente. Cada treinamento recebe uma aplicação quebrada. O agente pode:

ler código
→ executar testes
→ alterar arquivos
→ executar comandos
→ observar erro
→ tentar nova solução

Executar tudo no mesmo Pod do trainer seria perigoso. Com Agent Sandbox:

RL Trainer
     │
     ├── Claim #001
     │      └── repository clone
     │      └── generated code
     │      └── tests
     │
     ├── Claim #002
     │
     └── Claim #003

Cada episódio ocorre em seu próprio sandbox. Uma função de recompensa poderia utilizar:

testes aprovados        +10
lint aprovado            +2
build aprovado           +5
timeout                  -3
crash                    -5
teste de segurança falha -10

Ao terminar o episódio:

SandboxClaim deleted
         ↓
environment discarded
         ↓
new clean sandbox

Isso reduz contaminação entre episódios e aumenta reprodutibilidade.

Ambiente de produção

Um desenho mais próximo de produção seria:

                    ┌─────────────────────┐
                    │ RL Control Plane    │
                    │ Trainer / API       │
                    └──────────┬──────────┘
                               │
                ┌──────────────┴───────────────┐
                │                              │
                ▼                              ▼
         CPU Sandbox Pool               GPU Sandbox Pool
        e2 / n2 / c3 etc.             L4 / A100 / H100
                │                              │
          gVisor Runtime                  gVisor Runtime
                │                              │
             Env Pods                      Training Pods
                │                              │
                └──────────────┬───────────────┘
                               │
                        Observability
                               │
             ┌─────────────────┼─────────────────┐
             ▼                 ▼                 ▼
        Prometheus            Loki        OpenTelemetry

GKE Sandbox também suporta determinados tipos de GPU e TPU quando utilizados com versões compatíveis de GKE. Portanto, isolamento e aceleração de hardware não são necessariamente mutuamente exclusivos.

Uma arquitetura madura normalmente separaria environment execution, policy inference, training, reward model, orchestrator e observability em workloads independentes.

Observabilidade para RL

CPU e memória não são suficientes para observar RL.

O Google atualmente documenta instrumentação de workloads de RL em GKE utilizando OpenTelemetry, incluindo métricas específicas de sampling, training, reward e sincronização. Algumas métricas importantes:

MétricaObjetivo
rl.loop.durationduração do loop
rl.sample.durationtempo de sampling
rl.reward.durationtempo para calcular reward
rl.train.durationtempo de treinamento
rl.step.durationlatência de cada timestep
rl.sample.samplesquantidade de samples
rl.sample.episodesquantidade de episódios
rl.train.stepspassos de treinamento
rl.environment.reward.meanrecompensa média
rl.environment.episode.length.meanduração média do episódio
rl.train.lossloss atual
rl.train.mfuModel FLOP Utilization

Uma instrumentação simples:

import time

from opentelemetry import metrics


meter = metrics.get_meter("rl-sandbox")


step_duration = meter.create_histogram(
    "rl.step.duration",
    unit="ms",
)


reward_counter = meter.create_counter(
    "rl.reward.total",
)


start = time.perf_counter()

observation, reward, terminated, truncated, info = env.step(action)

elapsed_ms = (
    time.perf_counter() - start
) * 1000


step_duration.record(
    elapsed_ms,
    attributes={
        "rl.environment.name": "CartPole-v1",
        "rl.run.id": "experiment-001",
    },
)


reward_counter.add(
    reward,
    attributes={
        "rl.environment.name": "CartPole-v1",
    },
)

Prometheus, Grafana, Loki e OpenTelemetry

Em uma stack independente do Cloud Monitoring:

Sandbox
   │
   ├── stdout
   │      ↓
   │     Loki
   │
   ├── /metrics
   │      ↓
   │   Prometheus
   │
   └── OTLP
          ↓
     OpenTelemetry Collector
          ↓
   Tempo / Cloud Trace

Dashboards deveriam correlacionar infraestrutura e RL: reward médio versus CPU usage, step latency, sandbox cold-start e node provisioning latency. Isso ajuda a diferenciar algoritmo ruim de infraestrutura lenta.

Medindo cold-start

Cold-start não deve ser tratado como uma única métrica. Podemos dividir:

T0 Sandbox solicitado
 │
 ├── scheduler latency
 ├── node provisioning
 ├── image pull
 ├── container creation
 ├── Python startup
 ├── environment initialization
 │
 ▼
T1 Sandbox pronto

O GKE fornece diretamente a métrica kubernetes.io/pod/latencies/pod_first_ready, que mede o período entre criação do Pod e primeiro estado Ready, incluindo image pull.

Também existe kubernetes.io/node/latencies/startup, para medir criação da VM até o node ficar disponível no Kubernetes.

Essas duas métricas são fundamentais porque permitem separar Pod cold-start de Node cold-start.

Comparação de cold-starts

O laboratório deveria comparar pelo menos quatro cenários:

A. Pod normal + containerd
B. Pod gVisor sem warm pool
C. Agent Sandbox sem instância previamente quente
D. Agent Sandbox + WarmPool

É importante não publicar números como universais. Cold-start varia com tamanho da imagem, cache do node, tipo de VM, autoscaler, registry, dependências da aplicação, readiness probe, região e pressão do cluster.

Um resultado hipotético de laboratório, apenas para ilustrar o formato da análise, poderia ser:

CenárioP50P95
Pod containerd com node quente1,4 s2,3 s
Pod gVisor com node quente1,9 s3,1 s
Sandbox criado sob demanda3,8 s6,7 s
SandboxClaim em WarmPool180 ms420 ms
Node novo + sandbox55 s88 s

Esses números não são benchmarks oficiais do GKE. Eles devem ser substituídos por medições reais do ambiente.

O comportamento esperado, porém, explica bem a arquitetura. O maior custo normalmente não está em containerd versus gVisor, mas em:

node inexistente
     ↓
autoscaler
     ↓
Compute Engine VM
     ↓
node Ready
     ↓
image pull
     ↓
sandbox

Por isso warm pools e capacidade mínima podem ter um impacto muito maior na experiência do usuário do que pequenos ajustes no runtime.

Benchmark recomendado

Execute pelo menos 30 ou 50 amostras por cenário. Colete:

request_timestamp
sandbox_assigned_timestamp
pod_created_timestamp
pod_ready_timestamp
environment_ready_timestamp
first_action_timestamp

Calcule P50, P90, P95 e P99. Nunca compare apenas uma execução.

Também execute dois experimentos distintos: com image cached e image not cached. Caso contrário, o resultado pode medir apenas Artifact Registry/image pull em vez do runtime propriamente dito.

Warm Pool x escala para zero

Existe um trade-off direto:

  • Warm Pool: menor latência, maior custo ocioso;
  • Scale-to-zero: menor custo, maior cold-start.

Uma estratégia comum é:

horário de pico:   warm pool = 20
horário normal:    warm pool = 5
madrugada:         warm pool = 0

A capacidade deve ser determinada observando a taxa de criação de sessões. Se a taxa de chegada é de 10 sandboxes/s e a reposição leva 5 s, um warm pool de apenas três ambientes será rapidamente consumido.

Segurança

Agent Sandbox não elimina a necessidade de controles adicionais. A recomendação é combinar:

gVisor
+
NetworkPolicy
+
RBAC
+
resource limits
+
admission policies
+
secret isolation

O Agent Sandbox possui modelo de segurança com políticas de admissão que restringem configurações perigosas e estabelece postura de isolamento de rede para sandboxes.

Service Accounts

Um ponto particularmente importante é automountServiceAccountToken: false. Código executado dentro da sandbox não deveria receber automaticamente credenciais Kubernetes.

API keys

Evite colocar uma chave privilegiada de OpenAI diretamente no environment quando o código executado puder ser modificado pelo agente. Arquitetura mais segura:

Sandbox
   │
   ▼
Internal AI Gateway
   │
   ├── authentication
   ├── quota
   ├── model allowlist
   ├── rate limit
   └── auditing
   │
   ▼
OpenAI API

Assim, a sandbox nunca recebe a credencial principal.

NetworkPolicy

Uma postura razoável é começar com deny all e liberar apenas destinos necessários. Em workloads que executam código gerado por IA, egress irrestrito representa um risco particularmente importante.

sandbox
   ├─X→ metadata
   ├─X→ databases internos
   ├─X→ Kubernetes API
   ├─X→ outros namespaces
   │
   └──→ AI Gateway

Esse modelo transforma acesso externo em capability explicitamente concedida.

Requests e Limits

Outro erro comum é criar sandboxes com requests muito abaixo dos limits:

requests:
  cpu: 10m
limits:
  cpu: 4

Isso permite uma densidade teórica muito maior que a capacidade real. Imagine 100 ambientes com request = 100m e limit = 2 CPU:

Scheduler:  100 × 100m = 10 CPU
Potencial:  100 × 2 CPU = 200 CPU

Se todos iniciarem processamento simultaneamente, ocorrerá contenção severa. Para RL, monitore requested CPU, actual CPU, CPU throttling, working set memory, OOMKills, episodes/s e steps/s, e faça sizing baseado em execução real.

FinOps

Os maiores vetores de custo são normalmente:

número de sandboxes
tempo de vida
warm pool
GPU/TPU
node idle
requests superdimensionados
telemetria excessiva
chamadas externas de modelos

Para environments tolerantes à interrupção, Spot VMs podem ser bastante interessantes. Um desenho possível:

Trainer crítico                  → On-Demand
RL evaluation environments       → Spot
Warm pool de baixa latência      → On-Demand mínimo
Burst capacity                   → Spot

Essa separação evita utilizar capacidade premium para workloads facilmente reconstruíveis.

Desenvolvimento x produção

DesenvolvimentoProdução
Cluster zonalPreferência por regional quando HA é requisito
1 nodemúltiplas zonas
warm pool 1-2dimensionado por demanda
Kubernetes Secretgateway/secret management controlado
logs detalhadossampling e retenção definida
permissões amplasleast privilege
egress mais abertodefault deny
imagens experimentaisversões imutáveis
latestdigest ou versão fixa
métricas básicasSLOs e alertas
scale-to-zero agressivoequilíbrio cold-start/custo

Troubleshooting

Sandbox não é agendado

kubectl get pods -n rl-sandbox -o wide
kubectl describe pod <pod> -n rl-sandbox

Procure mensagens como untolerated taint ou node affinity conflict. Valide os nodes e os taints:

kubectl get nodes --show-labels
kubectl describe node <node> | grep -A5 Taints

Runtime não está usando gVisor

Verifique externamente:

kubectl get pod <pod> \
  -n rl-sandbox \
  -o jsonpath='{.spec.runtimeClassName}'

Esperado: gvisor. A documentação do GKE recomenda verificar o RuntimeClass a partir do Kubernetes, e não confiar em informações emitidas pelo workload potencialmente não confiável.

SandboxTemplate rejeitado

kubectl get events \
  -n rl-sandbox \
  --sort-by=.metadata.creationTimestamp

Possíveis causas:

automountServiceAccountToken ausente
runAsNonRoot ausente
limits ausentes
capabilities não removidas
hostPath configurado
privileged=true
runtimeClass diferente de gvisor

Warm pool não está disponível

kubectl get sandboxwarmpool -n rl-sandbox -o yaml
kubectl get sandbox -n rl-sandbox
kubectl get pods -n rl-sandbox

Se os Pods estiverem em Pending, investigue capacidade e autoscaling.

Cold-start alto

Verifique primeiro se é Node cold-start ou Pod cold-start, analisando kubernetes.io/node/latencies/startup e kubernetes.io/pod/latencies/pod_first_ready. Depois investigue image size, image pull, init containers, application initialization, autoscaler, resource pressure e readiness probe.

O que evitar

Não execute código produzido por agentes diretamente em Pods privilegiados. Evite privileged: true. Não utilize hostNetwork: true, hostPID: true ou hostIPC: true.

Não monte via hostPath:

/var/run/docker.sock
/
/proc
/sys

Também evite utilizar o mesmo node pool para agentes não confiáveis, bancos, controllers e observabilidade crítica. Mesmo com gVisor, isolamento de blast radius continua sendo parte fundamental da arquitetura.

Boas práticas

Para workloads desse tipo, uma baseline razoável seria:

  1. node pool exclusivo para sandboxes;
  2. runtimeClassName: gvisor;
  3. taints e node selectors;
  4. runAsNonRoot;
  5. capabilities removidas;
  6. filesystem read-only quando possível;
  7. automountServiceAccountToken: false;
  8. limites explícitos de CPU e memória;
  9. NetworkPolicy default-deny;
  10. warm pool para workloads interativos;
  11. imagens pequenas e previamente construídas;
  12. observabilidade de RL separada da observabilidade de infraestrutura;
  13. métricas P50/P95/P99 de startup;
  14. Spot apenas para workloads tolerantes a interrupção;
  15. expiração automática de sandboxes abandonados.

Quando utilizar (e quando não)

Agent Sandbox é especialmente interessante para:

AI coding agents
RL environments
browser agents
terminal agents
avaliação automática de código
notebooks isolados
CI executando código não confiável
multi-tenant developer environments
agent evaluation farms

O modelo também faz sentido quando ambientes precisam ser criados e destruídos programaticamente em grande escala.

Por outro lado, ele adiciona complexidade operacional. Se sua aplicação executa apenas uma API Java, um worker Go ou um frontend Node.js com código conhecido e controlado, um Deployment Kubernetes comum provavelmente é suficiente. Também não faz sentido transformar cada chamada HTTP em um Sandbox. Para workloads confiáveis, Deployment, Job, CronJob e StatefulSet continuam sendo abstrações mais simples.

Resumo prático

RL Orchestrator
      │
      ▼
SandboxClaim
      │
      ▼
SandboxWarmPool
      │
      ▼
Sandbox
      │
      ▼
Pod
      │
runtimeClassName: gvisor
      │
      ▼
Dedicated GKE Node Pool
      │
      ▼
Gymnasium / Agent Code

O principal valor do Agent Sandbox não é simplesmente executar um container com Python. É transformar environments potencialmente não confiáveis em um recurso gerenciado da plataforma.

E o principal aprendizado de performance é igualmente importante: cold-start não é apenas container startup. Ele é resultado de capacity, scheduler, node provisioning, image pull, runtime e application initialization. Warm pools atacam justamente o caminho crítico, removendo várias dessas etapas da requisição do usuário.

Conclusão

Reinforcement Learning e agentes autônomos introduzem um modelo operacional diferente das aplicações tradicionais. O Kubernetes continua sendo um excelente scheduler, mas Pods isolados apenas por containers não são necessariamente a fronteira de segurança adequada quando workloads podem executar código gerado dinamicamente.

A combinação de Agent Sandbox, gVisor, dedicated node pools, taints/tolerations, NetworkPolicy, resource isolation, Warm Pools e OpenTelemetry cria uma base muito mais adequada para plataformas modernas de execução de agentes.

Do ponto de vista de Platform Engineering, o resultado é especialmente interessante porque o time de IA não precisa conhecer detalhes sobre nodes, RuntimeClasses ou políticas de segurança. A aplicação simplesmente solicita um SandboxClaim, e a plataforma resolve scheduling, isolamento, capacidade, runtime, segurança e observabilidade.

Para workloads de RL em escala, a evolução natural é separar environments CPU de trainers GPU/TPU, introduzir filas e quotas, utilizar environments vetorizados quando isolamento por episódio não for necessário e construir autoscaling baseado na taxa real de SandboxClaims.

Nesse ponto, o Kubernetes deixa de ser apenas o lugar onde o modelo roda e passa a funcionar como control plane de uma plataforma de agentes e Reinforcement Learning.

Referências técnicas

  • Google Kubernetes Engine — GKE Agent Sandbox: arquitetura e conceitos.
  • Google Kubernetes Engine — habilitação e configuração do Agent Sandbox.
  • Google Kubernetes Engine — CRDs Sandbox, SandboxTemplate, SandboxClaim e SandboxWarmPool.
  • Google Kubernetes Engine — GKE Sandbox e gVisor.
  • Google Kubernetes Engine — observabilidade de Reinforcement Learning com OpenTelemetry.
  • Google Kubernetes Engine — métricas de startup de Pods e nodes.
  • Gymnasium — API de environments e environments vetorizados.
  • OpenAI — Responses API para integrações atuais.

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.