Docker 通過網(wǎng)絡(luò)驅(qū)動來支持容器的網(wǎng)絡(luò)通信,默認情況下,Docker 提供兩種網(wǎng)絡(luò)驅(qū)動供我們使用,一個是 bridge,一個是 overlay。我們也可以自己寫一個網(wǎng)絡(luò)驅(qū)動插件,如果你足夠大牛的話。
Docker 安裝時會自動在 host 上創(chuàng)建三個網(wǎng)絡(luò),我們可用docker network ls
命令查看:
root@ubuntu:~# docker network ls
NETWORK ID NAME DRIVER SCOPE
fe6e820d51e2 bridge bridge local
e91fa0de345b host host local
9fea16767e7a none null local
故名思議,none 網(wǎng)絡(luò)就是什么都沒有的網(wǎng)絡(luò)。掛在這個網(wǎng)絡(luò)下的容器除了 lo,沒有其他任何網(wǎng)卡。容器創(chuàng)建時,可以通過--network=none
指定使用 none 網(wǎng)絡(luò)。
docker run -it --network=none busybox
我們不禁會問,這樣一個封閉的網(wǎng)絡(luò)有什么用呢?
其實還真有應(yīng)用場景。封閉意味著隔離,一些對安全性要求高并且不需要聯(lián)網(wǎng)的應(yīng)用可以使用 none 網(wǎng)絡(luò)。
比如某個容器的唯一用途是生成隨機密碼,就可以放到 none 網(wǎng)絡(luò)中避免密碼被竊取。
當然大部分容器是需要網(wǎng)絡(luò)的,我們接著看 host 網(wǎng)絡(luò)。
連接到 host 網(wǎng)絡(luò)的容器共享 Docker host 的網(wǎng)絡(luò)棧,容器的網(wǎng)絡(luò)配置與 host 完全一樣。可以通過--network=host
指定使用 host 網(wǎng)絡(luò)。
docker run -it --network=host busybox
直接使用 Docker host 的網(wǎng)絡(luò)最大的好處就是性能,如果容器對網(wǎng)絡(luò)傳輸效率有較高要求,則可以選擇 host 網(wǎng)絡(luò)。當然不便之處就是犧牲一些靈活性,比如要考慮端口沖突問題,Docker host 上已經(jīng)使用的端口就不能再用了。
Docker host 的另一個用途是讓容器可以直接配置 host 網(wǎng)路。比如某些跨 host 的網(wǎng)絡(luò)解決方案,其本身也是以容器方式運行的,這些方案需要對網(wǎng)絡(luò)進行配置,比如管理 iptables
bridge 是一個很特殊的網(wǎng)絡(luò),Docker 安裝時會創(chuàng)建一個 命名為 docker0 的 linux bridge。如果不指定 --network,創(chuàng)建的容器默認都會掛到 docker0 上。
接口 docker0 是一個虛擬的以太網(wǎng)橋,用于連接容器和本地宿主網(wǎng)絡(luò)。
我們停掉所有的容器查看一下。
root@ubuntu:~# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242f47922c9 no
當前 docker0 上沒有任何其他網(wǎng)絡(luò)設(shè)備,我們創(chuàng)建一個容器看看有什么變化。
一個新的網(wǎng)絡(luò)接口 vethcd8f150 被掛到了 docker0 上,vethcd8f150 就是新創(chuàng)建容器的虛擬網(wǎng)卡。
下面看一下容器的網(wǎng)絡(luò)配置。
容器有一個網(wǎng)卡 eth0@if39。大家可能會問了,為什么不是 vethcd8f150 呢?
Docker 每創(chuàng)建一個容器就會創(chuàng)建一組互聯(lián)的網(wǎng)絡(luò)接口。這組接口就像管道的兩端(就是說,從一端發(fā)送的數(shù)據(jù)會在另一端接收到)。這組接口其中一端作為容器里的 eth0 接口,而領(lǐng)一端統(tǒng)一命名為類似 vethxxxx 這種名字,作為宿主機的一個端口??梢园?veth 接口認為是虛擬網(wǎng)線的一端。這個虛擬網(wǎng)線一端插在名為 docker0 的網(wǎng)橋上,另一端插到容器里。通過把每個 veth 接口綁定到 docker0 網(wǎng)橋,Docker 創(chuàng)建了一個虛擬子網(wǎng),這個子網(wǎng)由宿主機和所有的 Docker 容器共享。
所以說 eth0@if39 和 vethcd8f150 是一對 veth pair。veth pair 是一種成對出現(xiàn)的特殊網(wǎng)絡(luò)設(shè)備,可以把它們想象成由一根虛擬網(wǎng)線連接起來的一對網(wǎng)卡,網(wǎng)卡的一頭(eth0)在容器中,另一頭(vethcd8f150)掛在網(wǎng)橋 docker0 上,其效果就是將 eth0@if39 也掛在了 docker0 上。
我們還看到 eth0@if39 已經(jīng)配置了 IP 172.17.0.2,為什么是這個網(wǎng)段呢?讓我們通過docker network inspect bridge
看一下 bridge 網(wǎng)絡(luò)的配置信息:
root@ubuntu:~# docker network inspect bridge
[
{
"Name": "bridge",
"Id": "fe6e820d51e226b3262ce3bba44ebae6ec7106db36db333b59ffd09dd0608f23",
"Created": "2017-12-01T13:53:34.169111033+08:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"c14ec27277f6228eff709c5b5a437214d967247e716daf7186aacb5339497ac9": {
"Name": "unruffled_swanson",
"EndpointID": "5f8034274333c8582645620b238e2ede7c9267a7433369017a576a3aaaa08527",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
}
},
"Options": {
"com.docker.network.bridge.default_bridge": "true",
"com.docker.network.bridge.enable_icc": "true",
"com.docker.network.bridge.enable_ip_masquerade": "true",
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
"com.docker.network.bridge.name": "docker0",
"com.docker.network.driver.mtu": "1500"
},
"Labels": {}
}
]
原來 bridge 網(wǎng)絡(luò)配置的 subnet 就是 172.17.0.0/16,并且網(wǎng)關(guān)是 172.17.0.1,網(wǎng)關(guān)就是 docker0。
容器創(chuàng)建時,docker 會自動從 172.17.0.0/16 中分配一個 IP,這里 16 位的掩碼保證有足夠多的 IP 可以供容器使用。
我們也可以把一個容器從一個網(wǎng)絡(luò)中端口,使用如下命令即可。
docker network disconnect bridge <Container ID>
Docker 本身提供兩種網(wǎng)絡(luò)驅(qū)動:bridge 和 overlay。bridge 只能用于單機網(wǎng)絡(luò)模式,overlay 用于創(chuàng)建跨主機的網(wǎng)絡(luò),我們可通過 bridge 驅(qū)動創(chuàng)建類似前面默認的 bridge 網(wǎng)絡(luò),例如:
root@ubuntu:~# docker network create -d bridge my_bridge
423b660fbdbd5cfacc4cbf591bdf2bc977e7865261349efb6482dbdcbc7e1bd3
root@ubuntu:~# docker network ls
NETWORK ID NAME DRIVER SCOPE
fe6e820d51e2 bridge bridge local
e91fa0de345b host host local
423b660fbdbd my_bridge bridge local
9fea16767e7a none null local
-d 就是 --bridge 的簡寫,也可以使用 --bridge 替換進行創(chuàng)建。
查看一下當前 host 的網(wǎng)絡(luò)結(jié)構(gòu)變化:
root@ubuntu:~# brctl show
bridge name bridge id STP enabled interfaces
br-423b660fbdbd 8000.0242cb3347fd no
docker0 8000.0242f47922c9 no vethcd8f150
新增了一個網(wǎng)橋 br-423b660fbdbd
執(zhí)行docker network inspect
查看一下 my_bridge 的配置信息:
root@ubuntu:~# docker network inspect my_bridge
[
{
"Name": "my_bridge",
"Id": "423b660fbdbd5cfacc4cbf591bdf2bc977e7865261349efb6482dbdcbc7e1bd3",
"Created": "2017-12-04T16:29:23.726303365+08:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {},
"Options": {},
"Labels": {}
}
]
這里 172.18.0.0/16 是 Docker 自動分配的 IP 網(wǎng)段。
如果要自定義網(wǎng)絡(luò) IP 段,只需在創(chuàng)建網(wǎng)段時指定--subnet
和--gateway
參數(shù):
root@ubuntu:~# docker network create -d bridge --subnet 192.168.31.0/24 --gateway 192.168.31.1 my_bridge2
43043f6bbc1a74106bef92e158daec3ea376748de2f8695541c8e93964303b5b
root@ubuntu:~# docker network inspect my_bridge2
[
{
"Name": "my_bridge2",
"Id": "43043f6bbc1a74106bef92e158daec3ea376748de2f8695541c8e93964303b5b",
"Created": "2017-12-04T16:39:19.832006374+08:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "192.168.31.0/24",
"Gateway": "192.168.31.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {},
"Options": {},
"Labels": {}
}
]
這里我們創(chuàng)建了新的 bridge 網(wǎng)絡(luò) my_bridge2,網(wǎng)段為 192.168.31.0/24,網(wǎng)關(guān)為 192.168.31.1。與前面一樣,網(wǎng)關(guān)在 my_bridge2 對應(yīng)的網(wǎng)橋 br-43043f6bbc1a 上:
root@ubuntu:~# brctl show
bridge name bridge id STP enabled interfaces
br-423b660fbdbd 8000.0242cb3347fd no
br-43043f6bbc1a 8000.02426266f83e no
docker0 8000.0242f47922c9 no vethcd8f150
容器要使用新的網(wǎng)絡(luò),需要在啟動時通過--network
指定:
root@ubuntu:~# docker run -it --network=my_bridge2 --name busybox1 busybox
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1
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
42: eth0@if43: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 02:42:c0:a8:1f:02 brd ff:ff:ff:ff:ff:ff
inet 192.168.31.2/24 scope global eth0
valid_lft forever preferred_lft forever
容器分配到的 IP 為 192.168.31.2。
root@ubuntu:~# brctl show
bridge name bridge id STP enabled interfaces
br-423b660fbdbd 8000.0242cb3347fd no
br-43043f6bbc1a 8000.02426266f83e no vethfffb9d3
docker0 8000.0242f47922c9 no vethcd8f150
我們在啟動容器的時候,可以通過參數(shù) --ip 來指定特定的 IP,只有使用--subnet
創(chuàng)建的 User-defined 網(wǎng)絡(luò)才能指定靜態(tài) IP。
root@ubuntu:~# docker run -it --network=my_bridge2 --ip 192.168.31.25 --name busybox2 busybox
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1
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
44: eth0@if45: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 02:42:c0:a8:1f:19 brd ff:ff:ff:ff:ff:ff
inet 192.168.31.25/24 scope global eth0
valid_lft forever preferred_lft forever
root@ubuntu:~# brctl show
bridge name bridge id STP enabled interfaces
br-423b660fbdbd 8000.0242cb3347fd no
br-43043f6bbc1a 8000.02426266f83e no veth8e0d4f7
vethfffb9d3
docker0 8000.0242f47922c9 no vethcd8f150
兩個 busybox 容器都掛在 my_bridge2 上,應(yīng)該能夠互通,我們驗證一下:
可見同一網(wǎng)絡(luò)中的容器、網(wǎng)關(guān)之間都是可以通信的。
從拓撲圖來看,my_bridge2 與默認 bridge 兩個網(wǎng)絡(luò)屬于不同的網(wǎng)橋,應(yīng)該不能通信,我們通過實驗驗證一下,讓 busybox1 容器 ping httpd 容器:
確實 ping 不通,符合預(yù)期。
不同的網(wǎng)絡(luò)如果加上路由應(yīng)該就可以通信了吧?確實,如果 host 上對每個網(wǎng)絡(luò)的都有一條路由,同時操作系統(tǒng)上打開了 ip forwarding,host 就成了一個路由器,掛接在不同網(wǎng)橋上的網(wǎng)絡(luò)就能夠相互通信。下面我們來看看 docker host 滿不滿足這些條件呢?
ip r 查看 host 上的路由表:
root@ubuntu:~# ip r
default via 192.168.2.1 dev enp5s0 onlink
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
172.18.0.0/16 dev br-423b660fbdbd proto kernel scope link src 172.18.0.1 linkdown
192.168.2.0/24 dev enp5s0 proto kernel scope link src 192.168.2.206
192.168.31.0/24 dev br-43043f6bbc1a proto kernel scope link src 192.168.31.1
172.17.0.0/16 和 192.168.31.0/24 兩個網(wǎng)絡(luò)的路由都定義好了。再看看 ip forwarding:
root@ubuntu:~# sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1
ip forwarding 也已經(jīng)啟用了。
條件都滿足,為什么不能通行呢?
我們還得看看 iptables:
root@ubuntu:~# iptables-save
......
-A DOCKER-ISOLATION -i br-423b660fbdbd -o br-43043f6bbc1a -j DROP
-A DOCKER-ISOLATION -i br-43043f6bbc1a -o br-423b660fbdbd -j DROP
-A DOCKER-ISOLATION -i docker0 -o br-43043f6bbc1a -j DROP
-A DOCKER-ISOLATION -i br-43043f6bbc1a -o docker0 -j DROP
-A DOCKER-ISOLATION -i docker0 -o br-423b660fbdbd -j DROP
-A DOCKER-ISOLATION -i br-423b660fbdbd -o docker0 -j DROP
......
原因就在這里了:iptables DROP 掉了網(wǎng)橋 docker0 與 br-43043f6bbc1a 之間雙向的流量。
從規(guī)則的命名 DOCKER-ISOLATION 可知 docker 在設(shè)計上就是要隔離不同的 netwrok。
怎樣才能讓 busybox 與 httpd 通信呢?我們需要為 httpd 容器添加一塊 net_bridge2 的網(wǎng)卡。這個可以通過docker network connect 命令實現(xiàn)。
查看 httpd 容器的 ID。
root@ubuntu:~# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
488e6019f780 busybox "sh" About an hour ago Up 31 minutes busybox2
7bf37788f011 busybox "sh" About an hour ago Up 36 minutes busybox1
c14ec27277f6 httpd "httpd-foreground" 3 hours ago Up 3 hours 80/tcp unruffled_swanson
為 httpd 容器添加網(wǎng)卡。
docker network connect my_bridge2 c14ec27277f6
root@ubuntu:~# brctl show
bridge name bridge id STP enabled interfaces
br-423b660fbdbd 8000.0242cb3347fd no
br-43043f6bbc1a 8000.02426266f83e no veth8e0d4f7
vethf3faf80
vethfffb9d3
docker0 8000.0242f47922c9 no vethcd8f150
我們在 httpd 容器中查看一下網(wǎng)絡(luò)配置。
root@ubuntu:~# docker exec -ti c14ec27277f6 /bin/bash
root@c14ec27277f6:/usr/local/apache2# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
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
38: eth0@if39: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.2/16 scope global eth0
valid_lft forever preferred_lft forever
52: eth1@if53: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:c0:a8:1f:03 brd ff:ff:ff:ff:ff:ff
inet 192.168.31.3/24 scope global eth1
valid_lft forever preferred_lft forever
容器中增加了一個網(wǎng)卡 eth1,分配了 my_bridge2 的 IP 192.168.31.3?,F(xiàn)在 busybox 應(yīng)該能夠訪問 httpd 了,驗證一下:
參考文檔:https://docs.docker.com/engine/tutorials/networkingcontainers/
https://docs.docker.com/engine/userguide/networking/#related-information