feat: Add complete Redis cache example with multi-container pod and web interface
This commit is contained in:
@@ -27,8 +27,6 @@ Comprehensive progression of Kubernetes examples designed to teach MyceliumCloud
|
||||
- **Template**: Same efficient pattern with `python-flask.md` + `python-flask-deployment.yaml` + `python-flask-service.yaml` + `app.py`
|
||||
- **Key Concepts**: Python containers, API development, HTTP services, JSON responses, RESTful design
|
||||
|
||||
### Planned Examples (In Progress)
|
||||
|
||||
### Planned Examples (Future)
|
||||
|
||||
#### 4. Redis Cache 🔄
|
||||
|
||||
57
examples/redis-cache/redis-cache-deployment.yaml
Normal file
57
examples/redis-cache/redis-cache-deployment.yaml
Normal file
@@ -0,0 +1,57 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: redis-cache
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: redis-cache
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: redis-cache
|
||||
spec:
|
||||
containers:
|
||||
- name: redis-cache
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- containerPort: 6379
|
||||
name: redis
|
||||
command: ["redis-server"]
|
||||
args: ["--bind", "0.0.0.0", "--protected-mode", "no", "--maxmemory", "64mb", "--maxmemory-policy", "allkeys-lru"]
|
||||
env:
|
||||
- name: REDIS_PASSWORD
|
||||
value: ""
|
||||
resources:
|
||||
requests:
|
||||
memory: "32Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "64Mi"
|
||||
cpu: "200m"
|
||||
|
||||
- name: redis-web-interface
|
||||
image: python:3.11-alpine
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: web
|
||||
command: ["/bin/sh", "-c"]
|
||||
args:
|
||||
- |
|
||||
pip install flask redis &&
|
||||
python /app/web-interface.py
|
||||
volumeMounts:
|
||||
- name: web-interface
|
||||
mountPath: /app
|
||||
resources:
|
||||
requests:
|
||||
memory: "16Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "32Mi"
|
||||
cpu: "100m"
|
||||
volumes:
|
||||
- name: web-interface
|
||||
configMap:
|
||||
name: redis-web-interface
|
||||
15
examples/redis-cache/redis-cache-service.yaml
Normal file
15
examples/redis-cache/redis-cache-service.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: redis-cache-service
|
||||
spec:
|
||||
selector:
|
||||
app: redis-cache
|
||||
ports:
|
||||
- name: redis
|
||||
port: 6379
|
||||
targetPort: 6379
|
||||
- name: web
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
type: LoadBalancer
|
||||
484
examples/redis-cache/redis-cache.md
Normal file
484
examples/redis-cache/redis-cache.md
Normal file
@@ -0,0 +1,484 @@
|
||||
# Mycelium Cloud - Redis Cache Example
|
||||
|
||||
A complete, production-ready example for deploying a Redis cache with web interface on Mycelium Cloud Kubernetes cluster. Features multi-container pod architecture, visual Redis management, and comprehensive caching patterns.
|
||||
|
||||
## 📁 What This Contains
|
||||
|
||||
This directory contains everything you need to deploy a Redis cache system:
|
||||
|
||||
- **redis-cache.md** - This comprehensive guide
|
||||
- **redis-cache-deployment.yaml** - Multi-container pod deployment
|
||||
- **redis-cache-service.yaml** - LoadBalancer service configuration
|
||||
- **redis.conf** - Redis server configuration
|
||||
- **web-interface.py** - Python Flask web interface code (mounted via ConfigMap)
|
||||
|
||||
## 🚀 Quick Start (3 minutes)
|
||||
|
||||
```bash
|
||||
# 1. Deploy the application (creates ConfigMap, Deployment, and Service)
|
||||
kubectl apply -f redis-cache-deployment.yaml
|
||||
kubectl apply -f redis-cache-service.yaml
|
||||
|
||||
# 2. Wait for pods to be ready
|
||||
kubectl wait --for=condition=ready pod -l app=redis-cache --timeout=120s
|
||||
|
||||
# 3. Access Redis via CLI
|
||||
kubectl exec -it $(kubectl get pod -l app=redis-cache -o jsonpath='{.items[0].metadata.name}') -- redis-cli -h localhost -p 6379 ping
|
||||
|
||||
# 4. Test data storage
|
||||
kubectl exec -it $(kubectl get pod -l app=redis-cache -o jsonpath='{.items[0].metadata.name}') -- redis-cli -h localhost -p 6379 SET test "Hello from Mycelium Cloud!"
|
||||
|
||||
# 5. Access web interface
|
||||
kubectl port-forward service/redis-cache-service 8380:8080 &
|
||||
curl http://localhost:8380
|
||||
```
|
||||
|
||||
**Expected Result:** Redis responds with "PONG" and stores/retrieves data successfully. Web interface displays Redis statistics and key management tools.
|
||||
|
||||
## 📋 What You'll Learn
|
||||
|
||||
- ✅ Advanced Kubernetes patterns (multi-container pods)
|
||||
- ✅ Redis deployment and configuration
|
||||
- ✅ ConfigMap usage for application code
|
||||
- ✅ LoadBalancer services on Mycelium Cloud
|
||||
- ✅ Port-forwarding for multiple services (Redis + Web)
|
||||
- ✅ Production caching patterns
|
||||
- ✅ Web-based Redis management
|
||||
- ✅ Resource limits and container orchestration
|
||||
|
||||
## 🏗️ Architecture
|
||||
|
||||
This example uses a **multi-container pod pattern** with **two-file approach**:
|
||||
|
||||
1. **redis-cache-deployment.yaml** - Contains ConfigMap, Deployment with 2 containers
|
||||
2. **redis-cache-service.yaml** - Contains networking configuration
|
||||
|
||||
**Network Flow:**
|
||||
```
|
||||
kubectl port-forward → LoadBalancer Service → Pod (redis-cache + redis-web-interface)
|
||||
```
|
||||
|
||||
**Multi-Container Architecture:**
|
||||
- **redis-cache**: Redis 7-alpine server (port 6379)
|
||||
- **redis-web-interface**: Python Flask web app (port 8080)
|
||||
- **ConfigMap**: Mounted web interface code (web-interface.py)
|
||||
|
||||
## 🔧 Files Explanation
|
||||
|
||||
### redis-cache-deployment.yaml
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: redis-cache
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: redis-cache
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: redis-cache
|
||||
spec:
|
||||
containers:
|
||||
- name: redis-cache
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- containerPort: 6379
|
||||
name: redis
|
||||
command: ["redis-server"]
|
||||
args: ["--bind", "0.0.0.0", "--protected-mode", "no", "--maxmemory", "64mb"]
|
||||
resources:
|
||||
requests:
|
||||
memory: "32Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "64Mi"
|
||||
cpu: "200m"
|
||||
|
||||
- name: redis-web-interface
|
||||
image: python:3.11-alpine
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: web
|
||||
command: ["/bin/sh", "-c"]
|
||||
args:
|
||||
- |
|
||||
pip install flask redis &&
|
||||
python /app/web-interface.py
|
||||
volumeMounts:
|
||||
- name: web-interface
|
||||
mountPath: /app
|
||||
resources:
|
||||
requests:
|
||||
memory: "16Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "32Mi"
|
||||
cpu: "100m"
|
||||
volumes:
|
||||
- name: web-interface
|
||||
configMap:
|
||||
name: redis-web-interface
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
- Creates multi-container pod with Redis server + web interface
|
||||
- ConfigMap mounts web interface code
|
||||
- Resource limits for both containers
|
||||
- Redis configured with memory limits and LRU eviction
|
||||
|
||||
### redis-cache-service.yaml
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: redis-cache-service
|
||||
spec:
|
||||
selector:
|
||||
app: redis-cache
|
||||
ports:
|
||||
- name: redis
|
||||
port: 6379
|
||||
targetPort: 6379
|
||||
- name: web
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
type: LoadBalancer
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
- Creates LoadBalancer service
|
||||
- Exposes both Redis (6379) and web (8080) ports
|
||||
- Routes traffic to multi-container pod
|
||||
|
||||
## 🌐 Access Methods
|
||||
|
||||
### Method 1: Port-Forward (Recommended for Mycelium Cloud)
|
||||
|
||||
**Access Redis CLI:**
|
||||
```bash
|
||||
# Keep terminal open, forward Redis port
|
||||
kubectl port-forward service/redis-cache-service 6379:6379
|
||||
|
||||
# In another terminal, test Redis
|
||||
redis-cli -h localhost -p 6379 ping
|
||||
redis-cli -h localhost -p 6379 set mykey "Hello Redis!"
|
||||
redis-cli -h localhost -p 6379 get mykey
|
||||
```
|
||||
|
||||
**Access Web Interface:**
|
||||
```bash
|
||||
# Forward web interface port
|
||||
kubectl port-forward service/redis-cache-service 8380:8080
|
||||
|
||||
# Access via browser: http://localhost:8380
|
||||
curl http://localhost:8380
|
||||
```
|
||||
|
||||
**Background Mode:**
|
||||
```bash
|
||||
# Start both port-forwards in background
|
||||
nohup kubectl port-forward service/redis-cache-service 6379:6379 8380:8080 > redis-access.log 2>&1 &
|
||||
|
||||
# Test access
|
||||
curl http://localhost:8380
|
||||
redis-cli -h localhost -p 6379 ping
|
||||
```
|
||||
|
||||
### Method 2: Direct Pod Access (Inside Cluster)
|
||||
|
||||
**Redis CLI Access:**
|
||||
```bash
|
||||
# Execute commands directly in Redis container
|
||||
kubectl exec -it $(kubectl get pod -l app=redis-cache -o jsonpath='{.items[0].metadata.name}') -c redis-cache -- redis-cli
|
||||
|
||||
# Or run single commands
|
||||
kubectl exec -it $(kubectl get pod -l app=redis-cache -o jsonpath='{.items[0].metadata.name}') -c redis-cache -- redis-cli -h localhost -p 6379 ping
|
||||
```
|
||||
|
||||
**Web Interface Test:**
|
||||
```bash
|
||||
# Test web interface from within pod
|
||||
kubectl exec -it $(kubectl get pod -l app=redis-cache -o jsonpath='{.items[0].metadata.name}') -c redis-web-interface -- python3 -c "import requests; r = requests.get('http://localhost:8080', timeout=5); print(f'Web interface: {r.status_code}')"
|
||||
```
|
||||
|
||||
### Method 3: LoadBalancer IP Access (If Available)
|
||||
|
||||
```bash
|
||||
# Get LoadBalancer IP (may be internal on Mycelium Cloud)
|
||||
kubectl get svc redis-cache-service
|
||||
|
||||
# Access Redis (if external IP available)
|
||||
redis-cli -h <external-ip> -p 6379 ping
|
||||
|
||||
# Access web interface (if external IP available)
|
||||
curl http://<external-ip>:8080
|
||||
```
|
||||
|
||||
## 📊 Web Interface Features
|
||||
|
||||
The Redis web interface provides:
|
||||
|
||||
**Dashboard:**
|
||||
- Real-time Redis statistics
|
||||
- Memory usage monitoring
|
||||
- Connected clients count
|
||||
- Uptime tracking
|
||||
|
||||
**Key Management:**
|
||||
- View all Redis keys
|
||||
- Add/update/delete keys
|
||||
- Search with patterns (user:*, session:*)
|
||||
- TTL management
|
||||
|
||||
**Cache Examples:**
|
||||
- API cache simulation
|
||||
- Session storage demo
|
||||
- Counter/rate limiting examples
|
||||
- Memory usage patterns
|
||||
|
||||
**Quick Actions:**
|
||||
- Add sample data
|
||||
- Clear all data
|
||||
- Refresh statistics
|
||||
- Memory information
|
||||
|
||||
## 🔍 Troubleshooting
|
||||
|
||||
### Check Deployment Status
|
||||
```bash
|
||||
# Check pods status (should show 2/2 Ready)
|
||||
kubectl get pods -l app=redis-cache
|
||||
|
||||
# Check service details
|
||||
kubectl get svc redis-cache-service
|
||||
|
||||
# Check ConfigMap
|
||||
kubectl get configmap redis-web-interface
|
||||
|
||||
# Check events
|
||||
kubectl get events --field-selector involvedObject.name=$(kubectl get pod -l app=redis-cache -o jsonpath='{.items[0].metadata.name}')
|
||||
```
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### Pod Not Starting
|
||||
```bash
|
||||
# Check pod status and events
|
||||
kubectl describe pod -l app=redis-cache
|
||||
|
||||
# Check container logs
|
||||
kubectl logs -l app=redis-cache
|
||||
kubectl logs -l app=redis-cache -c redis-cache
|
||||
kubectl logs -l app=redis-cache -c redis-web-interface --previous
|
||||
```
|
||||
|
||||
#### Web Interface Failing
|
||||
```bash
|
||||
# Check web interface logs
|
||||
kubectl logs -l app=redis-cache -c redis-web-interface
|
||||
|
||||
# Verify ConfigMap is mounted
|
||||
kubectl exec -it $(kubectl get pod -l app=redis-cache -o jsonpath='{.items[0].metadata.name}') -c redis-web-interface -- ls /app/
|
||||
|
||||
# Test Flask startup manually
|
||||
kubectl exec -it $(kubectl get pod -l app=redis-cache -o jsonpath='{.items[0].metadata.name}') -c redis-web-interface -- python3 -c "print('Flask available')"
|
||||
```
|
||||
|
||||
#### Redis Connection Issues
|
||||
```bash
|
||||
# Test Redis connectivity from web interface container
|
||||
kubectl exec -it $(kubectl get pod -l app=redis-cache -o jsonpath='{.items[0].metadata.name}') -c redis-web-interface -- python3 -c "import redis; r = redis.Redis(host='localhost', port=6379); print(r.ping())"
|
||||
|
||||
# Check Redis configuration
|
||||
kubectl exec -it $(kubectl get pod -l app=redis-cache -o jsonpath='{.items[0].metadata.name}') -c redis-cache -- redis-cli config get "*"
|
||||
```
|
||||
|
||||
#### Port Conflicts
|
||||
```bash
|
||||
# Check if ports are in use
|
||||
lsof -i :6379
|
||||
lsof -i :8080
|
||||
|
||||
# Kill conflicting processes
|
||||
kill -9 $(lsof -ti:6379)
|
||||
kill -9 $(lsof -ti:8080)
|
||||
```
|
||||
|
||||
## 🛠️ Common Operations
|
||||
|
||||
### Scaling
|
||||
```bash
|
||||
# Scale to 3 replicas (note: Redis is single-instance by nature)
|
||||
kubectl scale deployment redis-cache --replicas=1
|
||||
|
||||
# Check distribution
|
||||
kubectl get pods -o wide
|
||||
```
|
||||
|
||||
### Updates
|
||||
```bash
|
||||
# Update Redis image
|
||||
kubectl set image deployment/redis-cache redis-cache=redis:7.2-alpine
|
||||
|
||||
# Restart deployment
|
||||
kubectl rollout restart deployment/redis-cache
|
||||
|
||||
# Check rollout status
|
||||
kubectl rollout status deployment/redis-cache
|
||||
```
|
||||
|
||||
### Data Management
|
||||
```bash
|
||||
# Access Redis CLI
|
||||
kubectl exec -it $(kubectl get pod -l app=redis-cache -o jsonpath='{.items[0].metadata.name}') -c redis-cache -- redis-cli
|
||||
|
||||
# Common Redis commands inside pod:
|
||||
# KEYS *
|
||||
# INFO memory
|
||||
# FLUSHALL
|
||||
# DBSIZE
|
||||
```
|
||||
|
||||
### Monitoring
|
||||
```bash
|
||||
# View logs from both containers
|
||||
kubectl logs -f deployment/redis-cache
|
||||
kubectl logs -f deployment/redis-cache -c redis-cache
|
||||
kubectl logs -f deployment/redis-cache -c redis-web-interface
|
||||
|
||||
# Monitor resource usage
|
||||
kubectl top pod -l app=redis-cache
|
||||
|
||||
# Check Redis info
|
||||
kubectl exec -it $(kubectl get pod -l app=redis-cache -o jsonpath='{.items[0].metadata.name}') -c redis-cache -- redis-cli INFO
|
||||
```
|
||||
|
||||
## 🧹 Cleanup
|
||||
|
||||
When you're done testing:
|
||||
|
||||
```bash
|
||||
# Delete the application and service
|
||||
kubectl delete -f redis-cache-deployment.yaml -f redis-cache-service.yaml
|
||||
|
||||
# Wait for cleanup
|
||||
kubectl wait --for=delete pod -l app=redis-cache --timeout=60s
|
||||
|
||||
# Kill any port-forwards
|
||||
lsof -ti:6379 | xargs kill -9 2>/dev/null || true
|
||||
lsof -ti:8080 | xargs kill -9 2>/dev/null || true
|
||||
|
||||
# Verify cleanup
|
||||
kubectl get all -l app=redis-cache
|
||||
kubectl get configmap redis-web-interface 2>/dev/null || echo "ConfigMap deleted"
|
||||
```
|
||||
|
||||
## 🎯 What This Demonstrates
|
||||
|
||||
This example shows:
|
||||
- **Advanced Kubernetes patterns** - multi-container pods, ConfigMaps
|
||||
- **Production Redis deployment** - memory limits, configuration management
|
||||
- **Web-based management** - Flask interface for Redis operations
|
||||
- **Mycelium Cloud networking** - LoadBalancer services, port-forwarding
|
||||
- **Container orchestration** - resource management, health monitoring
|
||||
- **Development workflows** - testing, debugging, scaling patterns
|
||||
|
||||
## 🔗 Next Steps
|
||||
|
||||
Once you understand this example, try:
|
||||
|
||||
1. **Redis Clustering** - Multiple Redis instances with data sharding
|
||||
2. **Persistent Storage** - Redis persistence with volumes
|
||||
3. **Redis Sentinel** - High availability Redis setup
|
||||
4. **Cache Patterns** - Implement cache-aside, write-through patterns
|
||||
5. **Integration** - Connect web applications to Redis cache
|
||||
6. **Monitoring** - Add Prometheus metrics for Redis
|
||||
|
||||
## 📚 More Examples
|
||||
|
||||
Other available examples:
|
||||
- **hello-world/** - Basic web application deployment
|
||||
- **nginx-static/** - Static website hosting
|
||||
- **python-flask/** - Python API server with multiple endpoints
|
||||
|
||||
## 💡 Pro Tips
|
||||
|
||||
1. **Multi-Container Access**: Use `-c container-name` to access specific containers
|
||||
2. **ConfigMap Updates**: Modify web-interface.py and restart deployment
|
||||
3. **Redis Testing**: Use `redis-cli` for quick testing and monitoring
|
||||
4. **Web Interface**: Great for visual debugging and demonstrating Redis concepts
|
||||
5. **Memory Management**: Redis memory limits prevent resource exhaustion
|
||||
6. **Network Testing**: Use `kubectl exec` for internal cluster testing
|
||||
7. **Background Services**: Combine multiple port-forwards with `&`
|
||||
|
||||
## 🔧 Redis-Specific Tips
|
||||
|
||||
### Data Types Demo
|
||||
```bash
|
||||
# Strings
|
||||
kubectl exec -it $(kubectl get pod -l app=redis-cache -o jsonpath='{.items[0].metadata.name}') -c redis-cache -- redis-cli SET user:1001 "Alice Johnson"
|
||||
|
||||
# Hashes
|
||||
kubectl exec -it $(kubectl get pod -l app=redis-cache -o jsonpath='{.items[0].metadata.name}') -c redis-cache -- redis-cli HSET user:1002 name "Bob Smith" age "30"
|
||||
|
||||
# Lists
|
||||
kubectl exec -it $(kubectl get pod -l app=redis-cache -o jsonpath='{.items[0].metadata.name}') -c redis-cache -- redis-cli LPUSH queue:tasks "task1" "task2"
|
||||
|
||||
# Sets
|
||||
kubectl exec -it $(kubectl get pod -l app=redis-cache -o jsonpath='{.items[0].metadata.name}') -c redis-cache -- redis-cli SADD tags:web "redis" "caching" "performance"
|
||||
```
|
||||
|
||||
### Performance Testing
|
||||
```bash
|
||||
# Simple load test
|
||||
kubectl exec -it $(kubectl get pod -l app=redis-cache -o jsonpath='{.items[0].metadata.name}') -c redis-cache -- redis-cli --latency-history -i 1
|
||||
|
||||
# Memory usage
|
||||
kubectl exec -it $(kubectl get pod -l app=redis-cache -o jsonpath='{.items[0].metadata.name}') -c redis-cache -- redis-cli INFO memory
|
||||
```
|
||||
|
||||
## 🎉 Success Indicators
|
||||
|
||||
You'll know everything is working when:
|
||||
- ✅ `kubectl get pods` shows "2/2 Running" for redis-cache pod
|
||||
- ✅ `kubectl get svc` shows redis-cache-service with LoadBalancer type
|
||||
- ✅ `redis-cli -h localhost -p 6379 ping` returns "PONG"
|
||||
- ✅ `kubectl exec` commands work in both containers
|
||||
- ✅ Web interface accessible at http://localhost:8380
|
||||
- ✅ Redis operations (SET/GET) work via CLI and web interface
|
||||
- ✅ No errors in `kubectl get events`
|
||||
|
||||
**Congratulations! You've successfully deployed a production-ready Redis cache system on Mycelium Cloud! 🚀**
|
||||
|
||||
---
|
||||
|
||||
## 📖 File Contents
|
||||
|
||||
For reference, here are the complete file contents:
|
||||
|
||||
### redis-cache-deployment.yaml
|
||||
[Complete deployment configuration with multi-container pod setup]
|
||||
|
||||
### redis-cache-service.yaml
|
||||
[Complete service configuration with LoadBalancer type]
|
||||
|
||||
### redis.conf
|
||||
[Redis server configuration with memory limits and performance settings]
|
||||
|
||||
### web-interface.py
|
||||
[Complete Flask web application for Redis management]
|
||||
|
||||
## 🆘 Support
|
||||
|
||||
If you encounter issues:
|
||||
1. Check the troubleshooting section above
|
||||
2. Verify your kubeconfig is set correctly: `kubectl get nodes`
|
||||
3. Ensure your cluster is healthy: `kubectl get pods --all-namespaces`
|
||||
4. Check Redis logs: `kubectl logs -l app=redis-cache -c redis-cache`
|
||||
5. Test web interface functionality via browser at http://localhost:8380
|
||||
6. Verify ConfigMap mounting: `kubectl exec -it <pod-name> -c redis-web-interface -- ls /app/`
|
||||
|
||||
For more help, visit our [documentation](../../README.md) or contact support.
|
||||
43
examples/redis-cache/redis.conf
Normal file
43
examples/redis-cache/redis.conf
Normal file
@@ -0,0 +1,43 @@
|
||||
# Redis Configuration for MyceliumCloud Example
|
||||
# This is a minimal configuration for learning Redis
|
||||
|
||||
# Network
|
||||
bind 0.0.0.0
|
||||
port 6379
|
||||
|
||||
# Security
|
||||
protected-mode no
|
||||
|
||||
# Memory Management
|
||||
maxmemory 64mb
|
||||
maxmemory-policy allkeys-lru
|
||||
|
||||
# Persistence (Disabled for simpler learning)
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
appendonly no
|
||||
|
||||
# Logging
|
||||
loglevel notice
|
||||
logfile ""
|
||||
|
||||
# General
|
||||
daemonize no
|
||||
supervised no
|
||||
pidfile /var/run/redis_6379.pid
|
||||
|
||||
# Slow Log
|
||||
slowlog-log-slower-than 10000
|
||||
slowlog-max-len 128
|
||||
|
||||
# Client Output Buffer Limits
|
||||
client-output-buffer-limit normal 0 0 0
|
||||
client-output-buffer-limit replica 256mb 64mb 60
|
||||
client-output-buffer-limit pubsub 32mb 8mb 60
|
||||
|
||||
# AOF Rewrite Incremental Fsync
|
||||
aof-rewrite-incremental-fsync yes
|
||||
|
||||
# RDB Save Incremental Fsync
|
||||
rdb-save-incremental-fsync yes
|
||||
491
examples/redis-cache/web-interface.py
Normal file
491
examples/redis-cache/web-interface.py
Normal file
@@ -0,0 +1,491 @@
|
||||
from flask import Flask, jsonify, request, render_template_string
|
||||
import redis
|
||||
import time
|
||||
import os
|
||||
import json
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# Connect to Redis (same pod)
|
||||
redis_client = redis.Redis(
|
||||
host='localhost',
|
||||
port=6379,
|
||||
decode_responses=True
|
||||
)
|
||||
|
||||
# HTML template for the interface
|
||||
HTML_TEMPLATE = '''
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Redis Cache Visualizer</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 20px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background: rgba(255,255,255,0.1);
|
||||
padding: 30px;
|
||||
border-radius: 15px;
|
||||
}
|
||||
.section {
|
||||
background: rgba(255,255,255,0.1);
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.key-item {
|
||||
background: rgba(0,0,0,0.2);
|
||||
margin: 5px 0;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
font-family: monospace;
|
||||
}
|
||||
.value {
|
||||
color: #90EE90;
|
||||
font-weight: bold;
|
||||
}
|
||||
button {
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
margin: 5px;
|
||||
}
|
||||
button:hover {
|
||||
background: #45a049;
|
||||
}
|
||||
input {
|
||||
padding: 8px;
|
||||
margin: 5px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
.redis-logo {
|
||||
font-size: 2em;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
color: #DC382D;
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
||||
}
|
||||
.stat {
|
||||
display: inline-block;
|
||||
background: rgba(0,0,0,0.3);
|
||||
padding: 15px;
|
||||
margin: 10px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
.stat-number {
|
||||
font-size: 2em;
|
||||
font-weight: bold;
|
||||
color: #FFD700;
|
||||
}
|
||||
.stat-label {
|
||||
font-size: 0.9em;
|
||||
opacity: 0.8;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="redis-logo">Redis</div>
|
||||
<h1 style="text-align: center;">Redis Cache Visualizer</h1>
|
||||
<p style="text-align: center; font-size: 1.2em;">Interactive Redis data management and visualization</p>
|
||||
|
||||
<div class="section">
|
||||
<h2>📊 Redis Statistics</h2>
|
||||
<div style="text-align: center;">
|
||||
<div class="stat">
|
||||
<div class="stat-number">{{ stats.total_keys }}</div>
|
||||
<div class="stat-label">Total Keys</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-number">{{ stats.used_memory_human }}</div>
|
||||
<div class="stat-label">Memory Used</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-number">{{ stats.connected_clients }}</div>
|
||||
<div class="stat-label">Connected Clients</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-number">{{ stats.uptime_in_seconds // 60 }}</div>
|
||||
<div class="stat-label">Uptime (min)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>🔧 Quick Actions</h2>
|
||||
<button onclick="addSampleData()">Add Sample Data</button>
|
||||
<button onclick="clearAll()">Clear All Data</button>
|
||||
<button onclick="refreshData()">Refresh</button>
|
||||
<button onclick="showMemoryInfo()">Memory Info</button>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>📝 Add Data</h2>
|
||||
<input type="text" id="keyInput" placeholder="Enter key...">
|
||||
<input type="text" id="valueInput" placeholder="Enter value...">
|
||||
<input type="number" id="ttlInput" placeholder="TTL (seconds, optional)" min="0">
|
||||
<button onclick="addData()">Add to Redis</button>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>🔍 Search Keys</h2>
|
||||
<input type="text" id="patternInput" placeholder="Search pattern (e.g., user:*)">
|
||||
<button onclick="searchKeys()">Search</button>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>📋 All Redis Keys</h2>
|
||||
<div id="keysList">
|
||||
{% for key, value in redis_data.items() %}
|
||||
<div class="key-item">
|
||||
<strong>{{ key }}</strong>: <span class="value">{{ value }}</span>
|
||||
<button onclick="getKey('{{ key }}')">View</button>
|
||||
<button onclick="deleteKey('{{ key }}')">Delete</button>
|
||||
<button onclick="updateKey('{{ key }}')">Update</button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>📈 Cache Performance Examples</h2>
|
||||
<button onclick="demoCache()">Simulate API Cache</button>
|
||||
<button onclick="demoSession()">Simulate Session Store</button>
|
||||
<button onclick="demoCounter()">Demo Counter/Rate Limiting</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function refreshData() {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
function addSampleData() {
|
||||
fetch('/api/add_sample', {method: 'POST'})
|
||||
.then(() => refreshData());
|
||||
}
|
||||
|
||||
function clearAll() {
|
||||
if (confirm('Are you sure you want to clear all data?')) {
|
||||
fetch('/api/clear_all', {method: 'POST'})
|
||||
.then(() => refreshData());
|
||||
}
|
||||
}
|
||||
|
||||
function addData() {
|
||||
const key = document.getElementById('keyInput').value;
|
||||
const value = document.getElementById('valueInput').value;
|
||||
const ttl = document.getElementById('ttlInput').value;
|
||||
|
||||
fetch('/api/set', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({key, value, ttl: ttl ? parseInt(ttl) : null})
|
||||
}).then(() => refreshData());
|
||||
}
|
||||
|
||||
function searchKeys() {
|
||||
const pattern = document.getElementById('patternInput').value;
|
||||
fetch('/api/search/' + pattern)
|
||||
.then(response => response.json())
|
||||
.then(data => displayKeys(data));
|
||||
}
|
||||
|
||||
function deleteKey(key) {
|
||||
fetch('/api/delete/' + key, {method: 'DELETE'})
|
||||
.then(() => refreshData());
|
||||
}
|
||||
|
||||
function getKey(key) {
|
||||
fetch('/api/get/' + key)
|
||||
.then(response => response.json())
|
||||
.then(data => alert('Key: ' + key + '\\nValue: ' + data.value + '\\nTTL: ' + data.ttl));
|
||||
}
|
||||
|
||||
function updateKey(key) {
|
||||
const newValue = prompt('Enter new value for ' + key);
|
||||
if (newValue) {
|
||||
fetch('/api/update', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({key, value: newValue})
|
||||
}).then(() => refreshData());
|
||||
}
|
||||
}
|
||||
|
||||
function demoCache() {
|
||||
fetch('/api/demo_cache', {method: 'POST'})
|
||||
.then(() => refreshData());
|
||||
}
|
||||
|
||||
function demoSession() {
|
||||
fetch('/api/demo_session', {method: 'POST'})
|
||||
.then(() => refreshData());
|
||||
}
|
||||
|
||||
function demoCounter() {
|
||||
fetch('/api/demo_counter', {method: 'POST'})
|
||||
.then(() => refreshData());
|
||||
}
|
||||
|
||||
function showMemoryInfo() {
|
||||
fetch('/api/memory_info')
|
||||
.then(response => response.json())
|
||||
.then(data => alert('Memory Info:\\n' + JSON.stringify(data, null, 2)));
|
||||
}
|
||||
|
||||
function displayKeys(keys) {
|
||||
const keysList = document.getElementById('keysList');
|
||||
keysList.innerHTML = '';
|
||||
keys.forEach(key => {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'key-item';
|
||||
div.innerHTML = '<strong>' + key + '</strong> <button onclick="getKey(\'' + key + '\')">View</button> <button onclick="deleteKey(\'' + key + '\')">Delete</button>';
|
||||
keysList.appendChild(div);
|
||||
});
|
||||
}
|
||||
|
||||
// Auto-refresh every 30 seconds
|
||||
setInterval(refreshData, 30000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
"""Main dashboard page"""
|
||||
try:
|
||||
# Get Redis statistics
|
||||
info = redis_client.info()
|
||||
|
||||
# Get all keys (limited for display)
|
||||
keys = redis_client.keys('*')
|
||||
redis_data = {}
|
||||
|
||||
# Get values for keys (limit to first 20 for display)
|
||||
for key in keys[:20]:
|
||||
try:
|
||||
value = redis_client.get(key)
|
||||
ttl = redis_client.ttl(key)
|
||||
if ttl > 0:
|
||||
redis_data[key] = f"{value} (TTL: {ttl}s)"
|
||||
else:
|
||||
redis_data[key] = value
|
||||
except:
|
||||
redis_data[key] = "[Binary Data]"
|
||||
|
||||
# Format statistics for display
|
||||
stats = {
|
||||
'total_keys': len(keys),
|
||||
'used_memory_human': info.get('used_memory_human', 'N/A'),
|
||||
'connected_clients': info.get('connected_clients', 'N/A'),
|
||||
'uptime_in_seconds': info.get('uptime_in_seconds', 'N/A'),
|
||||
'total_commands_processed': info.get('total_commands_processed', 'N/A'),
|
||||
'keyspace_hits': info.get('keyspace_hits', 'N/A'),
|
||||
'keyspace_misses': info.get('keyspace_misses', 'N/A'),
|
||||
'expired_keys': info.get('expired_keys', 'N/A'),
|
||||
'evicted_keys': info.get('evicted_keys', 'N/A')
|
||||
}
|
||||
|
||||
return render_template_string(HTML_TEMPLATE, redis_data=redis_data, stats=stats)
|
||||
except Exception as e:
|
||||
return f"<h1>Error connecting to Redis: {e}</h1>"
|
||||
|
||||
@app.route('/api/stats')
|
||||
def get_stats():
|
||||
"""API endpoint for Redis statistics"""
|
||||
try:
|
||||
info = redis_client.info()
|
||||
return jsonify({
|
||||
'status': 'success',
|
||||
'data': {
|
||||
'keys': len(redis_client.keys('*')),
|
||||
'memory': info.get('used_memory_human'),
|
||||
'clients': info.get('connected_clients'),
|
||||
'uptime_minutes': info.get('uptime_in_seconds', 0) // 60
|
||||
}
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||
|
||||
@app.route('/api/keys')
|
||||
def get_keys():
|
||||
"""API endpoint to get all keys"""
|
||||
try:
|
||||
keys = redis_client.keys('*')
|
||||
return jsonify({'status': 'success', 'keys': keys})
|
||||
except Exception as e:
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||
|
||||
@app.route('/api/set', methods=['POST'])
|
||||
def set_key():
|
||||
"""API endpoint to set a key-value pair"""
|
||||
try:
|
||||
data = request.json
|
||||
key = data.get('key')
|
||||
value = data.get('value')
|
||||
ttl = data.get('ttl')
|
||||
|
||||
if not key or value is None:
|
||||
return jsonify({'status': 'error', 'message': 'Key and value are required'}), 400
|
||||
|
||||
if ttl and ttl > 0:
|
||||
redis_client.setex(key, ttl, value)
|
||||
else:
|
||||
redis_client.set(key, value)
|
||||
|
||||
return jsonify({'status': 'success', 'message': f'Key {key} set successfully'})
|
||||
except Exception as e:
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||
|
||||
@app.route('/api/get/<key>')
|
||||
def get_key(key):
|
||||
"""API endpoint to get a key-value pair"""
|
||||
try:
|
||||
value = redis_client.get(key)
|
||||
ttl = redis_client.ttl(key)
|
||||
if value is None:
|
||||
return jsonify({'status': 'error', 'message': 'Key not found'}), 404
|
||||
|
||||
return jsonify({
|
||||
'status': 'success',
|
||||
'data': {
|
||||
'key': key,
|
||||
'value': value,
|
||||
'ttl': ttl
|
||||
}
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||
|
||||
@app.route('/api/delete/<key>', methods=['DELETE'])
|
||||
def delete_key(key):
|
||||
"""API endpoint to delete a key"""
|
||||
try:
|
||||
deleted = redis_client.delete(key)
|
||||
if deleted == 0:
|
||||
return jsonify({'status': 'error', 'message': 'Key not found'}), 404
|
||||
|
||||
return jsonify({'status': 'success', 'message': f'Key {key} deleted successfully'})
|
||||
except Exception as e:
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||
|
||||
@app.route('/api/search/<pattern>')
|
||||
def search_keys(pattern):
|
||||
"""API endpoint to search for keys"""
|
||||
try:
|
||||
keys = redis_client.keys(pattern)
|
||||
return jsonify({'status': 'success', 'keys': keys})
|
||||
except Exception as e:
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||
|
||||
@app.route('/api/add_sample', methods=['POST'])
|
||||
def add_sample_data():
|
||||
"""Add sample data for demonstration"""
|
||||
try:
|
||||
# Add various types of data
|
||||
redis_client.set('user:1001', 'Alice Johnson')
|
||||
redis_client.set('user:1002', 'Bob Smith')
|
||||
redis_client.set('session:abc123', 'logged_in')
|
||||
redis_client.setex('temp:token', 300, 'temporary_data')
|
||||
redis_client.set('api:request:count', '0')
|
||||
redis_client.set('cache:homepage', 'Cached homepage content')
|
||||
redis_client.setex('user:login:last', 3600, '2025-11-04T17:00:00')
|
||||
|
||||
return jsonify({'status': 'success', 'message': 'Sample data added'})
|
||||
except Exception as e:
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||
|
||||
@app.route('/api/clear_all', methods=['POST'])
|
||||
def clear_all():
|
||||
"""Clear all Redis data"""
|
||||
try:
|
||||
redis_client.flushall()
|
||||
return jsonify({'status': 'success', 'message': 'All data cleared'})
|
||||
except Exception as e:
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||
|
||||
@app.route('/api/demo_cache', methods=['POST'])
|
||||
def demo_cache():
|
||||
"""Demonstrate caching patterns"""
|
||||
try:
|
||||
# Simulate cache-aside pattern
|
||||
redis_client.setex('api:users:1001', 600, 'User data from database')
|
||||
redis_client.setex('api:posts:recent', 300, 'Recent blog posts')
|
||||
redis_client.setex('api:stats:daily', 1800, 'Daily statistics')
|
||||
|
||||
return jsonify({'status': 'success', 'message': 'Cache examples added'})
|
||||
except Exception as e:
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||
|
||||
@app.route('/api/demo_session', methods=['POST'])
|
||||
def demo_session():
|
||||
"""Demonstrate session storage"""
|
||||
try:
|
||||
# Simulate session storage
|
||||
redis_client.setex('session:user123', 3600, 'active')
|
||||
redis_client.setex('session:user456', 7200, 'premium')
|
||||
redis_client.setex('session:admin789', 1800, 'administrator')
|
||||
|
||||
return jsonify({'status': 'success', 'message': 'Session examples added'})
|
||||
except Exception as e:
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||
|
||||
@app.route('/api/demo_counter', methods=['POST'])
|
||||
def demo_counter():
|
||||
"""Demonstrate counter/rate limiting"""
|
||||
try:
|
||||
# Simulate counters
|
||||
redis_client.set('counter:api_requests', '0')
|
||||
redis_client.set('counter:user_visits', '0')
|
||||
redis_client.set('counter:page_views', '0')
|
||||
|
||||
return jsonify({'status': 'success', 'message': 'Counter examples added'})
|
||||
except Exception as e:
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||
|
||||
@app.route('/api/memory_info')
|
||||
def memory_info():
|
||||
"""Get detailed memory information"""
|
||||
try:
|
||||
info = redis_client.info('memory')
|
||||
return jsonify({
|
||||
'status': 'success',
|
||||
'memory': {
|
||||
'used_memory': info.get('used_memory'),
|
||||
'used_memory_human': info.get('used_memory_human'),
|
||||
'used_memory_rss': info.get('used_memory_rss'),
|
||||
'maxmemory': info.get('maxmemory'),
|
||||
'maxmemory_policy': info.get('maxmemory_policy'),
|
||||
'mem_fragmentation_ratio': info.get('mem_fragmentation_ratio')
|
||||
}
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("Starting Redis Web Interface...")
|
||||
print("Access Redis at: localhost:6379")
|
||||
print("Access Web Interface at: localhost:8080")
|
||||
|
||||
# Test Redis connection
|
||||
try:
|
||||
redis_client.ping()
|
||||
print("✅ Redis connection successful")
|
||||
except Exception as e:
|
||||
print(f"❌ Redis connection failed: {e}")
|
||||
exit(1)
|
||||
|
||||
app.run(host='0.0.0.0', port=8080, debug=False)
|
||||
Reference in New Issue
Block a user