Skip to content

Commit

Permalink
feat: Add Feast Operator RBAC example with Kubernetes Authentication …
Browse files Browse the repository at this point in the history
…type.

Signed-off-by: Abdul Hameed <ahameed@redhat.com>
  • Loading branch information
redhatHameed committed Feb 25, 2025
1 parent 2c46f6a commit 5f02b7b
Show file tree
Hide file tree
Showing 10 changed files with 1,422 additions and 4 deletions.
3 changes: 2 additions & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ The following examples illustrate various **Feast** use cases to enhance underst
The examples below showcase how to deploy and manage **Feast on Kubernetes** using the **Feast Go Operator**.

1. **[Operator Quickstart](operator-quickstart)**: Demonstrates how to install and use Feast on Kubernetes with the Feast Go Operator.
1. **[Operator Quickstart with Postgres in TLS](operator-postgres-tls-demo)**: Demonstrates installing and configuring Feast with PostgreSQL in TLS mode on Kubernetes using the Feast Go Operator, with an emphasis on volumes and VolumeMounts support.
1. **[Operator Quickstart with Postgres in TLS](operator-postgres-tls-demo)**: Demonstrates installing and configuring Feast with PostgreSQL in TLS mode on Kubernetes using the Feast Go Operator, with an emphasis on volumes and VolumeMounts support.
1. **[Operator RBAC with Kubernetes](operator-rbac)**: Demonstrates the Feast RBAC example on Kubernetes using the Feast Operator.
1 change: 1 addition & 0 deletions examples/operator-rbac/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/client/feature_repo/feature_store.yaml
6 changes: 6 additions & 0 deletions examples/operator-rbac/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Running the Feast RBAC example on Kubernetes using the Feast Operator.

The [k8s-rbac.ipynb](k8s-rbac.ipynb) will guide you through how to enable Role-Based Access Control (RBAC) for Feast using [Feast Operator](../../infra/feast-operator/) with the Kubernetes Authentication type.



50 changes: 50 additions & 0 deletions examples/operator-rbac/client/admin_user_resources.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: feast-admin-sa
namespace: feast-client
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: feast-admin-rolebinding
namespace: feast
subjects:
- kind: ServiceAccount
name: feast-admin-sa
namespace: feast-client
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: feast-writer
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: client-admin-user
namespace: feast-client
labels:
app: client-admin
spec:
replicas: 1
selector:
matchLabels:
app: client-admin
template:
metadata:
labels:
app: client-admin
spec:
serviceAccountName: feast-admin-sa
containers:
- name: client-admin-container
image: feastdev/feature-server:latest
imagePullPolicy: Always
command: ["sleep", "infinity"]
volumeMounts:
- name: client-feature-repo-config
mountPath: /opt/app-root/src
volumes:
- name: client-feature-repo-config
configMap:
name: client-feature-repo-config
140 changes: 140 additions & 0 deletions examples/operator-rbac/client/feature_repo/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import os
from datetime import datetime

import pandas as pd
from feast import FeatureStore
from feast.data_source import PushMode


def run_demo():
try:
os.environ["LOCAL_K8S_TOKEN"] = ""

store = FeatureStore(repo_path=".")

print("\n--- Historical features for training ---")
fetch_historical_features_entity_df(store, for_batch_scoring=False)

print("\n--- Historical features for batch scoring ---")
fetch_historical_features_entity_df(store, for_batch_scoring=True)

try:
print("\n--- Load features into online store/materialize_incremental ---")
feature_views= store.list_feature_views()
if not feature_views:
raise PermissionError("No access to feature-views or no feature-views available.")
store.materialize_incremental(end_date=datetime.now())
except PermissionError as pe:
print(f"Permission error: {pe}")
except Exception as e:
print(f"An occurred while performing materialize incremental: {e}")

print("\n--- Online features ---")
fetch_online_features(store)

print("\n--- Online features retrieved (instead) through a feature service---")
fetch_online_features(store, source="feature_service")

print(
"\n--- Online features retrieved (using feature service v3, which uses a feature view with a push source---"
)
fetch_online_features(store, source="push")

print("\n--- Simulate a stream event ingestion of the hourly stats df ---")
event_df = pd.DataFrame.from_dict(
{
"driver_id": [1001],
"event_timestamp": [datetime.now()],
"created": [datetime.now()],
"conv_rate": [1.0],
"acc_rate": [1.0],
"avg_daily_trips": [1000],
}
)
store.push("driver_stats_push_source", event_df, to=PushMode.ONLINE_AND_OFFLINE)

print("\n--- Online features again with updated values from a stream push---")
fetch_online_features(store, source="push")

except Exception as e:
print(f"An error occurred: {e}")


def fetch_historical_features_entity_df(store: FeatureStore, for_batch_scoring: bool):
try:
entity_df = pd.DataFrame.from_dict(
{
"driver_id": [1001, 1002, 1003],
"event_timestamp": [
datetime(2021, 4, 12, 10, 59, 42),
datetime(2021, 4, 12, 8, 12, 10),
datetime(2021, 4, 12, 16, 40, 26),
],
"label_driver_reported_satisfaction": [1, 5, 3],
# values we're using for an on-demand transformation
"val_to_add": [1, 2, 3],
"val_to_add_2": [10, 20, 30],

}

)
if for_batch_scoring:
entity_df["event_timestamp"] = pd.to_datetime("now", utc=True)

training_df = store.get_historical_features(
entity_df=entity_df,
features=[
"driver_hourly_stats:conv_rate",
"driver_hourly_stats:acc_rate",
"driver_hourly_stats:avg_daily_trips",
"transformed_conv_rate:conv_rate_plus_val1",
"transformed_conv_rate:conv_rate_plus_val2",
],
).to_df()
print(training_df.head())

except Exception as e:
print(f"An error occurred while fetching historical features: {e}")


def fetch_online_features(store, source: str = ""):
try:
entity_rows = [
# {join_key: entity_value}
{
"driver_id": 1001,
"val_to_add": 1000,
"val_to_add_2": 2000,
},
{
"driver_id": 1002,
"val_to_add": 1001,
"val_to_add_2": 2002,
},
]
if source == "feature_service":
features_to_fetch = store.get_feature_service("driver_activity_v1")
elif source == "push":
features_to_fetch = store.get_feature_service("driver_activity_v3")
else:
features_to_fetch = [
"driver_hourly_stats:acc_rate",
"transformed_conv_rate:conv_rate_plus_val1",
"transformed_conv_rate:conv_rate_plus_val2",
]
returned_features = store.get_online_features(
features=features_to_fetch,
entity_rows=entity_rows,
).to_dict()
for key, value in sorted(returned_features.items()):
print(key, " : ", value)

except Exception as e:
print(f"An error occurred while fetching online features: {e}")


if __name__ == "__main__":
try:
run_demo()
except Exception as e:
print(f"An error occurred in the main execution: {e}")
51 changes: 51 additions & 0 deletions examples/operator-rbac/client/readonly_user_resources.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: feast-user-sa
namespace: feast-client
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: feast-user-rolebinding
namespace: feast
subjects:
- kind: ServiceAccount
name: feast-user-sa
namespace: feast-client
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: feast-reader
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: client-readonly-user
namespace: feast-client
labels:
app: client-user
spec:
replicas: 1
selector:
matchLabels:
app: client-user
template:
metadata:
labels:
app: client-user
spec:
serviceAccountName: feast-user-sa
containers:
- name: client-user-container
image: feastdev/feature-server:latest
imagePullPolicy: Always
command: ["sleep", "infinity"]
volumeMounts:
- name: client-feature-repo-config
mountPath: /opt/app-root/src
volumes:
- name: client-feature-repo-config
configMap:
name: client-feature-repo-config

36 changes: 36 additions & 0 deletions examples/operator-rbac/client/unauthorized_user_resources.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: feast-unauthorized-user-sa
namespace: feast-client
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: client-unauthorized-user
namespace: feast-client
labels:
app: client-unauthorized-user
spec:
replicas: 1
selector:
matchLabels:
app: client-unauthorized-user
template:
metadata:
labels:
app: client-unauthorized-user
spec:
serviceAccountName: feast-unauthorized-user-sa
containers:
- name: client-unauthorized-user-container
image: feastdev/feature-server:latest
imagePullPolicy: Always
command: ["sleep", "infinity"]
volumeMounts:
- name: client-feature-repo-config
mountPath: /opt/app-root/src
volumes:
- name: client-feature-repo-config
configMap:
name: client-feature-repo-config
Loading

0 comments on commit 5f02b7b

Please sign in to comment.