# Hướng Dẫn Deployment ## Tổng Quan GoodGo Platform AI gồm bốn dịch vụ có thể deploy: | Dịch vụ | Công nghệ | Port mặc định | |---------|-----------|-------------| | **API** | NestJS (Node.js) | 3001 | | **Web** | Next.js | 3000 | | **AI Services** | FastAPI (Python) | 8000 | | **Infrastructure** | Docker Compose | Khác nhau | ## Yêu Cầu Trước - Docker Engine 24+ & Docker Compose v2 - Node.js 22 LTS - pnpm 10.27+ - Python 3.12 (cho AI services, nếu chạy ngoài Docker) ## Cấu Hình Môi Trường Sao chép `.env.example` thành `.env` và cấu hình tất cả giá trị bắt buộc: ```bash cp .env.example .env ``` ### Biến Bắt Buộc | Biến | Mô tả | Ví dụ | |----------|-------------|---------| | `DATABASE_URL` | Chuỗi kết nối PostgreSQL | `postgresql://user:pass@host:5432/goodgo` | | `JWT_SECRET` | Khóa ký JWT (tối thiểu 32 ký tự) | Tạo bằng `openssl rand -hex 32` | | `JWT_REFRESH_SECRET` | Khóa ký refresh token | Tạo bằng `openssl rand -hex 32` | | `REDIS_URL` | Chuỗi kết nối Redis | `redis://localhost:6379` | | `TYPESENSE_API_KEY` | API key admin Typesense | Tạo một khóa ngẫu nhiên an toàn | ### Biến Tùy Chọn | Biến | Mô tả | Mặc định | |----------|-------------|---------| | `API_PORT` | Port API server | `3000` | | `WEB_PORT` | Port web app | `3001` | | `NODE_ENV` | Chế độ môi trường | `development` | | `CORS_ORIGINS` | Các origin CORS được phép | — | | `CLAUDE_API_KEY` | Claude API key (cho content moderation) | — | | `NEXT_PUBLIC_MAPBOX_TOKEN` | Token Mapbox (cho bản đồ) | — | | `VNPAY_*`, `MOMO_*`, `ZALOPAY_*` | Thông tin payment gateway | — | ## Cài Đặt Hạ Tầng (Docker Compose) Khởi động tất cả dịch vụ hạ tầng: ```bash docker compose up -d ``` Lệnh này khởi động: - **PostgreSQL 16 + PostGIS 3.4** (port 5432) - **Redis 7** (port 6379) - **Typesense 27** (port 8108) - **MinIO** (API: 9000, Console: 9001) - **AI Services** (port 8000) - **pg-backup** — backup PostgreSQL hằng ngày tự động lúc 02:00 UTC, có verify lúc 04:00 UTC - **Loki** (port 3100) — tổng hợp log - **Promtail** — agent thu thập log (chuyển log container đến Loki) - **Prometheus** (port 9090) - **Grafana** (port 3002) — dashboard cho metric và log Kiểm tra tất cả dịch vụ đang khỏe mạnh: ```bash docker compose ps ``` Tất cả dịch vụ đều có health check. Đợi đến khi tất cả hiển thị trạng thái `healthy`. ## Cài Đặt Database ```bash # Sinh Prisma client pnpm db:generate # Áp dụng migration pnpm db:migrate:deploy # Seed dữ liệu khởi tạo (tùy chọn) pnpm db:seed ``` ## Build cho Production ### API (NestJS) ```bash cd apps/api pnpm build ``` Output: `apps/api/dist/` Chạy trong production: ```bash NODE_ENV=production PORT=3001 node apps/api/dist/main.js ``` ### Web (Next.js) ```bash cd apps/web pnpm build ``` Output: `apps/web/.next/` Chạy trong production: ```bash NODE_ENV=production pnpm --filter web start ``` ### AI Services (FastAPI) AI service chạy trong Docker qua `docker compose`. Để build riêng: ```bash cd libs/ai-services docker build -t goodgo-ai-services . docker run -p 8000:8000 --env-file ../../.env goodgo-ai-services ``` ## Checklist Production ### Bảo Mật - [ ] Đặt `JWT_SECRET` và `JWT_REFRESH_SECRET` mạnh, độc nhất (tối thiểu 32 ký tự) - [ ] Đặt `NODE_ENV=production` - [ ] Cấu hình `CORS_ORIGINS` chỉ cho phép domain của bạn - [ ] Đổi mật khẩu database mặc định - [ ] Đổi credential MinIO mặc định (`MINIO_USER`, `MINIO_PASSWORD`) - [ ] Đổi credential Grafana mặc định (`GRAFANA_ADMIN_USER`, `GRAFANA_ADMIN_PASSWORD`) - [ ] Dùng `TYPESENSE_API_KEY` mạnh, độc nhất - [ ] Bật SSL/TLS termination (reverse proxy) - [ ] Đặt `MINIO_USE_SSL=true` nếu MinIO được public ### Database - [ ] Chạy `pnpm db:migrate:deploy` (không dùng `db:migrate:dev`) - [ ] Bật connection pooling cho PostgreSQL (khuyến nghị PgBouncer) - [ ] Cấu hình backup tự động - [ ] Đặt `max_connections` phù hợp trong cấu hình PostgreSQL ### Monitoring - [ ] Xác nhận Prometheus đang scrape endpoint `/metrics` - [ ] Import dashboard Grafana từ `monitoring/grafana/dashboards/` - [ ] Cài đặt rule alerting cho error rate và latency ### Performance - [ ] Cấu hình Redis `maxmemory` và chính sách eviction - [ ] Đặt `--memory-limit` phù hợp cho Typesense - [ ] Bật nén gzip/brotli ở reverse proxy - [ ] Cấu hình CDN cho static asset (Next.js `/_next/static/`) ## Health Check | Dịch vụ | Endpoint | Phản hồi mong đợi | |---------|----------|-------------------| | API | `GET /health` | `{"status": "ok"}` | | API (Swagger) | `GET /api/v1/docs` | Trang Swagger UI | | API (Metrics) | `GET /api/v1/metrics` | Metric Prometheus | | AI Services | `GET /health` | `{"status": "ok"}` | | Typesense | `GET /health` | `{"ok": true}` | | Loki | `GET /ready` | 200 OK | | Redis | `redis-cli ping` | `PONG` | | PostgreSQL | `pg_isready -h host -p 5432` | Exit code 0 | ## Cân Nhắc Về Scaling ### Horizontal Scaling - **API**: Stateless — scale với nhiều instance phía sau load balancer - **Web**: Stateless — scale với nhiều instance hoặc deploy lên Vercel/Cloudflare - **AI Services**: CPU-bound — scale theo lượng yêu cầu định giá - **Redis**: Dùng Redis Cluster cho tính sẵn sàng cao - **PostgreSQL**: Read replica cho workload nhiều truy vấn ### Kiến Trúc Khuyến Nghị (Production) ``` ┌─────────────┐ │ Load Balancer│ │ (nginx/ALB) │ └──────┬──────┘ │ ┌────────────┼────────────┐ │ │ │ ┌─────▼──┐ ┌─────▼──┐ ┌─────▼──┐ │ API #1 │ │ API #2 │ │ API #N │ └────────┘ └────────┘ └────────┘ │ │ │ └────────────┼────────────┘ │ ┌────────────┼────────────┐ │ │ │ ┌─────▼──┐ ┌─────▼──┐ ┌─────▼─────┐ │ PG │ │ Redis │ │ Typesense │ │Primary │ │Cluster │ │ Cluster │ │+ Replica│ │ │ │ │ └────────┘ └────────┘ └────────────┘ ``` ## CI/CD Pipeline ### Chiến Lược Branch | Branch | Đích deploy | Trigger | Ghi chú | |--------|--------------|---------|-------| | `develop` | Staging | Tự động (push) | Mọi merge vào `develop` đều tự deploy lên staging | | `master` | Staging | Tự động (push) | Push master cũng deploy lên staging để verify | | Manual | Staging/Production | `workflow_dispatch` | Trigger thủ công qua GitHub Actions UI | ### Quy Trình Auto-Deploy Staging ``` Push to develop → Build images → Tag rollback → Deploy to staging → Smoke tests → Cleanup / Rollback ``` 1. **Build**: Docker image cho API, Web, và AI Services được build và push lên GHCR với tag `staging-latest` 2. **Tag rollback**: Image hiện đang chạy được tag là `:rollback` trước khi pull image mới 3. **Deploy**: Image mới được pull và dịch vụ được cập nhật qua rolling restart (zero-downtime) 4. **Verify**: Health check poll `$STAGING_URL/health` trong tối đa 100 giây 5. **Smoke test**: `scripts/smoke-test.sh` chạy với staging URL, kiểm tra health probe, các endpoint API cốt lõi, search và auth 6. **Cleanup**: Khi thành công, các tag `:rollback` được xóa và `docker image prune` dọn dẹp các layer cũ 7. **Notify**: Thông báo Slack khi thành công hoặc thất bại 8. **Rollback**: Nếu smoke test thất bại, rollback tự động khôi phục image có tag `:rollback` ### Thông Báo Trạng thái deploy được gửi đến Slack qua secret `SLACK_WEBHOOK_URL`: | Sự kiện | Kênh | Nội dung | |-------|---------|---------| | Smoke test staging pass | Slack | ✅ Commit SHA, branch, link đến run | | Smoke test staging fail | Slack | 🚨 Commit SHA, branch, link đến run | | Trigger rollback staging | Slack | ⚠️ Commit SHA, lý do, link đến run | | Deploy production thành công | Slack | ✅ Commit SHA, branch | | Trigger rollback production | Slack | ⚠️ Commit SHA, lý do, link đến run | ### Secret Bắt Buộc | Secret | Môi trường | Mô tả | |--------|-------------|-------------| | `STAGING_HOST` | staging | Hostname/IP server staging | | `STAGING_USER` | staging | User SSH cho deploy staging | | `STAGING_SSH_KEY` | staging | Khóa SSH private cho staging | | `STAGING_URL` | staging | URL gốc staging (vd: `https://staging.goodgo.vn`) | | `PRODUCTION_HOST` | production | Hostname/IP server production | | `PRODUCTION_USER` | production | User SSH cho deploy production | | `PRODUCTION_SSH_KEY` | production | Khóa SSH private cho production | | `PRODUCTION_URL` | production | URL gốc production | | `SLACK_WEBHOOK_URL` | cả hai | URL incoming webhook Slack | ## Rollback ### Cơ Chế An Toàn Khi Rollback Pipeline deploy sử dụng **tag image `:rollback` rõ ràng** để bảo đảm rollback an toàn. Cách hoạt động như sau: 1. **Trước khi pull image mới**: Image hiện đang chạy được tag là `goodgo-api:rollback`, `goodgo-web:rollback`, và `goodgo-ai-services:rollback` 2. **Sau khi pull image mới**: Dịch vụ được cập nhật với image mới qua rolling restart 3. **Sau khi smoke test pass**: Tag `:rollback` được xóa và `docker image prune` dọn dẹp layer cũ 4. **Nếu smoke test fail**: Image có tag `:rollback` được dùng để khôi phục phiên bản trước Điều này bảo đảm `docker image prune` không bao giờ xóa image cần cho rollback, vì: - Image pruning chỉ xảy ra **sau** khi smoke test pass - Tag `:rollback` giữ image trước được pin lại ngay cả khi pruning vô tình chạy ### Rollback Tự Động (Staging) Pipeline staging có rollback tự động khi smoke test thất bại: 1. **Trước deploy**: Image container hiện tại được tag với hậu tố `:rollback` trước khi pull image mới 2. **Smoke test thất bại**: Nếu `scripts/smoke-test.sh` thoát non-zero, job `rollback-staging` được trigger 3. **Thực hiện rollback**: Container được dừng và khởi động lại bằng image có tag `:rollback` 4. **Verify**: Health check xác nhận rollback đã thành công 5. **Notification**: Slack báo cáo rollback kèm link đến run thất bại ### Rollback Tự Động (Production) Cơ chế giống staging — smoke test thất bại sẽ trigger `rollback-production` dùng image có tag `:rollback`. ### Rollback Thủ Công Để rollback thủ công một deployment staging hoặc production: #### Lựa chọn 1: Re-deploy một commit đã biết là tốt ```bash # Trigger deploy của một commit cụ thể qua GitHub Actions gh workflow run deploy.yml \ --ref \ -f environment=staging ``` #### Lựa chọn 2: SSH rollback dùng tag :rollback (nhanh nhất) ```bash # SSH vào server staging/production ssh deploy@ cd ~/goodgo # Dừng dịch vụ hiện tại docker compose -f docker-compose.prod.yml stop api web ai-services # Xác nhận image :rollback tồn tại docker image inspect goodgo-api:rollback > /dev/null 2>&1 && echo "API rollback available" docker image inspect goodgo-web:rollback > /dev/null 2>&1 && echo "Web rollback available" docker image inspect goodgo-ai-services:rollback > /dev/null 2>&1 && echo "AI rollback available" # Khởi động lại dịch vụ (compose lấy image cache/rollback) docker compose -f docker-compose.prod.yml up -d --wait api web ai-services # Verify health curl -sf http://localhost:3001/health && echo "Rollback successful" ``` > **Lưu ý:** Tag `:rollback` chỉ có sẵn cho đến khi lần deploy thành công kế tiếp dọn chúng đi. Nếu cần rollback về phiên bản cũ hơn, dùng Lựa chọn 3 dưới đây. #### Lựa chọn 3: Pin về một image tag cụ thể ```bash ssh deploy@ cd ~/goodgo # Đặt IMAGE_TAG về một SHA đã biết là tốt export IMAGE_TAG= export REGISTRY_URL=ghcr.io/ # Pull và khởi động lại với tag đã pin docker compose -f docker-compose.prod.yml pull api web ai-services docker compose -f docker-compose.prod.yml up -d --no-deps --wait api web ai-services ``` #### Lựa chọn 4: Dùng deploy-production.sh (rollback tích hợp sẵn) Script deploy thủ công (`scripts/deploy-production.sh`) có hỗ trợ rollback tích hợp: - Tự động tag image `:rollback` trước khi pull - Chạy health check và smoke test - Tự rollback dùng tag `:rollback` nếu một trong hai thất bại - Chỉ prune image sau khi smoke test pass ```bash ssh ubuntu@185.225.232.65 cd ~/goodgo ./scripts/deploy-production.sh [image-tag] ``` ### Rollback Database Prisma không hỗ trợ down migration tự động. Nếu một migration cần được hoàn tác: 1. Xác định migration trong `prisma/migrations/` 2. Viết script rollback SQL thủ công 3. Áp dụng qua `psql` hoặc công cụ migration 4. Cập nhật bảng `_prisma_migrations` Luôn test migration với database staging trước khi deploy production. ### Checklist Sau Rollback - [ ] Xác nhận health endpoint phản hồi: `GET /health`, `GET /ready` - [ ] Chạy smoke test thủ công: `./scripts/smoke-test.sh ` - [ ] Kiểm tra log ứng dụng: `docker compose -f docker-compose.prod.yml logs --tail=100 api web` - [ ] Xác nhận dashboard Grafana hiển thị metric bình thường - [ ] Thông báo cho team qua Slack về rollback và nguyên nhân gốc