Skip to content

Commit 72ff9c6

Browse files
google-genai-botcopybara-github
authored andcommitted
feat: Add GkeCodeExecutor for sandboxed code execution on GKE #non-breaking
Merge #1629 close #2170 ### Summary This PR introduces `GkeCodeExecutor`, a new code executor that provides a secure and scalable method for running LLM-generated code by leveraging GKE Sandbox. It serves as a robust alternative to local or standard containerized executors by leveraging the **GKE Sandbox** environment, which uses gVisor for workload isolation. For each code execution request, it dynamically creates an ephemeral Kubernetes Job with a hardened Pod configuration, offering significant security benefits and ensuring that each code execution runs in a clean, isolated environment. ### Key Features of GkeCodeExecutor * **Dynamic Job Creation**: Uses the Kubernetes `batch/v1` API to create a new Job for each code snippet. * **Secure Code Mounting**: Injects code into the Pod via a temporary `ConfigMap`, which is mounted to a read-only file. * **gVisor Sandboxing**: Enforces execution within a `gvisor` runtime for kernel-level isolation. * **Hardened Security Context**: Pods run as non-root with all Linux capabilities dropped and a read-only root filesystem. * **Resource Management**: Applies configurable CPU and memory limits to prevent abuse. * **Automatic Cleanup**: Uses the `ttl_seconds_after_finished` feature on Jobs for robust, automatic garbage collection of completed Pods and Jobs. * **Node Scheduling**: The executor uses Kubernetes `tolerations` in its Pod specification. This allows the k8s scheduler to place the execution Pod onto a **_pre-configured_** gVisor-enabled node. * **Module Integration**: The `GkeCodeExecutor` is registered in the `code_executors/__init__.py`, making it available for use by agents. The `ImportError` handling is configured to check for the required `kubernetes` SDK. ### Execution Flow: 1. Agent invokes `GkeCodeExecutor` with the LLM-generated code. 2. The `GkeCodeExecutor` will `execute_code` – creates a temporary `ConfigMap`, and then create a k8s `Job` to run it. 3. This Job runs a standard `python:3.11-slim` container. The image is pulled once to the node and cached. The Job will mount the ConfigMap as `/app/code.py` 4. The GkeCodeExecutor will monitor the Job to completion, fetch `stdout/stderr` logs from the container, return `CodeExecutionResult` to the LlmAgent, and ensure all temp resources are deleted. 5. The calling agent formats the result and provides a final response to the user. If the result contains error, it will retry up to `error_retry_attempts` times. PiperOrigin-RevId: 804511467
1 parent e63fe0c commit 72ff9c6

File tree

6 files changed

+691
-0
lines changed

6 files changed

+691
-0
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""A Python coding agent using the GkeCodeExecutor for secure execution."""
16+
17+
from google.adk.agents import LlmAgent
18+
from google.adk.code_executors import GkeCodeExecutor
19+
20+
21+
def gke_agent_system_instruction():
22+
"""Returns: The system instruction for the GKE-based coding agent."""
23+
return """You are a helpful and capable AI agent that can write and execute Python code to answer questions and perform tasks.
24+
25+
When a user asks a question, follow these steps:
26+
1. Analyze the request.
27+
2. Write a complete, self-contained Python script to accomplish the task.
28+
3. Your code will be executed in a secure, sandboxed environment.
29+
4. Return the full and complete output from the code execution, including any text, results, or error messages."""
30+
31+
32+
gke_executor = GkeCodeExecutor(
33+
# This must match the namespace in your deployment_rbac.yaml where the
34+
# agent's ServiceAccount and Role have permissions.
35+
namespace="agent-sandbox",
36+
# Setting an explicit timeout prevents a stuck job from running forever.
37+
timeout_seconds=600,
38+
)
39+
40+
root_agent = LlmAgent(
41+
name="gke_coding_agent",
42+
model="gemini-2.0-flash",
43+
description=(
44+
"A general-purpose agent that executes Python code in a secure GKE"
45+
" Sandbox."
46+
),
47+
instruction=gke_agent_system_instruction(),
48+
code_executor=gke_executor,
49+
)
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
apiVersion: v1
2+
kind: Namespace
3+
metadata:
4+
name: agent-sandbox
5+
---
6+
apiVersion: v1
7+
kind: ServiceAccount
8+
metadata:
9+
name: adk-agent-sa
10+
namespace: agent-sandbox
11+
---
12+
apiVersion: rbac.authorization.k8s.io/v1
13+
kind: Role
14+
metadata:
15+
name: adk-agent-role
16+
namespace: agent-sandbox
17+
rules:
18+
- apiGroups: ["batch"]
19+
resources: ["jobs"]
20+
# create: Needed for _batch_v1.create_namespaced_job().
21+
# watch: Needed for watch.stream(self._batch_v1.list_namespaced_job, ...) to wait for completion
22+
# list/get: Required for the watch to initialize and to get job details.
23+
verbs: ["create", "get", "watch", "list", "delete"]
24+
- apiGroups: [""]
25+
resources: ["configmaps"]
26+
# create: Needed mount the agent's code into the Job's Pod.
27+
# delete: Needed for cleanup in the finally block
28+
verbs: ["create", "get", "list", "delete"]
29+
- apiGroups: [""]
30+
resources: ["pods"]
31+
# list: Needed to find the correct Pod _core_v1.list_namespaced_pod(label_selector=...)
32+
verbs: ["get", "list", "delete"]
33+
- apiGroups: [""]
34+
# get: Needed for _core_v1.read_namespaced_pod_log() to get the code execution results and logs.
35+
resources: ["pods/log"]
36+
verbs: ["get", "list"]
37+
---
38+
apiVersion: rbac.authorization.k8s.io/v1
39+
kind: RoleBinding
40+
metadata:
41+
name: adk-agent-binding
42+
namespace: agent-sandbox
43+
subjects:
44+
- kind: ServiceAccount
45+
name: adk-agent-sa
46+
namespace: agent-sandbox
47+
roleRef:
48+
kind: Role
49+
name: adk-agent-role
50+
apiGroup: rbac.authorization.k8s.io

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ test = [
110110
"langchain-community>=0.3.17",
111111
"langgraph>=0.2.60, <= 0.4.10", # For LangGraphAgent
112112
"litellm>=1.75.5, <2.0.0", # For LiteLLM tests
113+
"kubernetes>=29.0.0", # For GkeCodeExecutor
113114
"llama-index-readers-file>=0.4.0", # For retrieval tests
114115
"openai>=1.100.2", # For LiteLLM
115116
"pytest-asyncio>=0.25.0",
@@ -139,6 +140,7 @@ extensions = [
139140
"docker>=7.0.0", # For ContainerCodeExecutor
140141
"langgraph>=0.2.60", # For LangGraphAgent
141142
"litellm>=1.75.5", # For LiteLlm class. Currently has OpenAI limitations. TODO: once LiteLlm fix it
143+
"kubernetes>=29.0.0", # For GkeCodeExecutor
142144
"llama-index-readers-file>=0.4.0", # For retrieval using LlamaIndex.
143145
"llama-index-embeddings-google-genai>=0.3.0",# For files retrieval using LlamaIndex.
144146
"lxml>=5.3.0", # For load_web_page tool.

src/google/adk/code_executors/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
'UnsafeLocalCodeExecutor',
2929
'VertexAiCodeExecutor',
3030
'ContainerCodeExecutor',
31+
'GkeCodeExecutor',
3132
]
3233

3334

@@ -52,4 +53,14 @@ def __getattr__(name: str):
5253
'ContainerCodeExecutor requires additional dependencies. '
5354
'Please install with: pip install "google-adk[extensions]"'
5455
) from e
56+
elif name == 'GkeCodeExecutor':
57+
try:
58+
from .gke_code_executor import GkeCodeExecutor
59+
60+
return GkeCodeExecutor
61+
except ImportError as e:
62+
raise ImportError(
63+
'GkeCodeExecutor requires additional dependencies. '
64+
'Please install with: pip install "google-adk[extensions]"'
65+
) from e
5566
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")

0 commit comments

Comments
 (0)