Docker 網絡原理概述
概述
Docker
原生網絡是基於 Linux 的 網絡命名空間
(net namespace) 和 虛擬網絡設備
(veth pair)實現的。當 Docker
進程啓動時,會在宿主機上創建一個名稱爲 docker0
的 虛擬網橋,在該宿主機上啓動的 Docker
容器會連接到這個虛擬網橋上。
$ ifconfig
# 輸出如下
docker0: ... mtu 1500
inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255
...
虛擬網橋的工作方式和物理交換機類似,宿主機上所有的容器通過虛擬網橋連接在一個二層網絡中。
從 docker0
子網中分配一個 IP 給容器使用,並設置 docker0
的 IP 地址爲容器的默認網關。在宿主機上創建一對虛擬網卡 veth pair
設備, Docker
將 veth pair
設備的一端放在新創建的容器中,並命名爲 eth0
(容器的網卡), 另一端放在宿主機中,以 vethxxx
類似的名字命名, 並將這個網絡設備連接到 docker0
網橋中。
Docker 會自動配置 iptables 規則和配置 NAT,便於連通宿主機上的 docker0
網橋,完成這些操作之後,容器就可以使用它的 eth0
虛擬網卡,來連接其他容器和訪問外部網絡。
Docker
中的網絡接口默認都是虛擬的接口,Linux 在內核中通過 數據複製
實現接口之間的數據傳輸,可以充分發揮數據在不同 Docker
容器或容器與宿主機之間的轉發效率, 發送接口發送緩存中的數據包,將直接複製到接收接口的緩存中,無需通過物理網絡設備進行交換。
# 查詢主機上 veth 設備
$ ifconfig | grep veth*
veth06f40aa:
...
vethfdfd27a:
圖片來源: https://www.suse.com/c/rancher_blog/introduction-to-container-networking/
虛擬網橋 docker0
通過 iptables 配置與宿主機器上的網卡相連,符合條件的請求都會通過 iptables 轉發到 docker0
, 然後分發給對應的容器。
# 查看 docker 的 iptables 配置
$ iptables -t nat -L
# 輸出如下
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
DOCKER all -- anywhere anywhere ADDRTYPE match dst-type LOCAL
Chain DOCKER (2 references)
target prot opt source destination
RETURN all -- anywhere anywhere
網絡驅動
Docker
的網絡子系統支持插拔式的驅動程序,默認存在多個驅動程序,並提供核心網絡功能。
Docker daemon 通過調用 libnetwork 提供的 API 完成網絡的創建和管理等功能。libnetwork 中使用了 CNM 來完成網絡功能, CNM 中主要有沙盒(sandbox)、端點(endpoint)和網絡(network)3 種組件。
-
沙盒:一個沙盒包含了一個容器網絡棧的信息。一個沙盒可以有多個端點和多個網絡,沙盒可以對容器的接口、路由和 DNS 設置等進行管理,沙盒的實現可以是 Linux network namespace、FreeBSD Jail 或者類似的機制
-
端點:一個端點可以加入一個沙盒和一個網絡。一個端點只屬於一個網絡和一個沙盒,端點的實現可以是 veth pair、Open vSwitch 內部端口或者相似的設備
-
網絡:一個網絡是一組可以直接互相聯通的端點。一個網絡可以包含多個端點,網絡的實現可以是 Linux bridge、VLAN 等
bridge 模式
bridge
是默認的網絡模式,爲容器創建獨立的網絡命名空間,容器具有獨立的網卡等所有的網絡棧。使用該模式的所有容器都是連接到 docker0
這個網橋, 作爲 虛擬交換機
使容器可以相互通信,但是由於宿主機的 IP 地址與容器 veth pair 的 IP 地址不在同一個網段,所以爲了和宿主機以外的網絡通信, Docker 採用了端口綁定的方式,也就是通過 iptables 的 NAT,將宿主機上的端口流量轉發到容器。
bridge
模式已經可以滿足 Docker 容器最基本的使用需求了,但是其與外界通信時使用 NAT,增加了通信的複雜性,在複雜場景下使用會有限制。
$ docker network inspect bridge
# 輸出如下 (節選部分信息)
[
{
"Name": "bridge",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
"Containers": {
# 使用 bridge 網絡的容器列表
},
}
]
通過上面的輸出可以看到,虛擬網橋 的 IP 地址就是 bridge
網絡類型的網關地址。
我們可以從輸出的 Containers
容器列表中找一個容器,查看其網絡類型和配置:
$ docker inspect 容器ID
# 輸出如下 (節選部分信息)
[
...
"NetworkSettings": {
"Bridge": "",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.4",
"Networks": {
"bridge": {
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.4",
}
}
...
]
通過上面的輸出可以看到,虛擬網橋 的 IP 地址就是 bridge
網絡類型的容器的網關地址。
實現機制
在 iptables 做了 DNAT 規則,實現端口轉發功能:
# iptables 配置查看
$ iptables -t nat -vnL
# 輸出如下
Chain PREROUTING (policy ACCEPT 37M packets, 2210M bytes)
...
0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.4:80
當容器需要將端口映射到宿主機時,Docker
會自動爲該容器分配一個 IP 地址,同時新增一個 iptables 規則。
host 模式
容器不會獲得一個獨立的網絡命名空間,而是和宿主機共用一個。容器不會虛擬出自己的網卡,配置自己的 IP 等,而是直接使用宿宿主機的。但是容器的其他方面,如文件系統、進程列表等還是和宿宿主機隔離的,容器對外界是完全開放的,能夠訪問到宿主機,就能訪問到容器。
host
模式降低了容器與容器之間、容器與宿主機之間網絡層面的隔離性,雖然有性能上的優勢,但是引發了網絡資源的競爭與衝突,因此適用於容器集羣規模較小的場景。
啓動一個網絡類型爲 host
的 Nginx
容器:
$ docker run -d --net host nginx
Unable to find image 'nginx:latest' locally
latest: Pulling from library/nginx
...
f202870092fc40bc08a607dddbb2770df9bb4534475b066f45ea35252d6e76e2
查看網絡類型爲 host
的容器列表:
$ docker network inspect host
# 輸出如下 (節選部分信息)
[
{
"Name": "host",
"Scope": "local",
"Driver": "host",
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"Containers": {
# 使用 host 網絡的容器列表
"f202870092fc40bc08a607dddbb2770df9bb4534475b066f45ea35252d6e76e2": {
"Name": "frosty_napier",
"EndpointID": "7306a8e4103faf4edd081182f015fa9aa985baf3560f4a49b9045c00dc603190",
"MacAddress": "",
"IPv4Address": "",
"IPv6Address": ""
}
},
}
]
查看 Nginx
容器網絡類型和配置:
$ docker inspect f202870092fc4
# 輸出如下 (節選部分信息)
[
...
"NetworkSettings": {
"Bridge": "",
"Gateway": "",
"IPAddress": "",
"Networks": {
"host": {
"Gateway": "",
"IPAddress": "",
}
}
...
]
通過上面的輸出可以看到,Nginx
容器使用的網絡類型是 host
,沒有獨立的 IP。
查看 Nginx
容器 IP 地址:
# 進入容器內部 shell
$ docker exec -it f202870092fc4 /bin/bash
# 安裝 ip 命令
$ apt update && apt install -y iproute2
# 查看 IP 地址
$ ip a
# 輸出如下
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
...
2: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
...
10: eth0@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
...
# 退出容器,查看宿主機 IP 地址
$ exit
$ ip a
# 輸出如下
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
...
2: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
...
10: eth0@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
...
通過上面的輸出可以看到,Nginx
容器內部並沒有獨立的 IP,而是使用了宿主機的 IP。
查看宿主機的端口監聽狀態:
$ sudo netstat -ntpl
# 輸出如下
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 1378/nginx: master
tcp6 0 0 :::80 :::* LISTEN 1378/nginx: master
通過上面的輸出可以看到,監聽 80
端口的進程爲 nginx
, 而不是 docker-proxy
。
none 模式
容器擁有自己的 Network Namespace,但是並不進行任何網絡配置。也就意味着該容器沒有網卡、IP、路由等信息,需要手動爲容器添加網卡、配置 IP 等, none
模式下的容器會完全隔離,容器中只有 lo 這個 loopback(迴環網絡)網卡用於進程通信。
none
模式爲容器做了最少的網絡設置,在沒有網絡配置的情況下,通過自定義配置容器的網絡,提供了最高的靈活性。
啓動一個網絡類型爲 host
的 Nginx
容器:
$ docker run -d --net none nginx
Unable to find image 'nginx:latest' locally
latest: Pulling from library/nginx
...
d2d0606b7d2429c224e61e06c348019b74cd47f0b8c85347a7cdb8f1e30dcf86
查看網絡類型爲 none
的容器列表:
$ docker network inspect none
# 輸出如下 (節選部分信息)
[
{
"Name": "none",
"Scope": "local",
"Driver": "null",
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"Containers": {
# 使用 none 網絡的容器列表
"d2d0606b7d2429c224e61e06c348019b74cd47f0b8c85347a7cdb8f1e30dcf86": {
"Name": "hardcore_chebyshev",
"EndpointID": "b8ff645671518e608f403818a31b1db34d7fce66af60373346ea3ab673a4c6b2",
"MacAddress": "",
"IPv4Address": "",
"IPv6Address": ""
}
},
}
]
查看 Nginx
容器網絡類型和配置:
$ docker inspect d2d0606b7d242
# 輸出如下 (節選部分信息)
[
...
"NetworkSettings": {
"Bridge": "",
"Gateway": "",
"IPAddress": "",
"Networks": {
"none": {
"Gateway": "",
"IPAddress": "",
}
}
...
]
通過上面的輸出可以看到,Nginx
容器使用的網絡類型是 none
,沒有獨立的 IP。
查看 Nginx
容器 IP 地址:
# 進入容器內部 shell
$ docker exec -it d2d0606b7d242 /bin/bash
# 訪問公網鏈接
$ curl -I "https://www.docker.com"
curl: (6) Could not resolve host: www.docker.com
# 爲什麼會報錯呢? 這是因爲當前容器沒有網卡、IP、路由等信息,是完全獨立的運行環境,所以沒有辦法訪問公網鏈接。
# 查看 IP 地址
$ hostname -I
# 沒有任何輸出,該容器沒有 IP 地址
查看宿主機的端口監聽狀態:
$ docker port d2d0606b7d242
或者
$ sudo netstat -ntpl | grep :80
# 沒有任何輸出,Nginx 進程運行在容器中,端口沒有映射到宿主機
container 模式
與 host
模式類似,容器與指定的容器共享網絡命名空間。這兩個容器之間不存在網絡隔離,但它們與宿主機以及其他的容器存在網絡隔離。該模式下的容器可以通過 localhost 來訪問同一網絡命名空間下的其他容器,傳輸效率較高,且節約了一定的網絡資源。在一些特殊的場景中非常有用,例如 k8s 的 Pod。
其他模式
出於篇幅考慮,這裏不再贅述其他網絡模式,感興趣的讀者可以根據文章末尾的引用連接自行閱讀。
網絡驅動概述
-
當需要多個容器在同一臺宿主機上進行通信時,使用
bridge
-
當網絡棧不應該與宿主機隔離,但是希望容器的其他方面被隔離時,使用
host
-
當需要在不同宿主機上運行的容器進行通信時,使用
overlay
-
當從虛擬機遷移或需要使容器看起來像物理宿主機時,使用
Macvlan
, 每個容器都有一個唯一的 MAC 地址 -
當需要將 Docker 與專門的網絡棧集成,使用
Third-party
Docker 和 iptables
如果在公網可以訪問的服務器運行 Docker
,需要對應的 iptables
規則來限制訪問主機上的容器或其他服務。
在 Docker 規則之前添加 iptables 規則
Docker 安裝了兩個名爲 DOCKER-USER
和 DOCKER
的自定義 iptables
鏈,確保傳入的數據包始終先由這兩個鏈進行檢查。
# 可以通過該命令查看
$ iptables -L -n -v | grep -i docker
Docker
的所有 iptables
規則都被添加到 Docker
鏈中,不要手動修改此鏈 (可能會引發問題)。如果需要添加在一些在 Docker
之前加載的規則,將它們添加到 DOCKER-USER
鏈中,這些規則應用於 Docker
自動創建的所有規則之前。
添加到 FORWARD
鏈中的規則在這些鏈之後進行檢測,這意味着如果通過 Docker
公開一個端口,那麼無論防火牆配置了什麼規則,該端口都會被公開。如果想讓這些規則在通過 Docker
暴露端口時仍然適用,必須將這些規則添加到 DOCKER-USER
鏈中。
限制到 Docker 主機的連接
默認情況下,允許所有 外部 IP
連接 Docker
主機,爲了只允許特定的 IP 或網絡訪問容器,在 DOCKER-USER
過濾器鏈的頂部插入一個規則。
例如,只允許 192.168.1.1 訪問:
# 假設輸入接口爲 eth0
$ iptables -I DOCKER-USER -i eth0 ! -s 192.168.1.1 -j DROP
也可以允許來自源子網的連接,例如,允許 192.168.1.0/24 子網的用戶訪問:
# 假設輸入接口爲 eth0
$ iptables -I DOCKER-USER -i eth0 ! -s 192.168.1.0/24 -j DROP
阻止 Docker 操作 iptables
在 Docker
引擎的配置文件 /etc/docker/daemon.json
設置 iptables
的值爲 false
,但是最好不要修改,因爲這很可能破壞 Docker
引擎的容器網絡。
爲容器設置默認綁定地址
默認情況下,Docker
守護進程將公開 0.0.0.0
地址上的端口,即主機上的任何地址。如果希望將該行爲更改爲僅公開內部 IP 地址上的端口,則可以使用 --ip
選項指定不同的 IP 地址。
集成到防火牆
如果運行的是 Docker 20.10.0
或更高版本,在系統上啓用了 iptables
, Docker
會自動創建一個名爲 docker
的防火牆區域, 並將它創建的所有網絡接口 (例如 docker0) 加入到 docker
區域,以允許無縫組網。
運行命令將 docker
接口從防火牆區域中移除:
firewall-cmd --zone=trusted --remove-interface=docker0 --permanent
firewall-cmd --reload
Reference
-
Networking overview[1]
-
Networking tutorials[2]
-
Docker and iptables[3]
-
容器 Docker 詳解 [4]
-
Introduction to Container Networking[5]
-
docker 容器網絡方案:calico 網絡模型 [6]
-
Docker——容器與容器雲 [7]
引用鏈接
[1]
Networking overview: https://docs.docker.com/network/
[2]
Networking tutorials: https://docs.docker.com/network/network-tutorial-standalone/
[3]
Docker and iptables: https://docs.docker.com/network/iptables/
[4]
容器 Docker 詳解: https://juejin.cn/post/6844903766601236487
[5]
Introduction to Container Networking: https://www.suse.com/c/rancher_blog/introduction-to-container-networking/
[6]
docker 容器網絡方案:calico 網絡模型: https://cizixs.com/2017/10/19/docker-calico-network/
[7]
Docker——容器與容器雲: https://book.douban.com/subject/26894736/
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/U6cnU6ReOyUL62qY995Hkw