From e169fe1de437150a68e87a6e314b1e81d6765955 Mon Sep 17 00:00:00 2001 From: Danny Chiao Date: Wed, 9 Feb 2022 11:54:05 -0500 Subject: [PATCH 01/15] Fixing helm chart and adding java tutorial Signed-off-by: Danny Chiao --- examples/java-demo/README.md | 190 ++++++++++++++++++ examples/java-demo/feature_repo/__init__.py | 0 .../feature_repo/application-override.yaml | 17 ++ .../data/driver_stats_with_string.parquet | Bin 0 -> 35310 bytes .../java-demo/feature_repo/driver_repo.py | 61 ++++++ .../java-demo/feature_repo/feature_store.yaml | 11 + examples/java-demo/feature_repo/test.py | 65 ++++++ infra/charts/feast/README.md | 1 - .../feature-server/templates/configmap.yaml | 17 +- 9 files changed, 354 insertions(+), 8 deletions(-) create mode 100644 examples/java-demo/README.md create mode 100644 examples/java-demo/feature_repo/__init__.py create mode 100644 examples/java-demo/feature_repo/application-override.yaml create mode 100644 examples/java-demo/feature_repo/data/driver_stats_with_string.parquet create mode 100644 examples/java-demo/feature_repo/driver_repo.py create mode 100644 examples/java-demo/feature_repo/feature_store.yaml create mode 100644 examples/java-demo/feature_repo/test.py diff --git a/examples/java-demo/README.md b/examples/java-demo/README.md new file mode 100644 index 00000000000..724314ad83d --- /dev/null +++ b/examples/java-demo/README.md @@ -0,0 +1,190 @@ + +# Running Feast Java Server with Redis & calling with python (with registry in GCP) + +For this tutorial, we setup Feast with Redis, using the Feast CLI to register and materialize features, and then retrieving via a Feast Java server deployed in Kubernetes via a gRPC call. + + +## First, let's setup 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 +``` + +## Next, we setup a local Feast repo +1. Install Feast with Redis dependencies `pip install ‘feast[redis]’` +2. Use a default Feast template to generate some dummy data. + ```bash + feast init -t gcp + ``` + Result: + ```bash + Creating a new Feast repository in /Users/dannychiao/definite_elf. + ``` +3. Now we need to modify this feature repo to communicate with a bucket in GCS and the Redis cluster we spun up. Inspecting this directory, we see: + + + + We need to modify the `feature_store.yaml`. Replace it with something like + + ```yaml + project: feast_demo + provider: gcp + registry: gs://[YOUR BUCKET]/demo-repo/registry.db + online_store: + type: "redis" + connection_string: "localhost:6379,password=[INSERT REDIS_PASSWORD]" + offline_store: + type: file + + ``` + +## Now let's setup the k8s cluster +1. Add the gcp-auth addon to mount GCP credentials: + `minikube addons enable gcp-auth` +2. Make a bucket in GCS (below we use `gs://feast-demo-staging`) +7. Get the generated password from redis: + + ```bash + export REDIS_PASSWORD=$(kubectl get secret --namespace default my-redis -o jsonpath="{.data.redis-password}" | base64 --decode) + echo $REDIS_PASSWORD + + ``` + +8. feast apply a repo (e.g. feast-demo repo) that is wired for Redis with a remote registry (e.g. gcs..) + + ```yaml + project: feast_demo + provider: gcp + registry: gs://[YOUR BUCKET]/demo-repo/registry.db + online_store: + type: "redis" + connection_string: "localhost:6379,password=[INSERT REDIS_PASSWORD]" + offline_store: + type: file + + ``` +9. Materialize data to Redis: + ```bash + CURRENT_TIME=$(date -u +"%Y-%m-%dT%H:%M:%S") + feast materialize-incremental $CURRENT_TIME + ``` +10. Add Feast's Java feature server chart repo + + +```bash +helm repo add feast-charts https://feast-helm-charts.storage.googleapis.com +helm repo update + +``` + +11. Make application-override.yaml file (note: this is already made in the directory): + +```yaml +feature-server: + application-override.yaml: + enabled: true + feast: + activeStore: online + stores: + - name: online + type: REDIS + config: + host: my-redis-master + port: 6379 + password: [INSERT REDIS_PASSWORD] +global: + registry: + path: gs://[YOUR BUCKET]/demo-repo/registry.db + project: [INSERT PROJECT NAME] + +``` + +1. Install chart with override:`helm install feast-release feast-charts/feast --values application-override.yaml` +2. (Optional): check logs of the server to make sure it’s working + 1. `kubectl logs svc/feast-release-feature-server` +3. Port forward to expose the grpc endpoint: + 1. `kubectl port-forward svc/feast-release-feature-server 6566:6566` +4. Make a gRPC call (via gRPC cli or python, or java / go client sdks): + - gRPC cli: + + ```bash + grpc_cli call localhost:6566 GetOnlineFeatures ' + features { + val: "driver_hourly_stats:conv_rate" + val: "driver_hourly_stats:acc_rate" + } + entities { + key: "driver_id" + value { + val { + int64_val: 1001 + } + val { + int64_val: 1002 + } + } + } + + ``` + + - Response: + + ```bash + connecting to localhost:6566 + metadata { + feature_names { + val: "driver_hourly_stats:conv_rate" + val: "driver_hourly_stats:acc_rate" + } + } + results { + values { + float_val: 0.812357187 + } + values { + float_val: 0.379484832 + } + statuses: PRESENT + statuses: PRESENT + event_timestamps { + seconds: 1631725200 + } + event_timestamps { + seconds: 1631725200 + } + } + results { + values { + float_val: 0.840873241 + } + values { + float_val: 0.151376978 + } + statuses: PRESENT + statuses: PRESENT + event_timestamps { + seconds: 1631725200 + } + event_timestamps { + seconds: 1631725200 + } + } + Rpc succeeded with OK status + + ``` + + - Python library to do grpc call: \ No newline at end of file diff --git a/examples/java-demo/feature_repo/__init__.py b/examples/java-demo/feature_repo/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/java-demo/feature_repo/application-override.yaml b/examples/java-demo/feature_repo/application-override.yaml new file mode 100644 index 00000000000..d4aab0c910a --- /dev/null +++ b/examples/java-demo/feature_repo/application-override.yaml @@ -0,0 +1,17 @@ +feature-server: + application-override.yaml: + enabled: true + feast: + activeStore: online + stores: + - name: online + type: REDIS + config: + host: my-redis-master + port: 6379 + password: WW2Jew09Yt +global: + registry: + path: gs://feast-demo-staging/demo-repo/registry.db + cache_ttl_seconds: 60 + project: feast_java_demo \ No newline at end of file diff --git a/examples/java-demo/feature_repo/data/driver_stats_with_string.parquet b/examples/java-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 + + +# Define request feature view +driver_age_request_fv = RequestFeatureView( + name="driver_age", + request_data_source=RequestDataSource( + name="driver_age", schema={"driver_age": ValueType.INT64,} + ), +) diff --git a/examples/java-demo/feature_repo/feature_store.yaml b/examples/java-demo/feature_repo/feature_store.yaml new file mode 100644 index 00000000000..9cfdde5449a --- /dev/null +++ b/examples/java-demo/feature_repo/feature_store.yaml @@ -0,0 +1,11 @@ +registry: gs://feast-demo-staging/demo-repo/registry.db +project: feast_java_demo +provider: gcp +online_store: + type: redis + connection_string: localhost:6379,password=WW2Jew09Yt +offline_store: + type: file +flags: + alpha_features: true + on_demand_transforms: true diff --git a/examples/java-demo/feature_repo/test.py b/examples/java-demo/feature_repo/test.py new file mode 100644 index 00000000000..538334044bf --- /dev/null +++ b/examples/java-demo/feature_repo/test.py @@ -0,0 +1,65 @@ +from datetime import datetime, timedelta + +import pandas as pd +from driver_repo import driver, driver_stats_fv + +from feast import FeatureStore + + +def main(): + pd.set_option("display.max_columns", None) + pd.set_option("display.width", 1000) + + # Load the feature store from the current path + fs = FeatureStore(repo_path=".") + + # Deploy the feature store to GCP + print("Deploying feature store to GCP...") + fs.apply([driver, driver_stats_fv]) + + # Select features + features = ["driver_hourly_stats:conv_rate", "driver_hourly_stats:acc_rate"] + + # Create an entity dataframe. This is the dataframe that will be enriched with historical features + entity_df = pd.DataFrame( + { + "event_timestamp": [ + pd.Timestamp(dt, unit="ms", tz="UTC").round("ms") + for dt in pd.date_range( + start=datetime.now() - timedelta(days=3), + end=datetime.now(), + periods=3, + ) + ], + "driver_id": [1001, 1002, 1003], + } + ) + + print("Retrieving training data...") + + # Retrieve historical features by joining the entity dataframe to the BigQuery table source + training_df = fs.get_historical_features( + features=features, entity_df=entity_df + ).to_df() + + print() + print(training_df) + + print() + print("Loading features into the online store...") + fs.materialize_incremental(end_date=datetime.now()) + + print() + print("Retrieving online features...") + + # Retrieve features from the online store (Firestore) + online_features = fs.get_online_features( + features=features, entity_rows=[{"driver_id": 1001}, {"driver_id": 1002}], + ).to_dict() + + print() + print(pd.DataFrame.from_dict(online_features)) + + +if __name__ == "__main__": + main() diff --git a/infra/charts/feast/README.md b/infra/charts/feast/README.md index 40e67b0857c..2980fc5223e 100644 --- a/infra/charts/feast/README.md +++ b/infra/charts/feast/README.md @@ -36,7 +36,6 @@ In order to modify the default configuration of Feature Server, please use the ` ``` feature-server: application-override.yaml: - enabled: true feast: active_store: online stores: diff --git a/infra/charts/feast/charts/feature-server/templates/configmap.yaml b/infra/charts/feast/charts/feature-server/templates/configmap.yaml index c4bdd5a6646..032ba8a91ce 100644 --- a/infra/charts/feast/charts/feature-server/templates/configmap.yaml +++ b/infra/charts/feast/charts/feature-server/templates/configmap.yaml @@ -14,23 +14,26 @@ data: {{- if index .Values "application-generated.yaml" "enabled" }} feast: registry: {{ .Values.global.registry.path }} - registry-refresh-interval: {{ .Values.global.registry.cache_ttl_seconds }} + registryRefreshInterval: {{ .Values.global.registry.cache_ttl_seconds }} {{- if .Values.transformationService.host }} - transformation-service-endpoint: {{ .Values.transformationService.host}}:{{ .Values.transformationService.port }} + transformationServiceEndpoint: {{ .Values.transformationService.host}}:{{ .Values.transformationService.port }} {{- else }} - transformation-service-endpoint: {{ .Release.Name }}-transformation-service:{{ .Values.transformationService.port }} + transformationServiceEndpoint: {{ .Release.Name }}-transformation-service:{{ .Values.transformationService.port }} {{- end }} - active_store: online + activeStore: online stores: - name: online type: REDIS config: host: {{ .Release.Name }}-redis-master port: 6379 - - server: - port: {{ .Values.service.http.targetPort }} + rest: + server: + port: {{ .Values.service.http.targetPort }} + grpc: + server: + port: {{ .Values.service.grpc.targetPort }} {{- end }} application-override.yaml: | From bf6f6dd5ba75b158937331f38b0fbcf7c106f03f Mon Sep 17 00:00:00 2001 From: Danny Chiao Date: Wed, 9 Feb 2022 12:54:19 -0500 Subject: [PATCH 02/15] fix chart version Signed-off-by: Danny Chiao --- infra/charts/feast/README.md | 1 + infra/charts/feast/charts/transformation-service/Chart.yaml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/infra/charts/feast/README.md b/infra/charts/feast/README.md index 2980fc5223e..40e67b0857c 100644 --- a/infra/charts/feast/README.md +++ b/infra/charts/feast/README.md @@ -36,6 +36,7 @@ In order to modify the default configuration of Feature Server, please use the ` ``` feature-server: application-override.yaml: + enabled: true feast: active_store: online stores: diff --git a/infra/charts/feast/charts/transformation-service/Chart.yaml b/infra/charts/feast/charts/transformation-service/Chart.yaml index ea4f9ccfe5f..434850ca8b4 100644 --- a/infra/charts/feast/charts/transformation-service/Chart.yaml +++ b/infra/charts/feast/charts/transformation-service/Chart.yaml @@ -1,8 +1,8 @@ apiVersion: v1 description: "Transformation service: to compute on-demand features" name: transformation-service -version: 0.18.0 -appVersion: v0.18.0 +version: 0.18.1 +appVersion: v0.18.1 keywords: - machine learning - big data From fc9be6837e5c85c2feeb5c805e1321dcd0eb241a Mon Sep 17 00:00:00 2001 From: Danny Chiao Date: Wed, 9 Feb 2022 14:02:55 -0500 Subject: [PATCH 03/15] test Signed-off-by: Danny Chiao --- .../charts/feast/charts/feature-server/values.yaml | 4 ++-- infra/scripts/helm/push-helm-charts.sh | 2 +- java/infra/docker/feature-server/Dockerfile | 14 +++++++------- .../config/ApplicationPropertiesModule.java | 6 +++++- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/infra/charts/feast/charts/feature-server/values.yaml b/infra/charts/feast/charts/feature-server/values.yaml index a6cf0f41b7c..bf330fe04e1 100644 --- a/infra/charts/feast/charts/feature-server/values.yaml +++ b/infra/charts/feast/charts/feature-server/values.yaml @@ -3,9 +3,9 @@ replicaCount: 1 image: # image.repository -- Docker image for Feature Server repository - repository: feastdev/feature-server-java + repository: test/feature-server-java # image.tag -- Image tag - tag: 0.18.0 + tag: 0.18.1 # image.pullPolicy -- Image pull policy pullPolicy: IfNotPresent diff --git a/infra/scripts/helm/push-helm-charts.sh b/infra/scripts/helm/push-helm-charts.sh index f9750ccecc4..058aed735c6 100755 --- a/infra/scripts/helm/push-helm-charts.sh +++ b/infra/scripts/helm/push-helm-charts.sh @@ -19,4 +19,4 @@ helm package feast helm package feast-python-server helm gcs push --public feast-${1}.tgz feast-helm-chart-repo --force -helm gcs push --public feast-python-server-${1}.tgz feast-helm-chart-repo --force \ No newline at end of file +# helm gcs push --public feast-python-server-${1}.tgz feast-helm-chart-repo --force \ No newline at end of file diff --git a/java/infra/docker/feature-server/Dockerfile b/java/infra/docker/feature-server/Dockerfile index dbd8c914724..4f9e136aac1 100644 --- a/java/infra/docker/feature-server/Dockerfile +++ b/java/infra/docker/feature-server/Dockerfile @@ -20,7 +20,7 @@ COPY java/docs/coverage/pom.xml docs/coverage/pom.xml # user to optionally use cached repository when building the image by copying # the existing .m2 directory to $FEAST_REPO_ROOT/.m2 ENV MAVEN_OPTS="-Dmaven.repo.local=/build/.m2/repository -DdependencyLocationsEnabled=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=25 -Dmaven.wagon.http.retryHandler.count=3" -COPY java/pom.xml .m2/* .m2/ +# COPY java/pom.xml .m2/* .m2/ RUN mvn dependency:go-offline -DexcludeGroupIds:dev.feast 2>/dev/null || true COPY java/ . @@ -34,8 +34,8 @@ RUN mvn --also-make --projects serving -Drevision=$VERSION \ # https://kubernetes.io/blog/2018/10/01/health-checking-grpc-servers-on-kubernetes/ # RUN wget -q https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/v0.3.1/grpc_health_probe-linux-amd64 \ - -O /usr/bin/grpc-health-probe && \ - chmod +x /usr/bin/grpc-health-probe + -O /usr/bin/grpc-health-probe && \ + chmod +x /usr/bin/grpc-health-probe # ============================================================ # Build stage 2: Production @@ -46,7 +46,7 @@ ARG VERSION=dev COPY --from=builder /build/serving/target/feast-serving-$VERSION-jar-with-dependencies.jar /opt/feast/feast-serving.jar COPY --from=builder /usr/bin/grpc-health-probe /usr/bin/grpc-health-probe CMD ["java",\ - "-Xms1g",\ - "-Xmx4g",\ - "-jar",\ - "/opt/feast/feast-serving.jar"] + "-Xms1g",\ + "-Xmx4g",\ + "-jar",\ + "/opt/feast/feast-serving.jar"] diff --git a/java/serving/src/main/java/feast/serving/config/ApplicationPropertiesModule.java b/java/serving/src/main/java/feast/serving/config/ApplicationPropertiesModule.java index 07183fc7101..d8404d8d553 100644 --- a/java/serving/src/main/java/feast/serving/config/ApplicationPropertiesModule.java +++ b/java/serving/src/main/java/feast/serving/config/ApplicationPropertiesModule.java @@ -16,6 +16,7 @@ */ package feast.serving.config; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; @@ -42,7 +43,10 @@ public ApplicationProperties provideApplicationProperties() throws IOException { mapper.setDefaultMergeable(Boolean.TRUE); ApplicationProperties properties = new ApplicationProperties(); - ObjectReader objectReader = mapper.readerForUpdating(properties); + ObjectReader objectReader = + mapper + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .readerForUpdating(properties); String[] filePaths = this.args[0].split(","); for (String filePath : filePaths) { From 4d83d7e68180fad0f11e11b6ddcd379221dfcf93 Mon Sep 17 00:00:00 2001 From: Danny Chiao Date: Wed, 9 Feb 2022 17:09:15 -0500 Subject: [PATCH 04/15] Parse the overrides to not pass enabled Signed-off-by: Danny Chiao --- .../charts/feature-server/templates/configmap.yaml | 10 +++++++++- infra/charts/feast/charts/feature-server/values.yaml | 4 ++-- .../feast/charts/transformation-service/Chart.yaml | 4 ++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/infra/charts/feast/charts/feature-server/templates/configmap.yaml b/infra/charts/feast/charts/feature-server/templates/configmap.yaml index 032ba8a91ce..fbaf813b16a 100644 --- a/infra/charts/feast/charts/feature-server/templates/configmap.yaml +++ b/infra/charts/feast/charts/feature-server/templates/configmap.yaml @@ -38,5 +38,13 @@ data: application-override.yaml: | {{- if index .Values "application-override.yaml" "enabled" }} -{{- toYaml (index .Values "application-override.yaml") | nindent 4 }} + {{- if index .Values "application-override.yaml" "feast" }} + feast: {{- toYaml (index .Values "application-override.yaml" "feast") | nindent 6 }} + {{- end }} + {{- if index .Values "application-override.yaml" "rest" }} + rest: {{- toYaml (index .Values "application-override.yaml" "rest") | nindent 6 }} + {{- end }} + {{- if index .Values "application-override.yaml" "grpc" }} + grpc: {{- toYaml (index .Values "application-override.yaml" "grpc") | nindent 6 }} + {{- end }} {{- end }} diff --git a/infra/charts/feast/charts/feature-server/values.yaml b/infra/charts/feast/charts/feature-server/values.yaml index bf330fe04e1..a6cf0f41b7c 100644 --- a/infra/charts/feast/charts/feature-server/values.yaml +++ b/infra/charts/feast/charts/feature-server/values.yaml @@ -3,9 +3,9 @@ replicaCount: 1 image: # image.repository -- Docker image for Feature Server repository - repository: test/feature-server-java + repository: feastdev/feature-server-java # image.tag -- Image tag - tag: 0.18.1 + tag: 0.18.0 # image.pullPolicy -- Image pull policy pullPolicy: IfNotPresent diff --git a/infra/charts/feast/charts/transformation-service/Chart.yaml b/infra/charts/feast/charts/transformation-service/Chart.yaml index 434850ca8b4..ea4f9ccfe5f 100644 --- a/infra/charts/feast/charts/transformation-service/Chart.yaml +++ b/infra/charts/feast/charts/transformation-service/Chart.yaml @@ -1,8 +1,8 @@ apiVersion: v1 description: "Transformation service: to compute on-demand features" name: transformation-service -version: 0.18.1 -appVersion: v0.18.1 +version: 0.18.0 +appVersion: v0.18.0 keywords: - machine learning - big data From 3fc505661f90ae6d6fb3a89ec78c013890b7fd8b Mon Sep 17 00:00:00 2001 From: Danny Chiao Date: Wed, 9 Feb 2022 17:14:14 -0500 Subject: [PATCH 05/15] Disable secrets by default Signed-off-by: Danny Chiao --- infra/charts/feast/charts/feature-server/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/charts/feast/charts/feature-server/values.yaml b/infra/charts/feast/charts/feature-server/values.yaml index a6cf0f41b7c..7e62d217f83 100644 --- a/infra/charts/feast/charts/feature-server/values.yaml +++ b/infra/charts/feast/charts/feature-server/values.yaml @@ -25,7 +25,7 @@ application-generated.yaml: # "application-secret.yaml" -- Configuration to override the default [application.yaml](https://github.com/feast-dev/feast/blob/master/java/serving/src/main/resources/application.yml). Will be created as a Secret. `application-override.yaml` has a higher precedence than `application-secret.yaml`. It is recommended to either set `application-override.yaml` or `application-secret.yaml` only to simplify config management. application-secret.yaml: - enabled: true + enabled: false # "application-override.yaml" -- Configuration to override the default [application.yaml](https://github.com/feast-dev/feast/blob/master/java/serving/src/main/resources/application.yml). Will be created as a ConfigMap. `application-override.yaml` has a higher precedence than `application-secret.yaml` application-override.yaml: From a5701c70cf4c9fa4d420579e83956d1f1a96ebeb Mon Sep 17 00:00:00 2001 From: Danny Chiao Date: Wed, 9 Feb 2022 17:16:15 -0500 Subject: [PATCH 06/15] Change secrets default Signed-off-by: Danny Chiao --- .../feast/charts/feature-server/templates/secret.yaml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/infra/charts/feast/charts/feature-server/templates/secret.yaml b/infra/charts/feast/charts/feature-server/templates/secret.yaml index d821f8e6f18..ad1b030eaa2 100644 --- a/infra/charts/feast/charts/feature-server/templates/secret.yaml +++ b/infra/charts/feast/charts/feature-server/templates/secret.yaml @@ -12,4 +12,12 @@ metadata: type: Opaque stringData: application-secret.yaml: | -{{- toYaml (index .Values "application-secret.yaml") | nindent 4 }} + {{- if index .Values "application-secret.yaml" "feast" }} + feast: {{- toYaml (index .Values "application-secret.yaml" "feast") | nindent 6 }} + {{- end }} + {{- if index .Values "application-secret.yaml" "rest" }} + rest: {{- toYaml (index .Values "application-secret.yaml" "rest") | nindent 6 }} + {{- end }} + {{- if index .Values "application-secret.yaml" "grpc" }} + grpc: {{- toYaml (index .Values "application-secret.yaml" "grpc") | nindent 6 }} + {{- end }} \ No newline at end of file From 1aa458459bd8b25b9ab5a78bb4e0025263d66bc5 Mon Sep 17 00:00:00 2001 From: Danny Chiao Date: Wed, 9 Feb 2022 17:27:51 -0500 Subject: [PATCH 07/15] revert Signed-off-by: Danny Chiao --- infra/scripts/helm/push-helm-charts.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/scripts/helm/push-helm-charts.sh b/infra/scripts/helm/push-helm-charts.sh index 058aed735c6..f9750ccecc4 100755 --- a/infra/scripts/helm/push-helm-charts.sh +++ b/infra/scripts/helm/push-helm-charts.sh @@ -19,4 +19,4 @@ helm package feast helm package feast-python-server helm gcs push --public feast-${1}.tgz feast-helm-chart-repo --force -# helm gcs push --public feast-python-server-${1}.tgz feast-helm-chart-repo --force \ No newline at end of file +helm gcs push --public feast-python-server-${1}.tgz feast-helm-chart-repo --force \ No newline at end of file From 40b3268dd8361c13fbecdb9d5064b28b3d993c32 Mon Sep 17 00:00:00 2001 From: Danny Chiao Date: Wed, 9 Feb 2022 17:54:39 -0500 Subject: [PATCH 08/15] Update README Signed-off-by: Danny Chiao --- examples/java-demo/README.md | 127 +++++++++--------------- examples/java-demo/feature_repo/test.py | 87 +++++----------- examples/java-demo/redis-screenshot.png | Bin 0 -> 77442 bytes examples/java-demo/tree.png | Bin 0 -> 10353 bytes 4 files changed, 73 insertions(+), 141 deletions(-) create mode 100644 examples/java-demo/redis-screenshot.png create mode 100644 examples/java-demo/tree.png diff --git a/examples/java-demo/README.md b/examples/java-demo/README.md index 724314ad83d..81c61ff2f50 100644 --- a/examples/java-demo/README.md +++ b/examples/java-demo/README.md @@ -17,109 +17,81 @@ For this tutorial, we setup Feast with Redis, using the Feast CLI to register an ```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 -``` + ```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. Use a default Feast template to generate some dummy data. - ```bash - feast init -t gcp - ``` - Result: - ```bash - Creating a new Feast repository in /Users/dannychiao/definite_elf. - ``` -3. Now we need to modify this feature repo to communicate with a bucket in GCS and the Redis cluster we spun up. Inspecting this directory, we see: +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. Inspecting this directory, we see: - We need to modify the `feature_store.yaml`. Replace it with something like - + We need to modify the `feature_store.yaml`, which has two fields for you to replace: ```yaml - project: feast_demo + registry: gs://[YOUR BUCKET]/demo-repo/registry.db + project: feast_java_demo provider: gcp - registry: gs://[YOUR BUCKET]/demo-repo/registry.db online_store: - type: "redis" - connection_string: "localhost:6379,password=[INSERT REDIS_PASSWORD]" + type: redis + connection_string: localhost:6379,password=[YOUR PASSWORD] offline_store: type: file - + flags: + alpha_features: true + on_demand_transforms: true ``` +4. Run `feast apply` to apply your local features to the remote registry +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 k8s cluster -1. Add the gcp-auth addon to mount GCP credentials: +1. Add the gcp-auth addon to mount GCP credentials: `minikube addons enable gcp-auth` -2. Make a bucket in GCS (below we use `gs://feast-demo-staging`) -7. Get the generated password from redis: - +2. Add Feast's Java feature server chart repo ```bash - export REDIS_PASSWORD=$(kubectl get secret --namespace default my-redis -o jsonpath="{.data.redis-password}" | base64 --decode) - echo $REDIS_PASSWORD - + helm repo add feast-charts https://feast-helm-charts.storage.googleapis.com + helm repo update ``` - -8. feast apply a repo (e.g. feast-demo repo) that is wired for Redis with a remote registry (e.g. gcs..) - +3. Modify the application-override.yaml file to have your credentials + bucket location: ```yaml - project: feast_demo - provider: gcp - registry: gs://[YOUR BUCKET]/demo-repo/registry.db - online_store: - type: "redis" - connection_string: "localhost:6379,password=[INSERT REDIS_PASSWORD]" - offline_store: - type: file - - ``` -9. Materialize data to Redis: - ```bash - CURRENT_TIME=$(date -u +"%Y-%m-%dT%H:%M:%S") - feast materialize-incremental $CURRENT_TIME - ``` -10. Add Feast's Java feature server chart repo - - -```bash -helm repo add feast-charts https://feast-helm-charts.storage.googleapis.com -helm repo update - -``` - -11. Make application-override.yaml file (note: this is already made in the directory): - -```yaml -feature-server: - application-override.yaml: + feature-server: + application-override.yaml: enabled: true feast: - activeStore: online - stores: + activeStore: online + stores: - name: online type: REDIS config: host: my-redis-master port: 6379 - password: [INSERT REDIS_PASSWORD] -global: - registry: - path: gs://[YOUR BUCKET]/demo-repo/registry.db - project: [INSERT PROJECT NAME] - -``` - -1. Install chart with override:`helm install feast-release feast-charts/feast --values application-override.yaml` -2. (Optional): check logs of the server to make sure it’s working + password: [YOUR PASSWORD] + global: + registry: + path: gs://[YOUR BUCKET]/demo-repo/registry.db + cache_ttl_seconds: 60 + project: feast_java_demo + ``` +4. Install the Feast helm chart: `helm install feast-release feast-charts/feast --values application-override.yaml` +5. (Optional): check logs of the server to make sure it’s working 1. `kubectl logs svc/feast-release-feature-server` -3. Port forward to expose the grpc endpoint: +6. Port forward to expose the grpc endpoint: 1. `kubectl port-forward svc/feast-release-feature-server 6566:6566` -4. Make a gRPC call (via gRPC cli or python, or java / go client sdks): - - gRPC cli: +7. Make a gRPC call: + - Python example + ```bash + python test.py + ``` + - gRPC cli: ```bash grpc_cli call localhost:6566 GetOnlineFeatures ' @@ -137,8 +109,7 @@ global: int64_val: 1002 } } - } - + }' ``` - Response: @@ -185,6 +156,4 @@ global: } Rpc succeeded with OK status - ``` - - - Python library to do grpc call: \ No newline at end of file + ``` \ No newline at end of file diff --git a/examples/java-demo/feature_repo/test.py b/examples/java-demo/feature_repo/test.py index 538334044bf..e561fe2ceb7 100644 --- a/examples/java-demo/feature_repo/test.py +++ b/examples/java-demo/feature_repo/test.py @@ -1,65 +1,28 @@ -from datetime import datetime, timedelta - -import pandas as pd -from driver_repo import driver, driver_stats_fv - -from feast import FeatureStore - - -def main(): - pd.set_option("display.max_columns", None) - pd.set_option("display.width", 1000) - - # Load the feature store from the current path - fs = FeatureStore(repo_path=".") - - # Deploy the feature store to GCP - print("Deploying feature store to GCP...") - fs.apply([driver, driver_stats_fv]) - - # Select features - features = ["driver_hourly_stats:conv_rate", "driver_hourly_stats:acc_rate"] - - # Create an entity dataframe. This is the dataframe that will be enriched with historical features - entity_df = pd.DataFrame( - { - "event_timestamp": [ - pd.Timestamp(dt, unit="ms", tz="UTC").round("ms") - for dt in pd.date_range( - start=datetime.now() - timedelta(days=3), - end=datetime.now(), - periods=3, - ) - ], - "driver_id": [1001, 1002, 1003], - } +import grpc +from feast.protos.feast.serving.ServingService_pb2 import ( + FeatureList, + GetOnlineFeaturesRequest, +) +from feast.protos.feast.serving.ServingService_pb2_grpc import ServingServiceStub +from feast.protos.feast.types.Value_pb2 import RepeatedValue, Value + + +# Sample logic to fetch from a local gRPC java server deployed at 6566 +def fetch_java(): + channel = grpc.insecure_channel("localhost:6566") + stub = ServingServiceStub(channel) + feature_refs = FeatureList(val=["driver_hourly_stats:conv_rate"]) + entity_rows = { + "driver_id": RepeatedValue( + val=[Value(int64_val=driver_id) for driver_id in range(1001, 1020)] + ) + } + + print( + stub.GetOnlineFeatures( + GetOnlineFeaturesRequest(features=feature_refs, entities=entity_rows,) + ) ) - print("Retrieving training data...") - - # Retrieve historical features by joining the entity dataframe to the BigQuery table source - training_df = fs.get_historical_features( - features=features, entity_df=entity_df - ).to_df() - - print() - print(training_df) - - print() - print("Loading features into the online store...") - fs.materialize_incremental(end_date=datetime.now()) - - print() - print("Retrieving online features...") - - # Retrieve features from the online store (Firestore) - online_features = fs.get_online_features( - features=features, entity_rows=[{"driver_id": 1001}, {"driver_id": 1002}], - ).to_dict() - - print() - print(pd.DataFrame.from_dict(online_features)) - - if __name__ == "__main__": - main() + fetch_java() diff --git a/examples/java-demo/redis-screenshot.png b/examples/java-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/examples/java-demo/tree.png b/examples/java-demo/tree.png new file mode 100644 index 0000000000000000000000000000000000000000..1b1508ae4a9742b3259defd270c64608fddfe79c GIT binary patch literal 10353 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 Date: Wed, 9 Feb 2022 19:34:07 -0500 Subject: [PATCH 09/15] finally Signed-off-by: Danny Chiao --- examples/java-demo/README.md | 35 ++++++++++-------- .../feature_repo/application-override.yaml | 6 +-- .../java-demo/feature_repo/feature_store.yaml | 2 +- examples/java-demo/feature_repo/test.py | 2 +- examples/java-demo/tree.png | Bin 10353 -> 0 bytes .../feature-server/templates/configmap.yaml | 3 ++ java/serving/README.md | 18 ++++++++- .../config/ApplicationPropertiesModule.java | 6 +-- 8 files changed, 44 insertions(+), 28 deletions(-) delete mode 100644 examples/java-demo/tree.png diff --git a/examples/java-demo/README.md b/examples/java-demo/README.md index 81c61ff2f50..b908bb76254 100644 --- a/examples/java-demo/README.md +++ b/examples/java-demo/README.md @@ -2,7 +2,7 @@ # Running Feast Java Server with Redis & calling with python (with registry in GCP) For this tutorial, we setup Feast with Redis, using the Feast CLI to register and materialize features, and then retrieving via a Feast Java server deployed in Kubernetes via a gRPC call. - +> :point_right: for tips on how to run and debug this locally without using Kubernetes, see [java/serving/README.md](https://github.com/feast-dev/feast/blob/master/java/serving/README.md) ## First, let's setup a Redis cluster 1. Start minikube (`minikube start`) @@ -26,12 +26,9 @@ For this tutorial, we setup Feast with Redis, using the Feast CLI to register an ``` ## Next, we setup a local Feast repo -1. Install Feast with Redis dependencies `pip install ‘feast[redis]’` +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. Inspecting this directory, we see: - - - +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 @@ -53,15 +50,17 @@ For this tutorial, we setup Feast with Redis, using the Feast CLI to register an feast materialize-incremental $CURRENT_TIME ``` -## Now let's setup the k8s cluster +## Now let's setup the Feast Server 1. Add the gcp-auth addon to mount GCP credentials: - `minikube addons enable gcp-auth` -2. Add Feast's Java feature server chart repo + ```bash + minikube addons enable gcp-auth + ``` +3. Add Feast's Java feature server chart repo ```bash helm repo add feast-charts https://feast-helm-charts.storage.googleapis.com helm repo update ``` -3. Modify the application-override.yaml file to have your credentials + bucket location: +4. Modify the application-override.yaml file to have your credentials + bucket location: ```yaml feature-server: application-override.yaml: @@ -81,12 +80,16 @@ For this tutorial, we setup Feast with Redis, using the Feast CLI to register an cache_ttl_seconds: 60 project: feast_java_demo ``` -4. Install the Feast helm chart: `helm install feast-release feast-charts/feast --values application-override.yaml` -5. (Optional): check logs of the server to make sure it’s working - 1. `kubectl logs svc/feast-release-feature-server` -6. Port forward to expose the grpc endpoint: - 1. `kubectl port-forward svc/feast-release-feature-server 6566:6566` -7. Make a gRPC call: +5. Install the Feast helm chart: `helm install feast-release feast-charts/feast --values application-override.yaml` +6. (Optional): check logs of the server to make sure it’s working + ```bash + kubectl logs svc/feast-release-feature-server + ``` +7. Port forward to expose the grpc endpoint: + ```bash + kubectl port-forward svc/feast-release-feature-server 6566:6566 + ``` +8. Make a gRPC call: - Python example ```bash python test.py diff --git a/examples/java-demo/feature_repo/application-override.yaml b/examples/java-demo/feature_repo/application-override.yaml index d4aab0c910a..dbdeda4c04f 100644 --- a/examples/java-demo/feature_repo/application-override.yaml +++ b/examples/java-demo/feature_repo/application-override.yaml @@ -9,9 +9,9 @@ feature-server: config: host: my-redis-master port: 6379 - password: WW2Jew09Yt + password: [YOUR PASSWORD] global: registry: - path: gs://feast-demo-staging/demo-repo/registry.db + path: gs://[YOUR BUCKET]/demo-repo/registry.db cache_ttl_seconds: 60 - project: feast_java_demo \ No newline at end of file + project: feast_java_demo diff --git a/examples/java-demo/feature_repo/feature_store.yaml b/examples/java-demo/feature_repo/feature_store.yaml index 9cfdde5449a..11bc8ffa5d9 100644 --- a/examples/java-demo/feature_repo/feature_store.yaml +++ b/examples/java-demo/feature_repo/feature_store.yaml @@ -3,7 +3,7 @@ project: feast_java_demo provider: gcp online_store: type: redis - connection_string: localhost:6379,password=WW2Jew09Yt + connection_string: localhost:6379,password=cmqD98DXBn offline_store: type: file flags: diff --git a/examples/java-demo/feature_repo/test.py b/examples/java-demo/feature_repo/test.py index e561fe2ceb7..f73883019d6 100644 --- a/examples/java-demo/feature_repo/test.py +++ b/examples/java-demo/feature_repo/test.py @@ -14,7 +14,7 @@ def fetch_java(): feature_refs = FeatureList(val=["driver_hourly_stats:conv_rate"]) entity_rows = { "driver_id": RepeatedValue( - val=[Value(int64_val=driver_id) for driver_id in range(1001, 1020)] + val=[Value(int64_val=driver_id) for driver_id in range(1001, 1003)] ) } diff --git a/examples/java-demo/tree.png b/examples/java-demo/tree.png deleted file mode 100644 index 1b1508ae4a9742b3259defd270c64608fddfe79c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10353 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 Date: Wed, 9 Feb 2022 19:37:13 -0500 Subject: [PATCH 10/15] remove credentials Signed-off-by: Danny Chiao --- examples/java-demo/feature_repo/feature_store.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/java-demo/feature_repo/feature_store.yaml b/examples/java-demo/feature_repo/feature_store.yaml index 11bc8ffa5d9..91c65b512ad 100644 --- a/examples/java-demo/feature_repo/feature_store.yaml +++ b/examples/java-demo/feature_repo/feature_store.yaml @@ -1,9 +1,9 @@ -registry: gs://feast-demo-staging/demo-repo/registry.db +registry: gs://[YOUR BUCKET]/demo-repo/registry.db project: feast_java_demo provider: gcp online_store: type: redis - connection_string: localhost:6379,password=cmqD98DXBn + connection_string: localhost:6379,password=[YOUR PASSWORD] offline_store: type: file flags: From e593a4db8d967f201ae718c097b8123f96e2860a Mon Sep 17 00:00:00 2001 From: Danny Chiao Date: Wed, 9 Feb 2022 19:38:08 -0500 Subject: [PATCH 11/15] new line Signed-off-by: Danny Chiao --- infra/charts/feast/charts/feature-server/templates/secret.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/charts/feast/charts/feature-server/templates/secret.yaml b/infra/charts/feast/charts/feature-server/templates/secret.yaml index ad1b030eaa2..b6aa88c258e 100644 --- a/infra/charts/feast/charts/feature-server/templates/secret.yaml +++ b/infra/charts/feast/charts/feature-server/templates/secret.yaml @@ -20,4 +20,4 @@ stringData: {{- end }} {{- if index .Values "application-secret.yaml" "grpc" }} grpc: {{- toYaml (index .Values "application-secret.yaml" "grpc") | nindent 6 }} - {{- end }} \ No newline at end of file + {{- end }} From 0ec175ff0df09401d5e66d4ea0985c426bb3cb38 Mon Sep 17 00:00:00 2001 From: Danny Chiao Date: Wed, 9 Feb 2022 19:38:34 -0500 Subject: [PATCH 12/15] new line Signed-off-by: Danny Chiao --- java/infra/docker/feature-server/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/infra/docker/feature-server/Dockerfile b/java/infra/docker/feature-server/Dockerfile index 4f9e136aac1..669c1806a98 100644 --- a/java/infra/docker/feature-server/Dockerfile +++ b/java/infra/docker/feature-server/Dockerfile @@ -20,7 +20,7 @@ COPY java/docs/coverage/pom.xml docs/coverage/pom.xml # user to optionally use cached repository when building the image by copying # the existing .m2 directory to $FEAST_REPO_ROOT/.m2 ENV MAVEN_OPTS="-Dmaven.repo.local=/build/.m2/repository -DdependencyLocationsEnabled=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=25 -Dmaven.wagon.http.retryHandler.count=3" -# COPY java/pom.xml .m2/* .m2/ +COPY java/pom.xml .m2/* .m2/ RUN mvn dependency:go-offline -DexcludeGroupIds:dev.feast 2>/dev/null || true COPY java/ . From 1b3bff63e46ddd9340ea32bfbc2a9d027a4a78ac Mon Sep 17 00:00:00 2001 From: Danny Chiao Date: Wed, 9 Feb 2022 19:40:17 -0500 Subject: [PATCH 13/15] new line Signed-off-by: Danny Chiao --- java/infra/docker/feature-server/Dockerfile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/java/infra/docker/feature-server/Dockerfile b/java/infra/docker/feature-server/Dockerfile index 669c1806a98..dbd8c914724 100644 --- a/java/infra/docker/feature-server/Dockerfile +++ b/java/infra/docker/feature-server/Dockerfile @@ -34,8 +34,8 @@ RUN mvn --also-make --projects serving -Drevision=$VERSION \ # https://kubernetes.io/blog/2018/10/01/health-checking-grpc-servers-on-kubernetes/ # RUN wget -q https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/v0.3.1/grpc_health_probe-linux-amd64 \ - -O /usr/bin/grpc-health-probe && \ - chmod +x /usr/bin/grpc-health-probe + -O /usr/bin/grpc-health-probe && \ + chmod +x /usr/bin/grpc-health-probe # ============================================================ # Build stage 2: Production @@ -46,7 +46,7 @@ ARG VERSION=dev COPY --from=builder /build/serving/target/feast-serving-$VERSION-jar-with-dependencies.jar /opt/feast/feast-serving.jar COPY --from=builder /usr/bin/grpc-health-probe /usr/bin/grpc-health-probe CMD ["java",\ - "-Xms1g",\ - "-Xmx4g",\ - "-jar",\ - "/opt/feast/feast-serving.jar"] + "-Xms1g",\ + "-Xmx4g",\ + "-jar",\ + "/opt/feast/feast-serving.jar"] From 416a8dc7e2fd6d58d7eea9fff93025e57c0e68c6 Mon Sep 17 00:00:00 2001 From: Danny Chiao Date: Thu, 10 Feb 2022 12:11:10 -0500 Subject: [PATCH 14/15] Add reference from Gitbook Signed-off-by: Danny Chiao --- docs/SUMMARY.md | 1 + .../fetching-java-features-k8s.md | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 docs/how-to-guides/fetching-java-features-k8s.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 439742af9f5..deec3e9eed0 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -46,6 +46,7 @@ * [Load data into the online store](how-to-guides/feast-snowflake-gcp-aws/load-data-into-the-online-store.md) * [Read features from the online store](how-to-guides/feast-snowflake-gcp-aws/read-features-from-the-online-store.md) * [Running Feast in production](how-to-guides/running-feast-in-production.md) +* [Deploying a Java feature server on Kubernetes](how-to-guides/fetching-java-features-k8s.md) * [Upgrading from Feast 0.9](https://docs.google.com/document/u/1/d/1AOsr\_baczuARjCpmZgVd8mCqTF4AZ49OEyU4Cn-uTT0/edit) * [Adding a custom provider](how-to-guides/creating-a-custom-provider.md) * [Adding a new online store](how-to-guides/adding-support-for-a-new-online-store.md) diff --git a/docs/how-to-guides/fetching-java-features-k8s.md b/docs/how-to-guides/fetching-java-features-k8s.md new file mode 100644 index 00000000000..7e0cccc6e95 --- /dev/null +++ b/docs/how-to-guides/fetching-java-features-k8s.md @@ -0,0 +1,20 @@ +--- +description: >- +How to use Feast to define features in Python and retrieve from them with a Java feature server deployed in Kubernetes +--- + +# How to set up a Java feature server + +This tutorial guides you on how to: + +* Define features and data sources in Feast using the Feast CLI +* Materialize features to a Redis cluster deployed on Kubernetes. +* Deploy a Feast Java feature server into a Kubernetes cluster using the Feast helm charts +* Retrieve features using the gRPC API exposed by the Feast Java server + +Try it and let us know what you think! + +| ![](../.gitbook/assets/github-mark-32px.png)[ View guide in Github](../../examples/java-demo/README.md) | +|:--------------------------------------------------------------------------------------------------------| + + From 81759f91194a0e7becf9e7f0cb98db580dedc47f Mon Sep 17 00:00:00 2001 From: Danny Chiao Date: Thu, 10 Feb 2022 12:17:26 -0500 Subject: [PATCH 15/15] remove description Signed-off-by: Danny Chiao --- docs/how-to-guides/fetching-java-features-k8s.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/how-to-guides/fetching-java-features-k8s.md b/docs/how-to-guides/fetching-java-features-k8s.md index 7e0cccc6e95..1aa6abd52b0 100644 --- a/docs/how-to-guides/fetching-java-features-k8s.md +++ b/docs/how-to-guides/fetching-java-features-k8s.md @@ -1,8 +1,3 @@ ---- -description: >- -How to use Feast to define features in Python and retrieve from them with a Java feature server deployed in Kubernetes ---- - # How to set up a Java feature server This tutorial guides you on how to: