Skip to content

Commit

Permalink
PR4: Write a Kubernetes deployment YAML (Deployment, Service) for you…
Browse files Browse the repository at this point in the history
…r model's API.
  • Loading branch information
danilyef committed Nov 25, 2024
1 parent 79ad295 commit eff81c4
Show file tree
Hide file tree
Showing 7 changed files with 224 additions and 0 deletions.
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()

0 comments on commit eff81c4

Please sign in to comment.