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-api2nghe127.0.0.1— lỗ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ằngcurl localhost:8000cũng qua, mà bên ngoài không ai vào được. Sửa bằng cách cho nó nghe0.0.0.0.d-api3không nghe gì cả — tiến trình chết hoặc chưa khởi động xong. Xemdocker 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.