Skip to content

feat: cli funcionality to deploy an Agent to a running GKE cluster #1607

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
234 changes: 231 additions & 3 deletions src/google/adk/cli/cli_deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,11 +153,11 @@ def to_cloud_run(
app_name: The name of the app, by default, it's basename of `agent_folder`.
temp_folder: The temp folder for the generated Cloud Run source files.
port: The port of the ADK api server.
allow_origins: The list of allowed origins for the ADK api server.
trace_to_cloud: Whether to enable Cloud Trace.
with_ui: Whether to deploy with UI.
verbosity: The verbosity level of the CLI.
adk_version: The ADK version to use in Cloud Run.
allow_origins: The list of allowed origins for the ADK api server.
session_service_uri: The URI of the session service.
artifact_service_uri: The URI of the artifact service.
memory_service_uri: The URI of the memory service.
Expand All @@ -182,7 +182,7 @@ def to_cloud_run(
if os.path.exists(requirements_txt_path)
else ''
)
click.echo('Copying agent source code complete.')
click.echo('Copying agent source code completed.')

# create Dockerfile
click.echo('Creating Dockerfile...')
Expand Down Expand Up @@ -425,7 +425,7 @@ def to_agent_engine(
'async_stream': ['async_stream_query'],
'stream': ['stream_query', 'streaming_agent_run_with_events'],
},
sys_paths=[temp_folder[1:]],
sys_paths=[temp_folder],
)
agent_config = dict(
agent_engine=agent_engine,
Expand All @@ -443,3 +443,231 @@ def to_agent_engine(
finally:
click.echo(f'Cleaning up the temp folder: {temp_folder}')
shutil.rmtree(temp_folder)


def to_gke(
*,
agent_folder: str,
project: Optional[str],
region: Optional[str],
cluster_name: str,
service_name: str,
app_name: str,
temp_folder: str,
port: int,
trace_to_cloud: bool,
with_ui: bool,
log_level: str,
verbosity: str,
adk_version: str,
allow_origins: Optional[list[str]] = None,
session_service_uri: Optional[str] = None,
artifact_service_uri: Optional[str] = None,
memory_service_uri: Optional[str] = None,
a2a: bool = False,
):
"""Deploys an agent to Google Kubernetes Engine(GKE).

Args:
agent_folder: The folder (absolute path) containing the agent source code.
project: Google Cloud project id.
region: Google Cloud region.
cluster_name: The name of the GKE cluster.
service_name: The service name in GKE.
app_name: The name of the app, by default, it's basename of `agent_folder`.
temp_folder: The local directory to use as a temporary workspace for preparing deployment artifacts. The tool populates this folder with a copy of the agent's source code and auto-generates necessary files like a Dockerfile and deployment.yaml.
port: The port of the ADK api server.
trace_to_cloud: Whether to enable Cloud Trace.
with_ui: Whether to deploy with UI.
verbosity: The verbosity level of the CLI.
adk_version: The ADK version to use in GKE.
allow_origins: The list of allowed origins for the ADK api server.
session_service_uri: The URI of the session service.
artifact_service_uri: The URI of the artifact service.
memory_service_uri: The URI of the memory service.
"""
click.secho(
'\n🚀 Starting ADK Agent Deployment to GKE...', fg='cyan', bold=True
)
click.echo('--------------------------------------------------')
# Resolve project early to show the user which one is being used
project = _resolve_project(project)
click.echo(f' Project: {project}')
click.echo(f' Region: {region}')
click.echo(f' Cluster: {cluster_name}')
click.echo('--------------------------------------------------\n')

app_name = app_name or os.path.basename(agent_folder)

click.secho('STEP 1: Preparing build environment...', bold=True)
click.echo(f' - Using temporary directory: {temp_folder}')

# remove temp_folder if exists
if os.path.exists(temp_folder):
click.echo(' - Removing existing temporary directory...')
shutil.rmtree(temp_folder)

try:
# copy agent source code
click.echo(' - Copying agent source code...')
agent_src_path = os.path.join(temp_folder, 'agents', app_name)
shutil.copytree(agent_folder, agent_src_path)
requirements_txt_path = os.path.join(agent_src_path, 'requirements.txt')
install_agent_deps = (
f'RUN pip install -r "/app/agents/{app_name}/requirements.txt"'
if os.path.exists(requirements_txt_path)
else ''
)
click.secho('✅ Environment prepared.', fg='green')

allow_origins_option = (
f'--allow_origins={",".join(allow_origins)}' if allow_origins else ''
)

# create Dockerfile
click.secho('\nSTEP 2: Generating deployment files...', bold=True)
click.echo(' - Creating Dockerfile...')
host_option = '--host=0.0.0.0' if adk_version > '0.5.0' else ''
dockerfile_content = _DOCKERFILE_TEMPLATE.format(
gcp_project_id=project,
gcp_region=region,
app_name=app_name,
port=port,
command='web' if with_ui else 'api_server',
install_agent_deps=install_agent_deps,
service_option=_get_service_option_by_adk_version(
adk_version,
session_service_uri,
artifact_service_uri,
memory_service_uri,
),
trace_to_cloud_option='--trace_to_cloud' if trace_to_cloud else '',
allow_origins_option=allow_origins_option,
adk_version=adk_version,
host_option=host_option,
a2a_option='--a2a' if a2a else '',
)
dockerfile_path = os.path.join(temp_folder, 'Dockerfile')
os.makedirs(temp_folder, exist_ok=True)
with open(dockerfile_path, 'w', encoding='utf-8') as f:
f.write(
dockerfile_content,
)
click.secho(f'✅ Dockerfile generated: {dockerfile_path}', fg='green')

# Build and push the Docker image
click.secho(
'\nSTEP 3: Building container image with Cloud Build...', bold=True
)
click.echo(
' (This may take a few minutes. Raw logs from gcloud will be shown'
' below.)'
)
project = _resolve_project(project)
image_name = f'gcr.io/{project}/{service_name}'
subprocess.run(
[
'gcloud',
'builds',
'submit',
'--tag',
image_name,
'--verbosity',
log_level.lower() if log_level else verbosity,
temp_folder,
],
check=True,
)
click.secho('✅ Container image built and pushed successfully.', fg='green')

# Create a Kubernetes deployment
click.echo(' - Creating Kubernetes deployment.yaml...')
deployment_yaml = f"""
apiVersion: apps/v1
kind: Deployment
metadata:
name: {service_name}
labels:
app.kubernetes.io/name: adk-agent
app.kubernetes.io/version: {adk_version}
app.kubernetes.io/instance: {service_name}
app.kubernetes.io/managed-by: adk-cli
spec:
replicas: 1
selector:
matchLabels:
app: {service_name}
template:
metadata:
labels:
app: {service_name}
app.kubernetes.io/name: adk-agent
app.kubernetes.io/version: {adk_version}
app.kubernetes.io/instance: {service_name}
app.kubernetes.io/managed-by: adk-cli
spec:
containers:
- name: {service_name}
image: {image_name}
ports:
- containerPort: {port}
---
apiVersion: v1
kind: Service
metadata:
name: {service_name}
spec:
type: LoadBalancer
selector:
app: {service_name}
ports:
- port: 80
targetPort: {port}
"""
deployment_yaml_path = os.path.join(temp_folder, 'deployment.yaml')
with open(deployment_yaml_path, 'w', encoding='utf-8') as f:
f.write(deployment_yaml)
click.secho(
f'✅ Kubernetes deployment manifest generated: {deployment_yaml_path}',
fg='green',
)

# Apply the deployment
click.secho('\nSTEP 4: Applying deployment to GKE cluster...', bold=True)
click.echo(' - Getting cluster credentials...')
subprocess.run(
[
'gcloud',
'container',
'clusters',
'get-credentials',
cluster_name,
'--region',
region,
'--project',
project,
],
check=True,
)
click.echo(' - Applying Kubernetes manifest...')
result = subprocess.run(
['kubectl', 'apply', '-f', temp_folder],
check=True,
capture_output=True, # <-- Add this
text=True, # <-- Add this
)

# 2. Print the captured output line by line
click.secho(
' - The following resources were applied to the cluster:', fg='green'
)
for line in result.stdout.strip().split('\n'):
click.echo(f' - {line}')

finally:
click.secho('\nSTEP 5: Cleaning up...', bold=True)
click.echo(f' - Removing temporary directory: {temp_folder}')
shutil.rmtree(temp_folder)
click.secho(
'\n🎉 Deployment to GKE finished successfully!', fg='cyan', bold=True
)
Loading