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
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>
1227 lines
47 KiB
Markdown
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` và `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.
|