diff --git a/deploy/openshift/openwebui/README.md b/deploy/openshift/openwebui/README.md new file mode 100644 index 00000000..49d3ad9b --- /dev/null +++ b/deploy/openshift/openwebui/README.md @@ -0,0 +1,97 @@ +# OpenWebUI OpenShift Integration + +This directory contains the OpenShift deployment manifests for OpenWebUI, integrated with the existing semantic-router deployment. + +## Architecture + +- **Namespace**: `vllm-semantic-router-system` (same as semantic-router) +- **Backend Integration**: Connects to Envoy proxy endpoint with load balancing +- **External Access**: Available via OpenShift Route with HTTPS +- **Storage**: Persistent volume for user data and configurations + +## Quick Deployment + +### Using Scripts (Recommended) + +```bash +# Deploy OpenWebUI with full validation and setup +./deploy-openwebui-on-openshift.sh + +# Uninstall OpenWebUI (preserves data by default) +./uninstall-openwebui.sh +``` + +### Using Kubernetes Manifests + +```bash +# Deploy OpenWebUI manifests individually +oc apply -f pvc.yaml +oc apply -f deployment.yaml +oc apply -f service.yaml +oc apply -f route.yaml + +# Check deployment status +oc get pods -n vllm-semantic-router-system -l app=openwebui + +# Get the external URL +oc get route openwebui -n vllm-semantic-router-system -o jsonpath='{.spec.host}' +``` + +## Configuration + +OpenWebUI is configured to connect to the Envoy proxy automatically: + +- **Backend URL**: `http://semantic-router.vllm-semantic-router-system.svc.cluster.local:8801/v1` +- **Available Models**: `auto` (load balancer), `Model-A`, `Model-B` +- **Port**: Service exposed on port 3000, mapped to container port 8080 +- **Storage**: 2Gi persistent volume for user data + +### OpenWebUI Settings + +When configuring OpenWebUI in the interface: + +- **API Base URL**: `http://semantic-router.vllm-semantic-router-system.svc.cluster.local:8801/v1` +- **API Key**: `not-needed-for-local-models` (or leave empty) +- **Models**: Will auto-discover `auto`, `Model-A`, and `Model-B` + +## Files + +- `deploy-openwebui-on-openshift.sh` - Complete deployment script with validation +- `uninstall-openwebui.sh` - Safe uninstall script with data preservation options +- `deployment.yaml` - OpenWebUI deployment with OpenShift security contexts +- `service.yaml` - ClusterIP service exposing port 3000 +- `route.yaml` - OpenShift route for external HTTPS access +- `pvc.yaml` - Persistent volume claim for data storage +- `kustomization.yaml` - Kustomize configuration for easy deployment + +## Usage + +1. **Deploy**: Run `./deploy-openwebui-on-openshift.sh` +2. **Access**: Open the provided HTTPS URL in your browser +3. **Configure**: Models are pre-configured and auto-discovered +4. **Chat**: Start conversations with Model-A, Model-B, or auto (load balanced) + +## Cleanup + +```bash +# Safe uninstall with data preservation option +./uninstall-openwebui.sh + +# Or remove all resources immediately +oc delete -f route.yaml -f service.yaml -f deployment.yaml -f pvc.yaml +``` + +## Features + +- **Zero-config Setup**: Automatically connects to semantic-router +- **Load Balancing**: Access both models through Envoy proxy +- **Persistent Data**: User conversations and settings preserved +- **OpenShift Security**: Runs with restricted security contexts +- **HTTPS Access**: Secure external access via OpenShift routes +- **Health Monitoring**: Built-in health checks and monitoring + +## Troubleshooting + +- **503 Errors**: Check if service endpoints are available with `oc get endpoints openwebui` +- **Connection Issues**: Verify semantic-router is running with `oc get pods -l app=semantic-router` +- **Model Discovery**: Test backend connectivity with the deployment script validation diff --git a/deploy/openshift/openwebui/deploy-openwebui-on-openshift.sh b/deploy/openshift/openwebui/deploy-openwebui-on-openshift.sh new file mode 100755 index 00000000..58c37b96 --- /dev/null +++ b/deploy/openshift/openwebui/deploy-openwebui-on-openshift.sh @@ -0,0 +1,129 @@ +#!/bin/bash + +# Deploy OpenWebUI on OpenShift +# This script deploys OpenWebUI to work with the existing semantic-router deployment + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Script directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +echo -e "${BLUE}🚀 OpenWebUI OpenShift Deployment Script${NC}" +echo "================================================" + +# Check if oc is installed and logged in +if ! command -v oc &> /dev/null; then + echo -e "${RED}❌ Error: 'oc' command not found. Please install OpenShift CLI.${NC}" + exit 1 +fi + +if ! oc whoami &> /dev/null; then + echo -e "${RED}❌ Error: Not logged into OpenShift. Please run 'oc login'.${NC}" + exit 1 +fi + +# Get current user and project +CURRENT_USER=$(oc whoami) +echo -e "${GREEN}✅ Logged in as: ${CURRENT_USER}${NC}" + +# Check if we're in the right namespace or if it exists +NAMESPACE="vllm-semantic-router-system" +if ! oc get namespace "$NAMESPACE" &> /dev/null; then + echo -e "${RED}❌ Error: Namespace '$NAMESPACE' not found.${NC}" + echo "Please ensure the semantic-router is deployed first." + exit 1 +fi + +# Switch to the namespace +echo -e "${YELLOW}📁 Switching to namespace: ${NAMESPACE}${NC}" +oc project "$NAMESPACE" + +# Check if semantic-router is running +echo -e "${YELLOW}🔍 Checking semantic-router deployment...${NC}" +if ! oc get deployment semantic-router &> /dev/null; then + echo -e "${RED}❌ Error: semantic-router deployment not found.${NC}" + echo "Please deploy semantic-router first." + exit 1 +fi + +if ! oc get deployment semantic-router -o jsonpath='{.status.readyReplicas}' | grep -q "1"; then + echo -e "${YELLOW}⚠️ Warning: semantic-router deployment may not be ready.${NC}" + echo "Continuing with OpenWebUI deployment..." +fi + +echo -e "${GREEN}✅ semantic-router found and ready${NC}" + +# Deploy OpenWebUI components +echo -e "${YELLOW}🔧 Deploying OpenWebUI components...${NC}" + +echo " 📦 Creating Persistent Volume Claim..." +oc apply -f "$SCRIPT_DIR/pvc.yaml" + +echo " 🚀 Creating Deployment..." +oc apply -f "$SCRIPT_DIR/deployment.yaml" + +echo " 🌐 Creating Service..." +oc apply -f "$SCRIPT_DIR/service.yaml" + +echo " 🔗 Creating Route..." +oc apply -f "$SCRIPT_DIR/route.yaml" + +# Wait for deployment to be ready +echo -e "${YELLOW}⏳ Waiting for OpenWebUI deployment to be ready...${NC}" +oc rollout status deployment/openwebui --timeout=300s + +# Get the route URL +ROUTE_URL=$(oc get route openwebui -o jsonpath='{.spec.host}') +if [ -z "$ROUTE_URL" ]; then + echo -e "${RED}❌ Error: Could not get route URL${NC}" + exit 1 +fi + +# Check if OpenWebUI is responding +echo -e "${YELLOW}🔍 Testing OpenWebUI endpoint...${NC}" +if curl -k -s -o /dev/null -w "%{http_code}" "https://$ROUTE_URL" | grep -q "200"; then + echo -e "${GREEN}✅ OpenWebUI is responding${NC}" +else + echo -e "${YELLOW}⚠️ OpenWebUI may still be starting up...${NC}" +fi + +# Test backend connectivity +echo -e "${YELLOW}🔍 Testing backend connectivity...${NC}" +if oc exec deployment/openwebui -- curl -s -o /dev/null -w "%{http_code}" \ + "http://semantic-router.vllm-semantic-router-system.svc.cluster.local:8801/v1/models" | grep -q "200"; then + echo -e "${GREEN}✅ Backend connectivity working${NC}" +else + echo -e "${YELLOW}⚠️ Backend connectivity may need time to establish...${NC}" +fi + +# Display deployment information +echo "" +echo -e "${GREEN}🎉 OpenWebUI deployment completed successfully!${NC}" +echo "================================================" +echo -e "${BLUE}📊 Deployment Summary:${NC}" +echo " 🌐 URL: https://$ROUTE_URL" +echo " 🎯 Backend: http://semantic-router.vllm-semantic-router-system.svc.cluster.local:8801/v1" +echo " 📂 Namespace: $NAMESPACE" +echo "" +echo -e "${BLUE}🔧 Available Models:${NC}" +echo " • auto (load balancer)" +echo " • Model-A (Qwen/Qwen3-0.6B)" +echo " • Model-B (Qwen/Qwen3-0.6B)" +echo "" +echo -e "${BLUE}📝 Configuration for OpenWebUI:${NC}" +echo " • API Base URL: http://semantic-router.vllm-semantic-router-system.svc.cluster.local:8801/v1" +echo " • API Key: not-needed-for-local-models (or leave empty)" +echo "" +echo -e "${YELLOW}💡 Next Steps:${NC}" +echo " 1. Open https://$ROUTE_URL in your browser" +echo " 2. Complete initial setup in OpenWebUI" +echo " 3. The models should be automatically available" +echo "" +echo -e "${GREEN}✨ Happy chatting with your models!${NC}" \ No newline at end of file diff --git a/deploy/openshift/openwebui/deployment.yaml b/deploy/openshift/openwebui/deployment.yaml new file mode 100644 index 00000000..56b413e8 --- /dev/null +++ b/deploy/openshift/openwebui/deployment.yaml @@ -0,0 +1,85 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: openwebui + namespace: vllm-semantic-router-system + labels: + app: openwebui + component: frontend +spec: + replicas: 1 + selector: + matchLabels: + app: openwebui + template: + metadata: + labels: + app: openwebui + component: frontend + spec: + containers: + - name: openwebui + image: ghcr.io/open-webui/open-webui:main + ports: + - containerPort: 8080 + name: http + protocol: TCP + env: + # OpenWebUI Configuration - Connect to Envoy proxy + - name: OPENAI_API_BASE_URL + value: "http://semantic-router.vllm-semantic-router-system.svc.cluster.local:8801/v1" + - name: OPENAI_API_KEY + value: "not-needed-for-local-models" + - name: WEBUI_SECRET_KEY + value: "your-secret-key-change-in-production" + - name: DATA_DIR + value: "/app/backend/data" + # Enable multiple OpenAI-compatible endpoints + - name: ENABLE_OPENAI_API + value: "true" + # OpenShift compatible paths + - name: HOME + value: "/tmp/home" + - name: TMPDIR + value: "/tmp" + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + seccompProfile: + type: RuntimeDefault + volumeMounts: + - name: openwebui-data + mountPath: /app/backend/data + - name: tmp-volume + mountPath: /tmp + livenessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 10 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 15 + periodSeconds: 15 + timeoutSeconds: 5 + failureThreshold: 3 + resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "1Gi" + cpu: "500m" + volumes: + - name: openwebui-data + persistentVolumeClaim: + claimName: openwebui-data + - name: tmp-volume + emptyDir: {} \ No newline at end of file diff --git a/deploy/openshift/openwebui/kustomization.yaml b/deploy/openshift/openwebui/kustomization.yaml new file mode 100644 index 00000000..29a15993 --- /dev/null +++ b/deploy/openshift/openwebui/kustomization.yaml @@ -0,0 +1,22 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +metadata: + name: openwebui + namespace: vllm-semantic-router-system + +# Resources that make up the OpenWebUI deployment +resources: + - pvc.yaml + - deployment.yaml + - service.yaml + - route.yaml + +# Common labels applied to all resources +commonLabels: + app.kubernetes.io/name: openwebui + app.kubernetes.io/component: frontend + app.kubernetes.io/part-of: semantic-router + +# Namespace for all resources +namespace: vllm-semantic-router-system \ No newline at end of file diff --git a/deploy/openshift/openwebui/pvc.yaml b/deploy/openshift/openwebui/pvc.yaml new file mode 100644 index 00000000..5b76d798 --- /dev/null +++ b/deploy/openshift/openwebui/pvc.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: openwebui-data + namespace: vllm-semantic-router-system + labels: + app: openwebui + component: frontend +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 2Gi + # Use default storage class for OpenShift + # storageClassName: "" \ No newline at end of file diff --git a/deploy/openshift/openwebui/route.yaml b/deploy/openshift/openwebui/route.yaml new file mode 100644 index 00000000..c31d6fee --- /dev/null +++ b/deploy/openshift/openwebui/route.yaml @@ -0,0 +1,22 @@ +apiVersion: route.openshift.io/v1 +kind: Route +metadata: + name: openwebui + namespace: vllm-semantic-router-system + labels: + app: openwebui + component: frontend + annotations: + haproxy.router.openshift.io/timeout: "300s" +spec: + host: openwebui-vllm-semantic-router-system.apps.cluster-pbd96.pbd96.sandbox5333.opentlc.com + to: + kind: Service + name: openwebui + weight: 100 + port: + targetPort: http + tls: + termination: edge + insecureEdgeTerminationPolicy: Redirect + wildcardPolicy: None \ No newline at end of file diff --git a/deploy/openshift/openwebui/service.yaml b/deploy/openshift/openwebui/service.yaml new file mode 100644 index 00000000..e49e891c --- /dev/null +++ b/deploy/openshift/openwebui/service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: openwebui + namespace: vllm-semantic-router-system + labels: + app: openwebui + component: frontend +spec: + type: ClusterIP + ports: + - port: 3000 + targetPort: 8080 + protocol: TCP + name: http + selector: + app: openwebui \ No newline at end of file diff --git a/deploy/openshift/openwebui/uninstall-openwebui.sh b/deploy/openshift/openwebui/uninstall-openwebui.sh new file mode 100755 index 00000000..50b758ad --- /dev/null +++ b/deploy/openshift/openwebui/uninstall-openwebui.sh @@ -0,0 +1,159 @@ +#!/bin/bash + +# Uninstall OpenWebUI from OpenShift +# This script safely removes OpenWebUI without affecting the semantic-router deployment + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Script directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +echo -e "${BLUE}🗑️ OpenWebUI OpenShift Uninstall Script${NC}" +echo "================================================" + +# Check if oc is installed and logged in +if ! command -v oc &> /dev/null; then + echo -e "${RED}❌ Error: 'oc' command not found. Please install OpenShift CLI.${NC}" + exit 1 +fi + +if ! oc whoami &> /dev/null; then + echo -e "${RED}❌ Error: Not logged into OpenShift. Please run 'oc login'.${NC}" + exit 1 +fi + +# Get current user +CURRENT_USER=$(oc whoami) +echo -e "${GREEN}✅ Logged in as: ${CURRENT_USER}${NC}" + +# Check if namespace exists +NAMESPACE="vllm-semantic-router-system" +if ! oc get namespace "$NAMESPACE" &> /dev/null; then + echo -e "${YELLOW}⚠️ Namespace '$NAMESPACE' not found. Nothing to uninstall.${NC}" + exit 0 +fi + +# Switch to the namespace +echo -e "${YELLOW}📁 Switching to namespace: ${NAMESPACE}${NC}" +oc project "$NAMESPACE" + +# Check if OpenWebUI is deployed +if ! oc get deployment openwebui &> /dev/null; then + echo -e "${YELLOW}⚠️ OpenWebUI deployment not found. Nothing to uninstall.${NC}" + exit 0 +fi + +# Confirmation prompt +echo -e "${YELLOW}⚠️ This will remove OpenWebUI and ALL its data from OpenShift.${NC}" +echo -e "${YELLOW} This includes persistent data, conversations, and configurations.${NC}" +echo -e "${YELLOW} The semantic-router deployment will NOT be affected.${NC}" +echo "" +read -p "Are you sure you want to continue? (y/N): " -n 1 -r +echo +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo -e "${BLUE}❌ Uninstall cancelled.${NC}" + exit 0 +fi + +# Store the user confirmation for later use +USER_CONFIRMED="yes" + +echo "" +echo -e "${YELLOW}🗑️ Starting OpenWebUI uninstall...${NC}" + +# Remove OpenWebUI components in reverse order +echo " 🔗 Removing Route..." +if oc get route openwebui &> /dev/null; then + oc delete route openwebui + echo -e "${GREEN} ✅ Route removed${NC}" +else + echo -e "${YELLOW} ⚠️ Route not found${NC}" +fi + +echo " 🌐 Removing Service..." +if oc get service openwebui &> /dev/null; then + oc delete service openwebui + echo -e "${GREEN} ✅ Service removed${NC}" +else + echo -e "${YELLOW} ⚠️ Service not found${NC}" +fi + +echo " 🚀 Removing Deployment..." +if oc get deployment openwebui &> /dev/null; then + oc delete deployment openwebui + echo -e "${GREEN} ✅ Deployment removed${NC}" + + # Wait for pods to be terminated + echo " ⏳ Waiting for pods to terminate..." + oc wait --for=delete pod -l app=openwebui --timeout=60s 2>/dev/null || true + echo -e "${GREEN} ✅ Pods terminated${NC}" +else + echo -e "${YELLOW} ⚠️ Deployment not found${NC}" +fi + +# Remove PVC automatically since user confirmed complete uninstall +echo "" +echo -e "${YELLOW}📦 Removing Persistent Volume Claim and all data...${NC}" +if oc get pvc openwebui-data &> /dev/null; then + echo " 📦 Removing Persistent Volume Claim..." + oc delete pvc openwebui-data + echo -e "${GREEN} ✅ PVC and all data removed${NC}" +else + echo -e "${YELLOW} ⚠️ PVC not found${NC}" +fi + +# Check remaining OpenWebUI resources +echo "" +echo -e "${YELLOW}🔍 Checking for remaining OpenWebUI resources...${NC}" + +# Check for any resources with openwebui labels +REMAINING_RESOURCES=$(oc get all,pvc,configmap,secret -l app=openwebui -o name 2>/dev/null || true) +if [ -n "$REMAINING_RESOURCES" ]; then + echo -e "${YELLOW}⚠️ Found remaining resources:${NC}" + echo "$REMAINING_RESOURCES" + echo "" + read -p "Remove these resources too? (y/N): " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + echo "$REMAINING_RESOURCES" | xargs oc delete 2>/dev/null || true + echo -e "${GREEN}✅ Additional resources removed${NC}" + fi +else + echo -e "${GREEN}✅ No remaining OpenWebUI resources found${NC}" +fi + +# Verify semantic-router is still running +echo "" +echo -e "${YELLOW}🔍 Verifying semantic-router is still running...${NC}" +if oc get deployment semantic-router &> /dev/null; then + READY_REPLICAS=$(oc get deployment semantic-router -o jsonpath='{.status.readyReplicas}' 2>/dev/null || echo "0") + if [ "$READY_REPLICAS" = "1" ]; then + echo -e "${GREEN}✅ semantic-router is still running normally${NC}" + else + echo -e "${YELLOW}⚠️ semantic-router may need time to stabilize${NC}" + fi +else + echo -e "${YELLOW}⚠️ semantic-router deployment not found${NC}" +fi + +# Final summary +echo "" +echo -e "${GREEN}🎉 OpenWebUI uninstall completed!${NC}" +echo "================================================" +echo -e "${BLUE}📊 Uninstall Summary:${NC}" +echo " ✅ OpenWebUI deployment removed" +echo " ✅ Service and route removed" +echo " ✅ Data PVC and all user data removed" +echo " ✅ semantic-router deployment unaffected" +echo "" +echo -e "${BLUE}💡 To redeploy OpenWebUI:${NC}" +echo " ./deploy-openwebui-on-openshift.sh" +echo "" +echo -e "${GREEN}✨ Cleanup completed successfully!${NC}" \ No newline at end of file