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