From eff81c44158e2265635f4a2312f30d26dedf5dde Mon Sep 17 00:00:00 2001 From: danilyef Date: Mon, 25 Nov 2024 16:49:37 +0100 Subject: [PATCH] PR4: Write a Kubernetes deployment YAML (Deployment, Service) for your model's API. --- homework_9/pr4/Dockerfile | 17 ++++++ homework_9/pr4/README.md | 92 ++++++++++++++++++++++++++++++ homework_9/pr4/__init__.py | 0 homework_9/pr4/app.py | 42 ++++++++++++++ homework_9/pr4/k8s_deployment.yaml | 35 ++++++++++++ homework_9/pr4/requirements.txt | 12 ++++ homework_9/pr4/utils.py | 26 +++++++++ 7 files changed, 224 insertions(+) create mode 100644 homework_9/pr4/Dockerfile create mode 100644 homework_9/pr4/README.md create mode 100644 homework_9/pr4/__init__.py create mode 100644 homework_9/pr4/app.py create mode 100644 homework_9/pr4/k8s_deployment.yaml create mode 100644 homework_9/pr4/requirements.txt create mode 100644 homework_9/pr4/utils.py diff --git a/homework_9/pr4/Dockerfile b/homework_9/pr4/Dockerfile new file mode 100644 index 0000000..886a0c6 --- /dev/null +++ b/homework_9/pr4/Dockerfile @@ -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"] diff --git a/homework_9/pr4/README.md b/homework_9/pr4/README.md new file mode 100644 index 0000000..e05f9d4 --- /dev/null +++ b/homework_9/pr4/README.md @@ -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://: + ``` + 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. \ No newline at end of file diff --git a/homework_9/pr4/__init__.py b/homework_9/pr4/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/homework_9/pr4/app.py b/homework_9/pr4/app.py new file mode 100644 index 0000000..24c5d62 --- /dev/null +++ b/homework_9/pr4/app.py @@ -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) + ) \ No newline at end of file diff --git a/homework_9/pr4/k8s_deployment.yaml b/homework_9/pr4/k8s_deployment.yaml new file mode 100644 index 0000000..28ee54d --- /dev/null +++ b/homework_9/pr4/k8s_deployment.yaml @@ -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 diff --git a/homework_9/pr4/requirements.txt b/homework_9/pr4/requirements.txt new file mode 100644 index 0000000..ac4414a --- /dev/null +++ b/homework_9/pr4/requirements.txt @@ -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 \ No newline at end of file diff --git a/homework_9/pr4/utils.py b/homework_9/pr4/utils.py new file mode 100644 index 0000000..7565235 --- /dev/null +++ b/homework_9/pr4/utils.py @@ -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()