# Load balancer L4, L7 và reverse proxy: đưa dịch vụ của bạn vào sau hạ tầng mạng của khách

> Lúc dịch vụ của bạn phải chạy sau load balancer của khách, ba lỗi thường cùng lộ ra: mất IP thật của người dùng, session nhảy lung tung và health check báo sai. Cả ba đều sửa được nếu bạn biết mỗi lớp mạng nhìn thấy gì.

Bản gốc: https://fdetimes.net/vi/bach-khoa/load-balancer-reverse-proxy-mang-khach/

Thử hình dung hôm nay là ngày thứ ba bạn làm việc ở một ngân hàng. Dịch vụ hỏi đáp tài liệu nội bộ của bạn đã chạy ổn trên máy staging. Rồi team hạ tầng gửi tin nhắn: mọi lưu lượng phải đi qua load balancer của ngân hàng, không có ngoại lệ.

Một giờ sau khi chuyển sang, log ghi cùng một địa chỉ IP cho tất cả request. Rate limit theo IP chặn luôn cả phòng ban, còn người dùng thỉnh thoảng bị đăng xuất giữa cuộc hội thoại. Code của bạn không có lỗi nào. Vấn đề là bạn chưa hiểu mạng của khách nhìn thấy request của bạn ra sao.

Tình huống này là công việc thường ngày của một FDE. Bạn không được dựng hạ tầng từ đầu mà phải đưa dịch vụ vào một hệ thống có sẵn, do người khác vận hành và có luật riêng. Muốn làm được, bạn cần phân biệt rõ ba thứ: load balancer lớp 4, load balancer lớp 7 và reverse proxy.

## Load balancer của khách nhìn thấy gì?

Theo tài liệu thuật ngữ của F5, load balancer lớp 4 định tuyến bằng thông tin ở tầng transport, tức địa chỉ và cổng, và không đọc nội dung gói tin. Nó chỉ chuyển một luồng TCP tới một server và không biết bên trong là URL nào hay cookie gì.

Load balancer lớp 7 thì quyết định dựa trên các đặc điểm của HTTP header như URL hoặc cookie. Vì đọc được HTTP, nó có thể chuyển `/api` sang cụm này và `/static` sang cụm khác. Nó cũng có thể chèn thêm header trước khi gửi request vào trong.

Bạn cần quan tâm vì mỗi lớp quyết định dịch vụ của bạn nhận được gì, chẳng hạn có header `X-Forwarded-For` do proxy chèn vào hay không. Riêng TLS thì đừng đoán chỉ từ chữ L4 hay L7: giải mã ở load balancer hay ở dịch vụ của bạn là tùy cách khách đã cấu hình, nên bạn phải hỏi họ.

## Reverse proxy không phải là load balancer

Hai khái niệm này hay bị dùng lẫn. Reverse proxy nhận request từ client rồi chuyển tới một server xử lý được nó, còn load balancer phân phối request cho một nhóm server. Vì thế reverse proxy vẫn có ích ngay cả khi phía sau chỉ có một server.

Kể cả khi khách đã có load balancer, bạn vẫn nên có một reverse proxy của riêng mình, ví dụ NGINX, đứng ngay trước app. Đó là nơi bạn kiểm soát được: đọc lại IP thật, đặt timeout và trả health check. Bạn không phải xin team hạ tầng sửa thiết bị của họ mỗi lần cần đổi cấu hình.

NGINX hợp với vai trò này vì kiến trúc của nó. Blog kỹ thuật của NGINX giải thích rằng cách thiết kế ứng dụng mạng phổ biến là gán mỗi kết nối một thread hoặc process, và cách đó tốn chi phí chuyển ngữ cảnh.

NGINX chọn kiến trúc event-driven: master process lo phần việc cần đặc quyền như đọc cấu hình và bind cổng, còn các worker process làm phần việc chính.

**Điểm mấu chốt:** Code không lỗi vẫn có thể hỏng khi bạn không biết mạng của khách nhìn thấy gì.

## Làm thử từ đầu đến cuối: lấy lại IP thật của người dùng

Quay lại chuyện ở ngân hàng. Một request bây giờ đi theo đường: trình duyệt nhân viên → load balancer L7 của ngân hàng → NGINX của bạn → app. Khi kết nối đi qua bất kỳ proxy nào, server chỉ thấy IP của proxy cuối cùng, nên log của bạn chỉ toàn IP của load balancer.

Cách giải quyết là header `X-Forwarded-For`: proxy chèn vào header này IP gốc của client trước khi chuyển request vào trong. App đọc header đó để biết ai thật sự đang gọi.

Nhưng MDN cảnh báo rằng nếu dùng header này cho bảo mật, như rate limit hay kiểm soát truy cập theo IP, bạn chỉ được dùng những IP do proxy tin cậy thêm vào. Lý do là client có thể tự gửi kèm một header giả.

Giả sử team hạ tầng cho biết load balancer của họ nằm trong dải `10.20.0.0/24`. Cấu hình NGINX của bạn sẽ trông như sau:

```nginx
# Chỉ tin X-Forwarded-For khi request đến từ load balancer của khách
set_real_ip_from 10.20.0.0/24;
real_ip_header   X-Forwarded-For;
real_ip_recursive on;

upstream app {
server 127.0.0.1:8000;
}

server {
listen 80;

location /healthz {
proxy_pass http://app;
}

location / {
proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
proxy_set_header Host              $host;
proxy_pass http://app;
}
}
```

Dòng `set_real_ip_from` quan trọng nhất trong cả đoạn cấu hình. Không có nó, ai gửi `X-Forwarded-For: 1.2.3.4` cũng có thể giả làm người khác và vượt qua rate limit của bạn.

Có nó, NGINX chỉ tin giá trị đến từ dải IP của load balancer. Phần bài tập ở cuối sẽ chỉ cách kiểm tra điều này bằng `curl`.

## Vì sao session bị nhảy, và vì sao sticky không cứu được bạn

Tiếp theo là lỗi đăng xuất. Giả sử bạn chạy 3 instance và cấu hình cân bằng theo source IP. Tài liệu kiến trúc của HAProxy mô tả cách này như sau: cùng một IP luôn đến cùng một server, miễn là số server không thay đổi.

Hãy xem hai điều kiện đó hỏng như thế nào. Nếu bạn băm theo IP mà lớp của bạn nhìn thấy, mọi request đều mang IP của load balancer, nên cả 3 instance biến thành 1 instance đang quá tải. Nếu tuần sau bạn tăng lên 4 instance, phép băm chia lại, nhiều người dùng bị chuyển sang server khác và mất session đang nằm trong RAM.

Cách bền vững hơn là không giữ session trong app. Trong mô hình scale ngang, các server đứng sau load balancer, còn session nằm ở một kho dữ liệu chung mà mọi app server đều truy cập được.

Ở chỗ khách, kho đó thường là Redis hoặc database họ đã có sẵn. Cũng trong tình huống ở ngân hàng, hai cách cho kết quả như sau:

| Tình huống | Sticky theo IP, session trong RAM | Session ở kho dùng chung |
|---|---|---|
| Mọi request mang IP của load balancer | Cả 3 instance dồn thành 1 instance quá tải | Instance nào cũng đọc được session của người dùng |
| Tăng từ 3 lên 4 instance | Phép băm chia lại, nhiều người bị đăng xuất | Người dùng không bị đăng xuất |

## Năm bước trước buổi deployment

1. **Hỏi team hạ tầng ba câu.** Load balancer là L4 hay L7, TLS kết thúc ở đâu, và dải IP của proxy là gì. Ba câu trả lời này quyết định gần hết cấu hình của bạn.
2. **Quyết định ai lo TLS.** Load balancer có thể nhận phần mã hóa và giải mã để máy chủ tập trung vào việc chính.

Nếu khách đã làm vậy, bạn đọc `X-Forwarded-Proto` để biết request gốc có phải HTTPS không, thay vì tự cấu hình chứng chỉ.
3. **Viết `/healthz` có kiểm tra thật.** Theo tài liệu của AWS, Elastic Load Balancing kiểm tra sức khỏe các target và chỉ gửi request tới target còn khỏe.

Endpoint luôn trả 200 trong khi app mất kết nối database sẽ khiến người dùng tiếp tục bị gửi vào một instance đã hỏng.
4. **Đưa session và dữ liệu tạm ra kho dùng chung.** Khi đó khách thêm hay bớt instance cũng không ai bị đăng xuất.
5. **Thống nhất timeout.** Hỏi timeout của thiết bị là bao lâu.

Request gọi LLM có thể chạy lâu hơn mức mặc định, và bị cắt giữa chừng là lỗi rất khó tìm.

## Những lỗi hay gặp

Lỗi phổ biến nhất là tin mọi `X-Forwarded-For` mà không giới hạn nguồn, rồi dùng nó để làm rate limit. Lỗi thứ hai là bật sticky session theo IP mà không biết mọi request trông như đến từ cùng một địa chỉ.

Lỗi thứ ba là cho rằng có load balancer rồi thì không cần reverse proxy, nên mỗi lần đổi cấu hình lại phải chờ ticket của team khác. Lỗi thứ tư khó thấy hơn: health check chỉ kiểm tra process còn chạy, không kiểm tra app có phục vụ được hay không.

Với developer Việt Nam muốn chuyển sang FDE, đây là kỹ năng nên ghi rõ trong CV. Đừng chỉ viết "biết NGINX". Hãy mô tả một lần bạn đưa dịch vụ vào sau proxy có sẵn, giữ lại được IP thật và để dịch vụ chạy nhiều instance mà không mất session.

Khi đọc JD có "customer environment" hay "on-prem deployment", bạn có thể đoán người ta sẽ hỏi đúng những chuyện này.

## Bài tập: thử lừa chính proxy của bạn

Dựng NGINX với cấu hình ở trên trước một app nhỏ chỉ làm một việc là in IP client ra log. Đặt `set_real_ip_from` bằng dải IP của một máy bạn coi là load balancer.

Từ một máy nằm ngoài dải đó, chạy `curl -H "X-Forwarded-For: 1.2.3.4" http:///`. Nếu log ghi `1.2.3.4`, proxy của bạn đang bị lừa. Nếu log ghi IP thật của máy gửi, cấu hình đã đúng.

Sau đó xóa dòng `set_real_ip_from`, chạy lại lệnh và so sánh hai dòng log. Mạng của khách là thứ bạn không sửa được, nhưng một bài kiểm tra mười phút như thế này cho bạn biết dịch vụ của mình có đứng vững trong đó hay không.

**Thử ngay tuần này:**

- Dựng NGINX trước một app nhỏ, in IP client ra log, rồi gửi request kèm header X-Forwarded-For giả để xem app có bị lừa không
- Viết sẵn 4 câu hỏi gửi team hạ tầng của khách trước buổi deployment đầu tiên: load balancer là L4 hay L7, TLS kết thúc ở đâu, dải IP của proxy là gì, và timeout mặc định bao lâu
- Tách session trong một project cũ của bạn ra Redis, rồi chạy 2 instance sau load balancer để kiểm tra người dùng có bị đăng xuất không

## Nguồn

- [Layer 4 Load Balancing (F5 Glossary)](https://www.f5.com/glossary/layer-4-load-balancing)

- [Reverse Proxy (F5/NGINX Glossary)](https://www.f5.com/glossary/reverse-proxy)

- [Inside NGINX: How We Designed for Performance & Scale](https://blog.nginx.org/?p=5201)

- [HAProxy Architecture Guide](http://www.haproxy.org/download/1.2/doc/architecture.txt)

- [X-Forwarded-For header - HTTP | MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-For)

- [Scalability for Dummies](https://cs.fyi/guide/scalability-for-dummies)

- [What is Elastic Load Balancing? (AWS Docs)](https://docs.aws.amazon.com/elasticloadbalancing/latest/userguide/what-is-load-balancing.html)
