摘要:一直以來(lái),網(wǎng)絡(luò)都是容器中令人頭疼的問(wèn)題。本文的主要目的是帶你解決容器網(wǎng)絡(luò)問(wèn)題,讓你不再對(duì)它恐懼。或者,更準(zhǔn)確地說(shuō),是單主機(jī)容器網(wǎng)絡(luò)問(wèn)題。與其創(chuàng)建完全隔離的容器,不如將范圍限制在網(wǎng)絡(luò)堆棧中。
一直以來(lái),網(wǎng)絡(luò)都是容器中令人頭疼的問(wèn)題。本文的主要目的是帶你解決容器網(wǎng)絡(luò)問(wèn)題,讓你不再對(duì)它恐懼。
使用容器總是感覺(jué)像變魔術(shù)一樣。對(duì)那些了解其內(nèi)部原理的人來(lái)說(shuō),它是一種很好的方式;而對(duì)那些不了解其內(nèi)部原理的人來(lái)說(shuō),這是一種可怕的方式。幸運(yùn)的是,我們研究容器化技術(shù)的內(nèi)部原理已經(jīng)很長(zhǎng)一段時(shí)間了。我們甚至發(fā)現(xiàn),容器只是隔離的、受限制的 Linux 進(jìn)程,鏡像并不是運(yùn)行容器所必須的,相反——要構(gòu)建一個(gè)鏡像,我們需要運(yùn)行一些容器。
現(xiàn)在,讓我們來(lái)解決下容器網(wǎng)絡(luò)問(wèn)題?;蛘?,更準(zhǔn)確地說(shuō),是單主機(jī)容器網(wǎng)絡(luò)問(wèn)題。在本文中,我們將回答以下問(wèn)題:
因此,很明顯,單主機(jī)容器網(wǎng)絡(luò)只不過(guò)是一些眾所周知的 Linux 工具的簡(jiǎn)單組合:
不管怎樣,不需要任何代碼就可以讓網(wǎng)絡(luò)魔法發(fā)生……
任何還算不錯(cuò)的 Linux 發(fā)行版可能都足矣。本文中的所有例子都是在一個(gè)全新的 vagrant CentOS 8 虛擬機(jī)上完成的:
$ 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
簡(jiǎn)單起見(jiàn),在本文中,我們不打算依賴任何成熟的容器化解決方案(例如 docker 或 podman)。相反,我們將關(guān)注基本概念,并使用最簡(jiǎn)單的工具來(lái)實(shí)現(xiàn)我們的學(xué)習(xí)目標(biāo)。
Linux 網(wǎng)絡(luò)堆棧是由什么組成的?很明顯,是網(wǎng)絡(luò)設(shè)備的集合。還有什么?可能是路由規(guī)則集。不要忘了還有 netfilter 鉤子集,包括由 iptables 規(guī)則定義的。
我們可以快速創(chuàng)建一個(gè)不是很完善的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
在運(yùn)行它之前,讓我們稍微修改下 iptables 規(guī)則,讓其更容易識(shí)別:
$ sudo iptables -N ROOT_NS
之后,在我的機(jī)器上執(zhí)行 inspect 腳本會(huì)產(chǎn)生以下輸出:
$ 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
之所以對(duì)這個(gè)輸出感興趣,是因?yàn)槲覀兿氪_保即將創(chuàng)建的每個(gè)容器都將獲得一個(gè)多帶帶的網(wǎng)絡(luò)堆棧。你可能已經(jīng)聽(tīng)說(shuō)過(guò),用于容器隔離的其中一個(gè) Linux 名稱空間是網(wǎng)絡(luò)命名空間(network namespace)。按照man ip-netns的說(shuō)法,“網(wǎng)絡(luò)命名空間在邏輯上是網(wǎng)絡(luò)堆棧的另一個(gè)副本,有自己的路由、防火墻規(guī)則和網(wǎng)絡(luò)設(shè)備?!?簡(jiǎn)單起見(jiàn),這將是我們?cè)诒疚闹惺褂玫奈ㄒ幻臻g。與其創(chuàng)建完全隔離的容器,不如將范圍限制在網(wǎng)絡(luò)堆棧中。
創(chuàng)建網(wǎng)絡(luò)命名空間的一種方法是ip工具——是事實(shí)標(biāo)準(zhǔn) iproute2 工具集的一部分:
$ sudo ip netns add netns0
$ ip netns
netns0
如何開(kāi)始使用剛剛創(chuàng)建的命名空間?有一個(gè)可愛(ài)的 Linux 命令叫做nsenter。它輸入一個(gè)或多個(gè)指定的名稱空間,然后執(zhí)行給定的程序:
$ 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命名空間內(nèi)運(yùn)行的 bash 進(jìn)程看到的是一個(gè)完全不同的網(wǎng)絡(luò)堆棧。沒(méi)有路由規(guī)則,沒(méi)有自定義 iptables 鏈,只有一個(gè)環(huán)回網(wǎng)絡(luò)設(shè)備。到目前為止,一切順利……
如果我們不能與一個(gè)專用的網(wǎng)絡(luò)堆棧通信,那么它就沒(méi)那么有用了。幸運(yùn)的是,Linux 為此提供了一個(gè)合適工具——虛擬以太網(wǎng)設(shè)備!按照man veth的說(shuō)法,“veth 設(shè)備是虛擬以太網(wǎng)設(shè)備。它們可以作為網(wǎng)絡(luò)命名空間之間的隧道,創(chuàng)建一個(gè)連接到另一個(gè)命名空間中物理網(wǎng)絡(luò)設(shè)備的橋,但也可以作為獨(dú)立的網(wǎng)絡(luò)設(shè)備使用?!?/p>
虛擬以太網(wǎng)設(shè)備總是成對(duì)出現(xiàn)。不用擔(dān)心,讓我們看一下創(chuàng)建命令就會(huì)明白了:
$ sudo ip link add veth0 type veth peer name ceth0
通過(guò)這個(gè)命令,我們剛剛創(chuàng)建了一對(duì)相互連接的虛擬以太網(wǎng)設(shè)備。名稱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
創(chuàng)建后,veth0和ceth0都駐留在主機(jī)的網(wǎng)絡(luò)堆棧(也稱為根網(wǎng)絡(luò)命名空間)上。為了連接根命名空間和netns0命名空間,我們需要將一個(gè)設(shè)備保留在根命名空間中,并將另一個(gè)設(shè)備移到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
一旦我們打開(kāi)設(shè)備并分配了正確的 IP 地址,任何出現(xiàn)在其中一臺(tái)設(shè)備上的數(shù)據(jù)包都會(huì)立即出現(xiàn)在連接兩個(gè)命名空間的對(duì)端設(shè)備上。讓我們從根命名空間開(kāi)始:
$ sudo ip link set veth0 up
$ sudo ip addr add 172.18.0.11/16 dev veth0
接下來(lái)是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
通過(guò) veth 設(shè)備連接網(wǎng)絡(luò)命名空間
現(xiàn)在可以檢查下連接了:
# 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
同時(shí),如果我們?cè)噲D從netns0命名空間訪問(wèn)任何其他地址,都會(huì)失?。?/p>
# 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
不過(guò),這很容易解釋。對(duì)于這樣的數(shù)據(jù)包,在netns0的路由表中沒(méi)有路由。其中,唯一的條目顯示了如何到達(dá)172.18.0.0/16網(wǎng)絡(luò):
# From `netns0` namespace:
$ ip route
172.18.0.0/16 dev ceth0 proto kernel scope link src 172.18.0.10
Linux 有很多方法來(lái)填充路由表。其中之一是從直接連接的網(wǎng)絡(luò)接口提取路由。記住,在命名空間創(chuàng)建后,netns0的路由表是空的。但隨后我們添加了ceth0設(shè)備,并為它分配了一個(gè) IP 地址172.18.0.10/16。由于我們使用的不是一個(gè)簡(jiǎn)單的 IP 地址,而是地址和網(wǎng)絡(luò)掩碼的組合,網(wǎng)絡(luò)堆棧會(huì)設(shè)法從中提取路由信息。每個(gè)發(fā)往172.18.0.0/16網(wǎng)絡(luò)的數(shù)據(jù)包將通過(guò)ceth0設(shè)備發(fā)送。但是任何其他的包都會(huì)被丟棄。類似地,在根命名空間中有一條新路由:
# From `root` namespace:
$ ip route
# ... omited lines ...
172.18.0.0/16 dev veth0 proto kernel scope link src 172.18.0.11
現(xiàn)在,我們已經(jīng)回答了我們的第一個(gè)問(wèn)題。我們現(xiàn)在知道了如何隔離、虛擬化和連接 Linux 網(wǎng)絡(luò)堆棧。
容器化的整個(gè)理念可以歸結(jié)為有效的資源共享。也就是說(shuō),每臺(tái)機(jī)器一個(gè)容器的情況并不常見(jiàn)。相反,我們的目標(biāo)是在共享環(huán)境中運(yùn)行盡可能多的隔離進(jìn)程。那么,如果我們按照上面的veth方法將多個(gè)容器放在同一主機(jī)上,會(huì)發(fā)生什么呢?讓我們添加第二個(gè)容器:
# 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
有點(diǎn)不對(duì)勁……netns1遇到問(wèn)題。由于某些原因,它不能與根通信,我們也不能從根命名空間訪問(wèn)它。然而,由于兩個(gè)容器都位于同一個(gè) IP 網(wǎng)絡(luò) 172.18.0.0/16 中,我們現(xiàn)在可以從netns0容器與主機(jī)的veth1進(jìn)行通信。非常有趣……
我花了些時(shí)間才想明白,但顯然我們面臨的是路由沖突。讓我們檢查下根命名空間中的路由表:
$ 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
雖然在添加了第二個(gè)veth對(duì)后,根的網(wǎng)絡(luò)堆棧學(xué)習(xí)到了新的路由172.18.0.0/16 dev veth1 proto kernel scope li
我相信,如果我們?yōu)閚etns1選擇另一個(gè) IP 網(wǎng)絡(luò),一切就沒(méi)問(wèn)題了。然而,多個(gè)容器位于一個(gè) IP 網(wǎng)絡(luò)中是一個(gè)合理的用例。因此,我們需要以某種方式調(diào)整veth方法…
看看 Linux 網(wǎng)橋——另一種虛擬網(wǎng)絡(luò)設(shè)施!Linux 網(wǎng)橋的行為就像一個(gè)網(wǎng)絡(luò)交換機(jī)。它會(huì)在連接到它的接口之間轉(zhuǎn)發(fā)數(shù)據(jù)包。因?yàn)樗且粋€(gè)交換機(jī),所以它是在 L2(即以太網(wǎng))層完成這項(xiàng)工作的。
讓我們?cè)囍僮飨掳?。但首先,我們需要清理現(xiàn)有的設(shè)置,因?yàn)榈侥壳盀橹?,我們所做的一些配置更改?shí)際上已經(jīng)不再需要了。刪除網(wǎng)絡(luò)命名空間就足夠了:
$ 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
快速重建兩個(gè)容器。注意,我們沒(méi)有給新的veth0和veth1設(shè)備分配任何 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
確保主機(jī)上沒(méi)有新路由:
$ 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
最后,創(chuàng)建網(wǎng)橋接口:
$ sudo ip link add br0 type bridge
$ sudo ip link set br0 up
現(xiàn)在,將veth0和veth1兩端都連接到網(wǎng)橋上:
$ 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
真令人愉快!一切正常。使用這種新方法,我們根本沒(méi)有配置veth0和veth1。我們只在ceth0和ceth1端分配了兩個(gè) IP 地址。但是,由于它們都在同一個(gè)以太網(wǎng)段(記住,我們將它們連接到虛擬交換機(jī)),所以 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
恭喜,我們學(xué)會(huì)了 如何將容器變成友好的鄰居,防止它們相互干擾,并保持連接性。
容器之間可以通信了。但它們可以和主機(jī)(即根命名空間)通信嗎?
$ sudo nsenter --net=/var/run/netns/netns0
$ ping 10.0.2.15 # eth0 address
connect: Network is unreachable
很明顯,netns0中沒(méi)有相應(yīng)的路由:
$ 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
為了在根命名空間和容器命名空間之間建立連接,我們需要為網(wǎng)橋網(wǎng)絡(luò)接口分配 IP 地址:
$ sudo ip addr add 172.18.0.1/16 dev br0
一旦我們給網(wǎng)橋接口分配了 IP 地址,我們的主機(jī)路由表上就會(huì)多一條路由:
$ 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 網(wǎng)橋接口的能力,但它們?nèi)匀粺o(wú)法連接到主機(jī)的eth0。我們需要為容器添加默認(rèn)路由:
$ 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`
這項(xiàng)更改基本上把主機(jī)變成了路由器,網(wǎng)橋接口成了容器的默認(rèn)網(wǎng)關(guān)。
很好,我們將容器與根命名空間連接起來(lái)了。現(xiàn)在,讓我們嘗試將它們與外部世界連接起來(lái)。默認(rèn)情況下,在 Linux 中數(shù)據(jù)包轉(zhuǎn)發(fā)(即路由器功能)是禁用的。我們需要打開(kāi)它:
# 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...
還是不行。我們漏了什么嗎?如果容器向外部世界發(fā)送數(shù)據(jù)包,那么目標(biāo)服務(wù)器將不能將數(shù)據(jù)包發(fā)送回容器,因?yàn)槿萜鞯?IP 地址是私有的。也就是說(shuō),只有本地網(wǎng)絡(luò)才知道特定 IP 的路由規(guī)則。世界上有很多容器共享完全相同的私有 IP 地址172.18.0.10。
解決這個(gè)問(wèn)題的方法叫做網(wǎng)絡(luò)地址轉(zhuǎn)換(NAT)。在進(jìn)入外部網(wǎng)絡(luò)前,由容器發(fā)出的數(shù)據(jù)包將其源 IP 地址替換為主機(jī)的外部接口地址。主機(jī)還將跟蹤所有現(xiàn)有的映射,并且在數(shù)據(jù)包到達(dá)時(shí),它會(huì)在將其轉(zhuǎn)發(fā)回容器之前還原 IP 地址。聽(tīng)起來(lái)很復(fù)雜,但我有個(gè)好消息要告訴你!有了 iptables 模塊,我們只需要一個(gè)命令就可以實(shí)現(xiàn):
$ sudo iptables -t nat -A POSTROUTING -s 172.18.0.0/16 ! -o br0 -j MASQUERADE
這個(gè)命令相當(dāng)簡(jiǎn)單。我們正在向POSTROUTING鏈的nat表添加一條新規(guī)則,要求偽裝所有源自172.18.0.0/16網(wǎng)絡(luò)的數(shù)據(jù)包,但不是通過(guò)網(wǎng)橋接口。檢查連接:
$ 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
注意,我們遵循的是默認(rèn)允許(by default - allow)策略,這在現(xiàn)實(shí)世界中可能相當(dāng)危險(xiǎn)。對(duì)于每個(gè)鏈,主機(jī)默認(rèn)的 iptables 策略都是ACCEPT:
sudo iptables -S
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
相反,作為一個(gè)很好的例子,Docker 默認(rèn)限制了一切,然后只啟用已知路徑的路由。以下是在 CentOS 8 機(jī)器上(在 5005 端口上暴露了單個(gè)容器)Docker 守護(hù)進(jìn)程生成的轉(zhuǎn)儲(chǔ)規(guī)則:
$ 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
我們都知道,有一種做法是將容器端口發(fā)布到主機(jī)的部分(或全部)接口。但端口發(fā)布的真正含義是什么?
假設(shè)我們有一個(gè)在容器內(nèi)運(yùn)行的服務(wù)器:
$ sudo nsenter --net=/var/run/netns/netns0
$ python3 -m http.server --bind 172.18.0.10 5000
如果我們?cè)噲D從主機(jī)向這個(gè)服務(wù)器進(jìn)程發(fā)送一個(gè) HTTP 請(qǐng)求,一切都沒(méi)問(wèn)題(好吧,根命名空間和所有容器接口之間都有連接,為什么沒(méi)有呢?):
# From root namespace
$ curl 172.18.0.10:5000
# ... omited lines ...
但是,如果我們要從外部訪問(wèn)該服務(wù)器,我們將使用哪個(gè) IP 地址?我們知道的唯一 IP 地址可能是主機(jī)的外部接口地址eth0:
$ curl 10.0.2.15:5000
curl: (7) Failed to connect to 10.0.2.15 port 5000: Connection refused
因此,我們需要找到一種方法,將任何到達(dá)主機(jī)eth0接口 5000 端口的數(shù)據(jù)包轉(zhuǎn)發(fā)到目的地172.18.0.10:5000。或者,換句話說(shuō),我們需要在主機(jī)的eth0接口上發(fā)布容器的 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 攔截橋接網(wǎng)絡(luò)上的流量:
sudo modprobe br_netfilter
測(cè)試時(shí)間!
curl 10.0.2.15:5000
# ... omited lines ...
好的,我們能用這些無(wú)用的知識(shí)做什么呢?例如,我們可以試著理解一些 Docker 網(wǎng)絡(luò)模式!
https://docs.docker.com/network/#network-drivers
讓我們從--network host模式開(kāi)始。試著比較下命令ip li
下一個(gè)模式是--network none。sudo docker run -it --rm --network none alpine ip li
最后但同樣重要的是--network bridge(默認(rèn))模式。這正是我們?cè)谡恼轮性噲D再現(xiàn)的。我建議你試用下ip和iptables命令,并從主機(jī)和容器的角度檢查網(wǎng)絡(luò)堆棧。
podman容器管理器的一個(gè)很好的特性是針對(duì)無(wú)根容器的。然而,你可能已經(jīng)注意到,我們?cè)诒疚闹惺褂昧舜罅縮udo升級(jí)。換句話說(shuō),權(quán)限就不可能配置網(wǎng)絡(luò)。Podman 的 rootfull 網(wǎng)絡(luò)方法和 docker 非常接近。
https://www.redhat.com/sysadmin/container-networking-podman
但是當(dāng)涉及到無(wú)根容器時(shí),podman 依賴于 slirp4netns 項(xiàng)目:
從 Linux 3.8 開(kāi)始,非特權(quán)用戶可以創(chuàng)建 network_namespaces(7) 和 user_namespaces(7) 了。但是,非特權(quán)網(wǎng)絡(luò)命名空間并不是很有用,因?yàn)樵谥鳈C(jī)和網(wǎng)絡(luò)命名空間之間創(chuàng)建 veth(4) 對(duì)仍然需要 root 特權(quán)。(即沒(méi)有網(wǎng)絡(luò)連接)
通過(guò)將網(wǎng)絡(luò)命名空間中的 TAP 設(shè)備連接到用戶模式 TCP/IP 堆棧(“slirp”),slirp4netns 允許以完全非特權(quán)的方式將網(wǎng)絡(luò)命名空間連接到網(wǎng)絡(luò)。
無(wú)根網(wǎng)絡(luò)有很大的局限性:“從技術(shù)上講,容器本身沒(méi)有 IP 地址,因?yàn)闆](méi)有根權(quán)限,網(wǎng)絡(luò)設(shè)備關(guān)聯(lián)就無(wú)法實(shí)現(xiàn)。此外,無(wú)根容器無(wú)法 ping,因?yàn)樗鄙?ping 命令所需的 CAP_NET_RAW 安全能力。”但這總比完全沒(méi)有連接好。
https://www.redhat.com/sysadmin/container-networking-podman
本文探討的組織容器網(wǎng)絡(luò)的方法只是其中一種可能的方法(可能是使用最廣泛的一種)。還有很多其他的方法,通過(guò)官方或第三方插件實(shí)現(xiàn),但它們都嚴(yán)重依賴于 Linux 網(wǎng)絡(luò)可視化工具。因此,容器化可以被視為虛擬化技術(shù)。
翻譯自:https://iximiuz.com/en/posts/container-networking-is-simple/
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/126125.html
摘要:簡(jiǎn)單來(lái)說(shuō)就是把注冊(cè)的動(dòng)作異步化,當(dāng)異步執(zhí)行結(jié)束后會(huì)把執(zhí)行結(jié)果回填到中抽象類一般就是公共邏輯的處理,而這里的處理主要就是針對(duì)一些參數(shù)的判斷,判斷完了之后再調(diào)用方法。 閱讀這篇文章之前,建議先閱讀和這篇文章關(guān)聯(lián)的內(nèi)容。 1. 詳細(xì)剖析分布式微服務(wù)架構(gòu)下網(wǎng)絡(luò)通信的底層實(shí)現(xiàn)原理(圖解) 2. (年薪60W的技巧)工作了5年,你真的理解Netty以及為什么要用嗎?(深度干貨)...
摘要:華為云華為云在云原生這場(chǎng)游戲中,最具競(jìng)爭(zhēng)力的玩家之一。年,金山云在云原生領(lǐng)域推出了三款重磅產(chǎn)品星曜裸金屬服務(wù)器云服務(wù)器和云盤。在線上智博會(huì)上,浪潮云發(fā)布了經(jīng)過(guò)全新迭代升級(jí)的浪潮云,進(jìn)一步提升平臺(tái)云原生服務(wù)能力。面對(duì)數(shù)字時(shí)代復(fù)雜系統(tǒng)的不確定性,傳統(tǒng)的 IT 應(yīng)用架構(gòu)研發(fā)交付周期長(zhǎng)、維護(hù)成本高、創(chuàng)新升級(jí)難,煙囪式架構(gòu),開(kāi)放性差、組件復(fù)用度低,這些都成為了企業(yè)業(yè)務(wù)快速增長(zhǎng)的瓶頸。而云原生以其敏捷、...
摘要:本文從定義,作用,技術(shù)架構(gòu),安裝和使用等全方位帶你看懂。如圖中左邊紅框中和右邊的紅框中都唯一表示為同一個(gè)鏡像。最后,于開(kāi)發(fā)者而言提供了一種開(kāi)發(fā)環(huán)境的管理辦法,與測(cè)試人員而言保證了環(huán)境的同步,于運(yùn)維人員提供了可移植的標(biāo)準(zhǔn)化部署流程。 作者丨唐文廣:騰訊工程師,負(fù)責(zé)無(wú)線研發(fā)部地圖測(cè)試。 導(dǎo)語(yǔ):Docker,近兩年才流行起來(lái)的超輕量級(jí)虛擬機(jī),它可以讓你輕松完成持續(xù)集成、自動(dòng)交付、自動(dòng)部署...
閱讀 3580·2023-04-25 20:09
閱讀 3770·2022-06-28 19:00
閱讀 3115·2022-06-28 19:00
閱讀 3129·2022-06-28 19:00
閱讀 3230·2022-06-28 19:00
閱讀 2917·2022-06-28 19:00
閱讀 3104·2022-06-28 19:00
閱讀 2703·2022-06-28 19:00