Debug mạng: "API không gọi được" thì làm gì?
Tiếp đi, giờ có lỗi thì debug mạng thế nào?
Đây là bài tổng hợp. Mỗi bài trước (DNS, TCP, firewall, TLS, HTTP, Nginx, Tomcat, timeout) cho ta một "tầng". Debug mạng là đi từng tầng, từ dưới lên, dùng đúng công cụ cho tầng đó.
1. Bài toán
Sáng thứ Hai, mobile app báo "Không tạo được đơn". Log của order-service có I/O error on POST request for "http://payment.internal:8081/payments". Ai hỏng: DNS, NSG, payment chết, TLS, hay chậm?
2. Vấn đề: đoán mò
- Restart service "cho chắc" làm mất bằng chứng.
- Thử từ laptop thay vì từ đúng VM/pod đang gọi: khác DNS, khác firewall, khác route.
- Đọc nhầm lỗi: connection refused, connect timeout, read timeout, 502, 504 chỉ ra các tầng khác nhau.
3. Giải pháp: hộp đồ nghề theo tầng
| Tầng | Câu hỏi | Công cụ |
|---|---|---|
| DNS (9.4) | Tên ra IP nào? | dig, nslookup, getent hosts |
| Route (9.7) | Gói đi đường nào, mất ở đâu? | traceroute, mtr, tracert |
| TCP/port (9.2, 9.3) | Port có mở, có ai listen? | nc -vz, telnet, Test-NetConnection, ss -ltnp |
| TLS (9.5) | Cert đúng, còn hạn, đủ chain? | openssl s_client, curl -v |
| HTTP (9.6, 9.8) | Status, header, ai trả lời? | curl -v, curl -w |
| App (9.9, 9.10) | Thread, connection, chậm ở đâu? | ss -tanp, actuator, jcmd Thread.print |
| Gói tin | Thật sự gửi/nhận gì? | tcpdump |
4. Code từng bước (chạy trên VM của order-service)
Bước 1: DNS
dig +short payment.internal # 10.0.1.20 ? hay rỗng / IP cũ?
nslookup api.shop.example
getent hosts payment.internal # theo đúng thứ tự resolve của OS (/etc/hosts trước)
Bước 2: TCP
nc -vz -w 2 10.0.1.20 8081 # succeeded / refused / timed out
nc -vz -w 2 10.0.2.5 5432 # Postgres
# Windows
Test-NetConnection 10.0.1.20 -Port 8081
# trên VM payment: có ai listen không, listen ở địa chỉ nào?
ss -ltnp | grep 8081 # 127.0.0.1:8081 = chỉ local, máy khác không vào được
Bước 3: TLS
openssl s_client -connect api.shop.example:443 -servername api.shop.example </dev/null \
| openssl x509 -noout -subject -issuer -dates
Bước 4: HTTP + thời gian từng pha
curl -v http://10.0.1.20:8081/actuator/health
curl -s -o /dev/null -w 'dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} \
ttfb=%{time_starttransfer} total=%{time_total} code=%{http_code}\n' https://api.shop.example/api/orders/1
# dns=0.002 connect=0.010 tls=0.045 ttfb=3.020 total=3.021 code=200
# → mạng nhanh, server nghĩ 3s: nhìn app/DB, không phải mạng
Bước 5: trạng thái connection
ss -tanp | awk '{print $1}' | sort | uniq -c
# 180 ESTAB
# 2400 TIME_WAIT
# 95 CLOSE_WAIT ← đáng lo
ss -tanp state close-wait '( dport = :8081 )'
Bước 6: đường đi và gói tin
mtr -rwc 20 10.0.2.5 # mất gói ở hop nào
traceroute -T -p 5432 10.0.2.5 # TCP traceroute, đi qua firewall chặn ICMP
sudo tcpdump -i eth0 -nn host 10.0.1.20 and port 8081 -c 50
# chỉ thấy [S] lặp lại, không có [S.] → bị DROP (NSG/firewall)
# thấy [S] rồi [R.] → refused, không ai listen
Bước 7: từ bên trong container/pod
docker exec -it order-service sh -c 'getent hosts payment.internal; wget -qO- http://payment.internal:8081/actuator/health'
kubectl exec -it deploy/order-service -- curl -sv http://payment-service:8081/actuator/health
# image không có curl? dùng pod debug:
kubectl run netshoot --rm -it --image=nicolaka/netshoot -- bash
5. Cách hoạt động: cây quyết định "API không gọi được"
API không gọi được
│
├─ 1. Tên có resolve không? dig / getent
│ └─ không / sai IP → DNS record, /etc/hosts, Private DNS zone, cache TTL
│
├─ 2. TCP tới IP:port? nc -vz / Test-NetConnection
│ ├─ timed out → NSG, firewall, route, VPC peering, sai IP (tcpdump: chỉ có SYN)
│ ├─ refused → service chết, sai port, listen 127.0.0.1 (ss -ltnp trên server)
│ └─ succeeded → đi tiếp
│
├─ 3. HTTPS? openssl s_client / curl -v
│ └─ cert hết hạn, sai tên (SNI), thiếu chain, sai TLS version
│
├─ 4. HTTP status? curl -v
│ ├─ 502 → Nginx không nói chuyện được với Tomcat (app chết, sai upstream)
│ ├─ 504 → backend chậm hơn proxy_read_timeout
│ ├─ 4xx → request sai: path, Host header, auth, CORS (lỗi phía browser)
│ └─ 5xx từ app → đọc log app
│
└─ 5. Chậm? curl -w
├─ dns/connect cao → mạng, DNS
└─ ttfb cao → app: thread dump, Hikari, DB, downstream timeout (9.9, 9.10)
6. So sánh: trạng thái connection
| State | Nghĩa | Khi nào đáng lo |
|---|---|---|
| LISTEN | Socket server chờ connection | Thiếu → service không chạy hoặc sai port |
| ESTABLISHED | Đang kết nối | Số tăng mãi → leak, không đóng connection |
| TIME_WAIT | Bên chủ động đóng chờ ~60s để gói muộn không lẫn vào connection mới | Hàng chục nghìn → mở/đóng connection liên tục; dùng keep-alive, connection pool |
| CLOSE_WAIT | Bên kia đã đóng, app mình chưa close() | Gần như luôn là bug: không đóng response/stream, pool leak |
| SYN_SENT | Đã gửi SYN, chưa có trả lời | Kẹt nhiều → firewall DROP |
7. Lưu ý khi debug
- Luôn test từ đúng nơi gọi (VM, container, pod), không phải từ laptop.
pingkhông trả lời không có nghĩa là host chết: Azure/firewall hay chặn ICMP (bài 9.3). Test bằng TCP.- Thu bằng chứng (thread dump,
ss, log) trước khi restart. tcpdumpcần quyền root và có thể chứa dữ liệu nhạy cảm; lọc chặt, xóa file sau khi dùng.
Thấy hàng nghìn TIME_WAIT trên VM order-service, có phải lỗi không?
Chưa chắc. TIME_WAIT là trạng thái bình thường của bên chủ động đóng connection. Nó chỉ thành vấn đề khi số lượng đủ lớn để cạn ephemeral port (khoảng 28 nghìn port mặc định trên Linux) khi gọi tới cùng một đích. Nguyên nhân thường gặp: tạo HTTP client mới cho mỗi request, hoặc tắt keep-alive. Cách sửa: dùng một RestClient bean dùng chung với connection pool. Còn CLOSE_WAIT nhiều thì gần như chắc là bug của app: không đóng response body/stream.
Ping VM Postgres không được nhưng app vẫn kết nối được, sao vậy?
Đúng như bài 9.3: ping dùng ICMP, app dùng TCP 5432. NSG chỉ mở TCP 5432 từ subnet của order-service, không mở ICMP. Muốn kiểm tra đúng thì dùng nc -vz 10.0.2.5 5432 hoặc Test-NetConnection 10.0.2.5 -Port 5432.
Phỏng vấn hỏi "API không gọi được, em debug thế nào" thì trả lời sao?
Q1. Service A không gọi được service B, em debug thế nào?
🇻🇳 Tiếng Việt:
Em đi từng tầng, từ đúng máy hoặc pod của A. Đầu tiên kiểm tra DNS bằng
dig. Sau đó kiểm tra TCP bằngnc -vz: timeout thường là firewall hoặc route, refused là service không listen. Nếu là HTTPS thì kiểm tra cert bằngopenssl s_client. Cuối cùng dùngcurl -vxem status code, vàcurl -wxem chậm ở pha nào. Nếu là 5xx hoặc chậm thì xem log, thread dump, connection pool.
🇬🇧 English:
I go layer by layer, from the same host or pod as service A. First I check DNS with
dig. Then TCP withnc -vz: a timeout usually means a firewall or routing issue, refused means nothing is listening. For HTTPS I check the certificate withopenssl s_client. Then I usecurl -vfor the status code andcurl -wto see which phase is slow. For 5xx or slowness I look at logs, thread dumps and connection pools.
Q2. TIME_WAIT và CLOSE_WAIT khác nhau thế nào?
🇻🇳 Tiếng Việt:
TIME_WAIT nằm ở bên chủ động đóng connection, là bình thường, chỉ đáng lo khi quá nhiều làm cạn port, thường do không dùng connection pool. CLOSE_WAIT nghĩa là bên kia đã đóng nhưng ứng dụng của mình chưa gọi close, gần như luôn là bug leak connection.
🇬🇧 English:
TIME_WAIT is on the side that actively closed the connection. It is normal and only a problem when there are so many that ephemeral ports run out, usually because there is no connection pooling. CLOSE_WAIT means the peer has closed but our application has not called close yet, which is almost always a connection leak bug.
Q3. Connection refused và connection timeout nói lên điều gì?
🇻🇳 Tiếng Việt:
Refused là host trả RST: máy sống nhưng không có process listen port đó, hoặc chỉ listen 127.0.0.1. Timeout là không có phản hồi: firewall hoặc security group drop gói, sai route, hoặc host tắt.
🇬🇧 English:
Refused means the host answered with RST: the machine is up but nothing listens on that port, or it only listens on 127.0.0.1. Timeout means no answer at all: a firewall or security group drops packets, routing is wrong, or the host is down.
Cách nhớ ngắn gọn
- DNS → TCP → TLS → HTTP → App. Đi từ dưới lên.
- Test từ đúng nơi gọi, bằng TCP chứ không bằng ping.
- Refused = không ai nghe; Timeout = bị chặn.
curl -wtách được chậm do mạng hay do server.- CLOSE_WAIT nhiều = app quên close.
Tổng kết chương Networking
| Bài | Chủ đề | Một câu nhớ |
|---|---|---|
| 9.1 | Hành trình một request | Client → DNS → TCP → TLS → LB → Nginx → Tomcat → app → DB |
| 9.2 | IP, port, TCP, UDP | IP tìm máy, port tìm process; TCP tin cậy, UDP nhanh |
| 9.3 | Ping vs connect, firewall | Ping là ICMP; app dùng TCP; NSG mở theo port |
| 9.4 | DNS | Tên → IP, có cache và TTL |
| 9.5 | TLS/HTTPS | Cert chứng minh danh tính, mã hóa đường truyền, SNI |
| 9.6 | HTTP | Method, status, header, keep-alive |
| 9.7 | LB, NAT, VPC | Mạng riêng, IP public/private, L4 vs L7 |
| 9.8 | Nginx | Reverse proxy, TLS termination trước Tomcat |
| 9.9 | Tomcat | Acceptor/Poller/Worker; 200 thread; hết thread thì xếp hàng |
| 9.10 | Timeout | Connect vs read; ngoài lớn hơn trong; 502 vs 504 |
| 9.11 | Debug mạng | DNS → TCP → TLS → HTTP → App, test từ đúng nơi gọi |
Tự kiểm tra
nc -vz báo refused, bước tiếp theo?
Lên máy đích chạy ss -ltnp: service có chạy không, đúng port không, có listen 0.0.0.0 hay chỉ 127.0.0.1.
curl -w cho connect=0.01 ttfb=4.2: lỗi ở đâu?
Mạng nhanh; server xử lý chậm. Xem thread dump, Hikari, DB, downstream.
tcpdump chỉ thấy SYN lặp lại, không có SYN-ACK nghĩa là gì?
Gói bị drop trên đường (NSG/firewall/route) hoặc host không tồn tại.
Vì sao nên debug từ trong pod/container thay vì laptop?
DNS, network policy, route và firewall khác nhau; laptop thông không có nghĩa pod thông.
Kiểm tra cert của api.shop.example còn hạn bằng lệnh nào?
openssl s_client -connect api.shop.example:443 -servername api.shop.example | openssl x509 -noout -dates