本文主要講解 Docker 的網路原理。在此之前,最好對網路名稱空間、Veth 裝置對、網橋、路由、netfilter 與 iptables 等Linux基礎網路知識有所瞭解,詳見《Docker的Linux網路基礎》。
一、Docker 的網路原理
1. Docker 的網路模式
標準的 Docker 支援 4 種網路模式,可以在使用 docker run 命令啟動容器的時候透過 --net 引數指定容器的網路模式:
(1)bridge:--net=bridge,Docker 預設的網路模式,新建的容器將擁有獨立的網路名稱空間,並連線到 Docker 的網橋 docker0 中。
(2)container:--net=container:NAME_or_ID,新建的容器將與指定容器使用同一個網路名稱空間,共享網路棧,可以直接透過 lo 環回介面進行通訊,但其他資源還是相互隔離的。
(3)host:--net=host,新建的容器將與宿主機使用同一個網路名稱空間,但其他資源還是隔離的。容器程序可以跟主機其它 root 程序一樣開啟低範圍的埠,如果進一步的使用 --privileged=true,容器會被允許直接配置主機的網路堆疊。
(4)none:--net=none,新建的容器將擁有獨立的網路名稱空間,但不進行網路配置,後續需要手動配置。
2. Docker 啟動後的系統情況
(1)建立了 docker0 網橋並分配了 IP 地址

Docker Daemon 首次啟動時會建立一個虛擬網橋,預設的名稱是 docker0,然後按照 RPC1918 的模型在私有網路空間中給這個網橋分配一個子網。Docker Daemon 會在幾個備選地址段裡給 docker0 選一個地址,通常是以 172 開頭的一個地址。
(2)新增了 iptables 規則

第 6 條:從 docker0 發出的包可以被中轉給 docker0 本身,即連線在 docker0 網橋上的不同容器之間的通訊是允許的。
第 3 條:如果接收到發給 docker0 網橋的資料包屬於以前已經建立好的連線,那麼允許直接透過,這樣接收到的資料包自然又走回 docker0 並中轉到相應的容器。
第 5 條:從 docker0 發出的包,如果需要轉發到非 docker0 本地 IP 地址的裝置,則是允許的,這樣 docker0 裝置發出的包就可以根據路由規則中轉到宿主機的網路卡裝置,從而訪問外面的網路了。
第 1 條:若本地 docker0 網段發出的資料包不是發往 docker0 的,而是發往主機之外的裝置的,則都需要進行動態地址修改 (MASQUERADE) ,將源地址從容器的地址修改為宿主機網路卡的 IP 地址,之後就可以傳送給外面的網路了。
(3)Linux 的 ip_forward 功能開啟

二、bridge 網路模式
在 bridge 模式下,針對由 Docker 建的每一個容器,都會建立一個虛擬乙太網裝置 —— Veth 裝置對,其中一端關聯到網橋 docker0 上,另一端使用 Linux 的網路名稱空間技術對映到容器內的 eth0 裝置,然後在網橋的地址段內給 eth0 介面分配一個沒有使用過的 IP 地址。
Docker 的 bridge 模式的網路模型如下:

docker0 地址段預設情況下在宿主機外部是不可見的,所以在同一臺機器內的容器之間可以相互通訊,不同主機上的容器不能相互通訊,實際上它們甚至有可能在相同的網路地址範圍內(不同主機上的 docker0 地址段可能是一樣的)。
為了讓它們跨節點相互通訊,就必須在主機的地址上分配埠,然後透過這個埠將網路流量路由或代理到目標容器上。這樣做顯然意味著一定要在容器之間小心謹慎地協調好埠的分配情況,或者使用動態埠的分配技術,而這二者都是困難且會增加複雜度的事情。這都是 Docker 的網路模型在跨主機訪問時面臨的問題。
(1)建立一個 Docker 容器,預設使用 bridge 網路模式,同時指定埠對映

將宿主機 8080 埠對映到容器 4000 埠。
(2)檢視容器的網路模式

使用了 Docker 的預設網路模式——bridge。
(3)檢視網橋連線與容器路由

宿主機上的 Veth 裝置對已經建立,連線容器與網橋 docker0。
容器內預設停止的迴環裝置 lo 被啟動,外面宿主機連線進來的 Veth 裝置也被命名成了 eth0, 並且已經配置了地址 172.17.0 2。
容器內路由資訊表包含一條到 docker0 的子網路由和一條到 docker0 的預設路由。
(4)檢視 iptables 規則

請求宿主機 8080 埠的資料包目的地址將被轉換為容器的服務地址 172.17.0.2:4000。
三、container 網路模式
使用 container 網路模式建立的容器將與指定容器使用同一個網路名稱空間,共享網路棧,可以直接透過 lo 環回介面進行通訊,但其他資源還是相互隔離的。
(1)使用 container 網路模式建立一個容器

(2)檢視容器網路模式

(3)檢視容器網路

可以看到,container 模式的容器與指定容器處於同一網路名稱空間,使用同一個 ip,兩個容器之間可透過 lo 裝置通訊,使用埠不可重複。
container 網路模式的容器無法進行埠對映。
四、host 網路模式
使用 host 網路模式建立的容器將與宿主機使用同一個網路名稱空間,但其他資源還是隔離的。容器程序可以跟主機其它 root 程序一樣開啟低範圍的埠,如果進一步的使用 --privileged=true,容器會被允許直接配置主機的網路堆疊。
(1)使用 host 網路模式建立一個容器

(2)檢視容器網路模式

(3)檢視容器網路

容器與宿主機使用同一網路名稱空間,在容器中可以看到 docker0 網橋。
(4)檢視宿主機埠占用

容器直接佔用宿主機的埠。
五、none 網路模式
使用 none 網路模式建立的容器將擁有獨立的網路名稱空間,但不進行網路配置,後續需要手動配置。
(1)使用 none 網路模式建立一個容器

(2)檢視容器網路模式

(3)檢視容器網路

只有 lo 裝置,暫無 veth 裝置,未設定 ip。
六、Docker 網路的侷限性
Docker 一直以來的理念都是“簡單為美”,所以一開始沒有考慮到多機互聯的網路解決方案。若要基於 Docker 的網路模型實現跨主機訪問,要麼在容器之間小心謹慎地協調好埠的分配情況,要麼使用動態埠的分配技術,但這二者都是困難且會增加複雜度的事情。

參考:
《Kubernetes 權威指南第 5 版》