ทราฟฟิกของคอนเทนเนอร์ Docker เลี่ยง VPN ของโฮสต์: วิธีตรวจสอบ
VPN ของโฮสต์ครอบคลุมเพียงเนมสเปซเครือข่ายของโฮสต์เท่านั้น ใช้การตรวจสอบเหล่านี้เพื่อดูว่าทราฟฟิกของคอนเทนเนอร์บนบริดจ์เข้าสู่ทันเนลหรือออกไปโดยตรง
เบราว์เซอร์บนโฮสต์แสดงที่อยู่ขาออกของ VPN คอนเทนเนอร์รายงานการเชื่อมต่อที่บ้านของคุณ และมีคนเรียกสิ่งนั้นว่าการรั่วไหล ไม่มีอะไรรั่วไหล ทันเนลของโฮสต์คือชุดของอินเทอร์เฟซ เส้นทาง และกฎที่อยู่ในเนมสเปซเครือข่ายของโฮสต์ ส่วนคอนเทนเนอร์บนบริดจ์เป็นเนมสเปซอีกอันที่มีเส้นทางเริ่มต้นของตัวเอง ผมเคยนั่งอยู่บนสแตก Compose ที่ไคลเอนต์ VPN เชื่อมต่อมาหลายสัปดาห์และไม่มีแพ็กเก็ตของคอนเทนเนอร์แม้แต่แพ็กเก็ตเดียวที่เคยเข้าไปในนั้น
ตรวจสอบเนมสเปซก่อนไอคอนในถาดระบบ
เนมสเปซคือข้อโต้แย้งทั้งหมด เมื่อ Docker เริ่มคอนเทนเนอร์บนเครือข่ายบริดจ์ มันจะสร้างเนมสเปซเครือข่าย เชื่อมปลายด้านหนึ่งของคู่ veth เข้าไปข้างใน และให้ที่อยู่กับเส้นทางเริ่มต้นแก่เนมสเปซนั้น อินเทอร์เฟซของทันเนล ไม่ว่าจะเรียกชื่ออะไร ก็อาศัยอยู่ในเนมสเปซของโฮสต์ ทราฟฟิกในคอนเทนเนอร์มองไม่เห็นมัน และไม่มีการตั้งค่าไคลเอนต์ใดเปลี่ยนสิ่งนี้จากภายนอก
มีสองวิธีที่รองรับเพื่อพาคุณออกจากการแยกตัว --network host วางคอนเทนเนอร์ไว้ในเนมสเปซของโฮสต์ จึงสืบทอดเส้นทางของโฮสต์และทันเนลตามมา --network container:vpn แชร์เนมสเปซของคอนเทนเนอร์อื่น และนั่นคือวิธีที่การออกแบบ sidecar ส่วนใหญ่ส่งเวิร์กโหลดผ่านคอนเทนเนอร์ VPN ทั้งสองเป็นทางเลือกที่ชัดเจน โดยปกติมีเพียงหนึ่งเดียวที่เป็นสิ่งที่ผู้ติดตั้งตั้งใจไว้
โหมดโฮสต์ไม่ใช่การอัปเกรดฟรี คอนเทนเนอร์หมดที่อยู่ของตัวเอง การเผยแพร่พอร์ตใช้ไม่ได้อีก และโปรเซสที่ถือ NET_ADMIN อยู่ที่นั่นสามารถเขียนเส้นทางของโฮสต์ใหม่ได้ การยอมรับสิ่งนี้อาจเป็นการตัดสินใจที่ถูกต้อง การสันนิษฐานว่ามันเปิดอยู่แล้วคือสาเหตุที่ผู้คนใช้เวลาทั้งเย็นแก้ปัญหาทันเนลที่ไม่เคยอยู่ในเส้นทางเลย
ตามแพ็กเก็ตออกจากบริดจ์
คอนเทนเนอร์บนบริดจ์ส่งแพ็กเก็ตไปยังเกตเวย์เริ่มต้น ซึ่งก็คืออินเทอร์เฟซบริดจ์บนโฮสต์ จากนั้นโฮสต์จะส่งต่อแพ็กเก็ตและในการกำหนดค่าเริ่มต้นจะแปลที่อยู่ต้นทางเป็นของตัวเองก่อนที่แพ็กเก็ตจะออกไป ขั้นตอนการแปลนั้นเองที่ทำให้แนวนโยบายพ่ายแพ้อย่างเงียบ ๆ
เมื่อแพ็กเก็ตไปถึงปลายทางของทันเนล มันพาที่อยู่ของโฮสต์ไปด้วย กฎใด ๆ เส้นทางนโยบาย หรือการตัดสินใจเรื่องทันเนลแยกส่วนที่อิงกับที่อยู่เดิมของคอนเทนเนอร์จะไม่ตรงกันอีกต่อไป และคอนเทนเนอร์สองตัวที่ตั้งใจให้ออกผ่านทางออกต่างกันก็แยกจากกันด้วยต้นทางเพียงอย่างเดียวไม่ได้ หากคุณต้องการกำหนดเส้นทางต่อคอนเทนเนอร์ การตัดสินใจต้องเกิดขึ้นก่อนการแปลหรือภายในเนมสเปซที่คุณควบคุม
Docker ดูแลกฎเหล่านี้เอง ผ่านแบ็กเอนด์ไฟร์วอลล์ใดก็ตามที่เดมอนถูกกำหนดค่าไว้ กฎที่เขียนด้วยมือภายนอกฮุกที่รองรับสำหรับทราฟฟิกที่ส่งต่อ DOCKER-USER อาจถูกจัดเรียงใหม่หรือถูกทิ้งเมื่อเดมอนรีสตาร์ตหรือโฮสต์อัปเกรด เอกสารของ Docker เกี่ยวกับการกรองแพ็กเก็ต แสดงลำดับของเชน ประเด็นในทางปฏิบัติคือทุกกฎต้องมีเจ้าของและมีเส้นทางการโหลดใหม่ที่คุณทดสอบจริง ๆ แล้ว
ทดสอบสองระบบแยกกัน
หลักฐานจากเนมสเปซหนึ่งไม่ได้บอกอะไรเกี่ยวกับอีกเนมสเปซหนึ่ง คำขอที่รันภายในคอนเทนเนอร์ VPN พิสูจน์ว่าทันเนลทำงานสำหรับคอนเทนเนอร์นั้น ไม่ใช่หลักฐานเกี่ยวกับโฮสต์ และคำสั่งเดียวกันที่รันจากคอนเทนเนอร์ --network host ก็ไม่ใช่หลักฐานเกี่ยวกับบริดจ์
# host namespace
ip route get 1.1.1.1
ip -brief addr show
# a fresh container on the same bridge network
docker run --rm --network appnet alpine sh -c 'ip route; cat /etc/resolv.conf; ip -6 route'
จากนั้นทำการทดสอบซ้ำภายในคอนเทนเนอร์แอปพลิเคชันจริง ไม่ใช่ในอิมเมจที่คล้ายกัน แอปอาจมีเส้นทางเพิ่มเติม ตัวแปรสภาพแวดล้อมพร็อกซี หรือตัวแก้ไขชื่ออื่น และแต่ละอย่างก็เปลี่ยนคำตอบ ตรวจสอบการแก้ชื่อและตระกูลที่อยู่ที่สองทุกครั้ง คอนเทนเนอร์บนบริดจ์ที่ผู้ใช้กำหนดจะถูกแก้ผ่านตัวแก้ไขในตัวของ Docker ที่ 127.0.0.11 และเส้นทางเริ่มต้น IPv6 บนบริดจ์สามารถพาทราฟฟิกออกไปได้ในขณะที่คุณกำลังยุ่งอยู่กับการทดสอบทันเนลที่เป็น IPv4 เท่านั้น
อ่านรูปแบบก่อนจะแตะข้อมูลรับรอง
เมื่อโฮสต์ทำงานแต่คอนเทนเนอร์ไม่ทำงาน สาเหตุเกือบทุกครั้งเป็นเชิงโครงสร้าง และการหมุนคีย์หรือติดตั้งไคลเอนต์ใหม่ก็เสียเวลาช่วงบ่ายไปเปล่า ๆ จัดเรียงอาการก่อนแล้วการแก้ไขมักจะบอกชื่อตัวเอง
| What you see | Usual cause | First check |
|---|---|---|
| Host exits through the VPN, container shows the ISP address | The container never entered the tunnel | Default route inside the container |
| Container works until the daemon restarts, then stops | Rules outside the supported hook, or a changed backend | Daemon's reported firewall backend and DOCKER-USER |
| Container loses internal hosts after the tunnel comes up | VPN prefix overlaps the Docker bridge subnet | ip route on the host, then docker network inspect |
| Small replies arrive, large ones hang | Tunnel MTU or missing MSS clamping | Link MTU on both ends of the tunnel |
| Container falls back to the ISP address when the tunnel drops | Nothing enforces the kill switch for forwarded traffic | Stop the tunnel with a request in flight |
แถวการทับซ้อนสมควรได้ประโยคของตัวเอง โปรไฟล์ VPN ที่ดันเส้นทางสำหรับ 172.17.0.0/16 หรือสำหรับ 10.0.0.0/8 ที่บังเอิญมีคำนำหน้าของบริดจ์ของคุณอยู่ จะทำให้ทันเนลอ้างสิทธิ์ซับเน็ตของคอนเทนเนอร์ และคำตอบที่ส่งไปยังที่อยู่ที่ถูกแปลจะไปผิดทิศหรือหายไป การเปลี่ยนเลขเครือข่าย Docker มักสะอาดกว่าการสะสมข้อยกเว้น
ทำให้ความครอบคลุมชัดเจน แล้วจงใจทำให้มันพัง
การออกแบบสองแบบทนทานต่อการตรวจทาน แชร์เนมสเปซของคอนเทนเนอร์ VPN แล้วทุกเวิร์กโหลดที่ครอบคลุมจะใช้ทันเนลเดียวโดยมีขอบเขตการรีสตาร์ตเดียว ซึ่งง่ายจนกว่าบริการหนึ่งจะต้องการทางออกอื่นหรือเส้นทาง DNS ของตัวเอง หรือ กำหนดเส้นทางเครือข่ายคอนเทนเนอร์ไปยังเกตเวย์ VPN บนโฮสต์ เปิดการส่งต่อ คำนำหน้าของบริดจ์ชี้ไปที่ทันเนล ทราฟฟิกขากลับถูกจัดการอย่างชัดเจน สิ่งนี้ซื้อการควบคุมต่อเครือข่ายและแลกกับชุดกฎที่คุณต้องดูแลตลอดการอัปเกรดเดมอนและไคลเอนต์
ไม่ว่าจะทางไหน การทดสอบที่สำคัญคือกรณีล้มเหลว หยุดทันเนลในขณะที่คำขอของคอนเทนเนอร์กำลังลอยอยู่และดูว่าแพ็กเก็ตไปที่ไหน หากมันกลับไปใช้เส้นทางตรง ความครอบคลุมไม่เคยถูกบังคับใช้ เพราะ kill switch ของไคลเอนต์ควบคุมทราฟฟิกของไคลเอนต์เอง ไม่ใช่แพ็กเก็ตที่ส่งต่อจากบริดจ์ จากนั้นรีสตาร์ตเดมอนและโฮสต์และยืนยันผลลัพธ์เป็นครั้งที่สอง นั่นคือช่วงเวลาที่กฎที่เขียนด้วยมือหายไป
ก่อนจะบอกว่าเสร็จแล้ว ให้ยืนยันสี่อย่าง อินเทอร์เฟซของทันเนลอยู่ในเนมสเปซเดียวกับทราฟฟิก การทดสอบคอนเทนเนอร์รันภายในคอนเทนเนอร์จริง มีการตรวจสอบการแก้ชื่อและตระกูลที่อยู่ทั้งสอง และการทดสอบความล้มเหลวของทันเนลยังคงล้มเหลวแบบปิดอยู่ จากนั้นบันทึกแบ็กเอนด์ไฟร์วอลล์ เวอร์ชันไคลเอนต์ และเจ้าของของแต่ละกฎ บันทึกนั้นคือสิ่งที่ป้องกันไม่ให้การอัปเกรดครั้งถัดไปทิ้งส่วนผสมของสมมติฐานเก่าที่ไม่ได้บันทึกไว้