feat(infra): blue-green deployment via HAProxy
- HAProxy: api/stream/web backends with blue+green servers (backup) - docker-compose.prod: backend-api-blue/green, stream-server-blue/green, web-blue/green - haproxy-blue.cfg, haproxy-green.cfg: config variants for active stack - scripts/deploy-blue-green.sh: switch traffic via config copy + HUP reload
This commit is contained in:
parent
cdc4bd82e6
commit
0ff8a85684
5 changed files with 324 additions and 26 deletions
77
config/haproxy/haproxy-blue.cfg
Normal file
77
config/haproxy/haproxy-blue.cfg
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
# Blue stack active - copy to haproxy.cfg for blue deployment
|
||||
# Generated from haproxy.cfg - blue servers main, green backup
|
||||
global
|
||||
log stdout format raw local0
|
||||
maxconn 4096
|
||||
daemon
|
||||
stats socket /var/run/haproxy.sock level admin
|
||||
|
||||
defaults
|
||||
log global
|
||||
mode http
|
||||
option httplog
|
||||
option dontlognull
|
||||
option forwardfor
|
||||
option http-server-close
|
||||
timeout connect 5000ms
|
||||
timeout client 50000ms
|
||||
timeout server 50000ms
|
||||
timeout http-request 10000ms
|
||||
|
||||
frontend stats
|
||||
bind *:8404
|
||||
stats enable
|
||||
stats uri /stats
|
||||
stats refresh 30s
|
||||
acl from_internal src 127.0.0.1 172.20.0.0/16
|
||||
stats admin if from_internal
|
||||
|
||||
frontend http_frontend
|
||||
bind *:80
|
||||
mode http
|
||||
redirect scheme https code 301 if !{ ssl_fc }
|
||||
acl is_api path_beg /api/v1
|
||||
acl is_stream path_beg /stream
|
||||
acl is_hls path_beg /hls
|
||||
acl is_web path_beg /
|
||||
use_backend backend_api if is_api
|
||||
use_backend stream_ws if is_stream
|
||||
use_backend stream_ws if is_hls
|
||||
use_backend web_frontend if is_web
|
||||
|
||||
frontend https_frontend
|
||||
bind *:443 ssl crt /etc/ssl/veza/veza.pem
|
||||
mode http
|
||||
acl is_api path_beg /api/v1
|
||||
acl is_stream path_beg /stream
|
||||
acl is_hls path_beg /hls
|
||||
acl is_web path_beg /
|
||||
use_backend backend_api if is_api
|
||||
use_backend stream_ws if is_stream
|
||||
use_backend stream_ws if is_hls
|
||||
use_backend web_frontend if is_web
|
||||
|
||||
backend backend_api
|
||||
mode http
|
||||
balance roundrobin
|
||||
option httpchk GET /api/v1/health
|
||||
http-check expect status 200
|
||||
server api_blue backend-api-blue:8080 check inter 5s fall 3 rise 2
|
||||
server api_green backend-api-green:8080 check inter 5s fall 3 rise 2 backup
|
||||
|
||||
backend stream_ws
|
||||
mode http
|
||||
balance roundrobin
|
||||
option httpchk GET /health
|
||||
http-check expect status 200
|
||||
timeout tunnel 3600s
|
||||
server stream_blue stream-server-blue:3001 check inter 5s fall 3 rise 2
|
||||
server stream_green stream-server-green:3001 check inter 5s fall 3 rise 2 backup
|
||||
|
||||
backend web_frontend
|
||||
mode http
|
||||
balance roundrobin
|
||||
option httpchk GET /
|
||||
http-check expect status 200
|
||||
server web_blue web-blue:5173 check inter 5s fall 3 rise 2
|
||||
server web_green web-green:5173 check inter 5s fall 3 rise 2 backup
|
||||
77
config/haproxy/haproxy-green.cfg
Normal file
77
config/haproxy/haproxy-green.cfg
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
# Green stack active - copy to haproxy.cfg for green deployment
|
||||
# Generated from haproxy.cfg - green servers main, blue backup
|
||||
global
|
||||
log stdout format raw local0
|
||||
maxconn 4096
|
||||
daemon
|
||||
stats socket /var/run/haproxy.sock level admin
|
||||
|
||||
defaults
|
||||
log global
|
||||
mode http
|
||||
option httplog
|
||||
option dontlognull
|
||||
option forwardfor
|
||||
option http-server-close
|
||||
timeout connect 5000ms
|
||||
timeout client 50000ms
|
||||
timeout server 50000ms
|
||||
timeout http-request 10000ms
|
||||
|
||||
frontend stats
|
||||
bind *:8404
|
||||
stats enable
|
||||
stats uri /stats
|
||||
stats refresh 30s
|
||||
acl from_internal src 127.0.0.1 172.20.0.0/16
|
||||
stats admin if from_internal
|
||||
|
||||
frontend http_frontend
|
||||
bind *:80
|
||||
mode http
|
||||
redirect scheme https code 301 if !{ ssl_fc }
|
||||
acl is_api path_beg /api/v1
|
||||
acl is_stream path_beg /stream
|
||||
acl is_hls path_beg /hls
|
||||
acl is_web path_beg /
|
||||
use_backend backend_api if is_api
|
||||
use_backend stream_ws if is_stream
|
||||
use_backend stream_ws if is_hls
|
||||
use_backend web_frontend if is_web
|
||||
|
||||
frontend https_frontend
|
||||
bind *:443 ssl crt /etc/ssl/veza/veza.pem
|
||||
mode http
|
||||
acl is_api path_beg /api/v1
|
||||
acl is_stream path_beg /stream
|
||||
acl is_hls path_beg /hls
|
||||
acl is_web path_beg /
|
||||
use_backend backend_api if is_api
|
||||
use_backend stream_ws if is_stream
|
||||
use_backend stream_ws if is_hls
|
||||
use_backend web_frontend if is_web
|
||||
|
||||
backend backend_api
|
||||
mode http
|
||||
balance roundrobin
|
||||
option httpchk GET /api/v1/health
|
||||
http-check expect status 200
|
||||
server api_blue backend-api-blue:8080 check inter 5s fall 3 rise 2 backup
|
||||
server api_green backend-api-green:8080 check inter 5s fall 3 rise 2
|
||||
|
||||
backend stream_ws
|
||||
mode http
|
||||
balance roundrobin
|
||||
option httpchk GET /health
|
||||
http-check expect status 200
|
||||
timeout tunnel 3600s
|
||||
server stream_blue stream-server-blue:3001 check inter 5s fall 3 rise 2 backup
|
||||
server stream_green stream-server-green:3001 check inter 5s fall 3 rise 2
|
||||
|
||||
backend web_frontend
|
||||
mode http
|
||||
balance roundrobin
|
||||
option httpchk GET /
|
||||
http-check expect status 200
|
||||
server web_blue web-blue:5173 check inter 5s fall 3 rise 2 backup
|
||||
server web_green web-green:5173 check inter 5s fall 3 rise 2
|
||||
|
|
@ -2,6 +2,8 @@ global
|
|||
log stdout format raw local0
|
||||
maxconn 4096
|
||||
daemon
|
||||
# Blue-green: runtime API for server enable/disable
|
||||
stats socket /var/run/haproxy.sock level admin
|
||||
|
||||
defaults
|
||||
log global
|
||||
|
|
@ -67,35 +69,34 @@ frontend https_frontend
|
|||
use_backend web_frontend if is_web
|
||||
|
||||
# ============================================================================
|
||||
# BACKENDS
|
||||
# BACKENDS - Blue-Green Deployment
|
||||
# Use scripts/deploy-blue-green.sh to switch active stack
|
||||
# ============================================================================
|
||||
|
||||
# Backend API (Go)
|
||||
# Backend API (Go) - blue/green
|
||||
backend backend_api
|
||||
mode http
|
||||
balance roundrobin
|
||||
option httpchk GET /api/v1/health
|
||||
http-check expect status 200
|
||||
server backend1 backend-api:8080 check inter 5s fall 3 rise 2
|
||||
# Add more servers for load balancing:
|
||||
# server backend2 backend-api-2:8080 check inter 5s fall 3 rise 2
|
||||
server api_blue backend-api-blue:8080 check inter 5s fall 3 rise 2
|
||||
server api_green backend-api-green:8080 check inter 5s fall 3 rise 2 backup
|
||||
|
||||
# Stream WebSocket (Rust)
|
||||
# Stream WebSocket (Rust) - blue/green
|
||||
backend stream_ws
|
||||
mode http
|
||||
balance roundrobin
|
||||
option httpchk GET /health
|
||||
http-check expect status 200
|
||||
server stream1 stream-server:3001 check inter 5s fall 3 rise 2
|
||||
# WebSocket specific options
|
||||
timeout tunnel 3600s
|
||||
server stream_blue stream-server-blue:3001 check inter 5s fall 3 rise 2
|
||||
server stream_green stream-server-green:3001 check inter 5s fall 3 rise 2 backup
|
||||
|
||||
# Web Frontend (React/Vite)
|
||||
# Web Frontend (React/Vite) - blue/green
|
||||
backend web_frontend
|
||||
mode http
|
||||
balance roundrobin
|
||||
option httpchk GET /
|
||||
http-check expect status 200
|
||||
server web1 web:5173 check inter 5s fall 3 rise 2
|
||||
# Add more servers for load balancing:
|
||||
# server web2 web-2:5173 check inter 5s fall 3 rise 2
|
||||
server web_blue web-blue:5173 check inter 5s fall 3 rise 2
|
||||
server web_green web-green:5173 check inter 5s fall 3 rise 2 backup
|
||||
|
|
|
|||
|
|
@ -139,17 +139,19 @@ services:
|
|||
memory: 256M
|
||||
|
||||
# ============================================================================
|
||||
# APPLICATION SERVICES
|
||||
# APPLICATION SERVICES - Blue-Green Deployment
|
||||
# STACK_COLOR=blue|green. Use scripts/deploy-blue-green.sh to switch.
|
||||
# ============================================================================
|
||||
backend-api:
|
||||
backend-api-blue:
|
||||
build:
|
||||
context: ./veza-backend-api
|
||||
dockerfile: Dockerfile.production
|
||||
image: veza-backend-api:latest
|
||||
container_name: veza_backend_api
|
||||
container_name: veza_backend_api_blue
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- APP_ENV=production
|
||||
- STACK_COLOR=blue
|
||||
- DATABASE_URL=postgres://${DB_USER:-veza}:${DB_PASS:?DB_PASS must be set}@postgres:5432/${DB_NAME:-veza}?sslmode=require
|
||||
- REDIS_URL=redis://:${REDIS_PASSWORD:?REDIS_PASSWORD must be set}@redis:6379
|
||||
- AMQP_URL=amqp://${DB_USER:-veza}:${RABBITMQ_PASS:?RABBITMQ_PASS must be set}@rabbitmq:5672
|
||||
|
|
@ -192,14 +194,92 @@ services:
|
|||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
# Chat Server removed in v0.502 -- chat is now handled by backend-api WebSocket at /api/v1/ws
|
||||
backend-api-green:
|
||||
build:
|
||||
context: ./veza-backend-api
|
||||
dockerfile: Dockerfile.production
|
||||
image: veza-backend-api:latest
|
||||
container_name: veza_backend_api_green
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- APP_ENV=production
|
||||
- STACK_COLOR=green
|
||||
- DATABASE_URL=postgres://${DB_USER:-veza}:${DB_PASS:?DB_PASS must be set}@postgres:5432/${DB_NAME:-veza}?sslmode=require
|
||||
- REDIS_URL=redis://:${REDIS_PASSWORD:?REDIS_PASSWORD must be set}@redis:6379
|
||||
- AMQP_URL=amqp://${DB_USER:-veza}:${RABBITMQ_PASS:?RABBITMQ_PASS must be set}@rabbitmq:5672
|
||||
- JWT_SECRET=${JWT_SECRET:?JWT_SECRET must be set for production}
|
||||
- COOKIE_SECURE=true
|
||||
- COOKIE_SAME_SITE=strict
|
||||
- COOKIE_HTTP_ONLY=true
|
||||
- CORS_ALLOWED_ORIGINS=${CORS_ORIGINS:-http://veza.fr}
|
||||
- HYPERSWITCH_URL=http://hyperswitch:8080
|
||||
- HYPERSWITCH_API_KEY=${HYPERSWITCH_API_KEY:-}
|
||||
- HYPERSWITCH_WEBHOOK_SECRET=${HYPERSWITCH_WEBHOOK_SECRET:-}
|
||||
- HYPERSWITCH_ENABLED=${HYPERSWITCH_ENABLED:-false}
|
||||
- CHECKOUT_SUCCESS_URL=${CHECKOUT_SUCCESS_URL:-https://veza.fr/purchases}
|
||||
- ENABLE_CLAMAV=true
|
||||
- CLAMAV_REQUIRED=true
|
||||
- CLAMAV_ADDRESS=clamav:3310
|
||||
- AWS_S3_ENDPOINT=http://minio:9000
|
||||
- AWS_S3_BUCKET=veza-files
|
||||
- AWS_ACCESS_KEY_ID=${S3_ACCESS_KEY:?S3_ACCESS_KEY must be set}
|
||||
- AWS_SECRET_ACCESS_KEY=${S3_SECRET_KEY:?S3_SECRET_KEY must be set}
|
||||
- AWS_REGION=${AWS_REGION:-us-east-1}
|
||||
- HLS_STREAMING=true
|
||||
- HLS_STORAGE_DIR=/data/hls
|
||||
volumes:
|
||||
- hls_prod_data:/data/hls
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
rabbitmq:
|
||||
condition: service_healthy
|
||||
clamav:
|
||||
condition: service_started
|
||||
networks:
|
||||
- veza-network
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8080/api/v1/health"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
stream-server:
|
||||
stream-server-blue:
|
||||
build:
|
||||
context: ./veza-stream-server
|
||||
dockerfile: Dockerfile.production
|
||||
image: veza-stream-server:latest
|
||||
container_name: veza_stream_server
|
||||
container_name: veza_stream_server_blue
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- DATABASE_URL=postgres://${DB_USER:-veza}:${DB_PASS:?DB_PASS must be set}@postgres:5432/${DB_NAME:-veza}?sslmode=require
|
||||
- REDIS_URL=redis://:${REDIS_PASSWORD:?REDIS_PASSWORD must be set}@redis:6379
|
||||
- JWT_SECRET=${JWT_SECRET:?JWT_SECRET must be set}
|
||||
- PORT=3001
|
||||
- HLS_OUTPUT_DIR=/data/hls
|
||||
volumes:
|
||||
- hls_prod_data:/data/hls
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- veza-network
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3001/health"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
stream-server-green:
|
||||
build:
|
||||
context: ./veza-stream-server
|
||||
dockerfile: Dockerfile.production
|
||||
image: veza-stream-server:latest
|
||||
container_name: veza_stream_server_green
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- DATABASE_URL=postgres://${DB_USER:-veza}:${DB_PASS:?DB_PASS must be set}@postgres:5432/${DB_NAME:-veza}?sslmode=require
|
||||
|
|
@ -257,20 +337,42 @@ services:
|
|||
networks:
|
||||
- veza-network
|
||||
|
||||
web:
|
||||
web-blue:
|
||||
build:
|
||||
context: ./apps/web
|
||||
dockerfile: Dockerfile.production
|
||||
image: veza-web:latest
|
||||
container_name: veza_web
|
||||
container_name: veza_web_blue
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- VITE_API_URL=http://haproxy/api/v1
|
||||
- VITE_STREAM_URL=ws://haproxy/stream
|
||||
- VITE_UPLOAD_URL=http://haproxy/api/v1/uploads
|
||||
depends_on:
|
||||
- backend-api
|
||||
- stream-server
|
||||
- backend-api-blue
|
||||
- stream-server-blue
|
||||
networks:
|
||||
- veza-network
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:5173"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
web-green:
|
||||
build:
|
||||
context: ./apps/web
|
||||
dockerfile: Dockerfile.production
|
||||
image: veza-web:latest
|
||||
container_name: veza_web_green
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- VITE_API_URL=http://haproxy/api/v1
|
||||
- VITE_STREAM_URL=ws://haproxy/stream
|
||||
- VITE_UPLOAD_URL=http://haproxy/api/v1/uploads
|
||||
depends_on:
|
||||
- backend-api-green
|
||||
- stream-server-green
|
||||
networks:
|
||||
- veza-network
|
||||
healthcheck:
|
||||
|
|
@ -280,7 +382,7 @@ services:
|
|||
retries: 3
|
||||
|
||||
# ============================================================================
|
||||
# REVERSE PROXY - HAProxy
|
||||
# REVERSE PROXY - HAProxy (Blue-Green)
|
||||
# ============================================================================
|
||||
haproxy:
|
||||
image: haproxy:2.8-alpine
|
||||
|
|
@ -298,9 +400,12 @@ services:
|
|||
- ./config/haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
|
||||
- ./config/ssl:/etc/ssl/veza:ro
|
||||
depends_on:
|
||||
- backend-api
|
||||
- stream-server
|
||||
- web
|
||||
- backend-api-blue
|
||||
- backend-api-green
|
||||
- stream-server-blue
|
||||
- stream-server-green
|
||||
- web-blue
|
||||
- web-green
|
||||
networks:
|
||||
- veza-network
|
||||
healthcheck:
|
||||
|
|
|
|||
38
scripts/deploy-blue-green.sh
Executable file
38
scripts/deploy-blue-green.sh
Executable file
|
|
@ -0,0 +1,38 @@
|
|||
#!/bin/bash
|
||||
# Blue-Green deployment switch for Veza production
|
||||
# Usage: ./scripts/deploy-blue-green.sh [blue|green]
|
||||
# Requires: docker, config/haproxy/ directory, veza_haproxy container running
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
HAPROXY_DIR="$REPO_ROOT/config/haproxy"
|
||||
TARGET="${1:-}"
|
||||
|
||||
if [[ "$TARGET" != "blue" && "$TARGET" != "green" ]]; then
|
||||
echo "Usage: $0 [blue|green]"
|
||||
echo " blue - Route traffic to blue stack (default)"
|
||||
echo " green - Route traffic to green stack"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CONFIG_SOURCE="$HAPROXY_DIR/haproxy-$TARGET.cfg"
|
||||
CONFIG_DEST="$HAPROXY_DIR/haproxy.cfg"
|
||||
|
||||
if [[ ! -f "$CONFIG_SOURCE" ]]; then
|
||||
echo "Error: Config file not found: $CONFIG_SOURCE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Switching to $TARGET stack..."
|
||||
cp "$CONFIG_SOURCE" "$CONFIG_DEST"
|
||||
|
||||
# Reload HAProxy (graceful, no connection drop)
|
||||
if docker ps --format '{{.Names}}' | grep -q '^veza_haproxy$'; then
|
||||
docker kill -s HUP veza_haproxy 2>/dev/null || {
|
||||
echo "Warning: Could not send HUP to HAProxy. Try: docker restart veza_haproxy"
|
||||
}
|
||||
echo "HAProxy reloaded. Active stack: $TARGET"
|
||||
else
|
||||
echo "Warning: veza_haproxy container not running. Config updated; start stack to apply."
|
||||
fi
|
||||
Loading…
Reference in a new issue