# Redis Setup (Single Instance) **Prerequisites**: Complete [01_init_docker_swarm.md](01_init_docker_swarm.md) first **Time to Complete**: 15-20 minutes **What You'll Build**: - Single Redis instance on existing worker-1 - Password-protected with Docker secrets - Private network communication only (mapleopentech-private-prod overlay) - Persistent data with AOF + RDB - Ready for Go application connections --- ## Table of Contents 1. [Overview](#overview) 2. [Label Worker Node](#label-worker-node) 3. [Create Redis Password Secret](#create-redis-password-secret) 4. [Deploy Redis](#deploy-redis) 5. [Verify Redis Health](#verify-redis-health) 6. [Connect from Application](#connect-from-application) 7. [Redis Management](#redis-management) 8. [Troubleshooting](#troubleshooting) --- ## Overview ### Architecture ``` Docker Swarm Cluster: ├── mapleopentech-swarm-manager-1-prod (10.116.0.2) │ └── Orchestrates cluster │ ├── mapleopentech-swarm-worker-1-prod (10.116.0.3) │ └── Redis (single instance) │ ├── Network: mapleopentech-private-prod (overlay, shared) │ ├── Port: 6379 (private only) │ ├── Auth: Password (Docker secret) │ └── Data: Persistent volume │ └── mapleopentech-swarm-worker-2,3,4-prod └── Cassandra Cluster (3 nodes) └── Same network: mapleopentech-private-prod Shared Network (mapleopentech-private-prod): ├── All services can communicate ├── Service discovery by name (redis, cassandra-1, etc.) └── No public internet access Future Application: └── mapleopentech-swarm-worker-X-prod └── Go Backend → Connects to redis:6379 and cassandra:9042 on mapleopentech-private-prod ``` ### Redis Configuration - **Version**: Redis 7 (Alpine) - **Memory**: 512MB max (with LRU eviction) - **Persistence**: AOF (every second) + RDB snapshots - **Network**: Private overlay network only - **Authentication**: Required via Docker secret - **Security**: Dangerous commands disabled (FLUSHALL, CONFIG, etc.) ### Why Worker-1? - Already exists from Docker Swarm setup - Available capacity (2GB RAM droplet) - Keeps costs down (no new droplet needed) - Sufficient for caching workload --- ## Label Worker Node We'll use Docker node labels to ensure Redis always deploys to worker-1. **On your manager node:** ```bash # SSH to manager ssh dockeradmin@ # Label worker-1 for Redis placement docker node update --label-add redis=true mapleopentech-swarm-worker-1-prod # Verify label docker node inspect mapleopentech-swarm-worker-1-prod --format '{{.Spec.Labels}}' # Should show: map[redis:true] ``` --- ## Create Redis Password Secret Redis will use Docker secrets for password authentication. ### Step 1: Generate Strong Password **On your manager node:** ```bash # Generate a random 32-character password REDIS_PASSWORD=$(openssl rand -base64 32 | tr -d "=+/" | cut -c1-32) # Display it (SAVE THIS IN YOUR PASSWORD MANAGER!) echo $REDIS_PASSWORD # Example output: a8K9mP2nQ7rT4vW5xY6zB3cD1eF0gH8i ``` **⚠️ IMPORTANT**: Save this password in your password manager now! You'll need it for: - Application configuration - Manual Redis CLI connections - Troubleshooting ### Step 2: Create Docker Secret ```bash # Create secret from the password echo $REDIS_PASSWORD | docker secret create redis_password - # Verify secret was created docker secret ls # Should show: # ID NAME CREATED # abc123... redis_password About a minute ago ``` ### Step 3: Update .env File **On your local machine**, update your `.env` file: ```bash # Add to cloud/infrastructure/production/.env REDIS_HOST=redis REDIS_PORT=6379 REDIS_PASSWORD= ``` --- ## Deploy Redis ### Step 1: Create Redis Stack File **On your manager node:** ```bash # Create directory for stack files (if not exists) mkdir -p ~/stacks cd ~/stacks # Create Redis stack file vi redis-stack.yml ``` Copy and paste the following: ```yaml version: '3.8' networks: mapleopentech-private-prod: external: true volumes: redis-data: secrets: redis_password: external: true services: redis: image: redis:7-alpine hostname: redis networks: - mapleopentech-private-prod volumes: - redis-data:/data secrets: - redis_password # Command with password from secret command: > sh -c ' redis-server --requirepass "$$(cat /run/secrets/redis_password)" --bind 0.0.0.0 --port 6379 --protected-mode no --save 900 1 --save 300 10 --save 60 10000 --appendonly yes --appendfilename "appendonly.aof" --appendfsync everysec --maxmemory 512mb --maxmemory-policy allkeys-lru --loglevel notice --databases 16 --timeout 300 --tcp-keepalive 300 --io-threads 2 --io-threads-do-reads yes --slowlog-log-slower-than 10000 --slowlog-max-len 128 --activerehashing yes --maxclients 10000 --rename-command FLUSHDB "" --rename-command FLUSHALL "" --rename-command CONFIG "" ' deploy: replicas: 1 placement: constraints: - node.labels.redis == true restart_policy: condition: on-failure delay: 5s max_attempts: 3 resources: limits: memory: 768M reservations: memory: 512M healthcheck: test: ["CMD", "sh", "-c", "redis-cli -a $$(cat /run/secrets/redis_password) ping | grep PONG"] interval: 10s timeout: 3s retries: 3 start_period: 10s ``` Save and exit (`:wq` in vi). ### Step 2: Verify Shared Overlay Network **Check if the mapleopentech-private-prod network exists:** ```bash docker network ls | grep mapleopentech-private-prod ``` **You should see:** ``` abc123... mapleopentech-private-prod overlay swarm ``` **If you completed 02_cassandra.md** (Step 4), the network already exists and you're good to go! **If the network doesn't exist**, create it now: ```bash # Create the shared mapleopentech-private-prod network docker network create \ --driver overlay \ --attachable \ mapleopentech-private-prod # Verify it was created docker network ls | grep mapleopentech-private-prod ``` **What is this network?** - Shared by all Maple services (Cassandra, Redis, your Go backend) - Enables private communication between services - Service names act as hostnames (e.g., `redis`, `cassandra-1`) - No public exposure - overlay network is internal only ### Step 3: Deploy Redis Stack ```bash # Deploy Redis docker stack deploy -c redis-stack.yml redis # Expected output: # Creating service redis_redis ``` ### Step 4: Verify Deployment ```bash # Check service status docker service ls # Should show: # ID NAME REPLICAS IMAGE # xyz... redis_redis 1/1 redis:7-alpine # Check which node it's running on docker service ps redis_redis # Should show mapleopentech-swarm-worker-1-prod # Watch logs docker service logs -f redis_redis # Should see: "Ready to accept connections" # Press Ctrl+C when done ``` Redis should be up and running in ~10-15 seconds. --- ## Verify Redis Health ### Step 1: Test Redis Connection **SSH to worker-1:** ```bash # Get worker-1's public IP from your .env ssh dockeradmin@ # Get Redis container ID REDIS_CONTAINER=$(docker ps -q --filter "name=redis_redis") # Test connection (replace PASSWORD with your actual password) docker exec -it $REDIS_CONTAINER redis-cli -a YOUR_REDIS_PASSWORD ping # Should return: PONG ``` ### Step 2: Test Basic Operations ```bash # Set a test key docker exec -it $REDIS_CONTAINER redis-cli -a YOUR_REDIS_PASSWORD SET test:key "Hello Redis" # Returns: OK # Get the test key docker exec -it $REDIS_CONTAINER redis-cli -a YOUR_REDIS_PASSWORD GET test:key # Returns: "Hello Redis" # Check Redis info docker exec -it $REDIS_CONTAINER redis-cli -a YOUR_REDIS_PASSWORD INFO server # Shows Redis version, uptime, etc. # Check memory usage docker exec -it $REDIS_CONTAINER redis-cli -a YOUR_REDIS_PASSWORD INFO memory # Shows memory stats ``` --- ## Redis Management ### Restarting Redis ```bash # On manager node docker service update --force redis_redis # Wait for restart (10-15 seconds) docker service ps redis_redis ``` ### Stopping Redis ```bash # Remove Redis stack (data persists in volume) docker stack rm redis # Verify it's stopped docker service ls | grep redis # Should show nothing ``` ### Starting Redis After Stop ```bash # Redeploy the stack cd ~/stacks docker stack deploy -c redis-stack.yml redis # Data is intact from previous volume ``` ### Viewing Logs ```bash # Recent logs docker service logs redis_redis --tail 50 # Follow logs in real-time docker service logs -f redis_redis ``` ### Backing Up Redis Data ```bash # SSH to worker-1 ssh dockeradmin@ # Get container ID REDIS_CONTAINER=$(docker ps -q --filter "name=redis_redis") # Trigger manual save docker exec $REDIS_CONTAINER redis-cli -a YOUR_PASSWORD BGSAVE # Copy RDB file to host docker cp $REDIS_CONTAINER:/data/dump.rdb ~/redis-backup-$(date +%Y%m%d).rdb # Download to local machine (from your local terminal) scp dockeradmin@:~/redis-backup-*.rdb ./ ``` ### Clearing All Data (Dangerous!) Since FLUSHALL is disabled, you need to remove and recreate the volume: ```bash # On manager node docker stack rm redis # Wait for service to stop sleep 10 # SSH to worker-1 ssh dockeradmin@ # Remove volume (THIS DELETES ALL DATA!) docker volume rm redis_redis-data # Exit and redeploy from manager exit docker stack deploy -c redis-stack.yml redis ``` --- ## Troubleshooting ### Problem: Network Not Found During Deployment **Symptom**: `network "mapleopentech-private-prod" is declared as external, but could not be found` **Solution:** Create the shared `mapleopentech-private-prod` network first: ```bash # Create the network docker network create \ --driver overlay \ --attachable \ mapleopentech-private-prod # Verify it exists docker network ls | grep mapleopentech-private-prod # Should show: mapleopentech-private-prod overlay swarm # Then deploy Redis docker stack deploy -c redis-stack.yml redis ``` **Why this happens:** - You haven't completed Step 2 (verify network) - The network was deleted - First time deploying any Maple service **Note**: This network is shared by all services (Cassandra, Redis, backend). You only need to create it once, before deploying your first service. ### Problem: Service Won't Start **Symptom**: `docker service ls` shows `0/1` replicas **Solutions:** 1. **Check logs:** ```bash docker service logs redis_redis --tail 50 ``` 2. **Verify secret exists:** ```bash docker secret ls | grep redis_password # Must show the secret ``` 3. **Check node label:** ```bash docker node inspect mapleopentech-swarm-worker-1-prod --format '{{.Spec.Labels}}' # Must show: map[redis:true] ``` 4. **Verify mapleopentech-private-prod network exists:** ```bash docker network ls | grep mapleopentech-private-prod # Should show: mapleopentech-private-prod overlay swarm ``` ### Problem: Can't Connect (Authentication Failed) **Symptom**: `NOAUTH Authentication required` or `ERR invalid password` **Solutions:** 1. **Verify you're using the correct password:** ```bash # View the secret (from manager node) docker secret inspect redis_password # Compare ID with what you saved ``` 2. **Test with password from secret file:** ```bash # SSH to worker-1 REDIS_CONTAINER=$(docker ps -q --filter "name=redis_redis") docker exec $REDIS_CONTAINER sh -c 'redis-cli -a $(cat /run/secrets/redis_password) ping' # Should return: PONG ``` ### Problem: Container Keeps Restarting **Symptom**: `docker service ps redis_redis` shows multiple restarts **Solutions:** 1. **Check memory:** ```bash # On worker-1 free -h # Should have at least 1GB free ``` 2. **Check logs for errors:** ```bash docker service logs redis_redis # Look for "Out of memory" or permission errors ``` 3. **Verify volume permissions:** ```bash # On worker-1 docker volume inspect redis_redis-data # Check mountpoint permissions ``` ### Problem: Can't Connect from Application **Symptom**: Application can't reach Redis on port 6379 **Solutions:** 1. **Verify both services on same network:** ```bash # Check your app is on mapleopentech-private-prod network docker service inspect your_app --format '{{.Spec.TaskTemplate.Networks}}' # Should show mapleopentech-private-prod ``` 2. **Test DNS resolution:** ```bash # From your app container nslookup redis # Should resolve to Redis container IP ``` 3. **Test connectivity:** ```bash # From your app container (install redis-cli first) redis-cli -h redis -a YOUR_PASSWORD ping ``` ### Problem: Slow Performance **Symptom**: Redis responds slowly or times out **Solutions:** 1. **Check slow log:** ```bash docker exec $(docker ps -q --filter "name=redis_redis") \ redis-cli -a YOUR_PASSWORD SLOWLOG GET 10 ``` 2. **Check memory usage:** ```bash docker exec $(docker ps -q --filter "name=redis_redis") \ redis-cli -a YOUR_PASSWORD INFO memory # Look at used_memory_human and maxmemory_human ``` 3. **Check for evictions:** ```bash docker exec $(docker ps -q --filter "name=redis_redis") \ redis-cli -a YOUR_PASSWORD INFO stats | grep evicted_keys # High number means you need more memory ``` ### Problem: Data Lost After Restart **Symptom**: Data disappears when container restarts **Verification:** ```bash # On worker-1, check if volume exists docker volume ls | grep redis # Should show: redis_redis-data # Check volume is mounted docker inspect $(docker ps -q --filter "name=redis_redis") --format '{{.Mounts}}' # Should show /data mounted to volume ``` **This shouldn't happen** if volume is properly configured. If it does: 1. Check AOF/RDB files exist: `docker exec ls -lh /data/` 2. Check Redis config: `docker exec redis-cli -a PASSWORD CONFIG GET dir` --- ## Next Steps ✅ **You now have:** - Redis instance running on worker-1 - Password-protected access - Persistent data storage (AOF + RDB) - Private network connectivity - Ready for application integration **Next guides:** - **04_app_backend.md** - Deploy your Go backend application - Connect backend to Redis and Cassandra - Set up NGINX reverse proxy --- ## Performance Notes ### Current Setup (2GB RAM Worker) **Capacity:** - 512MB max Redis memory - Suitable for: ~50k-100k small keys - Cache hit rate: Monitor with `INFO stats` - Throughput: ~10,000-50,000 ops/sec **Limitations:** - Single instance (no redundancy) - No Redis Cluster (no automatic sharding) - Limited to 512MB (maxmemory setting) ### Upgrade Path **For Production with High Load:** 1. **Increase memory** (resize worker-1 to 4GB): - Update maxmemory to 2GB - Better for larger datasets 2. **Add Redis replica** (for redundancy): - Deploy second Redis on another worker - Configure replication - High availability with Sentinel 3. **Redis Cluster** (for very high scale): - 3+ worker nodes - Automatic sharding - Handles millions of keys For most applications starting out, **single instance with 512MB is sufficient**. --- **Last Updated**: November 3, 2025 **Maintained By**: Infrastructure Team