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:
zhangtuo
2026-02-28 14:57:02 +08:00
parent 9cdff18336
commit c82d408ea1
14 changed files with 1301 additions and 24 deletions

310
docker-compose.ha.yml Normal file
View 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