# 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 <known-good-commit-or-branch> \
  -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@<host>
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@<host>
cd ~/goodgo

# Đặt IMAGE_TAG về một SHA đã biết là tốt
export IMAGE_TAG=<known-good-commit-sha>
export REGISTRY_URL=ghcr.io/<owner>

# 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 <url>`
- [ ] 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
