1. <th id="orwrz"></th>
        <tbody id="orwrz"><pre id="orwrz"></pre></tbody>
      2. <rp id="orwrz"><object id="orwrz"></object></rp>
        首頁 運維干貨怎么從傳統的Linux網絡視角理解容器網絡?

        怎么從傳統的Linux網絡視角理解容器網絡?

        運維派隸屬馬哥教育旗下專業運維社區,是國內成立最早的IT運維技術社區,歡迎關注公眾號:yunweipai
        領取學習更多免費Linux云計算、Python、Docker、K8s教程關注公眾號:馬哥linux運維

        怎么從傳統的Linux網絡視角理解容器網絡?插圖

        使用容器總是感覺像使用魔法一樣。對于那些理解底層原理的人來說容器很好用,但是對于不理解的人來說就是個噩夢。很幸運的是,我們已經研究容器技術很久了,甚至成功揭秘容器只是隔離并受限的Linux進程,運行容器并不需要鏡像,以及另一個方面,構建鏡像需要運行一些容器。

        現在是時候解決容器網絡問題了?;蛘吒鼫蚀_地說,單主機容器網絡問題。本文會回答這些問題:

        • 如何虛擬化網絡資源,讓容器認為自己擁有獨占網絡?
        • 如何讓容器們和平共處,之間不會互相干擾,并且能夠互相通信?
        • 從容器內部如何訪問外部網絡?
        • 從外部世界如何訪問某臺機器上的容器呢(比如,端口發布)?

        最終結果很明顯,單主機容器網絡是已知的Linux功能的簡單組合:

        • 網絡命名空間(namespace)
        • 虛擬Ethernet設備(veth)
        • 虛擬網絡交換機(網橋)
        • IP路由和網絡地址翻譯(NAT)

        并且不需要任何代碼就可以讓這樣的網絡魔法發生……

        前提條件

        任意Linux發行版都可以。本文的所有例子都是在vagrant CentOS 8的虛擬機上執行的:

        $ vagrant init centos/8   
        $ vagrant up   
        $ vagrant ssh   
        
        [vagrant@localhost ~]$ uname -a   
        Linux localhost.localdomain 4.18.0-147.3.1.el8_1.x86_64
        

        為了簡單起見,本文使用容器化解決方案(比如,Docker或者Podman)。我們會重點介紹基本概念,并使用最簡單的工具來達到學習目標。

        network命名空間隔離容器

        Linux網絡棧包括哪些部分?顯然,是一系列網絡設備。還有別的嗎?可能還包括一系列的路由規則。并且不要忘記,netfilter hook,包括由iptables規則定義的。

        我們可以快速創建一個并不復雜的腳本inspect-net-stack.sh:

        #!/usr/bin/env bash   
        echo  "> Network devices"   
        ip link   
        
        echo -e "\\n> Route table"   
        ip route   
        
        echo -e "\\n> Iptables rules"   
        iptables --list-rules
        

        在運行腳本前,讓我們修改下iptable rule:

        $ sudo iptables -N ROOT_NS
        

        這之后,在機器上執行上面的腳本,輸出如下:

        $ sudo ./inspect-net-stack.sh   
            > Network devices   
            1: lo:  mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00   
            2: eth0:  mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000 link/ether 52:54:00:e3:27:77 brd ff:ff:ff:ff:ff:ff   
            > Route table   
            default via 10.0.2.2 dev eth0 proto dhcp metric 100   
            10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 100   
            > Iptables rules   
            -P INPUT ACCEPT   
            -P FORWARD ACCEPT   
            -P OUTPUT ACCEPT   
            -N ROOT_NS
        

        我們對這些輸出感興趣,因為要確保即將創建的每個容器都有各自獨立的網絡棧。

        你可能已經知道了,用于容器隔離的一個Linux命名空間是網絡命名空間(network namespace)。從man ip-netns可以看到,“網絡命名空間是網絡棧邏輯上的另一個副本,它有自己的路由,防火墻規則和網絡設備?!睘榱撕喕鹨?,這是本文使用的唯一的命名空間。我們并沒有創建完全隔離的容器,而是將范圍限制在網絡棧上。

        創建網絡命名空間的一種方法是IP工具——是iproute2的一部分:

        $ sudo ip netns add netns0   
        $ ip netns   
        netns0
        

        如何使用剛才創建的命名空間呢?一個很好用的命令nsenter。進入一個或多個特定的命名空間,然后執行指定的腳本:

        $ sudo nsenter --net=/var/run/netns/netns0 bash  
             # 新建的bash進程在netns0里  
         $ sudo ./inspect-net-stack.sh   
            > Network devices 1: lo:  mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000   
            link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00   
            > Route table   
            > Iptables rules   
            -P INPUT ACCEPT   
            -P FORWARD ACCEPT   
            -P OUTPUT ACCEPT
        
        
        

        從上面的輸出可以清楚地看到bash進程運行在netns0命名空間,這時看到的是完全不同的網絡棧。這里沒有路由規則,沒有自定義的iptables chain,只有一個loopback的網絡設備。

        怎么從傳統的Linux網絡視角理解容器網絡?插圖1

        使用虛擬的Ethernet設備(veth)將容器連接到主機上

        如果我們無法和某個專有的網絡棧通信,那么它看上去就沒什么用。幸運的是,Linux提供了好用的工具——虛擬Ethernet設備。從man veth可以看到,“veth設備是虛擬Ethernet設備。他們可以作為網絡命名空間之間的通道(tunnel),從而創建連接到另一個命名空間里的物理網絡設備的橋梁,但是也可以作為獨立的網絡設備使用?!?/p>

        虛擬Ethernet設備通常都成對出現。不用擔心,先看一下創建的腳本:

        $ sudo ip link add veth0 type veth peer name ceth0
        

        用這條簡單的命令,我們就可以創建一對互聯的虛擬Ethernet設備。默認選擇了veth0和ceth0這兩個名稱。

        $ ip link   
        1: lo:  mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000   
         link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00   
        2: eth0:  mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000  
          link/ether 52:54:00:e3:27:77 brd ff:ff:ff:ff:ff:ff   
        5: ceth0@veth0:  mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000   
          link/ether 66:2d:24:e3:49:3f brd ff:ff:ff:ff:ff:ff   
        6: veth0@ceth0:  mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000   
         link/ether 96:e8:de:1d:22:e0 brd ff:ff:ff:ff:ff:ff
        

        創建的veth0和ceth0都在主機的網絡棧(也稱為root網絡命名空間)上。將netns0命名空間連接到root命名空間,需要將一個設備留在root命名空間,另一個挪到netns0里:

        $ sudo ip link set ceth0 netns netns0   
            # 列出所有設備,可以看到ceth0已經從root棧里消失了   
           $ ip link 1: lo:  mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000   
            link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00   
           2: eth0:  mtu 1500 qdisc fq\_codel state UP mode DEFAULT group default qlen 1000   
           link/ether 52:54:00:e3:27:77 brd ff:ff:ff:ff:ff:ff   
           6: veth0@if5:  mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000   
            link/ether 96:e8:de:1d:22:e0 brd ff:ff:ff:ff:ff:ff link-netns netns0
        

        一旦啟用設備并且分配了合適的IP地址,其中一個設備上產生的包會立刻出現在其配對設備里,從而連接起兩個命名空間。從root命名空間開始:

        $ sudo ip link set veth0 up   
        $ sudo ip addr add 172.18.0.11/16 dev veth0
        

        然后是netns0:

        $ sudo nsenter --net=/var/run/netns/netns0   
        $ ip link set lo up   
        $ ip link set ceth0 up   
        $ ip addr add 172.18.0.10/16 dev ceth0   
        $ ip link   
        1: lo:  mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000   
         link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00   
        5: ceth0@if6:  mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000   
         link/ether 66:2d:24:e3:49:3f brd ff:ff:ff:ff:ff:ff link-netnsid 0
        

        怎么從傳統的Linux網絡視角理解容器網絡?插圖2

        檢查連通性:

             # 在netns0里ping root的 veth0   
         ping -c 2 172.18.0.11   PING 172.18.0.11 (172.18.0.11) 56(84) bytes of data.   64 bytes from 172.18.0.11: icmp_seq=1 ttl=64 time=0.038 ms   64 bytes from 172.18.0.11: icmp_seq=2 ttl=64 time=0.040 ms   --- 172.18.0.11 ping statistics ---   2 packets transmitted, 2 received, 0% packet loss, time 58ms   rtt min/avg/max/mdev = 0.038/0.039/0.040/0.001 ms       # 離開 netns0 exit    
              # 在root命名空間里ping ceth0   
              $ ping -c 2 172.18.0.10   
              PING 172.18.0.10 (172.18.0.10) 56(84) bytes of data.   
              64 bytes from 172.18.0.10: icmp_seq=1 ttl=64 time=0.073 ms   
              64 bytes from 172.18.0.10: icmp_seq=2 ttl=64 time=0.046 ms   
              --- 172.18.0.10 ping statistics ---   
              2 packets transmitted, 2 received, 0% packet loss, time 3ms   
              rtt min/avg/max/mdev = 0.046/0.059/0.073/0.015 ms
        

        同時,如果嘗試從netns0命名空間訪問其他地址,也同樣可以成功:

            # 在 root 命名空間   
           ip addr show dev eth0     2: eth0:  mtu 1500 qdisc fq_codel state UP group default qlen 1000      link/ether 52:54:00:e3:27:77 brd ff:ff:ff:ff:ff:ff      inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic noprefixroute eth0    valid_lft 84057sec preferred_lft 84057sec     inet6 fe80::5054:ff:fee3:2777/64 scope link       valid_lft forever preferred_lft forever      # 記住這里IP是10.0.2.15 sudo nsenter --net=/var/run/netns/netns0   
            # 嘗試ping主機的eth0   
            ping 10.0.2.15      connect: Network is unreachable      # 嘗試連接外網 ping 8.8.8.8   
            connect: Network is unreachable
        

        這也很好理解。在netns0路由表里沒有這類包的路由。唯一的entry是如何到達172.18.0.0/16網絡:

            # 在netns0命名空間:   
            $ ip route   
            172.18.0.0/16 dev ceth0 proto kernel scope link src 172.18.0.10
        

        Linux有好幾種方式建立路由表。其中一種是直接從網絡接口上提取路由。記住,命名空間創建后, netns0里的路由表是空的。但是隨后我們添加了ceth0設備并且分配了IP地址172.18.0.0/16。因為我們使用的不是簡單的IP地址,而是地址和子網掩碼的組合,網絡??梢詮钠渲刑崛〕雎酚尚畔?。目的地是172.18.0.0/16的每個網絡包都會通過ceth0設備。但是其他包會被丟棄。類似的,root命名空間也有了個新的路由:

            # 在root命名空間:   
            $ ip route   
            # ... 忽略無關行 ...   
            172.18.0.0/16 dev veth0 proto kernel scope link src 172.18.0.11
        

        這里,就可以回答第一個問題了。我們了解了如何隔離,虛擬化并且連接Linux網絡棧。

        使用虛擬網絡switch(網橋)連接容器

        容器化思想的驅動力是高效的資源共享。所以,一臺機器上只運行一個容器并不常見。相反,最終目標是盡可能地在共享的環境上運行更多的隔離進程。因此,如果按照上述veth方案,在同一臺主機上放置多個容器的話會發生什么呢?讓我們嘗試添加第二個容器。

        # 從 root 命名空間   
            sudo ip netns add netns1 sudo ip link add veth1 type veth peer name ceth1   
            sudo ip link set ceth1 netns netns1 sudo ip link set veth1 up   
            sudo ip addr add 172.18.0.21/16 dev veth1 sudo nsenter --net=/var/run/netns/netns1   
            ip link set lo up ip link set ceth1 up   
            $ ip addr add 172.18.0.20/16 dev ceth1
        

        檢查連通性:

        # 從netns1無法連通root 命名空間!   
            ping -c 2 172.18.0.21      PING 172.18.0.21 (172.18.0.21) 56(84) bytes of data.      From 172.18.0.20 icmp_seq=1 Destination Host Unreachable      From 172.18.0.20 icmp_seq=2 Destination Host Unreachable      --- 172.18.0.21 ping statistics ---      2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 55ms pipe 2      # 但是路由是存在的! ip route   
            172.18.0.0/16 dev ceth1 proto kernel scope link src 172.18.0.20   
            # 離開 `netns1`   
            exit       # 從 root 命名空間無法連通`netns1` ping -c 2 172.18.0.20   
            PING 172.18.0.20 (172.18.0.20) 56(84) bytes of data.   
            From 172.18.0.11 icmp_seq=1 Destination Host Unreachable   
            From 172.18.0.11 icmp_seq=2 Destination Host Unreachable   
        
        --- 172.18.0.20 ping statistics ---   
        2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 23ms pipe 2   
            # 從netns0可以連通 `veth1`   
            sudo nsenter --net=/var/run/netns/netns0 ping -c 2 172.18.0.21   
            PING 172.18.0.21 (172.18.0.21) 56(84) bytes of data.   
            64 bytes from 172.18.0.21: icmp_seq=1 ttl=64 time=0.037 ms   
            64 bytes from 172.18.0.21: icmp_seq=2 ttl=64 time=0.046 ms   
            --- 172.18.0.21 ping statistics ---   
            2 packets transmitted, 2 received, 0% packet loss, time 33ms   
            rtt min/avg/max/mdev = 0.037/0.041/0.046/0.007 ms   
            # 但是仍然無法連通netns1   
            $ ping -c 2 172.18.0.20   
            PING 172.18.0.20 (172.18.0.20) 56(84) bytes of data.   
            From 172.18.0.10 icmp_seq=1 Destination Host Unreachable   
            From 172.18.0.10 icmp_seq=2 Destination Host Unreachable   
            --- 172.18.0.20 ping statistics ---   
            2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 63ms pipe 2
        

        暈!有地方出錯了……netns1有問題。它無法連接到root,并且從root命名空間里也無法訪問到它。但是,因為兩個容器都在相同的IP網段172.18.0.0/16里,從netns0容器可以訪問到主機的veth1。

        這里花了些時間來找到原因,不過很明顯遇到的是路由問題。先查一下root命名空間的路由表:

        $ ip route   
            # ... 忽略無關行... #   
            172.18.0.0/16 dev veth0 proto kernel scope link src 172.18.0.11   
            172.18.0.0/16 dev veth1 proto kernel scope link src 172.18.0.21
        

        在添加了第二個veth對之后,root的網絡棧知道了新路由172.18.0.0/16 dev veth1 proto kernel scope link src 172.18.0.21,但是之前已經存在該網絡的路由了。當第二個容器嘗試ping veth1時,選中的是第一個路由規則,這導致網絡無法連通。如果我們刪除第一個路由sudo ip route delete 172.18.0.0/16 dev veth0 proto kernel scope link src 172.18.0.11,然后重新檢查連通性,應該就沒有問題了。netns1可以連通,但是netns0就不行了。

        怎么從傳統的Linux網絡視角理解容器網絡?插圖3

        如果我們為netns1選擇其他的網段,應該就都可以連通。但是,多個容器在同一個IP網段上應該是合理的使用場景。因此,我們需要調整veth方案。

        別忘了還有Linux網橋——另一種虛擬化網絡技術!Linux網橋作用類似于網絡switch。它會在連接到其上的接口間轉發網絡包。并且因為它是switch,它是在L2層完成這些轉發的。

        試試這個工具。但是首先,需要清除已有設置,因為之前的一些配置現在不再需要了。刪除網絡命名空間:

        $ sudo ip netns delete netns0   
        $ sudo ip netns delete netns1   
        $ sudo ip link delete veth0   
        $ sudo ip link delete ceth0   
        $ sudo ip link delete veth1   
        $ sudo ip link delete ceth1
        

        快速重建兩個容器。注意,我們沒有給新的veth0和veth1設備分配任何IP地址:

        $ sudo ip netns add netns0   
        $ sudo ip link add veth0 type veth peer name ceth0   
        $ sudo ip link set veth0 up   
        $ sudo ip link set ceth0 netns netns0   
        
        $ sudo nsenter --net=/var/run/netns/netns0   
        $ ip link set lo up   
        $ ip link set ceth0 up   
        $ ip addr add 172.18.0.10/16 dev ceth0   
        $ exit   
        
        $ sudo ip netns add netns1   
        $ sudo ip link add veth1 type veth peer name ceth1   
        $ sudo ip link set veth1 up   
        $ sudo ip link set ceth1 netns netns1   
        
        $ sudo nsenter --net=/var/run/netns/netns1   
        $ ip link set lo up   
        $ ip link set ceth1 up   
        $ ip addr add 172.18.0.20/16 dev ceth1   
        $ exit
        

        確保主機上沒有新的路由:

        $ ip route   
        default via 10.0.2.2 dev eth0 proto dhcp metric 100   
        10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 100
        

        最后創建網橋接口:

        $ sudo ip link add br0 type bridge   
        $ sudo ip link set br0 up
        

        將veth0和veth1接到網橋上:

        $ sudo ip link set veth0 master br0   
        $ sudo ip link set veth1 master br0
        

        怎么從傳統的Linux網絡視角理解容器網絡?插圖4

        檢查容器間的連通性:

        $ sudo nsenter --net=/var/run/netns/netns0   
        $ ping -c 2 172.18.0.20   
        PING 172.18.0.20 (172.18.0.20) 56(84) bytes of data.   
        64 bytes from 172.18.0.20: icmp_seq=1 ttl=64 time=0.259 ms   
        64 bytes from 172.18.0.20: icmp_seq=2 ttl=64 time=0.051 ms   
        --- 172.18.0.20 ping statistics ---   
        2 packets transmitted, 2 received, 0% packet loss, time 2ms   
        rtt min/avg/max/mdev = 0.051/0.155/0.259/0.104 ms  
        
        $ sudo nsenter --net=/var/run/netns/netns1   
        $ ping -c 2 172.18.0.10   
        PING 172.18.0.10 (172.18.0.10) 56(84) bytes of data.   
        64 bytes from 172.18.0.10: icmp_seq=1 ttl=64 time=0.037 ms   
        64 bytes from 172.18.0.10: icmp_seq=2 ttl=64 time=0.089 ms   
        --- 172.18.0.10 ping statistics ---   
        2 packets transmitted, 2 received, 0% packet loss, time 36ms   
        rtt min/avg/max/mdev = 0.037/0.063/0.089/0.026 ms
        

        太好了!工作得很好。用這種新方案,我們根本不需要配置veth0和veth1。只需要在ceth0和ceth1端點分配兩個IP地址。但是因為它們都連接在相同的Ethernet上(記住,它們連接到虛擬switch上),之間在L2層是連通的:

        $ sudo nsenter --net=/var/run/netns/netns0   
        $ ip neigh   
        172.18.0.20 dev ceth0 lladdr 6e:9c:ae:02:60:de STALE   
        $ exit   
        
        $ sudo nsenter --net=/var/run/netns/netns1   
        $ ip neigh   
        172.18.0.10 dev ceth1 lladdr 66:f3:8c:75:09:29 STALE   
        $ exit
        

        太好了,我們學習了如何將容器變成友鄰,讓它們互不干擾,但是又可以連通。

        連接外部世界(IP路由和地址偽裝)

        容器間可以通信。但是它們能和主機,比如root命名空間,通信嗎?

        $ sudo nsenter --net=/var/run/netns/netns0   
        $ ping 10.0.2.15 # eth0 address   
        connect: Network is unreachable
        

        這里很明顯,netns0沒有路由:

        $ ip route   
        172.18.0.0/16 dev ceth0 proto kernel scope link src 172.18.0.10
        

        root命名空間不能和容器通信:

            # 首先使用 exit 離開netns0:   
            ping -c 2 172.18.0.10      PING 172.18.0.10 (172.18.0.10) 56(84) bytes of data.      From 213.51.1.123 icmp_seq=1 Destination Net Unreachable      From 213.51.1.123 icmp_seq=2 Destination Net Unreachable      --- 172.18.0.10 ping statistics ---      2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 3ms ping -c 2 172.18.0.20   
        PING 172.18.0.20 (172.18.0.20) 56(84) bytes of data.   
        From 213.51.1.123 icmp_seq=1 Destination Net Unreachable   
        From 213.51.1.123 icmp_seq=2 Destination Net Unreachable   
        --- 172.18.0.20 ping statistics ---   
        2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 3ms
        

        要建立root和容器命名空間的連通性,我們需要給網橋網絡接口分配IP地址:

        $ sudo ip addr add 172.18.0.1/16 dev br0
        

        一旦給網橋網絡接口分配了IP地址,在主機的路由表里就會多一條路由:

        $ ip route   
            # ...忽略無關行 ...   
            172.18.0.0/16 dev br0 proto kernel scope link src 172.18.0.1   
        
        $ ping -c 2 172.18.0.10   
        PING 172.18.0.10 (172.18.0.10) 56(84) bytes of data.   
        64 bytes from 172.18.0.10: icmp_seq=1 ttl=64 time=0.036 ms   
        64 bytes from 172.18.0.10: icmp_seq=2 ttl=64 time=0.049 ms   
        
        --- 172.18.0.10 ping statistics ---   
        2 packets transmitted, 2 received, 0% packet loss, time 11ms   
        rtt min/avg/max/mdev = 0.036/0.042/0.049/0.009 ms   
        
        $ ping -c 2 172.18.0.20   
        PING 172.18.0.20 (172.18.0.20) 56(84) bytes of data.   
        64 bytes from 172.18.0.20: icmp_seq=1 ttl=64 time=0.059 ms   
        64 bytes from 172.18.0.20: icmp_seq=2 ttl=64 time=0.056 ms   
        
        --- 172.18.0.20 ping statistics ---   
        2 packets transmitted, 2 received, 0% packet loss, time 4ms   
        rtt min/avg/max/mdev = 0.056/0.057/0.059/0.007 ms
        

        容器可能也可以ping網橋接口,但是它們還是無法連接到主機的eth0。需要為容器添加默認的路由:

        $ sudo nsenter --net=/var/run/netns/netns0   
        $ ip route add default via 172.18.0.1   
        $ ping -c 2 10.0.2.15   
        PING 10.0.2.15 (10.0.2.15) 56(84) bytes of data.   
        64 bytes from 10.0.2.15: icmp_seq=1 ttl=64 time=0.036 ms   
        64 bytes from 10.0.2.15: icmp_seq=2 ttl=64 time=0.053 ms   
        --- 10.0.2.15 ping statistics ---   
        2 packets transmitted, 2 received, 0% packet loss, time 14ms   
        rtt min/avg/max/mdev = 0.036/0.044/0.053/0.010 ms   
            # 為\`netns1\`也做上述配置
        

        這個改動基本上把主機變成了路由,并且網橋接口變成了容器間的默認網關。

        怎么從傳統的Linux網絡視角理解容器網絡?插圖5

        很好,我們將容器連接到root命名空間上?,F在,繼續嘗試將它們連接到外部世界。Linux上默認disable了網絡包轉發(比如,路由功能)。我們需要先啟用這個功能:

            # 在 root 命名空間   
            sudo bash -c 'echo 1 > /proc/sys/net/ipv4/ip_forward'
        

        再次檢查連通性:

        $ sudo nsenter --net=/var/run/netns/netns0   
        $ ping 8.8.8.8   
            # hung住了...
        

        還是不工作。哪里弄錯了呢?如果容器可以向外部發包,那么目標服務器無法將包發回容器,因為容器的IP地址是私有的,那個特定IP的路由規則只有本地網絡知道。并且有很多容器共享的是完全相同的私有IP地址172.18.0.10。這個問題的解決方法稱為網絡地址翻譯(NAT)。

        在到達外部網絡之前,容器發出的包會將源IP地址替換為主機的外部網絡地址。主機還會跟蹤所有已有的映射,會在將包轉發回容器之前恢復之前被替換的IP地址。聽上去很復雜,但是有一個好消息!iptables模塊讓我們只需要一條命令就可以完成這一切:

        $ sudo iptables -t nat -A POSTROUTING -s 172.18.0.0/16 ! -o br0 -j MASQUERADE
        

        命令非常簡單。在nat表里添加了一條POSTROUTING chain的新路由,會替換偽裝所有源于172.18.0.0/16網絡的包,但是不通過網橋接口。

        檢查連通性:

        $ sudo nsenter --net=/var/run/netns/netns0   
        $ ping -c 2 8.8.8.8 PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.   
        64 bytes from 8.8.8.8: icmp_seq=1 ttl=61 time=43.2 ms   
        64 bytes from 8.8.8.8: icmp_seq=2 ttl=61 time=36.8 ms   
        --- 8.8.8.8 ping statistics ---   
        2 packets transmitted, 2 received, 0% packet loss, time 2ms   
        rtt min/avg/max/mdev = 36.815/40.008/43.202/3.199 ms
        

        要知道這里我們用的默認策略——允許所有流量,這在真實的環境里是非常危險的。主機的默認iptables策略是ACCEPT:

        sudo iptables -S   
        -P INPUT ACCEPT   
        -P FORWARD ACCEPT   
        -P OUTPUT ACCEPT
        

        Docker默認限制所有流量,隨后僅僅為已知的路徑啟用路由。

        如下是在CentOS 8機器上,單個容器暴露了端口5005時,由Docker daemon生成的規則:

        $ sudo iptables -t filter --list-rules   
        -P INPUT ACCEPT   
        -P FORWARD DROP   
        -P OUTPUT ACCEPT   
        -N DOCKER   
        -N DOCKER-ISOLATION-STAGE-1   
        -N DOCKER-ISOLATION-STAGE-2   
        -N DOCKER-USER   
        -A FORWARD -j DOCKER-USER   
        -A FORWARD -j DOCKER-ISOLATION-STAGE-1   
        -A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT  
        -A FORWARD -o docker0 -j DOCKER   
        -A FORWARD -i docker0 ! -o docker0 -j ACCEPT   
        -A FORWARD -i docker0 -o docker0 -j ACCEPT   
        -A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 5000 -j ACCEPT   
        -A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2   
        -A DOCKER-ISOLATION-STAGE-1 -j RETURN   
        -A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP   
        -A DOCKER-ISOLATION-STAGE-2 -j RETURN   
        -A DOCKER-USER -j RETURN   
        
        $ sudo iptables -t nat --list-rules   
        -P PREROUTING ACCEPT   
        -P INPUT ACCEPT   
        -P POSTROUTING ACCEPT   
        -P OUTPUT ACCEPT   
        -N DOCKER   
        -A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER   
        -A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE   
        -A POSTROUTING -s 172.17.0.2/32 -d 172.17.0.2/32 -p tcp -m tcp --dport 5000 -j MASQUERADE  
        -A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER   
        -A DOCKER -i docker0 -j RETURN   
        -A DOCKER ! -i docker0 -p tcp -m tcp --dport 5005 -j DNAT --to-destination 172.17.0.2:5000   
        
        $ sudo iptables -t mangle --list-rules   
        -P PREROUTING ACCEPT   
        -P INPUT ACCEPT   
        -P FORWARD ACCEPT   
        -P OUTPUT ACCEPT  
         -P POSTROUTING ACCEPT   
        
        $ sudo iptables -t raw --list-rules   
        -P PREROUTING ACCEPT   
        -P OUTPUT ACCEPT
        

        讓外部世界可以訪問容器(端口發布)

        大家都知道可以將容器端口發布給一些(或者所有)主機的接口。但是端口發布到底是什么意思呢?

        假設容器內運行著服務器:

        $ sudo nsenter --net=/var/run/netns/netns0   
        $ python3 -m http.server --bind 172.18.0.10 5000
        

        如果我們試著從主機上發送一個HTTP請求到這個服務器,一切都工作得很好(root命名空間和所有容器接口之間有鏈接,當然可以連接成功):

            # 從 root 命名空間   
            $ curl 172.18.0.10:5000   
        
            # ... 忽略無關行 ...
        

        但是,如果要從外部訪問這個服務器,應該使用哪個IP呢?我們知道的唯一IP是主機的外部接口地址eth0:

        $ curl 10.0.2.15:5000   
        curl: (7) Failed to connect to 10.0.2.15 port 5000: Connection refused
        

        因此,我們需要找到方法,能夠將到達主機eth05000端口的所有包轉發到目的地172.18.0.10:5000。又是iptables來幫忙!

        # 外部流量    
            sudo iptables -t nat -A PREROUTING -d 10.0.2.15 -p tcp -m tcp --dport 5000 -j DNAT --to-destination 172.18.0.10:5000   
            # 本地流量 (因為它沒有通過 PREROUTING chain)   
            sudo iptables -t nat -A OUTPUT -d 10.0.2.15 -p tcp -m tcp --dport 5000 -j DNAT --to-destination 172.18.0.10:5000
        

        另外,需要讓iptables能夠在橋接網絡上截獲流量:

        sudo modprobe br_netfilter
        

        測試:

        curl 10.0.2.15:5000   
        
              # ... 忽略無關行 ...
        

        理解Docker網絡驅動

        我們可以怎么使用這些知識呢?比如,可以試著理解Docker網絡模式。

        從–network host模式開始。試著比較一下命令ip link和sudo docker run -it –rm –network host alpine ip link的輸出。它們幾乎一樣!在host模式下,Docker簡單地沒有使用網絡命名空間隔離,容器就在root網絡命名空間里工作,并且和主機共享網絡棧。

        下一個模式是–network none。sudo docker run -it –rm –network host alpine ip link的輸出只有一個loopback網絡接口。這和之前創建的網絡命名空間,沒有添加veth設備前很相似。

        最后是–network bridge(默認)模式。這正是我們前文嘗試創建的模式。大家可以試試ip 和iptables命令,分別從主機和容器的角度觀察一下網絡棧。

        rootless容器和網絡

        podman容器管理器的一個很好的特性是關注于rootless容器。但是,你可能注意到,本文使用了很多sudo命令。說明,沒有root權限無法配置網絡。Podman在root網絡上的方案和Docker非常相似。但是在rootless容器上,Podman使用了slirp4netns項目:

        從Linux 3.8開始,非特權用戶可以創建user_namespaces(7)的同時創建network_namespaces(7)。但是,非特權網絡命名空間并不是很有用,因為在主機和網絡命名空間之間創建veth(4)仍然需要root權限。

        slirp4netns可以用完全非特權的方式將網絡命名空間連接到Internet上,通過網絡命名空間里的一個TAP設備連接到用戶態的TCP/IP棧(slirp)。

        rootless網絡是很有限的:“從技術上說,容器本身沒有IP地址,因為沒有root權限,無法實現網絡設備的關聯。另外,從rootless容器ping是不會工作的,因為它缺少CAP_NET_RAW安全能力,而這是ping命令必需的?!钡撬匀槐韧耆珱]有連接要好。

        結論

        本文介紹的組織容器網絡的方案僅僅是可能方案的一種(可能是最為廣泛使用的一種)。還有很多別的方式,由官方或者第三方插件實現,但是所有這些方案都嚴重依賴于Linux網絡虛擬化技術。因此,容器化可以認為是一種虛擬化技術。

        本文鏈接:http://www.abandonstatusquo.com/40888.html

        網友評論comments

        發表評論

        您的電子郵箱地址不會被公開。

        暫無評論

        Copyright ? 2012-2022 YUNWEIPAI.COM - 運維派 京ICP備16064699號-6
        掃二維碼
        掃二維碼
        返回頂部
        十分钟免费观看视频高清下载