From 8550c00aa35c4d463b0136b6242b9d9a97371c7f Mon Sep 17 00:00:00 2001 From: Danny Chiao Date: Fri, 19 Aug 2022 14:45:29 -0400 Subject: [PATCH 01/10] feat: Refactor feature server helm charts to allow passing feature_store.yaml in environment variables Signed-off-by: Danny Chiao --- CONTRIBUTING.md | 27 ++++++ Makefile | 12 +++ examples/java-demo/README.md | 9 +- .../java-demo/feature_repo/feature_store.yaml | 1 + examples/python-helm-demo/README.md | 89 ++++++++++++++++++ .../python-helm-demo/feature_repo/__init__.py | 0 .../data/driver_stats_with_string.parquet | Bin 0 -> 35310 bytes .../feature_repo/driver_repo.py | 61 ++++++++++++ .../feature_repo/feature_store.yaml | 10 ++ .../feature_repo/test_python_fetch.py | 43 +++++++++ .../python-helm-demo/redis-screenshot.png | Bin 0 -> 77442 bytes infra/charts/feast-feature-server/README.md | 56 +++-------- .../feast-feature-server/README.md.gotmpl | 49 ++-------- .../templates/deployment.yaml | 3 + infra/charts/feast-feature-server/values.yaml | 11 ++- infra/charts/feast-python-server/README.md | 4 +- .../feast-python-server/README.md.gotmpl | 4 +- sdk/python/feast/cli.py | 23 ++++- .../feature_servers/multicloud/Dockerfile | 12 +++ .../feature_servers/multicloud/Dockerfile.dev | 14 +++ .../feature_servers/multicloud/__init__.py | 0 21 files changed, 330 insertions(+), 98 deletions(-) create mode 100644 examples/python-helm-demo/README.md create mode 100644 examples/python-helm-demo/feature_repo/__init__.py create mode 100644 examples/python-helm-demo/feature_repo/data/driver_stats_with_string.parquet create mode 100644 examples/python-helm-demo/feature_repo/driver_repo.py create mode 100644 examples/python-helm-demo/feature_repo/feature_store.yaml create mode 100644 examples/python-helm-demo/feature_repo/test_python_fetch.py create mode 100644 examples/python-helm-demo/redis-screenshot.png create mode 100644 sdk/python/feast/infra/feature_servers/multicloud/Dockerfile create mode 100644 sdk/python/feast/infra/feature_servers/multicloud/Dockerfile.dev create mode 100644 sdk/python/feast/infra/feature_servers/multicloud/__init__.py diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6d5b21d4cc8..b78f6c704d8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -388,6 +388,33 @@ go test * Pushes will now run your edited workflow yaml file against your test code. * Unfortunately, in order to test any github workflow changes, you must push the code to the branch and see the output in the actions tab. +## Developing the Feast Helm charts +There are 3 helm charts: +- Feast Java feature server +- Feast Python / Go feature server +- (deprecated) Feast Python feature server + +Generally, you can override the images in the helm charts with locally built Docker images, and install the local helm +chart. + +All README's for helm charts are generated using [helm-docs](https://github.com/norwoodj/helm-docs). You can install it +(e.g. with `brew install norwoodj/tap/helm-docs`) and then run `make build-helm-docs`. + +### Feast Java Feature Server Helm Chart +See the Java demo example (it has development instructions too using minikube) [here](examples/java-demo/README.md) + +It will: +- run `make build-java-docker-dev` to build local Java feature server binaries +- configure the included `application-override.yaml` to override the image tag to use the locally built dev images. +- install the local chart with `helm install feast-release ../../../infra/charts/feast --values application-override.yaml` + +### Feast Python / Go Feature Server Helm Chart +See the Python demo example (it has development instructions too using minikube) [here](examples/python-helm-demo/README.md) + +It will: +- run `make build-feature-server-dev` to build a local python feature server binary +- install the local chart with `helm install feast-release ../../../infra/charts/feast-feature-server --set image.tag=dev --set feature_store_yaml_base64=$(base64 feature_store.yaml)` + ## Issues * pr-integration-tests workflow is skipped * Add `ok-to-test` github label. diff --git a/Makefile b/Makefile index a2d8bca5e49..f87f53ef5b0 100644 --- a/Makefile +++ b/Makefile @@ -363,6 +363,13 @@ build-feature-server-java-docker: -t $(REGISTRY)/feature-server-java:$(VERSION) \ -f java/infra/docker/feature-server/Dockerfile --load . +# Dev images + +build-feature-server-dev: + docker buildx build --build-arg VERSION=dev \ + -t feastdev/feature-server:dev \ + -f sdk/python/feast/infra/feature_servers/multicloud/Dockerfile.dev --load . + build-java-docker-dev: make build-java-no-tests REVISION=dev docker buildx build --build-arg VERSION=dev \ @@ -402,6 +409,11 @@ build-sphinx: compile-protos-python build-templates: python infra/scripts/compile-templates.py +build-helm-docs: + cd ${ROOT_DIR}/infra/charts/feast; helm-docs + cd ${ROOT_DIR}/infra/charts/feast-feature-server; helm-docs + cd ${ROOT_DIR}/infra/charts/feast-python-server; helm-docs + # Web UI # Note: requires node and yarn to be installed diff --git a/examples/java-demo/README.md b/examples/java-demo/README.md index ef4960fb4d1..0ae085e0a7a 100644 --- a/examples/java-demo/README.md +++ b/examples/java-demo/README.md @@ -30,18 +30,21 @@ For this tutorial, we setup Feast with Redis, using the Feast CLI to register an 2. Make a bucket in GCS (or S3) 3. The feature repo is already setup here, so you just need to swap in your GCS bucket and Redis credentials. We need to modify the `feature_store.yaml`, which has two fields for you to replace: - ```yaml - registry: gs://[YOUR BUCKET]/demo-repo/registry.db + ```yaml + registry: gs://[YOUR GCS BUCKET]/demo-repo/registry.db project: feast_java_demo provider: gcp online_store: type: redis + # Note: this would normally be using instance URL's to access Redis connection_string: localhost:6379,password=[YOUR PASSWORD] offline_store: type: file + entity_key_serialization_version: 2 ``` 4. Run `feast apply` to apply your local features to the remote registry -5. Materialize features to the online store: + - Note: you may need to authenticate to gcloud first with `gcloud auth login` +6. Materialize features to the online store: ```bash CURRENT_TIME=$(date -u +"%Y-%m-%dT%H:%M:%S") feast materialize-incremental $CURRENT_TIME diff --git a/examples/java-demo/feature_repo/feature_store.yaml b/examples/java-demo/feature_repo/feature_store.yaml index 29fe3c97ce6..16d426fc5a8 100644 --- a/examples/java-demo/feature_repo/feature_store.yaml +++ b/examples/java-demo/feature_repo/feature_store.yaml @@ -3,6 +3,7 @@ project: feast_java_demo provider: gcp online_store: type: redis + # Note: this would normally be using instance URL's to access Redis connection_string: localhost:6379,password=[YOUR PASSWORD] offline_store: type: file diff --git a/examples/python-helm-demo/README.md b/examples/python-helm-demo/README.md new file mode 100644 index 00000000000..44cd4799d56 --- /dev/null +++ b/examples/python-helm-demo/README.md @@ -0,0 +1,89 @@ + +# Running Feast Python / Go Feature Server with Redis on Kubernetes + +For this tutorial, we set up Feast with Redis. + +We use the Feast CLI to register and materialize features, and then retrieving via a Feast Python feature server deployed in Kubernetes + +## First, let's set up a Redis cluster +1. Start minikube (`minikube start`) +2. Use helm to install a default Redis cluster + ```bash + helm repo add bitnami https://charts.bitnami.com/bitnami + helm repo update + helm install my-redis bitnami/redis + ``` + ![](redis-screenshot.png) +3. Port forward Redis so we can materialize features to it + + ```bash + kubectl port-forward --namespace default svc/my-redis-master 6379:6379 + ``` +4. Get your Redis password using the command (pasted below for convenience). We'll need this to tell Feast how to communicate with the cluster. + + ```bash + export REDIS_PASSWORD=$(kubectl get secret --namespace default my-redis -o jsonpath="{.data.redis-password}" | base64 --decode) + echo $REDIS_PASSWORD + ``` + +## Next, we setup a local Feast repo +1. Install Feast with Redis dependencies `pip install "feast[redis]"` +2. Make a bucket in GCS (or S3) +3. The feature repo is already setup here, so you just need to swap in your GCS bucket and Redis credentials. + We need to modify the `feature_store.yaml`, which has two fields for you to replace: + ```yaml + registry: gs://[YOUR GCS BUCKET]/demo-repo/registry.db + project: feast_python_demo + provider: gcp + online_store: + type: redis + # Note: this would normally be using instance URL's to access Redis + connection_string: localhost:6379,password=[YOUR PASSWORD] + offline_store: + type: file + entity_key_serialization_version: 2 + ``` +4. Run `feast apply` from within the `feature_repo` directory to apply your local features to the remote registry + - Note: you may need to authenticate to gcloud first with `gcloud auth login` +5. Materialize features to the online store: + ```bash + CURRENT_TIME=$(date -u +"%Y-%m-%dT%H:%M:%S") + feast materialize-incremental $CURRENT_TIME + ``` + +## Now let's setup the Feast Server +1. Add the gcp-auth addon to mount GCP credentials: + ```bash + minikube addons enable gcp-auth + ``` +2. Add Feast's Python/Go feature server chart repo + ```bash + helm repo add feast-charts https://feast-helm-charts.storage.googleapis.com + helm repo update + ``` +3. For this tutorial, because we don't have a direct hosted endpoint into Redis, we need to change `feature_store.yaml` to talk to the Kubernetes Redis service + ```bash + sed -i '' 's/localhost:6379/my-redis-master:6379/g' feature_store.yaml + ``` +4. Install the Feast helm chart: `helm install feast-release feast-charts/feast-feature-server --set feature_store_yaml_base64=$(base64 feature_store.yaml)` + > **Dev instructions**: if you're changing the java logic or chart, you can do + 1. `eval $(minikube docker-env)` + 2. `make build-feature-server-dev` + 3. `helm install feast-release ../../../infra/charts/feast-feature-server --set image.tag=dev --set feature_store_yaml_base64=$(base64 feature_store.yaml)` +5. (Optional): check logs of the server to make sure it’s working + ```bash + kubectl logs svc/feast-feature-server + ``` +6. Port forward to expose the grpc endpoint: + ```bash + kubectl port-forward svc/feast-feature-server 6566:80 + ``` +7. Run test fetches for online features:8. + - First: change back the Redis connection string to allow localhost connections to Redis + ```bash + sed -i '' 's/my-redis-master:6379/localhost:6379/g' feature_store.yaml + ``` + - Then run the included fetch script, which fetches both via the HTTP endpoint and for comparison, via the Python SDK + ```bash + python test_python_fetch.py + ``` \ No newline at end of file diff --git a/examples/python-helm-demo/feature_repo/__init__.py b/examples/python-helm-demo/feature_repo/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/python-helm-demo/feature_repo/data/driver_stats_with_string.parquet b/examples/python-helm-demo/feature_repo/data/driver_stats_with_string.parquet new file mode 100644 index 0000000000000000000000000000000000000000..83b8c31aa51a5bc273fdce897fbe8a8473179d92 GIT binary patch literal 35310 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 pd.DataFrame: + df = pd.DataFrame() + df["conv_rate_plus_val1"] = inputs["conv_rate"] + inputs["val_to_add"] + df["conv_rate_plus_val2"] = inputs["conv_rate"] + inputs["val_to_add_2"] + return df + diff --git a/examples/python-helm-demo/feature_repo/feature_store.yaml b/examples/python-helm-demo/feature_repo/feature_store.yaml new file mode 100644 index 00000000000..d49c0cbd0eb --- /dev/null +++ b/examples/python-helm-demo/feature_repo/feature_store.yaml @@ -0,0 +1,10 @@ +registry: gs://[YOUR GCS BUCKET]/demo-repo/registry.db +project: feast_python_demo +provider: gcp +online_store: + type: redis + # Note: this would normally be using instance URL's to access Redis + connection_string: localhost:6379,password=[YOUR PASSWORD] +offline_store: + type: file +entity_key_serialization_version: 2 \ No newline at end of file diff --git a/examples/python-helm-demo/feature_repo/test_python_fetch.py b/examples/python-helm-demo/feature_repo/test_python_fetch.py new file mode 100644 index 00000000000..9889cc88d8f --- /dev/null +++ b/examples/python-helm-demo/feature_repo/test_python_fetch.py @@ -0,0 +1,43 @@ +from feast import FeatureStore +import requests +import json + + +def run_demo_http(): + print("\n--- Online features with HTTP endpoint ---") + online_request = { + "features": [ + "driver_hourly_stats:conv_rate", + ], + "entities": + {"driver_id": [1001, 1002]} + + } + r = requests.post('http://localhost:6566/get-online-features', data=json.dumps(online_request)) + print(json.dumps(r.json(), indent=4, sort_keys=True)) + + +def run_demo_sdk(): + store = FeatureStore(repo_path=".") + + print("\n--- Online features with SDK ---") + features = store.get_online_features( + features=[ + "driver_hourly_stats:conv_rate", + ], + entity_rows=[ + { + "driver_id": 1001, + }, + { + "driver_id": 1002, + } + ], + ).to_dict() + for key, value in sorted(features.items()): + print(key, " : ", value) + + +if __name__ == "__main__": + run_demo_sdk() + run_demo_http() diff --git a/examples/python-helm-demo/redis-screenshot.png b/examples/python-helm-demo/redis-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..489deb699dd184e68e004b598a7f03458d6f2c6a GIT binary patch literal 77442 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
{u2`hN>6{ZU6)WI&1WsiH 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%0Ag{BiPm(seQy~ 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 literal 0 HcmV?d00001 diff --git a/infra/charts/feast-feature-server/README.md b/infra/charts/feast-feature-server/README.md index c5c2b9d6542..2abf158bc44 100644 --- a/infra/charts/feast-feature-server/README.md +++ b/infra/charts/feast-feature-server/README.md @@ -3,59 +3,21 @@ Current chart version is `0.23.0` ## Installation -### Python feature server -Docker repository and tag are required. Helm install example: +A base64 encoded version of the `feature_store.yaml` file is needed. Helm install example: ``` -helm install feast-feature-server . --set image.repository=REPO --set image.tag=TAG +helm install feast-feature-server . --set feature_store_yaml_base64=$(base64 feature_store.yaml) ``` -Deployment assumes that `feature_store.yaml` exists on docker image. Example docker image: -``` -FROM python:3.8 - -RUN apt update && \ - apt install -y jq - -RUN pip install pip --upgrade - -RUN pip install feast - -COPY feature_store.yaml /feature_store.yaml -``` - -### Go feature server -> Warning: this is experimental, and only supports a local file registry + Redis - -Furthermore, if you wish to use the Go feature server, then you must install the Apache Arrow C++ libraries, and your `feature_store.yaml` should include `go_feature_server: True`. -For more details, see the [docs](https://docs.feast.dev/reference/feature-servers/go-feature-server). - -The docker image might look like: -``` - FROM python:3.8 - - RUN apt update && \ - apt install -y jq - RUN pip install pip --upgrade - RUN pip install feast - RUN apt update - RUN apt install -y -V ca-certificates lsb-release wget - RUN wget https://apache.jfrog.io/artifactory/arrow/$(lsb_release --id --short | tr 'A-Z' 'a-z')/apache-arrow-apt-source-latest-$(lsb_release --codename --short).deb - RUN apt install -y -V ./apache-arrow-apt-source-latest-$(lsb_release --codename --short).deb - RUN apt update - RUN apt -y install libarrow-dev - - COPY feature_store.yaml /feature_store.yaml - ``` - ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| | affinity | object | `{}` | | +| feature_store_yaml_base64 | string | `""` | [required] a base64 encoded version of feature_store.yaml | | fullnameOverride | string | `""` | | | image.pullPolicy | string | `"IfNotPresent"` | | -| image.repository | string | `""` | [required] The repository for the Docker image | -| image.tag | string | `""` | [required] The Docker image tag | +| image.repository | string | `"feastdev/feature-server"` | Docker image for Feature Server repository | +| image.tag | string | `"0.23.0"` | The Docker image tag (can be overwritten if custom feature server deps are needed for on demand transforms) | | imagePullSecrets | list | `[]` | | | livenessProbe.initialDelaySeconds | int | `30` | | | livenessProbe.periodSeconds | int | `30` | | @@ -70,4 +32,10 @@ The docker image might look like: | securityContext | object | `{}` | | | service.port | int | `80` | | | service.type | string | `"ClusterIP"` | | -| tolerations | list | `[]` | | \ No newline at end of file +| tolerations | list | `[]` | | + +## Development +To build a local +``` +helm install feast-python-server . --set image.repository=REPO --set image.tag=TAG +``` \ No newline at end of file diff --git a/infra/charts/feast-feature-server/README.md.gotmpl b/infra/charts/feast-feature-server/README.md.gotmpl index 79a681b3fcc..b53ffefc4af 100644 --- a/infra/charts/feast-feature-server/README.md.gotmpl +++ b/infra/charts/feast-feature-server/README.md.gotmpl @@ -3,50 +3,17 @@ Current chart version is `{{ template "chart.version" . }}` ## Installation -### Python feature server -Docker repository and tag are required. Helm install example: +A base64 encoded version of the `feature_store.yaml` file is needed. Helm install example: ``` -helm install feast-feature-server . --set image.repository=REPO --set image.tag=TAG +helm install feast-feature-server . --set feature_store_yaml_base64=$(base64 feature_store.yaml) ``` -Deployment assumes that `feature_store.yaml` exists on docker image. Example docker image: -``` -FROM python:3.8 - -RUN apt update && \ - apt install -y jq - -RUN pip install pip --upgrade - -RUN pip install feast - -COPY feature_store.yaml /feature_store.yaml -``` - -### Go feature server -> Warning: this is experimental, and only supports a local file registry + Redis +{{ template "chart.requirementsSection" . }} -Furthermore, if you wish to use the Go feature server, then you must install the Apache Arrow C++ libraries, and your `feature_store.yaml` should include `go_feature_server: True`. -For more details, see the [docs](https://docs.feast.dev/reference/feature-servers/go-feature-server). +{{ template "chart.valuesSection" . }} -The docker image might look like: +## Development +To build a local ``` - FROM python:3.8 - - RUN apt update && \ - apt install -y jq - RUN pip install pip --upgrade - RUN pip install feast - RUN apt update - RUN apt install -y -V ca-certificates lsb-release wget - RUN wget https://apache.jfrog.io/artifactory/arrow/$(lsb_release --id --short | tr 'A-Z' 'a-z')/apache-arrow-apt-source-latest-$(lsb_release --codename --short).deb - RUN apt install -y -V ./apache-arrow-apt-source-latest-$(lsb_release --codename --short).deb - RUN apt update - RUN apt -y install libarrow-dev - - COPY feature_store.yaml /feature_store.yaml - ``` - -{{ template "chart.requirementsSection" . }} - -{{ template "chart.valuesSection" . }} \ No newline at end of file +helm install feast-python-server . --set image.repository=REPO --set image.tag=TAG +``` \ No newline at end of file diff --git a/infra/charts/feast-feature-server/templates/deployment.yaml b/infra/charts/feast-feature-server/templates/deployment.yaml index 69cf92f6c02..94c56de9dda 100644 --- a/infra/charts/feast-feature-server/templates/deployment.yaml +++ b/infra/charts/feast-feature-server/templates/deployment.yaml @@ -30,6 +30,9 @@ spec: {{- toYaml .Values.securityContext | nindent 12 }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} + env: + - name: FEATURE_STORE_YAML_BASE64 + value: {{ .Values.feature_store_yaml_base64 }} command: ["feast", "serve", "-h", "0.0.0.0"] ports: - name: http diff --git a/infra/charts/feast-feature-server/values.yaml b/infra/charts/feast-feature-server/values.yaml index 6d0ab9c0ae4..9ffcb1485b7 100644 --- a/infra/charts/feast-feature-server/values.yaml +++ b/infra/charts/feast-feature-server/values.yaml @@ -5,16 +5,19 @@ replicaCount: 1 image: - # image.repository -- [required] The repository for the Docker image - repository: "" + # image.repository -- Docker image for Feature Server repository + repository: feastdev/feature-server pullPolicy: IfNotPresent - # image.tag -- [required] The Docker image tag - tag: "" + # image.tag -- The Docker image tag (can be overwritten if custom feature server deps are needed for on demand transforms) + tag: 0.23.0 imagePullSecrets: [] nameOverride: "" fullnameOverride: "" +# feature_store_yaml_base64 -- [required] a base64 encoded version of feature_store.yaml +feature_store_yaml_base64: "" + podAnnotations: {} podSecurityContext: {} diff --git a/infra/charts/feast-python-server/README.md b/infra/charts/feast-python-server/README.md index 2c8264c35c4..b0bc0be1e61 100644 --- a/infra/charts/feast-python-server/README.md +++ b/infra/charts/feast-python-server/README.md @@ -1,4 +1,6 @@ -# Feast Python Feature Server Helm Charts +# Feast Python Feature Server Helm Charts (deprecated) + +> Note: this helm chart is deprecated in favor of [feast-feature-server](../feast-feature-server/README.md) Current chart version is `0.23.0` diff --git a/infra/charts/feast-python-server/README.md.gotmpl b/infra/charts/feast-python-server/README.md.gotmpl index b9c88d11b43..cb264c0066a 100644 --- a/infra/charts/feast-python-server/README.md.gotmpl +++ b/infra/charts/feast-python-server/README.md.gotmpl @@ -1,4 +1,6 @@ -# Feast Python Feature Server Helm Charts +# Feast Python Feature Server Helm Charts (deprecated) + +> Note: this helm chart is deprecated in favor of [feast-feature-server](../feast-feature-server/README.md) Current chart version is `{{ template "chart.version" . }}` diff --git a/sdk/python/feast/cli.py b/sdk/python/feast/cli.py index f9d8bf37b6c..1ab5cf2f863 100644 --- a/sdk/python/feast/cli.py +++ b/sdk/python/feast/cli.py @@ -11,11 +11,14 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import base64 import json import logging +import tempfile from datetime import datetime from pathlib import Path from typing import List, Optional +import os import click import pkg_resources @@ -25,7 +28,7 @@ from pygments import formatters, highlight, lexers from feast import utils -from feast.constants import DEFAULT_FEATURE_TRANSFORMATION_SERVER_PORT +from feast.constants import DEFAULT_FEATURE_TRANSFORMATION_SERVER_PORT, FEATURE_STORE_YAML_ENV_NAME from feast.errors import FeastObjectNotFoundException, FeastProviderLoginError from feast.feature_store import FeatureStore from feast.feature_view import FeatureView @@ -685,9 +688,21 @@ def serve_command( ): """Start a feature server locally on a given port.""" repo = ctx.obj["CHDIR"] - fs_yaml_file = ctx.obj["FS_YAML_FILE"] - cli_check_repo(repo, fs_yaml_file) - store = FeatureStore(repo_path=str(repo), fs_yaml_file=fs_yaml_file) + + # If we received a base64 encoded version of feature_store.yaml, use that + config_base64 = os.getenv(FEATURE_STORE_YAML_ENV_NAME) + if config_base64: + print("Received base64 encoded feature_store.yaml") + 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) + store = FeatureStore(repo_path=str(repo_path.resolve())) + else: + fs_yaml_file = ctx.obj["FS_YAML_FILE"] + cli_check_repo(repo, fs_yaml_file) + store = FeatureStore(repo_path=str(repo), fs_yaml_file=fs_yaml_file) if go: # Turn on Go feature retrieval. diff --git a/sdk/python/feast/infra/feature_servers/multicloud/Dockerfile b/sdk/python/feast/infra/feature_servers/multicloud/Dockerfile new file mode 100644 index 00000000000..b853411e273 --- /dev/null +++ b/sdk/python/feast/infra/feature_servers/multicloud/Dockerfile @@ -0,0 +1,12 @@ +FROM python:3.8 + +RUN apt update && \ + apt install -y jq +RUN pip install pip --upgrade +RUN pip install "feast[aws,gcp,snowflake,redis,go]" +RUN apt update +RUN apt install -y -V ca-certificates lsb-release wget +RUN wget https://apache.jfrog.io/artifactory/arrow/$(lsb_release --id --short | tr 'A-Z' 'a-z')/apache-arrow-apt-source-latest-$(lsb_release --codename --short).deb +RUN apt install -y -V ./apache-arrow-apt-source-latest-$(lsb_release --codename --short).deb +RUN apt update +RUN apt -y install libarrow-dev \ No newline at end of file diff --git a/sdk/python/feast/infra/feature_servers/multicloud/Dockerfile.dev b/sdk/python/feast/infra/feature_servers/multicloud/Dockerfile.dev new file mode 100644 index 00000000000..f1dd7cc390b --- /dev/null +++ b/sdk/python/feast/infra/feature_servers/multicloud/Dockerfile.dev @@ -0,0 +1,14 @@ +FROM python:3.8 + +RUN apt update && \ + apt install -y jq +RUN pip install pip --upgrade +COPY . . + +RUN pip install ".[aws,gcp,snowflake,redis,go]" +RUN apt update +RUN apt install -y -V ca-certificates lsb-release wget +RUN wget https://apache.jfrog.io/artifactory/arrow/$(lsb_release --id --short | tr 'A-Z' 'a-z')/apache-arrow-apt-source-latest-$(lsb_release --codename --short).deb +RUN apt install -y -V ./apache-arrow-apt-source-latest-$(lsb_release --codename --short).deb +RUN apt update +RUN apt -y install libarrow-dev \ No newline at end of file diff --git a/sdk/python/feast/infra/feature_servers/multicloud/__init__.py b/sdk/python/feast/infra/feature_servers/multicloud/__init__.py new file mode 100644 index 00000000000..e69de29bb2d From cc4b1681fc6061fbcb12fe23c96ad989cfdd02a4 Mon Sep 17 00:00:00 2001 From: Danny Chiao Date: Fri, 19 Aug 2022 14:46:30 -0400 Subject: [PATCH 02/10] lint Signed-off-by: Danny Chiao --- sdk/python/feast/cli.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sdk/python/feast/cli.py b/sdk/python/feast/cli.py index 1ab5cf2f863..af9aa511918 100644 --- a/sdk/python/feast/cli.py +++ b/sdk/python/feast/cli.py @@ -14,11 +14,11 @@ import base64 import json import logging +import os import tempfile from datetime import datetime from pathlib import Path from typing import List, Optional -import os import click import pkg_resources @@ -28,7 +28,10 @@ from pygments import formatters, highlight, lexers from feast import utils -from feast.constants import DEFAULT_FEATURE_TRANSFORMATION_SERVER_PORT, FEATURE_STORE_YAML_ENV_NAME +from feast.constants import ( + DEFAULT_FEATURE_TRANSFORMATION_SERVER_PORT, + FEATURE_STORE_YAML_ENV_NAME, +) from feast.errors import FeastObjectNotFoundException, FeastProviderLoginError from feast.feature_store import FeatureStore from feast.feature_view import FeatureView From 9f92541432a0cc0b12ec2f596da0f3598b256c35 Mon Sep 17 00:00:00 2001 From: Danny Chiao Date: Fri, 19 Aug 2022 14:49:03 -0400 Subject: [PATCH 03/10] lint Signed-off-by: Danny Chiao --- CONTRIBUTING.md | 60 +++++++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b78f6c704d8..db553458fab 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,6 +23,7 @@ - [Integration Tests](#integration-tests) - [Local integration tests](#local-integration-tests) - [(Advanced) Full integration tests](#advanced-full-integration-tests) + - [Setup your GCP BigQuery Instance](#setup-your-gcp-bigquery-instance) - [(Advanced) Running specific provider tests or running your test against specific online or offline stores](#advanced-running-specific-provider-tests-or-running-your-test-against-specific-online-or-offline-stores) - [(Experimental) Run full integration tests against containerized services](#experimental-run-full-integration-tests-against-containerized-services) - [Contrib integration tests](#contrib-integration-tests) @@ -39,6 +40,9 @@ - [Code Style & Linting](#code-style--linting-1) - [Unit Tests](#unit-tests-1) - [Testing with Github Actions workflows](#testing-with-github-actions-workflows) +- [Developing the Feast Helm charts](#developing-the-feast-helm-charts) + - [Feast Java Feature Server Helm Chart](#feast-java-feature-server-helm-chart) + - [Feast Python / Go Feature Server Helm Chart](#feast-python--go-feature-server-helm-chart) - [Issues](#issues) ## Overview @@ -347,6 +351,35 @@ See [Feast contributing guide](ui/CONTRIBUTING.md) ## Feast Java Serving See [Java contributing guide](java/CONTRIBUTING.md) +See also development instructions related to the helm chart below at [Developing the Feast Helm charts](#developing-the-feast-helm-charts) + +## Developing the Feast Helm charts +There are 3 helm charts: +- Feast Java feature server +- Feast Python / Go feature server +- (deprecated) Feast Python feature server + +Generally, you can override the images in the helm charts with locally built Docker images, and install the local helm +chart. + +All README's for helm charts are generated using [helm-docs](https://github.com/norwoodj/helm-docs). You can install it +(e.g. with `brew install norwoodj/tap/helm-docs`) and then run `make build-helm-docs`. + +### Feast Java Feature Server Helm Chart +See the Java demo example (it has development instructions too using minikube) [here](examples/java-demo/README.md) + +It will: +- run `make build-java-docker-dev` to build local Java feature server binaries +- configure the included `application-override.yaml` to override the image tag to use the locally built dev images. +- install the local chart with `helm install feast-release ../../../infra/charts/feast --values application-override.yaml` + +### Feast Python / Go Feature Server Helm Chart +See the Python demo example (it has development instructions too using minikube) [here](examples/python-helm-demo/README.md) + +It will: +- run `make build-feature-server-dev` to build a local python feature server binary +- install the local chart with `helm install feast-release ../../../infra/charts/feast-feature-server --set image.tag=dev --set feature_store_yaml_base64=$(base64 feature_store.yaml)` + ## Feast Go Client ### Environment Setup Setting up your development environment for Feast Go SDK: @@ -388,33 +421,6 @@ go test * Pushes will now run your edited workflow yaml file against your test code. * Unfortunately, in order to test any github workflow changes, you must push the code to the branch and see the output in the actions tab. -## Developing the Feast Helm charts -There are 3 helm charts: -- Feast Java feature server -- Feast Python / Go feature server -- (deprecated) Feast Python feature server - -Generally, you can override the images in the helm charts with locally built Docker images, and install the local helm -chart. - -All README's for helm charts are generated using [helm-docs](https://github.com/norwoodj/helm-docs). You can install it -(e.g. with `brew install norwoodj/tap/helm-docs`) and then run `make build-helm-docs`. - -### Feast Java Feature Server Helm Chart -See the Java demo example (it has development instructions too using minikube) [here](examples/java-demo/README.md) - -It will: -- run `make build-java-docker-dev` to build local Java feature server binaries -- configure the included `application-override.yaml` to override the image tag to use the locally built dev images. -- install the local chart with `helm install feast-release ../../../infra/charts/feast --values application-override.yaml` - -### Feast Python / Go Feature Server Helm Chart -See the Python demo example (it has development instructions too using minikube) [here](examples/python-helm-demo/README.md) - -It will: -- run `make build-feature-server-dev` to build a local python feature server binary -- install the local chart with `helm install feast-release ../../../infra/charts/feast-feature-server --set image.tag=dev --set feature_store_yaml_base64=$(base64 feature_store.yaml)` - ## Issues * pr-integration-tests workflow is skipped * Add `ok-to-test` github label. From 33e7be32fec64d1348d1e00fb1985af9628d9072 Mon Sep 17 00:00:00 2001 From: Danny Chiao Date: Fri, 19 Aug 2022 14:59:02 -0400 Subject: [PATCH 04/10] add docs Signed-off-by: Danny Chiao --- .../concepts/feature-retrieval.md | 43 +++++++++++++++++-- .../feature_repo/test_python_fetch.py | 10 ++--- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/docs/getting-started/concepts/feature-retrieval.md b/docs/getting-started/concepts/feature-retrieval.md index eb7f7a18b18..0bc8c9e1d6a 100644 --- a/docs/getting-started/concepts/feature-retrieval.md +++ b/docs/getting-started/concepts/feature-retrieval.md @@ -6,7 +6,9 @@ Generally, Feast supports several patterns of feature retrieval: 1. Training data generation (via `feature_store.get_historical_features(...)`) 2. Offline feature retrieval for batch scoring (via `feature_store.get_historical_features(...)`) -3. Online feature retrieval for real-time model predictions (via `feature_store.get_online_features(...)`) +3. Online feature retrieval for real-time model predictions + - via the SDK: `feature_store.get_online_features(...)` + - via deployed feature server endpoints: `requests.post('http://localhost:6566/get-online-features', data=json.dumps(online_request))` Each of these retrieval mechanisms accept: @@ -100,7 +102,6 @@ batch_scoring_features = store.get_historical_features( ```python from feast import FeatureStore -import pandas as pd store = FeatureStore(repo_path=".") @@ -124,13 +125,23 @@ batch_scoring_features = store.get_historical_features(
-How to: retrieve online features for real-time model inference +How to: retrieve online features for real-time model inference (Python SDK) Feast will ensure the latest feature values for registered features are available. At retrieval time, you need to supply a list of **entities** and the corresponding **features** to be retrieved. Similar to `get_historical_features`, we recommend using feature services as a mechanism for grouping features in a model version. _Note: unlike `get_historical_features`, the `entity_rows` **do not need timestamps** since you only want one feature value per entity key._ ```python +from feast import RepoConfig, FeatureStore +from feast.repo_config import RegistryConfig + +repo_config = RepoConfig( + registry=RegistryConfig(path="gs://feast-test-gcs-bucket/registry.pb"), + project="feast_demo_gcp", + provider="gcp", +) +store = FeatureStore(config=repo_config) + features = store.get_online_features( features=[ "driver_hourly_stats:conv_rate", @@ -147,6 +158,32 @@ features = store.get_online_features(
+
+ +How to: retrieve online features for real-time model inference (Feature Server) + +Feast will ensure the latest feature values for registered features are available. At retrieval time, you need to supply a list of **entities** and the corresponding **features** to be retrieved. Similar to `get_historical_features`, we recommend using feature services as a mechanism for grouping features in a model version. + +_Note: unlike `get_historical_features`, the `entity_rows` **do not need timestamps** since you only want one feature value per entity key._ + +This approach requires you to deploy a feature server (see [Python feature server](../../reference/feature-servers/python-feature-server)). + +```python +import requests +import json + +online_request = { + "features": [ + "driver_hourly_stats:conv_rate", + ], + "entities": {"driver_id": [1001, 1002]}, +} +r = requests.post('http://localhost:6566/get-online-features', data=json.dumps(online_request)) +print(json.dumps(r.json(), indent=4, sort_keys=True)) +``` + +
+ ## Feature Services A feature service is an object that represents a logical group of features from one or more [feature views](feature-view.md#feature-view). Feature Services allows features from within a feature view to be used as needed by an ML model. Users can expect to create one feature service per model version, allowing for tracking of the features used by models. diff --git a/examples/python-helm-demo/feature_repo/test_python_fetch.py b/examples/python-helm-demo/feature_repo/test_python_fetch.py index 9889cc88d8f..f9c7c62f4fd 100644 --- a/examples/python-helm-demo/feature_repo/test_python_fetch.py +++ b/examples/python-helm-demo/feature_repo/test_python_fetch.py @@ -9,11 +9,11 @@ def run_demo_http(): "features": [ "driver_hourly_stats:conv_rate", ], - "entities": - {"driver_id": [1001, 1002]} - + "entities": {"driver_id": [1001, 1002]}, } - r = requests.post('http://localhost:6566/get-online-features', data=json.dumps(online_request)) + r = requests.post( + "http://localhost:6566/get-online-features", data=json.dumps(online_request) + ) print(json.dumps(r.json(), indent=4, sort_keys=True)) @@ -31,7 +31,7 @@ def run_demo_sdk(): }, { "driver_id": 1002, - } + }, ], ).to_dict() for key, value in sorted(features.items()): From 9e3c4f0fec73e905d01fa9c857a8d145f97b0ea4 Mon Sep 17 00:00:00 2001 From: Danny Chiao Date: Fri, 19 Aug 2022 15:03:41 -0400 Subject: [PATCH 05/10] lint Signed-off-by: Danny Chiao --- CONTRIBUTING.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index db553458fab..b0da6d424e1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,7 +23,6 @@ - [Integration Tests](#integration-tests) - [Local integration tests](#local-integration-tests) - [(Advanced) Full integration tests](#advanced-full-integration-tests) - - [Setup your GCP BigQuery Instance](#setup-your-gcp-bigquery-instance) - [(Advanced) Running specific provider tests or running your test against specific online or offline stores](#advanced-running-specific-provider-tests-or-running-your-test-against-specific-online-or-offline-stores) - [(Experimental) Run full integration tests against containerized services](#experimental-run-full-integration-tests-against-containerized-services) - [Contrib integration tests](#contrib-integration-tests) @@ -34,15 +33,15 @@ - [(Contrib) Running tests for HBase online store](#contrib-running-tests-for-hbase-online-store) - [(Experimental) Feast UI](#experimental-feast-ui) - [Feast Java Serving](#feast-java-serving) +- [Developing the Feast Helm charts](#developing-the-feast-helm-charts) + - [Feast Java Feature Server Helm Chart](#feast-java-feature-server-helm-chart) + - [Feast Python / Go Feature Server Helm Chart](#feast-python--go-feature-server-helm-chart) - [Feast Go Client](#feast-go-client) - [Environment Setup](#environment-setup-1) - [Building](#building) - [Code Style & Linting](#code-style--linting-1) - [Unit Tests](#unit-tests-1) - [Testing with Github Actions workflows](#testing-with-github-actions-workflows) -- [Developing the Feast Helm charts](#developing-the-feast-helm-charts) - - [Feast Java Feature Server Helm Chart](#feast-java-feature-server-helm-chart) - - [Feast Python / Go Feature Server Helm Chart](#feast-python--go-feature-server-helm-chart) - [Issues](#issues) ## Overview @@ -201,7 +200,6 @@ To test across clouds, on top of setting up Redis, you also need GCP / AWS / Sno > and commenting out tests that are added to `DEFAULT_FULL_REPO_CONFIGS` **GCP** -### Setup your GCP BigQuery Instance 1. You can get free credits [here](https://cloud.google.com/free/docs/free-cloud-features#free-trial). 2. You will need to setup a service account, enable the BigQuery API, and create a staging location for a bucket. * Setup your service account and project using steps 1-5 [here](https://codelabs.developers.google.com/codelabs/cloud-bigquery-python#0). From 6b04c31f24aca1aa529b5e937fca6b28e8aad6e2 Mon Sep 17 00:00:00 2001 From: Danny Chiao Date: Fri, 19 Aug 2022 15:05:30 -0400 Subject: [PATCH 06/10] revert bad helm docs Signed-off-by: Danny Chiao --- infra/charts/feast-feature-server/README.md | 8 +------- infra/charts/feast-feature-server/README.md.gotmpl | 8 +------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/infra/charts/feast-feature-server/README.md b/infra/charts/feast-feature-server/README.md index 2abf158bc44..43a10f83df1 100644 --- a/infra/charts/feast-feature-server/README.md +++ b/infra/charts/feast-feature-server/README.md @@ -32,10 +32,4 @@ helm install feast-feature-server . --set feature_store_yaml_base64=$(base64 fea | securityContext | object | `{}` | | | service.port | int | `80` | | | service.type | string | `"ClusterIP"` | | -| tolerations | list | `[]` | | - -## Development -To build a local -``` -helm install feast-python-server . --set image.repository=REPO --set image.tag=TAG -``` \ No newline at end of file +| tolerations | list | `[]` | | \ No newline at end of file diff --git a/infra/charts/feast-feature-server/README.md.gotmpl b/infra/charts/feast-feature-server/README.md.gotmpl index b53ffefc4af..aa8aa6316b7 100644 --- a/infra/charts/feast-feature-server/README.md.gotmpl +++ b/infra/charts/feast-feature-server/README.md.gotmpl @@ -10,10 +10,4 @@ helm install feast-feature-server . --set feature_store_yaml_base64=$(base64 fea {{ template "chart.requirementsSection" . }} -{{ template "chart.valuesSection" . }} - -## Development -To build a local -``` -helm install feast-python-server . --set image.repository=REPO --set image.tag=TAG -``` \ No newline at end of file +{{ template "chart.valuesSection" . }} \ No newline at end of file From 06b74ba5d0fe5f3a357248b9ebf5d0427d5347aa Mon Sep 17 00:00:00 2001 From: Danny Chiao Date: Fri, 19 Aug 2022 15:06:56 -0400 Subject: [PATCH 07/10] add to bump files Signed-off-by: Danny Chiao --- infra/scripts/release/files_to_bump.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/infra/scripts/release/files_to_bump.txt b/infra/scripts/release/files_to_bump.txt index 6a558e04f02..0cd7541ad7a 100644 --- a/infra/scripts/release/files_to_bump.txt +++ b/infra/scripts/release/files_to_bump.txt @@ -11,5 +11,6 @@ infra/charts/feast-python-server/Chart.yaml 5 infra/charts/feast-python-server/README.md 3 infra/charts/feast-feature-server/Chart.yaml 5 infra/charts/feast-feature-server/README.md 3 +infra/charts/feast-feature-server/values.yaml 12 java/pom.xml 38 ui/package.json 3 From 95e45f31ef7cbb10fa73f5d9399d04e623f0d968 Mon Sep 17 00:00:00 2001 From: Danny Chiao Date: Fri, 19 Aug 2022 15:07:39 -0400 Subject: [PATCH 08/10] add to bump files Signed-off-by: Danny Chiao --- infra/scripts/release/files_to_bump.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/scripts/release/files_to_bump.txt b/infra/scripts/release/files_to_bump.txt index 0cd7541ad7a..e94ec88db0a 100644 --- a/infra/scripts/release/files_to_bump.txt +++ b/infra/scripts/release/files_to_bump.txt @@ -8,7 +8,7 @@ infra/charts/feast/charts/feature-server/README.md 3 20 infra/charts/feast/charts/feature-server/values.yaml 8 infra/charts/feast/README.md 11 58 59 infra/charts/feast-python-server/Chart.yaml 5 -infra/charts/feast-python-server/README.md 3 +infra/charts/feast-python-server/README.md 5 infra/charts/feast-feature-server/Chart.yaml 5 infra/charts/feast-feature-server/README.md 3 infra/charts/feast-feature-server/values.yaml 12 From c3c5104aaf7f53fc52121c35d2b60bafe8666af9 Mon Sep 17 00:00:00 2001 From: Danny Chiao Date: Fri, 19 Aug 2022 15:10:22 -0400 Subject: [PATCH 09/10] add to release Signed-off-by: Danny Chiao --- .github/workflows/publish.yml | 2 +- Makefile | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9eb561263c3..46e16657543 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -49,7 +49,7 @@ jobs: needs: get-version strategy: matrix: - component: [feature-server-python-aws, feature-server-java, feature-transformation-server] + component: [feature-server-python, feature-server-python-aws, feature-server-java, feature-transformation-server] env: MAVEN_CACHE: gs://feast-templocation-kf-feast/.m2.2020-08-19.tar REGISTRY: feastdev diff --git a/Makefile b/Makefile index f87f53ef5b0..4bd1eaed4c1 100644 --- a/Makefile +++ b/Makefile @@ -330,7 +330,7 @@ lint-go: compile-protos-go compile-go-lib # Docker -build-docker: build-ci-docker build-feature-server-python-aws-docker build-feature-transformation-server-docker build-feature-server-java-docker +build-docker: build-ci-docker build-feature-server-python-docker build-feature-server-python-aws-docker build-feature-transformation-server-docker build-feature-server-java-docker push-ci-docker: docker push $(REGISTRY)/feast-ci:$(VERSION) @@ -339,13 +339,21 @@ push-ci-docker: build-ci-docker: docker buildx build -t $(REGISTRY)/feast-ci:$(VERSION) -f infra/docker/ci/Dockerfile --load . +push-feature-server-python-docker: + docker push $(REGISTRY)/feature-server:$$VERSION + +build-feature-server-python-docker: + docker buildx build --build-arg VERSION=$$VERSION \ + -t $(REGISTRY)/feature-server:$$VERSION \ + -f sdk/python/feast/infra/feature_servers/multicloud/Dockerfile --load . + push-feature-server-python-aws-docker: - docker push $(REGISTRY)/feature-server-python-aws:$$VERSION + docker push $(REGISTRY)/feature-server-python-aws:$$VERSION build-feature-server-python-aws-docker: - docker buildx build --build-arg VERSION=$$VERSION \ - -t $(REGISTRY)/feature-server-python-aws:$$VERSION \ - -f sdk/python/feast/infra/feature_servers/aws_lambda/Dockerfile --load . + docker buildx build --build-arg VERSION=$$VERSION \ + -t $(REGISTRY)/feature-server-python-aws:$$VERSION \ + -f sdk/python/feast/infra/feature_servers/aws_lambda/Dockerfile --load . push-feature-transformation-server-docker: docker push $(REGISTRY)/feature-transformation-server:$(VERSION) From 8d3b5489f23d0f3c36affcf3bc6e05da5a6404e2 Mon Sep 17 00:00:00 2001 From: Danny Chiao Date: Fri, 19 Aug 2022 15:12:56 -0400 Subject: [PATCH 10/10] fix readme for feast-feature-server Signed-off-by: Danny Chiao --- infra/charts/feast-feature-server/README.md | 12 +++++++++++- infra/charts/feast-feature-server/README.md.gotmpl | 12 +++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/infra/charts/feast-feature-server/README.md b/infra/charts/feast-feature-server/README.md index 43a10f83df1..8d551c82f7e 100644 --- a/infra/charts/feast-feature-server/README.md +++ b/infra/charts/feast-feature-server/README.md @@ -3,9 +3,19 @@ Current chart version is `0.23.0` ## Installation + +Run the following commands to add the repository + +``` +helm repo add feast-charts https://feast-helm-charts.storage.googleapis.com +helm repo update +``` + +Install Feast + A base64 encoded version of the `feature_store.yaml` file is needed. Helm install example: ``` -helm install feast-feature-server . --set feature_store_yaml_base64=$(base64 feature_store.yaml) +helm install feast-feature-server feast-charts/feast-feature-server --set feature_store_yaml_base64=$(base64 feature_store.yaml) ``` ## Values diff --git a/infra/charts/feast-feature-server/README.md.gotmpl b/infra/charts/feast-feature-server/README.md.gotmpl index aa8aa6316b7..75f28274663 100644 --- a/infra/charts/feast-feature-server/README.md.gotmpl +++ b/infra/charts/feast-feature-server/README.md.gotmpl @@ -3,9 +3,19 @@ Current chart version is `{{ template "chart.version" . }}` ## Installation + +Run the following commands to add the repository + +``` +helm repo add feast-charts https://feast-helm-charts.storage.googleapis.com +helm repo update +``` + +Install Feast + A base64 encoded version of the `feature_store.yaml` file is needed. Helm install example: ``` -helm install feast-feature-server . --set feature_store_yaml_base64=$(base64 feature_store.yaml) +helm install feast-feature-server feast-charts/feast-feature-server --set feature_store_yaml_base64=$(base64 feature_store.yaml) ``` {{ template "chart.requirementsSection" . }}