摘要:一直以來,網絡都是容器中令人頭疼的問題。本文的主要目的是帶你解決容器網絡問題,讓你不再對它恐懼。或者,更準確地說,是單主機容器網絡問題。與其創建完全隔離的容器,不如將范圍限制在網絡堆棧中。
一直以來,網絡都是容器中令人頭疼的問題。本文的主要目的是帶你解決容器網絡問題,讓你不再對它恐懼。
使用容器總是感覺像變魔術一樣。對那些了解其內部原理的人來說,它是一種很好的方式;而對那些不了解其內部原理的人來說,這是一種可怕的方式。幸運的是,我們研究容器化技術的內部原理已經很長一段時間了。我們甚至發現,容器只是隔離的、受限制的 Linux 進程,鏡像并不是運行容器所必須的,相反——要構建一個鏡像,我們需要運行一些容器。
現在,讓我們來解決下容器網絡問題。或者,更準確地說,是單主機容器網絡問題。在本文中,我們將回答以下問題:
因此,很明顯,單主機容器網絡只不過是一些眾所周知的 Linux 工具的簡單組合:
不管怎樣,不需要任何代碼就可以讓網絡魔法發生……
任何還算不錯的 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)。相反,我們將關注基本概念,并使用最簡單的工具來實現我們的學習目標。
Linux 網絡堆棧是由什么組成的?很明顯,是網絡設備的集合。還有什么?可能是路由規則集。不要忘了還有 netfilter 鉤子集,包括由 iptables 規則定義的。
我們可以快速創建一個不是很完善的inspect-net-stack.sh腳本:
#!/usr/bin/env bash
echo "> Network devices"
ip link
echo -e "
> Route table"
ip route
echo -e "
> Iptables rules"
iptables --list-rules
在運行它之前,讓我們稍微修改下 iptables 規則,讓其更容易識別:
$ sudo iptables -N ROOT_NS
之后,在我的機器上執行 inspect 腳本會產生以下輸出:
$ 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
如何開始使用剛剛創建的命名空間?有一個可愛的 Linux 命令叫做nsenter。它輸入一個或多個指定的名稱空間,然后執行給定的程序:
$ sudo nsenter --net=/var/run/netns/netns0 bash
# The newly created bash process lives in 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
從上面的輸出可以清楚地看出,在netns0命名空間內運行的 bash 進程看到的是一個完全不同的網絡堆棧。沒有路由規則,沒有自定義 iptables 鏈,只有一個環回網絡設備。到目前為止,一切順利……
如果我們不能與一個專用的網絡堆棧通信,那么它就沒那么有用了。幸運的是,Linux 為此提供了一個合適工具——虛擬以太網設備!按照man veth的說法,“veth 設備是虛擬以太網設備。它們可以作為網絡命名空間之間的隧道,創建一個連接到另一個命名空間中物理網絡設備的橋,但也可以作為獨立的網絡設備使用。”
虛擬以太網設備總是成對出現。不用擔心,讓我們看一下創建命令就會明白了:
$ sudo ip link add veth0 type veth peer name ceth0
通過這個命令,我們剛剛創建了一對相互連接的虛擬以太網設備。名稱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都駐留在主機的網絡堆棧(也稱為根網絡命名空間)上。為了連接根命名空間和netns0命名空間,我們需要將一個設備保留在根命名空間中,并將另一個設備移到netns0中:
$ sudo ip link set ceth0 netns netns0
# List all the devices to make sure one of them disappeared from the root stack
$ 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 地址,任何出現在其中一臺設備上的數據包都會立即出現在連接兩個命名空間的對端設備上。讓我們從根命名空間開始:
$ sudo ip link set veth0 up
$ sudo ip addr add 172.18.0.11/16 dev veth0
接下來是etns0:
$ sudo nsenter --net=/var/run/netns/netns0
$ ip link set lo up # whoops
$ 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
通過 veth 設備連接網絡命名空間
現在可以檢查下連接了:
# From `netns0` ping roots 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
# Leave `netns0`
$ exit
# From root namespace 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命名空間訪問任何其他地址,都會失敗:
# Inside root namespace
$ 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
# Remember this 10.0.2.15
$ sudo nsenter --net=/var/run/netns/netns0
# Try hosts eth0
$ ping 10.0.2.15
connect: Network is unreachable
# Try something from the Internet
$ ping 8.8.8.8
connect: Network is unreachable
不過,這很容易解釋。對于這樣的數據包,在netns0的路由表中沒有路由。其中,唯一的條目顯示了如何到達172.18.0.0/16網絡:
# From `netns0` namespace:
$ 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.10/16。由于我們使用的不是一個簡單的 IP 地址,而是地址和網絡掩碼的組合,網絡堆棧會設法從中提取路由信息。每個發往172.18.0.0/16網絡的數據包將通過ceth0設備發送。但是任何其他的包都會被丟棄。類似地,在根命名空間中有一條新路由:
# From `root` namespace:
$ ip route
# ... omited lines ...
172.18.0.0/16 dev veth0 proto kernel scope link src 172.18.0.11
現在,我們已經回答了我們的第一個問題。我們現在知道了如何隔離、虛擬化和連接 Linux 網絡堆棧。
容器化的整個理念可以歸結為有效的資源共享。也就是說,每臺機器一個容器的情況并不常見。相反,我們的目標是在共享環境中運行盡可能多的隔離進程。那么,如果我們按照上面的veth方法將多個容器放在同一主機上,會發生什么呢?讓我們添加第二個容器:
# From root namespace
$ 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
我最喜歡的部分,檢查連接:
# From `netns1` we cannot reach the root namespace!
$ 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
# But there is a route!
$ ip route
172.18.0.0/16 dev ceth1 proto kernel scope link src 172.18.0.20
# Leaving `netns1`
$ exit
# From root namespace we cannot reach the `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
# From `netns0` we CAN reach `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
# But we still cannot reach `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遇到問題。由于某些原因,它不能與根通信,我們也不能從根命名空間訪問它。然而,由于兩個容器都位于同一個 IP 網絡 172.18.0.0/16 中,我們現在可以從netns0容器與主機的veth1進行通信。非常有趣……
我花了些時間才想明白,但顯然我們面臨的是路由沖突。讓我們檢查下根命名空間中的路由表:
$ ip route
# ... omited lines ... #
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對后,根的網絡堆棧學習到了新的路由172.18.0.0/16 dev veth1 proto kernel scope li
我相信,如果我們為netns1選擇另一個 IP 網絡,一切就沒問題了。然而,多個容器位于一個 IP 網絡中是一個合理的用例。因此,我們需要以某種方式調整veth方法…
看看 Linux 網橋——另一種虛擬網絡設施!Linux 網橋的行為就像一個網絡交換機。它會在連接到它的接口之間轉發數據包。因為它是一個交換機,所以它是在 L2(即以太網)層完成這項工作的。
讓我們試著操作下吧。但首先,我們需要清理現有的設置,因為到目前為止,我們所做的一些配置更改實際上已經不再需要了。刪除網絡命名空間就足夠了:
$ sudo ip netns delete netns0
$ sudo ip netns delete netns1
# But if you still have some leftovers...
$ 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
然后檢查容器之間的連接:
$ 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 地址。但是,由于它們都在同一個以太網段(記住,我們將它們連接到虛擬交換機),所以 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
恭喜,我們學會了 如何將容器變成友好的鄰居,防止它們相互干擾,并保持連接性。
容器之間可以通信了。但它們可以和主機(即根命名空間)通信嗎?
$ 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
根命名空間也不能和容器通信:
# Use exit to leave `netns0` first:
$ 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
為了在根命名空間和容器命名空間之間建立連接,我們需要為網橋網絡接口分配 IP 地址:
$ sudo ip addr add 172.18.0.1/16 dev br0
一旦我們給網橋接口分配了 IP 地址,我們的主機路由表上就會多一條路由:
$ ip route
# ... omitted lines ...
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
# And repeat the change for `netns1`
這項更改基本上把主機變成了路由器,網橋接口成了容器的默認網關。
很好,我們將容器與根命名空間連接起來了。現在,讓我們嘗試將它們與外部世界連接起來。默認情況下,在 Linux 中數據包轉發(即路由器功能)是禁用的。我們需要打開它:
# In the root namespace
sudo bash -c echo 1 > /proc/sys/net/ipv4/ip_forward
又到我最喜歡的部分了,檢查連接:
$ sudo nsenter --net=/var/run/netns/netns0
$ ping 8.8.8.8
# hangs indefinitely long for me...
還是不行。我們漏了什么嗎?如果容器向外部世界發送數據包,那么目標服務器將不能將數據包發送回容器,因為容器的 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
這個命令相當簡單。我們正在向POSTROUTING鏈的nat表添加一條新規則,要求偽裝所有源自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
注意,我們遵循的是默認允許(by default - allow)策略,這在現實世界中可能相當危險。對于每個鏈,主機默認的 iptables 策略都是ACCEPT:
sudo iptables -S
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
相反,作為一個很好的例子,Docker 默認限制了一切,然后只啟用已知路徑的路由。以下是在 CentOS 8 機器上(在 5005 端口上暴露了單個容器)Docker 守護進程生成的轉儲規則:
$ 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 RELATEDESTABLISHED -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 請求,一切都沒問題(好吧,根命名空間和所有容器接口之間都有連接,為什么沒有呢?):
# From root namespace
$ curl 172.18.0.10:5000
# ... omited lines ...
但是,如果我們要從外部訪問該服務器,我們將使用哪個 IP 地址?我們知道的唯一 IP 地址可能是主機的外部接口地址eth0:
$ curl 10.0.2.15:5000
curl: (7) Failed to connect to 10.0.2.15 port 5000: Connection refused
因此,我們需要找到一種方法,將任何到達主機eth0接口 5000 端口的數據包轉發到目的地172.18.0.10:5000。或者,換句話說,我們需要在主機的eth0接口上發布容器的 5000 端口。iptables 拯救了我們!
# External traffic
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
# Local traffic (since it doesnt pass the 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
# ... omited lines ...
好的,我們能用這些無用的知識做什么呢?例如,我們可以試著理解一些 Docker 網絡模式!
https://docs.docker.com/network/#network-drivers
讓我們從--network host模式開始。試著比較下命令ip li
下一個模式是--network none。sudo docker run -it --rm --network none alpine ip li
最后但同樣重要的是--network bridge(默認)模式。這正是我們在整篇文章中試圖再現的。我建議你試用下ip和iptables命令,并從主機和容器的角度檢查網絡堆棧。
podman容器管理器的一個很好的特性是針對無根容器的。然而,你可能已經注意到,我們在本文中使用了大量sudo升級。換句話說,權限就不可能配置網絡。Podman 的 rootfull 網絡方法和 docker 非常接近。
https://www.redhat.com/sysadmin/container-networking-podman
但是當涉及到無根容器時,podman 依賴于 slirp4netns 項目:
從 Linux 3.8 開始,非特權用戶可以創建 network_namespaces(7) 和 user_namespaces(7) 了。但是,非特權網絡命名空間并不是很有用,因為在主機和網絡命名空間之間創建 veth(4) 對仍然需要 root 特權。(即沒有網絡連接)
通過將網絡命名空間中的 TAP 設備連接到用戶模式 TCP/IP 堆棧(“slirp”),slirp4netns 允許以完全非特權的方式將網絡命名空間連接到網絡。
無根網絡有很大的局限性:“從技術上講,容器本身沒有 IP 地址,因為沒有根權限,網絡設備關聯就無法實現。此外,無根容器無法 ping,因為它缺少 ping 命令所需的 CAP_NET_RAW 安全能力。”但這總比完全沒有連接好。
https://www.redhat.com/sysadmin/container-networking-podman
本文探討的組織容器網絡的方法只是其中一種可能的方法(可能是使用最廣泛的一種)。還有很多其他的方法,通過官方或第三方插件實現,但它們都嚴重依賴于 Linux 網絡可視化工具。因此,容器化可以被視為虛擬化技術。
翻譯自:https://iximiuz.com/en/posts/container-networking-is-simple/
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/126125.html
摘要:簡單來說就是把注冊的動作異步化,當異步執行結束后會把執行結果回填到中抽象類一般就是公共邏輯的處理,而這里的處理主要就是針對一些參數的判斷,判斷完了之后再調用方法。 閱讀這篇文章之前,建議先閱讀和這篇文章關聯的內容。 1. 詳細剖析分布式微服務架構下網絡通信的底層實現原理(圖解) 2. (年薪60W的技巧)工作了5年,你真的理解Netty以及為什么要用嗎?(深度干貨)...
摘要:華為云華為云在云原生這場游戲中,最具競爭力的玩家之一。年,金山云在云原生領域推出了三款重磅產品星曜裸金屬服務器云服務器和云盤。在線上智博會上,浪潮云發布了經過全新迭代升級的浪潮云,進一步提升平臺云原生服務能力。面對數字時代復雜系統的不確定性,傳統的 IT 應用架構研發交付周期長、維護成本高、創新升級難,煙囪式架構,開放性差、組件復用度低,這些都成為了企業業務快速增長的瓶頸。而云原生以其敏捷、...
摘要:本文從定義,作用,技術架構,安裝和使用等全方位帶你看懂。如圖中左邊紅框中和右邊的紅框中都唯一表示為同一個鏡像。最后,于開發者而言提供了一種開發環境的管理辦法,與測試人員而言保證了環境的同步,于運維人員提供了可移植的標準化部署流程。 作者丨唐文廣:騰訊工程師,負責無線研發部地圖測試。 導語:Docker,近兩年才流行起來的超輕量級虛擬機,它可以讓你輕松完成持續集成、自動交付、自動部署...
閱讀 3514·2023-04-25 20:09
閱讀 3720·2022-06-28 19:00
閱讀 3035·2022-06-28 19:00
閱讀 3058·2022-06-28 19:00
閱讀 3132·2022-06-28 19:00
閱讀 2859·2022-06-28 19:00
閱讀 3014·2022-06-28 19:00
閱讀 2610·2022-06-28 19:00