Mùa xuân này tôi dành gần cả buổi sáng để lần theo một container không phân giải nổi một tên nội bộ nào, trong khi trình duyệt bên cạnh mở wiki không một lời phàn nàn. Đường hầm đã dựng, trình duyệt chứng minh điều đó, và container nằm trong một không gian tên mạng khác với bảng tuyến đường khác và trình phân giải khác. Phần tiếp theo là thứ tự tôi kiểm tra bây giờ: bảng tuyến đường trước, phân giải tên thứ hai, chuyển tiếp thứ ba và cuối cùng là nền tảng máy chủ. Dừng ở bước đầu tiên đã hỏng, vì thêm quy tắc sâu hơn trong chồng sẽ che ranh giới thay vì dời nó.

So sánh ba bảng tuyến đường trước khi đổi một quy tắc

Một container trên cầu mặc định của Docker chỉ có một tuyến mặc định trỏ tới cổng docker0 và không hề biết tun0 tồn tại. Máy chủ có tun0, các tuyến do VPN đẩy xuống và có thể có một quy tắc chính sách đẩy lưu lượng thử ra qua giao diện khác với giao diện bạn mong đợi. WSL thêm bảng thứ ba sau bộ điều hợp ảo của riêng nó. In cả ba trước khi sửa bất kỳ bảng nào.

ContextCommandWhat a wrong answer looks like
Hostip route get 10.8.0.20Leaves a physical interface instead of tun0
Containerip routeOnly 172.17.0.0/16 via 172.17.0.1
Container DNScat /etc/resolv.confA public resolver, or an upstream the tunnel never pushed
WSLip addr; ip routeOnly the WSL subnet, no Windows VPN adapter

Nếu tuyến duy nhất của container là cầu và tun0 không bao giờ xuất hiện trong không gian tên của nó, không có gì bên dưới giúp được: không thay đổi máy chủ tên nào tạo ra tuyến, và không tuyến nào tạo ra danh tính đường hầm mà đối tác sẽ chấp nhận. Thứ tự quan trọng vì lỗi DNS và lỗi tuyến trông giống hệt nhau từ bên trong shell.

Hướng trình phân giải vào đường hầm thay vì vào một cái công cộng

Trên máy chủ, resolvectl status liệt kê máy chủ DNS theo từng liên kết, và readlink -f /etc/resolv.conf cho bạn biết tệp là stub của systemd, tệp NetworkManager hay thứ gì đó do ứng dụng của nhà cung cấp ghi lại. Bên trong một container trên mạng do người dùng định nghĩa, /etc/resolv.conf đặt tên 127.0.0.11, trình phân giải tích hợp của Docker, thứ chuyển tiếp tới upstream mà daemon được cấu hình. Còn container trên cầu mặc định lại nhận một bản sao tệp của máy chủ khi được tạo và giữ nguyên nó.

Chính bản sao đó là lý do một đường hầm dựng lên sau khi container khởi động lại để container hỏi upstream cũ. Hoặc truyền trình phân giải VPN một cách tường minh bằng docker run --dns 10.8.0.53, hoặc đặt "dns": ["10.8.0.53"] trong /etc/docker/daemon.json rồi khởi động lại daemon, hoặc tạo lại container khi đường hầm đã lên. Tạo lại thường rẻ hơn gỡ lỗi một trình phân giải từng đúng cách đây một giờ.

Đổi sang trình phân giải công cộng là cách sửa giữ nguyên triệu chứng. Nó phân giải internet và hỏng trong mọi vùng riêng, và có thể lặng lẽ bỏ qua việc lọc mà đường hầm tồn tại để thực thi. Nếu bạn thử ghi vào /etc/resolv.conf của một container đang chạy, hãy chờ Device or resource busy: Docker bind-mount tệp đó, nên việc sửa trước hết cần tháo gắn. Đặt --dns lúc tạo là trình tự sống sót qua lần khởi động lại.

Chuyển tiếp và dịch địa chỉ khi container mượn đường hầm

Với cầu mặc định, máy chủ là bộ định tuyến giữa docker0 và tun0, và mặc định nó không làm cả hai việc. Bật chuyển tiếp và dịch địa chỉ nguồn để đối tác thấy địa chỉ đường hầm của máy chủ thay vì một địa chỉ không có tuyến quay về (thay tun0 bằng tên mà ứng dụng của bạn đặt cho giao diện):

sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE
iptables -A FORWARD -i docker0 -o tun0 -j ACCEPT
iptables -A FORWARD -i tun0 -o docker0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

Các quy tắc FORWARD quan trọng trên máy chủ có chính sách chuyển tiếp là DROP. Docker chèn chuỗi riêng của nó, nên hãy thử bằng gói tin thay vì đọc iptables -S: từ container, ip route get 10.8.0.20 phải nêu tên cổng của cầu, và nc -vz 10.8.0.20 443 phải kết nối hoặc hết thời gian ở đường hầm chứ không phải ở trình phân giải.

Không có MASQUERADE, đối tác cần tuyến quay về 172.17.0.0/16, thứ mà hầu hết VPN truy cập từ xa không đẩy xuống. Đây cũng là chỗ ứng dụng dựa trên chính sách phá vỡ kế hoạch: nếu nó chỉ bắt lưu lượng của những UID hoặc cgroups nhất định, các quy tắc chuyển tiếp của bạn không bao giờ thấy gói tin bên trong đường hầm, và cách sửa thuộc về chính sách định tuyến của ứng dụng chứ không phải iptables. Quy tắc thêm bằng tay biến mất khi khởi động lại, nên hãy đặt chúng trong một unit systemd hoặc bảng netfilter-persistent rồi thử lại sau khi khởi động lại, vì một đường hầm lên muộn có thể cuốn chúng đi.

Đặt đường hầm vào bên trong container khi mục tiêu là cô lập

Nếu một container phải ra ngoài qua một nút cụ thể, hãy chạy ứng dụng trong container đó. Nó cần --cap-add NET_ADMIN và --device /dev/net/tun, và khi đó nó sở hữu tun0, tuyến đường và tường lửa của riêng mình. Nếu các container khác đi qua nó, nó cũng cần chuyển tiếp và một quy tắc MASQUERADE bên trong container, và những đối tác đó cần một tuyến hoặc proxy tường minh trỏ tới nó; cầu mặc định của Docker không thêm tuyến đó giúp bạn.

Mạng của máy chủ, docker run --network host, là phiên bản rẻ: container tham gia không gian tên của máy chủ và thấy tuyến đường hầm trực tiếp. Nó cũng bỏ cô lập mạng, mời gọi xung đột cổng, và trên thực tế đây là lựa chọn của Linux. Cổng được công bố không phải là thứ thay thế. -p 8080:80 làm một dịch vụ truy cập được từ máy chủ; nó không làm các địa chỉ nội bộ 10.x truy cập được từ bên trong container, và trộn lẫn hai thứ đó chính là cách một vấn đề định tuyến trở thành một cổng cơ sở dữ liệu hở.

Phân biệt lỗi tên với một mạng con chồng lấn

Cầu mặc định của Docker là 172.17.0.0/16, và nhiều nhóm VPN doanh nghiệp nằm trong 172.16.0.0/12. Khi đường hầm sở hữu dải đó, lưu lượng dành cho container có thể bị định tuyến vào đường hầm. Tên hỏng, lỗi trông như DNS, và vấn đề nằm ở địa chỉ. Hãy kiểm tra đích đến chứ không phải trình phân giải: ip route get 172.17.0.5 trên máy chủ, rồi so sánh tiền tố của cầu với nhóm VPN và với docker network inspect bridge --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'.

Cách sửa là dời một trong hai dải: hoặc một nhóm địa chỉ mặc định khác cho Docker trong daemon.json hoặc một nhóm khác trên bộ tập trung. Thêm một máy chủ tên nữa không làm được gì ở đây, và ssh -b cũng vậy: gắn ứng dụng vào một giao diện đổi địa chỉ nguồn, không đổi tuyến đích trỏ vào đường hầm. Phân chia công việc nhanh: một tên phân giải được trên máy chủ nhưng không trong container là vấn đề trình phân giải, còn một kết nối IP trực tiếp bị treo trong container nhưng chạy trên máy chủ là vấn đề tuyến đường hoặc chuỗi chuyển tiếp.

WSL và Docker Desktop không tuân theo quy tắc của Linux

Ở chế độ NAT mặc định, WSL ngồi sau bộ điều hợp ảo của riêng nó và không nhận các tuyến nội bộ hay hậu tố DNS của VPN Windows, nên trình duyệt chạy được và ping bên trong bản phân phối thì không. Mạng phản chiếu, có trong các bản dựng Windows 11 gần đây, chia sẻ giao diện của máy chủ thay vào đó, nhưng phải bật trong .wslconfig và khởi động lại bản phân phối bằng wsl --shutdown. Hãy xác nhận chế độ đang hoạt động bằng wslinfo --networking-mode trước khi áp dụng cách sửa, vì câu trả lời của NAT sai cho chế độ phản chiếu và ngược lại.

Docker Desktop thêm một lớp nữa. Các container Linux trên Windows ngồi sau một bản triển khai NAT mà trong lịch sử gọi là vpnkit, và đường đi của chúng khác với một máy chủ Linux bản địa. Các container Windows kết nối qua một switch ảo của Hyper-V, và một ứng dụng doanh nghiệp có thể chặn lưu lượng từ switch đó, nên container Linux trên cùng máy có thể tới được mạng trong khi container Windows không có gì. Một switch ngoài gắn vào bộ điều hợp vật lý là cách xử lý thường gặp, và nó đổi danh tính mạng của container theo cách mà chính sách có thể không cho phép.

Trước khi viết thêm một quy tắc, hãy đi qua bốn lớp theo thứ tự và dừng ở chỗ một trong số chúng sai:

  • Bên trong container: ip route có tới được tiền tố riêng không, và /etc/resolv.conf có nêu tên một upstream phân giải được nó không?
  • Trên máy chủ: chuyển tiếp có bật không, NAT có gắn vào giao diện đường hầm không, và chuỗi chuyển tiếp có chấp nhận từ cầu sang đường hầm không?
  • Ở đối tác: nó có biết đường quay về không, hay bạn trông cậy vào NAT để giấu mạng con của container?
  • Trên nền tảng: WSL ở chế độ NAT hay phản chiếu, và container là Linux hay Windows?

Chạy lại từng phép thử trong một container hoặc shell mới. Một tiến trình chạy lâu với câu trả lời đã cache hoặc kết nối đã thiết lập sẽ tiếp tục hỏng sau khi cấu hình đã đúng, và lỗi đó trông đúng như một cách sửa không hiệu quả.

Bài viết liên quan