Vận hành
1. Triển khai
| Thuộc tính | Giá trị |
|---|---|
| Image | registry/nx-seller-payment:<tag> |
| Container Port | 3000 |
| External Port | 31040 |
| Mode mặc định | FULL (snowflake 4) |
| Mode production | 1× API (snowflake 8) + N× WORKER (snowflake 91+i) |
| Replicas (FULL) | 1 (chỉ dev) |
| Replicas (API) | 1+ (scale theo lưu lượng) |
| Replicas (WORKER) | 1+ (scale theo độ sâu queue) |
| Resources (req/lim) | 200m / 1 CPU, 512Mi / 1Gi memory |
| Live probe | GET /v1/api/payment/healthz |
| Ready probe | GET /v1/api/payment/readyz |
| Migration | chạy như Job riêng trước khi rollout (RUN_MODE=migrate) |
Label routing Traefik
yaml
labels:
- "traefik.enable=true"
- "traefik.http.routers.payment.rule=PathPrefix(`/v1/api/payment`)"
- "traefik.http.services.payment.loadbalancer.server.port=3000"Pod WORKER không expose REST — không cần label Traefik.
Hạ tầng yêu cầu
| Phụ thuộc | Lý do |
|---|---|
| PostgreSQL | bảng Configuration, WebhookConfig, MQ-Pay |
| Redis | BullMQ cho queue scheduler + confirmation; WS emitter |
@nx/identity reachable | xác minh JWKS |
| VN Pay reachable | tích hợp nhà cung cấp |
| Webhook subscriber reachable | ít nhất sale phải chấp nhận phân phối webhook |
2. Observability
| Tín hiệu | Nguồn | Tìm ở đâu |
|---|---|---|
| Logs | stdout | kubectl logs deploy/payment-{api,worker} |
| Health | /healthz, /readyz | Gateway portal |
| OpenAPI live spec | GET /v1/api/payment/doc/openapi.json | Gateway portal |
| Độ sâu queue BullMQ | Redis | bull-board / Burrow |
| Lỗi dispatch webhook | logs | grep WebhookDispatcherService |
Trường log quan trọng
| Trường | Nguồn | Ghi chú |
|---|---|---|
mode | env APP_ENV_MQ_PAY_MODE | log khi boot |
transactionId / attemptId | per-event | trace vòng đời theo từng thanh toán |
webhookConfigId | log dispatch | subscriber nào đã nhận |
paymentProvider | event payload | VNPAY_QR_MMS / v.v. |
3. Bảo mật
| Mối quan tâm | Giải pháp |
|---|---|
| Rò rỉ credential nhà cung cấp | Lưu mã hóa AES-256-GCM trong Configuration.credential; cột ẩn khỏi CRUD; key từ K8s secret |
| Tính nhất quán khóa mã hóa | APP_ENV_APPLICATION_SECRET PHẢI giống nhau trên tất cả các pod (API + WORKER) |
| Giả mạo IPN | Chữ ký nhà cung cấp được xác minh trong @nx/mq-pay PaymentVerificationService |
| Tampering webhook | WebhookConfig.signingMethod (HMAC_SHA256) — subscriber xác minh HMAC của timestamp + eventType + body |
| AuthN | JWT (xác minh cục bộ) |
| AuthZ | Casbin qua PolicyDefinition |
| Network policy | Cilium — cho phép gateway + sale + identity + Redis + Postgres + VN Pay (egress) |
| Xoay vòng secret | Xoay vòng APP_ENV_APPLICATION_SECRET yêu cầu mã hóa lại tất cả row Configuration.credential (quy trình thủ công) |
4. Runbook
4.1 Các loại cảnh báo
| Cảnh báo | Kích hoạt | Kiểm tra | Khắc phục | Escalate |
|---|---|---|---|---|
PaymentHighErrorRate | 5xx >5% trong 5m | logs, xác định endpoint | rollback nếu mới deploy | on-call backend |
PaymentQueueLag | BullMQ pending >1000 | Redis bull-board | scale replica WORKER | on-call SRE |
PaymentWorkerStarvation | Pod WORKER 0 active job nhưng queue có việc | kiểm tra snowflake ID duy nhất (xung đột = không có job) | sửa APP_ENV_NODE_ID cho từng worker | on-call SRE |
PaymentWebhookDispatchFailures | Tỷ lệ lỗi phân phối webhook >10% | logs WebhookDispatcherService | kiểm tra sức khỏe subscriber (sale, v.v.) | on-call backend |
PaymentProviderUnreachable | VN Pay 5xx | logs + provider status page | đợi nhà cung cấp; chuyển sang phương án thay thế nếu được cấu hình | on-call backend |
PaymentEncryptionMismatch | spike log "decrypt failed" | kiểm tra tính nhất quán APP_ENV_APPLICATION_SECRET giữa các pod | redeploy với secret nhất quán | on-call SRE — HIGH |
4.2 Thao tác phổ biến
| Thao tác | Lệnh |
|---|---|
| Tail log API | kubectl logs -n <ns> -f deploy/payment-api |
| Tail log worker | kubectl logs -n <ns> -f deploy/payment-worker |
| Chạy migrations | kubectl run payment-migrate --image=...:tag --env="RUN_MODE=migrate" --rm --restart=Never |
| Liệt kê webhook đã đăng ký | SELECT id, name, url, event_types, status FROM "WebhookConfig" WHERE deleted_at IS NULL; |
| Replay transaction bị kẹt | Qua admin UI MQ-Pay hoặc DB: UPDATE Transaction SET status = 'NEW' WHERE id = '...' (cần phê duyệt cấp cao) |
| Kiểm tra config đã mã hóa | SELECT id, code, principal_type, principal_id, environment FROM "Configuration" WHERE group = 'INTEGRATION'; (không select credential) |
4.3 Kịch bản khôi phục
| Kịch bản | Khôi phục |
|---|---|
| Pod WORKER crash giữa job | BullMQ phân phối lại; idempotent ở lớp MQ-Pay |
| Pod API crash khi tạo transaction | Sale nhận 5xx; sale retry create — MQ-Pay dedupe theo transaction reference của sale |
| Webhook subscriber down | WebhookDispatcherService retry theo WebhookConfig.metadata.maxRetries; cuối cùng fail. Công cụ replay thủ công lấy trạng thái transaction và dispatch lại |
| Xoay vòng secret mã hóa sai | Worker không thể giải mã credential nhà cung cấp → mọi transaction fail. Roll back env change ngay. |
Xung đột Snowflake ID (hai pod WORKER cùng NODE_ID) | Trùng primary key trong bảng MQ-Pay → INSERT lỗi. Xác minh mỗi worker có APP_ENV_NODE_ID duy nhất (91, 92, …) |