Deploy Feast on Kubernetes¶
Build Feast Docker Image Locally¶
go to feast/
folder. 就會看到以下檔案
registry
: PostgreSQLonline_store
: Redisoffline_store
: BigQuery
account_features.py
from datetime import timedelta
from feast import (BigQuerySource, Entity, FeatureService, FeatureView,
ValueType, Field)
from feast.types import String, Int64, Bool
# Data Sources
# https://rtd.feast.dev/en/latest/index.html#feast.infra.offline_stores.bigquery_source.BigQuerySource
ds_acct_fraud_7d = BigQuerySource(
table=f"mlops-437709.dbt_kclai.feat_acct_fraud_7d",
timestamp_field="feature_timestamp"
)
ds_acct_num_txns_7d = BigQuerySource(
table=f"mlops-437709.dbt_kclai.feat_acct_num_txns_7d",
timestamp_field="feature_timestamp"
)
ds_acct_profiles = BigQuerySource(
table=f"mlops-437709.dbt_kclai.feat_acct_profiles",
timestamp_field="feature_timestamp"
)
# Entity
account_entity = Entity(
name="Account",
description="A user that has executed a transaction or received a transaction",
value_type=ValueType.STRING,
join_keys=["entity_id"]
)
# Feature Views
fv_acct_fraud_7d = FeatureView(
name="acct_fraud_7d",
entities=[account_entity],
schema=[
Field(name="has_fraud_7d", dtype=Bool)
],
ttl=timedelta(weeks=52),
source=ds_acct_fraud_7d
)
fv_acct_num_txns_7d = FeatureView(
name="acct_num_txns_7d",
entities=[account_entity],
schema=[
Field(name="num_transactions_7d", dtype=Int64)
],
ttl=timedelta(weeks=1),
source=ds_acct_num_txns_7d
)
fv_acct_profiles = FeatureView(
name="acct_profiles",
entities=[account_entity],
schema=[
Field(name="credit_score", dtype=Int64),
Field(name="account_age_days", dtype=Int64),
Field(name="has_2fa_installed", dtype=Bool)
],
ttl=timedelta(weeks=52),
source=ds_acct_profiles
)
# Feature Services
# Versioning features that power ML models:
# https://docs.feast.dev/master/how-to-guides/running-feast-in-production#id-3.2-versioning-features-that-power-ml-models
fs_fraud_detection_v1 = FeatureService(
name="fraud_detection_v1",
features=[
fv_acct_fraud_7d,
fv_acct_num_txns_7d[["num_transactions_7d"]],
fv_acct_profiles
]
)
entrypoint.sh
#!/bin/bash
set -e
echo "Running feast apply..."
feast apply
echo "Starting feast server..."
exec feast serve --host 0.0.0.0 --port 8080
Dockerfile
# Use Python 3.10 slim as the base image
FROM python:3.10-slim
# Set the working directory
WORKDIR /app
# Install Python dependencies
COPY requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
COPY entrypoint.sh /app/entrypoint.sh
RUN chmod +x entrypoint.sh
COPY feature_store.yaml /app/feature_store.yaml
COPY account_features.py /app/account_features.py
# Set the entrypoint to run Feast
ENTRYPOINT ["./entrypoint.sh"]
Build image
確認建置成功
Load Image into Minikube Cluster¶
在Local進入minikube
查看目前minikube裡有的images,沒有feast
REPOSITORY TAG IMAGE ID CREATED SIZE
registry.k8s.io/kube-apiserver v1.30.0 181f57fd3cdb 12 months ago 112MB
registry.k8s.io/kube-controller-manager v1.30.0 68feac521c0f 12 months ago 107MB
registry.k8s.io/kube-proxy v1.30.0 cb7eac0b42cc 12 months ago 87.9MB
registry.k8s.io/kube-scheduler v1.30.0 547adae34140 12 months ago 60.5MB
registry.k8s.io/etcd 3.5.12-0 014faa467e29 15 months ago 139MB
registry.k8s.io/coredns/coredns v1.11.1 2437cf762177 21 months ago 57.4MB
registry.k8s.io/pause 3.9 829e9de338bd 2 years ago 514kB
gcr.io/k8s-minikube/storage-provisioner v5 ba04bb24b957 4 years ago 29MB
docker@minikube:~$
在local開啟另個terminal,將local的docker image載入到minikube cluster裡
回到minikube裡,查看images,有feast了
REPOSITORY TAG IMAGE ID CREATED SIZE
feast v0.1.0 436abcf371e9 33 minutes ago 596MB
registry.k8s.io/kube-apiserver v1.30.0 181f57fd3cdb 12 months ago 112MB
registry.k8s.io/kube-controller-manager v1.30.0 68feac521c0f 12 months ago 107MB
registry.k8s.io/kube-scheduler v1.30.0 547adae34140 12 months ago 60.5MB
registry.k8s.io/kube-proxy v1.30.0 cb7eac0b42cc 12 months ago 87.9MB
registry.k8s.io/etcd 3.5.12-0 014faa467e29 15 months ago 139MB
registry.k8s.io/coredns/coredns v1.11.1 2437cf762177 21 months ago 57.4MB
registry.k8s.io/pause 3.9 829e9de338bd 2 years ago 514kB
gcr.io/k8s-minikube/storage-provisioner v5 ba04bb24b957 4 years ago 29MB
K8S¶
Registry¶
registry.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: registry
namespace: feast
spec:
replicas: 1
selector:
matchLabels:
app: registry
template:
metadata:
labels:
app: registry
spec:
containers:
- name: registry
image: postgres
env:
- name: POSTGRES_DB
value: feast
- name: POSTGRES_USER
value: postgres
- name: POSTGRES_PASSWORD
value: password
ports:
- containerPort: 5432
volumeMounts:
- name: storage
mountPath: /var/lib/postgresql/data
volumes:
- name: storage
hostPath:
path: /home/docker/data/feast/registry
type: DirectoryOrCreate
---
apiVersion: v1
kind: Service
metadata:
name: registry
namespace: feast
spec:
selector:
app: registry
type: ClusterIP
ports:
- port: 5432
targetPort: 5432
Online Store¶
online-store.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: online-store
namespace: feast
spec:
replicas: 1
selector:
matchLabels:
app: online-store
template:
metadata:
labels:
app: online-store
spec:
containers:
- name: online-store
image: redis
ports:
- containerPort: 6379
protocol: TCP
volumeMounts:
- name: storage
mountPath: /data
restartPolicy: Always
volumes:
- name: storage
hostPath:
path: /home/docker/data/feast/online-store
type: DirectoryOrCreate
---
apiVersion: v1
kind: Service
metadata:
name: online-store
namespace: feast
spec:
selector:
app: online-store
type: ClusterIP
ports:
- port: 6379
targetPort: 6379
Secret¶
Create GCP Service Account Key¶
確認Project id
建立Service Account
Reauthentication required.
Please enter your password:
Reauthentication successful.
Created service account [feast-sa].
替Service Account加上BigQuery權限
gcloud projects add-iam-policy-binding mlops-437709 \
--member="serviceAccount:feast-sa@mlops-437709.iam.gserviceaccount.com" \
--role="roles/bigquery.admin"
[1] EXPRESSION=request.time < timestamp("2025-04-09T07:42:05.596Z"), TITLE=cloudbuild-connection-setup
[2] None
[3] Specify a new condition
The policy contains bindings with conditions, so specifying a condition is required when adding a binding. Please specify a condition.: 2
Updated IAM policy for project [mlops-437709].
bindings:
- members:
- serviceAccount:service-362026176730@gcp-sa-aiplatform-cc.iam.gserviceaccount.com
role: roles/aiplatform.customCodeServiceAgent
- members:
- serviceAccount:service-362026176730@gcp-sa-vertex-op.iam.gserviceaccount.com
role: roles/aiplatform.onlinePredictionServiceAgent
- members:
- serviceAccount:service-362026176730@gcp-sa-aiplatform.iam.gserviceaccount.com
role: roles/aiplatform.serviceAgent
- members:
- serviceAccount:service-362026176730@gcp-sa-artifactregistry.iam.gserviceaccount.com
role: roles/artifactregistry.serviceAgent
- members:
- serviceAccount:feast-sa@mlops-437709.iam.gserviceaccount.com
role: roles/bigquery.admin
- members:
- serviceAccount:362026176730@cloudbuild.gserviceaccount.com
role: roles/cloudbuild.builds.builder
- members:
- serviceAccount:service-362026176730@gcp-sa-cloudbuild.iam.gserviceaccount.com
role: roles/cloudbuild.serviceAgent
- members:
- serviceAccount:service-362026176730@containerregistry.iam.gserviceaccount.com
role: roles/containerregistry.ServiceAgent
- members:
- serviceAccount:362026176730-compute@developer.gserviceaccount.com
role: roles/editor
- members:
- serviceAccount:service-362026176730@gcp-sa-firestore.iam.gserviceaccount.com
role: roles/firestore.serviceAgent
- members:
- serviceAccount:362026176730@cloudbuild.gserviceaccount.com
role: roles/iam.serviceAccountUser
- members:
- serviceAccount:service-362026176730@cloud-ml.google.com.iam.gserviceaccount.com
role: roles/ml.serviceAgent
- members:
- user:edison@kcl10.com
role: roles/owner
- members:
- serviceAccount:service-362026176730@gcp-sa-pubsub.iam.gserviceaccount.com
role: roles/pubsub.serviceAgent
- members:
- serviceAccount:362026176730@cloudbuild.gserviceaccount.com
role: roles/run.admin
- members:
- serviceAccount:service-362026176730@serverless-robot-prod.iam.gserviceaccount.com
role: roles/run.serviceAgent
- condition:
expression: request.time < timestamp("2025-04-09T07:42:05.596Z")
title: cloudbuild-connection-setup
members:
- serviceAccount:service-362026176730@gcp-sa-cloudbuild.iam.gserviceaccount.com
role: roles/secretmanager.admin
etag: BwY0his6v7Y=
version: 3
建立Key
gcloud iam service-accounts keys create feast-gcp-key.json \
--iam-account=feast-sa@mlops-437709.iam.gserviceaccount.com
created key [a2609fffff05f5fdf311de233f1a2e1e89288ab5] of type [json] as [feast-gcp-key.json] for [feast-sa@mlops-437709.iam.gserviceaccount.com]
Create K8S Secret¶
apiVersion: v1
kind: Secret
metadata:
name: feast-gcp-key
namespace: feast
type: Opaque
data:
key.json: <base64 encoded string>
Online Feature Server¶
online-feature-server.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: online-feature-server
namespace: feast
spec:
replicas: 1
selector:
matchLabels:
app: online-feature-server
template:
metadata:
labels:
app: online-feature-server
spec:
containers:
- name: online-feature-server
image: feast:v0.1.8
ports:
- containerPort: 8080
env:
- name: FEAST_FEATURE_SERVER_CONFIG_PATH
value: /app/config/feature_store.yaml
- name: GOOGLE_APPLICATION_CREDENTIALS
value: /var/secrets/google/key.json
volumeMounts:
- name: gcp-sa-key
mountPath: /var/secrets/google
readOnly: true
volumes:
- name: gcp-sa-key
secret:
secretName: gcp-sa-key
---
apiVersion: v1
kind: Service
metadata:
name: online-feature-server
namespace: feast
spec:
selector:
app: online-feature-server
type: NodePort
ports:
- port: 8080
targetPort: 8080
nodePort: 30000
Deploy using Helm¶
Step 0: Create feast Namespace
namespace/feast created
Step 1: Deploy Online Store
deployment.apps/online-store created
service/online-store created
Waiting for deployment "online-store" rollout to finish: 0 of 1 updated replicas are available...
deployment "online-store" successfully rolled out
Step 2: Deploy Registry
deployment.apps/registry created
service/registry created
Waiting for deployment "registry" rollout to finish: 0 of 1 updated replicas are available...
deployment "registry" successfully rolled out
Step 3: Deploy Feast Online Feature Server
secret/gcp-sa-key created
deployment.apps/online-feature-server created
service/online-feature-server created
Waiting for deployment "online-feature-server" rollout to finish: 0 of 1 updated replicas are available...
deployment "online-feature-server" successfully rolled out
Test¶
|-----------|-----------------------|-------------|---------------------------|
| NAMESPACE | NAME | TARGET PORT | URL |
|-----------|-----------------------|-------------|---------------------------|
| feast | online-feature-server | 8080 | http://192.168.49.2:30000 |
|-----------|-----------------------|-------------|---------------------------|
🏃 Starting tunnel for service online-feature-server.
|-----------|-----------------------|-------------|------------------------|
| NAMESPACE | NAME | TARGET PORT | URL |
|-----------|-----------------------|-------------|------------------------|
| feast | online-feature-server | | http://127.0.0.1:52316 |
|-----------|-----------------------|-------------|------------------------|
🎉 Opening service feast/online-feature-server in default browser...
❗ Because you are using a Docker driver on darwin, the terminal needs to be open to run it.
curl -X POST http://127.0.0.1:52316/get-online-features \
-H "Content-Type: application/json" \
-d @request-get-online-features.json | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 100 1010 100 682 100 328 10006 4812 --:--:-- --:--:-- --:--:-- 14852
{
"metadata": {
"feature_names": [
"user_id",
"transaction_count_7d",
"credit_score",
"account_age_days",
"user_has_2fa_installed",
"user_has_fraudulent_transactions_7d"
]
},
"results": [
{
"values": [
"v5zlw0"
],
"statuses": [
"PRESENT"
],
"event_timestamps": [
"1970-01-01T00:00:00Z"
]
},
{
"values": [
null
],
"statuses": [
"PRESENT"
],
"event_timestamps": [
"1970-01-01T00:00:00Z"
]
},
{
"values": [
480
],
"statuses": [
"PRESENT"
],
"event_timestamps": [
"2025-04-29T22:00:34Z"
]
},
{
"values": [
655
],
"statuses": [
"PRESENT"
],
"event_timestamps": [
"2025-04-29T22:00:34Z"
]
},
{
"values": [
1
],
"statuses": [
"PRESENT"
],
"event_timestamps": [
"2025-04-29T22:00:34Z"
]
},
{
"values": [
0.0
],
"statuses": [
"PRESENT"
],
"event_timestamps": [
"2025-05-05T22:00:50Z"
]
}
]
}