Docker 網絡原理
1. Docker 網絡理論
容器網絡實質上是由 Dokcer 為應用程序所創(chuàng)造的虛擬環(huán)境的一部分,它能讓應用從宿主機操作系統(tǒng)的網絡環(huán)境中獨立出來,形成容器自有的網絡設備、IP 協(xié)議棧、端口套接字、IP 路由表、防火墻等等與網絡相關的模塊。
Docker 為實現(xiàn)容器網絡,主要采用的架構由三部分組成:CNM、Libnetwork 和驅動。
1.1. CNM
Docker 網絡架構采用的設計規(guī)范是 CNM(Container Network Model):CNM 中規(guī)定了 Docker 網絡的基礎組成要素:Sandbox、Endpoint、Network。如圖所示,
- Sandbox,提供了容器的虛擬網絡棧,也即端口套接字、IP 路由表、防火墻、DNS 配置等內容。主要用于隔離容器網絡與宿主機網絡,形成了完全獨立的容器網絡環(huán)境。
- Network,Docker 內部的虛擬子網,網絡內的參與者相互可見并能夠進行通訊。Docker 的虛擬網路和宿主機網絡是存在隔離關系的,其目的主要是形成容器間的安全通訊環(huán)境。
- Endpoint,就是虛擬網絡的接口,就像普通網絡接口一樣,Endpoint 的主要職責是負責創(chuàng)建連接。在 CNM 中,終端負責將沙盒連接到網絡。個人理解:Endpoint 與常見的網絡適配器類似,也就意味著 Endpoint 只能接入某一個網絡。因此,如果容器需要接入到多個網絡,就需要多個 Endpoint。
★
如上圖所示(我們將圖中的三個容器從左到右依次標記為 1、2、3),那么容器 2 有兩個 endpoint 并且分別接入 NetworkdA 和 NetworkB。那么容器 1 和容器 2 是可以實現(xiàn)通信的,因為都接入了 NetworkA。但是容器 3 和容器 1,以及容器 2 的兩個 Endpoint 之間是不能通信的,除非有三層路由器的支持。
”
1.2. Libnetwork
Libnetwork 是 CNM 的標準實現(xiàn)。Libnetwork 是開源庫,采用 Go 語言編寫(跨平臺的),也是 Docker 所使用的庫,Docker 網絡架構的核心代碼都在這個庫中。Libnetwork 實現(xiàn)了 CNM 中定義的全部三個組件,此外它還實現(xiàn)了本地服務發(fā)現(xiàn)、基于 Ingress 的容器負載均衡,以及網絡控制層和管理層功能。
1.3. 驅動
如果說 Libnetwork 實現(xiàn)了控制層和管理層功能,那么驅動就負責實現(xiàn)數據層。比如網絡的連通性和隔離性是由驅動來處理的。驅動通過實現(xiàn)特定網絡類型的方式擴展了 Docker 網絡棧,例如橋接網絡和覆蓋網絡。
Docker 內置了若干驅動,通常被稱作原生驅動或者本地驅動。比如 Bridge Driver、Host Driver、Overlay Driver、MacLan Driver、None Driver 等等。第三方也可以編寫 Docker 網絡驅動,這些驅動被叫做遠程驅動,例如 Calico、Contiv、Kuryr 以及 Weave 等。每個驅動負責創(chuàng)建其上所有網絡資源的創(chuàng)建和管理。
其中 Bridge 和 Overlay 在開發(fā)過程中使用頻率較高。
- Bridge,Docker 容器的默認網絡驅動,通過網橋來實現(xiàn)網絡通訊。
- Overlay,借助 Docker 集群模塊 Docker Swarm 搭建的跨 Docker Daemon 網絡。通過它可以搭建跨物理網絡主機的虛擬網絡,進而讓不同物理機中運行的容器感知不到多個物理機的存在。
在 Docker 安裝時,會自動安裝一塊 Docker 網卡稱為 docker0,用于 Docker 各容器及宿主機的網絡通信。
docker0 Link encap:Ethernet HWaddr 02:42:be:6b:61:dc
inet addr:172.17.0.1 Bcast:172.17.255.255 Mask:255.255.0.0
inet6 addr: fe80::42:beff:fe6b:61dc/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:332 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 B) TX bytes:30787 (30.7 KB)
★
個人理解:CNM 就是一個設計文檔,指導你怎么去實現(xiàn)容器網絡,而 Libnetwork 和驅動則是其具體實現(xiàn),從而確保容器網絡的通信。
”
2. 橋接網絡
Docker 的 bridge 網絡采用內置的 bridge 驅動,而 bridge 的底層采用的是 Linux 內核中 Linux bridge 技術(這意味著 bridge 是高性能并且是非常穩(wěn)定的)。
那么 Linux 內核中 Linux bridge 應用于容器的話,到底是一個什么樣的拓撲圖呢?如圖所示(這個拓撲關系不清楚接下去的很多東西難以理解,所以先貼出采用 bridge 之后的一個拓撲圖),由于容器運行在自己單獨的 network namespace 中,所以有單獨的協(xié)議棧。容器中配置網關為 172.17.0.1,發(fā)出去的數據包先到達 br0,然后交給主機的協(xié)議棧,由于目的 IP 是外網 IP,且主機會開啟 IP forward 功能,于是數據包通過主機的 eth0 發(fā)出去。由于 172.17.0.1 是內網 IP ,所以一般發(fā)出去之前會做 NAT 轉換。由于要進過主機的協(xié)議棧并且要做 NAT 轉換,所以性能上可能會差點,但是優(yōu)點就是容器處于內網中,安全性相對要高點。
默認情況下,創(chuàng)建的容器在沒有使用 --network 參數指定要加入的 docker 網絡時,默認都是加入 Docker 默認的單機橋接網絡,也就是下面的 name 為 bridge 的網絡。
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
0dda6f303b8b bridge bridge local
而默認的 bridge 網絡是被映射到內核中為 docker0 的網橋上。
$ ip link show docker0
4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default
link/ether 02:**:**:**:**:** brd ff:ff:ff:ff:ff:ff
$ docker network inspect bridge | grep bridge.name
"com.docker.network.bridge.name": "docker0",
Docker 默認的 bridge 網絡和 Linux 內核中的 “docker0” 網橋是一個對應關系,如圖所示。bridge 是 Docker 中對網絡的命名,而 docker0 是內核中網橋的名字。(個人理解:你就可以把 bridge 和 docker0 當成 Linux 網橋的兩個名字,兩個都是代表同一個東西。docker 為了管理網絡,又給 docker0 這個網橋取名為 bridge)。
那么,容器在沒有指定要加入的網絡情況下,都是加入這個網絡的,假如之后的拓撲圖跟前面的一樣。另外,單機橋接網絡中的容器想要對外發(fā)布服務的話,需要依賴于端口映射,這也是為啥我們在啟動容器的時候需要指定端口映射關系 的原因。
下面我們通過創(chuàng)建一個新的 Docker 橋接網絡來闡述容器內部的通信、端口映射等情況。
2.1. 創(chuàng)建新的單機橋接網絡
使用 docker network create
命令,我們可創(chuàng)建一個名為 “l(fā)ocalnet” 的單機橋接網絡,并且在內核中還會多出一個新的 Linux 網橋。
$ docker network create -d bridge localnet
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
......
f55943e20201 localnet bridge local
在創(chuàng)建完之后,我們可以通過 brctl 工具來查看系統(tǒng)中的 Linux 網橋。可以看到,輸出的內容中包含了兩個網橋,docker0 是默認的 Docker bridge 網絡所使用的網橋,br-f55943e20201 是 Docker localnet 網絡所使用的網橋。這兩個網橋目前都沒有任何設備接入(看 interface 列)。這兩個網橋所處的網段是不同的,一個是 172.18.0.1,另一個則是 172.17.0.1。
$ brctl show
bridge name bridge id STP enabled interfaces
br-f55943e20201 8000.02421d9aa3e1 no
docker0 8000.0242be6b61dc no
$ ifconfig
br-f55943e20201 Link encap:Ethernet HWaddr 02:42:1d:9a:a3:e1
inet addr:172.18.0.1 Bcast:172.18.255.255 Mask:255.255.0.0
......
docker0 Link encap:Ethernet HWaddr 02:42:be:6b:61:dc
inet addr:172.17.0.1 Bcast:172.17.255.255 Mask:255.255.0.0
......
2.2. 同個網絡中的容器間通信
使用下面這條命令即可運行一個新的容器,并且讓這個新容器加入到 localnet 這個網絡中的。
$ docker container run -d --name demo1 --network localnet alpine sleep 3600
我們查看網橋的情況,demo1 的網絡接口連接到了網橋 br-f55943e20201 上,如圖所示。
$ brctl show
bridge name bridge id STP enabled interfaces
br-f55943e20201 8000.02421d9aa3e1 no vethf6a3fba
docker0 8000.0242be6b61dc no
如果在相同的網絡中繼續(xù)接入新的容器,那么新接入的容器是可以通過 demo1 這個名稱來 ping 通的。如下所示,我們創(chuàng)建了一個新的容器(demo2),并且在這個容器中直接 ping demo1 發(fā)現(xiàn)可以的 ping 通的。這是因為,demo2 運行了一個本地 DNS 解析器,該解析器會將該請求轉發(fā)到 Docker 內部 DNS 服務器中。DNS 服務器中記錄了容器啟動時通過 --name 或者 --net-alias 參數指定的名稱和容器之間的和映射關系。
之外,我們可以看到 demo1 的 IP 地址是 172.18.0.2,這個與網橋 br-f55943e20201 是處于同一個網段內的。
/ # ls
bin dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var
/ # ping demo1
PING demo1 (172.18.0.2): 56 data bytes
64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.230 ms
64 bytes from 172.18.0.2: seq=1 ttl=64 time=0.161 ms
★
Docker 默認的 bridge 網絡是不支持通過 Docker DNS 服務進行域名解析的,自定義橋接網絡是可以的。
”
2.3. 暴露端口
同一個網絡中的容器之間雖然可以互相 ping 通,但是并不意味著可以任意訪問容器中的任何服務。Docker 為容器增加了一套安全機制,只有容器自身允許的端口,才能被其他容器所訪問。如下所示,我們可以通過 docker container ls
命令可以看到容器暴露給其他容器訪問的端口是 80,那么我們只能容器的 80 端口進行訪問,而不能對沒有開放的 22 端口進行訪問。
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5a8dece3841d nginx "/docker-entrypoint.…" 3 minutes ago Up 3 minutes 80/tcp web
$ telnet 172.18.0.2 80
Trying 172.18.0.2...
Connected to 172.18.0.2.
Escape character is '^]'.
$ telnet 172.18.0.2 20
Trying 172.18.0.2...
telnet: Unable to connect to remote host: Connection refused
我們可以在鏡像創(chuàng)建的時候定義要暴露的端口,也可以在容器創(chuàng)建時定義要暴露的端口,使用 --expose。如下所示,就額外暴露了 20、22 這兩個端口。
$ docker container run -d --name web --expose 22 --expose 20 nginx
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4749dac32711 nginx "/docker-entrypoint.…" 12 seconds ago Up 10 seconds 20/tcp, 22/tcp, 80/tcp web
容器的端口暴露類似于打開了容器的防火墻,具體能不能通過這個端口訪問容器中的服務,還得看容器中有無應用監(jiān)聽并處理來自這個端口的請求。
2.4. 端口映射
上面提到的橋接網絡中的容器只能與位于相同網絡中的容器進行通信,假如一個容器想對外提供服務的話,需要進行端口映射。端口映射將容器的某個端口映射到 Docker 主機端口上。那么任何發(fā)送到該端口的流量,都會被轉發(fā)到容器中。如圖所示,容器內部開放端口為 80,該端口被映射到了 Docker 主機的 10.0.0.15 的 5000 端口上。最終訪問 10.0.0.15:5000 的所有流量都會被轉發(fā)到容器的 80 端口。
如下圖所示,假設我們運行了一個新的 web 服務容器,并且將容器 80 端口映射到 Dokcer 主機的 5000 端口。
$ docker container run -d --name web --network localnet -p 5000:80 nginx
那么,當我們通過 web 瀏覽器訪問 Docker 主機的 5000 端口時,會得到如圖所示的結果。外部系統(tǒng)可以通過訪問 Docker 主機的 TCP 端口 5000,來訪問運行在橋接網絡上的 Nginx 容器了。
端口映射之后,假如主機的 5000 端口被占用了,那么其他容器就不能再使用這個端口了。
3. 相關命令
# 列出運行在本地 docker 主機上的全部網絡
docker network ls
# 提供 Docker 網絡的詳細配置信息
docker network inspect <NETWORK_NAME>
# 創(chuàng)建新的單機橋接網絡,名為 localnet,其中 -d 不指定的話,默認是 bridge 驅動。并且主機內核中也會創(chuàng)建一個新的網橋。
docker network create -d bridge localnet
# 刪除 Docker 主機上指定的網絡
docker network rm
# 刪除主機上全部未使用的網絡
docker network prune
# 運行一個新的容器,并且讓這個容器加入 Docker 的 localnet 這個網絡中
docker container run -d --name demo1 --network localnet alpine sleep 3600
# 運行一個新的容器,并且讓這個容器暴露 22、20 兩個端口
docker container run -d --name web --expose 22 --expose 20 nginx
# 運行一個新的容器,并且將這個容器的 80 端口映射到主機的 5000 端口
docker container run -d --name web --network localnet -p 5000:80 nginx
# 查看系統(tǒng)中的網橋
brctl show
4. 巨人的肩膀
1. https://zhuanlan.zhihu.com/p/57203485