Triển khai giao diện Chat Frontend trên Kubernetes
Xây dựng Docker Image cho ứng dụng Streamlit
Sử dụng Streamlit thay vì React để giảm thời gian phát triển và tích hợp nhanh chóng với backend Python. Chúng ta cần một Dockerfile chuẩn để đóng gói ứng dụng chat.
Tạo file frontend/Dockerfile với nội dung:
FROM python:3.10-slim
WORKDIR /app
# Cài đặt các thư viện cần thiết cho Streamlit và giao tiếp HTTP
RUN pip install --no-cache-dir streamlit requests
# Copy mã nguồn ứng dụng
COPY app.py .
# Tạo user không root để bảo mật
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
USER appuser
# Mở port mặc định của Streamlit
EXPOSE 8501
# Chạy ứng dụng, expose ra toàn bộ network
CMD ["streamlit", "run", "app.py", "--server.address=0.0.0.0", "--server.port=8501"]
Kết quả mong đợi: File Dockerfile được tạo sẵn sàng để build image, cấu hình chạy với user không root và expose port 8501.
Viết ứng dụng Chat đơn giản (app.py)
Ứng dụng này sẽ gửi yêu cầu POST đến Backend RAG (đã deploy ở Phần 6) và hiển thị kết quả trả về cho người dùng.
Tạo file frontend/app.py với nội dung:
import streamlit as st
import requests
import json
# Cấu hình URL Backend RAG (Service name trong k8s namespace)
# Đảm bảo backend service name là 'rag-backend' và port 8000
BACKEND_URL = "http://rag-backend:8000/chat"
st.set_page_config(page_title="Private LLM Chat", page_icon="🤖")
st.title("🤖 Chat với Private LLM")
# Khởi tạo session state cho lịch sử chat
if "messages" not in st.session_state:
st.session_state.messages = []
# Hiển thị lịch sử chat
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
# Input từ người dùng
if prompt := st.chat_input("Nhập câu hỏi của bạn..."):
# Hiển thị message của user
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user"):
st.markdown(prompt)
# Gửi yêu cầu đến Backend RAG
with st.chat_message("assistant"):
with st.spinner("Đang tư vấn..."):
try:
payload = {"question": prompt, "context": []} # Có thể thêm context nếu cần
response = requests.post(BACKEND_URL, json=payload, timeout=60)
response.raise_for_status()
result = response.json()
answer = result.get("answer", "Không tìm thấy câu trả lời.")
st.markdown(answer)
st.session_state.messages.append({"role": "assistant", "content": answer})
except requests.exceptions.RequestException as e:
error_msg = f"Lỗi kết nối Backend: {str(e)}"
st.error(error_msg)
st.session_state.messages.append({"role": "assistant", "content": error_msg})
except Exception as e:
error_msg = f"Lỗi không xác định: {str(e)}"
st.error(error_msg)
st.session_state.messages.append({"role": "assistant", "content": error_msg})
Kết quả mong đợi: File Python xử lý luồng chat, gọi API backend và hiển thị lỗi nếu kết nối thất bại.
Deploy Frontend lên Kubernetes
Sử dụng kubectl để tạo Deployment và Service cho frontend, đảm bảo nó nằm trong cùng namespace với backend để truy cập nội bộ.
Tạo file frontend/deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: rag-frontend
namespace: llm-system
labels:
app: rag-frontend
spec:
replicas: 1
selector:
matchLabels:
app: rag-frontend
template:
metadata:
labels:
app: rag-frontend
spec:
containers:
- name: streamlit-app
image: your-docker-registry/rag-frontend:latest
imagePullPolicy: Always
ports:
- containerPort: 8501
resources:
limits:
cpu: "500m"
memory: "256Mi"
requests:
cpu: "100m"
memory: "128Mi"
Kết quả mong đợi: Deployment được tạo, pod chạy trạng thái Running, kéo image từ registry.
Tạo file frontend/service.yaml:
apiVersion: v1
kind: Service
metadata:
name: rag-frontend
namespace: llm-system
labels:
app: rag-frontend
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 8501
protocol: TCP
selector:
app: rag-frontend
Kết quả mong đợi: Service nội bộ được tạo, map port 80 của service về port 8501 của container.
Thực thi lệnh deploy:
kubectl apply -f frontend/deployment.yaml
kubectl apply -f frontend/service.yaml
Kết quả mong đợi: Kubernetes trả về "deployment.apps/rag-frontend created" và "service/rag-frontend created".
Cấu hình Ingress Controller và HTTPS
Triển khai Nginx Ingress Controller
Cần Ingress Controller để định hướng traffic từ internet vào cluster. Sử dụng Nginx Ingress Controller qua Helm để dễ quản lý và cấu hình SSL.
Đảm bảo bạn đã cài Helm. Nếu chưa, thực hiện lệnh cài đặt (nếu cần):
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
chmod 700 get_helm.sh
./get_helm.sh
Kết quả mong đợi: Helm được cài đặt vào PATH, lệnh 'helm version' chạy thành công.
Thêm repository của Nginx Ingress và deploy:
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install nginx-ingress ingress-nginx/ingress-nginx \
--namespace ingress-nginx --create-namespace \
--set controller.replicaCount=2 \
--set controller.service.type=LoadBalancer
Kết quả mong đợi: Controller được deploy, Service type LoadBalancer trả về một External IP (địa chỉ IP công cộng của cloud provider hoặc IP máy chủ nếu dùng local).
Cấu hình Ingress Resource cho Frontend
Tạo tài nguyên Ingress để ánh xạ domain (ví dụ: chat.yourdomain.com) vào Service rag-frontend. Cần cấu hình TLS (HTTPS) ở đây.
Tạo file frontend/ingress.yaml:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: rag-frontend-ingress
namespace: llm-system
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
tls:
- hosts:
- chat.yourdomain.com
secretName: chat-tls-secret
rules:
- host: chat.yourdomain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: rag-frontend
port:
number: 80
Kết quả mong đợi: File cấu hình Ingress sẵn sàng, chỉ định host và secret SSL.
Tạo chứng chỉ SSL (Let's Encrypt hoặc Self-signed)
Để demo hoặc môi trường nội bộ, tạo chứng chỉ tự ký (self-signed). Để production, dùng cert-manager. Ở đây dùng self-signed để đảm bảo chạy ngay.
Thực thi lệnh tạo secret TLS:
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout tls.key -out tls.crt \
-subj "/CN=chat.yourdomain.com"
kubectl create secret tls chat-tls-secret \
--key tls.key --cert tls.crt \
--namespace llm-system
Kết quả mong đợi: File tls.key và tls.crt được tạo, secret 'chat-tls-secret' được tạo trong namespace llm-system.
Deploy Ingress:
kubectl apply -f frontend/ingress.yaml
Kết quả mong đợi: Ingress được tạo, trạng thái "Backend" hiển thị "Active" sau vài giây.
Verify kết quả Frontend
Kiểm tra xem Ingress đã route đúng hay chưa:
kubectl get ingress -n llm-system
Kết quả mong đợi: Bảng hiển thị IP của LoadBalancer, host chat.yourdomain.com và đường dẫn / trỏ đến backend.
Thêm host vào file /etc/hosts (trên máy local) để test:
echo " chat.yourdomain.com" | sudo tee -a /etc/hosts
Kết quả mong đợi: Truy cập https://chat.yourdomain.com trên trình duyệt thấy giao diện Streamlit chạy (có thể cảnh báo an toàn do self-signed, click "Advanced" -> "Proceed").
Tạo API Gateway để định tuyến yêu cầu
Khái niệm API Gateway trong kiến trúc này
Trong môi trường Kubernetes, Ingress đã đóng vai trò là API Gateway cơ bản. Tuy nhiên, để quản lý traffic, rate limiting và routing phức tạp (ví dụ: phân luồng giữa chat và admin), ta cấu hình Ingress với nhiều path hoặc sử dụng Gateway API. Ở đây ta tối ưu hóa Ingress Nginx để đóng vai trò Gateway.
Mục tiêu: Định tuyến /chat về Backend RAG và / về Frontend.
Cấu hình Ingress Gateway chung
Tạo một Ingress duy nhất để quản lý cả Frontend và Backend, tạo điểm vào chung cho hệ thống.
Tạo file gateway/ingress-gateway.yaml:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: llm-gateway
namespace: llm-system
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "true"
# Cấu hình rate limiting đơn giản (100 req/s)
nginx.ingress.kubernetes.io/limit-rpm: "100"
spec:
tls:
- hosts:
- api.yourdomain.com
- chat.yourdomain.com
secretName: llm-gateway-tls
rules:
# Route cho Frontend (Streamlit)
- host: chat.yourdomain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: rag-frontend
port:
number: 80
# Route cho Backend RAG API
- host: api.yourdomain.com
http:
paths:
- path: /chat
pathType: Prefix
backend:
service:
name: rag-backend
port:
number: 8000
- path: /
pathType: Prefix
backend:
service:
name: rag-backend
port:
number: 8000
Kết quả mong đợi: Ingress gateway được tạo, xử lý 2 host khác nhau, route /chat về backend service.
Tạo Secret TLS chung cho Gateway
Tạo một secret chung cho cả domain chat và api để đơn giản hóa quản lý chứng chỉ.
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout gateway.key -out gateway.crt \
-subj "/CN=*.yourdomain.com"
kubectl create secret tls llm-gateway-tls \
--key gateway.key --cert gateway.crt \
--namespace llm-system
Kết quả mong đợi: Secret 'llm-gateway-tls' được tạo.
Deploy Gateway:
kubectl apply -f gateway/ingress-gateway.yaml
Kết quả mong đợi: Ingress llm-gateway được tạo và active.
Verify kết quả API Gateway
Kiểm tra cấu hình Ingress:
kubectl get ingress llm-gateway -n llm-system -o yaml
Kết quả mong đợi: Xem cấu hình chi tiết, đảm bảo paths và backends đúng.
Test truy cập API Backend từ bên ngoài (dùng curl):
curl -k https://api.yourdomain.com/chat \
-H "Content-Type: application/json" \
-d '{"question": "Hello", "context": []}'
Kết quả mong đợi: Trả về JSON chứa câu trả lời từ vLLM (ví dụ: {"answer": "Hello! How can I help?"}). Nếu lỗi, kiểm tra logs của pod rag-backend.
Test truy cập Frontend:
curl -k https://chat.yourdomain.com
Kết quả mong đợi: Trả về HTML source code của ứng dụng Streamlit.
Điều hướng series:
Mục lục: Series: Xây dựng nền tảng Private LLM với vLLM, RAG và VectorDB trên hạ tầng Kubernetes
« Phần 6: Xây dựng ứng dụng RAG Backend kết nối vLLM và VectorDB
Phần 8: Tối ưu hóa hiệu năng và scaling tự động cho Private LLM »