Saya menghabiskan sebagian besar pagi musim semi ini mengejar kontainer yang tidak bisa menyelesaikan satu pun nama internal, sementara peramban di sebelahnya membuka wiki tanpa keluhan. Tunnel sudah aktif, peramban membuktikannya, dan kontainer itu tetap berada di ruang nama jaringan lain dengan tabel rute berbeda dan resolver berbeda. Berikut adalah urutan yang sekarang saya periksa: tabel rute dulu, resolusi kedua, penerusan ketiga, dan platform host terakhir. Berhenti di langkah pertama yang sudah gagal, karena menambahkan aturan lebih jauh ke bawah tumpukan menyembunyikan batas alih-alih memindahkannya.

Bandingkan ketiga tabel rute sebelum Anda mengubah aturan

Kontainer pada bridge bawaan Docker punya satu rute bawaan yang menunjuk ke gateway docker0, dan tidak tahu bahwa tun0 ada. Host punya tun0, rute yang didorong VPN, dan mungkin aturan kebijakan yang mengirim trafik uji lewat antarmuka berbeda dari yang Anda duga. WSL menambahkan tabel ketiga di belakang adaptor virtualnya sendiri. Cetak ketiganya sebelum menyunting salah satunya.

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

Jika satu-satunya rute kontainer adalah bridge dan tun0 tidak pernah muncul di ruang namanya, tidak ada yang di bawah ini akan membantu: tidak ada perubahan server nama yang membuat rute, dan tidak ada rute yang membuat identitas tunnel yang akan diterima peer. Urutan penting karena kegagalan DNS dan kegagalan rute terlihat sama dari dalam shell.

Arahkan resolver ke tunnel, bukan ke resolver publik

Di host, resolvectl status mencantumkan server DNS per tautan, dan readlink -f /etc/resolv.conf memberi tahu Anda apakah berkas itu stub systemd, berkas NetworkManager, atau sesuatu yang ditulis ulang klien vendor. Di dalam kontainer pada jaringan yang ditentukan pengguna, /etc/resolv.conf menamai 127.0.0.11, resolver tertanam Docker, yang meneruskan ke upstream apa pun yang dipakai saat daemon dikonfigurasi. Kontainer pada bridge bawaan justru mendapat salinan berkas host saat dibuat, dan menyimpannya.

Salinan itulah sebabnya tunnel yang muncul setelah kontainer berjalan membuat kontainer menanyai upstream lama. Entah teruskan resolver VPN secara eksplisit dengan docker run --dns 10.8.0.53, atau setel "dns": ["10.8.0.53"] di /etc/docker/daemon.json dan mulai ulang daemon, atau buat ulang kontainer setelah tunnel aktif. Membuat ulang biasanya lebih murah daripada men-debug resolver yang benar satu jam sebelumnya.

Mengganti dengan resolver publik adalah perbaikan yang mempertahankan gejalanya. Resolver itu menyelesaikan internet dan gagal di setiap zona privat, dan bisa diam-diam melewati penyaringan yang menjadi alasan tunnel ada. Jika Anda mencoba menulis di dalam /etc/resolv.conf kontainer yang berjalan, perkirakan Device or resource busy: Docker melakukan bind-mount berkas itu, jadi penyuntingan perlu unmount dulu. Menetapkan --dns saat pembuatan adalah urutan yang bertahan setelah restart.

Teruskan dan terjemahkan saat kontainer meminjam tunnel

Dengan bridge bawaan, host adalah router antara docker0 dan tun0, dan secara bawaan ia tidak melakukan keduanya. Nyalakan penerusan, dan terjemahkan alamat sumber agar peer melihat alamat tunnel host alih-alih alamat yang tidak punya rute balik (ganti tun0 dengan nama yang klien Anda berikan pada antarmuka):

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

Aturan FORWARD penting pada host yang kebijakan penerusannya DROP. Docker menyisipkan rantainya sendiri, jadi ujilah dengan paket alih-alih membaca iptables -S: dari kontainer, ip route get 10.8.0.20 seharusnya menamai gateway bridge, dan nc -vz 10.8.0.20 443 seharusnya terhubung atau kehabisan waktu di tunnel, bukan di resolver.

Tanpa MASQUERADE peer memerlukan rute balik ke 172.17.0.0/16, yang tidak akan didorong sebagian besar VPN akses jarak jauh. Di sini juga klien berbasis kebijakan merusak rencana: jika ia hanya menangkap trafik dari UID atau cgroups tertentu, aturan penerusan Anda tidak pernah melihat paket di dalam tunnel, dan perbaikannya ada pada kebijakan perutean klien, bukan pada iptables. Aturan yang ditambahkan manual hilang saat reboot, jadi letakkan di unit systemd atau tabel netfilter-persistent dan uji ulang setelah reboot, karena tunnel yang muncul terlambat bisa menyapunya.

Letakkan tunnel di dalam kontainer bila tujuannya isolasi

Jika satu kontainer harus keluar melalui node tertentu, jalankan klien di kontainer itu. Ia membutuhkan --cap-add NET_ADMIN dan --device /dev/net/tun, lalu memiliki tun0, rute, dan firewall sendiri. Jika kontainer lain lewat melaluinya, ia juga butuh penerusan dan aturan MASQUERADE di dalam kontainer, dan peer tersebut butuh rute atau proksi eksplisit yang menunjuk ke sana; bridge bawaan Docker tidak menambahkan rute itu untuk Anda.

Jaringan host, docker run --network host, adalah versi murah: kontainer bergabung dengan ruang nama host dan melihat rute tunnel secara langsung. Ia juga melepas isolasi jaringan, mengundang benturan port, dan dalam praktiknya merupakan opsi Linux. Port yang dipublikasikan bukan pengganti. -p 8080:80 membuat layanan dapat dijangkau dari host; ia tidak membuat alamat internal 10.x dapat dijangkau dari dalam kontainer, dan mencampuradukkan keduanya adalah cara masalah perutean berubah menjadi port basis data yang terbuka.

Bedakan kegagalan nama dari subnet yang tumpang tindih

Bridge bawaan Docker adalah 172.17.0.0/16, dan banyak kumpulan VPN korporat berada di dalam 172.16.0.0/12. Begitu tunnel memiliki rentang itu, trafik yang dimaksudkan untuk kontainer bisa dirutekan ke tunnel. Nama gagal, galatnya tampak seperti DNS, dan masalahnya ada pada alamat. Periksa tujuannya, bukan resolver: ip route get 172.17.0.5 di host, lalu bandingkan prefiks bridge dengan kumpulan VPN dan dengan docker network inspect bridge --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'.

Perbaikannya adalah memindahkan salah satu rentang: entah kumpulan alamat bawaan berbeda untuk Docker di daemon.json atau kumpulan berbeda di konsentrator. Menambahkan server nama lain tidak melakukan apa pun di sini, dan begitu pula ssh -b: mengikat klien ke antarmuka mengubah alamat sumber, bukan rute tujuan yang menunjuk ke tunnel. Pembagian kerja cepat: nama yang terselesaikan di host tetapi tidak di kontainer adalah masalah resolver, sedangkan koneksi IP langsung yang menggantung di kontainer tetapi berfungsi di host adalah masalah rute atau rantai penerusan.

WSL dan Docker Desktop tidak mengikuti aturan Linux

Dalam mode NAT bawaannya, WSL berada di belakang adaptor virtualnya sendiri dan tidak menerima rute internal atau sufiks DNS VPN Windows, itulah sebabnya peramban berfungsi dan ping di dalam distribusi tidak. Jaringan cermin, yang tersedia pada build Windows 11 terbaru, justru berbagi antarmuka host, tetapi harus diaktifkan di .wslconfig dan distribusi dimulai ulang dengan wsl --shutdown. Pastikan mode aktifnya dengan wslinfo --networking-mode sebelum menerapkan perbaikan, karena jawaban NAT salah untuk mode cermin dan sebaliknya.

Docker Desktop menambahkan lapisan lain. Kontainer Linux di Windows berada di belakang implementasi NAT yang secara historis disebut vpnkit, dan jalurnya berbeda dari host Linux asli. Kontainer Windows terhubung melalui switch virtual Hyper-V, dan klien korporat dapat memblokir trafik dari switch itu, sehingga kontainer Linux di mesin yang sama bisa mencapai jaringan sementara kontainer Windows tidak punya apa-apa. Switch eksternal yang terikat ke adaptor fisik adalah solusi umum, dan itu mengubah identitas jaringan kontainer dengan cara yang mungkin tidak diizinkan kebijakan.

Sebelum menulis aturan lain, telusuri keempat lapisan secara berurutan dan berhenti di tempat salah satunya keliru:

  • Di dalam kontainer: apakah ip route mencapai prefiks privat, dan apakah /etc/resolv.conf menamai upstream yang menyelesaikannya?
  • Di host: apakah penerusan aktif, apakah NAT terikat ke antarmuka tunnel, dan apakah rantai penerusan menerima bridge ke tunnel?
  • Di peer: apakah ia tahu jalur balik, atau Anda mengandalkan NAT untuk menyembunyikan subnet kontainer?
  • Di platform: apakah WSL dalam mode NAT atau cermin, dan apakah kontainernya Linux atau Windows?

Jalankan ulang setiap uji di kontainer atau shell yang baru. Proses berumur panjang dengan jawaban tersimpan di cache atau koneksi yang sudah terjalin akan terus gagal setelah konfigurasi benar, dan kegagalan itu tampak persis seperti perbaikan yang tidak berhasil.

Artikel terkait