Load balancer L4/L7, NAT và VNet: order-service chạy 2 instance

4 câu hỏi~25 phútTừ khóa: L4, L7, health probe, sticky session, stateless, X-Forwarded-For, NAT, private IP, VNet/VPC, subnet, NAT gateway

1 VM chết là sập hết. Giờ muốn chạy 2 instance order-service thì làm sao?

Tới giờ ta có: domain api.shop.example, HTTPS, một Azure VM chạy order-service, Postgres ở VM khác. Giờ nâng lên 2 instance sau load balancer, đặt trong mạng riêng cho đúng.

1. Bài toán

Internet ──► VM1 (public IP) : order-service :8080 ──► VM-DB (public IP?) :5432

Black Friday, VM1 quá tải. Tuần sau Azure bảo trì host, VM1 reboot, API chết 5 phút.

2. Vấn đề

3. Giải pháp: Load balancer + mạng riêng (VNet/VPC)

Load balancer nhận traffic ở một địa chỉ, chia cho nhiều backend, và bỏ backend không khỏe ra.

L4 (Transport)L7 (Application)
Nhìn thấyIP, port, TCP/UDPHTTP: host, path, header, cookie
RoutingTheo 5-tuple (hash)/orders/** → pool A, /images → pool B
TLSPass-throughTerminate / re-encrypt, WAF
Hiệu năngRất nhanh, ít overheadTốn hơn vì parse HTTP
Ví dụAzure Load Balancer, AWS NLBAzure Application Gateway, AWS ALB, Nginx, HAProxy

VNet (Azure) / VPC (AWS): mạng ảo riêng, dải IP private (RFC 1918: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), chia subnet, mỗi subnet có NSG/security group.

VNet shop-vnet 10.0.0.0/16
├── snet-edge  10.0.1.0/24   Application Gateway / LB (có public IP)
├── snet-app   10.0.2.0/24   vm-order-1 10.0.2.4, vm-order-2 10.0.2.5 (KHÔNG public IP)
└── snet-db    10.0.3.0/24   vm-postgres 10.0.3.4 (KHÔNG public IP)

4. Code từng bước

Bước 1: Health endpoint cho probe (Actuator)

management:
  endpoints.web.exposure.include: health
  endpoint.health.probes.enabled: true   # /actuator/health/readiness, /liveness
server:
  shutdown: graceful                     # đang xử lý dở thì làm xong rồi mới tắt
spring:
  lifecycle.timeout-per-shutdown-phase: 20s
  datasource:
    url: jdbc:postgresql://10.0.3.4:5432/orders   # private IP
    username: ${DB_USER}
    password: ${DB_PASSWORD}

Bước 2: Tạo NSG chỉ cho phép đúng luồng (Azure CLI)

# App subnet: chỉ nhận 8080 từ subnet edge
az network nsg rule create -g shop-rg --nsg-name nsg-app -n allow-edge-8080 \
  --priority 100 --source-address-prefixes 10.0.1.0/24 \
  --destination-port-ranges 8080 --access Allow --protocol Tcp
# DB subnet: chỉ nhận 5432 từ subnet app
az network nsg rule create -g shop-rg --nsg-name nsg-db -n allow-app-5432 \
  --priority 100 --source-address-prefixes 10.0.2.0/24 \
  --destination-port-ranges 5432 --access Allow --protocol Tcp

Bước 3: Đọc IP thật của client. Sau LB/Nginx, request.getRemoteAddr() là IP của proxy. Proxy thêm header:

X-Forwarded-For: 203.0.113.7, 10.0.1.5     # client gốc, rồi các proxy
X-Forwarded-Proto: https
server:
  forward-headers-strategy: native
  tomcat:
    remoteip:
      internal-proxies: 10\\.0\\.1\\.\\d{1,3}   # chỉ tin XFF từ subnet edge
@GetMapping("/whoami")
public String whoami(HttpServletRequest req) {
    return req.getRemoteAddr();   // đã là IP client thật nhờ RemoteIpValve
}

Bước 4: Test

for i in 1 2 3 4; do curl -s https://api.shop.example/actuator/info | grep host; done
# host: vm-order-1 / vm-order-2 xen kẽ
sudo systemctl stop order-service      # trên vm-order-1
# sau vài lần probe fail, mọi request đi vm-order-2, không lỗi

5. Cách hoạt động

Client 203.0.113.7
   │ HTTPS api.shop.example → public IP của App Gateway
   ▼
┌──────────── snet-edge ────────────┐
│ Application Gateway (L7, TLS)     │  probe GET /actuator/health/readiness mỗi 5s
└───────┬────────────────┬──────────┘
        │ HTTP + XFF     │
        ▼                ▼
 vm-order-1 10.0.2.4   vm-order-2 10.0.2.5      (snet-app, không public IP)
        └───────┬────────┘
                ▼ JDBC 10.0.3.4:5432
         vm-postgres (snet-db)

Outbound (app gọi API thanh toán bên ngoài):
 10.0.2.4 ──► NAT Gateway (public IP 20.x.x.x) ──► Internet
             đổi src IP private → public, nhớ bảng mapping để trả về

NAT (Network Address Translation): IP private không đi được trên Internet. NAT đổi địa chỉ nguồn sang public IP (SNAT, ra ngoài) hoặc đổi đích từ public sang private (DNAT, LB/port forward vào trong). Với NAT gateway, mọi VM đi ra bằng một public IP cố định, tiện cho đối tác whitelist IP, và VM vẫn không nhận được kết nối từ ngoài vào.

6. So sánh: public IP vs private IP

Public IPPrivate IP
Định tuyến trên InternetCóKhông
Dùng choLB, App Gateway, NAT gateway, bastionApp VM, DB, cache
Rủi roBị scan, brute-forceChỉ vào được từ trong VNet / VPN / peering

7. Khi nào nên dùng / không nên dùng

Chạy 2 instance xong admin cứ bị logout. Bật sticky session là xong đúng không?

Bật sticky thì hết lỗi, nhưng đó là chữa triệu chứng. Nguyên nhân: session (bài 9.6) nằm trong memory của vm-order-1, request sau rơi vào vm-order-2 không có session đó.

Sticky session (cookie affinity):
  Client ──► LB ──(cookie ApplicationGatewayAffinity)──► luôn vm-order-1

Nhược điểm của sticky:

Cách tốt hơn: stateless. Mọi instance giống hệt nhau, state để ở ngoài.

<dependency>
  <groupId>org.springframework.session</groupId>
  <artifactId>spring-session-data-redis</artifactId>
</dependency>
spring:
  data.redis.host: 10.0.2.10
  session.timeout: 30m

Hoặc với API cho mobile: dùng JWT, server không giữ session. Khi đó LB chọn instance nào cũng được, scale và failover thoải mái.

Health check fail thì LB làm gì? Liveness với readiness khác gì?

Đúng hướng rồi. LB gọi probe định kỳ; fail liên tiếp N lần (unhealthy threshold) thì ngừng gửi traffic mới tới instance đó, khỏe lại thì đưa vào lại. LB không restart VM.

ProbeHỏiFail thì
ReadinessSẵn sàng nhận traffic chưa?LB / K8s ngừng gửi request
LivenessProcess còn sống không, có bị treo?Orchestrator restart (K8s, VMSS repair)

Lưu ý: đừng cho readiness phụ thuộc cứng vào service bên ngoài (ví dụ payment API). Payment chậm sẽ làm cả 2 instance bị gỡ khỏi LB, sập toàn bộ.

Phỏng vấn hỏi về load balancer, NAT thì trả lời sao?

Q1. Load balancer L4 và L7 khác nhau thế nào?

🇻🇳 Tiếng Việt:

L4 làm việc ở tầng TCP/UDP, chỉ nhìn IP và port nên rất nhanh nhưng không hiểu HTTP. L7 parse HTTP nên route theo path, host, header, terminate TLS và gắn WAF được, đổi lại tốn tài nguyên hơn. Trên Azure: Load Balancer là L4, Application Gateway là L7.

🇬🇧 English:

An L4 load balancer works at the TCP/UDP level and only sees IPs and ports, so it is very fast but HTTP-unaware. An L7 load balancer parses HTTP, so it can route by path, host or header, terminate TLS and run a WAF, at a higher cost. On Azure, Load Balancer is L4 and Application Gateway is L7.

Q2. Sticky session có nên dùng không?

🇻🇳 Tiếng Việt:

Chỉ nên là giải pháp tạm. Nó làm tải lệch và mất session khi instance chết hoặc deploy. Em thiết kế service stateless, session để ở Redis qua Spring Session hoặc dùng token như JWT.

🇬🇧 English:

Only as a stopgap. It causes uneven load and loses sessions when an instance dies or is redeployed. I keep services stateless and store sessions in Redis via Spring Session, or use tokens such as JWT.

Q3. Sau load balancer làm sao lấy IP thật của client?

🇻🇳 Tiếng Việt:

Proxy thêm X-Forwarded-For và X-Forwarded-Proto. Trong Spring Boot em bật forward-headers-strategy và cấu hình internal-proxies để chỉ tin header từ proxy của mình, tránh client giả mạo IP.

🇬🇧 English:

The proxy adds X-Forwarded-For and X-Forwarded-Proto. In Spring Boot I enable forward-headers-strategy and restrict trusted internal proxies, so clients cannot spoof their IP by sending the header themselves.

Q4. NAT gateway để làm gì?

🇻🇳 Tiếng Việt:

Cho các VM chỉ có IP private đi ra Internet bằng một public IP cố định, mà không cho ai từ ngoài kết nối vào. Đối tác có thể whitelist đúng IP đó.

🇬🇧 English:

It lets VMs with only private IPs reach the Internet through a fixed public IP while blocking inbound connections. Partners can whitelist that single IP.

Cách nhớ ngắn gọn


Tự kiểm tra

Muốn route /orders và /payments tới 2 pool khác nhau thì cần L4 hay L7?

L7, vì phải đọc path HTTP.

Sau LB, getRemoteAddr() trả về gì nếu không cấu hình gì?

IP của LB/proxy, không phải client.

Vì sao readiness không nên kiểm tra API bên ngoài?

API ngoài chậm sẽ làm mọi instance cùng bị gỡ khỏi LB, gây sập toàn bộ.

SNAT và DNAT khác nhau thế nào?

SNAT đổi địa chỉ nguồn (đi ra, ví dụ NAT gateway). DNAT đổi địa chỉ đích (đi vào, ví dụ LB/port forward).

Postgres VM nên đặt thế nào trong VNet?

Subnet riêng, không public IP, NSG chỉ cho 5432 từ subnet app.