refactor: simplify k8s-webui to single deployment file

master
arcbjorn 3 days ago
parent a6ef511ca6
commit efcaf45384

@ -1,162 +0,0 @@
# K8s WebUI Deployment
This directory contains Kubernetes manifests for deploying the K8s WebUI application to manage the base-infrastructure services.
## Overview
The K8s WebUI provides a web interface for monitoring and managing the Kubernetes infrastructure services including:
- **Gitea** - Git repository hosting
- **Umami** - Web analytics platform
- **Memos** - Notes and memos service
- **Uptime Kuma** - Uptime monitoring dashboard
- **FileBrowser** - File browser and management
- **Dozzle** - Container logs viewer
- **PostgreSQL** - Database server
## Architecture
The application consists of:
- **Frontend**: Svelte 5 application serving the web interface
- **Backend**: Bun.js API server with Kubernetes client integration
- **PostgreSQL**: Database for audit logs and application data
- **Redis**: Session storage and caching
## Deployment
### Prerequisites
1. Kubernetes cluster with nginx-ingress controller
2. cert-manager for SSL certificates (optional)
3. DNS configured for `k8s.arcbjorn.com`
### Build Images
First, build the Docker images for the frontend and backend:
```bash
# From the hellir/k8s-webui directory
cd ../../hellir/k8s-webui
# Build backend image
docker build -t k8s-webui-backend:latest ./backend
# Build frontend image
docker build -t k8s-webui-frontend:latest ./frontend
# Tag and push to your registry if needed
# docker tag k8s-webui-backend:latest your-registry/k8s-webui-backend:latest
# docker push your-registry/k8s-webui-backend:latest
```
### Deploy to Kubernetes
Apply the manifests in order:
```bash
# Create namespace and RBAC
kubectl apply -f namespace.yaml
kubectl apply -f rbac.yaml
# Create secrets and config
kubectl apply -f secrets.yaml
# Deploy database and cache
kubectl apply -f postgres.yaml
kubectl apply -f redis.yaml
# Wait for database to be ready
kubectl wait --for=condition=ready pod -l app=k8s-webui-postgres -n k8s-webui --timeout=120s
# Deploy application
kubectl apply -f backend-deployment.yaml
kubectl apply -f frontend-deployment.yaml
# Create ingress
kubectl apply -f ingress.yaml
```
### Verify Deployment
Check that all pods are running:
```bash
kubectl get pods -n k8s-webui
kubectl get services -n k8s-webui
kubectl get ingress -n k8s-webui
```
## Access
Once deployed, the application will be available at:
- **URL**: https://k8s.arcbjorn.com
- **Credentials**: admin / admin (change in production)
## Configuration
### Environment Variables
Key configuration is managed through:
- `k8s-webui-config` ConfigMap
- `k8s-webui-secrets` Secret
### RBAC Permissions
The application uses a ServiceAccount with minimal required permissions:
- Read access to pods, services, deployments, statefulsets, ingresses
- Read access to configmaps and secrets (data redacted)
- Limited write access for scaling deployments
- Access to pod logs
### Security Notes
- Generate a secure 32-byte PASETO key for production (run `bun run generate-key` in backend/)
- Update database passwords
- Review RBAC permissions based on your security requirements
- Consider using external secrets management
- PASETO tokens provide better security than JWT (immune to algorithm confusion attacks)
## Monitoring
The application provides:
- Real-time WebSocket updates for cluster status
- Health check endpoints at `/health`
- Prometheus-compatible metrics (if metrics-server is installed)
## Troubleshooting
### Common Issues
1. **Images not found**: Ensure Docker images are built and available in your cluster
2. **RBAC errors**: Verify ServiceAccount and ClusterRole are applied
3. **Database connection**: Check postgres StatefulSet is running and ready
4. **Ingress not working**: Verify nginx-ingress controller and DNS configuration
### Logs
```bash
# Backend logs
kubectl logs -f deployment/k8s-webui-backend -n k8s-webui
# Frontend logs
kubectl logs -f deployment/k8s-webui-frontend -n k8s-webui
# Database logs
kubectl logs -f statefulset/k8s-webui-postgres -n k8s-webui
```
## Development
For local development, use the docker-compose setup in the hellir/k8s-webui directory:
```bash
cd ../../hellir/k8s-webui
docker-compose up -d
```
This will start the application with hot reload and development databases.

@ -1,86 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: k8s-webui-backend
namespace: k8s-webui
spec:
replicas: 1
selector:
matchLabels:
app: k8s-webui-backend
template:
metadata:
labels:
app: k8s-webui-backend
spec:
serviceAccountName: k8s-webui
containers:
- name: backend
image: k8s-webui-backend:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 3001
name: http
env:
- name: PORT
value: "3001"
- name: NODE_ENV
valueFrom:
configMapKeyRef:
name: k8s-webui-config
key: NODE_ENV
- name: K8S_NAMESPACE
valueFrom:
configMapKeyRef:
name: k8s-webui-config
key: K8S_NAMESPACE
- name: REDIS_URL
valueFrom:
configMapKeyRef:
name: k8s-webui-config
key: REDIS_URL
- name: DATABASE_URL
valueFrom:
configMapKeyRef:
name: k8s-webui-config
key: DATABASE_URL
- name: PASETO_SECRET_KEY
valueFrom:
secretKeyRef:
name: k8s-webui-secrets
key: PASETO_SECRET_KEY
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
name: k8s-webui-backend
namespace: k8s-webui
spec:
selector:
app: k8s-webui-backend
ports:
- port: 3001
targetPort: http
name: http
type: ClusterIP

@ -1,105 +0,0 @@
#!/bin/bash
# K8s WebUI Deployment Script
# This script deploys the K8s WebUI to the Kubernetes cluster
set -e
echo "🚀 Deploying K8s WebUI to Kubernetes cluster..."
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Function to print colored output
print_status() {
echo -e "${GREEN}[INFO]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Check if kubectl is available
if ! command -v kubectl &> /dev/null; then
print_error "kubectl is not installed or not in PATH"
exit 1
fi
# Check if we can connect to the cluster
if ! kubectl cluster-info &> /dev/null; then
print_error "Cannot connect to Kubernetes cluster. Check your kubeconfig."
exit 1
fi
print_status "Connected to Kubernetes cluster"
# Apply namespace and RBAC first
print_status "Creating namespace and RBAC..."
kubectl apply -f namespace.yaml
kubectl apply -f rbac.yaml
# Apply secrets and configuration
print_status "Creating secrets and configuration..."
kubectl apply -f secrets.yaml
# Deploy database and cache
print_status "Deploying PostgreSQL database..."
kubectl apply -f postgres.yaml
print_status "Deploying Redis cache..."
kubectl apply -f redis.yaml
# Wait for PostgreSQL to be ready
print_status "Waiting for PostgreSQL to be ready..."
kubectl wait --for=condition=ready pod -l app=k8s-webui-postgres -n k8s-webui --timeout=300s
print_status "Waiting for Redis to be ready..."
kubectl wait --for=condition=ready pod -l app=k8s-webui-redis -n k8s-webui --timeout=180s
# Deploy application services
print_status "Deploying backend API..."
kubectl apply -f backend-deployment.yaml
print_status "Deploying frontend..."
kubectl apply -f frontend-deployment.yaml
# Wait for deployments to be ready
print_status "Waiting for backend to be ready..."
kubectl wait --for=condition=available deployment/k8s-webui-backend -n k8s-webui --timeout=300s
print_status "Waiting for frontend to be ready..."
kubectl wait --for=condition=available deployment/k8s-webui-frontend -n k8s-webui --timeout=300s
# Apply ingress
print_status "Creating ingress..."
kubectl apply -f ingress.yaml
# Show deployment status
echo ""
print_status "Deployment completed successfully! 🎉"
echo ""
echo "📊 Deployment Status:"
kubectl get pods -n k8s-webui
echo ""
kubectl get services -n k8s-webui
echo ""
kubectl get ingress -n k8s-webui
echo ""
print_status "K8s WebUI should be available at: https://k8s.arcbjorn.com"
print_warning "Default credentials: admin / admin (please change in production)"
echo ""
echo "🔍 To check logs:"
echo " Backend: kubectl logs -f deployment/k8s-webui-backend -n k8s-webui"
echo " Frontend: kubectl logs -f deployment/k8s-webui-frontend -n k8s-webui"
echo ""
echo "🗑️ To remove:"
echo " kubectl delete namespace k8s-webui"

@ -1,62 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: k8s-webui-frontend
namespace: k8s-webui
spec:
replicas: 1
selector:
matchLabels:
app: k8s-webui-frontend
template:
metadata:
labels:
app: k8s-webui-frontend
spec:
containers:
- name: frontend
image: k8s-webui-frontend:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 3000
name: http
env:
- name: NODE_ENV
value: "production"
- name: BACKEND_URL
value: "http://k8s-webui-backend:3001"
livenessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "200m"
restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
name: k8s-webui-frontend
namespace: k8s-webui
spec:
selector:
app: k8s-webui-frontend
ports:
- port: 3000
targetPort: http
name: http
type: ClusterIP

@ -1,45 +0,0 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: k8s-webui-ingress
namespace: k8s-webui
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/ssl-redirect: "true"
cert-manager.io/cluster-issuer: "letsencrypt-prod" # If using cert-manager
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/websocket-services: "k8s-webui-backend"
spec:
tls:
- hosts:
- k8s.arcbjorn.com
secretName: k8s-webui-tls
rules:
- host: k8s.arcbjorn.com
http:
paths:
# API routes go to backend
- path: /api
pathType: Prefix
backend:
service:
name: k8s-webui-backend
port:
number: 3001
# WebSocket routes go to backend
- path: /ws
pathType: Prefix
backend:
service:
name: k8s-webui-backend
port:
number: 3001
# Everything else goes to frontend
- path: /
pathType: Prefix
backend:
service:
name: k8s-webui-frontend
port:
number: 3000

@ -0,0 +1,154 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: k8s-webui-backend
namespace: base-infrastructure
spec:
replicas: 1
selector:
matchLabels:
app: k8s-webui-backend
template:
metadata:
labels:
app: k8s-webui-backend
spec:
serviceAccountName: k8s-webui
containers:
- name: backend
image: git.arcbjorn.com/archellir/k8s-webui-backend:latest
ports:
- containerPort: 3001
env:
- name: PORT
value: "3001"
- name: NODE_ENV
value: "production"
- name: K8S_NAMESPACE
value: "base-infrastructure"
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: app-secrets
key: K8S_WEBUI_DATABASE_URL
- name: PASETO_SECRET_KEY
valueFrom:
secretKeyRef:
name: app-secrets
key: K8S_WEBUI_PASETO_KEY
livenessProbe:
httpGet:
path: /health
port: 3001
initialDelaySeconds: 30
periodSeconds: 10
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: k8s-webui-frontend
namespace: base-infrastructure
spec:
replicas: 1
selector:
matchLabels:
app: k8s-webui-frontend
template:
metadata:
labels:
app: k8s-webui-frontend
spec:
containers:
- name: frontend
image: git.arcbjorn.com/archellir/k8s-webui-frontend:latest
ports:
- containerPort: 3000
env:
- name: BACKEND_URL
value: "http://k8s-webui-backend:3001"
livenessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "200m"
---
apiVersion: v1
kind: Service
metadata:
name: k8s-webui-backend
namespace: base-infrastructure
spec:
selector:
app: k8s-webui-backend
ports:
- port: 3001
targetPort: 3001
type: ClusterIP
---
apiVersion: v1
kind: Service
metadata:
name: k8s-webui-frontend
namespace: base-infrastructure
spec:
selector:
app: k8s-webui-frontend
ports:
- port: 3000
targetPort: 3000
type: ClusterIP
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: k8s-webui
rules:
- apiGroups: [""]
resources: ["pods", "services", "configmaps", "secrets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets"]
verbs: ["get", "list", "watch", "patch"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: k8s-webui
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: k8s-webui
subjects:
- kind: ServiceAccount
name: k8s-webui
namespace: base-infrastructure
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: k8s-webui
namespace: base-infrastructure

@ -1,6 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: k8s-webui
labels:
name: k8s-webui

@ -1,89 +0,0 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: k8s-webui-postgres
namespace: k8s-webui
spec:
serviceName: k8s-webui-postgres-headless
replicas: 1
selector:
matchLabels:
app: k8s-webui-postgres
template:
metadata:
labels:
app: k8s-webui-postgres
spec:
containers:
- name: postgres
image: postgres:15
ports:
- containerPort: 5432
name: postgres
env:
- name: POSTGRES_DB
value: k8s_webui
- name: POSTGRES_USER
value: k8s_user
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: k8s-webui-secrets
key: POSTGRES_PASSWORD
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
livenessProbe:
exec:
command:
- /bin/sh
- -c
- pg_isready -U k8s_user -d k8s_webui
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command:
- /bin/sh
- -c
- pg_isready -U k8s_user -d k8s_webui
initialDelaySeconds: 5
periodSeconds: 5
volumeClaimTemplates:
- metadata:
name: postgres-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 5Gi
---
apiVersion: v1
kind: Service
metadata:
name: k8s-webui-postgres
namespace: k8s-webui
spec:
selector:
app: k8s-webui-postgres
ports:
- port: 5432
targetPort: postgres
name: postgres
type: ClusterIP
---
apiVersion: v1
kind: Service
metadata:
name: k8s-webui-postgres-headless
namespace: k8s-webui
spec:
selector:
app: k8s-webui-postgres
ports:
- port: 5432
targetPort: postgres
name: postgres
clusterIP: None

@ -1,48 +0,0 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: k8s-webui
namespace: k8s-webui
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: k8s-webui-reader
rules:
# Allow reading most resources in base-infrastructure namespace
- apiGroups: [""]
resources: ["pods", "services", "configmaps", "secrets", "namespaces"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets", "replicasets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get", "list", "watch"]
# Allow limited write operations for scaling
- apiGroups: ["apps"]
resources: ["deployments/scale", "statefulsets/scale"]
verbs: ["get", "patch"]
# Allow reading pod logs
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
# Allow reading metrics if metrics-server is available
- apiGroups: ["metrics.k8s.io"]
resources: ["nodes", "pods"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: k8s-webui-reader
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: k8s-webui-reader
subjects:
- kind: ServiceAccount
name: k8s-webui
namespace: k8s-webui

@ -1,70 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: k8s-webui-redis
namespace: k8s-webui
spec:
replicas: 1
selector:
matchLabels:
app: k8s-webui-redis
template:
metadata:
labels:
app: k8s-webui-redis
spec:
containers:
- name: redis
image: redis:7-alpine
ports:
- containerPort: 6379
name: redis
volumeMounts:
- name: redis-data
mountPath: /data
livenessProbe:
exec:
command:
- redis-cli
- ping
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command:
- redis-cli
- ping
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: redis-data
persistentVolumeClaim:
claimName: k8s-webui-redis-data
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: k8s-webui-redis-data
namespace: k8s-webui
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: Service
metadata:
name: k8s-webui-redis
namespace: k8s-webui
spec:
selector:
app: k8s-webui-redis
ports:
- port: 6379
targetPort: redis
name: redis
type: ClusterIP

@ -1,21 +0,0 @@
apiVersion: v1
kind: Secret
metadata:
name: k8s-webui-secrets
namespace: k8s-webui
type: Opaque
stringData:
PASETO_SECRET_KEY: "change-this-32-byte-secret-in-production-please-use-random-key"
POSTGRES_PASSWORD: "k8s_webui_password"
REDIS_PASSWORD: "" # Redis without password for simplicity
---
apiVersion: v1
kind: ConfigMap
metadata:
name: k8s-webui-config
namespace: k8s-webui
data:
NODE_ENV: "production"
K8S_NAMESPACE: "base-infrastructure"
REDIS_URL: "redis://k8s-webui-redis:6379"
DATABASE_URL: "postgres://k8s_user:k8s_webui_password@k8s-webui-postgres:5432/k8s_webui"
Loading…
Cancel
Save