Skip to content

Commit 8487ac3

Browse files
committed
feat: Add Feast Operator RBAC example with Kubernetes Authentication type.
Signed-off-by: Abdul Hameed <ahameed@redhat.com>
1 parent 69d462c commit 8487ac3

File tree

9 files changed

+1420
-3
lines changed

9 files changed

+1420
-3
lines changed

examples/operator-rbac/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/client/feature_repo/feature_store.yaml

examples/operator-rbac/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Install and run a Feature Store on Kubernetes with the Feast Operator
2+
3+
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.
4+
5+
6+
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
apiVersion: v1
2+
kind: ServiceAccount
3+
metadata:
4+
name: feast-admin-sa
5+
namespace: feast-client
6+
---
7+
apiVersion: rbac.authorization.k8s.io/v1
8+
kind: RoleBinding
9+
metadata:
10+
name: feast-admin-rolebinding
11+
namespace: feast
12+
subjects:
13+
- kind: ServiceAccount
14+
name: feast-admin-sa
15+
namespace: feast-client
16+
roleRef:
17+
apiGroup: rbac.authorization.k8s.io
18+
kind: Role
19+
name: feast-writer
20+
---
21+
apiVersion: apps/v1
22+
kind: Deployment
23+
metadata:
24+
name: client-admin-user
25+
namespace: feast-client
26+
labels:
27+
app: client-admin
28+
spec:
29+
replicas: 1
30+
selector:
31+
matchLabels:
32+
app: client-admin
33+
template:
34+
metadata:
35+
labels:
36+
app: client-admin
37+
spec:
38+
serviceAccountName: feast-admin-sa
39+
containers:
40+
- name: client-admin-container
41+
image: feastdev/feature-server:latest
42+
imagePullPolicy: Always
43+
command: ["sleep", "infinity"]
44+
volumeMounts:
45+
- name: client-feature-repo-config
46+
mountPath: /opt/app-root/src
47+
volumes:
48+
- name: client-feature-repo-config
49+
configMap:
50+
name: client-feature-repo-config
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import os
2+
from datetime import datetime
3+
4+
import pandas as pd
5+
from feast import FeatureStore
6+
from feast.data_source import PushMode
7+
8+
9+
def run_demo():
10+
try:
11+
os.environ["LOCAL_K8S_TOKEN"] = ""
12+
13+
store = FeatureStore(repo_path=".")
14+
15+
print("\n--- Historical features for training ---")
16+
fetch_historical_features_entity_df(store, for_batch_scoring=False)
17+
18+
print("\n--- Historical features for batch scoring ---")
19+
fetch_historical_features_entity_df(store, for_batch_scoring=True)
20+
21+
try:
22+
print("\n--- Load features into online store/materialize_incremental ---")
23+
feature_views= store.list_feature_views()
24+
if not feature_views:
25+
raise PermissionError("No access to feature-views or no feature-views available.")
26+
store.materialize_incremental(end_date=datetime.now())
27+
except PermissionError as pe:
28+
print(f"Permission error: {pe}")
29+
except Exception as e:
30+
print(f"An occurred while performing materialize incremental: {e}")
31+
32+
print("\n--- Online features ---")
33+
fetch_online_features(store)
34+
35+
print("\n--- Online features retrieved (instead) through a feature service---")
36+
fetch_online_features(store, source="feature_service")
37+
38+
print(
39+
"\n--- Online features retrieved (using feature service v3, which uses a feature view with a push source---"
40+
)
41+
fetch_online_features(store, source="push")
42+
43+
print("\n--- Simulate a stream event ingestion of the hourly stats df ---")
44+
event_df = pd.DataFrame.from_dict(
45+
{
46+
"driver_id": [1001],
47+
"event_timestamp": [datetime.now()],
48+
"created": [datetime.now()],
49+
"conv_rate": [1.0],
50+
"acc_rate": [1.0],
51+
"avg_daily_trips": [1000],
52+
}
53+
)
54+
store.push("driver_stats_push_source", event_df, to=PushMode.ONLINE_AND_OFFLINE)
55+
56+
print("\n--- Online features again with updated values from a stream push---")
57+
fetch_online_features(store, source="push")
58+
59+
except Exception as e:
60+
print(f"An error occurred: {e}")
61+
62+
63+
def fetch_historical_features_entity_df(store: FeatureStore, for_batch_scoring: bool):
64+
try:
65+
entity_df = pd.DataFrame.from_dict(
66+
{
67+
"driver_id": [1001, 1002, 1003],
68+
"event_timestamp": [
69+
datetime(2021, 4, 12, 10, 59, 42),
70+
datetime(2021, 4, 12, 8, 12, 10),
71+
datetime(2021, 4, 12, 16, 40, 26),
72+
],
73+
"label_driver_reported_satisfaction": [1, 5, 3],
74+
# values we're using for an on-demand transformation
75+
"val_to_add": [1, 2, 3],
76+
"val_to_add_2": [10, 20, 30],
77+
78+
}
79+
80+
)
81+
if for_batch_scoring:
82+
entity_df["event_timestamp"] = pd.to_datetime("now", utc=True)
83+
84+
training_df = store.get_historical_features(
85+
entity_df=entity_df,
86+
features=[
87+
"driver_hourly_stats:conv_rate",
88+
"driver_hourly_stats:acc_rate",
89+
"driver_hourly_stats:avg_daily_trips",
90+
"transformed_conv_rate:conv_rate_plus_val1",
91+
"transformed_conv_rate:conv_rate_plus_val2",
92+
],
93+
).to_df()
94+
print(training_df.head())
95+
96+
except Exception as e:
97+
print(f"An error occurred while fetching historical features: {e}")
98+
99+
100+
def fetch_online_features(store, source: str = ""):
101+
try:
102+
entity_rows = [
103+
# {join_key: entity_value}
104+
{
105+
"driver_id": 1001,
106+
"val_to_add": 1000,
107+
"val_to_add_2": 2000,
108+
},
109+
{
110+
"driver_id": 1002,
111+
"val_to_add": 1001,
112+
"val_to_add_2": 2002,
113+
},
114+
]
115+
if source == "feature_service":
116+
features_to_fetch = store.get_feature_service("driver_activity_v1")
117+
elif source == "push":
118+
features_to_fetch = store.get_feature_service("driver_activity_v3")
119+
else:
120+
features_to_fetch = [
121+
"driver_hourly_stats:acc_rate",
122+
"transformed_conv_rate:conv_rate_plus_val1",
123+
"transformed_conv_rate:conv_rate_plus_val2",
124+
]
125+
returned_features = store.get_online_features(
126+
features=features_to_fetch,
127+
entity_rows=entity_rows,
128+
).to_dict()
129+
for key, value in sorted(returned_features.items()):
130+
print(key, " : ", value)
131+
132+
except Exception as e:
133+
print(f"An error occurred while fetching online features: {e}")
134+
135+
136+
if __name__ == "__main__":
137+
try:
138+
run_demo()
139+
except Exception as e:
140+
print(f"An error occurred in the main execution: {e}")
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
apiVersion: v1
2+
kind: ServiceAccount
3+
metadata:
4+
name: feast-user-sa
5+
namespace: feast-client
6+
---
7+
apiVersion: rbac.authorization.k8s.io/v1
8+
kind: RoleBinding
9+
metadata:
10+
name: feast-user-rolebinding
11+
namespace: feast
12+
subjects:
13+
- kind: ServiceAccount
14+
name: feast-user-sa
15+
namespace: feast-client
16+
roleRef:
17+
apiGroup: rbac.authorization.k8s.io
18+
kind: Role
19+
name: feast-reader
20+
---
21+
apiVersion: apps/v1
22+
kind: Deployment
23+
metadata:
24+
name: client-readonly-user
25+
namespace: feast-client
26+
labels:
27+
app: client-user
28+
spec:
29+
replicas: 1
30+
selector:
31+
matchLabels:
32+
app: client-user
33+
template:
34+
metadata:
35+
labels:
36+
app: client-user
37+
spec:
38+
serviceAccountName: feast-user-sa
39+
containers:
40+
- name: client-user-container
41+
image: feastdev/feature-server:latest
42+
imagePullPolicy: Always
43+
command: ["sleep", "infinity"]
44+
volumeMounts:
45+
- name: client-feature-repo-config
46+
mountPath: /opt/app-root/src
47+
volumes:
48+
- name: client-feature-repo-config
49+
configMap:
50+
name: client-feature-repo-config
51+
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
apiVersion: v1
2+
kind: ServiceAccount
3+
metadata:
4+
name: feast-unauthorized-user-sa
5+
namespace: feast-client
6+
---
7+
apiVersion: apps/v1
8+
kind: Deployment
9+
metadata:
10+
name: client-unauthorized-user
11+
namespace: feast-client
12+
labels:
13+
app: client-unauthorized-user
14+
spec:
15+
replicas: 1
16+
selector:
17+
matchLabels:
18+
app: client-unauthorized-user
19+
template:
20+
metadata:
21+
labels:
22+
app: client-unauthorized-user
23+
spec:
24+
serviceAccountName: feast-unauthorized-user-sa
25+
containers:
26+
- name: client-unauthorized-user-container
27+
image: feastdev/feature-server:latest
28+
imagePullPolicy: Always
29+
command: ["sleep", "infinity"]
30+
volumeMounts:
31+
- name: client-feature-repo-config
32+
mountPath: /opt/app-root/src
33+
volumes:
34+
- name: client-feature-repo-config
35+
configMap:
36+
name: client-feature-repo-config

0 commit comments

Comments
 (0)