一篇文章講明白 Docker 網(wǎng)絡原理
概述
Docker 原生網(wǎng)絡是基于 Linux 的 網(wǎng)絡命名空間(net namespace) 和 虛擬網(wǎng)絡設備(veth pair)實現(xiàn)的。當 Docker 進程啟動時,會在宿主機上創(chuàng)建一個名稱為 docker0 的 虛擬網(wǎng)橋,在該宿主機上啟動的 Docker 容器會連接到這個虛擬網(wǎng)橋上。
虛擬網(wǎng)橋的工作方式和物理交換機類似,宿主機上所有的容器通過虛擬網(wǎng)橋連接在一個二層網(wǎng)絡中。
從 docker0 子網(wǎng)中分配一個 IP 給容器使用,并設置 docker0 的 IP 地址為容器的默認網(wǎng)關。在宿主機上創(chuàng)建一對虛擬網(wǎng)卡 veth pair 設備, Docker 將 veth pair 設備的一端放在新創(chuàng)建的容器中,并命名為 eth0(容器的網(wǎng)卡), 另一端放在宿主機中,以 vethxxx 類似的名字命名, 并將這個網(wǎng)絡設備連接到 docker0 網(wǎng)橋中。
Docker 會自動配置 iptables 規(guī)則和配置 NAT,便于連通宿主機上的 docker0 網(wǎng)橋,完成這些操作之后,容器就可以使用它的 eth0 虛擬網(wǎng)卡,來連接其他容器和訪問外部網(wǎng)絡。
Docker 中的網(wǎng)絡接口默認都是虛擬的接口,Linux 在內(nèi)核中通過 數(shù)據(jù)復制 實現(xiàn)接口之間的數(shù)據(jù)傳輸,可以充分發(fā)揮數(shù)據(jù)在不同 Docker 容器或容器與宿主機之間的轉發(fā)效率, 發(fā)送接口發(fā)送緩存中的數(shù)據(jù)包,將直接復制到接收接口的緩存中,無需通過物理網(wǎng)絡設備進行交換。
圖片來源: https://www.suse.com/c/rancher_blog/introduction-to-container-networking/
虛擬網(wǎng)橋 docker0 通過 iptables 配置與宿主機器上的網(wǎng)卡相連,符合條件的請求都會通過 iptables 轉發(fā)到 docker0, 然后分發(fā)給對應的容器。
網(wǎng)絡驅(qū)動
Docker 的網(wǎng)絡子系統(tǒng)支持插拔式的驅(qū)動程序,默認存在多個驅(qū)動程序,并提供核心網(wǎng)絡功能。
名稱 | 描述 |
bridge | 默認的網(wǎng)絡設備,當應用程序所在的容器需要通信時使用 |
host | 移除容器與宿主機之間的網(wǎng)絡隔離,直接使用宿主機的網(wǎng)絡 |
overlay | 將多個容器連接,并使集群服務能夠相互通信 |
ipvlan | 使用戶可以完全控制 IPv4 和 IPv6 尋址 |
macvlan | 可以為容器分配 MAC 地址 |
none | 禁用所有網(wǎng)絡 |
Network plugins | 通過 Docker 安裝和使用第三方網(wǎng)絡插件 |
圖片來源: Docker——容器與容器云
Docker daemon 通過調(diào)用 libnetwork 提供的 API 完成網(wǎng)絡的創(chuàng)建和管理等功能。libnetwork 中使用了 CNM 來完成網(wǎng)絡功能, CNM 中主要有沙盒(sandbox)、端點(endpoint)和網(wǎng)絡(network)3 種組件。
? 沙盒:一個沙盒包含了一個容器網(wǎng)絡棧的信息。一個沙盒可以有多個端點和多個網(wǎng)絡,沙盒可以對容器的接口、路由和 DNS 設置等進行管理,沙盒的實現(xiàn)可以是 Linux network namespace、FreeBSD Jail或者類似的機制
? 端點:一個端點可以加入一個沙盒和一個網(wǎng)絡。一個端點只屬于一個網(wǎng)絡和一個沙盒,端點的實現(xiàn)可以是 veth pair、Open vSwitch 內(nèi)部端口或者相似的設備
? 網(wǎng)絡:一個網(wǎng)絡是一組可以直接互相聯(lián)通的端點。一個網(wǎng)絡可以包含多個端點,網(wǎng)絡的實現(xiàn)可以是Linux bridge、VLAN 等
bridge 模式
bridge 是默認的網(wǎng)絡模式,為容器創(chuàng)建獨立的網(wǎng)絡命名空間,容器具有獨立的網(wǎng)卡等所有的網(wǎng)絡棧。使用該模式的所有容器都是連接到 docker0 這個網(wǎng)橋, 作為 虛擬交換機 使容器可以相互通信,但是由于宿主機的 IP 地址與容器 veth pair 的 IP 地址不在同一個網(wǎng)段,所以為了和宿主機以外的網(wǎng)絡通信, Docker 采用了端口綁定的方式,也就是通過 iptables 的 NAT,將宿主機上的端口流量轉發(fā)到容器。
bridge 模式已經(jīng)可以滿足 Docker 容器最基本的使用需求了,但是其與外界通信時使用 NAT,增加了通信的復雜性,在復雜場景下使用會有限制。
通過上面的輸出可以看到,虛擬網(wǎng)橋 的 IP 地址就是 bridge 網(wǎng)絡類型的網(wǎng)關地址。
我們可以從輸出的 Containers 容器列表中找一個容器,查看其網(wǎng)絡類型和配置:
通過上面的輸出可以看到,虛擬網(wǎng)橋 的 IP 地址就是 bridge 網(wǎng)絡類型的容器的網(wǎng)關地址。
實現(xiàn)機制
在 iptables 做了 DNAT 規(guī)則,實現(xiàn)端口轉發(fā)功能:
當容器需要將端口映射到宿主機時,Docker 會自動為該容器分配一個 IP 地址,同時新增一個 iptables 規(guī)則。
host 模式
容器不會獲得一個獨立的網(wǎng)絡命名空間,而是和宿主機共用一個。容器不會虛擬出自己的網(wǎng)卡,配置自己的IP等,而是直接使用宿宿主機的。但是容器的其他方面,如文件系統(tǒng)、進程列表等還是和宿宿主機隔離的,容器對外界是完全開放的,能夠訪問到宿主機,就能訪問到容器。
host 模式降低了容器與容器之間、容器與宿主機之間網(wǎng)絡層面的隔離性,雖然有性能上的優(yōu)勢,但是引發(fā)了網(wǎng)絡資源的競爭與沖突,因此適用于容器集群規(guī)模較小的場景。
啟動一個網(wǎng)絡類型為 host 的 Nginx 容器:
查看網(wǎng)絡類型為 host 的容器列表:
查看 Nginx 容器網(wǎng)絡類型和配置:
通過上面的輸出可以看到,Nginx 容器使用的網(wǎng)絡類型是 host,沒有獨立的 IP。
查看 Nginx 容器 IP 地址:
通過上面的輸出可以看到,Nginx 容器內(nèi)部并沒有獨立的 IP,而是使用了宿主機的 IP。
查看宿主機的端口監(jiān)聽狀態(tài):
通過上面的輸出可以看到,監(jiān)聽 80 端口的進程為 nginx, 而不是 docker-proxy。
none 模式
容器擁有自己的 Network Namespace,但是并不進行任何網(wǎng)絡配置。也就意味著該容器沒有網(wǎng)卡、IP、路由等信息,需要手動為容器添加網(wǎng)卡、配置 IP 等, none 模式下的容器會完全隔離,容器中只有 lo 這個 loopback(回環(huán)網(wǎng)絡)網(wǎng)卡用于進程通信。
none 模式為容器做了最少的網(wǎng)絡設置,在沒有網(wǎng)絡配置的情況下,通過自定義配置容器的網(wǎng)絡,提供了最高的靈活性。
啟動一個網(wǎng)絡類型為 host 的 Nginx 容器:
查看網(wǎng)絡類型為 none 的容器列表:
查看 Nginx 容器網(wǎng)絡類型和配置:
通過上面的輸出可以看到,Nginx 容器使用的網(wǎng)絡類型是 none,沒有獨立的 IP。
查看 Nginx 容器 IP 地址:
查看宿主機的端口監(jiān)聽狀態(tài):
container 模式
與 host 模式類似,容器與指定的容器共享網(wǎng)絡命名空間。這兩個容器之間不存在網(wǎng)絡隔離,但它們與宿主機以及其他的容器存在網(wǎng)絡隔離。該模式下的容器可以通過 localhost 來訪問同一網(wǎng)絡命名空間下的其他容器,傳輸效率較高,且節(jié)約了一定的網(wǎng)絡資源。在一些特殊的場景中非常有用,例如 k8s 的 Pod。
其他模式
出于篇幅考慮,這里不再贅述其他網(wǎng)絡模式,感興趣的讀者可以根據(jù)文章末尾的引用連接自行閱讀。
網(wǎng)絡驅(qū)動概述
- ? 當需要多個容器在同一臺宿主機上進行通信時,使用 bridge
- ? 當網(wǎng)絡棧不應該與宿主機隔離,但是希望容器的其他方面被隔離時,使用 host
- ? 當需要在不同宿主機上運行的容器進行通信時,使用 overlay
- ? 當從虛擬機遷移或需要使容器看起來像物理宿主機時,使用 Macvlan, 每個容器都有一個唯一的 MAC 地址
- ? 當需要將 Docker 與專門的網(wǎng)絡棧集成,使用 Third-party
Docker 和 iptables
如果在公網(wǎng)可以訪問的服務器運行 Docker,需要對應的 iptables 規(guī)則來限制訪問主機上的容器或其他服務。
在 Docker 規(guī)則之前添加 iptables 規(guī)則
Docker 安裝了兩個名為 DOCKER-USER 和 DOCKER 的自定義 iptables 鏈,確保傳入的數(shù)據(jù)包始終先由這兩個鏈進行檢查。
Docker 的所有 iptables 規(guī)則都被添加到 Docker 鏈中,不要手動修改此鏈 (可能會引發(fā)問題)。如果需要添加在一些在 Docker 之前加載的規(guī)則,將它們添加到 DOCKER-USER 鏈中,這些規(guī)則應用于 Docker 自動創(chuàng)建的所有規(guī)則之前。
添加到 FORWARD 鏈中的規(guī)則在這些鏈之后進行檢測,這意味著如果通過 Docker 公開一個端口,那么無論防火墻配置了什么規(guī)則,該端口都會被公開。如果想讓這些規(guī)則在通過 Docker 暴露端口時仍然適用,必須將這些規(guī)則添加到 DOCKER-USER 鏈中。
限制到 Docker 主機的連接
默認情況下,允許所有 外部 IP 連接 Docker 主機,為了只允許特定的 IP 或網(wǎng)絡訪問容器,在 DOCKER-USER 過濾器鏈的頂部插入一個規(guī)則。
例如,只允許 192.168.1.1 訪問:
也可以允許來自源子網(wǎng)的連接,例如,允許 192.168.1.0/24 子網(wǎng)的用戶訪問:
阻止 Docker 操作 iptables
在 Docker 引擎的配置文件 /etc/docker/daemon.json 設置 iptables 的值為 false,但是最好不要修改,因為這很可能破壞 Docker 引擎的容器網(wǎng)絡。
為容器設置默認綁定地址
默認情況下,Docker 守護進程將公開 0.0.0.0 地址上的端口,即主機上的任何地址。如果希望將該行為更改為僅公開內(nèi)部 IP 地址上的端口,則可以使用 --ip 選項指定不同的IP地址。
集成到防火墻
如果運行的是 Docker 20.10.0 或更高版本,在系統(tǒng)上啟用了 iptables, Docker 會自動創(chuàng)建一個名為 docker 的防火墻區(qū)域, 并將它創(chuàng)建的所有網(wǎng)絡接口 (例如 docker0 ) 加入到 docker 區(qū)域,以允許無縫組網(wǎng)。
運行命令將 docker 接口從防火墻區(qū)域中移除:
Reference
? Networking overview[1]
? Networking tutorials[2]
? Docker and iptables[3]
? 容器Docker詳解[4]
? Introduction to Container Networking[5]
? docker 容器網(wǎng)絡方案:calico 網(wǎng)絡模型[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 容器網(wǎng)絡方案:calico 網(wǎng)絡模型: ??https://cizixs.com/2017/10/19/docker-calico-network/??
[7] Docker——容器與容器云: https://book.douban.com/subject/26894736/