mirror of https://github.com/interlegis/sapl.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
|
|
3 weeks ago | |
|---|---|---|
| .. | ||
| README.md | 18 hours ago | |
| redis-configmap.yaml | 18 hours ago | |
| redis-deployment.yaml | 18 hours ago | |
| redis-service.yaml | 18 hours ago | |
| sapl-deploy.sh | 8 months ago | |
| sapl-k8s.yaml | 8 months ago | |
README.md
SAPL — Kubernetes Redis
Manifests for the shared Redis instance used by all SAPL pods for cross-pod rate limiting (DB 1) and view/static-file caching (DB 0).
Directory layout
docker/k8s/
├── redis-configmap.yaml # redis.conf — no persistence, allkeys-lru, 5 GB ceiling
├── redis-deployment.yaml # Deployment (1 replica, redis:7-alpine)
├── redis-service.yaml # ClusterIP service on port 6379
└── README.md # this file
Prerequisites
kubectlconfigured to talk to the target cluster.- A
redisnamespace (created below if it doesn't exist).
Deploy
# 1. Create the namespace (idempotent)
kubectl create namespace redis --dry-run=client -o yaml | kubectl apply -f -
# 2. Apply all three manifests
kubectl apply -f docker/k8s/redis-configmap.yaml
kubectl apply -f docker/k8s/redis-deployment.yaml
kubectl apply -f docker/k8s/redis-service.yaml
# 3. Verify the pod is Running
kubectl -n redis get pods -l app=sapl-redis
Expected output:
NAME READY STATUS RESTARTS AGE
sapl-redis-6d9f8b7c4d-xk2lm 1/1 Running 0 30s
Wire a SAPL namespace to Redis
# Create the per-namespace Secret (one-off per tenant)
kubectl create secret generic sapl-redis \
--namespace=<NAMESPACE> \
--from-literal=REDIS_URL="redis://sapl-redis.redis.svc.cluster.local:6379" \
--dry-run=client -o yaml | kubectl apply -f -
# Ensure the waffle switch row exists (starts OFF)
kubectl exec -n <NAMESPACE> deploy/sapl -- \
python manage.py waffle_switch REDIS_CACHE off --create
# Enable Redis for this namespace
kubectl exec -n <NAMESPACE> deploy/sapl -- \
python manage.py waffle_switch REDIS_CACHE on
# Rolling restart so start.sh picks up the new switch value
kubectl rollout restart deployment/sapl -n <NAMESPACE>
kubectl rollout status deployment/sapl -n <NAMESPACE>
Fleet-wide rollout
kubectl get namespaces -l app=sapl -o name | sed 's|namespace/||' | \
xargs -P 10 -I{} kubectl exec -n {} deploy/sapl -- \
python manage.py waffle_switch REDIS_CACHE on --create
kubectl get namespaces -l app=sapl -o name | sed 's|namespace/||' | \
xargs -P 5 -I{} kubectl rollout restart deployment/sapl -n {}
Roll back (without removing the Secret)
kubectl exec -n <NAMESPACE> deploy/sapl -- \
python manage.py waffle_switch REDIS_CACHE off
kubectl rollout restart deployment/sapl -n <NAMESPACE>
Monitor
Pod and events
# Pod status
kubectl -n redis get pods -l app=sapl-redis -o wide
# Deployment events (useful right after apply)
kubectl -n redis describe deployment sapl-redis
# Pod events (OOMKill, restarts, etc.)
kubectl -n redis describe pod -l app=sapl-redis
Logs
# Tail live logs
kubectl -n redis logs -f deploy/sapl-redis
# Last 100 lines
kubectl -n redis logs deploy/sapl-redis --tail=100
Redis INFO
# Memory usage
kubectl exec -n redis deploy/sapl-redis -- \
redis-cli info memory \
| grep -E 'used_memory_human|maxmemory_human|mem_fragmentation_ratio'
# Connection pressure
kubectl exec -n redis deploy/sapl-redis -- \
redis-cli info stats \
| grep -E 'rejected_connections|instantaneous_ops_per_sec'
# Key distribution per DB
kubectl exec -n redis deploy/sapl-redis -- redis-cli info keyspace
# Recent slow queries
kubectl exec -n redis deploy/sapl-redis -- redis-cli slowlog get 10
# Live command sampling (1-second window)
kubectl exec -n redis deploy/sapl-redis -- redis-cli --latency-history -i 1
Rate-limiter keys (DB 1)
kubectl exec -n redis deploy/sapl-redis -- \
redis-cli -n 1 dbsize
kubectl exec -n redis deploy/sapl-redis -- \
redis-cli -n 1 --scan --pattern 'rl:ip:*' | head -20
Seed the UA deny list (once after first deploy)
kubectl exec -n redis deploy/sapl-redis -- redis-cli -n 1 \
SADD rl:bot:ua:blocked \
"$(echo -n 'GPTBot' | sha256sum | cut -d' ' -f1)" \
"$(echo -n 'ClaudeBot' | sha256sum | cut -d' ' -f1)" \
"$(echo -n 'PerplexityBot' | sha256sum | cut -d' ' -f1)" \
"$(echo -n 'Bytespider' | sha256sum | cut -d' ' -f1)" \
"$(echo -n 'AhrefsBot' | sha256sum | cut -d' ' -f1)" \
"$(echo -n 'meta-externalagent' | sha256sum | cut -d' ' -f1)"
# Add a new offender at runtime (no restart required)
kubectl exec -n redis deploy/sapl-redis -- redis-cli -n 1 \
SADD rl:bot:ua:blocked "$(echo -n 'NewBot/1.0' | sha256sum | cut -d' ' -f1)"
Local standalone Redis (development / testing)
No Kubernetes? Run Redis directly with Docker:
sudo docker run --rm -p 6379:6379 redis:7-alpine \
redis-server --save "" --appendonly no
Then point Django at it by exporting the env var before starting the dev server:
export REDIS_URL="redis://localhost:6379"
export CACHE_BACKEND="redis"
python manage.py runserver
Or add them to your local .env file:
REDIS_URL=redis://localhost:6379
CACHE_BACKEND=redis
Note: the waffle switch
REDIS_CACHEmust also beonin your local database forstart.shto activate the Redis backend. Run:python manage.py waffle_switch REDIS_CACHE on --create
Update redis.conf without redeploying
# Edit the ConfigMap
kubectl -n redis edit configmap redis-config
# Restart the pod to pick up the new config
kubectl -n redis rollout restart deployment/sapl-redis
Key schema reference
| DB | Use case | Key pattern | TTL |
|---|---|---|---|
| 0 | Page / view cache | sapl:cache:* |
60 – 3 600 s |
| 0 | Static file cache (logos) | static:{ns}:{sha256} |
3 – 24 h |
| 0 | PDF cache (≤ 360 KB) | file:{ns}:{sha256} |
1 h |
| 1 | IP rate-limit counter | rl:ip:{ip}:reqs |
60 s |
| 1 | IP blocked marker | rl:ip:{ip}:blocked |
300 s |
| 1 | User rate-limit counter | rl:{ns}:user:{id}:reqs |
60 s |
| 1 | Path counter | rl:{ns}:path:{sha256}:reqs |
60 s |
| 1 | UA deny list | rl:bot:ua:blocked |
permanent SET |
| 2 | Django Channels (future) | channels:* |
session TTL |