Files
goodgo-platform/docs/RUNBOOK.md
Ho Ngoc Hai d8b409a9ab
Some checks failed
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 18s
CI / E2E Tests (push) Has been skipped
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 2m15s
Deploy / Build API Image (push) Failing after 28s
Deploy / Build Web Image (push) Failing after 16s
Deploy / Build AI Services Image (push) Failing after 17s
E2E Tests / Playwright E2E (push) Failing after 31s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 3s
Security Scanning / Trivy Scan — API Image (push) Failing after 1m46s
Security Scanning / Trivy Scan — Web Image (push) Failing after 1m7s
Security Scanning / Trivy Scan — AI Services Image (push) Failing after 53s
Security Scanning / Trivy Filesystem Scan (push) Failing after 35s
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
Security Scanning / Security Gate (push) Failing after 0s
Deploy / Rollback Staging (push) Has been skipped
Deploy / Rollback Production (push) Has been skipped
docs: dịch 22 file Markdown còn lại sang tiếng Việt có dấu (TEC-2881)
Hoàn tất đợt cuối của nhiệm vụ chuyển toàn bộ tài liệu sang tiếng Việt.
Đã dịch 22 file `.md` còn sót (~9.7k dòng) — gồm RUNBOOK, audits,
docs/architecture, docs/load-testing, libs READMEs và các quick references.
Giữ nguyên code blocks, đường dẫn, identifier kỹ thuật, URL và biến môi trường.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-19 03:26:14 +07:00

1227 lines
47 KiB
Markdown

# GoodGo Platform — Runbook Vận hành Production
> **Đối tượng:** SRE on-call, kỹ sư DevOps và người vận hành nền tảng.
> **Cập nhật lần cuối:** 2026-04-11
---
## Mục lục
1. [Danh sách dịch vụ](#1-service-inventory)
2. [Health Checks](#2-health-checks)
3. [Các incident thường gặp](#3-common-incidents)
- [3.1 Cạn kiệt connection pool của database](#31-database-connection-pool-exhaustion)
- [3.2 Lỗi kết nối Redis](#32-redis-connection-failure)
- [3.3 Typesense không khả dụng](#33-typesense-unavailable)
- [3.4 Latency API cao](#34-high-api-latency)
- [3.5 Lỗi callback thanh toán](#35-payment-callback-failures)
- [3.6 Cảnh báo dung lượng đĩa](#36-disk-space-alerts)
- [3.7 Lỗi MinIO / Object Storage](#37-minio--object-storage-failure)
- [3.8 AI Services không khả dụng](#38-ai-services-unavailable)
- [3.9 Lỗi pipeline log (Loki/Promtail)](#39-log-pipeline-failure-lokipromtail)
- [3.10 Tăng đột biến tỷ lệ lỗi 5xx](#310-5xx-error-rate-spike)
4. [Quy trình khôi phục](#4-recovery-procedures)
- [4.1 Khôi phục database từ backup](#41-database-restore-from-backup)
- [4.2 Xóa cache Redis và warm-up](#42-redis-cache-flush--warm-up)
- [4.3 Quy trình rolling restart](#43-rolling-restart-procedures)
- [4.4 Rollback deployment](#44-rollback-deployment)
- [4.5 Reindex Typesense từ PostgreSQL](#45-typesense-reindex-from-postgresql)
- [4.6 Khôi phục toàn bộ host](#46-full-host-recovery)
5. [Ma trận leo thang](#5-escalation-matrix)
6. [Dashboard giám sát](#6-monitoring-dashboards)
7. [Truy vấn PromQL hữu ích](#7-useful-promql-queries)
8. [Tham chiếu nhanh môi trường](#8-environment-quick-reference)
---
## 1. Danh sách dịch vụ
### Dịch vụ Production (`docker-compose.prod.yml`)
| Dịch vụ | Image | Port | Giới hạn tài nguyên | Health Check |
|---------|-------|------|-----------------|--------------|
| **api** (NestJS) | `ghcr.io/goodgo/goodgo-api` | 3001 | 1 CPU / 1 GB | `GET /health` (node fetch) |
| **web** (Next.js) | `ghcr.io/goodgo/goodgo-web` | 3000 | 0.5 CPU / 512 MB | `GET /` (node fetch) |
| **ai-services** (FastAPI) | `ghcr.io/goodgo/goodgo-ai-services` | 8000 | 1 CPU / 1 GB | `GET /health` (httpx) |
| **postgres** | `postgis/postgis:16-3.4` | 5432 (nội bộ) | 2 CPU / 2 GB, shm=256m | `pg_isready` |
| **pgbouncer** | `edoburu/pgbouncer:1.23.1-p2` | 6432 (nội bộ) | 0.5 CPU / 256 MB | `pg_isready -p 6432` |
| **redis** | `redis:7-alpine` | 6379 (nội bộ) | 0.5 CPU / 768 MB | `redis-cli ping` |
| **typesense** | `typesense/typesense:27.1` | 8108 (nội bộ) | 1 CPU / 1 GB | `curl /health` |
| **minio** | `minio/minio:latest` | 9000/9001 (nội bộ) | 0.5 CPU / 1 GB | `mc ready local` |
| **pg-backup** | `postgis/postgis:16-3.4` | — | 0.5 CPU / 512 MB | — (cron daemon) |
| **loki** | `grafana/loki:3.0.0` | 3100 (nội bộ) | 0.5 CPU / 512 MB | `wget /ready` |
| **promtail** | `grafana/promtail:3.0.0` | — | 0.25 CPU / 256 MB | — |
| **prometheus** | `prom/prometheus:v2.51.0` | 9090 (nội bộ) | 0.5 CPU / 1 GB | `wget /-/healthy` |
| **grafana** | `grafana/grafana:10.4.1` | 3002 (bên ngoài) | 0.5 CPU / 512 MB | `wget /api/health` |
| **alertmanager** | `prom/alertmanager:v0.27.0` | 9093 (nội bộ) | 0.25 CPU / 256 MB | `wget /-/healthy` |
### Dịch vụ chỉ dùng cho Development (`docker-compose.yml`)
Môi trường development sử dụng cùng các dịch vụ dữ liệu và giám sát nhưng chạy API/Web trực tiếp trên host. Dịch vụ `pg-backup` cũng chạy trong dev với credential mặc định.
### Chuỗi phụ thuộc giữa các dịch vụ
```
web --> api --> pgbouncer --> postgres
|-> redis
|-> typesense
|-> minio
|-> ai-services
grafana --> prometheus --> alertmanager
|-> loki --> promtail (Docker socket)
pg-backup --> postgres
```
---
## 2. Health Checks
### Các endpoint health của ứng dụng
| Endpoint | Loại | Kiểm tra | Phản hồi mong đợi |
|----------|------|--------|-------------------|
| `GET /health` | Liveness | Tiến trình đang chạy | `200 { status: "ok" }` |
| `GET /health/ready` | Readiness | PostgreSQL + Redis | `200 { status: "ok", info: { database: ..., redis: ... } }` |
| `GET /health/db` | Chỉ database | Kết nối PostgreSQL | `200 { status: "ok", info: { database: ... } }` |
| `GET /health/redis` | Chỉ Redis | Kết nối Redis | `200 { status: "ok", info: { redis: ... } }` |
### Xác minh tất cả dịch vụ đang khỏe mạnh
```bash
# Kiểm tra nhanh — tất cả container
docker compose -f docker-compose.prod.yml ps --format "table {{.Name}}\t{{.Status}}\t{{.Health}}"
# API liveness
curl -sf http://localhost:3001/health && echo "API OK" || echo "API FAIL"
# API readiness (DB + Redis)
curl -sf http://localhost:3001/health/ready | jq .
# Kiểm tra từng phụ thuộc
curl -sf http://localhost:3001/health/db | jq .
curl -sf http://localhost:3001/health/redis | jq .
# Typesense
curl -sf -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" http://localhost:8108/health
# MinIO
docker exec goodgo-minio mc ready local && echo "MinIO OK"
# AI Services
curl -sf http://localhost:8000/health && echo "AI OK" || echo "AI FAIL"
# PostgreSQL (trực tiếp)
docker exec goodgo-postgres pg_isready -U ${DB_USER} -d ${DB_NAME}
# PgBouncer
docker exec goodgo-pgbouncer pg_isready -h 127.0.0.1 -p 6432 -U ${DB_USER}
# Redis
docker exec goodgo-redis redis-cli -a "${REDIS_PASSWORD}" ping
# Prometheus
curl -sf http://localhost:9090/-/healthy && echo "Prometheus OK"
# Loki
curl -sf http://localhost:3100/ready && echo "Loki OK"
# Grafana
curl -sf http://localhost:3002/api/health | jq .
# Alertmanager
curl -sf http://localhost:9093/-/healthy && echo "Alertmanager OK"
```
### Tiêu thụ tài nguyên của container
```bash
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}"
```
---
## 3. Các incident thường gặp
### 3.1 Cạn kiệt connection pool của database
**Triệu chứng:**
- API trả về 503 hoặc treo khi xử lý request
- `/health/ready` trả về unhealthy cho `database`
- Log PgBouncer: `no more connections allowed` hoặc `query_wait_timeout`
- Prometheus: tăng đột biến số kết nối active trong `pg_stat_activity`
**Chẩn đoán:**
```bash
# Kiểm tra trạng thái pool của PgBouncer
docker exec goodgo-pgbouncer psql -h 127.0.0.1 -p 6432 -U pgbouncer_admin pgbouncer -c "SHOW POOLS;"
docker exec goodgo-pgbouncer psql -h 127.0.0.1 -p 6432 -U pgbouncer_admin pgbouncer -c "SHOW CLIENTS;"
docker exec goodgo-pgbouncer psql -h 127.0.0.1 -p 6432 -U pgbouncer_admin pgbouncer -c "SHOW STATS;"
# Kiểm tra số kết nối active của PostgreSQL
docker exec goodgo-postgres psql -U ${DB_USER} -d ${DB_NAME} -c \
"SELECT state, count(*) FROM pg_stat_activity WHERE datname = '${DB_NAME}' GROUP BY state;"
# Xác định các truy vấn chạy lâu
docker exec goodgo-postgres psql -U ${DB_USER} -d ${DB_NAME} -c \
"SELECT pid, now() - pg_stat_activity.query_start AS duration, query, state
FROM pg_stat_activity
WHERE datname = '${DB_NAME}' AND state != 'idle'
ORDER BY duration DESC
LIMIT 10;"
```
**Cách xử lý:**
```bash
# 1. Hủy các truy vấn chạy lâu (> 5 phút)
docker exec goodgo-postgres psql -U ${DB_USER} -d ${DB_NAME} -c \
"SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE datname = '${DB_NAME}'
AND state != 'idle'
AND now() - query_start > interval '5 minutes'
AND pid <> pg_backend_pid();"
# 2. Nếu pool đã cạn kiệt hoàn toàn, restart PgBouncer
docker compose -f docker-compose.prod.yml restart pgbouncer
# 3. Nếu vấn đề vẫn còn, tạm thời tăng pool size
# Chỉnh sửa PGBOUNCER_POOL_SIZE trong .env, sau đó:
docker compose -f docker-compose.prod.yml up -d --no-deps pgbouncer
```
**Tham chiếu cấu hình PgBouncer:**
- Pool mode: `transaction` (kết nối được trả về pool sau mỗi transaction)
- Pool size mặc định: 20 server connection cho mỗi cặp user/db
- Số client connection tối đa: 200
- Reserve pool: 5 kết nối dự phòng (sau khi chờ 3s)
- Query wait timeout: 120s (báo lỗi nếu client chờ lâu hơn)
### 3.2 Lỗi kết nối Redis
**Triệu chứng:**
- `/health/redis` trả về unhealthy
- Thời gian phản hồi API tăng (cache miss đánh vào DB)
- Log API hiển thị lỗi kết nối Redis
**Chẩn đoán:**
```bash
# Kiểm tra container Redis
docker logs --tail=50 goodgo-redis
# Kiểm tra kết nối
docker exec goodgo-redis redis-cli -a "${REDIS_PASSWORD}" ping
docker exec goodgo-redis redis-cli -a "${REDIS_PASSWORD}" INFO server
docker exec goodgo-redis redis-cli -a "${REDIS_PASSWORD}" INFO memory
docker exec goodgo-redis redis-cli -a "${REDIS_PASSWORD}" INFO clients
```
**Cách xử lý:**
```bash
# 1. Restart Redis (dữ liệu được lưu qua AOF)
docker compose -f docker-compose.prod.yml restart redis
# 2. Nếu OOM — kiểm tra sử dụng bộ nhớ
docker exec goodgo-redis redis-cli -a "${REDIS_PASSWORD}" INFO memory | grep used_memory_human
# Bộ nhớ tối đa là 512 MB (prod), eviction policy: allkeys-lru
# 3. Nếu AOF bị hỏng
docker compose -f docker-compose.prod.yml stop redis
docker exec goodgo-redis redis-check-aof --fix /data/appendonly.aof
docker compose -f docker-compose.prod.yml start redis
```
**Graceful Degradation:** API được thiết kế để tiếp tục hoạt động khi Redis không khả dụng. Các cache miss sẽ đi thẳng xuống PostgreSQL. Hiệu năng sẽ giảm nhưng chức năng vẫn được bảo đảm. Redis không quan trọng đối với các hoạt động cốt lõi.
### 3.3 Typesense không khả dụng
**Triệu chứng:**
- Chức năng tìm kiếm trả về lỗi hoặc fallback về tìm kiếm DB cơ bản
- `curl http://localhost:8108/health` thất bại
- Log API hiển thị timeout kết nối Typesense
**Chẩn đoán:**
```bash
# Kiểm tra trạng thái container
docker logs --tail=50 goodgo-typesense
# Kiểm tra health
curl -sf -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" http://localhost:8108/health
# Kiểm tra collection
curl -sf -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" http://localhost:8108/collections | jq .
# Kiểm tra dung lượng đĩa cho volume dữ liệu Typesense
docker system df -v | grep typesense
```
**Cách xử lý:**
```bash
# 1. Restart Typesense
docker compose -f docker-compose.prod.yml restart typesense
# 2. Nếu dữ liệu bị hỏng — rebuild từ PostgreSQL
docker compose -f docker-compose.prod.yml stop typesense
docker volume rm goodgo-platform-ai_typesense_data
docker compose -f docker-compose.prod.yml up -d typesense
# Chờ tới khi healthy, sau đó reindex:
docker compose -f docker-compose.prod.yml exec api npx ts-node scripts/typesense-reindex.ts
# Hoặc: pnpm run typesense:reindex
# 3. Nếu có backup volume — khôi phục
docker compose -f docker-compose.prod.yml stop typesense
docker run --rm -v goodgo-platform-ai_typesense_data:/data -v $(pwd)/backups:/backup \
alpine sh -c "rm -rf /data/* && tar xzf /backup/typesense_YYYYMMDD_HHMMSS.tar.gz -C /data"
docker compose -f docker-compose.prod.yml start typesense
```
**Hành vi Fallback:** Khi Typesense không khả dụng, tìm kiếm bất động sản sẽ fallback về PostgreSQL full-text search kết hợp với truy vấn geo PostGIS. Chất lượng tìm kiếm giảm nhưng chức năng cốt lõi vẫn hoạt động.
### 3.4 Latency API cao
**Triệu chứng:**
- Alert Prometheus `ApiLatencyP99High` kích hoạt (p99 > 1s trong 5 phút)
- Alert critical `ApiLatencyP99Critical` kích hoạt (p99 > 3s trong 3 phút — vi phạm SLO)
- Người dùng báo tải trang chậm
**Chẩn đoán:**
```bash
# 1. Kiểm tra endpoint nào đang chậm
# Grafana: dashboard GoodGo API Latency
# Hoặc qua PromQL:
curl -s "http://localhost:9090/api/v1/query" --data-urlencode \
'query=topk(10, histogram_quantile(0.99, sum(rate(goodgo_api_request_duration_seconds_bucket[5m])) by (le, route, method)))' \
| jq '.data.result[] | {route: .metric.route, method: .metric.method, p99: .value[1]}'
# 2. Kiểm tra slow query của database
docker exec goodgo-postgres psql -U ${DB_USER} -d ${DB_NAME} -c \
"SELECT pid, now() - query_start AS duration, left(query, 100) AS query_preview
FROM pg_stat_activity
WHERE state = 'active' AND now() - query_start > interval '1 second'
ORDER BY duration DESC;"
# 3. Kiểm tra thời gian chờ của PgBouncer
docker exec goodgo-pgbouncer psql -h 127.0.0.1 -p 6432 -U pgbouncer_admin pgbouncer -c "SHOW POOLS;"
# 4. Kiểm tra sử dụng tài nguyên container
docker stats --no-stream goodgo-api goodgo-postgres goodgo-redis goodgo-pgbouncer
# 5. Kiểm tra latency Redis
docker exec goodgo-redis redis-cli -a "${REDIS_PASSWORD}" --latency-history -i 3
# 6. Kiểm tra log ứng dụng để tìm lỗi
docker logs --tail=200 --since=5m goodgo-api 2>&1 | grep -i "error\|timeout\|slow"
```
**Cách xử lý:**
```bash
# 1. Nếu có slow query DB — terminate chúng
docker exec goodgo-postgres psql -U ${DB_USER} -d ${DB_NAME} -c \
"SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'active' AND now() - query_start > interval '30 seconds';"
# 2. Nếu connection pool cạn kiệt — xem Mục 3.1
# 3. Nếu Redis chậm — restart
docker compose -f docker-compose.prod.yml restart redis
# 4. Nếu container API bị OOM — restart với nhiều memory hơn
docker compose -f docker-compose.prod.yml restart api
# 5. Nếu một endpoint cụ thể là bottleneck — kiểm tra log Loki:
# Grafana > Explore > Loki > {container_name="goodgo-api"} |= "slow"
```
### 3.5 Lỗi callback thanh toán
**Triệu chứng:**
- Người dùng báo thanh toán bị kẹt ở trạng thái "pending"
- Callback IPN của VNPay/MoMo/ZaloPay trả về lỗi
- Không khớp đối soát thanh toán
**Chẩn đoán:**
```bash
# 1. Kiểm tra log callback thanh toán
docker logs goodgo-api 2>&1 | grep -i "payment\|callback\|vnpay\|momo\|zalopay" | tail -50
# 2. Kiểm tra các thanh toán pending trong DB
docker exec goodgo-postgres psql -U ${DB_USER} -d ${DB_NAME} -c \
"SELECT id, provider, status, \"amountVND\", \"createdAt\"
FROM \"Payment\"
WHERE status = 'PENDING'
AND \"createdAt\" > now() - interval '24 hours'
ORDER BY \"createdAt\" DESC
LIMIT 20;"
# 3. Xác minh URL callback có thể truy cập được từ bên ngoài
curl -sf https://your-domain.com/api/payments/vnpay/callback && echo "Callback URL reachable"
# 4. Kiểm tra API có đang nhận callback không (qua Loki)
# Grafana > Explore > Loki > {container_name="goodgo-api"} |= "callback" |= "payment"
```
**Cách xử lý:**
```bash
# 1. Nếu callback bị timeout — kiểm tra health API và restart nếu cần
docker compose -f docker-compose.prod.yml restart api
# 2. Nếu xác thực chữ ký VNPay thất bại — kiểm tra các env var VNPAY_*
docker compose -f docker-compose.prod.yml exec api printenv | grep VNPAY
# 3. Đối với thanh toán kẹt — đối soát thủ công
# Kiểm tra cổng merchant VNPay/MoMo để biết trạng thái giao dịch thực tế
# Cập nhật trạng thái thanh toán trong DB nếu xác nhận đã thanh toán:
docker exec goodgo-postgres psql -U ${DB_USER} -d ${DB_NAME} -c \
"UPDATE \"Payment\" SET status = 'COMPLETED', \"updatedAt\" = now()
WHERE id = '<payment-id>' AND status = 'PENDING';"
# 4. Nếu callback không đến được server — kiểm tra:
# - Quy tắc firewall (cổng 3001 hoặc cổng reverse proxy phải mở)
# - Hiệu lực chứng chỉ SSL
# - Phân giải DNS
# - Cấu hình webhook của payment provider (URL callback đúng)
```
**Quan trọng:** Trình xử lý callback thanh toán dùng xử lý idempotent với state transition nguyên tử. Việc replay một callback là an toàn và sẽ không tạo ra thanh toán trùng.
### 3.6 Cảnh báo dung lượng đĩa
**Triệu chứng:**
- Container thất bại khi khởi động hoặc bị crash
- PostgreSQL từ chối ghi (`PANIC: could not write to file`)
- Docker daemon hết dung lượng
**Chẩn đoán:**
```bash
# Dung lượng đĩa của host
df -h
# Dung lượng đĩa của Docker
docker system df
docker system df -v
# Kiểm tra kích thước từng volume
for vol in $(docker volume ls -q | grep goodgo); do
echo -n "$vol: "
docker run --rm -v "${vol}:/data" alpine du -sh /data 2>/dev/null
done
# Kiểm tra volume backup
docker exec goodgo-pg-backup du -sh /backups/
docker exec goodgo-pg-backup ls -lht /backups/
```
**Cách xử lý:**
```bash
# 1. Dọn dẹp các tạo phẩm Docker
docker system prune -f # Xóa container đã dừng, network không dùng, image dangling
docker image prune -a -f # Xóa TẤT CẢ image không dùng (cẩn thận trên prod)
# 2. Xóa backup cũ (nếu retention không hoạt động)
docker exec goodgo-pg-backup find /backups -name "goodgo_*.sql.gz" -mtime +7 -delete
# 3. Dọn dẹp dữ liệu Prometheus (nếu quá lớn)
# Retention của Prometheus là 30d (prod) / 15d (dev) — cấu hình qua --storage.tsdb.retention.time
# Để ép compaction:
curl -sf -XPOST http://localhost:9090/-/quit # Tắt graceful sẽ kích hoạt compaction
docker compose -f docker-compose.prod.yml start prometheus
# 4. Dọn dẹp dữ liệu Loki (retention 15 ngày)
# Loki tự dọn dẹp qua compactor. Nếu khẩn cấp:
docker compose -f docker-compose.prod.yml restart loki
# 5. Truncate log container Docker
sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' goodgo-api)
# Hoặc cho tất cả container:
sudo sh -c 'truncate -s 0 /var/lib/docker/containers/*/*-json.log'
```
**Phòng ngừa:** Tất cả container production dùng logging `json-file` với `max-size: 10m``max-file: 3-5`. Retention backup là 7 ngày (cấu hình qua `BACKUP_RETENTION_DAYS`).
### 3.7 Lỗi MinIO / Object Storage
**Triệu chứng:**
- Upload ảnh/file thất bại
- Ảnh bất động sản không tải được
- Console MinIO không truy cập được ở cổng 9001
**Chẩn đoán:**
```bash
docker logs --tail=50 goodgo-minio
docker exec goodgo-minio mc ready local
docker exec goodgo-minio mc admin info local
```
**Cách xử lý:**
```bash
# 1. Restart MinIO
docker compose -f docker-compose.prod.yml restart minio
# 2. Nếu volume dữ liệu bị hỏng
docker compose -f docker-compose.prod.yml stop minio
docker volume rm goodgo-platform-ai_minio_data # CẢNH BÁO: mất dữ liệu
docker compose -f docker-compose.prod.yml up -d minio
# Tạo lại bucket qua API hoặc admin console
```
### 3.8 AI Services không khả dụng
**Triệu chứng:**
- Tính năng dùng AI (AVM, mô tả bất động sản) thất bại
- `GET /health` trên cổng 8000 thất bại
- Log API hiển thị timeout kết nối AI service
**Chẩn đoán:**
```bash
docker logs --tail=50 goodgo-ai-services
curl -sf http://localhost:8000/health
docker stats --no-stream goodgo-ai-services
```
**Cách xử lý:**
```bash
# 1. Restart AI services
docker compose -f docker-compose.prod.yml restart ai-services
# 2. Kiểm tra rate limit (mặc định: 60/phút)
docker compose -f docker-compose.prod.yml exec ai-services printenv | grep AI_RATE_LIMIT
# 3. Nếu OOM — service có giới hạn 1 GB; có thể cần tăng cho các model lớn
```
**Graceful Degradation:** Tính năng AI là tùy chọn. API nên xử lý việc AI service không khả dụng một cách mượt mà và trả về kết quả không dùng AI.
### 3.9 Lỗi pipeline log (Loki/Promtail)
**Triệu chứng:**
- Log explorer Grafana trả về kết quả trống
- Container Promtail unhealthy hoặc bị crash-loop
- Loki trả về 503
**Chẩn đoán:**
```bash
docker logs --tail=50 goodgo-loki
docker logs --tail=50 goodgo-promtail
curl -sf http://localhost:3100/ready && echo "Loki ready" || echo "Loki NOT ready"
```
**Cách xử lý:**
```bash
# 1. Restart pipeline
docker compose -f docker-compose.prod.yml restart loki promtail
# 2. Nếu dữ liệu Loki bị hỏng
docker compose -f docker-compose.prod.yml stop loki promtail
docker volume rm goodgo-platform-ai_loki_data
docker compose -f docker-compose.prod.yml up -d loki promtail
# Log lịch sử sẽ mất nhưng log mới sẽ chảy ngay lập tức
# 3. Nếu Promtail không truy cập được Docker socket
ls -la /var/run/docker.sock
# Đảm bảo container promtail có mount Docker socket
```
### 3.10 Tăng đột biến tỷ lệ lỗi 5xx
**Triệu chứng:**
- Alert Prometheus `ApiErrorRate5xxHigh` kích hoạt (> 1% 5xx trong 5 phút)
- Người dùng báo lỗi
**Chẩn đoán:**
```bash
# Kiểm tra endpoint nào trả về 5xx
curl -s "http://localhost:9090/api/v1/query" --data-urlencode \
'query=topk(10, sum(rate(http_requests_total{job="goodgo-api", status_code=~"5.."}[5m])) by (route, method))' \
| jq '.data.result'
# Kiểm tra log lỗi API
docker logs --tail=200 --since=5m goodgo-api 2>&1 | grep -i "error\|exception\|500"
# Kiểm tra health của tất cả phụ thuộc
curl -sf http://localhost:3001/health/ready | jq .
```
**Cách xử lý:**
1. Nếu liên quan đến DB: xem [Mục 3.1](#31-database-connection-pool-exhaustion)
2. Nếu liên quan đến Redis: xem [Mục 3.2](#32-redis-connection-failure)
3. Nếu là do deploy gần đây: xem [Mục 4.4](#44-rollback-deployment)
4. Nếu không rõ nguyên nhân: restart API và điều tra log
---
## 4. Quy trình khôi phục
### 4.1 Khôi phục database từ backup
**Backup tự động chạy hàng ngày lúc 02:00 UTC** qua container `pg-backup`. Retention: 7 ngày. Định dạng: `pg_dump --format=custom --compress=6`.
**Xác minh tự động chạy hàng ngày lúc 04:00 UTC** — khôi phục vào một database test cô lập, kiểm tra sự tồn tại của table, số dòng, checksum, extension PostGIS, index và enum. Báo cáo được ghi vào `/backups/verify-latest.json`.
#### Liệt kê các backup hiện có
```bash
docker exec goodgo-pg-backup ls -lht /backups/goodgo_*.sql.gz
```
#### Tạo backup theo yêu cầu
```bash
docker exec goodgo-pg-backup /scripts/pg-backup.sh
```
#### Quy trình khôi phục đầy đủ
```bash
# 1. Dừng các dịch vụ ứng dụng
docker compose -f docker-compose.prod.yml stop api web ai-services
# 2. (Production) Dừng PgBouncer để tránh kết nối cũ
docker compose -f docker-compose.prod.yml stop pgbouncer
# 3. Chạy script restore
docker exec -it goodgo-pg-backup /scripts/pg-restore.sh /backups/goodgo_YYYYMMDD_HHMMSS.sql.gz
# Script sẽ:
# - Terminate các kết nối DB đang active
# - DROP và tạo lại database
# - Khôi phục từ file backup
# 4. Xác minh việc khôi phục
docker exec goodgo-postgres psql -U ${DB_USER} -d ${DB_NAME} -c '\dt'
docker exec goodgo-postgres psql -U ${DB_USER} -d ${DB_NAME} -c 'SELECT count(*) FROM "User";'
# 5. Áp dụng các migration đang chờ (nếu có)
docker compose -f docker-compose.prod.yml exec api npx prisma migrate deploy
# 6. Khởi động lại tất cả dịch vụ
docker compose -f docker-compose.prod.yml up -d
# 7. Xác minh health của ứng dụng
curl -sf http://localhost:3001/health/ready | jq .
```
#### Xác minh backup mà không khôi phục
```bash
# Chạy verification trên backup mới nhất (tạo DB tạm, sau đó drop)
docker compose run --rm pg-verify-backup
# Hoặc xác minh một file backup cụ thể
docker exec goodgo-pg-backup /scripts/pg-verify-backup.sh /backups/goodgo_YYYYMMDD_HHMMSS.sql.gz
# Kiểm tra báo cáo verification mới nhất
docker exec goodgo-pg-backup cat /backups/verify-latest.json | jq .
```
**RPO/RTO:**
- RPO: ≤ 24 giờ (backup hàng ngày; cân nhắc WAL archiving để giảm RPO)
- RTO: ~15 phút (volume local), ~30 phút (off-site)
### 4.2 Xóa cache Redis và warm-up
```bash
# Xóa toàn bộ dữ liệu Redis
docker exec goodgo-redis redis-cli -a "${REDIS_PASSWORD}" FLUSHALL
# Xác minh việc xóa
docker exec goodgo-redis redis-cli -a "${REDIS_PASSWORD}" DBSIZE
# Phải trả về: (integer) 0
```
**Warm-up:** Redis dùng eviction `allkeys-lru`. Cache tự ấm dần khi người dùng gửi request. Không cần script warm-up thủ công — cache miss sẽ đi thẳng xuống PostgreSQL.
**Khi nào cần flush:**
- Sau khi restore database (cache tham chiếu cũ)
- Sau khi dữ liệu bị hỏng ở tầng ứng dụng
- Sau khi thay đổi schema làm thay đổi cấu trúc dữ liệu được cache
### 4.3 Quy trình rolling restart
#### Restart từng dịch vụ (Zero Downtime)
```bash
# API — cờ --wait đảm bảo health check pass trước khi tiếp tục
docker compose -f docker-compose.prod.yml up -d --no-deps --wait api
# Web
docker compose -f docker-compose.prod.yml up -d --no-deps --wait web
# AI Services
docker compose -f docker-compose.prod.yml up -d --no-deps --wait ai-services
```
#### Rolling restart toàn bộ stack
```bash
# Các dịch vụ dữ liệu trước (thứ tự quan trọng theo chuỗi phụ thuộc)
docker compose -f docker-compose.prod.yml restart redis
docker compose -f docker-compose.prod.yml restart typesense
# Chờ các dịch vụ dữ liệu healthy
sleep 10
# Connection pooling
docker compose -f docker-compose.prod.yml restart pgbouncer
sleep 5
# Các dịch vụ ứng dụng
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
# Xác minh
curl -sf http://localhost:3001/health/ready | jq .
```
#### Khẩn cấp: Restart mọi thứ
```bash
docker compose -f docker-compose.prod.yml down
docker compose -f docker-compose.prod.yml up -d --wait
```
### 4.4 Rollback deployment
#### Cách hoạt động của Rollback Image
Mỗi lần deploy (cả CI/CD lẫn thủ công) đều gắn tag `:rollback` cho image hiện đang chạy **trước khi** pull image mới. Điều này đảm bảo phiên bản trước được giữ lại ngay cả khi `docker image prune` chạy. Các tag `:rollback` chỉ được dọn dẹp sau khi smoke test pass.
Vòng đời image trong quá trình deploy:
1. `docker tag <current-image> goodgo-api:rollback` (giữ lại phiên bản trước)
2. `docker compose pull` (kéo image mới)
3. `docker compose up` (khởi động phiên bản mới)
4. Chạy smoke test
5. **Nếu smoke test pass:** xóa tag `:rollback`, chạy `docker image prune`
6. **Nếu smoke test thất bại:** các image `:rollback` được đổi tag để khớp với template của compose và các dịch vụ được khởi động lại
#### Rollback tự động (CI/CD)
Pipeline CI/CD (`.github/workflows/deploy.yml`) tự động kích hoạt rollback nếu smoke test thất bại. Job rollback sẽ:
1. Dừng các container bị lỗi
2. Đổi tag các image `:rollback` để khớp với template image của compose (`${REGISTRY_URL}/goodgo-{svc}:${IMAGE_TAG}`)
3. Khởi động lại compose — lúc này sẽ resolve về image trước đó (đang hoạt động)
4. Gửi thông báo Slack tới `#deployments`
Không cần can thiệp thủ công cho các deploy do CI kích hoạt.
#### Rollback nhanh dùng tag :rollback (Thủ công)
```bash
# SSH vào host
ssh deploy@$PRODUCTION_HOST
cd ~/goodgo
# Xác minh các image rollback có tồn tại
for svc in goodgo-api goodgo-web goodgo-ai-services; do
docker image inspect "${svc}:rollback" > /dev/null 2>&1 \
&& echo "OK: ${svc}:rollback" \
|| echo "MISSING: ${svc}:rollback"
done
# Dừng các container hiện tại
docker compose -f docker-compose.prod.yml stop api web ai-services
# Đổi tag image rollback để khớp với template compose
export REGISTRY_URL=ghcr.io/goodgo
export IMAGE_TAG=$(docker inspect --format='{{index .Config.Labels "org.opencontainers.image.revision"}}' goodgo-api 2>/dev/null || echo "latest")
for svc in goodgo-api goodgo-web goodgo-ai-services; do
docker tag "${svc}:rollback" "${REGISTRY_URL}/${svc}:${IMAGE_TAG}"
done
# Khởi động lại với image rollback
docker compose -f docker-compose.prod.yml up -d --no-deps --wait api web ai-services
# Xác minh
curl -sf http://localhost:3001/health && echo "Rollback successful"
```
#### Rollback dùng deploy-production.sh (Script thủ công)
Script deploy thủ công (`scripts/deploy-production.sh`) có tích hợp sẵn rollback. Nếu health check hoặc smoke test thất bại, script sẽ tự động khôi phục từ các image được gắn tag `:rollback` và khởi động lại dịch vụ.
```bash
# Chạy deploy thủ công — rollback tự động khi thất bại
cd ~/goodgo
./scripts/deploy-production.sh <image-tag>
```
#### Rollback về một Git commit / image tag cụ thể
```bash
# Đặt tag mục tiêu (git SHA)
export IMAGE_TAG=<previous-commit-sha>
export REGISTRY_URL=ghcr.io/goodgo
# Pull phiên bản cụ thể
docker compose -f docker-compose.prod.yml pull api web ai-services
# Deploy
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
# Xác minh
curl -sf http://localhost:3001/health/ready | jq .
```
#### Rollback migration database
```bash
# CẢNH BÁO: Prisma không hỗ trợ down-migration tự động.
# Để rollback migration, khôi phục từ backup trước khi migration:
# 1. Dừng ứng dụng
docker compose -f docker-compose.prod.yml stop api web ai-services pgbouncer
# 2. Khôi phục từ backup đã tạo trước khi migration
docker exec -it goodgo-pg-backup /scripts/pg-restore.sh /backups/<pre-migration-backup>.sql.gz
# 3. Deploy phiên bản code trước đó (IMAGE_TAG cũ hơn)
export IMAGE_TAG=<previous-commit-sha>
docker compose -f docker-compose.prod.yml up -d --wait
```
### 4.5 Reindex Typesense từ PostgreSQL
Nếu dữ liệu Typesense bị mất hoặc hỏng, rebuild index tìm kiếm từ PostgreSQL:
```bash
# 1. Đảm bảo Typesense đang chạy và healthy
curl -sf -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" http://localhost:8108/health
# 2. Chạy reindex
docker compose -f docker-compose.prod.yml exec api npx ts-node scripts/typesense-reindex.ts
# Hoặc từ host:
pnpm run typesense:reindex
# 3. Xác minh collection
curl -sf -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" http://localhost:8108/collections | jq '.[].name'
```
### 4.6 Khôi phục toàn bộ host
Dành cho trường hợp host bị lỗi hoàn toàn hoặc di chuyển sang máy chủ mới:
```bash
# 1. Cung cấp host mới với Docker + Docker Compose
# Yêu cầu: Docker >= 24, Docker Compose v2, tối thiểu 8 GB RAM
# 2. Clone repository và cấu hình
git clone <repo-url> ~/goodgo && cd ~/goodgo
cp .env.example .env
# Chỉnh sửa .env với các secret production (từ secrets manager)
# 3. Khôi phục backup PostgreSQL từ bộ lưu trữ off-site
# Chuyển file backup sang host mới
scp backups/goodgo_latest.sql.gz deploy@newhost:~/goodgo/backups/
# 4. Khởi động các dịch vụ hạ tầng
docker compose -f docker-compose.prod.yml up -d postgres redis typesense minio
# 5. Chờ PostgreSQL sẵn sàng, sau đó restore
docker compose -f docker-compose.prod.yml exec postgres pg_isready
docker exec goodgo-pg-backup /scripts/pg-restore.sh /backups/goodgo_latest.sql.gz
# 6. Khởi động các dịch vụ ứng dụng
docker compose -f docker-compose.prod.yml up -d
# 7. Chạy migration (nếu backup cũ hơn code mới nhất)
docker compose -f docker-compose.prod.yml exec api npx prisma migrate deploy
# 8. Rebuild index Typesense
pnpm run typesense:reindex
# 9. Flush Redis (cache cũ từ host cũ)
docker exec goodgo-redis redis-cli -a "${REDIS_PASSWORD}" FLUSHALL
# 10. Xác minh mọi thứ
curl -sf http://localhost:3001/health/ready | jq .
curl -sf http://localhost:3000 > /dev/null && echo "Web OK"
# RTO dự kiến: ~60 phút (phụ thuộc tốc độ truyền backup)
```
---
## 5. Ma trận leo thang
| Mức độ | Điều kiện | Người phản hồi đầu tiên | Leo thang | SLA |
|----------|-----------|-----------------|------------|-----|
| **P0 — Nghiêm trọng** | Ngừng dịch vụ hoàn toàn, mất dữ liệu, hỏng thanh toán | SRE on-call | CTO + CEO trong 15 phút | Acknowledge: 5 phút, Resolve: 1 giờ |
| **P1 — Cao** | Ngừng một phần, vi phạm SLO (p99 > 3s), 5xx > 5% | SRE on-call | Engineering lead trong 30 phút | Acknowledge: 15 phút, Resolve: 4 giờ |
| **P2 — Trung bình** | Hiệu năng suy giảm, một dịch vụ (không trọng yếu) bị down, p99 > 1s | SRE on-call | Team lead vào ngày làm việc kế tiếp | Acknowledge: 1 giờ, Resolve: 24 giờ |
| **P3 — Thấp** | Vấn đề về thẩm mỹ, thiếu giám sát, cải tiến không cấp bách | Kỹ sư được giao | Sprint planning | Sprint tiếp theo |
### Kênh liên lạc
| Vai trò | Kênh |
|------|---------|
| SRE on-call | Slack `#sre-oncall` + PagerDuty |
| Engineering Lead | Slack `#engineering` |
| CTO | Slack DM / Điện thoại (xem PagerDuty) |
| Vấn đề thanh toán | Slack `#payments` + cổng hỗ trợ VNPay/MoMo |
| Hạ tầng | Slack `#infrastructure` |
### Thông báo Slack
Pipeline deploy tự động thông báo tới `#deployments` (qua `SLACK_WEBHOOK_URL`) khi:
- Deploy production thành công
- Smoke test staging thất bại
- Rollback production được kích hoạt
---
## 6. Dashboard giám sát
Tất cả dashboard được provision tự động qua `monitoring/grafana/provisioning/` và có sẵn trong thư mục **GoodGo** trên Grafana.
| Dashboard | Đường dẫn Grafana | Mục đích |
|-----------|--------------|---------|
| **API Overview** | `api-overview` | Tốc độ request, status code, kết nối active |
| **API Latency** | `api-latency` | Latency p50/p95/p99 theo endpoint, heatmap latency |
| **Database** | `database` | Kết nối PostgreSQL, hiệu năng truy vấn, thống kê PgBouncer |
| **Search** | `search` | Tốc độ truy vấn Typesense, latency, kích thước index |
| **Business Metrics** | `business-metrics` | Tin đăng, inquiry, thanh toán, đăng ký người dùng |
| **Web Vitals** | `web-vitals` | Core Web Vitals (LCP, FID, CLS), thời gian load trang |
| **Logs** | `logs` | Explorer log Loki với filter theo service, level, correlation ID |
**Truy cập:** `http://localhost:3002` (credential mặc định trong `.env`: `GRAFANA_ADMIN_USER` / `GRAFANA_ADMIN_PASSWORD`)
**Nguồn dữ liệu:**
- **Prometheus** (`http://prometheus:9090`) — Metric (mặc định)
- **Loki** (`http://loki:3100`) — Log, có correlation ID liên kết với Prometheus
- **Alertmanager** (`http://alertmanager:9093`) — Trạng thái alert và silence
---
## 7. Truy vấn PromQL hữu ích
### Hiệu năng API
```promql
# Latency p99 tổng thể
histogram_quantile(0.99, sum(rate(goodgo_api_request_duration_seconds_bucket{job="goodgo-api"}[5m])) by (le))
# Latency p99 theo từng endpoint (top 10 chậm nhất)
topk(10, histogram_quantile(0.99, sum(rate(goodgo_api_request_duration_seconds_bucket{job="goodgo-api"}[5m])) by (le, route, method)))
# Tốc độ request theo status code
sum(rate(http_requests_total{job="goodgo-api"}[5m])) by (status_code)
# Phần trăm lỗi 5xx
(sum(rate(http_requests_total{job="goodgo-api", status_code=~"5.."}[5m])) / sum(rate(http_requests_total{job="goodgo-api"}[5m]))) * 100
```
### Database
```promql
# Kết nối active
pg_stat_activity_count{datname="goodgo", state="active"}
# Tỷ lệ sử dụng connection pool (nếu metric PgBouncer được scrape)
# Kiểm tra thủ công qua: SHOW POOLS trong admin console PgBouncer
```
### Hạ tầng
```promql
# Sử dụng memory của container
container_memory_usage_bytes{name=~"goodgo-.*"}
# Sử dụng CPU của container
rate(container_cpu_usage_seconds_total{name=~"goodgo-.*"}[5m])
```
---
## 8. Tham chiếu nhanh môi trường
### Biến môi trường chính
| Biến | Bắt buộc | Mô tả |
|----------|----------|-------------|
| `DATABASE_URL` | Có | PostgreSQL qua PgBouncer (`postgresql://user:pass@pgbouncer:6432/db`) |
| `DATABASE_URL_DIRECT` | Có (prod) | PostgreSQL trực tiếp cho migration (`postgresql://user:pass@postgres:5432/db`) |
| `JWT_SECRET` | Có | Secret ký JWT |
| `JWT_REFRESH_SECRET` | Có | Secret ký refresh token |
| `REDIS_URL` | Có | Kết nối Redis (`redis://:password@redis:6379`) |
| `REDIS_PASSWORD` | Có (prod) | Mật khẩu auth Redis |
| `TYPESENSE_API_KEY` | Có | API key admin của Typesense |
| `MINIO_ACCESS_KEY` | Có | Root user của MinIO |
| `MINIO_SECRET_KEY` | Có | Mật khẩu root của MinIO |
| `VNPAY_*` | Có | Cấu hình payment gateway VNPay |
| `AI_API_KEY` | Có | Xác thực AI services |
| `GRAFANA_ADMIN_USER` | Có (prod) | Username admin Grafana |
| `GRAFANA_ADMIN_PASSWORD` | Có (prod) | Mật khẩu admin Grafana |
| `PGBOUNCER_POOL_SIZE` | Không | Kích thước pool PgBouncer (mặc định: 20) |
| `PGBOUNCER_MAX_CLIENT_CONN` | Không | Số client connection tối đa của PgBouncer (mặc định: 200) |
| `BACKUP_RETENTION_DAYS` | Không | Thời gian lưu giữ backup (mặc định: 7) |
| `IMAGE_TAG` | Không (prod) | Tag image container (mặc định: `latest`) |
### Bản đồ cổng
| Cổng | Dịch vụ | Phơi bày |
|------|---------|---------|
| 3000 | Web (Next.js) | Bên ngoài |
| 3001 | API (NestJS) | Bên ngoài |
| 3002 | Grafana | Bên ngoài (chỉ admin) |
| 5432 | PostgreSQL | Nội bộ |
| 6432 | PgBouncer | Nội bộ |
| 6379 | Redis | Nội bộ |
| 8000 | AI Services | Nội bộ |
| 8108 | Typesense | Nội bộ |
| 9000 | MinIO API | Nội bộ |
| 9001 | MinIO Console | Nội bộ |
| 9090 | Prometheus | Nội bộ |
| 3100 | Loki | Nội bộ |
### Docker Volume
| Volume | Dịch vụ | Mục đích |
|--------|---------|---------|
| `pgdata` | PostgreSQL | File database |
| `redis_data` | Redis | Bền vững AOF |
| `typesense_data` | Typesense | Dữ liệu index tìm kiếm |
| `minio_data` | MinIO | Object storage (ảnh, file) |
| `pg_backups` | pg-backup | File backup database |
| `loki_data` | Loki | Lưu trữ log (retention 15 ngày) |
| `prometheus_data` | Prometheus | Metric (retention 30 ngày prod / 15 ngày dev) |
| `grafana_data` | Grafana | Trạng thái dashboard, tùy chọn người dùng |
---
## 9. Xác thực Disaster Recovery
### Xác minh tự động
Xác minh backup chạy **hàng ngày lúc 04:00 UTC** bên trong container `pg-backup`. Nó khôi phục backup mới nhất vào một database test cô lập và kiểm tra:
- Sự tồn tại của table (tất cả 22 Prisma model)
- So sánh số dòng với database live
- Checksum dữ liệu trên các bảng quan trọng (User, Property, Listing, Payment, Subscription, Transaction, Plan)
- Khả dụng của extension PostGIS
- Khớp số lượng index
- Khớp số lượng enum type
**Kiểm tra báo cáo xác minh mới nhất:**
```bash
docker exec goodgo-pg-backup cat /backups/verify-latest.json | jq .
```
**Kiểm tra log xác minh:**
```bash
docker exec goodgo-pg-backup cat /var/log/pg-verify.log
```
### Quy trình xác thực DR thủ công
Chạy hàng quý (hoặc sau khi thay đổi schema lớn) để xác thực toàn bộ quy trình DR end-to-end.
#### Bước 1: Xác minh backup tồn tại và mới
```bash
# Liệt kê backup kèm timestamp và kích thước
docker exec goodgo-pg-backup ls -lht /backups/goodgo_*.sql.gz
# Xác minh backup mới nhất dưới 25 giờ tuổi
LATEST=$(docker exec goodgo-pg-backup ls -t /backups/goodgo_*.sql.gz | head -1)
echo "Latest backup: $LATEST"
```
#### Bước 2: Chạy xác minh trên backup mới nhất
```bash
# Xác minh tự động (tạo DB tạm, validate, drop)
docker exec -e REPORT_FILE=/backups/verify-latest.json goodgo-pg-backup \
/scripts/pg-verify-backup.sh
# Xem xét kết quả
docker exec goodgo-pg-backup cat /backups/verify-latest.json | jq .
```
**Kết quả mong đợi:** Tất cả các kiểm tra pass, việc restore hoàn thành trong < 60 giây với dataset thông thường.
#### Bước 3: Test restore đầy đủ (chỉ trên Staging)
> ⚠️ **CẢNH BÁO:** Chỉ thực hiện trên môi trường staging hoặc môi trường cô lập. Không bao giờ trên production.
```bash
# 1. Tạo môi trường test riêng biệt
docker compose -f docker-compose.yml -p goodgo-dr-test up -d postgres
# 2. Chờ PostgreSQL sẵn sàng
docker exec goodgo-dr-test-postgres-1 pg_isready
# 3. Chạy restore vào môi trường test
PGHOST=localhost PGPORT=<test-port> PGUSER=goodgo PGPASSWORD=<password> \
/scripts/pg-restore.sh /backups/<latest-backup>.sql.gz
# 4. Xác minh các bảng quan trọng
docker exec goodgo-dr-test-postgres-1 psql -U goodgo -d goodgo -c \
"SELECT count(*) FROM \"User\"; SELECT count(*) FROM \"Property\"; SELECT count(*) FROM \"Listing\";"
# 5. Dọn dẹp môi trường test
docker compose -f docker-compose.yml -p goodgo-dr-test down -v
```
#### Bước 4: Xác thực chuỗi khôi phục dịch vụ
Kiểm tra rằng tất cả dịch vụ có thể khởi động từ trạng thái sạch với dữ liệu đã khôi phục:
```bash
# 1. Ghi chú trạng thái dịch vụ hiện tại
docker compose -f docker-compose.prod.yml ps --format "table {{.Name}}\t{{.Status}}\t{{.Health}}"
# 2. Khởi động lại tất cả dịch vụ theo thứ tự phụ thuộc
docker compose -f docker-compose.prod.yml restart postgres
sleep 10 # Chờ PostgreSQL
docker compose -f docker-compose.prod.yml restart pgbouncer redis typesense
sleep 10 # Chờ các dịch vụ dữ liệu
docker compose -f docker-compose.prod.yml restart api web ai-services
sleep 15 # Chờ các dịch vụ ứng dụng
# 3. Xác minh tất cả health check
curl -sf http://localhost:3001/health/ready | jq .
curl -sf http://localhost:3000 > /dev/null && echo "Web OK"
curl -sf http://localhost:9090/-/healthy && echo "Prometheus OK"
curl -sf http://localhost:9093/-/healthy && echo "Alertmanager OK"
curl -sf http://localhost:3002/api/health | jq .
```
#### Bước 5: Xác thực pipeline cảnh báo
```bash
# 1. Kiểm tra Prometheus đang load các alert rule
curl -sf http://localhost:9090/api/v1/rules | jq '.data.groups | length'
# Mong đợi: 7 group
# 2. Kiểm tra các alert hiện tại (phải rỗng nếu khỏe mạnh)
curl -sf http://localhost:9090/api/v1/alerts | jq '.data.alerts | length'
# 3. Kiểm tra Alertmanager đang nhận từ Prometheus
curl -sf http://localhost:9093/api/v2/status | jq '.cluster'
# 4. Xác minh cấu hình Alertmanager đã được load
curl -sf http://localhost:9093/api/v2/status | jq '.config'
```
### Danh sách kiểm tra xác thực DR
Dùng danh sách này trong các đợt review DR hàng quý:
- [ ] Backup mới nhất dưới 25 giờ tuổi
- [ ] Báo cáo xác minh tự động cho thấy tất cả kiểm tra pass
- [ ] Restore thủ công vào DB test thành công với số dòng chính xác
- [ ] Restart đầy đủ dịch vụ hoàn thành trong mục tiêu RTO (< 30 phút)
- [ ] Tất cả các health endpoint phản hồi sau khi restart
- [ ] Alert rule Prometheus đã được load (7 group)
- [ ] Alertmanager có thể truy cập và đã được cấu hình
- [ ] Kênh thông báo Slack nhận được cảnh báo test
- [ ] Dashboard Grafana hiển thị dữ liệu sau khi restart
- [ ] Tìm kiếm Typesense trả về kết quả sau khi restart
### Tóm tắt RPO/RTO
| Chỉ số | Mục tiêu | Thực tế (đo được) | Ghi chú |
|--------|--------|-------------------|-------|
| **RPO** | ≤ 24 giờ | ~24h (hàng ngày lúc 02:00 UTC) | Giảm bằng WAL archiving |
| **RTO — Backup local** | ≤ 15 phút | Đo trong DR test | Restore + restart dịch vụ |
| **RTO — Backup off-site** | ≤ 30 phút | Đo trong DR test | Cộng thêm thời gian chuyển dữ liệu |
| **RTO — Khôi phục toàn bộ host** | ≤ 60 phút | Đo trong DR test | Host mới + restore + deploy |
---
## Phụ lục: Tham chiếu Alert Rule
### Alert API & Lỗi
| Alert | Biểu thức | Mức độ | Thời lượng |
|-------|-----------|----------|----------|
| `ApiLatencyP99High` | p99 > 1s | Warning | 5 phút |
| `ApiEndpointLatencyP99High` | p99 theo từng route > 2s | Warning | 5 phút |
| `ApiLatencyP99Critical` | p99 > 3s (vi phạm SLO) | Critical | 3 phút |
| `ApiErrorRate5xxHigh` | Tỷ lệ 5xx > 1% | Warning | 5 phút |
| `ApiErrorRate5xxCritical` | Tỷ lệ 5xx > 5% | Critical | 3 phút |
| `ApiNoTraffic` | Tốc độ request = 0 | Warning | 10 phút |
### Alert Database
| Alert | Biểu thức | Mức độ | Thời lượng |
|-------|-----------|----------|----------|
| `PostgresActiveConnectionsHigh` | Kết nối active > 15 | Warning | 5 phút |
| `PostgresConnectionPoolCritical` | Tổng số kết nối > 180 | Critical | 2 phút |
| `PostgresSlowQueries` | Truy vấn chờ lock > 5 | Warning | 5 phút |
| `PostgresDown` | Scrape target của API down | Critical | 1 phút |
### Alert Redis
| Alert | Biểu thức | Mức độ | Thời lượng |
|-------|-----------|----------|----------|
| `RedisMemoryHigh` | Sử dụng memory > 80% | Warning | 5 phút |
| `RedisMemoryCritical` | Sử dụng memory > 95% | Critical | 2 phút |
| `RedisConnectedClientsHigh` | Số client > 150 | Warning | 5 phút |
| `RedisRejectedConnections` | Kết nối bị từ chối > 0 | Critical | 1 phút |
### Alert Tài nguyên Container
| Alert | Biểu thức | Mức độ | Thời lượng |
|-------|-----------|----------|----------|
| `ContainerRestartLoop` | > 3 lần restart trong 15 phút | Critical | 5 phút |
| `ContainerMemoryHigh` | Memory > 85% giới hạn | Warning | 5 phút |
| `ContainerCPUThrottled` | Tỷ lệ CPU throttle > 0.5s/s | Warning | 10 phút |
### Alert Đĩa & Hạ tầng
| Alert | Biểu thức | Mức độ | Thời lượng |
|-------|-----------|----------|----------|
| `HostDiskUsageHigh` | Đĩa root > 80% | Warning | 10 phút |
| `HostDiskUsageCritical` | Đĩa root > 90% | Critical | 5 phút |
| `ApiHealthCheckFailing` | Health probe thất bại | Critical | 2 phút |
| `PrometheusTargetDown` | Scrape target down | Warning | 5 phút |
### Alert Backup
| Alert | Biểu thức | Mức độ | Thời lượng |
|-------|-----------|----------|----------|
| `BackupTooOld` | Backup cuối > 25 giờ trước | Warning | 5 phút |
| `BackupVerificationFailed` | Kết quả verify = fail | Warning | 1 phút |
### Routing Alert
Alert được route qua Alertmanager (`monitoring/alertmanager/alertmanager.yml`):
| Kênh | Route | Khoảng lặp lại |
|---------|--------|-----------------|
| `#sre-oncall` (Slack) | Tất cả alert warning | 4 giờ |
| `#sre-oncall` (Slack) | Tất cả alert critical (ưu tiên) | 1 giờ |
| `#infrastructure` (Slack) | Alert liên quan backup | 6 giờ |
**Inhibition:** Các alert warning bị chặn khi đã có alert critical cho cùng một dịch vụ đang kích hoạt.
Alert rule được định nghĩa trong `monitoring/prometheus/alert-rules.yml` và được đánh giá mỗi 15 giây.