feat: add HA infrastructure, CI/CD pipeline, and Redis/Celery hardening
- Add docker-compose.ha.yml for PostgreSQL/Redis HA setup with Patroni and Sentinel - Add docker-compose.prod.yml for production deployment - Add GitHub Actions CI/CD workflow (build.yml) - Add install.cmd for Windows one-click setup - Harden Redis connection with retry logic and health checks - Add Celery HA config with Redis Sentinel support - Add HA operations runbook - Update README with deployment and architecture docs - Move landing page spec to .claude/specs/design/ - Update memory intelligence PRD
This commit is contained in:
310
docker-compose.ha.yml
Normal file
310
docker-compose.ha.yml
Normal file
@@ -0,0 +1,310 @@
|
||||
# docker-compose.ha.yml
|
||||
# HA 覆盖配置(建议与 docker-compose.yml 叠加使用)
|
||||
#
|
||||
# 启动示例:
|
||||
# docker compose -f docker-compose.yml -f docker-compose.ha.yml up -d
|
||||
|
||||
services:
|
||||
# ==============================================
|
||||
# 应用服务 Sentinel 配置覆盖
|
||||
# ==============================================
|
||||
backend:
|
||||
environment:
|
||||
- DATABASE_URL=postgresql+asyncpg://${POSTGRES_USER:-dreamweaver}:${POSTGRES_PASSWORD:-dreamweaver_password}@db:5432/${POSTGRES_DB:-dreamweaver_db}
|
||||
- REDIS_SENTINEL_ENABLED=true
|
||||
- REDIS_SENTINEL_NODES=redis-sentinel-1:26379,redis-sentinel-2:26379,redis-sentinel-3:26379
|
||||
- REDIS_SENTINEL_MASTER_NAME=mymaster
|
||||
- REDIS_SENTINEL_DB=0
|
||||
- REDIS_SENTINEL_SOCKET_TIMEOUT=0.5
|
||||
|
||||
backend-admin:
|
||||
environment:
|
||||
- DATABASE_URL=postgresql+asyncpg://${POSTGRES_USER:-dreamweaver}:${POSTGRES_PASSWORD:-dreamweaver_password}@db:5432/${POSTGRES_DB:-dreamweaver_db}
|
||||
- REDIS_SENTINEL_ENABLED=true
|
||||
- REDIS_SENTINEL_NODES=redis-sentinel-1:26379,redis-sentinel-2:26379,redis-sentinel-3:26379
|
||||
- REDIS_SENTINEL_MASTER_NAME=mymaster
|
||||
- REDIS_SENTINEL_DB=0
|
||||
- REDIS_SENTINEL_SOCKET_TIMEOUT=0.5
|
||||
|
||||
worker:
|
||||
environment:
|
||||
- DATABASE_URL=postgresql+asyncpg://${POSTGRES_USER:-dreamweaver}:${POSTGRES_PASSWORD:-dreamweaver_password}@db:5432/${POSTGRES_DB:-dreamweaver_db}
|
||||
- REDIS_SENTINEL_ENABLED=true
|
||||
- REDIS_SENTINEL_NODES=redis-sentinel-1:26379,redis-sentinel-2:26379,redis-sentinel-3:26379
|
||||
- REDIS_SENTINEL_MASTER_NAME=mymaster
|
||||
- REDIS_SENTINEL_DB=0
|
||||
- REDIS_SENTINEL_SOCKET_TIMEOUT=0.5
|
||||
|
||||
celery-beat:
|
||||
environment:
|
||||
- DATABASE_URL=postgresql+asyncpg://${POSTGRES_USER:-dreamweaver}:${POSTGRES_PASSWORD:-dreamweaver_password}@db:5432/${POSTGRES_DB:-dreamweaver_db}
|
||||
- REDIS_SENTINEL_ENABLED=true
|
||||
- REDIS_SENTINEL_NODES=redis-sentinel-1:26379,redis-sentinel-2:26379,redis-sentinel-3:26379
|
||||
- REDIS_SENTINEL_MASTER_NAME=mymaster
|
||||
- REDIS_SENTINEL_DB=0
|
||||
- REDIS_SENTINEL_SOCKET_TIMEOUT=0.5
|
||||
|
||||
# ==============================================
|
||||
# PostgreSQL 主库(覆盖默认 db)
|
||||
# ==============================================
|
||||
db:
|
||||
image: postgres:15-alpine
|
||||
container_name: dreamweaver_db_primary
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER:-dreamweaver}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-dreamweaver_password}
|
||||
POSTGRES_DB: ${POSTGRES_DB:-dreamweaver_db}
|
||||
command:
|
||||
- postgres
|
||||
- -c
|
||||
- wal_level=replica
|
||||
- -c
|
||||
- max_wal_senders=10
|
||||
- -c
|
||||
- max_replication_slots=10
|
||||
- -c
|
||||
- hot_standby=on
|
||||
- -c
|
||||
- hba_file=/etc/postgresql/pg_hba.conf
|
||||
ports:
|
||||
- "52432:5432"
|
||||
volumes:
|
||||
- postgres_primary_data:/var/lib/postgresql/data
|
||||
- ./ops/postgres-ha/pg_hba.conf:/etc/postgresql/pg_hba.conf:ro
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-dreamweaver} -d ${POSTGRES_DB:-dreamweaver_db}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
# ==============================================
|
||||
# PostgreSQL 从库(基于 pg_basebackup 初始化)
|
||||
# ==============================================
|
||||
db-replica:
|
||||
image: postgres:15-alpine
|
||||
container_name: dreamweaver_db_replica
|
||||
restart: unless-stopped
|
||||
user: "postgres"
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER:-dreamweaver}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-dreamweaver_password}
|
||||
POSTGRES_DB: ${POSTGRES_DB:-dreamweaver_db}
|
||||
PGDATA: /var/lib/postgresql/data
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- postgres_replica_data:/var/lib/postgresql/data
|
||||
command:
|
||||
- /bin/sh
|
||||
- -ec
|
||||
- |
|
||||
if [ ! -s "$$PGDATA/PG_VERSION" ]; then
|
||||
echo "Initializing replica from primary..."
|
||||
until pg_isready -h db -U "$$POSTGRES_USER" -d "$$POSTGRES_DB"; do sleep 2; done
|
||||
export PGPASSWORD="$$POSTGRES_PASSWORD"
|
||||
rm -rf "$$PGDATA"/*
|
||||
pg_basebackup -h db -D "$$PGDATA" -U "$$POSTGRES_USER" -Fp -Xs -P -R
|
||||
fi
|
||||
chmod 700 "$$PGDATA"
|
||||
exec postgres -c hot_standby=on
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD-SHELL",
|
||||
"pg_isready -U ${POSTGRES_USER:-dreamweaver} -d ${POSTGRES_DB:-dreamweaver_db} && psql -U ${POSTGRES_USER:-dreamweaver} -d ${POSTGRES_DB:-dreamweaver_db} -tAc 'select pg_is_in_recovery();' | grep -q t",
|
||||
]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
# ==============================================
|
||||
# PostgreSQL 备份任务(每日一次,保留 7 天)
|
||||
# ==============================================
|
||||
postgres-backup:
|
||||
image: postgres:15-alpine
|
||||
container_name: dreamweaver_postgres_backup
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER:-dreamweaver}
|
||||
POSTGRES_DB: ${POSTGRES_DB:-dreamweaver_db}
|
||||
PGPASSWORD: ${POSTGRES_PASSWORD:-dreamweaver_password}
|
||||
BACKUP_INTERVAL_SECONDS: ${BACKUP_INTERVAL_SECONDS:-86400}
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- postgres_backups:/backups
|
||||
command:
|
||||
- /bin/sh
|
||||
- -ec
|
||||
- |
|
||||
while true; do
|
||||
ts=$$(date +%Y%m%d_%H%M%S);
|
||||
pg_dump -h db -U "$$POSTGRES_USER" -d "$$POSTGRES_DB" -F c -f "/backups/dreamweaver_$${ts}.dump";
|
||||
find /backups -type f -name '*.dump' -mtime +7 -delete;
|
||||
sleep "$$BACKUP_INTERVAL_SECONDS";
|
||||
done
|
||||
|
||||
# ==============================================
|
||||
# Redis 主库(覆盖默认 redis)
|
||||
# ==============================================
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: dreamweaver_redis_master
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "52379:6379"
|
||||
volumes:
|
||||
- redis_master_data:/data
|
||||
command: ["redis-server", "--appendonly", "yes", "--protected-mode", "no"]
|
||||
networks:
|
||||
default:
|
||||
ipv4_address: 172.29.0.10
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
# ==============================================
|
||||
# Redis 从库
|
||||
# ==============================================
|
||||
redis-replica:
|
||||
image: redis:7-alpine
|
||||
container_name: dreamweaver_redis_replica
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- redis_replica_data:/data
|
||||
command:
|
||||
[
|
||||
"redis-server",
|
||||
"--appendonly",
|
||||
"yes",
|
||||
"--protected-mode",
|
||||
"no",
|
||||
"--replicaof",
|
||||
"172.29.0.10",
|
||||
"6379",
|
||||
]
|
||||
networks:
|
||||
default:
|
||||
ipv4_address: 172.29.0.11
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
# ==============================================
|
||||
# Redis Sentinel (3 节点)
|
||||
# ==============================================
|
||||
redis-sentinel-1:
|
||||
image: redis:7-alpine
|
||||
container_name: dreamweaver_redis_sentinel_1
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "52631:26379"
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
redis-replica:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
default:
|
||||
ipv4_address: 172.29.0.21
|
||||
command:
|
||||
- /bin/sh
|
||||
- -ec
|
||||
- |
|
||||
cat > /tmp/sentinel.conf <<EOF
|
||||
port 26379
|
||||
sentinel resolve-hostnames yes
|
||||
sentinel announce-hostnames yes
|
||||
sentinel monitor mymaster 172.29.0.10 6379 2
|
||||
sentinel down-after-milliseconds mymaster 5000
|
||||
sentinel failover-timeout mymaster 10000
|
||||
sentinel parallel-syncs mymaster 1
|
||||
protected-mode no
|
||||
dir /tmp
|
||||
EOF
|
||||
exec redis-sentinel /tmp/sentinel.conf
|
||||
|
||||
redis-sentinel-2:
|
||||
image: redis:7-alpine
|
||||
container_name: dreamweaver_redis_sentinel_2
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "52632:26379"
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
redis-replica:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
default:
|
||||
ipv4_address: 172.29.0.22
|
||||
command:
|
||||
- /bin/sh
|
||||
- -ec
|
||||
- |
|
||||
cat > /tmp/sentinel.conf <<EOF
|
||||
port 26379
|
||||
sentinel resolve-hostnames yes
|
||||
sentinel announce-hostnames yes
|
||||
sentinel monitor mymaster 172.29.0.10 6379 2
|
||||
sentinel down-after-milliseconds mymaster 5000
|
||||
sentinel failover-timeout mymaster 10000
|
||||
sentinel parallel-syncs mymaster 1
|
||||
protected-mode no
|
||||
dir /tmp
|
||||
EOF
|
||||
exec redis-sentinel /tmp/sentinel.conf
|
||||
|
||||
redis-sentinel-3:
|
||||
image: redis:7-alpine
|
||||
container_name: dreamweaver_redis_sentinel_3
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "52633:26379"
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
redis-replica:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
default:
|
||||
ipv4_address: 172.29.0.23
|
||||
command:
|
||||
- /bin/sh
|
||||
- -ec
|
||||
- |
|
||||
cat > /tmp/sentinel.conf <<EOF
|
||||
port 26379
|
||||
sentinel resolve-hostnames yes
|
||||
sentinel announce-hostnames yes
|
||||
sentinel monitor mymaster 172.29.0.10 6379 2
|
||||
sentinel down-after-milliseconds mymaster 5000
|
||||
sentinel failover-timeout mymaster 10000
|
||||
sentinel parallel-syncs mymaster 1
|
||||
protected-mode no
|
||||
dir /tmp
|
||||
EOF
|
||||
exec redis-sentinel /tmp/sentinel.conf
|
||||
|
||||
volumes:
|
||||
postgres_primary_data:
|
||||
postgres_replica_data:
|
||||
postgres_backups:
|
||||
redis_master_data:
|
||||
redis_replica_data:
|
||||
|
||||
networks:
|
||||
default:
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.29.0.0/24
|
||||
Reference in New Issue
Block a user