Tôi dựng bốn container: một cái nằm sai mạng, một cái nghe nhầm 127.0.0.1, một cái không chạy ứng dụng, một cái bình thường. Rồi gọi cả bốn từ cùng một chỗ:

d-api1   ma=000
d-api2   ma=000
d-api3   ma=000
d-api4   ma=200

Ba nguyên nhân hoàn toàn khác nhau, một triệu chứng duy nhất. Bài này là cái thang để tách chúng ra.

Bước 0: đọc mã lỗi của curl

Mẹo rẻ nhất, và rất hay bị bỏ qua vì -s nuốt mất thông báo. Dùng -sS:

Thông báo
d-api1 curl: (6) Could not resolve host: d-api1
d-api2 curl: (7) Failed to connect to d-api2:8000
d-api3 curl: (7) Failed to connect to d-api3:8000

Mã 6 và mã 7 là hai thế giới khác nhau. Mã 6 là vấn đề tên; mã 7 là vấn đề kết nối. Chỉ một chữ số đó đã cắt đôi không gian tìm kiếm.

Bước 1: tên có phân giải được không

docker exec <nguon> getent hosts <dich>
Kết quả
d-api1 rỗng
d-api2 192.168.0.2
d-api3 192.168.0.3
d-api4 192.168.0.4

d-api1 xong ngay tại đây: nó nằm ở mạng khác nên DNS nhúng không biết nó (phần 38). Kiểm tra bằng:

docker inspect <dich> --format '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}'

Hai container phải có ít nhất một mạng chung. Nếu bạn đang dùng Compose, hai service ở hai project khác nhau thì mặc định không chung mạng.

Bước 2: gói tin có tới nơi không

docker exec <nguon> ping -c1 -W2 <dich>

Cả d-api2, d-api3, d-api4 đều tới được. Nghĩa là định tuyến ổn, không có tường lửa nào chặn, container đích đang sống. Vấn đề nằm bên trong nó.

(Nếu ping không tới trong khi tên phân giải được, hãy nghi enable_icc=false hoặc một luật iptables — cả hai đều cho đúng triệu chứng "tên ra IP nhưng gói tin biến mất".)

Bước 3: bên trong, cái gì đang nghe

Đây là bước tách được hai sự cố còn lại:

docker exec <dich> ss -ltn
Đang nghe
d-api2 127.0.0.1:8000
d-api3 (không có gì trên 8000)
d-api4 0.0.0.0:8000
  • d-api2 nghe 127.0.0.1lỗi phổ biến nhất trong tất cả. Ứng dụng chạy hoàn hảo, log đẹp, test trong container bằng curl localhost:8000 cũng qua, mà bên ngoài không ai vào được. Sửa bằng cách cho nó nghe 0.0.0.0.
  • d-api3 không nghe gì cả — tiến trình chết hoặc chưa khởi động xong. Xem docker logs.

Cả hai đều không phải vấn đề mạng, dù triệu chứng ban đầu trông hệt như vậy.

Bước 4: container không có shell thì làm sao

Image distroless hoặc FROM scratch không có sh, không có ss, không có gì:

$ docker exec d-tron sh -c 'echo ok'
OCI runtime exec failed: exec failed: unable to start container process...

Cách vào: chạy một container khác dùng chung namespace mạng với nó.

docker run --rm --network container:d-tron nicolaka/netshoot ss -ltnp
State  Recv-Q Send-Q Local Address:Port
LISTEN 0      5          127.0.0.1:8000
LISTEN 0      4096      127.0.0.11:45659

Bắt được thủ phạm ngay: dịch vụ nghe 127.0.0.1. Container đích không cần có bất cứ công cụ nào — --network container: cho container phụ chính namespace mạng đó, nên nó thấy đúng những gì container kia thấy. Cùng cách này dùng được với ip, tcpdump, dig, curl.

Dòng 127.0.0.11 là DNS nhúng của Docker, nó luôn có mặt trên mạng tự tạo (phần 34).

Bước 5: nhìn tận gói tin

Khi bốn bước trên chưa đủ, bắt gói ngay trong namespace của bên gọi:

docker run --rm --network container:<nguon> nicolaka/netshoot \
  tcpdump -n -i any tcp port 8000

Hai lời gọi, một hỏng một chạy, cạnh nhau:

06:58:45.654306 Out IP 192.168.0.5.57470 > 192.168.0.2.8000: Flags [S]
06:58:45.654380 In  IP 192.168.0.2.8000 > 192.168.0.5.57470: Flags [R.]   <- RST sau 74 us

06:58:46.668834 Out IP 192.168.0.5.36206 > 192.168.0.4.8000: Flags [S]
06:58:46.668957 In  IP 192.168.0.4.8000 > 192.168.0.5.36206: Flags [S.]   <- bat tay binh thuong

Đây là chỗ phân biệt quan trọng nhất ở tầng gói tin:

Thấy gì Nghĩa là
RST quay về ngay Gói tin đã tới. Máy đích có đó, cổng không mở.
Im lặng hoàn toàn Gói tin không tới nơi. Định tuyến, tường lửa, sai mạng.
SYN lặp lại nhiều lần Không ai trả lời — thường là tường lửa DROP.
Bắt tay xong rồi im Nghi MTU (phần 39).

RST về sau 74 micro giây như trên là bằng chứng chắc chắn rằng đây không phải vấn đề mạng — không có đường truyền nào trả lời nhanh thế nếu gói tin phải đi đâu xa.

Bảng tra nhanh

Triệu chứng Nguyên nhân hay gặp Kiểm bằng
curl: (6) không phân giải tên Khác mạng, hoặc đang ở bridge mặc định getent hosts, docker inspect
curl: (7), ping tới được, RST ngay Ứng dụng nghe 127.0.0.1, hoặc chưa chạy ss -ltn trong container đích
curl: (7), ping không tới enable_icc=false, luật iptables, sai mạng ping, iptables -L
Kết nối được, treo với dữ liệu lớn MTU ping -M do -s 1472
Chạy được vài phút rồi hỏng DNS cache giữ IP cũ sau deploy so IP ứng dụng dùng với docker inspect
Từ máy chủ vào được, từ máy khác thì không Bind 127.0.0.1: khi publish docker ps --format '{{.Ports}}'

Thử ba mươi giây

Một hàm dán vào ~/.zshrc, chạy đủ ba bước đầu:

kiemtra() {   # kiemtra <container-nguon> <ten-dich> <cong>
  docker exec "$1" sh -c "
    echo '--- ten:'    ; getent hosts $2 || echo 'KHONG phan giai duoc'
    echo '--- ping:'   ; ping -c1 -W2 $2 >/dev/null 2>&1 && echo 'toi duoc' || echo 'khong toi'
    echo '--- goi:'    ; curl -sS --max-time 3 -o /dev/null -w '%{http_code}\n' http://$2:$3/
  "
  echo '--- ben trong dich dang nghe gi:'
  docker run --rm --network container:$2 nicolaka/netshoot ss -ltn 2>/dev/null
}

Ba bước đó giải quyết gần hết. Chỉ khi cả ba đều bình thường mà vẫn hỏng thì mới cần tới tcpdump.

Phần sau chuyển hẳn sang lưu trữ: volume, bind mount và tmpfs khác nhau ở đâu, và tốc độ ghi của từng loại.