diff --git a/sdk/python/feast/infra/feature_servers/gcp_cloudrun/Dockerfile b/sdk/python/feast/infra/feature_servers/gcp_cloudrun/Dockerfile new file mode 100644 index 0000000000..b4e1e4adb9 --- /dev/null +++ b/sdk/python/feast/infra/feature_servers/gcp_cloudrun/Dockerfile @@ -0,0 +1,28 @@ +FROM python:3.9-slim + +# Allow statements and log messages to immediately appear in the Knative logs +ENV PYTHONUNBUFFERED True + +# Copy local code to the container image. +ENV APP_HOME /app +WORKDIR $APP_HOME + +# Copy app handler code +COPY sdk/python/feast/infra/feature_servers/gcp_cloudrun/app.py ./app.py + +# Copy necessary parts of the Feast codebase +COPY sdk/python ./sdk/python +COPY protos ./protos +COPY README.md ./README.md + +# Install production dependencies. +RUN pip install --no-cache-dir \ + -e 'sdk/python[gcp,redis]' \ + -r ./sdk/python/feast/infra/feature_servers/gcp_cloudrun/requirements.txt + +# Run the web service on container startup. Here we use the gunicorn +# webserver, with one worker process and 8 threads. +# For environments with multiple CPU cores, increase the number of workers +# to be equal to the cores available. +# Timeout is set to 0 to disable the timeouts of the workers to allow Cloud Run to handle instance scaling. +CMD exec gunicorn -k uvicorn.workers.UvicornWorker --bind :$PORT --workers 1 --threads 8 --timeout 0 app:app diff --git a/sdk/python/feast/infra/feature_servers/gcp_cloudrun/app.py b/sdk/python/feast/infra/feature_servers/gcp_cloudrun/app.py new file mode 100644 index 0000000000..06749b0cd3 --- /dev/null +++ b/sdk/python/feast/infra/feature_servers/gcp_cloudrun/app.py @@ -0,0 +1,24 @@ +import base64 +import os +import tempfile +from pathlib import Path + +from feast import FeatureStore +from feast.constants import FEATURE_STORE_YAML_ENV_NAME +from feast.feature_server import get_app + +# Load RepoConfig +config_base64 = os.environ[FEATURE_STORE_YAML_ENV_NAME] +config_bytes = base64.b64decode(config_base64) + +# Create a new unique directory for writing feature_store.yaml +repo_path = Path(tempfile.mkdtemp()) + +with open(repo_path / "feature_store.yaml", "wb") as f: + f.write(config_bytes) + +# Initialize the feature store +store = FeatureStore(repo_path=str(repo_path.resolve())) + +# Create the FastAPI app +app = get_app(store) diff --git a/sdk/python/feast/infra/feature_servers/gcp_cloudrun/config.py b/sdk/python/feast/infra/feature_servers/gcp_cloudrun/config.py new file mode 100644 index 0000000000..728ac56ae2 --- /dev/null +++ b/sdk/python/feast/infra/feature_servers/gcp_cloudrun/config.py @@ -0,0 +1,20 @@ +from pydantic import StrictBool +from pydantic.typing import Literal + +from feast.repo_config import FeastConfigBaseModel + + +class GcpCloudRunFeatureServerConfig(FeastConfigBaseModel): + """Feature server config for GCP CloudRun.""" + + type: Literal["gcp_cloudrun"] = "gcp_cloudrun" + """Feature server type selector.""" + + enabled: StrictBool = False + """Whether the feature server should be launched.""" + + public: StrictBool = True + """Whether the endpoint should be publicly accessible.""" + + auth: Literal["none", "api-key"] = "none" + """Authentication method for the endpoint.""" diff --git a/sdk/python/feast/infra/feature_servers/gcp_cloudrun/requirements.txt b/sdk/python/feast/infra/feature_servers/gcp_cloudrun/requirements.txt new file mode 100644 index 0000000000..8f22dccf99 --- /dev/null +++ b/sdk/python/feast/infra/feature_servers/gcp_cloudrun/requirements.txt @@ -0,0 +1 @@ +gunicorn diff --git a/sdk/python/feast/repo_config.py b/sdk/python/feast/repo_config.py index 1aa6ec2f7a..5bbe09d8e0 100644 --- a/sdk/python/feast/repo_config.py +++ b/sdk/python/feast/repo_config.py @@ -41,6 +41,7 @@ FEATURE_SERVER_CONFIG_CLASS_FOR_TYPE = { "aws_lambda": "feast.infra.feature_servers.aws_lambda.config.AwsLambdaFeatureServerConfig", + "gcp_cloudrun": "feast.infra.feature_servers.gcp_cloudrun.config.GcpCloudRunFeatureServerConfig", }