DEV Community

Cover image for OpenTelemetry in Action on Kubernetes: Part 3 - Deploying the Application on Kubernetes
Kartik Dudeja
Kartik Dudeja

Posted on • Edited on

OpenTelemetry in Action on Kubernetes: Part 3 - Deploying the Application on Kubernetes

Deploying Our Instrumented ML App to Kubernetes

Welcome to Part 3! If you’ve followed along so far, by the end of Part 2 you had:

  • A FastAPI-based machine learning app
  • Instrumented with OpenTelemetry for full-stack observability
  • Dockerized and ready to ship

Now, it's time to bring in the big orchestration guns — Kubernetes.

OTel-k8s


Understanding Kubernetes Deployment & Service

Before we throw YAML at a cluster, let’s understand what these two crucial building blocks do:

Deployment

A Deployment in Kubernetes manages a set of replicas (identical Pods running our app). It provides:

  • Declarative updates: You describe what you want, K8s makes it so.
  • Rolling updates: Smooth upgrades without downtime.
  • Self-healing: If a Pod dies, K8s spins up a new one.

Think of it as a smart manager for your app's pods.

Service

A Service exposes your app inside the cluster (or externally, if needed). It:

  • Provides a stable DNS name.
  • Load balances traffic between pods.
  • In our case, exposes:

    • Port 80 → App port 8000 (FastAPI HTTP)
    • Port 4317 → OTLP gRPC (Telemetry)

Kubernetes Manifest Breakdown

Let’s break down the configuration:

Deployment: house-price-service

apiVersion: apps/v1 kind: Deployment metadata: name: house-price-service 
Enter fullscreen mode Exit fullscreen mode

We declare a Deployment that manages our app.

spec: replicas: 2 
Enter fullscreen mode Exit fullscreen mode

We want 2 replicas of our app running — high availability for the win.

 strategy: type: RollingUpdate rollingUpdate: maxSurge: 25% maxUnavailable: 25% 
Enter fullscreen mode Exit fullscreen mode

Kubernetes will update pods gracefully. It allows some extra pods during rollout and ensures some stay alive.

 containers: - name: app image: house-price-predictor:v2 
Enter fullscreen mode Exit fullscreen mode

We use the Docker image built in Part 2, deployed as a container.

 ports: - containerPort: 8000 # App port - containerPort: 4317 # OTLP telemetry port 
Enter fullscreen mode Exit fullscreen mode

Complete Deployment Manifest:

apiVersion: apps/v1 kind: Deployment metadata: name: house-price-service labels: app: house-price-service spec: replicas: 2 revisionHistoryLimit: 3 strategy: type: RollingUpdate rollingUpdate: maxSurge: 25% # Allow 25% more pods than desired during update maxUnavailable: 25% # Allow 25% of desired pods to be unavailable during update selector: matchLabels: app: house-price-service template: metadata: labels: app: house-price-service spec: containers: - name: app image: house-price-predictor:v2 imagePullPolicy: IfNotPresent resources: requests: cpu: "10m" memory: "128Mi" limits: cpu: "20m" memory: "256Mi" ports: - containerPort: 8000 # Application Port - containerPort: 4317 # OTLP gRPC Port 
Enter fullscreen mode Exit fullscreen mode

Service: house-price-service

apiVersion: v1 kind: Service metadata: name: house-price-service labels: app: house-price-service 
Enter fullscreen mode Exit fullscreen mode

This ClusterIP Service lets other K8s workloads communicate with our app.

 ports: - port: 80 targetPort: 8000 - port: 4317 targetPort: 4317 
Enter fullscreen mode Exit fullscreen mode

The Service maps:

  • Port 80 → App HTTP server
  • Port 4317 → For OTLP spans, metrics, logs

Complete Service Manifest File:

apiVersion: v1 kind: Service metadata: name: house-price-service labels: app: house-price-service spec: selector: app: house-price-service ports: - name: http protocol: TCP port: 80 targetPort: 8000 - name: otlp-grpc protocol: TCP port: 4317 targetPort: 4317 type: ClusterIP 
Enter fullscreen mode Exit fullscreen mode

Add both in the one file: house-price-app.yaml

Deploying with kubectl

Before deploying the app, let's create a Kubernetes namespace. This helps group related resources together.

kubectl create namespace mlapp 
Enter fullscreen mode Exit fullscreen mode

Run the following to deploy your app:

kubectl -n mlapp apply -f house-price-app.yaml 
Enter fullscreen mode Exit fullscreen mode

To check the deployment status:

kubectl -n mlapp get deployments kubectl -n mlapp get pods 
Enter fullscreen mode Exit fullscreen mode

mlapp-k8s-get-deploy-pods

To see pod logs (structured JSON + OpenTelemetry info):

kubectl -n mlapp logs -f -l app=house-price-service 
Enter fullscreen mode Exit fullscreen mode

To view the exposed service:

kubectl -n mlapp get svc -l app=house-price-service 
Enter fullscreen mode Exit fullscreen mode

Testing the App in Kubernetes

Get the Endpoint IP from the K8s service:

API_ENDPOINT_IP=$(kubectl -n mlapp get svc -l app=house-price-service -o json | jq -r '.items[].spec.clusterIP') 
Enter fullscreen mode Exit fullscreen mode

Test it locally using curl or Postman:

curl -X POST "http://${API_ENDPOINT_IP}:80/predict/" \ -H "Content-Type: application/json" \ -d '{"features": [1200]}' 
Enter fullscreen mode Exit fullscreen mode

You should get a prediction response like:

{"predicted_price": 170000.0} 
Enter fullscreen mode Exit fullscreen mode

And voilà — telemetry data is flowing.

What’s Next: Meet the OpenTelemetry Collector

In Part 4, we’ll introduce the OpenTelemetry Collector Agent:

  • Deploy it as a DaemonSet alongside your app
  • Configure it to collect traces, metrics, and logs
  • Route the data to a gateway, and onward to backends like Prometheus, Jaeger, and Loki

TL;DR: It’s where the real observability magic begins.


{ "author" : "Kartik Dudeja", "email" : "kartikdudeja21@gmail.com", "linkedin" : "https://linkedin.com/in/kartik-dudeja", "github" : "https://github.com/Kartikdudeja" } 
Enter fullscreen mode Exit fullscreen mode

Top comments (0)