Some checks failed
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 29s
CI / E2E Tests (push) Has been skipped
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 2m42s
Deploy / Build Web Image (push) Failing after 27s
Deploy / Build AI Services Image (push) Failing after 29s
E2E Tests / Playwright E2E (push) Failing after 43s
Deploy / Build API Image (push) Failing after 1m31s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 6s
Security Scanning / Trivy Scan — API Image (push) Failing after 5m35s
Security Scanning / Trivy Scan — AI Services Image (push) Failing after 3m45s
Deploy / Deploy to Staging (push) Has been skipped
Deploy / Smoke Test Staging (push) Has been skipped
Deploy / Deploy to Production (push) Has been skipped
Deploy / Smoke Test Production (push) Has been skipped
Deploy / Rollback Staging (push) Has been skipped
Deploy / Rollback Production (push) Has been skipped
Security Scanning / Trivy Scan — Web Image (push) Failing after 13m51s
Security Scanning / Trivy Filesystem Scan (push) Failing after 14m46s
Security Scanning / Security Gate (push) Has been cancelled
1458 lines
50 KiB
Markdown
1458 lines
50 KiB
Markdown
# GoodGo Platform — Sổ Tay Vận Hành Hạ Tầng
|
|
|
|
**Cập Nhật Lần Cuối:** 11 tháng 4, 2026
|
|
**Phiên Bản:** 1.0
|
|
**Mục Đích:** Tài liệu tham chiếu hạ tầng toàn diện dành cho đội vận hành, SRE và kỹ sư trực
|
|
|
|
---
|
|
|
|
## Mục Lục
|
|
|
|
1. [Tóm Tắt Điều Hành](#tóm-tắt-điều-hành)
|
|
2. [Kiến Trúc Dịch Vụ](#kiến-trúc-dịch-vụ)
|
|
3. [Thông Số Docker Compose](#thông-số-docker-compose)
|
|
4. [Tầng Cơ Sở Dữ Liệu](#tầng-cơ-sở-dữ-liệu)
|
|
5. [Bộ Nhớ Đệm & Tìm Kiếm](#bộ-nhớ-đệm--tìm-kiếm)
|
|
6. [Giám Sát & Quan Sát](#giám-sát--quan-sát)
|
|
7. [Tích Hợp Thanh Toán](#tích-hợp-thanh-toán)
|
|
8. [Kiểm Tra Sức Khỏe](#kiểm-tra-sức-khỏe)
|
|
9. [Biến Môi Trường](#biến-môi-trường)
|
|
10. [Sao Lưu & Phục Hồi](#sao-lưu--phục-hồi)
|
|
11. [Quy Trình Triển Khai](#quy-trình-triển-khai)
|
|
12. [Hướng Dẫn Xử Lý Sự Cố](#hướng-dẫn-xử-lý-sự-cố)
|
|
|
|
---
|
|
|
|
## Tóm Tắt Điều Hành
|
|
|
|
**GoodGo Platform** là một monorepo marketplace bất động sản được xây dựng với:
|
|
- **Frontend:** Next.js (TypeScript)
|
|
- **Backend API:** NestJS (TypeScript)
|
|
- **Dịch Vụ AI:** Python/FastAPI
|
|
- **Cơ Sở Dữ Liệu:** PostgreSQL 16 + PostGIS
|
|
- **Bộ Nhớ Đệm:** Redis 7
|
|
- **Tìm Kiếm:** Typesense 27.1
|
|
- **Lưu Trữ Đối Tượng:** MinIO (tương thích S3)
|
|
- **Giám Sát:** Prometheus + Grafana + Loki + Promtail
|
|
- **Hàng Đợi Thông Điệp:** CQRS/Event Bus tích hợp sẵn (NestJS)
|
|
|
|
**Tổng Số Dịch Vụ Trong Sản Xuất:** 12+ (chi tiết bên dưới)
|
|
|
|
---
|
|
|
|
## Kiến Trúc Dịch Vụ
|
|
|
|
### Danh Sách Dịch Vụ
|
|
|
|
| Dịch Vụ | Image | Cổng | Mục Đích | Kiểm Tra Sức Khỏe | Phụ Thuộc |
|
|
|---------|-------|------|---------|--------------|--------------|
|
|
| **api** | `goodgo-api:latest` | 3001 | NestJS REST API | `GET /health` (3x30s) | postgres, redis, typesense, pgbouncer |
|
|
| **web** | `goodgo-web:latest` | 3000 | Frontend Next.js | `GET /` (3x30s) | api |
|
|
| **ai-services** | `goodgo-ai-services:latest` | 8000 | Python FastAPI (ước tính giá, NLP) | `GET /health` (3x30s) | n/a |
|
|
| **postgres** | `postgis/postgis:16-3.4` | 5432 | Cơ sở dữ liệu chính | `pg_isready` (5x10s) | n/a |
|
|
| **pgbouncer** | `edoburu/pgbouncer:1.23.1-p2` | 6432 | Gộp kết nối (chế độ giao dịch) | `pg_isready` (5x10s) | postgres |
|
|
| **redis** | `redis:7-alpine` | 6379 | Bộ nhớ đệm + lưu trữ phiên | `PING` (5x10s) | n/a |
|
|
| **typesense** | `typesense/typesense:27.1` | 8108 | Chỉ mục tìm kiếm toàn văn | `GET /health` (5x10s) | n/a |
|
|
| **minio** | `minio/minio:latest` | 9000/9001 | Lưu trữ đối tượng + bảng điều khiển | `mc ready local` (5x10s) | n/a |
|
|
| **loki** | `grafana/loki:3.0.0` | 3100 | Tổng hợp nhật ký | `GET /ready` (5x15s) | n/a |
|
|
| **promtail** | `grafana/promtail:3.0.0` | 9080 | Trình chuyển nhật ký | (phụ thuộc loki healthy) | loki |
|
|
| **prometheus** | `prom/prometheus:v2.51.0` | 9090 | Thu thập số liệu | `GET /-/healthy` (3x15s) | n/a |
|
|
| **grafana** | `grafana/grafana:10.4.1` | 3002 | Bảng điều khiển + cảnh báo | `GET /api/health` (3x15s) | prometheus, loki |
|
|
| **pg-backup** | `postgis/postgis:16-3.4` | — | Cron sao lưu tự động | depends_on postgres | postgres |
|
|
|
|
### Mạng & Ổ Đĩa
|
|
|
|
- **Mạng:** Mạng bridge Docker `goodgo-net`
|
|
- **Ổ Đĩa:**
|
|
- `pgdata` — Tệp dữ liệu PostgreSQL
|
|
- `redis_data` — Ảnh chụp Redis (AOF)
|
|
- `typesense_data` — Chỉ mục tìm kiếm
|
|
- `minio_data` — Lưu trữ đối tượng
|
|
- `pg_backups` — Bản sao lưu cơ sở dữ liệu (lưu giữ hàng ngày: 7 ngày)
|
|
- `loki_data` — Khối nhật ký (lưu giữ: 15 ngày)
|
|
- `prometheus_data` — TSDB số liệu (lưu giữ: 30 ngày trên prod, 15 ngày trên dev)
|
|
- `grafana_data` — Bảng điều khiển, cấu hình nguồn dữ liệu
|
|
|
|
---
|
|
|
|
## Thông Số Docker Compose
|
|
|
|
### Môi Trường Phát Triển (`docker-compose.yml`)
|
|
|
|
**12 Dịch Vụ (phụ thuộc tối thiểu, không giới hạn tài nguyên)**
|
|
|
|
```yaml
|
|
services:
|
|
postgres: PostGIS 16, port 5432, healthcheck: pg_isready (30s start-period)
|
|
redis: Alpine 7, port 6379, maxmemory: 256mb LRU, AOF enabled
|
|
typesense: v27.1, port 8108, CORS enabled, healthcheck /health
|
|
minio: latest, ports 9000 (API) / 9001 (console)
|
|
ai-services: Custom Python build, port 8000
|
|
pg-backup: Automated daily dumps at 02:00 UTC, cron retention cleanup
|
|
pg-verify-backup: On-demand backup restore verification (profile: tools)
|
|
loki: v3.0.0, port 3100, 15-day retention, 2h compaction delay
|
|
promtail: v3.0.0, Docker socket instrumentation, Pino JSON parsing
|
|
prometheus: v2.51.0, port 9090, 15-day retention, lifecycle API enabled
|
|
grafana: v10.4.1, port 3002, datasources pre-provisioned
|
|
```
|
|
|
|
**Điểm Khác Biệt Chính So Với Prod:**
|
|
- Không giới hạn tài nguyên (dùng toàn bộ CPU/bộ nhớ hiện có)
|
|
- Cửa sổ lưu giữ nhỏ hơn (7-15 ngày)
|
|
- PostgreSQL trên cổng 5432 (trực tiếp, không có pgbouncer)
|
|
- loki/prometheus/grafana trên các cổng thay thế
|
|
|
|
### Môi Trường Sản Xuất (`docker-compose.prod.yml`)
|
|
|
|
**14 Dịch Vụ (có pgbouncer, giới hạn tài nguyên, cập nhật cuốn chiếu)**
|
|
|
|
```yaml
|
|
services:
|
|
api: NestJS, resource limits: 1g CPU / 1g memory
|
|
web: Next.js, resource limits: 0.5 CPU / 512m memory
|
|
ai-services: Python, resource limits: 1.0 CPU / 1g memory
|
|
postgres: PostGIS, resource limits: 2.0 CPU / 2g memory
|
|
pgbouncer: Connection pool (NEW), 20 default connections, transaction mode
|
|
redis: 7-alpine, resource limits: 0.5 CPU / 768m memory, password auth
|
|
typesense: 27.1, resource limits: 1.0 CPU / 1g memory
|
|
minio: latest, resource limits: 0.5 CPU / 1g memory
|
|
loki: v3.0.0, resource limits: 0.5 CPU / 512m memory
|
|
promtail: v3.0.0, resource limits: 0.25 CPU / 256m memory
|
|
prometheus: v2.51.0, resource limits: 0.5 CPU / 1g memory, 30-day retention
|
|
grafana: v10.4.1, resource limits: 0.5 CPU / 512m memory
|
|
pg-backup: Same as dev
|
|
```
|
|
|
|
**Cờ Chỉ Dành Cho Sản Xuất:**
|
|
- `read_only: true` trên các container ứng dụng (api, web, ai-services)
|
|
- `tmpfs: [/tmp]` cho tệp tạm thời khi chạy
|
|
- `security_opt: [no-new-privileges:true]`
|
|
- `logging: json-file` với max-size 10m, xoay vòng 3-5 tệp
|
|
- **PgBouncer chèn giữa ứng dụng ↔ Postgres** (cổng 6432)
|
|
- Quản lý bí mật: `GRAFANA_ADMIN_USER`, `GRAFANA_ADMIN_PASSWORD` từ Docker secrets
|
|
- Redis yêu cầu xác thực bằng mật khẩu
|
|
|
|
### Môi Trường CI/E2E (`docker-compose.ci.yml`)
|
|
|
|
**Tối Giản 4 Dịch Vụ (tmpfs để tăng tốc độ)**
|
|
|
|
```yaml
|
|
services:
|
|
postgres: goodgo_test DB, tmpfs (/var/lib/postgresql/data)
|
|
redis: --save "" --appendonly no (no persistence)
|
|
typesense: tmpfs (/data)
|
|
minio: tmpfs (/data)
|
|
```
|
|
|
|
**Được Sử Dụng Bởi:**
|
|
- Bộ kiểm thử E2E GitHub Actions
|
|
- Lệnh local `docker compose -f docker-compose.ci.yml up --wait`
|
|
|
|
---
|
|
|
|
## Tầng Cơ Sở Dữ Liệu
|
|
|
|
### PostgreSQL + PostGIS
|
|
|
|
**Phiên Bản:** 16.3.4 với extension PostGIS
|
|
**Schema:** 22 mô hình Prisma + theo dõi migration Prisma
|
|
|
|
#### Các Mô Hình Schema Prisma
|
|
|
|
1. **Xác Thực:** User, RefreshToken, OAuthAccount, Agent
|
|
2. **Danh Sách:** Property, PropertyMedia, Listing
|
|
3. **Tìm Kiếm:** SavedSearch
|
|
4. **Giao Dịch:** Transaction, Inquiry, Lead
|
|
5. **Thanh Toán:** Payment (với enum PaymentProvider: VNPAY, MOMO, ZALOPAY, BANK_TRANSFER)
|
|
6. **Đăng Ký:** Plan, Subscription, UsageRecord
|
|
7. **Phân Tích:** Valuation, MarketIndex
|
|
8. **Thông Báo:** NotificationLog, NotificationPreference
|
|
9. **Kiểm Toán:** AdminAuditLog
|
|
10. **Đánh Giá:** Review
|
|
|
|
#### Tính Năng Cơ Sở Dữ Liệu Quan Trọng
|
|
|
|
- **Hình Học PostGIS:** Property.location (Point, SRID 4326) với chỉ mục GIST
|
|
- **Enum:** UserRole, KYCStatus, PropertyType, TransactionType, ListingStatus, Direction, OAuthProvider, TransactionStatus, LeadStatus, PaymentProvider, PaymentStatus, PaymentType, PlanTier, SubscriptionStatus, NotificationChannel, NotificationStatus, AdminAction, AuditTargetType
|
|
- **Chỉ Mục Kép:** Tối ưu hóa truy vấn trên (role, isActive, createdAt), (sellerId, status, publishedAt), (userId, status, createdAt), v.v.
|
|
- **Ràng Buộc:** Khóa idempotency duy nhất trên Payment (userId, provider, idempotencyKey)
|
|
|
|
#### Gộp Kết Nối: PgBouncer
|
|
|
|
**Chế Độ Dev (docker-compose.yml):**
|
|
- Ứng dụng kết nối trực tiếp đến `postgres:5432`
|
|
- Không có overhead gộp kết nối
|
|
|
|
**Chế Độ Prod (docker-compose.prod.yml):**
|
|
- Ứng dụng kết nối đến `pgbouncer:6432`
|
|
- **Chế Độ Pool:** `transaction` (kết nối được trả về sau mỗi giao dịch)
|
|
- **Kích Thước Pool:** 20 kết nối (mặc định, điều chỉnh qua `PGBOUNCER_POOL_SIZE`)
|
|
- **Kết Nối Client Tối Đa:** 200 (điều chỉnh qua `PGBOUNCER_MAX_CLIENT_CONN`)
|
|
- **Pool Dự Phòng:** 5 kết nối (dự phòng khi pool cạn kiệt)
|
|
- **Thời Gian Chờ:**
|
|
- server_connect_timeout: 15s
|
|
- server_idle_timeout: 600s
|
|
- server_lifetime: 3600s (tái sử dụng kết nối)
|
|
- query_wait_timeout: 120s
|
|
- query_timeout: 0 (vô hiệu hóa)
|
|
- **Bảng Điều Khiển Admin:** người dùng pgbouncer_admin (mật khẩu qua biến môi trường PGBOUNCER_ADMIN_PASSWORD)
|
|
- **Bảng Điều Khiển Thống Kê:** người dùng pgbouncer_stats (mật khẩu qua biến môi trường PGBOUNCER_STATS_PASSWORD)
|
|
|
|
**Giải Pháp Thay Thế Cho Migration:**
|
|
- API có hai biến môi trường DATABASE_URL:
|
|
- `DATABASE_URL` → pgbouncer:6432 (truy vấn thông thường)
|
|
- `DATABASE_URL_DIRECT` → postgres:5432 (migration, introspection, DDL)
|
|
- `RUN_MIGRATIONS=true` chuyển ứng dụng sang dùng DATABASE_URL_DIRECT cho `prisma migrate deploy`
|
|
|
|
#### Chiến Lược Sao Lưu
|
|
|
|
**Sao Lưu Tự Động:**
|
|
- **Lịch Trình:** Hàng ngày lúc 02:00 UTC (cron bên trong container pg-backup)
|
|
- **Định Dạng:** Định dạng tùy chỉnh với nén gzip (cấp độ 6)
|
|
- **Lưu Giữ:** 7 ngày (có thể cấu hình qua BACKUP_RETENTION_DAYS)
|
|
- **Vị Trí:** Ổ đĩa `pg_backups` (gắn vào lưu trữ bền vững trên prod)
|
|
- **Mẫu Tệp:** `goodgo_YYYYMMDD_HHMMSS.sql.gz`
|
|
- **Script Khôi Phục:** `/scripts/backup/pg-restore.sh` (khôi phục thủ công)
|
|
- **Script Xác Minh:** `/scripts/backup/pg-verify-backup.sh` (xác minh E2E tự động)
|
|
|
|
**Quy Trình Xác Minh (chạy hàng tuần):**
|
|
1. Khôi phục bản sao lưu mới nhất vào cơ sở dữ liệu kiểm tra riêng biệt (`goodgo_verify_<timestamp>`)
|
|
2. Xác minh tất cả 22 bảng tồn tại
|
|
3. So sánh số lượng hàng giữa cơ sở dữ liệu nguồn và được khôi phục
|
|
4. Tổng kiểm tra các bảng quan trọng (User, Property, Listing, Payment, Subscription, Transaction, Plan, _prisma_migrations)
|
|
5. Kiểm tra extension PostGIS, chỉ mục, loại enum
|
|
6. Tạo báo cáo JSON với kết quả đạt/không đạt
|
|
7. **Dọn Dẹp:** Xóa cơ sở dữ liệu kiểm tra khi thoát (trừ khi SKIP_CLEANUP=1)
|
|
8. **Mã Thoát:** 0=đạt, 1=kiểm tra thất bại, 2=lỗi thiết lập
|
|
|
|
**Xác Minh Sao Lưu CI/CD:**
|
|
- GitHub Action: `.github/workflows/backup-verify.yml`
|
|
- Chạy hàng tuần vào Chủ nhật 05:00 UTC
|
|
- Cũng có thể kích hoạt thủ công với tùy chọn skip_cleanup
|
|
- Tải lên báo cáo JSON dưới dạng artifact
|
|
|
|
---
|
|
|
|
## Bộ Nhớ Đệm & Tìm Kiếm
|
|
|
|
### Redis
|
|
|
|
**Image:** `redis:7-alpine`
|
|
**Cổng:** 6379
|
|
|
|
**Cấu Hình Sản Xuất:**
|
|
```bash
|
|
redis-server \
|
|
--appendonly yes \ # Lưu trữ AOF (chỉ cập nhật)
|
|
--requirepass ${REDIS_PASSWORD} \ # Yêu cầu xác thực
|
|
--maxmemory 512mb \ # Giới hạn bộ nhớ tối đa (prod)
|
|
--maxmemory-policy allkeys-lru # Loại bỏ LRU khi đầy
|
|
```
|
|
|
|
**Cấu Hình Phát Triển:**
|
|
```bash
|
|
redis-server \
|
|
--appendonly yes \
|
|
--maxmemory 256mb \
|
|
--maxmemory-policy allkeys-lru
|
|
```
|
|
|
|
**Cấu Hình Client ioredis:**
|
|
```typescript
|
|
// From RedisService in apps/api/src/modules/shared/infrastructure/redis.service.ts
|
|
{
|
|
host: process.env.REDIS_HOST ?? 'localhost',
|
|
port: Number(process.env.REDIS_PORT ?? 6379),
|
|
password: process.env.REDIS_PASSWORD ?? undefined,
|
|
lazyConnect: true, // Ứng dụng khởi động ngay cả khi Redis không khả dụng
|
|
enableReadyCheck: false, // Ngăn lỗi "Redis is not ready" trong thời gian ngắn bị gián đoạn
|
|
maxRetriesPerRequest: 1, // Thất bại nhanh (thử lại một lần, không có backoff lũy thừa)
|
|
retryStrategy(times: number): number {
|
|
return Math.min(times * 1000, 5000); // 1s → 2s → 3s → 4s → 5s → 5s...
|
|
}
|
|
}
|
|
```
|
|
|
|
**Giảm Thiểu Ưu Ái:**
|
|
- Lỗi cache không làm hỏng ứng dụng
|
|
- CacheService bắt lỗi Redis và trả về cache miss
|
|
- Ứng dụng phục vụ dữ liệu trực tiếp từ PostgreSQL nếu Redis bị ngắt
|
|
- Kiểm tra sức khỏe tại `GET /health/redis` cảnh báo nhưng không làm hỏng readiness probe
|
|
|
|
**Các Trường Hợp Sử Dụng:**
|
|
- Lưu trữ phiên
|
|
- Tầng bộ nhớ đệm cho các truy vấn tốn kém
|
|
- Giới hạn tốc độ (nếu được triển khai)
|
|
- Bộ đếm thời gian thực
|
|
|
|
---
|
|
|
|
### Typesense
|
|
|
|
**Image:** `typesense/typesense:27.1`
|
|
**Cổng:** 8108 (chỉ HTTP, mạng nội bộ Docker)
|
|
**API Key:** `${TYPESENSE_API_KEY}` (phải được đặt trong .env)
|
|
|
|
**Schema Collection:**
|
|
```
|
|
Collection Name: "listings"
|
|
Fields:
|
|
- listingId (string)
|
|
- propertyId (string)
|
|
- title (string, searchable, highlights)
|
|
- description (string, searchable, highlights)
|
|
- propertyType (string, faceted)
|
|
- transactionType (string, faceted: SALE/RENT)
|
|
- priceVND (int64, sortable)
|
|
- pricePerM2 (float, optional)
|
|
- areaM2 (float)
|
|
- bedrooms (int32, faceted)
|
|
- bathrooms (int32, faceted)
|
|
- floors (int32)
|
|
- direction (string, faceted: NORTH/SOUTH/EAST/WEST/etc)
|
|
- address (string)
|
|
- ward (string, faceted)
|
|
- district (string, faceted)
|
|
- city (string, faceted)
|
|
- location (geopoint) — for radius search
|
|
- agentId (string)
|
|
- sellerId (string)
|
|
- status (string, faceted: ACTIVE/SOLD/DRAFT/etc)
|
|
- publishedAt (int64, sortable)
|
|
- viewCount (int32)
|
|
- saveCount (int32)
|
|
- projectName (string, faceted)
|
|
- amenities (string[], faceted)
|
|
```
|
|
|
|
**Tính Năng Tìm Kiếm:**
|
|
- **Tìm kiếm toàn văn** trên: title, description, address, district, city, projectName
|
|
- **Trọng số truy vấn:** title=5, description=3, address=2, district=2, city=1, projectName=2
|
|
- **Lọc:** propertyType, transactionType, bedrooms, district, city, status, amenities
|
|
- **Tìm kiếm địa lý:** truy vấn theo bán kính (lat, lng, km)
|
|
- **Sắp xếp:** giá (tăng/giảm), khoảng cách (tăng từ geopoint), ngày (giảm), độ phù hợp
|
|
- **Đánh dấu:** Đánh dấu HTML trên các thuật ngữ khớp trong title và description
|
|
- **Facets:** Trả về số lượng tổng hợp để lọc
|
|
|
|
**TypesenseSearchRepository (`apps/api/src/modules/search/infrastructure/services/typesense-search.repository.ts`):**
|
|
- `ensureCollection()` — Tạo schema nếu chưa tồn tại
|
|
- `dropCollection()` — Dọn dẹp (chỉ kiểm thử)
|
|
- `indexDocument(doc)` — Upsert một tài liệu
|
|
- `indexDocuments(docs)` — Import hàng loạt với báo cáo lỗi
|
|
- `removeDocument(id)` — Xóa theo ID
|
|
- `search(params)` — Thực hiện tìm kiếm với bộ lọc, sắp xếp, phân trang
|
|
|
|
**Giảm Thiểu Ưu Ái:**
|
|
- Nếu Typesense bị ngắt, tìm kiếm chuyển về tìm kiếm toàn văn PostgreSQL
|
|
- TypesenseClientService triển khai logic thử lại với backoff lũy thừa
|
|
- Kiểm tra sức khỏe tại `GET /health` trả về trạng thái JSON
|
|
|
|
---
|
|
|
|
## Giám Sát & Quan Sát
|
|
|
|
### Prometheus
|
|
|
|
**Image:** `prom/prometheus:v2.51.0`
|
|
**Cổng:** 9090
|
|
**Lưu Giữ:** 15 ngày (dev), 30 ngày (prod)
|
|
**Lifecycle API:** Bật (`--web.enable-lifecycle`)
|
|
|
|
**Mục Tiêu Thu Thập (`monitoring/prometheus/prometheus.yml`):**
|
|
```yaml
|
|
scrape_configs:
|
|
- job_name: goodgo-api
|
|
metrics_path: /metrics
|
|
static_configs:
|
|
- targets: ['host.docker.internal:3001'] # Dev (API on host)
|
|
- targets: ['api:3001'] # Prod (API in container)
|
|
labels:
|
|
service: goodgo-api
|
|
environment: [development|production]
|
|
|
|
- job_name: prometheus
|
|
static_configs:
|
|
- targets: ['localhost:9090']
|
|
```
|
|
|
|
**Số Liệu Dự Kiến Từ API:**
|
|
- `goodgo_api_request_duration_seconds_bucket{le, route, method}` — Histogram độ trễ yêu cầu
|
|
- `http_requests_total{status_code, job}` — Số lượng yêu cầu theo mã trạng thái
|
|
- Số liệu kinh doanh tùy chỉnh (nếu được triển khai trong NestJS @prometheus decorators)
|
|
|
|
### Quy Tắc Cảnh Báo (`monitoring/prometheus/alert-rules.yml`)
|
|
|
|
**Cảnh Báo Độ Trễ:**
|
|
1. **ApiLatencyP99High** (cảnh báo)
|
|
- Kích hoạt: độ trễ p99 > 1s trong 5 phút
|
|
- Bảng điều khiển: `/d/goodgo-api-latency/goodgo-api-latency`
|
|
- Sổ tay: `https://docs.goodgo.vn/runbooks/api-latency-high`
|
|
|
|
2. **ApiEndpointLatencyP99High** (cảnh báo)
|
|
- Kích hoạt: p99 mỗi endpoint > 2s trong 5 phút
|
|
- Chú thích: nhãn method, route
|
|
|
|
3. **ApiLatencyP99Critical** (nghiêm trọng - vi phạm SLO)
|
|
- Kích hoạt: độ trễ p99 > 3s trong 3 phút
|
|
- Yêu cầu leo thang
|
|
- Sổ tay: `https://docs.goodgo.vn/runbooks/api-latency-critical`
|
|
|
|
**Cảnh Báo Tỷ Lệ Lỗi:**
|
|
1. **ApiErrorRate5xxHigh** (cảnh báo)
|
|
- Kích hoạt: tỷ lệ lỗi 5xx > 1% trong 5 phút
|
|
- Sử dụng: `(lỗi 5xx / tổng yêu cầu) * 100`
|
|
|
|
### Grafana
|
|
|
|
**Image:** `grafana/grafana:10.4.1`
|
|
**Cổng:** 3002
|
|
**Xác Thực:** Người dùng/mật khẩu admin từ secrets (prod) hoặc biến môi trường (dev)
|
|
|
|
**Nguồn Dữ Liệu Được Cài Sẵn:**
|
|
- Prometheus (mặc định, chính)
|
|
- Loki (với trường dẫn xuất để liên kết correlationId)
|
|
|
|
**Bảng Điều Khiển:**
|
|
1. `api-latency.json` — API p99/p95/p50, phân tích theo route, endpoint chậm
|
|
2. `api-overview.json` — Tỷ lệ yêu cầu, tỷ lệ lỗi, trạng thái uptime
|
|
3. `database.json` — Độ trễ truy vấn, sử dụng connection pool, truy vấn chậm
|
|
4. `logs.json` — Khối lượng nhật ký, nhật ký lỗi, liên kết trace đến Prometheus
|
|
5. `search.json` — Độ trễ truy vấn Typesense, tỷ lệ lập chỉ mục, kích thước collection
|
|
6. `web-vitals.json` — Core Web Vitals Frontend (nếu có thiết bị đo phía client)
|
|
7. `business-metrics.json` — Danh sách đã tạo, thanh toán đã xử lý, đăng ký người dùng
|
|
|
|
**Truy Cập Bảng Điều Khiển Admin:**
|
|
- URL: `http://localhost:3002` (dev) hoặc `${GRAFANA_PORT}` (prod)
|
|
- Người dùng mặc định: `admin` (thay đổi mật khẩu khi đăng nhập lần đầu)
|
|
- Chế độ không đăng ký (`GF_USERS_ALLOW_SIGN_UP: false`)
|
|
|
|
### Loki & Promtail (Tổng Hợp Nhật Ký)
|
|
|
|
**Loki:** `grafana/loki:3.0.0`, cổng 3100
|
|
|
|
**Cấu Hình:**
|
|
```yaml
|
|
schema:
|
|
- from: 2024-01-01
|
|
store: tsdb
|
|
schema: v13
|
|
limits:
|
|
max_entries_limit_per_query: 5000
|
|
ingestion_rate_mb: 4
|
|
ingestion_burst_size_mb: 6
|
|
retention: 360h (15 days)
|
|
```
|
|
|
|
**Promtail:** `grafana/promtail:3.0.0`
|
|
|
|
**Cấu Hình:**
|
|
- Thu thập nhật ký Docker từ mạng bridge `goodgo-net`
|
|
- Phân tích nhật ký có cấu trúc **Pino JSON**
|
|
- Trích xuất nhãn: level, context, component, service
|
|
- Metadata có cấu trúc: method, url, statusCode, correlationId, duration
|
|
- Lấy timestamp từ đầu ra Pino (RFC3339Nano)
|
|
|
|
**Định Dạng Nhật Ký Dự Kiến (Pino):**
|
|
```json
|
|
{
|
|
"level": 30, // info
|
|
"time": "2026-04-11T10:30:00Z",
|
|
"msg": "POST /api/listings",
|
|
"correlationId": "abc-123-def",
|
|
"context": "ListingController",
|
|
"component": "api",
|
|
"method": "POST",
|
|
"url": "/api/listings",
|
|
"statusCode": 201,
|
|
"duration": 150
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Tích Hợp Thanh Toán
|
|
|
|
### Các Nhà Cung Cấp Thanh Toán Được Hỗ Trợ
|
|
|
|
**Enum:** `PaymentProvider` (Prisma)
|
|
- `VNPAY` — VNPay (cổng thanh toán Việt Nam)
|
|
- `MOMO` — MoMo (ví điện thoại di động Việt Nam)
|
|
- `ZALOPAY` — ZaloPay (ví kỹ thuật số Việt Nam)
|
|
- `BANK_TRANSFER` — Chuyển khoản ngân hàng thủ công (ngoại tuyến)
|
|
|
|
### Luồng Thanh Toán & Xử Lý Callback
|
|
|
|
**Schema Cơ Sở Dữ Liệu (Mô Hình Payment):**
|
|
```typescript
|
|
model Payment {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
transactionId String?
|
|
provider PaymentProvider
|
|
type PaymentType // SUBSCRIPTION, LISTING_FEE, DEPOSIT, FEATURED_LISTING
|
|
amountVND BigInt
|
|
status PaymentStatus // PENDING, PROCESSING, COMPLETED, FAILED, REFUNDED
|
|
providerTxId String? // External transaction ID from VNPay/MoMo/ZaloPay
|
|
callbackData Json? // Raw callback payload (for audit)
|
|
idempotencyKey String? // Prevent duplicate payments (userId, provider, idempotencyKey unique)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
}
|
|
|
|
enum PaymentStatus {
|
|
PENDING, PROCESSING, COMPLETED, FAILED, REFUNDED
|
|
}
|
|
|
|
enum PaymentType {
|
|
SUBSCRIPTION, LISTING_FEE, DEPOSIT, FEATURED_LISTING
|
|
}
|
|
```
|
|
|
|
**Command Handler: `HandleCallbackHandler`**
|
|
(`apps/api/src/modules/payments/application/commands/handle-callback/handle-callback.handler.ts`)
|
|
|
|
1. **Xác Minh Chữ Ký Callback:**
|
|
- Sử dụng `PAYMENT_GATEWAY_FACTORY` để định tuyến đến nhà cung cấp đúng (VNPay/MoMo/ZaloPay)
|
|
- Gateway.verifyCallback() xác thực chữ ký HMAC
|
|
- Ném `ValidationException` nếu chữ ký không hợp lệ
|
|
|
|
2. **Chuyển Đổi Trạng Thái Idempotent:**
|
|
- Chỉ cập nhật các thanh toán ở trạng thái: `PENDING` hoặc `PROCESSING`
|
|
- Chuyển đổi nguyên tử sang `COMPLETED` hoặc `FAILED`
|
|
- Nếu đã ở trạng thái cuối (COMPLETED/FAILED/REFUNDED), trả về trạng thái hiện có (idempotent)
|
|
- Ghi nhật ký cảnh báo nếu không tìm thấy thanh toán
|
|
|
|
3. **Phát Sự Kiện Miền:**
|
|
- Tái tạo thực thể miền từ repository
|
|
- Phát `PaymentCompletedEvent` hoặc `PaymentFailedEvent`
|
|
- Event bus xuất bản sự kiện đến các subscriber (ví dụ: tạo đăng ký, kích hoạt danh sách)
|
|
|
|
4. **Phản Hồi:**
|
|
```typescript
|
|
{
|
|
paymentId: string,
|
|
status: PaymentStatus,
|
|
isSuccess: boolean
|
|
}
|
|
```
|
|
|
|
**Giao Diện Payment Gateway (`payment-gateway.interface.ts`):**
|
|
```typescript
|
|
interface IPaymentGateway {
|
|
readonly provider: PaymentProvider
|
|
createPaymentUrl(params: CreatePaymentUrlParams): Promise<CreatePaymentUrlResult>
|
|
verifyCallback(data: Record<string, string>): CallbackVerifyResult
|
|
refund(params: RefundParams): Promise<RefundResult>
|
|
}
|
|
|
|
interface CreatePaymentUrlParams {
|
|
orderId: string
|
|
amountVND: bigint
|
|
description: string
|
|
returnUrl: string
|
|
ipAddress: string
|
|
}
|
|
|
|
interface CallbackVerifyResult {
|
|
isValid: boolean
|
|
orderId: string
|
|
providerTxId: string
|
|
isSuccess: boolean
|
|
rawData: Record<string, unknown>
|
|
}
|
|
|
|
interface RefundParams {
|
|
providerTxId: string
|
|
amountVND: bigint
|
|
reason: string
|
|
}
|
|
|
|
interface RefundResult {
|
|
success: boolean
|
|
refundTxId: string | null
|
|
}
|
|
```
|
|
|
|
### Biến Môi Trường
|
|
|
|
**VNPay:**
|
|
```env
|
|
VNPAY_TMN_CODE=<merchant terminal code>
|
|
VNPAY_HASH_SECRET=<HMAC secret key>
|
|
VNPAY_BASE_URL=https://sandbox.vnpayment.vn/paymentv2/vpcpay.html
|
|
VNPAY_API_URL=https://sandbox.vnpayment.vn/merchant_webapi/api/transaction
|
|
```
|
|
|
|
**MoMo:**
|
|
```env
|
|
MOMO_PARTNER_CODE=<partner code>
|
|
MOMO_ACCESS_KEY=<access key>
|
|
MOMO_SECRET_KEY=<secret key>
|
|
MOMO_ENDPOINT=https://test-payment.momo.vn/v2/gateway/api
|
|
```
|
|
|
|
**ZaloPay:**
|
|
```env
|
|
ZALOPAY_APP_ID=<app ID>
|
|
ZALOPAY_KEY1=<key 1 (for creating payments)>
|
|
ZALOPAY_KEY2=<key 2 (for callback verification)>
|
|
ZALOPAY_ENDPOINT=https://sb-openapi.zalopay.vn/v2
|
|
```
|
|
|
|
### Bảo Vệ Race Condition & Idempotency
|
|
|
|
**Vấn Đề:** Nhiều callback có thể đến cho cùng một thanh toán (thử lại mạng, thông báo trùng lặp)
|
|
|
|
**Giải Pháp:**
|
|
1. **Khóa Idempotency Duy Nhất:** `Payment_idempotency_unique(userId, provider, idempotencyKey)`
|
|
- Ngăn bản ghi thanh toán trùng lặp
|
|
- Được tạo bởi client/API trước khi tạo thanh toán
|
|
|
|
2. **Cập Nhật Trạng Thái Nguyên Tử:** `paymentRepo.updateIfStatus(orderId, ['PENDING', 'PROCESSING'], newStatus)`
|
|
- Chỉ cập nhật nếu trạng thái hiện tại nằm trong danh sách được phép
|
|
- Trả về thực thể đã cập nhật hoặc null nếu đã ở trạng thái cuối
|
|
|
|
3. **Kiểm Tra Trạng Thái Cuối:** Nếu đã là COMPLETED/FAILED/REFUNDED, handler trả về trạng thái hiện có
|
|
- Không kích hoạt lại sự kiện miền
|
|
- Không tính tiền hai lần hoặc giao dịch trùng lặp
|
|
|
|
---
|
|
|
|
## Kiểm Tra Sức Khỏe
|
|
|
|
### Các Endpoint Sức Khỏe API
|
|
|
|
**Health Controller** (`apps/api/src/modules/health/health.controller.ts`)
|
|
|
|
1. **GET /health** — Liveness Probe (luôn 200 nếu tiến trình đang chạy)
|
|
- Sử dụng: `@HealthCheck()` trên danh sách probe rỗng
|
|
- Phản hồi: `{ "status": "ok", "timestamp": "..." }`
|
|
- **Trường Hợp Sử Dụng:** Readiness Kubernetes/Docker (khởi động ban đầu)
|
|
|
|
2. **GET /health/ready** — Readiness Probe (kiểm tra các phụ thuộc)
|
|
- Kiểm tra: Kết nối PostgreSQL + Redis
|
|
- Phản hồi:
|
|
```json
|
|
{
|
|
"status": "ok",
|
|
"checks": {
|
|
"database": { "status": "up" },
|
|
"redis": { "status": "up" }
|
|
}
|
|
}
|
|
```
|
|
- **Trường Hợp Sử Dụng:** Bộ cân bằng tải, trước khi chấp nhận lưu lượng
|
|
- **Thất Bại:** Trả về 503 nếu bất kỳ phụ thuộc nào bị ngắt
|
|
|
|
3. **GET /health/db** — Chỉ Readiness Cơ Sở Dữ Liệu
|
|
- Kiểm tra: Kết nối PostgreSQL qua truy vấn `SELECT 1`
|
|
- **Trường Hợp Sử Dụng:** Khắc phục sự cố cơ sở dữ liệu thủ công
|
|
|
|
4. **GET /health/redis** — Chỉ Readiness Redis
|
|
- Kiểm tra: Lệnh Redis PING
|
|
- **Trường Hợp Sử Dụng:** Khắc phục sự cố Redis thủ công
|
|
|
|
### Triển Khai Kiểm Tra Sức Khỏe
|
|
|
|
**PrismaHealthIndicator** (`apps/api/src/modules/health/infrastructure/prisma.health.ts`):
|
|
```typescript
|
|
async isHealthy(key: string): Promise<HealthIndicatorResult> {
|
|
try {
|
|
await this.prisma.$queryRawUnsafe('SELECT 1');
|
|
return this.getStatus(key, true);
|
|
} catch {
|
|
throw new HealthCheckError('Database check failed', this.getStatus(key, false));
|
|
}
|
|
}
|
|
```
|
|
|
|
**RedisHealthIndicator** (`apps/api/src/modules/health/infrastructure/redis.health.ts`):
|
|
```typescript
|
|
async isHealthy(key: string): Promise<HealthIndicatorResult> {
|
|
try {
|
|
const client = this.redis.getClient();
|
|
const pong = await client.ping();
|
|
const isHealthy = pong === 'PONG';
|
|
const result = this.getStatus(key, isHealthy);
|
|
if (isHealthy) return result;
|
|
throw new HealthCheckError('Redis ping failed', result);
|
|
} catch (error) {
|
|
if (error instanceof HealthCheckError) throw error;
|
|
throw new HealthCheckError('Redis check failed', this.getStatus(key, false));
|
|
}
|
|
}
|
|
```
|
|
|
|
### Kiểm Tra Sức Khỏe Container Docker
|
|
|
|
**Container API:**
|
|
```yaml
|
|
healthcheck:
|
|
test: ['CMD', 'node', '-e', "fetch('http://localhost:3001/health').then(r => { if (!r.ok) throw 1 }).catch(() => process.exit(1))"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 30s
|
|
```
|
|
|
|
**Container Web:**
|
|
```yaml
|
|
healthcheck:
|
|
test: ['CMD', 'node', '-e', "fetch('http://localhost:3000').then(r => { if (!r.ok) throw 1 }).catch(() => process.exit(1))"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 15s
|
|
```
|
|
|
|
**PostgreSQL:**
|
|
```yaml
|
|
healthcheck:
|
|
test: ['CMD-SHELL', 'pg_isready -U ${DB_USER} -d ${DB_NAME}']
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 30s
|
|
```
|
|
|
|
**Redis:**
|
|
```yaml
|
|
healthcheck:
|
|
test: ['CMD', 'redis-cli', '-a', '${REDIS_PASSWORD}', 'ping']
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 10s
|
|
```
|
|
|
|
**Typesense:**
|
|
```yaml
|
|
healthcheck:
|
|
test: ['CMD', 'curl', '-sf', 'http://localhost:8108/health']
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 15s
|
|
```
|
|
|
|
---
|
|
|
|
## Biến Môi Trường
|
|
|
|
### Tham Chiếu `.env.example` Đầy Đủ
|
|
|
|
**PostgreSQL:**
|
|
```env
|
|
DB_HOST=localhost
|
|
DB_PORT=5432
|
|
DB_NAME=goodgo
|
|
DB_USER=goodgo
|
|
DB_PASSWORD=CHANGE_ME
|
|
DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?schema=public
|
|
DATABASE_URL_DIRECT=postgresql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?schema=public
|
|
```
|
|
|
|
**PgBouncer (Chỉ Prod):**
|
|
```env
|
|
PGBOUNCER_POOL_SIZE=20
|
|
PGBOUNCER_MAX_CLIENT_CONN=200
|
|
PGBOUNCER_ADMIN_PASSWORD=CHANGE_ME
|
|
PGBOUNCER_STATS_PASSWORD=CHANGE_ME
|
|
```
|
|
|
|
**Redis:**
|
|
```env
|
|
REDIS_HOST=localhost
|
|
REDIS_PORT=6379
|
|
REDIS_PASSWORD=
|
|
REDIS_URL=redis://${REDIS_HOST}:${REDIS_PORT}
|
|
```
|
|
|
|
**Typesense:**
|
|
```env
|
|
TYPESENSE_HOST=localhost
|
|
TYPESENSE_PORT=8108
|
|
TYPESENSE_PROTOCOL=http
|
|
TYPESENSE_API_KEY=CHANGE_ME
|
|
```
|
|
|
|
**MinIO:**
|
|
```env
|
|
MINIO_ENDPOINT=localhost
|
|
MINIO_PORT=9000
|
|
MINIO_CONSOLE_PORT=9001
|
|
MINIO_ACCESS_KEY=CHANGE_ME
|
|
MINIO_SECRET_KEY=CHANGE_ME
|
|
MINIO_BUCKET=goodgo-media
|
|
MINIO_USE_SSL=false
|
|
```
|
|
|
|
**NestJS API:**
|
|
```env
|
|
API_PORT=3000
|
|
PORT=3001
|
|
NODE_ENV=development
|
|
CORS_ORIGINS=http://localhost:3000,http://localhost:3001
|
|
```
|
|
|
|
**JWT / Xác Thực (BẮT BUỘC):**
|
|
```env
|
|
JWT_SECRET=<generate with: openssl rand -base64 48>
|
|
JWT_EXPIRES_IN=15m
|
|
JWT_REFRESH_SECRET=<generate with: openssl rand -base64 48>
|
|
JWT_REFRESH_EXPIRES_IN=7d
|
|
```
|
|
|
|
**Nhà Cung Cấp OAuth:**
|
|
```env
|
|
GOOGLE_CLIENT_ID=
|
|
GOOGLE_CLIENT_SECRET=
|
|
GOOGLE_CALLBACK_URL=http://localhost:3001/auth/google/callback
|
|
|
|
ZALO_APP_ID=
|
|
ZALO_APP_SECRET=
|
|
ZALO_CALLBACK_URL=http://localhost:3001/auth/zalo/callback
|
|
|
|
FRONTEND_URL=http://localhost:3000
|
|
```
|
|
|
|
**Next.js Web:**
|
|
```env
|
|
NEXT_PUBLIC_API_URL=http://localhost:3000
|
|
WEB_PORT=3001
|
|
```
|
|
|
|
**Dịch Vụ AI (Python/FastAPI):**
|
|
```env
|
|
AI_SERVICE_PORT=8000
|
|
AI_SERVICE_URL=http://localhost:8000
|
|
CLAUDE_API_KEY=
|
|
AI_DEBUG=false
|
|
AI_LOG_LEVEL=info
|
|
```
|
|
|
|
**Tích Hợp Bản Đồ:**
|
|
```env
|
|
NEXT_PUBLIC_MAPBOX_TOKEN=
|
|
```
|
|
|
|
**Cổng Thanh Toán:**
|
|
```env
|
|
VNPAY_TMN_CODE=
|
|
VNPAY_HASH_SECRET=
|
|
VNPAY_BASE_URL=https://sandbox.vnpayment.vn/paymentv2/vpcpay.html
|
|
VNPAY_API_URL=https://sandbox.vnpayment.vn/merchant_webapi/api/transaction
|
|
|
|
MOMO_PARTNER_CODE=
|
|
MOMO_ACCESS_KEY=
|
|
MOMO_SECRET_KEY=
|
|
MOMO_ENDPOINT=https://test-payment.momo.vn/v2/gateway/api
|
|
|
|
ZALOPAY_APP_ID=
|
|
ZALOPAY_KEY1=
|
|
ZALOPAY_KEY2=
|
|
ZALOPAY_ENDPOINT=https://sb-openapi.zalopay.vn/v2
|
|
```
|
|
|
|
**Email / SMTP:**
|
|
```env
|
|
SMTP_HOST=localhost
|
|
SMTP_PORT=1025
|
|
SMTP_USER=
|
|
SMTP_PASS=
|
|
SMTP_FROM=noreply@goodgo.vn
|
|
```
|
|
|
|
**Firebase Cloud Messaging (Tùy Chọn):**
|
|
```env
|
|
FIREBASE_SERVICE_ACCOUNT=
|
|
```
|
|
|
|
**Theo Dõi Lỗi Sentry:**
|
|
```env
|
|
SENTRY_DSN=
|
|
NEXT_PUBLIC_SENTRY_DSN=
|
|
SENTRY_AUTH_TOKEN=
|
|
SENTRY_ORG=
|
|
SENTRY_PROJECT=
|
|
```
|
|
|
|
**Mã Hóa Trường KYC (BẮT BUỘC Prod):**
|
|
```env
|
|
KYC_ENCRYPTION_KEY=<generate with: openssl rand -hex 32> # 64 hex chars (32 bytes)
|
|
KYC_ENCRYPTION_KEY_VERSION=1
|
|
```
|
|
|
|
**Ghi Nhật Ký:**
|
|
```env
|
|
LOG_LEVEL=info
|
|
```
|
|
|
|
---
|
|
|
|
## Sao Lưu & Phục Hồi
|
|
|
|
### Sao Lưu Tự Động Hàng Ngày
|
|
|
|
**Dịch Vụ:** Container `pg-backup` (chạy bên trong docker compose)
|
|
|
|
**Script Sao Lưu:** `scripts/backup/pg-backup.sh`
|
|
|
|
```bash
|
|
# Daily cron job: 02:00 UTC
|
|
PGHOST=postgres \
|
|
PGPORT=5432 \
|
|
PGUSER=goodgo \
|
|
PGDATABASE=goodgo \
|
|
PGPASSWORD=<secret> \
|
|
BACKUP_DIR=/backups \
|
|
RETENTION_DAYS=7 \
|
|
/scripts/pg-backup.sh
|
|
```
|
|
|
|
**Hành Vi:**
|
|
1. Tạo dump với `pg_dump --format=custom --compress=6`
|
|
2. Lưu dưới dạng `goodgo_YYYYMMDD_HHMMSS.sql.gz`
|
|
3. Xóa các bản sao lưu cũ hơn 7 ngày (có thể cấu hình)
|
|
4. Ghi nhật ký vào `/var/log/pg-backup.log`
|
|
|
|
**Khôi Phục Từ Bản Sao Lưu:**
|
|
|
|
```bash
|
|
# Interactive restore prompt
|
|
docker compose -f docker-compose.prod.yml exec pg-backup bash -c \
|
|
'pg_restore -h postgres -p 5432 -U goodgo -d goodgo \
|
|
--clean --if-exists /backups/goodgo_20260410_020000.sql.gz'
|
|
|
|
# Or using restore script
|
|
docker compose -f docker-compose.prod.yml run --rm pg-verify-backup bash -c \
|
|
'source /scripts/pg-restore.sh /backups/goodgo_20260410_020000.sql.gz'
|
|
```
|
|
|
|
### Xác Minh Sao Lưu
|
|
|
|
**Dịch Vụ:** Container `pg-verify-backup` (theo yêu cầu, profile: tools)
|
|
|
|
**Script Xác Minh:** `scripts/backup/pg-verify-backup.sh`
|
|
|
|
```bash
|
|
# Usage:
|
|
docker compose -f docker-compose.prod.yml run --rm pg-verify-backup
|
|
|
|
# Or with options:
|
|
SKIP_CLEANUP=1 REPORT_FILE=/backups/verify-report.json \
|
|
docker compose -f docker-compose.prod.yml run --rm pg-verify-backup
|
|
```
|
|
|
|
**Các Bước Xác Minh:**
|
|
1. Tạo cơ sở dữ liệu kiểm tra riêng biệt: `goodgo_verify_<timestamp>`
|
|
2. Bật extension PostGIS
|
|
3. Khôi phục bản sao lưu vào cơ sở dữ liệu kiểm tra
|
|
4. Xác minh tất cả 22 bảng tồn tại
|
|
5. So sánh số lượng hàng giữa nguồn và bản được khôi phục
|
|
6. Tổng kiểm tra các bảng quan trọng bằng hash MD5
|
|
7. Kiểm tra chỉ mục, loại enum
|
|
8. Tạo báo cáo JSON với kết quả
|
|
9. **Dọn Dẹp:** Xóa cơ sở dữ liệu kiểm tra (trừ khi SKIP_CLEANUP=1)
|
|
|
|
**Cấu Trúc Báo Cáo JSON:**
|
|
```json
|
|
{
|
|
"timestamp": "2026-04-11T10:30:00Z",
|
|
"backupFile": "/backups/goodgo_20260410_020000.sql.gz",
|
|
"backupSize": "150M",
|
|
"testDatabase": "goodgo_verify_20260411_103000",
|
|
"restoreDurationSeconds": 45,
|
|
"passed": 28,
|
|
"failed": 0,
|
|
"warnings": 2,
|
|
"result": "pass",
|
|
"checks": [
|
|
{ "check": "Database creation", "status": "pass", "detail": "Test database created" },
|
|
{ "check": "Restore", "status": "pass", "detail": "pg_restore completed cleanly in 45s" },
|
|
{ "check": "Table existence", "status": "pass", "detail": "All 22 expected tables present" },
|
|
{ "check": "Row counts", "status": "pass", "detail": "All tables match source database" },
|
|
{ "check": "Checksum: User identities", "status": "pass", "detail": "Hashes match (abc123def456...)" },
|
|
...
|
|
]
|
|
}
|
|
```
|
|
|
|
**Xác Minh Sao Lưu GitHub Action:**
|
|
- Tệp: `.github/workflows/backup-verify.yml`
|
|
- Lịch trình: Hàng tuần vào Chủ nhật 05:00 UTC
|
|
- Cũng có: Kích hoạt thủ công với tùy chọn skip_cleanup
|
|
- Artifacts: Tải lên báo cáo JSON trong 30 ngày
|
|
|
|
---
|
|
|
|
## Quy Trình Triển Khai
|
|
|
|
### CI/CD GitHub Actions
|
|
|
|
**Các Workflow:**
|
|
1. `.github/workflows/ci.yml` — Lint, typecheck, test, build (khi push/PR đến master)
|
|
2. `.github/workflows/deploy.yml` — Build Docker images, triển khai đến staging/prod
|
|
3. `.github/workflows/e2e.yml` — Kiểm thử E2E (khởi chạy toàn bộ docker-compose.ci.yml)
|
|
4. `.github/workflows/backup-verify.yml` — Xác minh sao lưu hàng tuần
|
|
5. `.github/workflows/security.yml` — Quét phụ thuộc, SAST
|
|
6. `.github/workflows/codeql.yml` — Phân tích GitHub CodeQL
|
|
7. `.github/workflows/load-test.yml` — Kiểm thử tải K6
|
|
|
|
### CI Pipeline (`ci.yml`)
|
|
|
|
**Khi:** `push master`, `pull_request master`
|
|
**Node:** 22
|
|
**Đồng Thời:** Hủy các lần chạy trước trên cùng ref
|
|
|
|
**Công Việc:**
|
|
1. **Lint → Typecheck → Test → Build**
|
|
- Cài đặt pnpm, Node 22
|
|
- Chạy linter (eslint)
|
|
- Kiểm tra kiểu (tsc)
|
|
- Kiểm thử đơn vị (jest)
|
|
- Build tất cả ứng dụng (turbo)
|
|
- Dịch vụ PostgreSQL 16 khả dụng (goodgo_test DB)
|
|
|
|
2. **Kiểm Thử E2E** (phụ thuộc vào công việc ci)
|
|
- Toàn bộ dịch vụ docker-compose.ci.yml (postgres, redis, typesense, minio)
|
|
- Chạy bộ kiểm thử end-to-end
|
|
- Thời gian chờ: 20 phút
|
|
- Biến môi trường: DATABASE_URL, JWT secrets, mã kiểm thử thanh toán
|
|
|
|
### Deploy Pipeline (`deploy.yml`)
|
|
|
|
**Khi:**
|
|
- `push master` (tự động triển khai đến staging)
|
|
- `workflow_dispatch` thủ công (chọn staging hoặc production)
|
|
|
|
**Công Việc:**
|
|
1. **Build API Image**
|
|
- Build: `goodgo-api:${IMAGE_TAG}`
|
|
- Dockerfile: `apps/api/Dockerfile`
|
|
- Registry: `ghcr.io/goodgo/goodgo-api`
|
|
- Tags: git SHA, tên nhánh, `latest` (trên master)
|
|
|
|
2. **Build Web Image**
|
|
- Build: `goodgo-web:${IMAGE_TAG}`
|
|
- Dockerfile: `apps/web/Dockerfile`
|
|
- Registry: `ghcr.io/goodgo/goodgo-web`
|
|
|
|
3. **Build AI Services Image**
|
|
- Build: `goodgo-ai-services:${IMAGE_TAG}`
|
|
- Context: `libs/ai-services/`
|
|
- Registry: `ghcr.io/goodgo/goodgo-ai-services`
|
|
|
|
4. **Triển Khai Đến Staging**
|
|
- Điều kiện: `github.event_name == 'push' || inputs.environment == 'staging'`
|
|
- SSH vào máy chủ staging
|
|
- Pull images mới từ GHCR
|
|
- **Cập nhật cuốn chiếu** (không có downtime):
|
|
```bash
|
|
docker compose -f docker-compose.prod.yml up -d --no-deps --wait api
|
|
docker compose -f docker-compose.prod.yml up -d --no-deps --wait web
|
|
docker compose -f docker-compose.prod.yml up -d --no-deps --wait ai-services
|
|
```
|
|
- Chạy migrations: `docker compose exec api npx prisma migrate deploy`
|
|
- Xóa images cũ
|
|
|
|
5. **Triển Khai Đến Production**
|
|
- Chỉ khi `workflow_dispatch` thủ công với `environment: production`
|
|
- Các bước giống staging
|
|
- Yêu cầu phê duyệt `environment: production` (bảo mật GitHub)
|
|
|
|
### Build Docker Multi-Stage
|
|
|
|
**API (apps/api/Dockerfile):**
|
|
- **Base:** node:22-slim + pnpm 10.27.0
|
|
- **Deps:** Cài đặt các phụ thuộc đã khóa (cache layer)
|
|
- **Build:** Biên dịch TypeScript, tạo Prisma client
|
|
- **Prune:** `pnpm deploy --prod` (xóa dev deps, nâng cấp prod deps)
|
|
- **Production:** Image tối giản, dumb-init cho signals, người dùng không phải root
|
|
|
|
**Web (apps/web/Dockerfile):**
|
|
- **Base:** node:22-slim + pnpm
|
|
- **Deps:** Cài đặt phụ thuộc
|
|
- **Build:** `next build` → standalone output + tệp tĩnh
|
|
- **Production:** Sao chép .next/standalone, public, tài sản tĩnh
|
|
|
|
**AI Services (libs/ai-services/Dockerfile):**
|
|
- **Base:** python:3.12-slim
|
|
- **Install:** Phụ thuộc hệ thống (gcc, g++), dumb-init, FastAPI/XGBoost/underthesea
|
|
- **Models:** Tải xuống trước các mô hình ML underthesea tại thời gian build
|
|
- **User:** Chạy với appuser không phải root
|
|
- **CMD:** `uvicorn app.main:app --host 0.0.0.0 --port 8000`
|
|
|
|
---
|
|
|
|
## Hướng Dẫn Xử Lý Sự Cố
|
|
|
|
### Kiểm Tra Trạng Thái Dịch Vụ
|
|
|
|
```bash
|
|
# All services
|
|
docker compose -f docker-compose.prod.yml ps
|
|
|
|
# Single service
|
|
docker compose -f docker-compose.prod.yml ps api
|
|
|
|
# Get logs
|
|
docker compose -f docker-compose.prod.yml logs -f api --tail=100
|
|
|
|
# Health check status
|
|
docker compose -f docker-compose.prod.yml exec api curl http://localhost:3001/health
|
|
```
|
|
|
|
### Các Vấn Đề Phổ Biến
|
|
|
|
#### 1. Dịch Vụ API Không Khỏe Mạnh (bị kẹt ở trạng thái "health-check-failed")
|
|
|
|
**Triệu Chứng:**
|
|
- `docker compose ps` hiển thị `(health: starting)` trong hơn 2 phút
|
|
- `docker compose logs api` hiển thị lỗi kết nối
|
|
|
|
**Chẩn Đoán:**
|
|
```bash
|
|
# Check API liveness
|
|
docker compose exec api curl http://localhost:3001/health
|
|
|
|
# Check readiness (includes DB + Redis checks)
|
|
docker compose exec api curl http://localhost:3001/health/ready
|
|
|
|
# Check specific dependencies
|
|
docker compose exec api curl http://localhost:3001/health/db
|
|
docker compose exec api curl http://localhost:3001/health/redis
|
|
```
|
|
|
|
**Giải Pháp:**
|
|
|
|
- **PostgreSQL chưa sẵn sàng:**
|
|
```bash
|
|
docker compose ps postgres # Should show (healthy)
|
|
docker compose exec postgres pg_isready -U goodgo -d goodgo
|
|
docker compose logs postgres --tail=50
|
|
```
|
|
|
|
- **Redis chưa sẵn sàng:**
|
|
```bash
|
|
docker compose exec redis redis-cli ping # Should return PONG
|
|
docker compose logs redis --tail=50
|
|
```
|
|
|
|
- **PgBouncer chưa sẵn sàng (prod):**
|
|
```bash
|
|
docker compose exec pgbouncer pg_isready -h 127.0.0.1 -p 6432 -U goodgo
|
|
docker compose logs pgbouncer --tail=50
|
|
```
|
|
|
|
- **Schema cơ sở dữ liệu chưa được khởi tạo:**
|
|
```bash
|
|
# Run migrations manually
|
|
docker compose exec api npx prisma migrate deploy
|
|
# Or check if schema exists
|
|
docker compose exec postgres psql -U goodgo -d goodgo -c "\dt"
|
|
```
|
|
|
|
#### 2. Cạn Kiệt Connection Pool Cơ Sở Dữ Liệu Cao
|
|
|
|
**Triệu Chứng:**
|
|
- Lỗi: `Error: unable to get a connection from the pool after X s`
|
|
- Các truy vấn chậm tích lũy
|
|
- Độ trễ API tăng đột biến
|
|
|
|
**Chẩn Đoán:**
|
|
```bash
|
|
# Check pool stats (prod, PgBouncer)
|
|
docker compose exec pgbouncer psql -h 127.0.0.1 -p 6432 -U pgbouncer_stats -c "SHOW stats"
|
|
|
|
# Or query PostgreSQL directly
|
|
docker compose exec postgres psql -U goodgo -d goodgo -c "SELECT count(*) FROM pg_stat_activity"
|
|
```
|
|
|
|
**Giải Pháp:**
|
|
- Tăng `PGBOUNCER_POOL_SIZE` (mặc định: 20)
|
|
- Tăng `PGBOUNCER_MAX_CLIENT_CONN` (mặc định: 200)
|
|
- Giảm các truy vấn chạy dài (thêm thời gian chờ truy vấn)
|
|
- Kiểm tra các kết nối nhàn rỗi: `server_idle_timeout`
|
|
|
|
#### 3. Lỗi Kết Nối Redis (Không Gây Chết)
|
|
|
|
**Triệu Chứng:**
|
|
- Nhật ký: `Redis check failed` hoặc `ECONNREFUSED`
|
|
- Nhưng API vẫn phản hồi với các đọc cơ sở dữ liệu chậm hơn
|
|
- Kiểm tra sức khỏe `/health/ready` trả về 503
|
|
|
|
**Hành Vi Dự Kiến:** Cache miss → ứng dụng phục vụ từ cơ sở dữ liệu
|
|
|
|
**Chẩn Đoán:**
|
|
```bash
|
|
# Check Redis availability
|
|
docker compose exec redis redis-cli ping
|
|
|
|
# Check RedisService logs
|
|
docker compose logs api | grep -i redis
|
|
```
|
|
|
|
**Giải Pháp:**
|
|
- Khởi động lại Redis: `docker compose restart redis`
|
|
- Kiểm tra bộ nhớ: `docker compose exec redis redis-cli info memory`
|
|
- Nếu đạt `maxmemory`, tăng trong docker-compose.yml và khởi động lại
|
|
|
|
#### 4. Typesense Không Lập Chỉ Mục
|
|
|
|
**Triệu Chứng:**
|
|
- Tìm kiếm trả về 0 kết quả
|
|
- Danh sách đã được tạo nhưng không thể tìm kiếm
|
|
- `/health` của typesense hiển thị xanh, nhưng collection trống
|
|
|
|
**Chẩn Đoán:**
|
|
```bash
|
|
# Check collection exists
|
|
curl http://localhost:8108/collections -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}"
|
|
|
|
# Check collection stats
|
|
curl "http://localhost:8108/collections/listings" \
|
|
-H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" | jq .
|
|
|
|
# Check recent docs
|
|
curl "http://localhost:8108/collections/listings/documents/search?q=*" \
|
|
-H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" | jq '.found'
|
|
```
|
|
|
|
**Giải Pháp:**
|
|
- Xác minh `TYPESENSE_API_KEY` khớp với biến môi trường container
|
|
- Lập lại chỉ mục tất cả danh sách:
|
|
```bash
|
|
docker compose exec api npx ts-node scripts/reindex-listings.ts
|
|
```
|
|
- Nếu collection bị hỏng, xóa và tạo lại:
|
|
```bash
|
|
curl -X DELETE "http://localhost:8108/collections/listings" \
|
|
-H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}"
|
|
# Then restart API service to recreate schema
|
|
docker compose restart api
|
|
```
|
|
|
|
#### 5. Lỗi Callback Thanh Toán
|
|
|
|
**Triệu Chứng:**
|
|
- Trạng thái thanh toán bị kẹt ở `PENDING`
|
|
- Nhật ký: `Invalid callback signature for provider=VNPAY`
|
|
|
|
**Chẩn Đoán:**
|
|
```bash
|
|
# Check payment record in DB
|
|
docker compose exec postgres psql -U goodgo -d goodgo -c \
|
|
"SELECT id, status, provider, \"providerTxId\", \"callbackData\" FROM \"Payment\" \
|
|
WHERE \"providerTxId\" = 'your-txid' ORDER BY \"createdAt\" DESC LIMIT 1;"
|
|
|
|
# Check logs for callback handler
|
|
docker compose logs api | grep -i "HandleCallbackHandler\|callback"
|
|
```
|
|
|
|
**Giải Pháp:**
|
|
- Xác minh thông tin xác thực cổng thanh toán (VNPAY_HASH_SECRET, MOMO_SECRET_KEY, v.v.)
|
|
- Xác minh thủ công chữ ký callback (liên hệ hỗ trợ nhà cung cấp thanh toán)
|
|
- Phát lại callback thủ công (nếu có khóa idempotent):
|
|
```bash
|
|
curl -X POST http://localhost:3001/api/payments/callback \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"provider":"VNPAY",...callback data...}'
|
|
```
|
|
|
|
#### 6. Xác Minh Sao Lưu Thất Bại
|
|
|
|
**Triệu Chứng:**
|
|
- GitHub Action `.github/workflows/backup-verify.yml` thất bại
|
|
- Cơ sở dữ liệu kiểm tra khôi phục hiển thị số lượng hàng không khớp
|
|
|
|
**Chẩn Đoán:**
|
|
```bash
|
|
# Run verification manually
|
|
docker compose -f docker-compose.ci.yml up postgres
|
|
docker compose -f docker-compose.ci.yml exec postgres \
|
|
/scripts/pg-verify-backup.sh /backups/goodgo_latest.sql.gz
|
|
|
|
# Check JSON report
|
|
cat /tmp/backups/verify-report.json | jq .
|
|
```
|
|
|
|
**Giải Pháp:**
|
|
- Kiểm tra nếu tệp sao lưu bị hỏng: `file goodgo_*.sql.gz`
|
|
- Xác minh quá trình khôi phục: `pg_restore --verbose`
|
|
- Kiểm tra tính khả dụng của extension PostGIS: `psql -c "CREATE EXTENSION postgis;"`
|
|
|
|
#### 7. Áp Lực Bộ Nhớ/CPU
|
|
|
|
**Triệu Chứng:**
|
|
- OOM kills, container thoát 137
|
|
- CPU throttling, độ trễ tăng đột biến
|
|
- `container_memory_usage_bytes` Prometheus gần giới hạn
|
|
|
|
**Chẩn Đoán:**
|
|
```bash
|
|
# Check Docker stats
|
|
docker stats --no-stream
|
|
|
|
# Check limits in compose file
|
|
docker compose config | grep -A3 "resources:"
|
|
|
|
# Check actual memory usage
|
|
docker inspect goodgo-api | jq '.HostConfig.Memory'
|
|
```
|
|
|
|
**Giải Pháp:**
|
|
- Tăng giới hạn tài nguyên trong `docker-compose.prod.yml`
|
|
- Giảm độ chi tiết nhật ký (đặt LOG_LEVEL=warn)
|
|
- Triển khai phân trang cho các truy vấn lớn
|
|
- Mở rộng theo chiều ngang (thêm nhiều replica API)
|
|
|
|
### Truy Vấn Prometheus Để Gỡ Lỗi
|
|
|
|
```promql
|
|
# API request latency p99
|
|
histogram_quantile(0.99, sum(rate(goodgo_api_request_duration_seconds_bucket[5m])) by (le))
|
|
|
|
# API error rate (5xx)
|
|
(sum(rate(http_requests_total{status_code=~"5.."}[5m])) / sum(rate(http_requests_total[5m]))) * 100
|
|
|
|
# Container memory usage
|
|
container_memory_usage_bytes{name="goodgo-api"}
|
|
|
|
# Container CPU usage
|
|
rate(container_cpu_usage_seconds_total{name="goodgo-api"}[5m])
|
|
|
|
# PostgreSQL active queries
|
|
pg_stat_activity_count{state="active"}
|
|
|
|
# Redis memory usage
|
|
redis_memory_used_bytes / 1024 / 1024 # in MB
|
|
|
|
# Typesense collection size
|
|
typesense_documents_count{collection="listings"}
|
|
```
|
|
|
|
### Thủ Tục Khẩn Cấp
|
|
|
|
**Reset Toàn Bộ Hệ Thống (chỉ dev):**
|
|
```bash
|
|
docker compose down -v # Remove all volumes!
|
|
docker system prune -a
|
|
docker compose up -d --wait
|
|
docker compose exec api npx prisma db push
|
|
docker compose exec api npx ts-node scripts/seed.ts
|
|
```
|
|
|
|
**Khôi Phục Cơ Sở Dữ Liệu Khẩn Cấp:**
|
|
```bash
|
|
# Find latest backup
|
|
ls -t /var/lib/docker/volumes/pg_backups/_data/goodgo_*.sql.gz | head -1
|
|
|
|
# Restore to new database
|
|
pg_restore -h localhost -p 5432 -U goodgo -d goodgo_restored \
|
|
--clean --if-exists --verbose /path/to/backup.sql.gz
|
|
|
|
# Verify restore
|
|
psql -U goodgo -d goodgo_restored -c "SELECT count(*) FROM \"User\";"
|
|
```
|
|
|
|
**Buộc Dừng Dịch Vụ Bị Kẹt:**
|
|
```bash
|
|
# If health check broken
|
|
docker compose kill api
|
|
docker compose rm -f api
|
|
docker compose up -d api
|
|
```
|
|
|
|
---
|
|
|
|
## Phụ Lục: Vị Trí Tệp Quan Trọng
|
|
|
|
```
|
|
/Users/velikho/Desktop/WORKING/goodgo-platform-ai/
|
|
├── docker-compose.yml # Môi trường dev
|
|
├── docker-compose.prod.yml # Môi trường prod (có pgbouncer, giới hạn tài nguyên)
|
|
├── docker-compose.ci.yml # Môi trường kiểm thử CI/E2E
|
|
├── .env.example # Mẫu cho tất cả biến môi trường bắt buộc
|
|
│
|
|
├── apps/
|
|
│ ├── api/
|
|
│ │ ├── Dockerfile # Build NestJS multi-stage
|
|
│ │ ├── docker-entrypoint.sh # Script khởi động (migrations, khởi động ứng dụng)
|
|
│ │ ├── src/
|
|
│ │ │ ├── modules/health/health.controller.ts
|
|
│ │ │ ├── modules/payments/application/commands/handle-callback/
|
|
│ │ │ ├── modules/shared/infrastructure/redis.service.ts
|
|
│ │ │ └── modules/search/infrastructure/services/typesense-search.repository.ts
|
|
│ │ └── package.json
|
|
│ │
|
|
│ └── web/
|
|
│ ├── Dockerfile # Build Next.js multi-stage
|
|
│ └── package.json
|
|
│
|
|
├── libs/
|
|
│ └── ai-services/
|
|
│ ├── Dockerfile # Build Python FastAPI
|
|
│ ├── app/main.py # Điểm vào ứng dụng FastAPI
|
|
│ └── pyproject.toml
|
|
│
|
|
├── prisma/
|
|
│ └── schema.prisma # Schema Prisma đầy đủ (22 mô hình)
|
|
│
|
|
├── infra/
|
|
│ └── pgbouncer/
|
|
│ ├── pgbouncer.ini # Cấu hình connection pooling
|
|
│ ├── userlist.txt.template # Danh sách người dùng (được tạo mẫu)
|
|
│ └── entrypoint.sh # Script thay thế biến môi trường
|
|
│
|
|
├── scripts/
|
|
│ └── backup/
|
|
│ ├── pg-backup.sh # Tự động sao lưu hàng ngày
|
|
│ ├── pg-verify-backup.sh # Xác minh khôi phục
|
|
│ └── pg-restore.sh # Script khôi phục thủ công
|
|
│
|
|
├── monitoring/
|
|
│ ├── prometheus/
|
|
│ │ ├── prometheus.yml # Cấu hình scrape (số liệu goodgo-api)
|
|
│ │ └── alert-rules.yml # Cảnh báo độ trễ + tỷ lệ lỗi
|
|
│ ├── loki/
|
|
│ │ └── loki-config.yml # Cấu hình tổng hợp nhật ký (lưu giữ 15 ngày)
|
|
│ ├── promtail/
|
|
│ │ └── promtail-config.yml # Chuyển nhật ký (phân tích Pino JSON)
|
|
│ └── grafana/
|
|
│ ├── provisioning/
|
|
│ │ ├── datasources/datasource.yml
|
|
│ │ └── dashboards/dashboard.yml
|
|
│ └── dashboards/
|
|
│ ├── api-latency.json
|
|
│ ├── api-overview.json
|
|
│ ├── database.json
|
|
│ ├── logs.json
|
|
│ ├── search.json
|
|
│ ├── web-vitals.json
|
|
│ └── business-metrics.json
|
|
│
|
|
└── .github/workflows/
|
|
├── ci.yml # Lint, test, build
|
|
├── deploy.yml # Build images, triển khai đến staging/prod
|
|
├── e2e.yml # Kiểm thử end-to-end
|
|
├── backup-verify.yml # Xác minh sao lưu hàng tuần
|
|
├── security.yml # Quét phụ thuộc/SAST
|
|
├── codeql.yml # GitHub CodeQL
|
|
└── load-test.yml # Kiểm thử tải K6
|
|
```
|
|
|
|
---
|
|
|
|
## Lịch Sử Phiên Bản Tài Liệu
|
|
|
|
| Phiên Bản | Ngày | Tác Giả | Thay Đổi |
|
|
|---------|------|--------|---------|
|
|
| 1.0 | 2026-04-11 | Đội DevOps | Sổ tay toàn diện ban đầu |
|
|
|
|
---
|
|
|
|
**Cập Nhật Lần Cuối:** 11 tháng 4, 2026
|
|
**Duy Trì Bởi:** Đội GoodGo Platform SRE
|
|
**Liên Hệ:** devops@goodgo.vn
|