Debug mạng: "API không gọi được" thì làm gì?

4 câu hỏi~30 phútTừ khóa: curl -v, curl -w, dig, nslookup, nc, Test-NetConnection, ss, TIME_WAIT, CLOSE_WAIT, traceroute, mtr, tcpdump, openssl s_client, kubectl exec

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ò

3. Giải pháp: hộp đồ nghề theo tầng

TầngCâu hỏiCô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 tinThậ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

StateNghĩaKhi nào đáng lo
LISTENSocket server chờ connectionThiếu → service không chạy hoặc sai port
ESTABLISHEDĐang kết nốiSố tăng mãi → leak, không đóng connection
TIME_WAITBên chủ động đóng chờ ~60s để gói muộn không lẫn vào connection mớiHàng chục nghìn → mở/đóng connection liên tục; dùng keep-alive, connection pool
CLOSE_WAITBê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ờiKẹt nhiều → firewall DROP

7. Lưu ý khi debug

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ằng nc -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ằng openssl s_client. Cuối cùng dùng curl -v xem status code, và curl -w xem 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 with nc -vz: a timeout usually means a firewall or routing issue, refused means nothing is listening. For HTTPS I check the certificate with openssl s_client. Then I use curl -v for the status code and curl -w to 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

Tổng kết chương Networking

BàiChủ đềMột câu nhớ
9.1Hành trình một requestClient → DNS → TCP → TLS → LB → Nginx → Tomcat → app → DB
9.2IP, port, TCP, UDPIP tìm máy, port tìm process; TCP tin cậy, UDP nhanh
9.3Ping vs connect, firewallPing là ICMP; app dùng TCP; NSG mở theo port
9.4DNSTên → IP, có cache và TTL
9.5TLS/HTTPSCert chứng minh danh tính, mã hóa đường truyền, SNI
9.6HTTPMethod, status, header, keep-alive
9.7LB, NAT, VPCMạng riêng, IP public/private, L4 vs L7
9.8NginxReverse proxy, TLS termination trước Tomcat
9.9TomcatAcceptor/Poller/Worker; 200 thread; hết thread thì xếp hàng
9.10TimeoutConnect vs read; ngoài lớn hơn trong; 502 vs 504
9.11Debug mạngDNS → 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