Gateway Routing
1. Tổng quan
Routing trong BANA dùng mẫu Service-Owned Prefix. Mỗi dịch vụ tự đặt base path (/v1/api/<service>) và khai báo route Traefik của nó qua Docker labels. Không tồn tại cấu hình route trung tâm.
Nguồn: Docker labels từ infrastructure/deployments/develop/*/docker-compose.yml, config tĩnh từ packages/gateway/config/traefik.yml
2. Pipeline Edge → Traefik → Middleware
Traffic vào qua Nginx (TLS termination), rồi Traefik nhận nó trên port :30080. Hai provider cấu hình cấp cho Traefik: Docker Provider đọc label container cho router/service, File Provider đọc middlewares.yml cho định nghĩa middleware dùng chung.
| Component | Nguồn | Chi tiết |
|---|---|---|
| Nginx | Lớp edge (:80/:443) | TLS termination, thêm X-Forwarded-For/X-Real-IP |
| Entrypoint | Traefik web (:80) | Nhận HTTP từ Nginx trên host port :30080 |
| rate-limit@file | middlewares.yml | 200 req/s theo IP, burst: 400, ipStrategy.depth: 1 |
| circuit-breaker@file | middlewares.yml | net error > 10% hoặc P95 latency > 3s (vế tỷ lệ 5xx bị comment) |
| security-headers@file | middlewares.yml | XSS filter, nosniff, frame deny, strip Server header |
| Docker Provider | /var/run/docker.sock | Đọc label traefik.*, tự tạo router + service |
| File Provider | config/dynamic/middlewares.yml | Middleware dùng chung, router dashboard + basic auth, hot reload |
3. Ánh xạ Router → Service
Sau khi đi qua chuỗi middleware, Traefik khớp request với một trong các router đã đăng ký. Mỗi router được tự khám phá từ Docker labels. Router khớp forward tới dịch vụ backend tương ứng trên mạng Docker.
| Router | Rule | Middlewares | Ghi chú |
|---|---|---|---|
| identity | PathPrefix(/v1/api/identity) | rate-limit-auth@file, circuit-breaker@file, security-headers@file | Rate limit auth nghiêm hơn |
| commerce | PathPrefix(/v1/api/commerce) | rate-limit@file, circuit-breaker@file, security-headers@file | |
| sale | PathPrefix(/v1/api/sale) | rate-limit@file, circuit-breaker@file, security-headers@file | |
| finance | PathPrefix(/v1/api/finance) | rate-limit@file, circuit-breaker@file, security-headers@file | |
| inventory | PathPrefix(/v1/api/inventory) | rate-limit@file, circuit-breaker@file, security-headers@file | |
| payment | PathPrefix(/v1/api/payment) | rate-limit@file, circuit-breaker@file, security-headers@file | |
| signal-rest | PathPrefix(/v1/api/signal) | rate-limit@file, circuit-breaker@file, security-headers@file | |
| signal-ws | PathPrefix(/stream) | — | WebSocket passthrough |
| payment-webhook | Host(hook.bana.nexpando.vn) | payment-add-prefix@docker, security-headers@file | priority: 100, path rewrite |
| portal | PathPrefix(/) | dashboard-auth@file | priority: 1, Portal dashboard |
4. Components Traefik
| Component | Nguồn | Mô tả |
|---|---|---|
| Docker Provider | /var/run/docker.sock | Theo dõi container đang chạy để tìm label traefik.*. Tự tạo router và service khi một container khởi động, và gỡ chúng khi nó dừng. |
| File Provider | config/dynamic/middlewares.yml | Nạp định nghĩa middleware dùng chung (rate-limit, circuit-breaker, security-headers, dashboard-auth). Theo dõi file để tìm thay đổi — chỉnh sửa có hiệu lực mà không restart Traefik. |
| Router | Docker labels | Khớp request đến theo rule (PathPrefix, Host). Mỗi container dịch vụ khai báo router riêng. Traefik đánh giá rule theo thứ tự priority (priority mặc định = độ dài chuỗi rule). Dùng priority tường minh khi nhiều router có thể khớp cùng request. |
| Middleware | File provider (@file) hoặc Docker labels (@docker) | Xử lý request trước khi tới backend. Middleware dùng chung nằm trong file provider và phải được tham chiếu với hậu tố @file từ Docker labels. |
| Service | Docker labels | Đích backend — Traefik phân giải IP container qua mạng Docker và forward tới port đã khai báo. Mỗi dịch vụ bao gồm cấu hình health check. |
| Health Check | Docker labels | Traefik chủ động poll /v1/api/<service>/health mỗi 30 giây. Nếu một dịch vụ fail, Traefik gỡ nó khỏi routing cho đến khi sức khỏe được khôi phục. |
5. Vòng đời Request
6. Ba Mẫu Routing
BANA dùng ba mẫu routing riêng biệt tùy theo loại traffic:
Mẫu 1: REST API chuẩn
Mẫu phổ biến nhất. Một router khớp theo path prefix và áp dụng chuỗi middleware đầy đủ.
Client → Nginx → Traefik
→ Router: PathPrefix(/v1/api/commerce)
→ Middlewares: rate-limit@file → circuit-breaker@file → security-headers@file
→ Service: dev-nx-commerce:3000Dùng bởi: identity, commerce, sale, finance, inventory, payment, signal (REST).
Mẫu 2: WebSocket (Không Middleware)
Kết nối WebSocket yêu cầu kết nối bền vững và không được gián đoạn bởi rate limiting hay circuit breaking. Router signal-ws không có middleware.
Client → Nginx (Connection: upgrade) → Traefik
→ Router: PathPrefix(/stream)
→ Middlewares: (không)
→ Service: dev-nx-signal:3000Traefik hỗ trợ WebSocket nguyên bản — các header Connection: Upgrade và Upgrade: websocket được forward tự động.
Mẫu 3: Webhook bên thứ 3 với Path Rewrite
Cho tích hợp bên thứ 3 legacy nơi URL đã đăng ký không thể đổi. Một router dựa-trên-Host khớp theo domain, và một middleware replacepathregex rewrite path để thêm prefix dịch vụ.
VNPAY → Nginx (Host: hook.bana.nexpando.vn) → Traefik
→ Router: Host(hook.bana.nexpando.vn), priority: 100
→ Middlewares: payment-add-prefix@docker → security-headers@file
→ Path rewrite: /v1/api/payments/* → /v1/api/payment/payments/*
→ Service: dev-nx-payment:3000Middleware payment-add-prefix được định nghĩa trong Docker labels (không phải file provider) vì nó riêng cho chỉ dịch vụ payment.
Vì sao
priority: 100?PathPrefixcủa Traefik là khớp prefix thuần —PathPrefix(/v1/api/payment)cũng khớp/v1/api/payments/...vì/v1/api/paymentlà prefix của/v1/api/payments. Không có priority tường minh, routerpayment(PathPrefix) và routerpayment-webhook(Host) có priority mặc định tương tự (tính từ độ dài chuỗi rule), và Traefik có thể route request webhook qua sai router — bỏ qua path rewrite. Đặtpriority: 100đảm bảo router webhook dựa-trên-Host luôn thắng khi cóHost: hook.bana.nexpando.vn.
7. Cấu hình Docker Label
Dịch vụ chuẩn (Identity)
Nguồn: infrastructure/deployments/develop/identity/docker-compose.yml
services:
dev-nx-identity:
labels:
- "traefik.enable=true"
# Router
- "traefik.http.routers.identity.rule=PathPrefix(`/v1/api/identity`)"
- "traefik.http.routers.identity.entrypoints=web"
- "traefik.http.routers.identity.middlewares=rate-limit-auth@file,circuit-breaker@file,security-headers@file"
# Service
- "traefik.http.services.identity.loadbalancer.server.port=3000"
- "traefik.http.services.identity.loadbalancer.healthcheck.path=/v1/api/identity/health"
- "traefik.http.services.identity.loadbalancer.healthcheck.interval=30s"Dịch vụ WebSocket (Signal)
Nguồn: infrastructure/deployments/develop/signal/docker-compose.yml
Dịch vụ signal cần hai router — một cho REST API, một cho WebSocket:
services:
dev-nx-signal:
labels:
- "traefik.enable=true"
# REST API
- "traefik.http.routers.signal-rest.rule=PathPrefix(`/v1/api/signal`)"
- "traefik.http.routers.signal-rest.entrypoints=web"
- "traefik.http.routers.signal-rest.middlewares=rate-limit@file,circuit-breaker@file,security-headers@file"
# WebSocket
- "traefik.http.routers.signal-ws.rule=PathPrefix(`/stream`)"
- "traefik.http.routers.signal-ws.entrypoints=web"
# Service dùng chung
- "traefik.http.services.signal.loadbalancer.server.port=3000"
- "traefik.http.services.signal.loadbalancer.healthcheck.path=/v1/api/signal/health"
- "traefik.http.services.signal.loadbalancer.healthcheck.interval=30s"Router Webhook bên thứ 3 (Payment)
Nguồn: infrastructure/deployments/develop/payment/docker-compose.yml
Dịch vụ payment có hai router dùng chung một service. Router thứ hai xử lý webhook bên thứ 3 từ một domain legacy (hook.bana.nexpando.vn) nơi URL đã đăng ký không thể đổi. Một middleware replacepathregex rewrite path để bao gồm prefix /payment.
services:
dev-nx-payment:
labels:
- "traefik.enable=true"
# Router — traffic API thông thường
- "traefik.http.routers.payment.rule=PathPrefix(`/v1/api/payment`)"
- "traefik.http.routers.payment.entrypoints=web"
- "traefik.http.routers.payment.middlewares=rate-limit@file,circuit-breaker@file,security-headers@file"
# Router — webhook bên thứ 3 (path legacy không có prefix /payment)
# Priority 100 đảm bảo cái này thắng router PathPrefix khi Host khớp
- "traefik.http.routers.payment-webhook.rule=Host(`hook.bana.nexpando.vn`)"
- "traefik.http.routers.payment-webhook.priority=100"
- "traefik.http.routers.payment-webhook.entrypoints=web"
- "traefik.http.routers.payment-webhook.middlewares=payment-add-prefix@docker,security-headers@file"
# Middleware — rewrite /v1/api/* thành /v1/api/payment/*
- "traefik.http.middlewares.payment-add-prefix.replacepathregex.regex=^/v1/api/(.*)"
- "traefik.http.middlewares.payment-add-prefix.replacepathregex.replacement=/v1/api/payment/$$1"
# Service (dùng chung bởi cả hai router)
- "traefik.http.services.payment.loadbalancer.server.port=3000"
- "traefik.http.services.payment.loadbalancer.healthcheck.path=/v1/api/payment/health"
- "traefik.http.services.payment.loadbalancer.healthcheck.interval=30s"Luồng request cho webhook bên thứ 3:
API Portal (Catch-all, priority thấp nhất)
Nguồn: infrastructure/deployments/develop/gateway/docker-compose.yml
Portal là một dashboard Astro + React hiển thị sức khỏe dịch vụ, endpoint OpenAPI, thời gian phản hồi, và tổng quan hệ thống. Nó chạy như một container nginx phục vụ output tĩnh đã build và bắt mọi route không khớp.
services:
dev-nx-portal:
image: nginx:1.27-alpine
labels:
- "traefik.enable=true"
- "traefik.http.routers.portal.rule=PathPrefix(`/`)"
- "traefik.http.routers.portal.entrypoints=web"
- "traefik.http.routers.portal.priority=1"
- "traefik.http.routers.portal.middlewares=dashboard-auth@file"
- "traefik.http.services.portal.loadbalancer.server.port=80"Middleware dashboard-auth@file áp dụng cùng HTTP Basic Auth như Traefik dashboard (user: nx.eventry).
8. Poll Health Check
Traefik chủ động poll endpoint health của mỗi dịch vụ backend mỗi 30 giây. Dịch vụ không khỏe bị gỡ khỏi routing cho đến khi hồi phục. Việc này chạy độc lập với xử lý request.
9. Luồng Tự Khám phá
Khi một container dịch vụ mới khởi động với traefik.enable=true, điều này xảy ra tự động:
Không cần restart Traefik. Không có cấu hình route trung tâm để cập nhật. Dịch vụ khai báo route riêng và Traefik nhận nó trong vài giây.
10. Namespace Provider Middleware
Middleware được định nghĩa trong packages/gateway/config/dynamic/middlewares.yml (file provider) phải được tham chiếu với hậu tố @file trong Docker labels. Không có hậu tố, Traefik tìm middleware trong provider @docker và fail âm thầm.
# Đúng — tham chiếu middleware từ file provider
- "traefik.http.routers.commerce.middlewares=rate-limit@file,circuit-breaker@file,security-headers@file"
# Sai — Traefik tìm "rate-limit" trong Docker provider (không tồn tại)
- "traefik.http.routers.commerce.middlewares=rate-limit,circuit-breaker,security-headers"11. Priority Router & Bẫy PathPrefix
PathPrefix của Traefik là khớp prefix thuần — nó không bắt buộc ranh giới /. Điều này có nghĩa:
| Rule PathPrefix | Path Request | Khớp? |
|---|---|---|
PathPrefix(/v1/api/payment) | /v1/api/payment/webhook | Có |
PathPrefix(/v1/api/payment) | /v1/api/payments/vnpay/ipn | Có (không ranh giới /!) |
PathPrefix(/v1/api/sale) | /v1/api/sale-orders | Có (cùng vấn đề) |
Khi nhiều router khớp cùng request, Traefik chọn cái có priority cao nhất. Priority mặc định được tính từ độ dài chuỗi rule, có thể dẫn đến kết quả khó lường.
Khi nào đặt priority tường minh:
- Khi một router dựa-trên-Host trùng lặp với một router PathPrefix (như
payment-webhookvspayment) - Khi một router catch-all (như SPA client với
PathPrefix(/)) phải có priority thấp nhất - Khi hai rule PathPrefix trùng lặp (ví dụ
/v1/api/salevs/v1/api/sale-ordersgiả định)
# Số cao hơn = priority cao hơn (thắng)
- "traefik.http.routers.payment-webhook.priority=100" # Thắng router PathPrefix
- "traefik.http.routers.client.priority=1" # Thua mọi thứ khác12. Cấu hình Base Path Dịch vụ
Mỗi dịch vụ đặt base path qua biến môi trường:
# infrastructure/deployments/develop/<service>/.env.development
APP_ENV_SERVER_BASE_PATH=/v1/api/<service>| Dịch vụ | APP_ENV_SERVER_BASE_PATH |
|---|---|
| identity | /v1/api/identity |
| commerce | /v1/api/commerce |
| sale | /v1/api/sale |
| finance | /v1/api/finance |
| inventory | /v1/api/inventory |
| payment | /v1/api/payment |
| signal | /v1/api/signal |
13. Thêm một Dịch vụ Mới
- Đặt
APP_ENV_SERVER_BASE_PATH=/v1/api/<new-service>trong.env.developmentcủa dịch vụ - Thêm Traefik labels vào
docker-compose.ymlcủa dịch vụ:yamllabels: - "traefik.enable=true" - "traefik.http.routers.<new-service>.rule=PathPrefix(`/v1/api/<new-service>`)" - "traefik.http.routers.<new-service>.entrypoints=web" - "traefik.http.routers.<new-service>.middlewares=rate-limit@file,circuit-breaker@file,security-headers@file" - "traefik.http.services.<new-service>.loadbalancer.server.port=3000" - "traefik.http.services.<new-service>.loadbalancer.healthcheck.path=/v1/api/<new-service>/health" - "traefik.http.services.<new-service>.loadbalancer.healthcheck.interval=30s" - Thêm file compose vào mảng COMPOSE_FILES
infrastructure/deployments/develop/dc - Traefik tự khám phá dịch vụ mới — không cần đổi config gateway
14. Giao tiếp Liên-Dịch-vụ
Các dịch vụ gọi nhau (ví dụ commerce → identity) KHÔNG nên đi qua Traefik. Chúng giao tiếp trực tiếp qua mạng Docker:
# Trực tiếp container-tới-container (không đi qua gateway)
APP_ENV_IDENTITY_SERVICE_BASE_URL=http://dev-nx-identity:3000/v1/api/identity
APP_ENV_COMMERCE_SERVICE_BASE_URL=http://dev-nx-commerce:3000/v1/api/commerce15. Lộ trình Migrate K8s
Docker labels chuyển đổi cơ học sang Traefik IngressRoute CRDs:
# Docker label
traefik.http.routers.identity.rule=PathPrefix(`/v1/api/identity`)
# Tương đương K8s IngressRoute CRD
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
spec:
routes:
- match: PathPrefix(`/v1/api/identity`)
services:
- name: identity-service
port: 300016. Trang liên quan
| Tài liệu | Mô tả |
|---|---|
| Tổng quan Gateway | Thẻ định danh + service catalog |
| Kiến trúc | Góc nhìn C4, luồng request-routing |
| Middlewares | Rate limiting, circuit breaker, security headers |
| Resilience | Trạng thái circuit breaker, health check, retry |
| Cấu hình | Bảng route Nginx cục bộ, hằng số |
| Quyết định | ADR-0001 (label routing), ADR-0002 (dev parity) |