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

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
175 changes: 166 additions & 9 deletions src/google/adk/cli/cli_deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@

EXPOSE {port}

CMD adk {command} --port={port} {host_option} {service_option} {trace_to_cloud_option} {allow_origins_option} "/app/agents"
CMD adk {command} --port={port} {host_option} {service_option} {trace_to_cloud_option} "/app/agents"
"""

_AGENT_ENGINE_APP_TEMPLATE = """
Expand Down Expand Up @@ -121,10 +121,8 @@ def to_cloud_run(
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,
Expand Down Expand Up @@ -152,7 +150,6 @@ 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.
Expand Down Expand Up @@ -186,9 +183,6 @@ def to_cloud_run(
# create Dockerfile
click.echo('Creating Dockerfile...')
host_option = '--host=0.0.0.0' if adk_version > '0.5.0' else ''
allow_origins_option = (
f'--allow_origins={",".join(allow_origins)}' if allow_origins else ''
)
dockerfile_content = _DOCKERFILE_TEMPLATE.format(
gcp_project_id=project,
gcp_region=region,
Expand All @@ -203,7 +197,6 @@ def to_cloud_run(
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,
)
Expand Down Expand Up @@ -233,7 +226,7 @@ def to_cloud_run(
'--port',
str(port),
'--verbosity',
log_level.lower() if log_level else verbosity,
verbosity,
'--labels',
'created-by=adk',
],
Expand Down Expand Up @@ -414,3 +407,167 @@ 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,
verbosity: str,
adk_version: str,
session_service_uri: Optional[str] = None,
artifact_service_uri: Optional[str] = None,
memory_service_uri: Optional[str] = None,
):
"""Deploys an agent to Google Kubernetes Engine.

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 temp folder for the generated GKE source files.
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.
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.
"""
app_name = app_name or os.path.basename(agent_folder)

click.echo(f'Start generating GKE source files in {temp_folder}')

# remove temp_folder if exists
if os.path.exists(temp_folder):
click.echo('Removing existing files')
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.echo('Copying agent source code complete.')

# create Dockerfile
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 '',
adk_version=adk_version,
host_option=host_option,
)
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.echo(f'Creating Dockerfile complete: {dockerfile_path}')

# Build and push the Docker image
click.echo('Building and pushing the Docker image...')
project = _resolve_project(project)
image_name = f'gcr.io/{project}/{service_name}'
subprocess.run(
['gcloud', 'builds', 'submit', '--tag', image_name, temp_folder],
check=True,
)
click.echo('Building and pushing the Docker image complete.')

# Create a Kubernetes deployment
click.echo('Creating Kubernetes deployment...')
deployment_yaml = f"""
apiVersion: apps/v1
kind: Deployment
metadata:
name: {service_name}
spec:
replicas: 1
selector:
matchLabels:
app: {service_name}
template:
metadata:
labels:
app: {service_name}
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.echo(f'Creating Kubernetes deployment complete: {deployment_yaml_path}')

# Apply the deployment
click.echo('Applying the deployment...')
subprocess.run(
[
'gcloud',
'container',
'clusters',
'get-credentials',
cluster_name,
'--region',
region,
'--project',
project,
],
check=True,
)
subprocess.run(
['kubectl', 'apply', '-f', temp_folder],
check=True,
)
click.echo('Applying the deployment complete.')

finally:
click.echo(f'Cleaning up the temp folder: {temp_folder}')
shutil.rmtree(temp_folder)
Loading
Loading