docker ip地址_理解 Docker 网络(番外) -- 《Docker 源码分析》勘误
前言
本來(lái)打算這篇文章是分析 Docker Overlay 網(wǎng)絡(luò)是如何建立以及如何手動(dòng)實(shí)現(xiàn) Docker 的跨主機(jī)通信的。但是在完成了上一篇文章之后,打算找一些文章或者書籍印證我的文章是否正確。這時(shí)看了一下案頭的《Docker源碼分析》,翻到 Docker 容器網(wǎng)絡(luò)部分,然后快速過(guò)了一遍,發(fā)現(xiàn)了有一段和我的觀點(diǎn)不太一致。
有問(wèn)題的部分是 95 頁(yè)的一段話,摘錄如下[1]:
5)宿主機(jī)將經(jīng)過(guò) SNAT 處理后的報(bào)文通過(guò)請(qǐng)求的目的IP地址(宿主機(jī)以外世界的IP地址)發(fā)送至外界。在這里,很多人肯定要問(wèn):對(duì)于 Docker 容器內(nèi)部主動(dòng)發(fā)起的網(wǎng)絡(luò)請(qǐng)求,請(qǐng)求到達(dá)宿主機(jī)進(jìn)行 SNAT 處理發(fā)給外界之后,當(dāng)外界響應(yīng)請(qǐng)求時(shí),響應(yīng)報(bào)文中的目的IP地址肯定是 Docker Daemon 所在宿主機(jī)的 IP 地址,那響應(yīng)報(bào)文回到宿主機(jī)的時(shí)候,宿主機(jī)又是如何轉(zhuǎn)給 Docker 容器的呢?關(guān)于這樣的響應(yīng),由于沒(méi)有做相應(yīng)的 DNAT 轉(zhuǎn)換,原則上不會(huì)被發(fā)送到容器內(nèi)部。為什么說(shuō)對(duì)于這樣的響應(yīng),不會(huì)做 DNAT 轉(zhuǎn)換呢。原因很簡(jiǎn)單,DNAT 轉(zhuǎn)換是針對(duì)特定容器內(nèi)部服務(wù)監(jiān)聽(tīng)的特定端口做的,該端口是供服務(wù)監(jiān)聽(tīng)使用,而容器內(nèi)部發(fā)起的請(qǐng)求報(bào)文中,源端口肯定不會(huì)占用服務(wù)監(jiān)聽(tīng)的端口,故容器內(nèi)部發(fā)起請(qǐng)求的響應(yīng)不會(huì)在宿主機(jī)上經(jīng)過(guò) DNAT 處理。
其實(shí),這一環(huán)節(jié)的關(guān)鍵在于 iptables,具體的 iptables 規(guī)則如下: iptables -I FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT ...
所以這篇文章將會(huì)從邏輯,理論,實(shí)驗(yàn)來(lái)分析這段文字是否正確。
邏輯性和理論性分析
邏輯性分析
按照這段文字,如果我沒(méi)有理解錯(cuò)的話,作者的意思是 Docker 容器通過(guò) SNAT 向外發(fā)送請(qǐng)求,但是此時(shí)宿主機(jī)的 iptables 沒(méi)有配置 DNAT 那么在不配置 DNAT 的情況下必須要通過(guò) iptables -I FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 來(lái)保證響應(yīng)能夠正確返回到 Docker 容器中。首先分析 iptables -I FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT,這里可以直接引用作者的分析[1]
此 iptables 規(guī)則的意思是:在宿主機(jī)發(fā)往 docker0 網(wǎng)橋的網(wǎng)絡(luò)數(shù)據(jù)報(bào)文,如果該數(shù)據(jù)報(bào)文所處的連接已經(jīng)建立,則無(wú)條件接受,并且有 Linux 內(nèi)核轉(zhuǎn)發(fā)到原來(lái)的連接上,即回到 Docker 容器內(nèi)部。這里的意思有些偏頗,但不影響邏輯分析。這里要知道關(guān)于路由轉(zhuǎn)發(fā)的一些知識(shí):數(shù)據(jù)包會(huì)發(fā)往 docker0 網(wǎng)橋的充分必要條件是路由表上由能將數(shù)據(jù)包正確轉(zhuǎn)發(fā)到 docker0 網(wǎng)橋的項(xiàng)。有這些作為基礎(chǔ)就可以開(kāi)始邏輯性分析了:
假設(shè)條件:結(jié)論:
- Docker 容器收到了數(shù)據(jù)包
如果按照沒(méi)有 DNAT 就不會(huì)將目標(biāo)地址改寫為容器 IP 這個(gè)邏輯的話,數(shù)據(jù)包會(huì)直接進(jìn)入到 INPUT 鏈而不是進(jìn)入到 FORWARD 鏈[5],就算進(jìn)入到 FORWARD 鏈數(shù)據(jù)包目標(biāo)地址不是目標(biāo)容器的 IP ,數(shù)據(jù)包也不會(huì)經(jīng) docker0 輸出,此時(shí) -o docker0 的 iptables 也不會(huì)生效,所以作者認(rèn)為的關(guān)鍵 iptables 規(guī)則并沒(méi)有生效,因而在這里作者的邏輯是不通的。
理論性分析
作者在這里對(duì) SNAT, DNAT 和 iptables 的理論基礎(chǔ)還不夠扎實(shí),至少在寫作的時(shí)候還不扎實(shí)。這里會(huì)補(bǔ)充一些作者在寫作的時(shí)候遺漏的一些知識(shí)。
無(wú)論是 SNAT 還是 DNAT,Linux 在實(shí)現(xiàn) NAT 的時(shí)候都會(huì)生成一個(gè) NAT 轉(zhuǎn)換表[3],用來(lái)對(duì)發(fā)送的請(qǐng)求和接受的響應(yīng)進(jìn)行轉(zhuǎn)換,SNAT 映射的轉(zhuǎn)換也會(huì)在 PREROUTING 鏈后 FORWARD 鏈前進(jìn)行。以 Docker 容器 SNAT 的場(chǎng)景舉例, Docker 容器通過(guò) iptables -A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE 實(shí)現(xiàn) SNAT。在發(fā)生 SNAT 轉(zhuǎn)換的時(shí)候 iptables 就會(huì)保存一個(gè)映射表。以 Docker 容器訪問(wèn)遠(yuǎn)程數(shù)據(jù)庫(kù)為例,容器使用 172.17.0.2:1234 發(fā)出請(qǐng)求到 14.215.177.39:3306,當(dāng)容器請(qǐng)求的數(shù)據(jù)包進(jìn)入到 POSTROUTING 鏈之后被偽裝成主機(jī)的 192.168.0.2:4567 端口,這時(shí)候 iptables 保存了 172.17.0.2:1234<->192.168.0.2:4567 的映射。遠(yuǎn)程服務(wù)器收到的是 192.168.0.2:4567 的請(qǐng)求,然后 14.215.177.39:3306 返回響應(yīng)給 192.168.0.2:4567 ,根據(jù)映射規(guī)則 iptables 將響應(yīng)數(shù)據(jù)包的目標(biāo)地址改寫為 172.17.0.2 將目標(biāo)端口改寫為 1234,由于 172.17.0.2 地址不屬于宿主機(jī),數(shù)據(jù)包進(jìn)入 FORWARD 鏈處理。
DNAT 的例子可以自己類推,重點(diǎn)是 DNAT 的映射也會(huì)在 POSTROUTING 鏈之后進(jìn)行轉(zhuǎn)換。不然發(fā)送的數(shù)據(jù)包的目標(biāo)地址沒(méi)有改寫為外網(wǎng)地址,數(shù)據(jù)包就無(wú)法發(fā)往外網(wǎng)。
接著是 iptables 的處理流程,iptables 的處理流程如下圖所示
詳細(xì)的可以查看參考資料[5],請(qǐng)求通過(guò) PREROUTING 之后會(huì)判斷數(shù)據(jù)包的目標(biāo)地址是不是本機(jī)的 IP 地址,如果屬于本機(jī) IP 地址的話就跳轉(zhuǎn)到 INPUT 鏈處理,通過(guò) INPUT 鏈進(jìn)入到用戶空間進(jìn)行處理,否則進(jìn)入 FORWARD 鏈,交由內(nèi)核進(jìn)行路由轉(zhuǎn)發(fā)。
接著是 iptables 命令的 -i 和 -o 參數(shù),-i 參數(shù)是指在知道數(shù)據(jù)包由某端口(在路由交換設(shè)備上稱為端口,對(duì)應(yīng)服務(wù)器的網(wǎng)卡)傳入的時(shí)候該 iptables 規(guī)則才生效,例如 -i eth0 是數(shù)據(jù)包由 eth0 傳入的時(shí)候該規(guī)則生效,如何知道數(shù)據(jù)包由 eth0 傳入呢?目標(biāo)地址和 eth0 的 ip 地址相同,那么數(shù)據(jù)包肯定是通過(guò) eth0 傳入的;那么 -i docker0 的情況呢?數(shù)據(jù)包的源地址址的網(wǎng)段和 docker0 IP 地址屬于同一個(gè)網(wǎng)段,數(shù)據(jù)包就是從 docker0 傳入的,例如主機(jī)接收到一個(gè)數(shù)據(jù)包,源地址為 172.17.0.2/16,和 docker0 的 IP 地址 172.17.0.1/16屬于同一個(gè)網(wǎng)段,那么這個(gè)數(shù)據(jù)包是通過(guò) docker0 傳入的。
-o 同理,-o 參數(shù)就是是在知道數(shù)據(jù)包要發(fā)往哪個(gè)端口時(shí) iptables 規(guī)則才生效。iptables 怎么知道數(shù)據(jù)包要發(fā)往哪個(gè)端口呢?一個(gè)是路由表,一個(gè)是網(wǎng)段。如果路由表中指定了目標(biāo)地址符合什么要求就發(fā)往某端口, 那么就能根據(jù)路由表知道數(shù)據(jù)包發(fā)往哪個(gè)端口。如果路由表沒(méi)有指定那么可以根據(jù)主機(jī)自身已有的端口所屬的網(wǎng)段來(lái)判斷發(fā)往哪個(gè)端口,如數(shù)據(jù)包的目標(biāo)地址為 172.17.0.2/16 和 docker0 的網(wǎng)段 172.17.0.0/16 一致,那么就發(fā)往 docker0 端口。
實(shí)驗(yàn)分析
實(shí)驗(yàn)主要圍繞兩部分驗(yàn)證:
增加實(shí)驗(yàn)內(nèi)容
1.1 驗(yàn)證 iptables -I FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 是否數(shù)據(jù)包發(fā)往容器的關(guān)鍵點(diǎn)
這個(gè)實(shí)驗(yàn)在理解 Docker 網(wǎng)絡(luò)(一) -- Docker 對(duì)宿主機(jī)網(wǎng)絡(luò)環(huán)境的影響已經(jīng)做過(guò)一次類似的。實(shí)驗(yàn)過(guò)程很簡(jiǎn)單,首先在宿主機(jī)上執(zhí)行下面的命令刪除該規(guī)則
iptables -D FORWARD -o docker0 -m conntrack --ctsate RELATED,ESTABLISHED -j ACCEPT然后創(chuàng)建容器并執(zhí)行簡(jiǎn)單的 ping 指令
docker run --rm debian:stretch-slim bash -c " echo 'deb http://mirrors.163.com/debian/ stretch main non-free contrib' > /etc/apt/sources.list && apt-get update > /dev/null && apt-get install -y inetutils-ping >/dev/null 2>&1 && ping -c4 www.baidu.com"執(zhí)行結(jié)果如下
-o docker0 的規(guī)則是否為關(guān)鍵可見(jiàn)是不能獲取正確的響應(yīng)的。但這就說(shuō)明這條規(guī)則是關(guān)鍵了嗎,請(qǐng)繼續(xù)往下看
接下來(lái)為了簡(jiǎn)化實(shí)驗(yàn)步驟,這里不對(duì) DOCKER 鏈進(jìn)行處理,只對(duì) FORWARD 鏈進(jìn)行處理。由于 iptables 默認(rèn)的 FORWARD 規(guī)則是 DROP 此時(shí)任何的數(shù)據(jù)包都不會(huì)被轉(zhuǎn)發(fā),只要執(zhí)行下面命令將 FORWARD 鏈的默認(rèn)規(guī)則設(shè)置為 ACCEPT
iptables -P FORWARD ACCEPT再次執(zhí)行創(chuàng)建容器并執(zhí)行簡(jiǎn)單 ping 指令
docker run --rm debian:stretch-slim bash -c " echo 'deb http://mirrors.163.com/debian/ stretch main non-free contrib' > /etc/apt/sources.list && apt-get update > /dev/null && apt-get install -y inetutils-ping >/dev/null 2>&1 && ping -c4 www.baidu.com"執(zhí)行結(jié)果如下
-o docker0 的規(guī)則是否為關(guān)鍵可見(jiàn) Docker 容器能夠正確的接收響應(yīng)了。
所以得出 iptables -I FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 并不是數(shù)據(jù)包能發(fā)往容器的關(guān)鍵點(diǎn),這條規(guī)則也沒(méi)有作者所說(shuō)的“Linux 內(nèi)核轉(zhuǎn)發(fā)到原來(lái)的連接上” 這個(gè)功能。
1.2 驗(yàn)證只指定了 SNAT 沒(méi)有指定 DNAT,接收到的數(shù)據(jù)包的目標(biāo)地址會(huì)不會(huì)被改寫
首先我們執(zhí)行兩條指令將 iptables 復(fù)原
iptables -I FORWARD -o docker0 -m conntrack --ctsate RELATED,ESTABLISHED -j ACCEPT iptables -P FORWARD DROP在這里需要用到抓包工具和數(shù)據(jù)包分析工具,抓包工具使用 Linux 的 TCPdump,數(shù)據(jù)包分析工具使用 Windows 的 Wireshark
TCPdump 需要抓取宿主機(jī)外網(wǎng)網(wǎng)絡(luò)設(shè)備(在這里是 enp0s3,一般是 eth0)的數(shù)據(jù)包和 docker0 的數(shù)據(jù)包。在這里開(kāi)啟三個(gè)終端。
先執(zhí)行 ip a 查看主機(jī)的網(wǎng)絡(luò)設(shè)備信息
網(wǎng)絡(luò)設(shè)備信息可以得出主機(jī) enp0s3 網(wǎng)卡的 IP 地址是 10.0.2.15,docker0 網(wǎng)卡的 IP 地址是 172.17.0.1,推算出 Docker 容器的 IP 地址應(yīng)該符合 172.17.0.0/16 的模式
終端一中監(jiān)聽(tīng) enp0s3 ,執(zhí)行命令
tcpdump -i enp0s3 -w ./enp0s3.cap終端二中監(jiān)聽(tīng) docker0 ,執(zhí)行命令
tcpdump -i docker0 -w ./docker0.cap終端三中創(chuàng)建容器并執(zhí)行 ping 命令
docker run --rm debian:stretch-slim bash -c " echo 'deb http://mirrors.163.com/debian/ stretch main non-free contrib' > /etc/apt/sources.list && apt-get update > /dev/null && apt-get install -y inetutils-ping >/dev/null 2>&1 && ping -c4 www.baidu.com"等 ping 命令執(zhí)行完之后,結(jié)束終端一和終端二的命令。在 Windows 上使用 Wireshark 分析 enp0s3.cap 和 docker0.cap,在這里可以直接查看最后一個(gè)數(shù)據(jù)包。
Wireshark 可視化數(shù)據(jù)包分析通過(guò) Data 段可以得出這兩個(gè)數(shù)據(jù)包的內(nèi)容是一致的, Internet Protocol Version 4段可以得出兩個(gè)包的源IP Src 都為 14.215.177.38,而目標(biāo)IP Dst 不一樣。enp0s3 中抓取的數(shù)據(jù)包 Dst 為 10.0.2.15 與 enp0s3 的 IP 地址一致。 docker0 抓取的數(shù)據(jù)包 Dst 為 172.17.0.2 IP 符合 172.17.0.0/16 的模式。
可以看出數(shù)據(jù)包的目標(biāo) IP 被重寫了,而這時(shí)并沒(méi)有設(shè)置 DNAT。因此可以得出,只設(shè)置了 SNAT 沒(méi)有設(shè)置 DNAT ,接收到的數(shù)據(jù)包的目標(biāo)地址一樣會(huì)被重寫。
2.1 指定了 SNAT 后,外網(wǎng)響應(yīng)的數(shù)據(jù)包的目標(biāo)地址的改寫是發(fā)生在進(jìn)入 PREROUTING 鏈前還是出 PREROUTING 鏈后
為了確定在 POSTROUTING 指定了 SNAT 之后,外網(wǎng)響應(yīng)的數(shù)據(jù)包目標(biāo)地址的改寫出現(xiàn)在 PREROUTING 之前還是之后,只需要在 PREROUTING 鏈上加上規(guī)則
iptables -t nat -A PREROUTING -d 172.17.0.0/16 -j DNAT --to 10.0.2.15如果目標(biāo)地址改寫發(fā)生在 PREROUTING 之前,那么響應(yīng)數(shù)據(jù)包的目標(biāo)地址將會(huì)被重寫為本機(jī)的 enp0s3 地址,因而數(shù)據(jù)包不會(huì)進(jìn)入到 FORWARD 流程,容器就不能收到響應(yīng)數(shù)據(jù)包。
docker run --rm debian:stretch-slim bash -c " echo 'deb http://mirrors.163.com/debian/ stretch main non-free contrib' > /etc/apt/sources.list && apt-get update > /dev/null && apt-get install -y inetutils-ping >/dev/null 2>&1 && ping -c4 www.baidu.com"執(zhí)行結(jié)果如下
SNAT 也會(huì)作用在 PREROUTING 之后可以得出指定 SNAT 后, 外網(wǎng)響應(yīng)數(shù)據(jù)包目標(biāo)地址的改寫是發(fā)生在數(shù)據(jù)包出 PREROUTING 鏈之后進(jìn)入 FORWARD 鏈之前。
2.2 指定了 DNAT 后,響應(yīng)外網(wǎng)的數(shù)據(jù)包的源地址的改寫是發(fā)生在進(jìn)入 POSTROUTING 鏈前還是出 POSTROUTING 鏈后
這里先恢復(fù) PREROUTING 鏈
iptables -t nat -A PREROUTING -d 172.17.0.0/16 -j DNAT --to 10.0.2.15然后使用端口映射創(chuàng)建一個(gè) nginx 容器
docker run --name nginx -d -p 8080:80 nginx接著修改 POSTROUTING 鏈,這里要做的修改有,將 Docker 自帶的 SNAT 和創(chuàng)建端口映射容器產(chǎn)生的 SNAT 刪除
iptables -t nat -D POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE iptables -t nat -D POSTROUTING -s 172.17.0.2/32 -d 172.17.0.2/32 -p tcp -m tcp --dport 80 -j MASQUERADE然后增加一條新的 SNAT
iptables -t nat -D POSTROUTING -p tcp -m tcp --dport 8080 -j SNAT --to 172.17.0.2打開(kāi)一個(gè)新終端,使用 tcpdump 監(jiān)聽(tīng)連接外網(wǎng)網(wǎng)卡的信息(這里使用的是 enp0s8)
tcpdump -i enp0s8 -w ./enp0s8.cap接著外網(wǎng)訪問(wèn) 8080 端口,結(jié)束 tcpdump ,使用 Wireshark 打開(kāi) enp0s8.cap
DNAT也會(huì)作用在POSTROUTING之后可以看到從 enp0s8 端口輸出的數(shù)據(jù)包的 Src 都是 192.168.99.1 。
這說(shuō)明指定了 DNAT 沒(méi)有指定 SNAT 的時(shí)候在 POSTROUTING 鏈之后也會(huì)對(duì)數(shù)據(jù)包進(jìn)行源地址和源端口重寫。
總結(jié)
這篇文章主要從邏輯,理論和實(shí)驗(yàn)分析了《Docker 源碼分析》中關(guān)于 Docker 容器網(wǎng)絡(luò) SNAT,DNAT 和 iptables 的一些錯(cuò)誤。考慮到書籍作者 @孫宏亮 也在知乎上,希望作者在確認(rèn)了問(wèn)題后下版能夠做出相應(yīng)的修改。如果已經(jīng)有人提出過(guò)這個(gè)問(wèn)題并已經(jīng)打算修正那么大可以忽略這篇文章。
這篇文章主要是分析了 SNAT 和 DNAT 在 Docker 容器中的作用,使用 tcpdump + Wireshark 的工具組合抓取和分析數(shù)據(jù)包。這本來(lái)是三言兩語(yǔ)就能夠說(shuō)出對(duì)錯(cuò)的問(wèn)題,但為了增加文章的篇幅特地的將問(wèn)題分解為邏輯分析,理論分析和實(shí)驗(yàn)驗(yàn)證三個(gè)步驟,希望對(duì)大家有用。
參考資料
- [1] 《Docker 源碼分析》
- [2] SNAT 與 DNAT
- [3] 聊聊 NAT 技術(shù)那些事
- [4] 理解 Docker 網(wǎng)絡(luò)(一) -- Docker 對(duì)宿主機(jī)網(wǎng)絡(luò)環(huán)境的影響
- [5] iptables詳解(1): iptables概念
總結(jié)
以上是生活随笔為你收集整理的docker ip地址_理解 Docker 网络(番外) -- 《Docker 源码分析》勘误的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 这张图可以用于电脑桌面了把桌面上的图片
- 下一篇: 葫芦巴籽功效与作用