一篇文章講明白Docker 網絡原理

2023.04.08

一篇文章講明白Docker 網絡原理

Docker 原生網絡是基於Linux 的網絡命名空間(net namespace) 和虛擬網絡設備(veth pair)實現的。當Docker 進程啟動時,會在宿主機上創建一個名稱為docker0 的虛擬網橋,在該宿主機上啟動的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
...
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

虛擬網橋的工作方式和物理交換機類似,宿主機上所有的容器通過虛擬網橋連接在一個二層網絡中。

從 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:
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

圖片

圖片來源: 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
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

網絡驅動

Docker 的網絡子系統支持插拔式的驅動程序,默認存在多個驅動程序,並提供核心網絡功能。

名稱

描述

默認的網絡設備,當應用程序所在的容器需要通信時使用

主持人

移除容器與宿主機之間的網絡隔離,直接使用宿主機的網絡

覆蓋

將多個容器連接,並使集群服務能夠相互通信

ipvlan

使用戶可以完全控制IPv4 和IPv6 尋址

macvlan

可以為容器分配MAC 地址

沒有任何

禁用所有網絡

網絡插件

通過Docker 安裝和使用第三方網絡插件

圖片

圖片來源: 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 网络的容器列表
        },
    }
]
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.

通過上面的輸出可以看到,虛擬網橋的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",
        }
    }

  ...
]
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

通過上面的輸出可以看到,虛擬網橋的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
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

當容器需要將端口映射到宿主機時,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
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

查看網絡類型為 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": ""
            }        
        },
    }
]
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.

查看 Nginx 容器網絡類型和配置:

$ docker inspect f202870092fc4

# 输出如下 (节选部分信息)
[
  ...
  
  "NetworkSettings": {
    "Bridge": "",
    "Gateway": "",
    "IPAddress": "",
    "Networks": {
        "host": {
            "Gateway": "",
            "IPAddress": "",
        }
    }
  ...
]
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

通過上面的輸出可以看到,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
    ...
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.

通過上面的輸出可以看到,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
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

通過上面的輸出可以看到,監聽 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
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

查看網絡類型為 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": ""
            }        
        },
    }
]
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.

查看 Nginx 容器網絡類型和配置:

$ docker inspect d2d0606b7d242

# 输出如下 (节选部分信息)
[
  ...
  
  "NetworkSettings": {
    "Bridge": "",
    "Gateway": "",
    "IPAddress": "",
    "Networks": {
        "none": {
            "Gateway": "",
            "IPAddress": "",
        }
    }
  ...
]
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

通過上面的輸出可以看到,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 地址
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

查看宿主機的端口監聽狀態:

$ docker port d2d0606b7d242

或者

$ sudo netstat -ntpl | grep :80

# 没有任何输出,Nginx 进程运行在容器中,端口没有映射到宿主机
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

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
  • 1.
  • 2.
  • 3.

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
  • 1.
  • 2.

也可以允許來自源子網的連接,例如,允許192.168.1.0/24 子網的用戶訪問:

# 假设输入接口为 eth0
$ iptables -I DOCKER-USER -i eth0 ! -s 192.168.1.0/24 -j DROP
  • 1.
  • 2.

阻止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 --znotallow=trusted --remove-interface=docker0 --permanent
firewall-cmd --reload
  • 1.
  • 2.

參考

• 網絡概覽[1]

• 網絡教程[2]

• Docker 和 iptables[3]

• 容器Docker詳解[4]

• 容器網絡簡介[5]

• docker 容器網絡方案:calico 網絡模型[6]

• Docker——容器與容器雲[7]

引用鏈接

[1] 網絡概覽: https://docs.docker.com/network/[2 ]網絡教程: https://docs.docker.com/network/network-tutorial-standalone/ [3] Docker與iptables: https://docs.docker.com/network/iptables/

[4]​ 容器Docker詳解: ​​https://juejin.cn/post/6844903766601236487​

[5] 容器網絡介紹: 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/