Skip to content

Commit 9f9e262

Browse files
raflFaisalfaisal rafiuddin
authored and
faisal rafiuddin
committed
enhancement: generalising docker build and run for multicloud environments
1 parent aa7a98a commit 9f9e262

File tree

9 files changed

+328
-85
lines changed

9 files changed

+328
-85
lines changed

README.md

+20
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,26 @@ coordinator = LlmAgent(
109109
)
110110
```
111111

112+
### 🐳 Docker
113+
114+
Running the Agent Locally as a Docker Container:
115+
116+
```bash
117+
adk deploy cloud_run <agent-folder> --with_ui
118+
```
119+
120+
Running the Agent in Google Cloud (GCP)
121+
122+
```bash
123+
adk deploy cloud_run <agent-folder> --with_ui --cloud-provider gcp
124+
```
125+
126+
You may set the following environment variables in adk command, or in a .env file instead.
127+
128+
```bash
129+
adk deploy cloud_run <agent-folder> --with_ui --cloud-provider gcp --env GOOGLE_GENAI_USE_VERTEXAI=1
130+
```
131+
112132
### Development UI
113133

114134
A built-in development UI to help you test, evaluate, debug, and showcase your agent(s).

src/google/adk/cli/cli_deploy.py

+33-83
Original file line numberDiff line numberDiff line change
@@ -14,68 +14,15 @@
1414

1515
import os
1616
import shutil
17-
import subprocess
18-
from typing import Optional
19-
2017
import click
21-
22-
_DOCKERFILE_TEMPLATE = """
23-
FROM python:3.11-slim
24-
WORKDIR /app
25-
26-
# Create a non-root user
27-
RUN adduser --disabled-password --gecos "" myuser
28-
29-
# Change ownership of /app to myuser
30-
RUN chown -R myuser:myuser /app
31-
32-
# Switch to the non-root user
33-
USER myuser
34-
35-
# Set up environment variables - Start
36-
ENV PATH="/home/myuser/.local/bin:$PATH"
37-
38-
ENV GOOGLE_GENAI_USE_VERTEXAI=1
39-
ENV GOOGLE_CLOUD_PROJECT={gcp_project_id}
40-
ENV GOOGLE_CLOUD_LOCATION={gcp_region}
41-
42-
# Set up environment variables - End
43-
44-
# Install ADK - Start
45-
RUN pip install google-adk
46-
# Install ADK - End
47-
48-
# Copy agent - Start
49-
50-
COPY "agents/{app_name}/" "/app/agents/{app_name}/"
51-
{install_agent_deps}
52-
53-
# Copy agent - End
54-
55-
EXPOSE {port}
56-
57-
CMD adk {command} --port={port} {session_db_option} {trace_to_cloud_option} "/app/agents"
58-
"""
59-
60-
61-
def _resolve_project(project_in_option: Optional[str]) -> str:
62-
if project_in_option:
63-
return project_in_option
64-
65-
result = subprocess.run(
66-
['gcloud', 'config', 'get-value', 'project'],
67-
check=True,
68-
capture_output=True,
69-
text=True,
70-
)
71-
project = result.stdout.strip()
72-
click.echo(f'Use default project: {project}')
73-
return project
74-
18+
from typing import Optional, Tuple
19+
from .deployers.deployer_factory import DeployerFactory
20+
from .config.dockerfile_template import _DOCKERFILE_TEMPLATE
7521

7622
def to_cloud_run(
7723
*,
7824
agent_folder: str,
25+
cloud_provider: str,
7926
project: Optional[str],
8027
region: Optional[str],
8128
service_name: str,
@@ -86,6 +33,8 @@ def to_cloud_run(
8633
with_ui: bool,
8734
verbosity: str,
8835
session_db_url: str,
36+
provider_args: Tuple[str],
37+
env: Tuple[str],
8938
):
9039
"""Deploys an agent to Google Cloud Run.
9140
@@ -104,6 +53,7 @@ def to_cloud_run(
10453
10554
Args:
10655
agent_folder: The folder (absolute path) containing the agent source code.
56+
cloud_provider: Target deployment platform (gcp, local, etc).
10757
project: Google Cloud project id.
10858
region: Google Cloud region.
10959
service_name: The service name in Cloud Run.
@@ -114,10 +64,14 @@ def to_cloud_run(
11464
with_ui: Whether to deploy with UI.
11565
verbosity: The verbosity level of the CLI.
11666
session_db_url: The database URL to connect the session.
67+
provider_args: The arguments specific to cloud provider
68+
env: The environment valriables provided
11769
"""
11870
app_name = app_name or os.path.basename(agent_folder)
71+
mode = 'web' if with_ui else 'api_server'
72+
trace_to_cloud_option = '--trace_to_cloud' if trace_to_cloud else ''
11973

120-
click.echo(f'Start generating Cloud Run source files in {temp_folder}')
74+
click.echo(f'Start generating deployment files in {temp_folder}')
12175

12276
# remove temp_folder if exists
12377
if os.path.exists(temp_folder):
@@ -144,12 +98,12 @@ def to_cloud_run(
14498
gcp_region=region,
14599
app_name=app_name,
146100
port=port,
147-
command='web' if with_ui else 'api_server',
101+
command=mode,
148102
install_agent_deps=install_agent_deps,
149103
session_db_option=f'--session_db_url={session_db_url}'
150104
if session_db_url
151105
else '',
152-
trace_to_cloud_option='--trace_to_cloud' if trace_to_cloud else '',
106+
trace_to_cloud_option=trace_to_cloud_option,
153107
)
154108
dockerfile_path = os.path.join(temp_folder, 'Dockerfile')
155109
os.makedirs(temp_folder, exist_ok=True)
@@ -159,30 +113,26 @@ def to_cloud_run(
159113
)
160114
click.echo(f'Creating Dockerfile complete: {dockerfile_path}')
161115

162-
# Deploy to Cloud Run
163-
click.echo('Deploying to Cloud Run...')
164-
region_options = ['--region', region] if region else []
165-
project = _resolve_project(project)
166-
subprocess.run(
167-
[
168-
'gcloud',
169-
'run',
170-
'deploy',
171-
service_name,
172-
'--source',
173-
temp_folder,
174-
'--project',
175-
project,
176-
*region_options,
177-
'--port',
178-
str(port),
179-
'--verbosity',
180-
verbosity,
181-
'--labels',
182-
'created-by=adk',
183-
],
184-
check=True,
116+
# Deploy using the appropriate deployer
117+
if cloud_provider is None:
118+
cloud_provider = 'local'
119+
120+
click.echo(f'Deploying to {cloud_provider}...')
121+
deployer = DeployerFactory.get_deployer(cloud_provider)
122+
deployer.deploy(
123+
agent_folder=agent_folder,
124+
temp_folder=temp_folder,
125+
service_name=service_name,
126+
provider_args=provider_args,
127+
env_vars=env,
128+
project=project,
129+
region=region,
130+
port=port,
131+
verbosity=verbosity,
185132
)
133+
134+
click.echo(f'Deployment to {cloud_provider} complete.')
135+
186136
finally:
187137
click.echo(f'Cleaning up the temp folder: {temp_folder}')
188138
shutil.rmtree(temp_folder)

src/google/adk/cli/cli_tools_click.py

+25-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import logging
1919
import os
2020
import tempfile
21-
from typing import Optional
21+
from typing import Optional, Tuple
2222

2323
import click
2424
from fastapi import FastAPI
@@ -463,6 +463,24 @@ def cli_api_server(
463463

464464

465465
@deploy.command("cloud_run")
466+
@click.option(
467+
"--cloud-provider",
468+
type=str,
469+
help=(
470+
"Optional. Name of cloud provider where to deploy the agent. When absent,"
471+
" default the agent will be deployed locally as a docker container."
472+
),
473+
)
474+
@click.option(
475+
"--env",
476+
multiple=True,
477+
help="Optional. Environment variables as multiple --env key=value pairs.",
478+
)
479+
@click.option(
480+
"--provider-args",
481+
multiple=True,
482+
help="Optional. Provider-specific arguments as multiple --provider-args key=value pairs.",
483+
)
466484
@click.option(
467485
"--project",
468486
type=str,
@@ -563,6 +581,7 @@ def cli_api_server(
563581
)
564582
def cli_deploy_cloud_run(
565583
agent: str,
584+
cloud_provider: Optional[str],
566585
project: Optional[str],
567586
region: Optional[str],
568587
service_name: str,
@@ -573,6 +592,8 @@ def cli_deploy_cloud_run(
573592
with_ui: bool,
574593
verbosity: str,
575594
session_db_url: str,
595+
provider_args: Tuple[str],
596+
env: Tuple[str],
576597
):
577598
"""Deploys an agent to Cloud Run.
578599
@@ -585,6 +606,7 @@ def cli_deploy_cloud_run(
585606
try:
586607
cli_deploy.to_cloud_run(
587608
agent_folder=agent,
609+
cloud_provider=cloud_provider,
588610
project=project,
589611
region=region,
590612
service_name=service_name,
@@ -595,6 +617,8 @@ def cli_deploy_cloud_run(
595617
with_ui=with_ui,
596618
verbosity=verbosity,
597619
session_db_url=session_db_url,
620+
provider_args=provider_args,
621+
env=env,
598622
)
599623
except Exception as e:
600624
click.secho(f"Deploy failed: {e}", fg="red", err=True)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# config/dockerfile_template.py
2+
3+
_DOCKERFILE_TEMPLATE = """
4+
FROM python:3.11-slim
5+
WORKDIR /app
6+
7+
# Create a non-root user
8+
RUN adduser --disabled-password --gecos "" myuser
9+
10+
# Change ownership of /app to myuser
11+
RUN chown -R myuser:myuser /app
12+
13+
# Switch to the non-root user
14+
USER myuser
15+
16+
# Set up environment variables
17+
ENV PATH="/home/myuser/.local/bin:$PATH"
18+
19+
# Install ADK
20+
RUN pip install google-adk
21+
22+
# Copy agent
23+
COPY "agents/{app_name}/" "/app/agents/{app_name}/"
24+
{install_agent_deps}
25+
26+
EXPOSE {port}
27+
28+
CMD adk {command} --port={port} {session_db_option} {trace_to_cloud_option} "/app/agents"
29+
"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from abc import ABC, abstractmethod
2+
from typing import Dict
3+
4+
class Deployer(ABC):
5+
@abstractmethod
6+
def deploy(self, temp_folder: str, service_name: str, provider_args: Dict[str, str], env_vars: Dict[str, str], **kwargs):
7+
"""Deploys the agent to the target platform."""
8+
pass
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from ..deployers.gcp_deployer import GCPDeployer
2+
from ..deployers.local_docker_deployer import LocalDockerDeployer
3+
# Future deployers can be added here
4+
5+
class DeployerFactory:
6+
@staticmethod
7+
def get_deployer(cloud_provider: str):
8+
"""Returns the appropriate deployer based on the cloud provider."""
9+
deployers = {
10+
'local': LocalDockerDeployer(),
11+
'gcp': GCPDeployer(),
12+
# Future providers: 'aws': AWSDeployer(), 'k8s': KubernetesDeployer()
13+
}
14+
15+
if cloud_provider not in deployers:
16+
raise ValueError(f"Unsupported cloud provider: {cloud_provider}")
17+
18+
return deployers[cloud_provider]

0 commit comments

Comments
 (0)