很多人可能在項目中已經(jīng)使用docker很長時間,但是卻很少有人知道docker的網(wǎng)絡是如何實現(xiàn)的。我應該就算是很多人中的一個呆瞻。
開始前有一點需要注意的是:如果你現(xiàn)在正使用的是docker for mac,建議你還是在mac上安裝vagrant,然后使用vagrant開啟一臺linux虛擬機柴我,然后在這臺虛擬機上安裝docker for linux。這樣我們的學習環(huán)境也更加的貼近生產(chǎn)環(huán)境扩然。
docker0網(wǎng)橋
當在一臺未經(jīng)特殊網(wǎng)絡配置的ubuntu機器上安裝完docker之后艘儒,在宿主機上通過使用ifconfig
命令可以看到多了一塊名為docker0的網(wǎng)卡:
vagrant@vagrant-ubuntu-trusty-64:~$ ifconfig
docker0 Link encap:Ethernet HWaddr 02:42:b6:12:b7:33
inet addr:172.17.0.1 Bcast:0.0.0.0 Mask:255.255.0.0
inet6 addr: fe80::42:b6ff:fe12:b733/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:45 errors:0 dropped:0 overruns:0 frame:0
TX packets:18 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:3044 (3.0 KB) TX bytes:1460 (1.4 KB)
eth0 Link encap:Ethernet HWaddr 08:00:27:36:92:90
inet addr:10.0.2.15 Bcast:10.0.2.255 Mask:255.255.255.0
inet6 addr: fe80::a00:27ff:fe36:9290/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:503098 errors:0 dropped:0 overruns:0 frame:0
TX packets:236748 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:397962985 (397.9 MB) TX bytes:14862746 (14.8 MB)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
可以看到此docker0網(wǎng)卡的IP為172.17.0.1/16。有了這樣一塊網(wǎng)卡夫偶,宿主機也會在內(nèi)核路由表上添加一條到達相應網(wǎng)絡的靜態(tài)路由界睁,可以通過route -n
查看。
vagrant@vagrant-ubuntu-trusty-64:~$ route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.0.2.2 0.0.0.0 UG 0 0 0 eth0
10.0.2.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
當一宿主機要發(fā)送數(shù)據(jù)時兵拢,它會查自己的內(nèi)核路由表晕窑,找到一條最精確匹配目標主機IP地址的路由來轉(zhuǎn)發(fā)數(shù)據(jù)。
我們知道卵佛,同一網(wǎng)段內(nèi)發(fā)送數(shù)據(jù)是不需要經(jīng)過網(wǎng)關(guān)的杨赤,而不同網(wǎng)段之間發(fā)送數(shù)據(jù)必需經(jīng)過網(wǎng)關(guān)敞斋。
我們的宿主機有三張網(wǎng)卡:
lo(127.0.0.1
),本機回環(huán)網(wǎng)卡疾牲;
eth0(10.0.2.15
)在10.0.2.0/24
網(wǎng)段;
docker0(172.17.0.1
)在172.17.0.0/16
網(wǎng)段;
假如:
Destination IP是
10.0.2.13
植捎,則會匹配到第二條路由。即所有目的IP為10.0.2.0/24
網(wǎng)絡的數(shù)據(jù)包從eth0網(wǎng)卡發(fā)出阳柔。Gateway為0.0.0.0
表示不經(jīng)過網(wǎng)關(guān)焰枢,這個很好理解,因為同一網(wǎng)段內(nèi)發(fā)送數(shù)據(jù)不需要經(jīng)過網(wǎng)關(guān)舌剂。Destination IP是
172.17.0.3
济锄,則會匹配到第三條路由。即所有目的IP為172.17.0.0/16
網(wǎng)絡的數(shù)據(jù)包從docker0網(wǎng)卡發(fā)出霍转。Destination IP是其它荐绝,比如
32.10.2.0
,則會匹配到第一條路由:默認路由避消。即數(shù)據(jù)包從eth0發(fā)出低滩,而發(fā)往外網(wǎng)的數(shù)據(jù)包需要經(jīng)過網(wǎng)關(guān)轉(zhuǎn)發(fā),所發(fā)配置了Gateway為10.0.2.2
岩喷。
現(xiàn)在使用docker run
創(chuàng)建一個docker容器恕沫。
sudo docker run -it --name myubuntu ubuntu /bin/bash
然后在容器中執(zhí)行ifconfig
,如果你發(fā)現(xiàn)你的容器中沒有ifconfig
命令纱意,可以執(zhí)行以下命令安裝net-tools:
root@bde36ed1f506:/# apt-get update
root@bde36ed1f506:/# apt-get install net-tools
// 順帶也把iputils-ping裝了
root@bde36ed1f506:/# apt-get install iputils-ping
然后再執(zhí)行ifconfig
:
root@bde36ed1f506:/# ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:ac:11:00:02
inet addr:172.17.0.2 Bcast:0.0.0.0 Mask:255.255.0.0
inet6 addr: fe80::42:acff:fe11:2/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:11218 errors:0 dropped:0 overruns:0 frame:0
TX packets:11128 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:24859036 (24.8 MB) TX bytes:606400 (606.4 KB)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
在myubuntu容器中有兩塊網(wǎng)卡:eth0和lo婶溯。eth0為容器與外界通信的網(wǎng)卡。而且eth0(172.17.0.2
)與宿主機中的docker0(172.17.0.1
)在同一個網(wǎng)段偷霉。
現(xiàn)在我們查看myubuntu的路由表:
root@bde36ed1f506:/# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 172.17.0.1 0.0.0.0 UG 0 0 0 eth0
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 eth0
可以發(fā)現(xiàn)myubuntu的默認網(wǎng)關(guān)正是宿主機的docker0網(wǎng)卡迄委。我們剛才install了那么多package說明容器是可以訪問到外網(wǎng)的,說明容器的eth0與宿主機的docker0網(wǎng)卡是互通的腾它。
現(xiàn)在我們回到宿主機的console跑筝,執(zhí)行ifconfig
:
vagrant@vagrant-ubuntu-trusty-64:~$ ifconfig
...
vethee89fa0 Link encap:Ethernet HWaddr 36:32:c3:bf:5e:78
inet6 addr: fe80::3432:c3ff:febf:5e78/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:11330 errors:0 dropped:0 overruns:0 frame:0
TX packets:11422 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:619186 (619.1 KB) TX bytes:26177038 (26.1 MB)
會發(fā)現(xiàn)有一塊以"veth"開頭的網(wǎng)卡,如上:vethee89fa0瞒滴。我們可以猜測這塊網(wǎng)卡是veth設備曲梗,而veth pair總是成對出現(xiàn)的,很多人可能不知道veth設備是什么妓忍。
veth pair是用于不同network namespace間進行通信的方式虏两,veth pair將一個network namespace數(shù)據(jù)發(fā)往另一個network namespace的veth。
如下:
如果多個network namespace需要進行通信世剖,則需要借助bridge:
現(xiàn)在我們知道, veth pair通常用來連接兩個network namespace定罢,那么另一個應該就是docker容器中的eth0了。之前我們已經(jīng)知道m(xù)yubuntu容器的eth0和宿主機的docker0是相連的旁瘫,那么vethee89fa0也應該是與docker0相連的祖凫。那么琼蚯,現(xiàn)在來看docker0就不只是一個簡單的網(wǎng)卡設備了,而是一個網(wǎng)橋惠况。
真實情況正是如此遭庶,下圖即為docker默認網(wǎng)絡模式(bridge模式)下的網(wǎng)絡環(huán)境拓撲圖:
我們在宿主機上安將docker時會添加一個docker0的網(wǎng)卡,當我們使用docker run創(chuàng)建一個容器稠屠,并且沒有指定網(wǎng)絡模式的時候峦睡,會使用默認網(wǎng)絡模式,創(chuàng)建出一個docker0網(wǎng)橋权埠,并以veth pair連接各容器的網(wǎng)絡榨了,容器中的數(shù)據(jù)通過docker0網(wǎng)橋轉(zhuǎn)發(fā)到宿主機的eth0網(wǎng)卡上。
在linux中攘蔽,可以使用brctl
命令查看和管理網(wǎng)橋(需要安裝bridge-utils軟件包)龙屉,如查看本機上的linux網(wǎng)橋以及其上的端口:
vagrant@vagrant-ubuntu-trusty-64:~$ sudo brctl show
bridge name | bridge id | STP enabled | interfaces |
---|---|---|---|
docker0 | 8000.0242b612b733 | no | vethee89fa0 |
如果我再使用docker run
再創(chuàng)建一個docker容器后再查看linux網(wǎng)橋以及其上的端口:
vagrant@vagrant-ubuntu-trusty-64:~$ sudo brctl show
bridge name | bridge id | STP enabled | interfaces |
---|---|---|---|
docker0 | 8000.0242b612b733 | no | vethee89fa0 |
veth0c3e53a |
會發(fā)現(xiàn)又多了一雙veth pair連接到了docker0網(wǎng)橋。
現(xiàn)在我們再來看這個網(wǎng)橋秩彤,我們會覺得它就像是一個交換機叔扼,為連在其上的設備轉(zhuǎn)發(fā)數(shù)據(jù)幀事哭。網(wǎng)橋上的veth網(wǎng)卡設備相當于交換機上的端口漫雷,可以將多個容器或虛擬機連接在其上,這些端口工作在二層鳍咱,所以是不需要配置IP信息的降盹。圖中docker0網(wǎng)橋變?yōu)檫B在其上的容器轉(zhuǎn)發(fā)數(shù)據(jù)幀,使得同一臺宿主機上的docker容器之間可以相互通信谤辜。也許你應該已經(jīng)注意到docker0既然是二層設備蓄坏,其上怎么也配置了IP呢? docker0是普通的Linux網(wǎng)橋丑念,它是可以在上面配置IP的涡戳,可以認為其內(nèi)部有一個可以用于配置IP信息的網(wǎng)卡接口。那么為什么要為它配置IP呢脯倚?在Docker的橋接網(wǎng)絡模式中渔彰,docker0的IP地址作為連接于之上的容器的默認網(wǎng)關(guān)存在。
iptables規(guī)則
Docker安裝完成后推正,默認會在宿主機上增加一些iptables規(guī)則恍涂,以用于docker容器和容器之間以及和外界有通信,可以使用iptables-save
命令查看規(guī)則植榕。
vagrant@vagrant-ubuntu-trusty-64:~$ sudo iptables-save
# Generated by iptables-save v1.4.21 on Sun Jun 18 08:24:03 2017
*nat
:PREROUTING ACCEPT [4:1276]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [480:28821]
:POSTROUTING ACCEPT [480:28821]
:DOCKER - [0:0]
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -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.3/32 -d 172.17.0.3/32 -p tcp -m tcp --dport 3306 -j MASQUERADE
-A DOCKER -i docker0 -j RETURN
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 3306 -j DNAT --to-destination 172.17.0.3:3306
COMMIT
# Completed on Sun Jun 18 08:24:03 2017
# Generated by iptables-save v1.4.21 on Sun Jun 18 08:24:03 2017
*filter
:INPUT ACCEPT [2374:4027249]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [1986:129221]
:DOCKER - [0:0]
:DOCKER-ISOLATION - [0:0]
-A FORWARD -j DOCKER-ISOLATION
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A DOCKER -d 172.17.0.3/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 3306 -j ACCEPT
-A DOCKER-ISOLATION -j RETURN
COMMIT
# Completed on Sun Jun 18 08:24:03 2017
可以看到nat表上的POSTROUTING鏈有這么一條規(guī)則:
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
這條規(guī)則關(guān)系著docker容器和外界的通信再沧,含義是將源地址為172.17.0.0/16
的數(shù)據(jù)包(即Docker容器發(fā)出的數(shù)據(jù)),當不是從docker0網(wǎng)卡發(fā)出時做SNAT(源地址轉(zhuǎn)換尊残,將IP包的數(shù)據(jù)地址替換為相應網(wǎng)卡的地址)炒瘸。這樣一來淤堵,從Docker容器訪問外網(wǎng)的流量,在外部看來就是從宿主機上發(fā)出的顷扩,外部感覺不到Docker容器的存在粘勒。
在iptables中可以靈活的做各種網(wǎng)絡地址換(NAT),我想很多人可能不是太了解NAT(Network address translation)屎即。
網(wǎng)絡地址轉(zhuǎn)換主要有兩種: SNAT(source NAT)和DNAT(destination NAT)庙睡。
SNAT,即源地址目標轉(zhuǎn)換技俐。比如乘陪,多個PC機使用ADSL路由器共享上網(wǎng)。每個PC機都配置了內(nèi)網(wǎng)IP雕擂,PC機訪問外部網(wǎng)絡的時候啡邑,路由器將數(shù)據(jù)包的報頭中的源地址替換成路由器的IP,當外部網(wǎng)絡的服務器井赌,比如谤逼,網(wǎng)站web服務器接到訪問請求的時候,他的日志記錄下來的是路由器的IP地址仇穗,而不是PC機的內(nèi)網(wǎng)IP流部。
這是因為,這個服務器收到的數(shù)據(jù)包的報頭里邊的源地址已經(jīng)被換了纹坐,所以叫做SNAT枝冀,基于源地址的地址轉(zhuǎn)換。
DNAT耘子,即目標地址轉(zhuǎn)換果漾。典型的應用是,有個web服務器放在內(nèi)網(wǎng)谷誓,前端有個防火墻配置公網(wǎng)IP绒障。互聯(lián)網(wǎng)上的訪問者使用公網(wǎng)IP來訪問這個網(wǎng)站捍歪。當訪問的時候户辱,客戶端發(fā)出一個數(shù)據(jù)包,這個數(shù)據(jù)包的報頭里邊费封,目標地址寫的是防火墻的公網(wǎng)IP焕妙。防火墻會把這個數(shù)據(jù)包的報頭改寫一次,將目標地址改寫成web服務器的內(nèi)網(wǎng)IP弓摘,然后再把這個數(shù)據(jù)包發(fā)送到內(nèi)網(wǎng)的web服務器上焚鹊。
MASQUERADE,地址偽裝,在iptables中有著和SNAT相近的效果末患,但也有一些區(qū)別研叫。
使用SNAT的時候,即可以NAT成一地址也可以NAT成多個地址璧针,但是嚷炉,對于SNAT,不管是幾個地址探橱,必須明確的指定要SNAT的IP申屹。假如當前系統(tǒng)用的是ADSL動態(tài)撥號方式,那么每次撥號隧膏,出口IP都會改變哗讥,這個時候如果按照現(xiàn)在的方式來配置iptables就會出現(xiàn)問題。因為每次撥號后胞枕,服務器地址都會變化杆煞,而iptables規(guī)則內(nèi)的ip是不會隨著自動變化的。每次地址變化后都必須手工修改一次iptables腐泻,把規(guī)則里邊的固定IP改成新的IP决乎,這樣很不方便。
MASQUERADE就是針對這種場景而設計的派桩,他的作用是构诚,從服務器的網(wǎng)卡上,自動獲取當前IP地址來做NAT窄坦。
比如下邊的命令:
iptables -t nat -A POSTROUTING -s 10.8.0.0/255.255.255.0 -o eth0 -j MASQUERADE
如此配置的話唤反,不用指定SNAT的目標IP了凳寺。不管現(xiàn)在eth0的出口獲得了怎樣的動態(tài)IP鸭津,MASQUERADE會自動讀取eth0現(xiàn)在的IP地址然后做SNAT。這樣就很好的實現(xiàn)了動態(tài)SNAT地址轉(zhuǎn)換肠缨。