docker的容器網路溝通

docker做了什麼?

Timothy Liao
Coding Book Club
14 min readJun 5, 2022

--

前言

在學習docker的網路溝通時,發現他的實踐與了解我們的「電腦」如何與外界(internet、intranet)溝通脫不了關係,於是會有很多關鍵字我會列在前面,並附上一些資料,並假設讀者在往下看的時候已經知道這些關鍵字的定義。若不明白的話,可以透過其中我以自己的角度理解的定義,也可以在額外自行搜尋資料探究一下~有錯的話歡迎糾正。

另外,我會透過思考「容器要能夠通訊需要解決那些問題」來探究容器之所以能跟外界通訊是用什麼樣的技術解決了什麼問題。

另外推薦這份資料,他頗完整的說明了當你點下網頁按鈕時背後所發生的事情。

友情提示:
前半段比較偏向基礎的理論、原理,後半段比較偏向工具及操作,如果需要的話可以直接從中場休息後開始看。

關鍵字

  1. NAT(Network Address Translation網路地址轉換): NAT最大的用途是用於解決IP位址不夠用的狀況,是一種將私有地址轉換為公開地址的技術,可以把使用NAT技術的server視作出入口,當內部網路要對外溝通時,這個出口就會把該私有地址翻譯成公開地址,也因此,在談NAT時,一般都能相對區分得出誰是內部、誰是外部,比如多個docker(內)對主機(外)、多台共用分享器的主機(內)對網路網路(外)。
    舉例:路由器、公司私人網路、docker
  2. port-forwarding(通訊埠轉發): 剛剛NAT是由內部對外部,而由外部到內部就需要透過這個通訊埠轉發的技術,當有一個request進到一個內部網路的路口時,會由該路口對request的ip及port進行翻譯,翻譯成內部網路的ip及port,然後將該資料轉發到該位址。
  3. DNS(domain name system網域名稱系統): 一般來說,我們的地址都是IPv4或是IPv6,像是xxx.xxx.xxx.xxx,但這其實很不好記憶及無意義,可以把DNS當作一本電話簿,把目前我們看到的網域名稱(domain name),比如google.com.tw翻譯成正確的ip位置。
  4. veth(Virtual Ethernet)-pair: linux提供的網路裝置,我把他理解成是如同硬體的網路卡,他有入口及出口,有資料進入一端必定有資料從另一端流出,並且每個veth都可以被賦予ip地址,而一個實例(主機、容器、name space…)必定需要至少一個pair才能與外界溝通。

容器要能夠通訊,需要解決那些問題?

作為使用container的用戶,我建立了一個web application,使得其他用戶可以透過www.fakeApplication.com.tw(fake!!!)訪問在我的主機裡面的container 正在 listen的port及ip 。

我們跳過網路溝通的DNS翻譯、握手,只談ip,並聚焦在資訊如何進出主機及container,於是有以下問題。

  1. container如何在建立時獲取內部的唯一地址?
  2. container之間可以互相溝通嗎?為什麼?
  3. 容器如何向外訪問internet?
  4. 外部如何訪問容器?

container如何在建立時獲取內部的唯一地址?

實際案例

這個問題我們可以透過實際建立container來看一下

// 建立nginx container
docker container run -d --rm --name sample-app nginx
// 查詢network的資料
docker network ls
NETWORK ID NAME DRIVER SCOPE
e184e932b2f2 bridge bridge local
605e12b6501d host host local
// 查詢該network的詳細資料; 這邊省略大部分的資訊
docker network inspect bridge
[
{
"Name": "bridge",
"Containers": {
"e448509b28d239385da53f5933f3e6cdcccfc43957ff13ac8cb2b52c0c3aea33": {
"Name": "sample-app",
"EndpointID": "58400cb7ce166b6395e58ba1a2087abbe5e440901091b6d9a3ffacdfec05cbe3",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
}
},
}
]
// 也可以直接查詢剛剛建立的container的詳細資料; 一樣省略大部分內容
docker container inspect sample-app
[
{
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"MacAddress": "02:42:ac:11:00:02",
"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "e184e932b2f2527174219213377d5ab3e855301d6473362e50543bc637987526",
"EndpointID": "58400cb7ce166b6395e58ba1a2087abbe5e440901091b6d9a3ffacdfec05cbe3",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:11:00:02",
"DriverOpts": null
}
}
}
}
]

透過建立container,並調查container的網路分配狀況,我們可以看到container被分配給一個叫做bridge的東西,此外這個container在bridge內部的ip是172.17.0.2,而這個內部網路的NAT Gateway的地址則是在172.17.0.1。

另外補充,在linux系統內,這個bridge的名稱叫做docker0(註1)。

問題來了,這個bridge究竟是什麼?他又做了什麼事情呢?

註1: 在wsl裡面,你使用brtcl show想看到這個bridge的命名是沒辦法的喔。

linux namespace

在說到bridge以及他做了什麼之前不得不稍微說說linux的namespace及他的網路隔離,namespace是linux用來隔離資源的方式,你可以把不同的namespace視為完全不同的個體。

他總共有六個型別的資源可以使用namespace隔離,網路就是其中一種,讓每個namespace擁有獨立的網路設備、IP、路由、port

參考連結: https://www.youtube.com/watch?v=j_UUnlVC2Ss&ab_channel=KodeKloud

當我們需要建立container的連線時,需要以下的步驟,因為在下圖已經說得夠清楚了,我就直接引用了:

https://philipzheng.gitbook.io/docker_practice/underly/network

不過有趣的部份就在於其實也可以直接透過--net=host直接使用主機的namespace,那bridge做到了什麼?

container間可以互相溝通嗎?為什麼?

這個問題的答案是這樣的,只要設定好都可以XD這樣是不是很像沒有回答?

那我們說的在更精準一點,所有使用default值,不另外設定網路的container都可以互相溝通,因為他們都共享一個交換器: Bridge。

https://cloud.tencent.com/developer/article/1587094

Bridge的用處

  1. 作為虛擬交換機,幫助交換器內的container可以溝通
  2. 隔離沒有連接到同一個bridge的container
  3. 保護內部
  4. 代替使用者完成所有veth-pair的建立及綁定,並建立NAT規則(不算是bridge的功能,但是docker0會做的事情)

缺點

  1. 因為資料出去時得經過NAT,回來也需要經過IpTable mapping,會相對比較慢一些

另外有一些docker的行為可以了解一下

  1. 預設值:若沒有指定,docker的網路會直接連接到docker0這個bridge
  2. 可以自定義bridge,自定義bridge有DNS的功能

原理探查:

至於linux是怎麼讓資料在container之間互相傳遞的,可以使用以下指令

# 查看路由; 我只節錄需要的部分
$ ip route
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1

這個路由的設定代表你輸入的是172.17.0.0/16這個範圍內的路由時會透過172.17.0.1進入,還記得前面有提到我們的gateway也就是docker0的位置也就是在172.17.0.1嗎?

參考資料

container如何向外溝通?

從前面我們已經知道container的地址是如何建立,以及container間如何溝通,接著會來了解一下container是如何與外界溝通。

兩個子問題

  1. 當我們在request ip時發生什麼(例如ip 172.17.0.2)?
  2. 當外部希望request container時發生了什麼?

當我們在request ip時發生什麼?

再看一次路由表

# 查看路由
$ ip route
default via 172.20.80.1 dev eth0

這個路由代表所有未包含在其他路由的要求會透過172.20.80.1出去

而當我們request 外部ip時,外部要如何認得這是哪個container(內部ip)?如果狀況是需要內部IP轉外部IP時,就是NAT出馬的機會到了!!

關於翻譯的規則是透過ip table來定義及實現的:

# 查詢ip table
sudo iptables --list -t nat
# 在進入路由之後進行的工作
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 172.17.0.0/16 anywher

可以看到ip table將所有172.17.0.0/16翻譯成172.20.80.1

不過實際上ip table是怎麼運作的我就不太清楚了,關於ip table我只知道功能很強可以做到不少事情。

目前僅知道docker幫我們設定好了!!XD

外部如何訪問容器?

從外部主機要訪問內部container ip最大的問題點就是:既然出去的時候都統一被當成公開的ip,那其他外部的request要如何可以連線/辨認內部的container ip?

舉個例子:比如說我們起了一個container nginx,預設80 port,我們在container及本地都可用,但外部還是需要能夠訪問這個container阿,該怎麼做呢?

這時候就會用到另一個由外部進到內部的技術叫做port-forwarding(通訊埠轉發),在docker的使用方法是

# 使用 -p 參數
docker container run -p 8080:80 <image name>

這個參數的寫法代表的是<主機監聽ip>: <映射的container ip>

這個參數做的事情其實就是去新增ip table的設定

sudo iptables -t nat -nvxL
太長了會跑版…改貼到文件檔上

中場休息

到這邊大概將docker網路一些基本的原理說明了,接著會寫的是關於工具(docker)操作的部分。

建立自己的bridge

指令操作

# 建立新的bridge 
docker network create -d bridge <bridge name>
# 將bridge連到container
docker network connect <bridge name> <container name>
# 建立container的同時連接自建的bridge
docker container run — network <bridge name> <image name>

自建bridge具有DNS server的功能

default bridge 不具備此功能。

用途是可以使用其他container的名字代替ip位置

官方建議

官方建議在production上使用自建的bridge

https://docs.docker.com/network/network-tutorial-standalone/

至於為什麼,這裡有更詳細的解釋

  1. 不完整的功能:
    無法支援DNS在文件中視作技術債(legacy detail),雖然可以使用--link,但讓過程變得更為複雜。
  2. 自定義bridge提供更完整的隔離:
    所有新建立的container會被自動連結到該bridge,可能會產生安全性風險。
  3. 是否可在運行中調整網路選項:
    當你使用的是預設bridge時,在需要更換bridge時需要暫停整個container並重新建立(recreate)實例。
  4. default bridge configure造成的麻煩:
    雖然default也可以調整設定值,但除了會造成如上述的麻煩外,default bridge的設定是在docker之外的,所以需要將docker重啟才行…

容器網路設定

# 在run container的時候使用--net 就可以了

包括有下圖所有以及none,我就介紹host及none就好~

容器網路driver: host

定義

For standalone containers, remove network isolation between the container and the Docker host, and use the host’s networking directly. See use the host network.                   --docker官方文件

大概意思就是說直接使用host的network namespace

優點

效能upup,

缺點

  1. 僅能用於linux
  2. 安全性問題(?),打個問號是我在一篇資料上有看到共用host的namespace可能導致安全性問題,但我後來找不到那篇資料了…然後也沒在docker的文件看到有強調此事,但隔離本身就能提高安全性,所以應該是沒有必要就不要使用host mode吧。

參考資料

容器網路driver: none

定義

除了建立容器的network namespace外,沒有其他container網路設定。

就等於是一張白紙,通常應該是有特殊需求,不希望docker做太多設定。

後記

我花比較多篇幅寫docker container network的概念,工具的部分反倒少了,而且修了又修,實在很累人,希望能讓自己對這些概念更內化,並也希望有機會幫助到其他也想多了解一些原理的人。

--

--