Phần 33 đo được mạng overlay có MTU 1450 thay vì 1500 và nói rằng con số đó gây ra "kiểu lỗi khó chẩn đoán nhất". Bài này dựng lại đúng lỗi đó để xem nó trông như thế nào.
Kết quả cuối cùng, trên một đường mạng mà mọi giao diện đều khai MTU 1500:
| Kích thước response | Kết quả |
|---|---|
| 1000 byte | 200, đủ 1000 byte, 0,1 s |
| 1300 byte | 200, đủ 1300 byte, 0,1 s |
| 1340 byte | 200, đủ 1340 byte, 0,1 s |
| 1350 byte | 200, nhận 0 byte, treo tới hết giờ |
| 2000 byte | 200, nhận 0 byte, treo 10,1 s |
| 100.000 byte | 200, nhận 0 byte, treo 10,1 s |
Chú ý cột kết quả: mã 200 đã về. Bắt tay TCP xong, HTTP header xong, rồi im lặng. Với ứng dụng, đây là "server trả lời rồi mà response rỗng" — nghe như lỗi ứng dụng, không giống lỗi mạng chút nào.
Dựng lại
Ba container: client, router, server. Router bật ip_forward, mỗi bên một mạng.
Bước 1 — hạ MTU chân client của router xuống 1400, giữ client và server ở 1500:
| Kích thước | Kết quả |
|---|---|
| 500 byte | ✅ |
| 100.000 byte | ✅ |
Vẫn chạy tốt. Path MTU Discovery làm đúng việc của nó: router gửi ICMP "fragmentation needed" về, server học được và thu nhỏ gói. Bằng chứng nằm trong bảng định tuyến của server:
10.10.1.10 via 10.10.2.2 dev eth0 ... cache expires 599sec mtu 1400
Vậy MTU lệch một mình không gây hoạ. Đó là điều tôi phải đo mới tin, vì rất nhiều bài viết nói ngược lại.
Bước 2 — chặn đúng gói ICMP đó trên router:
iptables -I OUTPUT -p icmp --icmp-type fragmentation-needed -j DROP
Và dựng lại server để nó chưa học PMTU. Đó là lúc bảng ở đầu bài xuất hiện.
Chỉ khi hai điều kiện cùng xảy ra — MTU trên đường nhỏ hơn MTU của giao diện, và ICMP bị chặn — mới thành hố đen. Ngoài đời cả hai đều rất phổ biến: VPN và mạng overlay tạo ra điều thứ nhất, còn "chặn hết ICMP cho an toàn" là một cấu hình tường lửa kinh điển.
Ngưỡng nằm ở đâu
1340 chạy, 1350 treo. Cộng ngược lại: MTU đường đi 1400, trừ 20 byte header IP và 20 byte header TCP còn 1360 byte dữ liệu mỗi gói; response HTTP còn có mấy chục byte header nữa, nên phần thân vượt qua khoảng 1340 là gói đầu tiên đã chạm trần.
Điều này giải thích một triệu chứng rất đặc trưng: API trả JSON nhỏ thì chạy, JSON lớn thì treo. Cùng một endpoint, cùng một mã, chỉ khác dữ liệu.
Nó còn làm chết cả server
Server trong lần đo đầu của tôi là TCPServer đơn luồng. Sau vài request treo:
tcp 84 0 10.10.2.10:8000 10.10.1.10:51698 CLOSE_WAIT
tcp 84 0 10.10.2.10:8000 10.10.1.10:55728 CLOSE_WAIT
tcp 83 0 10.10.2.10:8000 10.10.1.10:47146 CLOSE_WAIT
tcp 84 0 10.10.2.10:8000 10.10.1.10:49890 CLOSE_WAIT
Sau đó mọi request đều hỏng, kể cả request 100 byte vốn chạy tốt trước đó. Tiến trình vẫn running, cổng vẫn mở, nhưng nó kẹt cứng ở một lời gọi ghi không bao giờ trả về.
Tôi suýt kết luận rằng ngưỡng đã đổi giữa hai lần chạy. Cái làm lộ ra là chạy lại một tải nhỏ đã biết chắc phải chạy — nó cũng hỏng, và một tải nhỏ không thể là vấn đề MTU. Đó là dấu hiệu vấn đề nằm ở server chứ không ở mạng.
Sau khi đổi sang server đa luồng, ngưỡng 1340/1350 mới ổn định và lặp lại được.
Cách bắt: đo MTU thật của đường đi
ip link chỉ cho bạn biết giao diện khai gì, không cho biết đường đi chịu được gì. Muốn biết thật thì gửi gói có bit "không phân mảnh":
ping -M do -s 1372 -c1 10.10.2.10
| Dữ liệu | Gói IP tổng | Kết quả |
|---|---|---|
| 100 byte | 128 | qua được |
| 1300 byte | 1328 | qua được |
| 1372 byte | 1400 | qua được |
| 1373 byte | 1401 | KHÔNG qua |
| 1472 byte | 1500 | KHÔNG qua |
Ranh giới nằm chính xác ở 1400, trong khi mọi ip link trên đường đều ghi 1500. Cộng 28 byte (20 IP + 8 ICMP) vào con số -s lớn nhất còn qua được là ra MTU thật.
Và để ý: gói quá cỡ không sinh ra thông báo lỗi nào cả — ping chỉ im lặng rồi báo mất gói. Không có dòng "Frag needed and DF set" quen thuộc, vì đó chính là gói ICMP đang bị chặn.
Một cái bẫy trong chính công cụ đo
Lần đầu chạy, ping -M do báo thất bại ở mọi kích thước, kể cả 100 byte. Tôi suýt kết luận đường mạng hỏng hoàn toàn.
Thật ra ping trong Alpine là busybox, và busybox không có tuỳ chọn -M. Nó in ra trang trợ giúp thay vì chạy, và vì tôi chỉ xét mã thoát nên mọi thứ trông như "không qua".
apk add --no-cache iputils # ping that, co -M do
Ca đối chứng phát hiện ra chuyện này: một gói 100 byte phải qua được. Khi ca chắc chắn đúng cũng báo sai, thì thứ hỏng là công cụ đo. Đây là lần thứ ba trong sê-ri này busybox làm hỏng một phép đo bằng cách im lặng không hỗ trợ thứ tôi tưởng nó hỗ trợ.
Hai cách vá, đo cả hai
Kẹp MSS ở router. Sửa MSS trong gói SYN để hai đầu tự thoả thuận kích thước nhỏ hơn:
iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --clamp-mss-to-pmtu
| Kích thước | Sau khi kẹp MSS |
|---|---|
| 1350 byte | ✅ 0,1 s |
| 2000 byte | ✅ 0,1 s |
| 100.000 byte | ✅ 0,1 s |
Hạ MTU của chính mạng Docker. Không cần đụng tường lửa:
docker network create --opt com.docker.network.driver.mtu=1400 ten-mang
Đo lại với eth0 của hai container ở 1400: cả ba kích thước đều xong trong 0,1 giây.
Cách thứ hai đúng hơn về mặt khái niệm — bạn khai đúng sự thật thay vì vá triệu chứng — nhưng nó đòi bạn biết con số đúng, và phải nhớ khai cho mọi mạng mới tạo.
Thử ba mươi giây
Khi một dịch vụ "chạy với dữ liệu nhỏ, treo với dữ liệu lớn":
docker run --rm --network <mang> nicolaka/netshoot \
ping -M do -s 1472 -c2 <dich>
- Qua được → không phải MTU, tìm chỗ khác.
- Không qua → giảm dần
-stới khi qua, cộng 28, đó là MTU thật của đường đi.
Ba dấu hiệu nhận dạng nhanh:
- Request nhỏ chạy, request lớn treo — cùng một endpoint.
- Mã HTTP đã về nhưng thân rỗng.
ip linktrên mọi chặng đều ghi 1500, màping -M do -s 1472không qua.
Phần sau gom lại thành một quy trình gỡ lỗi mạng container hoàn chỉnh, chạy trên vài sự cố dựng sẵn.