This repository contains the operational setup for the SMS Spam Detection system, covering assignments A1 through A4 with complete deployment instructions.
- app - Spring Boot frontend and API gateway
- model-service - Flask ML backend with scikit-learn
- lib-version - Version-aware Maven library
- operation - Docker Compose orchestration, Kubernetes Provisioning, Monitoring, Service Mesh (this repository)
- Git: Version control system
# Ubuntu/Debian
sudo apt update && sudo apt install git
# macOS
brew install git- Docker & Docker Compose: Container runtime
# Ubuntu/Debian
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER
sudo apt install docker-compose-plugin
# Restart session or run: newgrp docker- Java 25 & Maven: For building Java applications
sudo apt install openjdk-25-jdk maven- Python 3: For machine learning service
sudo apt install python3 python3-pip python3-flask python3-scikit-learn python3-joblib- Vagrant & VirtualBox: For Kubernetes cluster provisioning (A2)
sudo apt install virtualbox vagrant- kubectl & Helm: Kubernetes tools (A2-A4)
# kubectl
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl && sudo mv kubectl /usr/local/bin/
# Helm
curl https://get.helm.sh/helm-v3.14.0-linux-amd64.tar.gz -o helm.tar.gz
tar -zxvf helm.tar.gz && sudo mv linux-amd64/helm /usr/local/bin/Set these environment variables for GitHub Package Registry access:
export GITHUB_USERNAME="your-github-username"
export GITHUB_TOKEN="your-github-personal-access-token"git clone https://github.com/doda25-team6/app.git
git clone https://github.com/doda25-team6/model-service.git
git clone https://github.com/doda25-team6/lib-version.git
git clone https://github.com/doda25-team6/operation.git
cd operation
git checkout a1
docker compose up --buildAccess the application at http://localhost:8080/sms/
cd operation
vagrant up
cd ansible && ansible-playbook -u vagrant -i 192.168.56.100, finalization.yml
# Enable Istio sidecar injection for the namespace
kubectl label ns default istio-injection=enabled
# Deploy the application with Prometheus
cd /vagrant/charts/project-app
helm install project .This will:
- Create 3 VMs (1 controller + 2 workers)
- Install Kubernetes v1.32.4
- Configure networking (Flannel CNI)
- Install cluster services (MetalLB, Nginx Ingress, Dashboard, Istio)
Access the cluster:
# From host machine
kubectl --kubeconfig=admin.conf get nodes
# Or SSH into controller
vagrant ssh ctrl
kubectl get nodesDestroy cluster:
vagrant destroy -fDefaults (can be overridden):
- Gateway name:
istio.gateway.name(default:istio-gateway) - IngressGateway selector labels:
istio.gateway.selector(default:{ istio: ingressgateway })
- The chart implements “assign once, then pin”:
- First request gets assigned via 90/10 routing to v1/v2.
- The response sets a cookie (default
exp_canary=v1|v2). - Reloads send the cookie, and routing becomes sticky per user.
First request (observe assignment via Set-Cookie):
curl -i http://192.168.56.91/Sticky follow-up:
curl -i -c user.cookies http://192.168.56.91/
curl -i -b user.cookies http://192.168.56.91/Force a version with a cookie:
curl -i -H "Cookie: exp_canary=v1" http://192.168.56.91/
curl -i -H "Cookie: exp_canary=v2" http://192.168.56.91/This section explains the configuration, testing, and verification steps for the rate limiting implementation.
Rate limiting policies are defined in the values.yaml file.
- Domain:
ratelimit - Unit:
minute - SMS Bucket (
/sms*): Limited to 6 requests per minute. - Default Bucket (Fallback): Limited to 50 requests per minute for all other paths.
# Global rate limiting configuration
rateLimit:
unit: minute
limits:
sms:
requestsPerUnit: 20
default:
requestsPerUnit: 60You can verify the rate limits by running the following curl loops.
Expected Behavior: The first 20 requests return 200, and subsequent requests return 429.
for i in $(seq 1 40); do
code=$(curl -s -o /dev/null -w "%{http_code}" "http://192.168.56.91/sms/")
echo "call $i: HTTP $code"
done
Expected Behavior: The first 60 requests return 200, and subsequent requests return 429.
for i in $(seq 1 70); do
code=$(curl -s -o /dev/null -w "%{http_code}" "http://192.168.56.91/")
echo "call $i: HTTP $code"
done
To inspect the actual counters in Redis, access the cluster via the VM and execute the Redis CLI commands.
Find the name of the rate limit Redis pod:
kubectl get pods -n default | grep redis
Open a shell inside the Redis container:
kubectl exec -it -n default {step1_podname} -- redis-cli
Once inside the Redis CLI prompt, you can list keys and check specific counters:
# List all keys to find the rate limit key
keys *
# Get the value of a specific key
get <keyname>
This assignment demonstrates containerization of the SMS Spam Detection system using Docker Compose.
# Set environment variables for GitHub access
export GITHUB_USERNAME="your-username"
export GITHUB_TOKEN="your-token"
# Build the shared library
cd lib-version
mvn clean compile
mvn test
cd ..# Navigate to operation directory
cd operation
# Build and start all services
docker compose up --build# Check running containers
docker compose ps
# View service logs
docker compose logs -f app
docker compose logs -f model-service
# Test the API
curl -X POST -H "Content-Type: application/json" \
-d '{"sms": "Hello world"}' \
http://localhost:8080/sms/- Web Interface: http://localhost:8080/sms/
- API Endpoint: POST to http://localhost:8080/sms/ with JSON body
{"sms": "your message"}
docker compose down| Service | IP Address | Purpose |
|---|---|---|
| Controller | 192.168.56.100 | Kubernetes API server |
| Worker 1 | 192.168.56.101 | Worker node |
| Worker 2 | 192.168.56.102 | Worker node |
| Nginx Ingress | 192.168.56.90 | HTTP/HTTPS traffic |
| Istio Gateway | 192.168.56.91 | Service mesh traffic |
| MetalLB Pool | 192.168.56.90-99 | LoadBalancer IPs |
- Kubernetes: v1.32.4 (kubeadm, kubelet, kubectl)
- Container Runtime: Containerd 1.7.28
- CNI: Flannel v0.26.7
- Load Balancer: MetalLB v0.15.2
- Ingress: Nginx Ingress Controller
- Dashboard: Kubernetes Dashboard
- Service Mesh: Istio 1.25.2
- Package Manager: Helm 3.x
- Storage: NFS server on controller
# Navigate to operation directory
cd operation
# Start Kubernetes cluster (takes 10-15 minutes)
vagrant upThis creates:
- 1 control plane node (192.168.56.100)
- 2 worker nodes (192.168.56.101, 192.168.56.102)
# Run finalization playbook
cd ansible
ansible-playbook -u vagrant -i 192.168.56.100, finalization.yml
cd ..# Set kubectl context
export KUBECONFIG=$(pwd)/admin.conf
# Verify cluster
kubectl get nodes
kubectl get pods -AAfter vagrant up completes, verify all services:
vagrant ssh ctrl
# All nodes should be Ready
kubectl get nodes# Check cluster components
kubectl get pods -n metallb-system # Load balancer
kubectl get pods -n ingress-nginx # Ingress controller
kubectl get pods -n istio-system # Service mesh
istioctl version
kubectl get pods -n kubernetes-dashboard # Dashboard
kubectl get svc --all-namespaces | grep LoadBalancer # Check Loadbalancer servicesOption 1: Port forwarding
From ctrl:
kubectl port-forward -n kubernetes-dashboard svc/kubernetes-dashboard-kong-proxy 8443:443 --address 0.0.0.0Then access: https://192.168.56.100:8443
Option 2: Via hostname
Add to /etc/hosts:
192.168.56.90 dashboard.local
Access: http://dashboard.local
Get login token:
vagrant ssh ctrl
kubectl -n kubernetes-dashboard create token admin-userThis assignment deploys monitoring and operational tools using Helm charts.
# Enable sidecar injection for default namespace
kubectl label ns default istio-injection=enabled# Deploy using Helm chart
cd /vagrant/charts/project-app
helm install project .# Get service IPs
kubectl get svc -A
# Access URLs (replace with actual IPs from above):
# Grafana: http://192.168.56.90/grafana/
# Prometheus: http://192.168.56.90/prometheus/
# Kubernetes Dashboard: http://192.168.56.90/kubernetes-dashboard/From ctrl:
kubectl port-forward --address 0.0.0.0 svc/project-project-app-prometheus 9090:9090Then access: http://192.168.56.100:9090
Option 2: Via hostname
Add to /etc/hosts:
192.168.56.90 prometheus.local
Access: http://prometheus.local
From ctrl:
kubectl port-forward --address 0.0.0.0 svc/project-project-app-alertmanager 9093:9093Then access: http://192.168.56.100:9093
Option 2: Via hostname
Add to /etc/hosts:
192.168.56.90 alertmanager.local
Access: http://alertmanager.local
AlertManager displays Prometheus alerts. To test, generate high traffic and alerts will appear when request rate exceeds 15/min for 1 minute.
Generate Test Traffic:
# Sends 90 requests at ~60/min (triggers alert)
for i in {1..90}; do curl -s "http://192.168.56.91/" > /dev/null; echo "Request $i sent"; sleep 1.0; doneTo configure email alerts:
- Get Gmail App Password: https://myaccount.google.com/apppasswords (requires 2FA)
- Create Secret:
kubectl create secret generic alertmanager-smtp --from-literal=smtp-password='YOUR_APP_PASSWORD' - Deploy:
helm upgrade project . --set alertmanager.email.to=your@gmail.com --set alertmanager.email.from=your@gmail.com
Add to /etc/hosts:
192.168.56.91 project.local
Access: http://project.local http://project.local/metrics
The application includes Prometheus monitoring with three types of metrics:
- Counter: click_rate_total, navigation_requests_total{page}
- Gauge: time_on_site_seconds
- Histogram: page_load_seconds{page}
Grafana is automatically deployed with the application for metrics visualization.
Access via port-forward:
kubectl port-forward --address 0.0.0.0 svc/project-project-app-grafana 3000:3000Then access: http://192.168.56.100:3000
- Username:
admin - Password:
admin
Option 2: Via hostname
Add to /etc/hosts:
192.168.56.90 grafana.local
Access: http://grafana.local
Dashboard Location:
- Navigate to Dashboards → "Application Metrics"
- Dashboard is automatically provisioned on deployment
Dashboard Panels (using Prometheus metric names):
- click_rate_total - Time Series showing request rate (using
rate()function) - time_on_site_seconds - Gauge with color thresholds (red <10s, yellow 10-30s, green >30s)
- navigation_requests_total - Histogram showing page visit distribution
- page_load_seconds (P95) - Time Series showing 95th percentile load time (using
histogram_quantile()) - click_rate_total (Total) - Stat panel showing total prediction count
- page_load_seconds (Statistics) - Table with avg, P50, P95, P99 by page
Advanced Features:
- Uses PromQL functions:
rate(),histogram_quantile(),sum by() - Aggregates metrics across pods where appropriate
- Interactive timeframe selector (5m to 30d)
- Auto-refresh intervals (5s to 5m)
Manual Dashboard Import (Optional):
If the dashboard isn't auto-loaded:
- Copy JSON from
charts/project-app/dashboards/application-metrics.json - In Grafana: Dashboards → Import → Paste JSON
- Select "Prometheus" as datasource
- Click Import
Experimentation Dashboard (A4):
- Name: "Continuous Experimentation Dashboard"
- Focus: Comparing v1 (Stable) vs v2 (Canary) performance
Dashboard Location:
- Navigate to Dashboards → "Continuous Experimentation Dashboard"
- Dashboard is automatically provisioned on deployment
Dashboard Panels:
- Request Rate Comparison - Traffic volume split between versions
- Response Time Comparison (P95) - Gauge showing if v2 is slower/faster
- Average Prediction Confidence - Gauge tracking model certainty (Green > 80%)
- Low Confidence Predictions - Rate of predictions < 70% confidence
- User Engagement - Time on site comparison (sticky session duration)
Manual Dashboard Import (Optional):
- Copy JSON from
charts/project-app/dashboards/experimentation-dashboard.json - Follow the same import steps as above.
kubectl get podsThis assignment demonstrates Istio service mesh capabilities including traffic management and observability.
# Enable Istio sidecar injection for the namespace
kubectl label ns default istio-injection=enabled
# Deploy the application with Prometheus
cd /vagrant/charts/project-app
helm install project .
# Note: This Helm chart deploys the application, model service, Prometheus, Grafana, and associated Istio resources (Gateway, VirtualServices, DestinationRules) for traffic management.# Check Istio components
kubectl get pods -n istio-system
# Verify sidecar injection
kubectl get pods -o jsonpath='{.items[*].spec.containers[*].name}'# Check virtual services
kubectl get virtualservice -n default
# Check destination rules
kubectl get destinationrule -n default
# Check gateway
kubectl get gateway -n default- Istio Gateway: http://192.168.56.91/sms/
# Access Istio Gateway directly
kubectl port-forward --address 0.0.0.0 svc/istio-gateway 8080:80 -n istio-system
# Then access: http://localhost:8080/sms/- Permission denied: Add user to docker group:
sudo usermod -aG docker $USER - Port already in use: Change ports in
docker-compose.yml - Build fails: Ensure Docker daemon is running
- VT-x not available: Enable virtualization in BIOS
- Vagrant fails: Run
vagrant destroy -fthenvagrant up - Ansible fails: Check SSH connectivity to VMs
- Pods not starting:
kubectl describe pod <pod-name> -n <namespace>
kubectl logs <pod-name> -n <namespace>- MetalLB not assigning IPs:
kubectl describe ipaddresspool -n metallb-system
kubectl logs -n metallb-system -l app=metallb- Istio issues:
istioctl analyze
kubectl get pods -n istio-system- GitHub authentication: Set
GITHUB_USERNAMEandGITHUB_TOKEN - Out of disk space: Clean up with
docker system prune -a - Network issues: Check firewall settings
Change worker count:
WORKER_COUNT=3 vagrant upAdjust resources:
CTRL_CPUS=4 CTRL_MEMORY=8192 vagrant up# Docker cleanup
docker compose down --remove-orphans
docker system prune -a
# Kubernetes cleanup
kubectl delete ns project
helm uninstall project
# Vagrant cleanup
vagrant destroy -f