理解Docker(5):Docker 網絡

 本系列文章將介紹 Docker的相關知識:html

(1)Docker 安裝及基本用法python

(2)Docker 鏡像linux

(3)Docker 容器的隔離性 - 使用 Linux namespace 隔離容器的運行環境git

(4)Docker 容器的隔離性 - 使用 cgroups 限制容器使用的資源github

(5)Docker 網絡web

 

1. Docker 網絡概況

用一張圖來講明 Docker 網絡的基本概況:docker

 

2. 四種單節點網絡模式

2.1 bridge 模式

Docker 容器默認使用 bridge 模式的網絡。其特色以下:數據庫

  • 使用一個 linux bridge,默認爲 docker0
  • 使用 veth 對,一頭在容器的網絡 namespace 中,一頭在 docker0 上
  • 該模式下Docker Container不具備一個公有IP,由於宿主機的IP地址與veth pair的 IP地址不在同一個網段內
  • Docker採用 NAT 方式,將容器內部的服務監聽的端口與宿主機的某一個端口port 進行「綁定」,使得宿主機之外的世界能夠主動將網絡報文發送至容器內部
  • 外界訪問容器內的服務時,須要訪問宿主機的 IP 以及宿主機的端口 port
  • NAT 模式因爲是在三層網絡上的實現手段,故確定會影響網絡的傳輸效率。
  • 容器擁有獨立、隔離的網絡棧;讓容器和宿主機之外的世界經過NAT創建通訊
  • 關於容器經過 NAT 鏈接外網的原理,請參考個人另外一篇文章 Netruon 理解(11):使用 NAT 將 Linux network namespace 鏈接外網

iptables 的 SNTA 規則,使得從容器離開去外界的網絡包的源 IP 地址被轉換爲 Docker 主機的IP地址:json

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
MASQUERADE  all  --  172.17.0.0/16        0.0.0.0/0
MASQUERADE  all  --  172.18.0.0/16        0.0.0.0/0

效果是這樣的:網絡

圖片來源

示意圖:

2.2 Host 模式

定義:

Host 模式並無爲容器建立一個隔離的網絡環境。而之因此稱之爲host模式,是由於該模式下的 Docker 容器會和 host 宿主機共享同一個網絡 namespace,故 Docker Container能夠和宿主機同樣,使用宿主機的eth0,實現和外界的通訊。換言之,Docker Container的 IP 地址即爲宿主機 eth0 的 IP 地址。其特色包括:

    • 這種模式下的容器沒有隔離的 network namespace
    • 容器的 IP 地址同 Docker host 的 IP 地址
    • 須要注意容器中服務的端口號不能與 Docker host 上已經使用的端口號相沖突
    • host 模式可以和其它模式共存

實驗:

(1)啓動一個 host 網絡模式的容器

docker run -d --name hostc1 --network host -p 5001:5001 training/webapp python app.py

(2)檢查其 network namespace,其中能夠看到主機上的全部網絡設備

複製代碼
root@docker2:/home/sammy# ln -s /proc/28353/ns/net /var/run/netns/hostc1
root@docker2:/home/sammy# ip netns
hostc1
root@docker2:/home/sammy# ip netns exec hostc1
No command specified
root@docker2:/home/sammy# ip netns exec hostc1 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:d4:66:75 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.20/24 brd 192.168.1.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fed4:6675/64 scope link
       valid_lft forever preferred_lft forever
......
複製代碼

示意圖:

2.3 container 模式

定義:

 Container 網絡模式是 Docker 中一種較爲特別的網絡的模式。處於這個模式下的 Docker 容器會共享其餘容器的網絡環境,所以,至少這兩個容器之間不存在網絡隔離,而這兩個容器又與宿主機以及除此以外其餘的容器存在網絡隔離。  

實驗:

(1)啓動一個容器: 

docker run -d --name hostcs1 -p 5001:5001 training/webapp python app.py

(2)啓動另外一個容器,並使用第一個容器的 network namespace

docker run -d --name hostcs2 --network container:hostcs1  training/webapp python app.py

注意:由於此時兩個容器要共享一個 network namespace,所以須要注意端口衝突狀況,不然第二個容器將沒法被啓動。

示意圖:

2.4 none 模式

定義:

 網絡模式爲 none,即不爲 Docker 容器構造任何網絡環境。一旦Docker 容器採用了none 網絡模式,那麼容器內部就只能使用loopback網絡設備,不會再有其餘的網絡資源。Docker Container的none網絡模式意味着不給該容器建立任何網絡環境,容器只能使用127.0.0.1的本機網絡。

實驗:

(1)建立並啓動一個容器: docker run -d --name hostn1 --network none training/webapp python app.py

(2)檢查其網絡設備,除了 loopback 設備外沒有其它設備

複製代碼
root@docker2:/home/sammy# ip netns exec hostn1 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
複製代碼

3. 多節點 Docker 網絡

  Docker 多節點網絡模式能夠分爲兩類,一類是 Docker 在 1.19 版本中引入的基於 VxLAN 的對跨節點網絡的原生支持;另外一種是經過插件(plugin)方式引入的第三方實現方案,好比 Flannel,Calico 等等。

3.1 Docker 原生overlay 網絡

  Docker 1.19 版本中增長了對 overlay 網絡的原生支持。Docker 支持 Consul, Etcd, 和 ZooKeeper 三種分佈式key-value 存儲。其中,etcd 是一個高可用的分佈式 k/v存儲系統,使用etcd的場景默認處理的數據都是控制數據,對於應用數據,只推薦數據量很小,可是更新訪問頻繁的狀況。

3.1.1 安裝配置

準備三個節點:

  • devstack 192.168.1.18
  • docker1 192.168.1.21
  • docker2 192.168.1.19

在 devstack 上使用Docker 啓動 etcd 容器:

複製代碼
export HostIP="192.168.1.18"
docker run -d -v /usr/share/ca-certificates/:/etc/ssl/certs -p 4001:4001 -p 2380:2380 -p 2379:2379 \
 --name etcd quay.io/coreos/etcd \
/usr/local/bin/etcd \ -name etcd0 \ -advertise-client-urls http://${HostIP}:2379,http://${HostIP}:4001 \ -listen-client-urls http://0.0.0.0:2379,http://0.0.0.0:4001 \ -initial-advertise-peer-urls http://${HostIP}:2380 \ -listen-peer-urls http://0.0.0.0:2380 \ -initial-cluster-token etcd-cluster-1 \ -initial-cluster etcd0=http://${HostIP}:2380 \ -initial-cluster-state new
複製代碼

使用 Docker 啓動 etcd 請參考 https://coreos.com/etcd/docs/latest/docker_guide.html。不過,應該是由於製造鏡像所使用的Dockerfile 緣由,官網上的命令由於少了上面紅色字體部分而會形成啓動失敗:

 b847195507addf4fb5a01751eb9c4101416a13db4a8a835e1c2fa1db1e6f364e
docker: Error response from daemon: oci runtime error: exec: "-name": executable file not found in $PATH.

添加紅色部分後,容器能夠被正確建立:

root@devstack:/# docker exec -it 179cd52b494d /usr/local/bin/etcdctl cluster-health
member 5d72823aca0e00be is healthy: got healthy result from http://:2379
cluster is healthy
複製代碼
root@devstack:/home/sammy# docker ps
CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS                                                      NAMES
179cd52b494d        quay.io/coreos/etcd   "/usr/local/bin/etcd "   8 seconds ago       Up 8 seconds        0.0.0.0:2379-2380->2379-2380/tcp, 0.0.0.0:4001->4001/tcp   etcd
root@devstack:/home/sammy# netstat -nltp | grep 2380
tcp6       0      0 :::2380                 :::*                    LISTEN      4072/docker-proxy
root@devstack:/home/sammy# netstat -nltp | grep 4001
tcp6       0      0 :::4001                 :::*                    LISTEN      4047/docker-proxy
複製代碼

在docker1 和 docker2 節點上修改 /etc/default/docker,添加:

DOCKER_OPTS="--cluster-store=etcd://192.168.1.18:2379 --cluster-advertise=192.168.1.20:2379"

而後分別重啓 docker deamon。注意,要使用IP地址;要是使用 hostname 的話,docker 服務將啓動失敗:

root@docker2:/home/sammy# docker ps
An error occurred trying to connect: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.24/containers/json: read unix @->/var/run/docker.sock: read: connection reset by peer

3.1.2 使用 Docker overlay 網絡

(1)在docker1上運行下面的命令建立一個 overlay 網絡:

複製代碼
root@docker1:/home/sammy# docker network create -d overlay overlaynet1
1de982804f632169380609b9be7c1466b0064dce661a8f4c9e30d781e79fc45a
root@docker1:/home/sammy# docker network inspect overlaynet1
[
    {
        "Name": "overlaynet1",
        "Id": "1de982804f632169380609b9be7c1466b0064dce661a8f4c9e30d781e79fc45a",
        "Scope": "global",
        "Driver": "overlay",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "10.0.0.0/24",
                    "Gateway": "10.0.0.1/24"
                }
            ]
        },
        "Internal": false,
        "Containers": {},
        "Options": {},
        "Labels": {}
    }
]
複製代碼

在 docker2 上你也會看到這個網絡,說明經過 etcd,網絡數據是分佈式而不是本地的了。

(2)在網絡中建立容器

在 docker2 上,運行 docker run -d --name over2 --network overlaynet1 training/webapp python app.py

在 docker1 上,運行 docker run -d --name over1 --network overlaynet1 training/webapp python app.py

進入容器 over2,發現它有兩塊網卡:

複製代碼
root@docker2:/home/sammy# ln -s /proc/23576/ns/net /var/run/netns/over2
root@docker2:/home/sammy# ip netns
over2
root@docker2:/home/sammy# ip netns exec over2 ip a

22: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default
    link/ether 02:42:0a:00:00:02 brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.2/24 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:aff:fe00:2/64 scope link
       valid_lft forever preferred_lft forever
24: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:13:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.19.0.2/16 scope global eth1
       valid_lft forever preferred_lft forever
    inet6 fe80::42:acff:fe13:2/64 scope link
       valid_lft forever preferred_lft forever
複製代碼

其中 eth1 的網絡是一個內部的網段,其實它走的仍是普通的 NAT 模式;而 eth0 是 overlay 網段上分配的IP地址,也就是它走的是 overlay 網絡,它的 MTU 是 1450 而不是 1500.

進一步查看它的路由表,你會發現只有同一個 overlay 網絡中的容器之間的通訊纔會經過 eth0,其它全部通訊仍是走 eth1.

root@docker2:/home/sammy# ip netns exec over2 route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.19.0.1      0.0.0.0         UG    0      0        0 eth1
10.0.0.0        0.0.0.0         255.255.255.0   U     0      0        0 eth0
172.19.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth1

先看此時的網絡拓撲圖:

可見:

  • Docker 在每一個節點上建立了兩個 linux bridge,一個用於 overlay 網絡(ov-000100-1de98),一個用於非 overlay 的 NAT 網絡(docker_gwbridge)
  • 容器內的到overlay 網絡的其它容器的網絡流量走 overlay 網卡(eth0),其它網絡流量走 NAT 網卡(eth1)
  • 當前 Docker 建立 vxlan 隧道的ID範圍爲 256~1000,於是最多能夠建立745個網絡,所以,本例中的這個 vxlan 隧道使用的 ID 是 256
  • Docker vxlan 驅動使用 4789 UDP 端口
  • overlay網絡模型底層須要相似 consul 或 etcd 的 KV 存儲系統進行消息同步
  • Docker overlay 不使用多播
  • Overlay 網絡中的容器處於一個虛擬的大二層網絡中
  • 關於 linux bridge + vxlan 組網,請參考  Neutron 理解(14):Neutron ML2 + Linux bridge + VxLAN 組網
  • 關於 linux network namspace + NAT 組網,請參考 Netruon 理解(11):使用 NAT 將 Linux network namespace 鏈接外網
  • github 上代碼在這裏 https://github.com/docker/libnetwork/blob/master/drivers/overlay/

ov-000100-1de98 的初始情形:

root@docker1:/home/sammy# ip -d link show dev vx-000100-1de98
8: vx-000100-1de98: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master ov-000100-1de98 state UNKNOWN mode DEFAULT group default
    link/ether 22:3c:3f:8f:94:f6 brd ff:ff:ff:ff:ff:ff promiscuity 1
    vxlan id 256 port 32768 61000 proxy l2miss l3miss ageing 300
root@docker1:/home/sammy# bridge fdb show dev vx-000100-1de98
22:3c:3f:8f:94:f6 vlan 0 permanent

這裏很明顯的一個問題是,vxlan dev vx-000100-1de98 的 fdb 表內容不全,致使從容器1 ping 容器2 不通。待選的解決方式不外乎下面幾種:

  • 使用一箇中央數據庫,它保存全部容器的 IP 地址和所在節點的 IP 地址的映射關係
  • 使用多播
  • 使用好比 BGP 的特殊協議來廣告(advertise)容器的 IP 和所在節點的 IP 的映射關係

Docker 從某種程度上利用了第一種和第三種方式的組合,首先Docker 利用 consul 以及 etcd 這樣的分佈式 key/value 存儲來保存IP地址映射關係,另外一方面個Docker 節點也經過某種協議來直接廣告映射關係。

爲了測試,中間重啓了 docker1 節點,發現 over1 容器沒法啓動,報錯以下:

docker: Error response from daemon: network sandbox join failed: could not get network sandbox (oper true): failed get network namespace "": no such file or directory.

根據 https://github.com/docker/docker/issues/25215,這是 Docker 的一個bug,fix 剛剛推出。一個 workaround 是從新建立 overlay network。

回到容器之間沒法ping通對方的問題,尚不知道根本緣由是什麼(想吐槽Docker目前的問題真很多)。要使得互相 ping 能工做,至少必須具有下面的條件:

在 docker1 上,

  • 爲 vxlan dev 添加一條 fdb entry:02:42:14:00:00:03 dst 192.168.1.20 self
  • 在容器中添加一條 arp entry:ip netns exec over1 arp -s 20.0.0.3 02:42:14:00:00:03

在 docker 2 上,

  • 爲 vxlan dev 添加一條 fdb entry:02:42:14:00:00:02 dst 192.168.1.21 self permanent
  • 在容器中添加一條 arp entry:ip netns exec over4 arp -s 20.0.0.2 02:42:14:00:00:02

3. 網絡性能對比

3.1 在個人測試環境中的數據

使用 iperf 工具檢查測試了一下性能並作對比:

類型 TCP UDP
Overlay 網絡中的兩個容器之間 (A) 913 Mbits/sec 1.05 Mbits/sec
Bridge/NAT 網絡中的兩個容器之間 (B) 1.73 Gbits/sec  
主機間 (C) 2.06 Gbits/sec 1.05 Mbits/sec
主機到另外一個主機上的 bridge 網絡模式的容器 (D) 1.88 Gbits/sec  
主機到本主機上的容器 (E) 20.5 Gbits/sec  
主機到另外一個主機上的 host 網絡模式的容器 (F) 2.02 Gbits/sec 1.05 Mbits/sec
容器 Overlay 效率 (A/C) 44%  100% ?
單個 NAT 效率 (D/C) 91%  
兩個 NAT 效率 (B/C) 83%  
Host 網絡模式效率 (F/C) 98% 100%

 兩臺主機是同一個物理機上的兩個虛機,所以,結果的絕對值其實沒多少意義,相對值有必定的參考性。

3.2 網上文章中的對比數據

文章 Testing Docker multi-host network performance 對比了多種網絡模式下的性能,結果以下:

看起來這個表裏面的數據和個人表裏面的數據差不了太多。

3.3 關於Docker 網絡模式選擇的簡單結論

  • Bridge 模式的性能損耗大概爲10%
  • 原生 overlay 模式的性能損耗很是高,甚至達到了 56%,所以,在生產環境下使用這種模式須要很是謹慎。
  • 若是必定要使用 overlay 模式的話,能夠考慮使用 Cisco 發起的  Calico 模式,它的性能和 bridge 至關。
  • Weave overlay 模式的性能數據很是可疑,按理說應該不可能這麼差。

 

參考連接:

相關文章
相關標籤/搜索