1. Introducción
En este lab, se detalla cómo compilar, aprovisionar y ejecutar un bucle de entrenamiento de aprendizaje por refuerzo (RL) distribuido de alto rendimiento en GKE Standard con zonas de pruebas de agentes de GKE (gVisor), mediante el algoritmo de optimización de políticas relativas de grupo (GRPO) con la trl biblioteca.
El objetivo es demostrar cómo evaluar de forma segura el código no confiable generado por LLM durante un bucle de entrenamiento de RL. Para ello, desacoplamos el plano de organización (Ray) del plano de ejecución (zonas de pruebas de agentes de GKE).
El desafío técnico de la evaluación del código de RL
Cuando se entrenan agentes de LLM con aprendizaje por refuerzo (p.ej., entrenar un modelo para escribir código mediante la evaluación de su resultado en pruebas unitarias), el bucle de entrenamiento debe ejecutar miles de secuencias de comandos de Python no confiables generadas por LLM en paralelo. Esto presenta desafíos críticos:
- El cuello de botella de la rotación de pods: Los frameworks de evaluación tradicionales activan un contenedor de Docker nuevo por tarea. Hacer esto de forma dinámica para cientos de implementaciones paralelas durante un bucle de entrenamiento de RL causa una carga grave en el plano de control de Kubernetes. La latencia imposibilita el entrenamiento de RL de alta frecuencia.
- El riesgo de seguridad: La ejecución de código arbitrario generado por LLM dentro de los entornos de ejecución de contenedores estándar comparte el kernel del SO host. Una sola vulnerabilidad de escape puede poner en riesgo tus nodos.
- El robo de tokens de IAM: El código generado por LLM que se ejecuta dentro de un pod de Kubernetes puede consultar el servidor de metadatos del proveedor de servicios en la nube para robar tokens de cuenta de servicio de IAM del nodo.
La solución: Organización y ejecución desacopladas
Esta arquitectura desacopla la organización de la ejecución:
- El organizador (Ray): Un clúster de Ray distribuido administra el bucle de entrenamiento de RL y distribuye la generación de implementaciones.
- El plano de ejecución (zona de pruebas de agentes de GKE): En lugar de crear pods de Kubernetes de forma dinámica, los trabajadores de Ray realizan llamadas HTTP simples a un enrutador de zona de pruebas dedicado. El enrutador asigna al trabajador de forma instantánea un contenedor aislado y precalentado que se ejecuta en GKE Sandbox.
- Latencia inferior a un segundo: Debido a que las zonas de pruebas se precalientan en un
SandboxWarmPooladministrado y se administran a través de una puerta de enlace HTTP de alta velocidad, la creación del entorno se reduce a menos de 200 ms, lo que omite por completo el plano de control de Kubernetes.
Objetivos del lab
En este codelab, aprenderás lo siguiente:
- Los desafíos y las soluciones arquitectónicas para evaluar el código no confiable en bucles de RL
- Cómo compilar imágenes de zona de pruebas personalizadas para implementaciones eficientes
- Cómo configurar y usar las zonas de pruebas de agentes de GKE y SandboxWarmPools
- Cómo aislar de forma segura las zonas de pruebas para evitar el robo de tokens de IAM
- Cómo ejecutar un trabajo de entrenamiento de RL básico con SweBench y TRL con Ray para desacoplar la organización de la ejecución
2. Creación y requisitos previos del clúster
Antes de continuar, necesitas un clúster de GKE con un grupo de nodos de GPU de alto rendimiento y el operador de Ray instalado para administrar la carga de trabajo de entrenamiento.
Requisitos previos
En este codelab, se supone que las siguientes herramientas están instaladas y configuradas:
- SDK de Google Cloud (
gcloud) - Docker (necesario para compilar imágenes personalizadas de forma local)
kubectl
Variables de entorno
Primero, establece las variables de entorno que se usarán durante este codelab. Los siguientes comandos usan valores predeterminados sensibles, pero puedes cambiarlos según sea necesario para que coincidan con tu entorno específico de Google Cloud:
export PROJECT_ID=$(gcloud config get-value project)
export REGION="us-west3"
export ZONE="us-west3-a"
export REPO_NAME="rl-sandbox-repo"
Crea un repositorio de Artifact Registry para contener las imágenes de contenedor personalizadas:
gcloud artifacts repositories create $REPO_NAME \
--repository-format=docker \
--location=$REGION \
--description="Repository for RL Sandbox images"
Configuración del clúster
Para obtener una guía completa sobre el aprovisionamiento de un clúster de GKE optimizado para cargas de trabajo de IA (incluido el cableado de red RDMA de GPUDirect), consulta la documentación oficial: Crea un clúster personalizado de GKE AI Hypercompute
Requisito previo fundamental: Cuando crees tu clúster o un grupo de nodos de ejecución específico, asegúrate de pasar las marcas --enable-agent-sandbox y --sandbox type=gvisor para instalar las definiciones de recursos personalizados (CRD) necesarias para los grupos de nodos de zona de pruebas precalentados.
Si se supone que tu clúster, las GPU y el operador de Ray están en ejecución, todo lo que se indica a continuación detalla cómo configurar el plano de ejecución y ejecutar el bucle de RL.
3. Compila imágenes personalizadas
Un aspecto fundamental de la ejecución de RL de alto rendimiento es incorporar dependencias en tus imágenes. Necesitamos dos imágenes distintas: una para los trabajadores de GPU que ejecutan el modelo y otra para las zonas de pruebas aisladas que ejecutan el código de evaluación no confiable.
1. Compila la imagen del trabajador de GPU
El trabajador de GPU de Ray necesita bibliotecas para ejecutar el modelo de lenguaje y organizar el bucle de entrenamiento. Compilamos esta imagen sobre la imagen oficial de vLLM para que admita las GPU más recientes y tenga PyTorch/CUDA preinstalados.
Ejecuta el siguiente comando para crear Dockerfile.gpu_worker:
cat << 'EOF' > Dockerfile.gpu_worker
# ==============================================================================
# Base Image: Use the official vLLM production image.
# This image comes pre-baked with PyTorch 2.11, CUDA 13.0, and vLLM.
# It supports sm_100 Blackwell GPUs natively!
# ==============================================================================
FROM vllm/vllm-openai:latest
USER root
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
numactl \
libnuma-dev \
wget \
ca-certificates \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Install Ray, TRL, and Sandbox tools
# TRL does not require compiling flash_attn from source.
RUN pip install --no-cache-dir \
"ray[default]==2.55.1" \
"numpy<2.0" \
gymnasium>=0.28.1 \
k8s-agent-sandbox>=0.4.6 \
trl transformers packaging ninja cachetools accelerate datasets peft
EOF
Compila y envía la imagen a tu repositorio de Artifact Registry:
export WORKER_REPO="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}/ray-gpu-worker:v1"
docker build -f Dockerfile.gpu_worker -t $WORKER_REPO .
docker push $WORKER_REPO
Nota: En esta guía, se usan comandos docker locales para compilar imágenes. Si prefieres compilar las imágenes de forma remota, puedes usar Cloud Build (p.ej., con gcloud builds submit).
2. Compila la imagen principal de la CPU
El nodo principal de Ray solo organiza el clúster y no ejecuta los modelos de entrenamiento de GPU pesados. Para evitar un cuello de botella masivo de extracción de imágenes (por lo general, más de 15 GB) en tus nodos de CPU estándar, compilamos una imagen ligera solo para CPU para el nodo principal. Esta imagen contiene Ray y las bibliotecas de Python necesarias, pero excluye bibliotecas de GPU pesadas como CUDA y vLLM.
Ejecuta el siguiente comando para crear Dockerfile.head:
cat << 'EOF' > Dockerfile.head
# ==============================================================================
# Base Image: Use the official Python slim image for the exact patch version.
# This aligns the Python version (3.12.13) with the GPU worker node.
# ==============================================================================
FROM python:3.12.13-slim
USER root
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
wget \
ca-certificates \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Install Ray, TRL, and Sandbox tools (CPU versions where applicable)
# We install torch CPU first to avoid pulling the 2GB+ CUDA torch package.
RUN pip install --no-cache-dir torch --index-url https://download.pytorch.org/whl/cpu && \
pip install --no-cache-dir \
"ray[default]==2.55.1" \
"numpy<2.0" \
gymnasium>=0.28.1 \
k8s-agent-sandbox>=0.4.6 \
trl transformers packaging ninja cachetools accelerate datasets peft
# Create a 'ray' user to run the container securely and match Ray conventions
RUN useradd -ms /bin/bash ray
USER ray
WORKDIR /home/ray
EOF
Compila y envía la imagen:
export HEAD_REPO="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}/ray-head:v1"
docker build -f Dockerfile.head -t $HEAD_REPO .
docker push $HEAD_REPO
3. Compila la imagen de la zona de pruebas
La zona de pruebas necesita las dependencias específicas de la tarea que estamos evaluando para que la instalación del entorno de ejecución sea instantánea. Para este codelab, usaremos un problema del repositorio django/django en SWE-bench. Preclonaremos el repositorio y compilaremos previamente los entornos de Python para que nuestras secuencias de comandos del modelo no pierdan tiempo descargándolos en el bucle de RL.
Ejecuta el siguiente comando para crear Dockerfile.sandbox:
cat << 'EOF' > Dockerfile.sandbox
# Use a stable Debian-based Miniconda image
FROM condaforge/miniforge3:latest
# 1. Install essential system libraries (including sqlite3 for Django tests)
RUN apt-get update && apt-get install -y \
git \
build-essential \
libsqlite3-dev \
&& rm -rf /var/lib/apt/lists/*
# 2. Set up the /workspace directory and grant ownership to the pre-existing non-root 'ubuntu' user (UID 1000)
RUN mkdir -p /workspace \
&& chown -R 1000:1000 /workspace
# 3. Switch to the non-root user
USER ubuntu
WORKDIR /workspace
# 4. Pre-configure Git globally so the agent can run git commands
RUN git config --global user.email "agent@gke-sandbox.local" \
&& git config --global user.name "Agent"
# 5. Pre-clone the repository as the non-root user
RUN git clone https://github.com/django/django.git .
# 6. Pre-build Conda environments and pre-cache common dependencies
# We do NOT run "pip install -e ." here to avoid Python version conflicts with the main branch.
# Instead, we pre-install the heavy dependencies so that runtime installation is instantaneous.
RUN conda create -y -n django-py39 python=3.9 \
&& conda run -n django-py39 pip install --no-cache-dir asgiref sqlparse tzdata pytest pytest-django
RUN conda create -y -n django-py310 python=3.10 \
&& conda run -n django-py310 pip install --no-cache-dir asgiref sqlparse tzdata pytest pytest-django
# --- Add Agent Server ---
# We use a multi-stage build to copy the agent server from the official python-runtime-sandbox image
COPY --from=registry.k8s.io/agent-sandbox/python-runtime-sandbox:v0.1.0 /app /opt/sandbox-agent
USER root
RUN chown -R 1000:1000 /opt/sandbox-agent \
&& /opt/conda/bin/pip install --no-cache-dir -r /opt/sandbox-agent/requirements.txt \
&& sed -i 's|"/app"|"/workspace"|g' /opt/sandbox-agent/main.py
USER ubuntu
# ------------------------
# Prepend the django-py39 conda environment bin to PATH for commands executed inside the container
ENV PATH=/home/ubuntu/.conda/envs/django-py39/bin:$PATH
# Keep the container alive and run the agent server using the system Python
CMD ["/opt/conda/bin/python3", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8888", "--log-level", "trace", "--app-dir", "/opt/sandbox-agent"]
EOF
Compila y envía la imagen:
export SANDBOX_REPO="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}/django-sandbox:v1"
docker build -f Dockerfile.sandbox -t $SANDBOX_REPO .
docker push $SANDBOX_REPO
4. Configura la organización y la ejecución
Ahora implementamos el clúster de Ray para la organización y los recursos de la zona de pruebas para la ejecución.
1. Configuración del clúster de Ray
Implementa un recurso personalizado de RayCluster. Ten en cuenta que los recursos disponibles de tu clúster (como la memoria, la CPU o el tipo de GPU) pueden diferir. Ajusta las solicitudes y los límites de resources según corresponda.
Ejecuta el siguiente comando para crear raycluster.yaml. Esto usa cat << EOF para sustituir automáticamente tus variables de entorno en el manifiesto:
cat << EOF > raycluster.yaml
apiVersion: ray.io/v1
kind: RayCluster
metadata:
name: grpo-cluster
namespace: default
spec:
rayVersion: "2.55.1"
headGroupSpec:
rayStartParams:
dashboard-host: "0.0.0.0"
template:
spec:
containers:
- name: ray-head
image: ${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}/ray-head:v1
ports:
- containerPort: 6379
name: gcs-server
- containerPort: 8265
name: dashboard
- containerPort: 10001
name: client
resources:
limits:
cpu: "2"
memory: "8Gi"
requests:
cpu: "2"
memory: "8Gi"
workerGroupSpecs:
- groupName: gpu-group
replicas: 1
minReplicas: 1
maxReplicas: 1
rayStartParams: {}
template:
spec:
containers:
- name: ray-worker
image: ${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}/ray-gpu-worker:v1
resources:
limits:
cpu: "12"
memory: "120Gi"
nvidia.com/gpu: "1"
requests:
cpu: "12"
memory: "120Gi"
nvidia.com/gpu: "1"
EOF
Aplícalo:
kubectl apply -f raycluster.yaml
Verifica que el clúster esté creado y en ejecución (esto puede tardar unos minutos):
kubectl get raycluster
Resultado esperado:
NAME DESIRED WORKERS AVAILABLE WORKERS CPUS MEMORY GPUS STATUS AGE rl-cluster 1 1 ready 2m
2. Configuración de SandboxRouter
SandboxRouter actúa como una puerta de enlace HTTP de alta velocidad, que recibe solicitudes de los trabajadores de Ray y los conecta de forma instantánea a los pods de gVisor disponibles, lo que omite el ciclo de vida más lento del pod del servidor de la API de Kubernetes.
Ejecuta el siguiente comando para crear sandbox_router.yaml:
cat << 'EOF' > sandbox_router.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: sandbox-claim-manager
rules:
- apiGroups: ["extensions.agents.x-k8s.io"]
resources: ["sandboxclaims"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["agents.x-k8s.io"]
resources: ["sandboxes"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: sandbox-claim-manager-binding
namespace: default
subjects:
- kind: ServiceAccount
name: default
namespace: default
roleRef:
kind: Role
name: sandbox-claim-manager
apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: Service
metadata:
name: sandbox-router
namespace: default
spec:
type: ClusterIP
selector:
app: sandbox-router
ports:
- name: http
protocol: TCP
port: 8080
targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: sandbox-router-deployment
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: sandbox-router
template:
metadata:
labels:
app: sandbox-router
spec:
containers:
- name: router
image: us-central1-docker.pkg.dev/k8s-staging-images/agent-sandbox/sandbox-router:latest-main
ports:
- containerPort: 8080
env:
- name: ALLOW_UNAUTHENTICATED_ROUTER
value: "true"
EOF
Aplícalo:
kubectl apply -f sandbox_router.yaml
Verifica que la implementación esté en ejecución:
kubectl get deployment sandbox-router-deployment
Resultado esperado:
NAME READY UP-TO-DATE AVAILABLE AGE sandbox-router-deployment 2/2 2 2 1m
3. Configuración de SandboxTemplate y WarmPool
La zona de pruebas de agentes de GKE permite la asignación instantánea de contenedores aislados y precalentados con el enrutador de zona de pruebas. Definimos un SandboxTemplate y un SandboxWarmPool para mantener los pods listos.
Ejecuta el siguiente comando para crear sandbox_warmpool.yaml con tus variables de entorno:
cat << EOF > sandbox_warmpool.yaml
apiVersion: extensions.agents.x-k8s.io/v1alpha1
kind: SandboxTemplate
metadata:
name: swe-bench-django
namespace: default
spec:
podTemplate:
spec:
runtimeClassName: gvisor
securityContext:
runAsNonRoot: true
runAsUser: 1000
nodeSelector:
sandbox.gke.io/runtime: gvisor
tolerations:
- key: sandbox.gke.io/runtime
operator: Equal
value: gvisor
effect: NoSchedule
containers:
- name: sandbox
image: ${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}/django-sandbox:v1
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
resources:
requests:
cpu: "2"
memory: "4Gi"
limits:
cpu: "2"
memory: "4Gi"
---
apiVersion: extensions.agents.x-k8s.io/v1alpha1
kind: SandboxWarmPool
metadata:
name: swe-bench-django-warmpool
namespace: default
spec:
replicas: 10
sandboxTemplateRef:
name: swe-bench-django
EOF
Aplícalo:
kubectl apply -f sandbox_warmpool.yaml
Verifica que se haya inicializado SandboxWarmPool:
kubectl get sandboxwarmpool
Resultado esperado:
NAME READY AGE swe-bench-django-warmpool 10 1m
4. Aislamiento de seguridad
Una NetworkPolicy aísla estrictamente las zonas de pruebas, lo que impide la salida al servidor de metadatos de GCP y, por lo tanto, evita el robo de tokens de IAM.
Ejecuta el siguiente comando para crear network_policy.yaml:
cat << 'EOF' > network_policy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: block-metadata-egress
namespace: default
spec:
podSelector:
matchLabels:
sandbox.gke.io/runtime: gvisor
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 169.254.169.254/32
EOF
Aplica la política:
kubectl apply -f network_policy.yaml
Verifica que se haya creado NetworkPolicy:
kubectl get networkpolicy
Resultado esperado:
NAME POD-SELECTOR AGE block-metadata-egress sandbox.gke.io/runtime=gvisor 1m
5. Trabajo básico de RL con SweBench y TRL
Una vez que se preparan el clúster y las zonas de pruebas, podemos ejecutar un bucle de entrenamiento de GRPO. Usaremos la biblioteca trl para organizar el algoritmo de GRPO y las funciones remotas de Ray para evaluar el código generado dentro de las zonas de pruebas aisladas.
Para que la ejecución sea rápida para este codelab, filtraremos un solo problema de Django. La lógica de enrutamiento que se muestra a continuación muestra cómo seleccionarías diferentes grupos de nodos precalentados para diferentes repositorios, lo que es útil cuando se expande al conjunto de datos completo de SWE-bench.
La secuencia de comandos de entrenamiento
Ejecuta el siguiente comando para crear train_trl.py:
cat << 'EOF' > train_trl.py
import ray
from k8s_agent_sandbox import SandboxClient
from k8s_agent_sandbox.models import SandboxDirectConnectionConfig
from trl import GRPOConfig, GRPOTrainer
from transformers import AutoModelForCausalLM, AutoTokenizer
from datasets import load_dataset
import urllib.request
import re
ray.init(ignore_reinit_error=True)
# 1. Define the Ray remote evaluation function
@ray.remote
def evaluate_rollout(code, prompt_data):
client = SandboxClient(connection_config=SandboxDirectConnectionConfig(api_url="http://sandbox-router.default.svc.cluster.local:8080"))
# Claim a pre-warmed sandbox instantly based on the repo
repo = prompt_data.get("repo")
# In a full system, you'd route to different warmpools based on repo
# Here we default to django for our single task
sandbox = client.create_sandbox(
template="swe-bench-django",
warmpool="swe-bench-django-warmpool",
sandbox_ready_timeout=600
)
try:
# Check if the code is correctly formatted
bash_match = re.search(r"```bash\n(.*?)\n```", code, re.DOTALL)
if not bash_match:
return 0.0
script = bash_match.group(1)
# In a real environment, we would apply the base commit and install here
# For simplicity, we just execute the script
import shlex
script_cmd = f"bash -c {shlex.quote(script)}"
result = sandbox.commands.run(script_cmd, timeout=60)
# Calculate continuous reward based on test passage ratio
if result.exit_code == 0:
return 1.0
# Very simple heuristic reward
return 0.1
finally:
# Clean up and release the sandbox back to the pool
client.delete_sandbox(sandbox.claim_name)
# 2. Define the Reward Function for TRL
def sandbox_reward_func(prompts, completions, **kwargs):
# Dispatch evaluation to Ray cluster
futures = [
evaluate_rollout.remote(completion, {
"repo": kwargs.get('repo', [])[i] if 'repo' in kwargs else None,
"base_commit": kwargs.get('base_commit', [])[i] if 'base_commit' in kwargs else None
}) for i, completion in enumerate(completions)
]
# Block and wait for all sandbox evaluations to complete
rewards = ray.get(futures)
return rewards
# 3. Setup GRPO Trainer
@ray.remote(num_gpus=1, num_cpus=8)
def train():
# Load dataset
dataset = load_dataset("princeton-nlp/SWE-bench_Lite", split="test")
# Filter to our selected target issue
dataset = dataset.filter(lambda x: x["instance_id"] == "django__django-15388")
def format_dataset(example):
files = re.findall(r'^\+\+\+ b/(.+)$', example["patch"], re.MULTILINE)
target_file = files[0] if files else ""
file_content = ""
if target_file:
try:
github_repo = example["repo"]
url = f"https://raw.githubusercontent.com/{github_repo}/{example['base_commit']}/{target_file}"
with urllib.request.urlopen(url) as response:
file_content = response.read().decode('utf-8')
except Exception as e:
pass
prompt = f"""You are an expert software engineer.
You are given a GitHub issue and the content of the file that contains the bug.
Write an executable bash script that will modify the target file to fix the bug (e.g. using cat << 'EOF' > {target_file} or inline python edits).
Wrap your bash script in ```bash ... ``` tags. Do not output raw python code directly.
Target File: {target_file}
Original File Content:
```python
{file_content}
```
Issue:
{example['problem_statement']}
"""
return {
"prompt": prompt,
"repo": example["repo"],
"instance_id": example["instance_id"],
"base_commit": example["base_commit"],
}
dataset = dataset.map(format_dataset)
model_name = "Qwen/Qwen2.5-Coder-1.5B-Instruct"
tokenizer = AutoTokenizer.from_pretrained(model_name)
training_args = GRPOConfig(
output_dir="outputs",
learning_rate=5e-6,
max_steps=50,
per_device_train_batch_size=1,
gradient_accumulation_steps=4,
num_generations=4,
)
trainer = GRPOTrainer(
model=model_name,
processing_class=tokenizer,
reward_funcs=[sandbox_reward_func],
args=training_args,
train_dataset=dataset,
)
print("Starting GRPO training with GKE Agent Sandboxes...")
trainer.train()
def main():
print("Submitting training job to GPU worker...")
ray.get(train.remote())
if __name__ == "__main__":
main()
EOF
Envía el trabajo al clúster
Primero, reenvía el puerto al panel de Ray Head y envía el trabajo de entrenamiento desde tu máquina local:
kubectl port-forward service/grpo-cluster-head-svc 8265:8265 &
ray job submit \
--address http://localhost:8265 \
--runtime-env-json '{"working_dir": "."}' \
-- python train_trl.py
Supervisa la ejecución
Puedes supervisar el progreso de tu ejecución:
- Panel de Ray: Abre
http://localhost:8265en tu navegador. - Reclamos de zona de pruebas: Observa cómo GKE reclama y libera de forma dinámica las zonas de pruebas en gVisor:
watch -n 1 "kubectl get sandboxclaims,sandboxes,pods"
6. Conclusión
¡Felicitaciones! Configuraste y ejecutaste con éxito un bucle de entrenamiento de RL distribuido de alto rendimiento de forma segura en GKE Standard con las zonas de pruebas de agentes de GKE.