Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR4: Write a Kubernetes deployment YAML (Deployment, Service) for your model's API. #37

Merged
merged 1 commit into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions homework_9/pr4/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Use official Python image
FROM python:3.11

# Set working directory
WORKDIR /app

# Copy files
COPY . .

# Install dependencies
RUN pip install -r requirements.txt

# Expose the port the app runs on
EXPOSE 8000

# Start the FastAPI app
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
92 changes: 92 additions & 0 deletions homework_9/pr4/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
## correct home directory

```bash
cd homework_9/pr4
```

## start minikube

```bash
minikube start
eval $(minikube -p minikube docker-env)
```


## build docker image

```bash
docker build -t fastapi-app:latest .
```

## deploy to minikube

```bash
kubectl apply -f k8s_deployment.yaml
```

## get url

```bash
minikube service fastapi-service --url
```


## test predict

```bash
curl -X POST -H "Content-Type: application/json" \
-d '{"text": "this is good"}' \
http://127.0.0.1:51561/predict
```




In Kubernetes, **`type: NodePort`** is used in a Service when you want to access your application from outside the Kubernetes cluster (like your laptop or local browser).

Here’s why you might use it in simple terms:

---

### **1. Kubernetes Runs on Its Own Network**
- Kubernetes creates an internal network for all the Pods.
- By default, this network isn’t accessible from the outside (e.g., your computer).

---

### **2. Services Expose Pods**
- A **Service** connects your app (running in Pods) to the outside world.
- **`type: NodePort`** exposes your app on a specific port on every node in your cluster.

---

### **3. Why Use `NodePort`?**
- When you set `type: NodePort`, Kubernetes assigns a port (like `30000-32767`) on the node's IP address.
- You can now access your app by visiting:
```
http://<node-ip>:<node-port>
```
For example:
```
http://192.168.99.100:30000
```
Here, `192.168.99.100` is the Minikube node's IP, and `30000` is the NodePort.

---

### **4. Why Not Use ClusterIP?**
- By default, Services use **`type: ClusterIP`**, which only allows access *within* the Kubernetes cluster.
- This is useful for internal communication between apps but not for external access.

---

### **5. Why NodePort is Good for Minikube**
- In Minikube, you're running Kubernetes on your local machine.
- Using `NodePort` is a quick and simple way to test and access your app from your browser or other devices on the same network.

---

### **In Summary**
- **`type: NodePort`** makes your app accessible outside Kubernetes on a specific port.
- This is great for testing or development, especially in Minikube.
- Later, in production, you might use other Service types (like `LoadBalancer` or `Ingress`) for more advanced routing.
Empty file added homework_9/pr4/__init__.py
Empty file.
42 changes: 42 additions & 0 deletions homework_9/pr4/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from fastapi import FastAPI
from pydantic import BaseModel
from utils import Model

model = Model(model_name='distilbert-base-uncased-finetuned-sst-2-english')

app = FastAPI()


class SentimentRequest(BaseModel):
text: str

class SentimentResponse(BaseModel):
text: str
sentiment: str
probability: float

class ProbabilityResponse(BaseModel):
text: str
probability: float

@app.get("/")
def read_root():
return {"message": "Welcome to the sentiment analysis API"}

@app.post("/predict")
def predict_sentiment(request: SentimentRequest) -> SentimentResponse:
label = model.predict(request.text)
probability = model.predict_proba(request.text)
return SentimentResponse(
text=request.text,
sentiment=label,
probability=float(probability)
)

@app.post("/probability")
def get_probability(request: SentimentRequest) -> ProbabilityResponse:
probability = model.predict_proba(request.text)
return ProbabilityResponse(
text=request.text,
probability=float(probability)
)
35 changes: 35 additions & 0 deletions homework_9/pr4/k8s_deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: fastapi-app
labels:
app: fastapi-app
spec:
replicas: 2
selector:
matchLabels:
app: fastapi-app
template:
metadata:
labels:
app: fastapi-app
spec:
containers:
- name: fastapi-app
image: fastapi-app:latest
imagePullPolicy: Never
ports:
- containerPort: 8000
---
apiVersion: v1
kind: Service
metadata:
name: fastapi-service
spec:
selector:
app: fastapi-app
ports:
- protocol: TCP
port: 8000
targetPort: 8000
type: NodePort
12 changes: 12 additions & 0 deletions homework_9/pr4/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
transformers==4.42.3
numpy==1.26.4
pandas==2.2.1
torch==2.2.1
-f https://download.pytorch.org/whl/cpu
streamlit==1.39.0
pytest==8.3.0
gradio==4.44.1
fastapi[standard]==0.114.0
pydantic==2.8.2
uvicorn==0.30.5
requests==2.32.2
26 changes: 26 additions & 0 deletions homework_9/pr4/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from transformers import DistilBertTokenizer, DistilBertForSequenceClassification
import torch

class Model:
def __init__(self, model_name="distilbert-base-uncased-finetuned-sst-2-english"):
self.tokenizer = DistilBertTokenizer.from_pretrained(model_name)
self.model = DistilBertForSequenceClassification.from_pretrained(model_name)
self.model.eval()

def predict(self, text):
inputs = self.tokenizer(
text, return_tensors="pt", truncation=True, padding=True
)
with torch.no_grad():
outputs = self.model(**inputs)
predicted_class_id = torch.argmax(outputs.logits, dim=1).item()
return self.model.config.id2label[predicted_class_id]

def predict_proba(self, text):
inputs = self.tokenizer(
text, return_tensors="pt", truncation=True, padding=True
)
with torch.no_grad():
outputs = self.model(**inputs)
probabilities = torch.softmax(outputs.logits, dim=1)
return probabilities.squeeze().max().item()