{"id":246,"date":"2026-04-29T15:29:30","date_gmt":"2026-04-29T15:29:30","guid":{"rendered":"https:\/\/blog.ngocha.biz\/?p=246"},"modified":"2026-04-29T15:29:30","modified_gmt":"2026-04-29T15:29:30","slug":"setup-feature-store-feast-on-kubernetes","status":"publish","type":"post","link":"https:\/\/blog.ngocha.biz\/?p=246","title":{"rendered":"How to Setup Feast Feature Store on Kubernetes"},"content":{"rendered":"<p>In this step-by-step guide, you will learn to setup and validate Feast Feature store on Kubernetes using Feast Operator. <\/p>\n<p>Feature Store is the core component of <a href=\"https:\/\/devopscube.com\/devops-to-mlops\/\" rel=\"noreferrer\">MLOps<\/a> and <a href=\"https:\/\/feast.dev\/?ref=devopscube.com\" rel=\"noreferrer\">Feast<\/a> is the widely used feature store for ML projects.<\/p>\n<p>By the end of this guide, you will have a clear understanding of:<\/p>\n<ul>\n<li>What is Feast?<\/li>\n<li>Key Feast components<\/li>\n<li>Feast Operator setup on Kubernetes<\/li>\n<li>Deploy Feast using CRD and configure offline and online stores.<\/li>\n<li>Use a simple Python script to verify feature serving<\/li>\n<\/ul>\n<p>Lets get started.<\/p>\n<h2 id=\"what-is-feast\">What is Feast?<\/h2>\n<p>Feast is an open source feature store used by teams to store, manage, and serve ML features for both training and inference. <\/p>\n<p>Feast acts as a centralized storage for features that will be used by different teams. The following image illustrates the Feast architecture on <a href=\"https:\/\/devopscube.com\/kubernetes-tutorials-beginners\/\" rel=\"noreferrer\">Kubernetes<\/a>. <\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2026\/04\/image-112.png\" class=\"kg-image\" alt=\"Architecture diagram of the Feast feature store on Kubernetes showing core components\" loading=\"lazy\" width=\"2000\" height=\"1345\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2026\/04\/image-112.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2026\/04\/image-112.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1600\/2026\/04\/image-112.png 1600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w2400\/2026\/04\/image-112.png 2400w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>The following are the key components of Feast.<\/p>\n<ul>\n<li><strong>PostgreSQL Feature Registry<\/strong>: It stores the definitions (schema) of the features. Eg,. age, name, etc.<\/li>\n<li><strong>Redis Online store<\/strong>: It is used to reduce the latency during inferencing.<\/li>\n<li><strong>Offline store<\/strong>: It is used to store historical features used for model training.<\/li>\n<li><strong>Feast pod<\/strong>: It serves latest features to ML model by reading from the registry and the online store.<\/li>\n<li><strong>Feast CLI<\/strong>: It is a tool used to connect Feast and to run Feast commands. It connects to feature registry, offline Store and online Store<\/li>\n<\/ul>\n<p>Lets get started with the setup.<\/p>\n<h2 id=\"setup-prerequisites\">Setup Prerequisites<\/h2>\n<p>Following  are the prerequisites to follow this guide.<\/p>\n<ul>\n<li><a href=\"https:\/\/devopscube.com\/upgrade-kubernetes-cluster-kubeadm\/\" rel=\"noreferrer\">Kubernetes cluster<\/a><\/li>\n<li><a href=\"https:\/\/devopscube.com\/kubectl-set-context\/\" rel=\"noreferrer\">kubectl<\/a><\/li>\n<li>Python 3+<\/li>\n<li>Pip<\/li>\n<\/ul>\n<h2 id=\"github-repository\">GitHub Repository<\/h2>\n<p>First, fork and clone the <a href=\"https:\/\/github.com\/techiescamp\/mlops-for-devops.git?ref=devopscube.com\" rel=\"noreferrer\">MLOPs Github repository. <\/a><\/p>\n<pre><code>https:\/\/github.com\/techiescamp\/mlops-for-devops.git<\/code><\/pre>\n<p>CD into the <code>mlops-for-devops<\/code> directory.<\/p>\n<pre><code class=\"language-bash\">\ncd mlops-for-devops<\/code><\/pre>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-text\"><b><strong style=\"white-space: pre-wrap;\">Note:<\/strong><\/b> I have already pushed the required manifest file for PostgreSQL. Redis and Feast CR inside the <code spellcheck=\"false\" style=\"white-space: pre-wrap;\">platform-tools\/feast<\/code> folder of the repository that you cloned before.<\/div>\n<\/div>\n<p>The files we are going to use in this guide are shown below.<\/p>\n<pre><code class=\"language-bash\">.\n\u251c\u2500\u2500 phase-1-local-dev\n\u2502   \u2514\u2500\u2500 datasets\n\u2502       \u2514\u2500\u2500 employee_attrition.csv\n\u251c\u2500\u2500 phase-2-enterprise-setup\n\u2502   \u2514\u2500\u2500 feast\n\u2502      \u251c\u2500\u2500 csv_to_parquet.py\n\u2502      \u251c\u2500\u2500 feature_store.yaml\n\u2502      \u251c\u2500\u2500 feature_definitions.py\n\u2502      \u2514\u2500\u2500 smoke_test.py\n\u2514\u2500\u2500 platform-tools\n    \u2514\u2500\u2500 feast\n        \u251c\u2500\u2500 feast.yaml\n        \u251c\u2500\u2500 psql.yaml\n        \u2514\u2500\u2500 redis.yaml\n<\/code><\/pre>\n<h2 id=\"setup-feast-on-a-kubernetes-cluster\">Setup Feast on a Kubernetes Cluster<\/h2>\n<p>The first step is to setup Feast on a <a href=\"https:\/\/devopscube.com\/kubernetes-architecture-explained\/\" rel=\"noreferrer\">Kubernetes cluster<\/a> using the <a href=\"https:\/\/github.com\/feast-dev\/feast\/tree\/master\/infra\/feast-operator?ref=devopscube.com\" rel=\"noreferrer\">Feast Operator.<\/a><\/p>\n<p>Here is what we are going to do.<\/p>\n<ul>\n<li>Setup feast operator<\/li>\n<li>Deploy Redis and <a href=\"https:\/\/devopscube.com\/deploy-postgresql-statefulset\/\" rel=\"noreferrer\">PostgreSQL<\/a> for the registry and Online store<\/li>\n<li>Uses <strong><code>FeatureStore<\/code><\/strong> <a href=\"https:\/\/devopscube.com\/kubernetes-objects-resources\/\" rel=\"noreferrer\">Custom Resource<\/a> to create Feast feature store<\/li>\n<\/ul>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\ud83d\udca1<\/div>\n<div class=\"kg-callout-text\">We will expose Redis, PostgreSQL, and Feast as NodePort, because we need to access it from local to store and get features.<\/p>\n<p>In actual setup, use <a href=\"https:\/\/devopscube.com\/kubernetes-ingress-tutorial\/\" rel=\"noreferrer\">Ingress<\/a> or <a href=\"https:\/\/devopscube.com\/kubernetes-gateway-api\/\" rel=\"noreferrer\">Gateway API <\/a>to expose Feast externally.<\/div>\n<\/div>\n<p>Lets get started.<\/p>\n<h3 id=\"step1-install-feast-operator\">Step1: Install Feast Operator<\/h3>\n<p>Use the following command to deploy the Feast Operator. It will deploy the operator on the <code>feast-operator-system<\/code> namespace.<\/p>\n<pre><code class=\"language-bash\">kubectl apply --server-side --force-conflicts -f https:\/\/raw.githubusercontent.com\/feast-dev\/feast\/refs\/heads\/stable\/infra\/feast-operator\/dist\/install.yaml<\/code><\/pre>\n<p>Run the following command to verify if the operator is running and CRD is created.<\/p>\n<pre><code class=\"language-bash\">$ kubectl get pods -n feast-operator-system\n\n$ kubectl get crd featurestores.feast.dev<\/code><\/pre>\n<p>You should see the following output.<\/p>\n<pre><code class=\"language-bash\">NAME                                     READY  STATUS   RESTARTS  AGE\n\nfeast-operator-controller-manager-482tt  1\/1    Running  0         38s\n\nNAME                      CREATED AT\n\nfeaturestores.feast.dev   2026-01-08T10:32:38Z<\/code><\/pre>\n<h3 id=\"step-2-create-a-namespace-for-feast-deployment\">Step 2: Create a Namespace for Feast Deployment<\/h3>\n<p>We will be deploying the Feast pods on a dedicated namespace named <strong><code>feast<\/code><\/strong>. Create it using the following command.<\/p>\n<pre><code class=\"language-bash\">$ kubectl create namespace feast<\/code><\/pre>\n<h3 id=\"step-3-setup-postgresql-for-feast-registry\">Step 3: Setup PostgreSQL for Feast Registry<\/h3>\n<p>We need to set up PostgreSQL as a registry for Feast.  It stores the metadata of Feast.<\/p>\n<p>Assuming you are in the <code><strong>root<\/strong><\/code> directory, run the following command to move into the <code>platform-tools\/feast<\/code> folder.<\/p>\n<pre><code class=\"language-bash\">cd platform-tools\/feast<\/code><\/pre>\n<p>You will find the following files.<\/p>\n<pre><code class=\"language-bash\">.\n\u251c\u2500\u2500 feast.yaml\n\u251c\u2500\u2500 psql.yaml\n\u2514\u2500\u2500 redis.yaml<\/code><\/pre>\n<p>The <strong><code>psql.yaml<\/code><\/strong> manifest, deploys a kubernetes <strong><code>secret<\/code><\/strong> that contains the PostgreSQL username, password, and database name as shown below. (<strong>feast<\/strong> as default)<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2026\/04\/image-12.png\" class=\"kg-image\" alt=\"modify psql manifest file\" loading=\"lazy\" width=\"374\" height=\"232\"><\/figure>\n<p>It also contains manifest to deploy <a href=\"https:\/\/devopscube.com\/deploy-postgresql-statefulset\/\" rel=\"noreferrer\">PostgreSQL as a StatefulSet<\/a> and Service to expose PostgreSQL as Nodeport.<\/p>\n<p>Now, apply the PostgreSQL manifest using the following command.<\/p>\n<pre><code class=\"language-bash\">$ kubectl apply -f psql.yaml<\/code><\/pre>\n<p>Then run the following command to ensure the postgres <a href=\"https:\/\/devopscube.com\/kubernetes-pod\/\" rel=\"noreferrer\">pod<\/a> is up and running.<\/p>\n<pre><code class=\"language-bash\">$ kubectl get po -n feast\n\nNAME         READY   STATUS        RESTARTS   AGE\n\npostgres-0   1\/1     Running       0          34s<\/code><\/pre>\n<h3 id=\"step-4-setup-redis-for-feast-online-store\">Step 4: Setup Redis for Feast Online Store<\/h3>\n<p>Next, we need to deploy <strong>Redis Statefulset<\/strong>. It will be the <strong>online store for Feast.<\/strong><\/p>\n<p>The <code>redis.yaml<\/code> manifest creates a <code>secret<\/code> that contains the Redis password, Redis as StatefulSet, and a Service to expose Redis as Nodeport.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2026\/04\/image-14.png\" class=\"kg-image\" alt=\"\" loading=\"lazy\" width=\"401\" height=\"190\"><\/figure>\n<p>Now, apply the manifest using the following command.<\/p>\n<pre><code class=\"language-bash\">$ kubectl apply -f redis.yaml<\/code><\/pre>\n<p>Then run the following command to ensure the redis pod is up and running.<\/p>\n<pre><code class=\"language-bash\">$ kubectl get po -n feast\n\nNAME         READY   STATUS    RESTARTS   AGE\n\npostgres-0   1\/1     Running   0          1m\nredis-0      1\/1     Running   0          8s<\/code><\/pre>\n<p>You can see a Redis pod along with the PostgreSQL pod.<\/p>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\ud83d\udca1<\/div>\n<div class=\"kg-callout-text\">In this setup, we will be using a <b><strong style=\"white-space: pre-wrap;\">CSV file directory as offline store<\/strong><\/b>. For production environment, the offline store would be usually on BigQuery, Snowflake, Redshift, and <a href=\"https:\/\/docs.feast.dev\/reference\/offline-stores?ref=devopscube.com\">others<\/a>.<\/div>\n<\/div>\n<h3 id=\"step-5-deploy-feast-feature-store\">Step 5: Deploy Feast Feature Store<\/h3>\n<p>Now its time to deploy the Feast feature store using the <strong><code>FeatureStore<\/code><\/strong> CR. It tells the Feast operator to deploy the Feast pod, its service, and configure the backend storage.<\/p>\n<p>The <code>feast.yaml<\/code> manifest will create a secret that contains the <strong>connection string of Redis and PostgreSQL<\/strong>, along with the credentials to authenticate them.<\/p>\n<p>Additionally, it will also create an extra service to expose it as a NodePort.<\/p>\n<p>Apply the manifest using the following command.<\/p>\n<pre><code class=\"language-bash\">$ kubectl apply -f feast.yaml<\/code><\/pre>\n<p>Then run the following command to check if the Feast <a href=\"https:\/\/devopscube.com\/kubernetes-deployment-tutorial\/\" rel=\"noreferrer\">deployment<\/a> is up and running.<\/p>\n<pre><code class=\"language-bash\">$ kubectl get po -n feast\n\nNAME               READY   STATUS    RESTARTS   AGE\n\nfeast-feast-tbn5k  1\/1     Running   0          30s\npostgres-0         1\/1     Running   0          3m\nredis-0            1\/1     Running   0          2m<\/code><\/pre>\n<p>You can see that all three pods are running.<\/p>\n<h2 id=\"feature-repository\">Feature Repository<\/h2>\n<p>The <strong><code>phase-2-enterprise-setup\/feast<\/code><\/strong> directory which contains all the feast Python files to create, store, and retrieve features.  It is called the <strong>feature respository.<\/strong><\/p>\n<pre><code class=\"language-bash\">cd ..\/..\/phase-2-enterprise-setup\/feast\/<\/code><\/pre>\n<p>Here is the structure of our feature repository.<\/p>\n<pre><code class=\"language-bash\">.\n\u251c\u2500\u2500 csv_to_parquet.py\n\u251c\u2500\u2500 feature_store.yaml\n\u251c\u2500\u2500 feature_definitions.py\n\u251c\u2500\u2500 smoke_test.py\n\u2514\u2500\u2500.feastignore<\/code><\/pre>\n<p>In this:<\/p>\n<ul>\n<li><strong><code>feature_store.yaml<\/code><\/strong> &#8211; It is the configuration file that Feast CLI uses to read the Redis and PostgreSQL connection strings. It should be present in root of a feature repository<\/li>\n<li><strong><code>csv_to_parquet.py<\/code><\/strong> &#8211; Contains logic to process the CSV file and convert it to a Parquet file.<\/li>\n<li><strong><code>feature_definitions.py<\/code><\/strong>&#8211; It is the <strong>schema\/contract<\/strong>&nbsp;for the feature store (feature registry definition file)&nbsp;. Meaning, it defines&nbsp;what&nbsp;features exist,&nbsp;where&nbsp;the data comes from, and&nbsp;how&nbsp;they should be served<\/li>\n<li><strong><code>smoke_test.py<\/code><\/strong> &#8211; It is a smoke-test script&nbsp;that hits the Feast online feature store HTTP API and verifies that real-time feature retrieval is working end-to-end.<\/li>\n<li><strong><code>.feastignore<\/code><\/strong> &#8211; Contains file and folder names that should be ignored by feast apply.<\/li>\n<\/ul>\n<h2 id=\"store-features-into-feast-feature-store\">Store Features into Feast feature store<\/h2>\n<p>Now, our setup is ready. Now lets look at how to store features into Feast feature store. Both offline and online store.<\/p>\n<p>Lets get started.<\/p>\n<h3 id=\"step-1-setup-feast-cli\">Step 1: Setup Feast CLI<\/h3>\n<p>In the local environment, we need to install Feast CLI along with PostgreSQL and Redis packages.<\/p>\n<p>Run the following command to create a isolated virtual environment and install the Feast CLI with PostgreSQL and Redis packages.<\/p>\n<pre><code class=\"language-bash\">python3.11 -m venv env\n\nsource env\/bin\/activate\n\npip install --upgrade pip setuptools wheel\n\npip install 'feast[postgres,redis]'<\/code><\/pre>\n<h3 id=\"step-2-update-nodeport-ip-to-featurestoreyaml\">Step 2: Update NodePort  IP to feature_store.yaml<\/h3>\n<p>Since we need to run the feast commands from our local workstations, we have exposed Feast as NodePort. So you need to update one of the cluster node IP in the <strong><code>feature_store.yaml<\/code><\/strong> file.<\/p>\n<p>To get the node IP, run the following command.<\/p>\n<pre><code class=\"language-bash\">kubectl get no -o wide<\/code><\/pre>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\ud83d\udca1<\/div>\n<div class=\"kg-callout-text\">Ideally the feast endpoint would be a kubernetes service url if feast is running in the same cluster or a <a href=\"https:\/\/devopscube.com\/kubernetes-ingress-tutorial\/\" rel=\"noreferrer\">ingress<\/a> endpoint if it is in a different cluster.<\/div>\n<\/div>\n<h3 id=\"step-3-setup-offline-source-convert-csv-to-parquet\">Step 3:  Setup Offline Source (Convert CSV to Parquet)<\/h3>\n<p>For model training, an offline store is used. For <strong>file based feast offline store<\/strong>, we need to <strong>convert the CSV to Parquet format.<\/strong><\/p>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\ud83d\udca1<\/div>\n<div class=\"kg-callout-text\">Feast&#8217;s file-based offline store (local\/S3\/GCS) uses <a href=\"https:\/\/parquet.apache.org\/?ref=devopscube.com\" rel=\"noreferrer\"><b><strong style=\"white-space: pre-wrap;\">Parquet<\/strong><\/b><\/a> (A column-oriented data file format) &amp; <a href=\"https:\/\/delta.io\/?ref=devopscube.com\" rel=\"noreferrer\">Delta<\/a> as its source format. <\/p>\n<p>For other data warehouse-based offline stores like BigQuery and Redshift, Feast interacts with native tables and SQL queries.<\/p><\/div>\n<\/div>\n<p>The <strong><code>csv_to_parquet.py<\/code><\/strong> has the code to generate the Parquet file by processing the data on the <strong><code>employee_attrition.csv<\/code><\/strong> file. <\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2026\/04\/image-116.png\" class=\"kg-image\" alt=\"code to generate the Parquet file by processing the data on the employee_attrition.csv file. \" loading=\"lazy\" width=\"2000\" height=\"584\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2026\/04\/image-116.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2026\/04\/image-116.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1600\/2026\/04\/image-116.png 1600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2026\/04\/image-116.png 2371w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Here is high level overview of what the script does.<\/p>\n<ul>\n<li>Reads <code>employee_attrition.csv<\/code> from your <code>phase-1-local-dev\/datasets\/<\/code><\/li>\n<li>Since ML models need numbers, it converts <code>Yes\/No<\/code> columns (Overtime, Remote Work, etc.) to <code>1\/0<\/code><\/li>\n<li>Feast expects <code><strong>snake_case<\/strong><\/code> column names. So it converts <code>Monthly Income\"<\/code> to <code>\"monthly_income<\/code> etc<\/li>\n<li>Add Feast-required timestamp column like <strong><code>event_timestamp<\/code><\/strong> &amp; <strong><code>created<\/code><\/strong><\/li>\n<li>Writes the final output to <code>data\/employee_attrition_features.parquet<\/code> . This file becomes the <strong>offline data source<\/strong> that Feast reads from.<\/li>\n<\/ul>\n<p>Run the Python Script <strong><code>csv_to_parquet.py<\/code><\/strong> using the following command.<\/p>\n<pre><code class=\"language-bash\">python csv_to_parquet.py<\/code><\/pre>\n<p>Now, if you check the <code>\/phase-2-enterprise-setup\/feast\/data<\/code> folder, you can see a <code>employee_attrition_features.parquet<\/code> file created.<\/p>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\ud83d\udca1<\/div>\n<div class=\"kg-callout-text\">Now we are using the offline store in local environment under the <b><code spellcheck=\"false\" style=\"white-space: pre-wrap;\"><strong>\/data<\/strong><\/code><\/b> folder. It is perfect for local development. <\/p>\n<p>During actual training, the feature data (Parquet file) is generated using data pipelines ( <a href=\"https:\/\/www.kubeflow.org\/?ref=devopscube.com\" rel=\"noreferrer\">Kubeflow<\/a> or <a href=\"https:\/\/devopscube.com\/apache-airflow-on-kubernetes\/\" rel=\"noreferrer\">Airflow<\/a>) and stored on services like S3, GCS etc. <\/div>\n<\/div>\n<h3 id=\"step-4-register-features-to-feast\">Step 4: Register Features to Feast<\/h3>\n<p>I already have a Parquet file. <em>So why do I need to register features in Feast?<\/em><\/p>\n<p>Well, Parquet file only stores data. Feast needs a structured definition (schema, entity, source, and usage) to serve those features consistently across training and inference.<\/p>\n<p>Registering features involves two commands:<\/p>\n<ul>\n<li><strong>feast apply<\/strong> &#8211; This reads the <code>feature_definitions.py<\/code> that has the feature definitions and registers them in its internal registry (In our case its <a href=\"https:\/\/devopscube.com\/deploy-postgresql-statefulset\/\" rel=\"noreferrer\">PostgreSQL<\/a>). <\/li>\n<li><strong>feast materialize<\/strong> &#8211; This reads the definitions in PostgreSQL registry and Parquet file in the offline store and stores the values in Redis online store.<\/li>\n<\/ul>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\ud83d\udca1<\/div>\n<div class=\"kg-callout-text\">Materilizing is the porcess of moving features from offline store to the online store<\/div>\n<\/div>\n<p>The folloing image illustrates the feast apply and materialize workflows.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2026\/04\/image-114.png\" class=\"kg-image\" alt=\"image illustrating the feast apply and materialize workflows.\" loading=\"lazy\" width=\"2000\" height=\"1499\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2026\/04\/image-114.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2026\/04\/image-114.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1600\/2026\/04\/image-114.png 1600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w2400\/2026\/04\/image-114.png 2400w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Lets run the apply command.<\/p>\n<pre><code class=\"language-bash\">feast apply<\/code><\/pre>\n<p>You will get the following output if the apply command is successful.<\/p>\n<pre><code class=\"language-bash\">Applying changes for project employee_attrition\nDeploying infrastructure for employee_features<\/code><\/pre>\n<p>Now that the features are registered, you can view them using the following command. When a developer runs this, they immediately know exactly which features the model needs and in what order without asking a data scientist.<\/p>\n<pre><code class=\"language-bash\">feast feature-views describe employee_features<\/code><\/pre>\n<p>You will get an output as shown below.<\/p>\n<pre><code class=\"language-yaml\">spec:\n  name: employee_features\n  entities:\n  - employee_id\n  features:\n  - name: age\n    valueType: INT64\n    description: Employee age\n  .\n  .<\/code><\/pre>\n<p>Here, the <strong>entity is the primary key<\/strong>. So, when you fetch features at inference time, you dont have to fetch all the features. You say &#8220;<em>give me features for<\/em> <code><em>employee_id<\/em><\/code>. Feast uses <code>employee_id<\/code> as the lookup key to find the right row in the online store (Ex: Redis)<\/p>\n<h3 id=\"step-5-setup-online-store-redis\">Step 5:  Setup Online Store (Redis)<\/h3>\n<p>To setup the online store, we need to <strong>run the materialize command<\/strong> as shown below.<\/p>\n<pre><code class=\"language-bash\">feast materialize-incremental $(date -u +\"%Y-%m-%dT%H:%M:%S\")<\/code><\/pre>\n<p>You will get the following output if the materialization is successful.<\/p>\n<pre><code class=\"language-bash\">Materializing 1 feature views to 2026-04-02 12:49:12+00:00 into the redis online store.\n\nemployee_features from 2025-04-02 12:49:39+00:00 to 2026-04-02 12:49:12+00:00:<\/code><\/pre>\n<p>In the feast command, <code>$(date -u +\"%Y-%m-%dT%H:%M:%S\")<\/code> means we are specifying the current UTC timestamp as the time features are materialized and stored in the registry for tracking.<\/p>\n<p>And, when you run the materialize-incremental command again, it will only save the changes from the last run.<\/p>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\ud83d\udca1<\/div>\n<div class=\"kg-callout-text\">In production setup, the feature materialization happens through Kubeflow pipeline or an Airflow DAG.<\/div>\n<\/div>\n<h3 id=\"step-6-validate-feature-serving\">Step 6: Validate Feature Serving<\/h3>\n<p>Now, the features have been added to the Feast feature store. Let&#8217;s use simple Python script to validate if Feast is able to serve the features.<\/p>\n<p>The Python script <strong>smoke_test.py <\/strong>we are going to use in the <code>phase-2-enterprise-setup\/feast<\/code> folder.<\/p>\n<p>This script sends multiple requests to the Feast online API and measures the time taken to fetch features. It prints feature serving latency metrics such as <strong>p50, p95, and p99<\/strong>, which are commonly used in SRE and DevOps.<\/p>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\ud83d\udca1<\/div>\n<div class=\"kg-callout-text\">Open the <code spellcheck=\"false\" style=\"white-space: pre-wrap;\">smoke_test.py<\/code> and update your <code spellcheck=\"false\" style=\"white-space: pre-wrap;\">node-ip<\/code> in <code spellcheck=\"false\" style=\"white-space: pre-wrap;\">FEAST_ENDPOINT<\/code><\/div>\n<\/div>\n<p>Now, run the script using the following command.<\/p>\n<pre><code class=\"language-bash\">$ python smoke_test.py <\/code><\/pre>\n<p>You will get the following output.<\/p>\n<pre><code class=\"language-bash\">Feature Serving Latency (100 runs)\nMin:  491.81 ms\np50:  513.82 ms\np95:  674.24 ms\np99:  732.57 ms\nMax:  732.57 ms\nMean: 542.29 ms<\/code><\/pre>\n<p>These metrics help us understand real-time feature serving performance.  It is particularly useful to define and validate <a href=\"https:\/\/sre.google\/sre-book\/service-level-objectives\/?ref=devopscube.com\" rel=\"noreferrer\"><strong>SLOs<\/strong><\/a><strong> (Service Level Objectives) as <\/strong>an MLOps engineer. <\/p>\n<p>For example, you can set an SLO like <em>\u201cp95 latency should be under 200 ms\u201d<\/em> .<\/p>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\ud83d\udca1<\/div>\n<div class=\"kg-callout-text\">In production these metrics are monitored using tools like <a href=\"https:\/\/devopscube.com\/setup-prometheus-monitoring-on-kubernetes\/\" rel=\"noreferrer\">Prometheus<\/a> and <a href=\"https:\/\/devopscube.com\/setup-grafana-kubernetes\/\" rel=\"noreferrer\">Grafana<\/a><\/div>\n<\/div>\n<h2 id=\"clean-up\">Clean Up<\/h2>\n<p>If you no longer need the setup, run the following commands to delete it<\/p>\n<pre><code class=\"language-bash\">$ kubectl delete namespace feast\n\n$ kubectl delete -f https:\/\/raw.githubusercontent.com\/feast-dev\/feast\/refs\/heads\/stable\/infra\/feast-operator\/dist\/install.yaml<\/code><\/pre>\n<h2 id=\"feast-troubleshooting\">Feast Troubleshooting<\/h2>\n<p>Following are some of the issues you may face and their solutions.<\/p>\n<h3 id=\"1-pvc-stuck-in-pending-state\">1. PVC Stuck in Pending State<\/h3>\n<p>While creating PostgreSQL and Redis, their PVC might end up in pending state.<\/p>\n<p>To troubleshoot, check the following:<\/p>\n<ul>\n<li>Dynamic PV creation is available in your cluster<\/li>\n<li>Check if you have given the correct <a href=\"https:\/\/devopscube.com\/provsion-persistent-volume-on-eks\/\" rel=\"noreferrer\">StorageClass<\/a> name under the <code>volumeClaimTemplate<\/code> block.<\/li>\n<\/ul>\n<h3 id=\"2-missing-crd-error\">2. Missing CRD Error<\/h3>\n<p>When you apply the feast.yaml manifest, you may get the following error.<\/p>\n<pre><code class=\"language-bash\">the server could not find the requested resource (post featurestores.feast.dev)<\/code><\/pre>\n<p>If you got this error, check if the Feast Operator pods are up and running.<\/p>\n<pre><code class=\"language-bash\">kubectl get pods -n feast-operator-system<\/code><\/pre>\n<p>If the pods are not running, delete and re-run the installation command.<\/p>\n<h2 id=\"conclusion\">Conclusion<\/h2>\n<p>You now have a production-style Feast setup running on Kubernetes, with the registry, online store, and feature serving all deployed through the Feast Operator.<\/p>\n<p>Most importantly, you have seen how <code>feast apply<\/code> registers schema in PostgreSQL, how <code>feast materialize<\/code> moves data from the offline store into Redis, and how the online API serves features.<\/p>\n<p>If you are a <a href=\"https:\/\/devopscube.com\/become-devops-engineer\/\" rel=\"noreferrer\">DevOps engineer<\/a>, understanding feast architecture and its workflows will help you in MLOps projects as it is a core component in ML development.<\/p>\n<p>Another key area is monitoring feature freshness and serving latency as SLOs. We will cover it in detail in a separate blog.<\/p>\n<p>If you have any questions or face any issues during the setup, do let us know in the comments.<\/p>\n<hr>\n<p><strong>Ngu\u1ed3n:<\/strong> <a href=\"https:\/\/devopscube.com\/setup-feature-store-feast-on-kubernetes\/\" target=\"_blank\" rel=\"noopener noreferrer\">How to Setup Feast Feature Store on Kubernetes \u2014 DevOpsCube<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Source: https:\/\/devopscube.com\/setup-feature-store-feast-on-kubernetes\/<\/p>\n","protected":false},"author":1,"featured_media":247,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-246","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops"],"_links":{"self":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/246","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=246"}],"version-history":[{"count":0,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/246\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/media\/247"}],"wp:attachment":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=246"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=246"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=246"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}