Đối chiếu từng khuyến nghị trong System Design Guideline — Finance API Platform (Draft v1, 03/07/2026) với hạ tầng kioskgaming đang chạy trên Hetzner (khảo sát trực tiếp 5 server + Hetzner Console, 04/07), và lộ trình chuyển đổi. Kiến trúc đích sau chuyển đổi: xem Hệ Thống Đích · hiện trạng chi tiết: Deep Dive hạ tầng · Sơ đồ hệ thống.
Guideline đề xuất 4 thay đổi chính. Đối chiếu với hạ tầng thật cho kết quả như sau.
| Khuyến nghị chính của guideline | Hiện trạng | Kết luận |
|---|---|---|
| Nginx thứ 2 + Cloudflare LB (xóa SPOF) | Chưa có | Ưu tiên #1. Thực tế nghiêm trọng hơn guideline mô tả — xem mục 04. |
| Idempotency key qua Redis | Chưa có | Việc của code backend (kioskgaming_backend), làm song song giai đoạn 1. |
| PgBouncer transaction mode | Chưa có | Cần audit connection ngay (giai đoạn 2), triển khai giai đoạn 3. |
| Đo đạc trước khi thêm compute | Đo 1 lần | Snapshot docker stats trên V2 (04/07): CPU <1%, ~90–100MB/instance — không nghẽn compute. Còn thiếu số liệu giờ cao điểm. |
| PM2 cluster trong compute layer | Đã có | Đã làm ở DDoS Resilience Phase 1 — guideline viết như chưa có. |
| Postgres read replica "nếu cần" | Đã có | postgres-slave trên V4, streaming không lag (xác nhận 04/07) — chỉ thiếu định tuyến read. |
| (Ngoài guideline) Network firewall khớp với tầng application | Lệch | Phát hiện 04/07: rule :80 Hetzner Firewall chỉ whitelist 5/15 dải Cloudflare mà nginx tin — sửa ngay được trên Console, xem Giai đoạn 0. |
Đồng thuận với guideline: đây là bổ sung, không đập đi xây lại. Phần lớn nền tảng (Cloudflare edge, least_conn, health check upstream, Redis/Postgres replication, PM2 cluster) đã đúng hướng. Việc còn thiếu tập trung ở 3 chỗ: SPOF nginx, idempotency, và kiểm soát connection tới Postgres.
Chi tiết đầy đủ trong Ha-Tang-Deep-Dive.html.
| Server | Plan | IP nội bộ | Vai trò |
|---|---|---|---|
| V1-Kiosk | CCX23 · 4 vCPU/16GB | 10.0.0.5 | nginx-lb :80 (gateway duy nhất toàn hệ) + API prod :3001–3004 + API dev :4001 |
| V2-Kiosk | CCX13 · 2 vCPU/8GB | 10.0.0.4 | API prod :3001–3004 + API dev :4001 — đo thực tế: ~90–100MB/instance, CPU thấp |
| V3-Kiosk | CCX13 · 2 vCPU/8GB | 10.0.0.2 | Redis master :6379 + Postgres master :5432 |
| V4-Kiosk | CCX13 · 2 vCPU/8GB | 10.0.0.3 | 7 container: Redis slave + Redis dev + Postgres slave :5433 + RabbitMQ + RedisInsight + OmniDB + Dozzle |
| kiosk-gamify-engine | CCX23 · 4 vCPU/16GB | 10.0.0.6 | Gamify dev/pro (5 instance) + OpenSearch + Dashboards + Fluent Bit |
Luồng request: client → HTTPS → Cloudflare (Flexible mode) → HTTP :80 → nginx-lb trên V1 → upstream backend least_conn 8 instance (V1 + V2). Postgres và Redis replicate sang V4, online không lag; failover hiện là thủ công. 6 site frontend (client/shop/agent/super-agent/admin/cashier) hosting trên Cloudflare Pages — ngoài phạm vi 5 server này.
① Hetzner Firewall firewall-ssh (duy nhất trong project, áp cho 5/5 server): rule :80 chỉ whitelist 5/15 dải Cloudflare — traffic thật qua PoP thuộc 10 dải thiếu bị DROP trước khi tới nginx; :22 mở toàn Internet; các port data (6379/5432/9200…) default-deny ✓. ② Mọi container Dozzle (4/4 server) báo unhealthy — lỗi healthcheck của image, không phải sự cố. ③ V2 và V3 trùng hostname ubuntu-8gb-ash-1 — nhận diện server phải bằng IP.
Từng điểm trong guideline, đối chiếu với cấu hình thật.
| Guideline đề xuất | Hiện trạng thực tế (nguồn kiểm chứng) | Trạng thái |
|---|---|---|
| Cloudflare ở edge chặn DDoS L3/4 | Mọi domain *.kioskservice.club sau Cloudflare; 15 dải set_real_ip_from + CF-Connecting-IP trong mọi conf (nginx/conf.d/*.conf) |
Đã đạt |
| 2 Nginx độc lập + Cloudflare LB | 1 nginx-lb duy nhất trên V1. Nặng hơn guideline mô tả: V1 gánh cả gateway lẫn 4/8 instance API prod — V1 chết là mất gateway và nửa app cùng lúc |
SPOF |
least_conn + max_fails=3 + fail_timeout |
Đã có đủ trong upstream backend (conf.d/default.conf) |
Đã đạt |
keepalive tới upstream |
Đang bị comment — # keepalive 32; (default.conf:15) |
Tắt |
Rate limit limit_req/limit_conn tầng nginx |
Không tồn tại trong bất kỳ file conf nào (đã grep toàn bộ conf.d/ + nginx.conf). Rate limit hiện chỉ có ở tầng backend (DDoS Phase 1) |
Chưa có |
| Timeout ngắn: connect 3s / read 8s | Đang 60s / 60s (default.conf:61-63) |
Quá dài |
PM2 instances = số vCPU, không hơn |
PM2 cluster đã chạy (DDoS Phase 1). V2 là CCX13 2 vCPU chạy 4 instance :3001–3004 — vượt số core. Snapshot 04/07: CPU <1%, RAM ~100MB/instance ⇒ không nghẽn, nhưng 4 instance trên 2 core vẫn là thừa (chỉ tốn context-switch, không thêm throughput) | Đo 1 lần |
| (Ngoài guideline) Firewall tầng network khớp danh sách trust tầng nginx | Lệch (Console, 04/07): rule :80 chỉ có 5/15 dải Cloudflare trong khi set_real_ip_from tin 15 dải; rule :443 lại đủ 15/15 dù nginx không listen 443; :22 mở Any IPv4+IPv6 |
Lệch |
| PgBouncer transaction pooling | Chưa có. max_connections=100 (postgres-master.yml:31); 8 instance prod + 2 dev + gamify cùng trỏ 1 master — pool 10/instance là đã chạm trần |
Chưa có |
| Read replica "cân nhắc khi cần" | Đã có sẵn: postgres-slave streaming replication từ 10.0.0.2 (postgres-slave.yml) — guideline không biết. Chỉ thiếu định tuyến read từ app |
Đã có |
| Redis: cache + idempotency key | Redis master-slave đã chạy, đang dùng cache + rate limit backend. Idempotency key chưa có — là việc của code kioskgaming_backend, không thuộc repo nginx | Một nửa |
| Không dùng Kubernetes ở quy mô này | Hiện không dùng K8s — đồng thuận giữ nguyên | Đồng ý |
Guideline viết cho mô hình tổng quát "Cloudflare → 1 Nginx → app → DB" nên thiếu vài thực tế của hệ thống này.
Redis master và Postgres master cùng nằm trên một server CCX13. Slave của cả hai đã có nhưng failover là thủ công — không có Sentinel (Redis) hay Patroni/repmgr (Postgres). Sau khi xóa SPOF nginx, đây là điểm chết kế tiếp của hệ thống: V3 chết là mất toàn bộ write path dù app và gateway vẫn sống.
Guideline coi Nginx là một node riêng. Thực tế V1 vừa là gateway toàn hệ, vừa chạy 4 instance API prod + API dev — sự cố tài nguyên tầng app trên V1 kéo sập luôn gateway. Nginx #2 đặt trên V4 dù V4 đang cõng 7 container (Redis slave 1.5GB limit + Postgres slave shared_buffers 1GB + RabbitMQ 1GB + 4 tool) trên 8GB RAM — vì nginx rất nhẹ (vài chục MB) và failure-domain của V4 tốt hơn: mất V4 = mất gateway #2 + replica (đường write vẫn sống); đặt trên V2 thì mất V2 = mất gateway #2 + nửa API prod cùng lúc — lặp lại đúng cấu trúc lỗi của V1. Điều kiện kèm theo: audit RAM V4 trước khi cài (bước 1A).
Guideline chỉ bàn từ Cloudflare xuống nginx, bỏ qua lớp firewall của nhà cung cấp hạ tầng đứng trước nginx. Thực tế lớp này đang lệch với tầng application: rule :80 thiếu 10/15 dải Cloudflare (traffic thật có thể bị DROP tùy PoP — lỗi ngắt quãng khó truy), :22 mở toàn Internet. Đây là 2 việc sửa trên Console trong vài phút, không đụng server — đưa vào Giai đoạn 0 bên dưới, làm trước mọi thứ khác.
Guideline khuyến nghị rate=5r/s cho "finance API gọi thưa". Kioskgaming là nền tảng gaming — client gọi dày hơn nhiều (polling số dư, game callback, session). Áp thẳng 5r/s sẽ chặn nhầm user thật. Cần bắt đầu lỏng (≈20r/s burst 40), quan sát log rồi siết dần. Tương tự với timeout 3s/8s — các endpoint report/game callback có thể chậm hơn.
Rate limit + hardening tầng nginx trùng với Edge L0–L1 của DDoS-Resilience-Technical-Plan (đang pending). Nên gộp làm một lần sửa conf.d/, tránh hai kế hoạch đụng cùng file.
Giai đoạn 0: sửa lệch firewall phát hiện 04/07 — chỉ thao tác Console, vài phút, làm ngay. Giai đoạn 1: xóa SPOF nginx + Idempotency — rủi ro business cao nhất.
Console → Firewalls → firewall-ssh → rule TCP :80 → thêm: 173.245.48.0/20, 103.21.244.0/22, 103.22.200.0/22, 103.31.4.0/22, 141.101.64.0/18, 108.162.192.0/18, 190.93.240.0/20, 188.114.96.0/20, 197.234.240.0/22, 198.41.128.0/17 — sau đó rule :80 khớp đúng 15 dải của set_real_ip_from. Áp dụng tức thì cho cả 5 server, không cần restart gì. Đây là việc đang âm thầm chặn traffic thật, ưu tiên trước mọi thứ.
Rule :22 đang mở Any IPv4+IPv6. Nếu có IP admin/VPN cố định → giới hạn về dải đó. Nếu chưa có → tối thiểu xác nhận PasswordAuthentication no trong sshd_config cả 5 server + cân nhắc fail2ban; ghi lại quyết định để không quên.
Chọn V4 vì failure-domain: mất V4 = mất gateway #2 + replica, đường write còn sống; đặt trên V2 thì mất V2 = mất gateway #2 + nửa API prod — lặp lại lỗi cấu trúc của V1. V4 đang chạy 7 container nhưng nginx chỉ tốn vài chục MB nên vẫn vừa, với điều kiện kiểm tra trước.
free -h + docker stats --no-stream trên V4 — xác nhận còn ≥500MB RAM trống ổn định trước khi cài.nginx.yml + thư mục nginx/ (conf.d, htpasswd) sang V4 → docker compose -f nginx.yml up -d. Config dùng IP nội bộ 10.0.0.x nên chạy nguyên xi, không sửa gì. Lưu ý network_mode: host: nginx nghe :80 trên host — V4 chưa có gì chiếm :80 nên không xung đột.firewall-ssh áp cho cả 5 server nên :80 trên V4 đã mở sẵn cho dải Cloudflare — không cần thao tác thêm (đã gộp phần sửa 15/15 vào bước 0A).curl -H "Host: api.kioskservice.club" http://178.156.211.230/ từ IP được phép (hoặc từ chính V4: curl -H "Host: api.kioskservice.club" http://localhost/).Hai phương án, chọn theo ngân sách:
Từ đây mọi thay đổi conf.d/ phải sync cả 2 server. Thêm sync-nginx.sh vào repo: rsync conf → nginx -t từng con → reload từng con → dừng ngay nếu nginx -t fail. Cấu hình lệch nhau giữa 2 gateway là lỗi vận hành khó truy nhất về sau.
Không thuộc repo nginx — tách task riêng cho backend. Middleware đọc header Idempotency-Key, dùng SET key value NX EX ttl trên Redis master cho các endpoint deposit / withdraw / transfer. Lưu ý: phải lưu cả kết quả response để trả lại khi trùng key (SETNX minh họa trong guideline mới chỉ chặn xử lý đôi, chưa trả kết quả cũ). TTL 60–300s phủ hết chu kỳ retry của client.
Đo đạc & audit — không thay đổi compute trước khi có số liệu, đúng nguyên tắc 05 của guideline.
docker stats / pm2 monit. Nghi vấn cần trả lời: V2 (2 vCPU) chạy 4 instance :3001–3004 — nếu CPU không phải bottleneck, giảm còn 2 instance để giải phóng RAM và bớt context-switching.
Trên master: SELECT count(*), usename, application_name FROM pg_stat_activity GROUP BY 2,3;. Tính tổng = (8 instance prod + 2 dev + gamify) × pool size mỗi instance, so với trần max_connections=100 trừ buffer 10–20 cho backup/migration/monitoring. Con số này quyết định giai đoạn 3 làm gấp hay từ từ.
keepalive 32 trong upstream backend (bỏ comment default.conf:15, thêm proxy_http_version 1.1 + proxy_set_header Connection "").limit_req_zone ≈20r/s burst 40 (không dùng 5r/s của guideline — xem mục 04), key theo $binary_remote_addr sau real_ip Cloudflare. Quan sát log 1–2 tuần rồi siết dần.docker exec nginx-lb nginx -t → nginx -s reload từng con.PgBouncer + các việc để sau, triển khai theo số liệu giai đoạn 2.
Thêm service vào postgres-master.yml (hoặc yml riêng), pool_mode=transaction, max_client_conn=500, default_pool_size=20. App đổi connection từ :5432 → :6432 — đổi dần từng instance, có thể rollback từng con.
Transaction pooling không hỗ trợ prepared statement mức session. Trước khi chuyển: kiểm tra ORM/driver của kioskgaming_backend (TypeORM / node-postgres) — hoặc bật max_prepared_statements (PgBouncer ≥ 1.21), hoặc tắt prepared statements phía driver. Bỏ qua bước này sẽ gây lỗi ngẫu nhiên khó truy trên production.
Report, lịch sử giao dịch, dashboard đọc từ slave. Chỉ làm khi số liệu 2B cho thấy master chịu tải đọc lớn — replica đã chạy sẵn nên chi phí chỉ là sửa connection string phía app.
Tối thiểu: văn bản hóa quy trình failover thủ công Redis/Postgres (promote slave, đổi IP trong env backend, thứ tự thao tác). Nâng cao: Redis Sentinel; Postgres cân nhắc repmgr/Patroni khi đội vận hành sẵn sàng.
Giới hạn phạm vi rõ ràng để kế hoạch không phình.
max_connections Postgres — che triệu chứng; PgBouncer là fix đúng.free -h, docker stats) — tiền đề của 1A.sync-nginx.sh vào repo nginx (bước 1C).number_of_replicas: 0 + đặt retention/ISM cho OpenSearch; đổi hostname V2/V3/V4 cho hết trùng.Mọi thay đổi conf soạn ở repo local trước; áp dụng trên server luôn theo trình tự docker exec nginx-lb nginx -t → docker exec nginx-lb nginx -s reload (không restart). Thay đổi ảnh hưởng data service (Postgres/Redis) chỉ đưa lệnh để người vận hành tự chạy, kèm cảnh báo tác động.