Docker網絡配置(高級網絡功能)

目錄


 

 


網絡的高級知識,包括網絡的啓動和配置參數、DNS的使用配置、容器訪問和端口映射的相關實現。

在一些具體場景中,Docker支持的網絡定製配置,通過Linux命令來調整、補充、甚至替換Docker默認的網絡配置。

 

網絡啓動與配置參數

Docker啓動時會在主機上自動創建一個docker0虛擬網橋,實際上是一個Linux網橋,可以理解爲一個軟件交換機,它會在掛載其上的接口之間進行轉發。

同時,Docker隨機分配一個本地未佔用的私有網段(在RFC1918中定義)中的一個地址給docker0接口。比如典型的172.17.42.1,掩碼爲255.255.0.0。此後啓動的容器內的網口也會自動分配一個同一網段(172.17.0.0/16)的地址。

當創建一個Docker容器的時候,同時會創建了一對veth pair接口(當數據包發送到一個接口時,另外一個接口也可以收到相同的數據包)。這對接口一端在容器內,即eth0;另一端在本地並被掛載到docker0網橋,名稱以veth開頭(例如vethAQI2QT)。通過這種方式,主機可以跟容器通信,容器之間也可以相互通信。如此一來,Docker就創建了在主機和所有容器之間一個虛擬共享網絡。

下面是跟Docker網絡相關的命令參數。其中有些命令選項只有在Docker服務啓動的時候才能配置,而且不能馬上生效:

  1. -b BRIDGE or--bridge=BRIDGE——指定容器掛載的網橋;
  2. --bip=CIDR——定製docker0的掩碼;
  3. -H SOCKET...or--host=SOCKET...——Docker服務端接收命令的通道;
  4. --icc=true|false——是否支持容器之間進行通信;
  5. --ip-forward=true|false——啓用net.ipv4.ip_forward,即打開轉發功能;
  6. --iptables=true|false——禁止Docker添加iptables規則;
  7. --mtu=BYTES——容器網絡中的MTU。

下面2個命令選項既可以在啓動服務時指定,也可以Docker容器啓動(docker run)時候指定。在Docker服務啓動的時候指定則會成爲默認值,後續執行docker run時可以覆蓋設置的默認值。

  1. --dns=IP_ADDRESS...——使用指定的DNS服務器;
  2. --dns-search=DOMAIN...——指定DNS搜索域。

最後這些選項只能在docker run執行時使用,因爲它是針對容器的特性內容:

  1. -h HOSTNAME or--hostname=HOSTNAME——配置容器主機名;
  2. --link=CONTAINER_NAME:ALIAS——添加到另一個容器的連接;
  3. --net=bridge|none|container:NAME_or_ID|host|user_defined_network——配置容器的橋接模式;
  4. -p SPEC or--publish=SPEC——映射容器端口到宿主主機;
  5. -P or--publish-all=true|false——映射容器所有端口到宿主主機。

其中,--net選項支持五種模式,如下所示:

  1. --net=bridge——默認配置。爲容器創建獨立的網絡命名空間,分配網卡、IP地址等網絡配置,並通過veth接口對將容器掛載到一個虛擬網橋(默認爲docker0)上;
  2. --net=none——爲容器創建獨立的網絡命名空間,但不進行網絡配置,即容器內沒有創建網卡、IP地址等;
  3. --net=container:NAME_or_ID——意味着新創建的容器共享指定的已存在容器的網絡命名空間,兩個容器內的網絡配置共享,但其他資源(進程空間、文件系統等)還是相互隔離的;
  4. --net=host——意味着不爲容器創建獨立的網絡命名空間,容器內看到的網絡配置(網卡信息、路由表、Iptables規則等)均與主機上保持一致。注意其他資源還是與主機隔離的;
  5. --net=user_defined_network——用戶自行用network相關命令創建一個網絡,同一個網絡內的容器彼此可見,可以採用更多類型的網絡插件。

 

配置容器DNS和主機名

Docker支持自定義容器的主機名和DNS配置。

1.相關配置文件

實際上,容器中主機名和DNS配置信息都是通過三個系統配置文件來維護的:/etc/resolv.conf、/etc/hostname和/etc/hosts。

啓動一個容器,在容器中使用mount命令可以看到這三個文件掛載信息:

$ docker run -it ubuntu

root@75dbd6685305:/# mount

/etc/resolv.conf文件在創建容器時候,默認會與宿主機/etc/resolv.conf文件內容保持一致:

root@75dbd6685305:/# cat /etc/resolv.conf

/etc/hosts文件中默認只記錄了容器自身的一些地址和名稱:

root@75dbd6685305:/# cat /etc/hosts

/etc/hostname文件則記錄了容器的主機名。

root@75dbd6685305:/# cat /etc/hostname

2.容器內修改配置文件

Docker 1.2.0開始支持在運行中的容器裏直接編輯/etc/hosts,/etc/hostname和/etc/resolve.conf文件。

但是這些修改是臨時的,只在運行的容器中保留,容器終止或重啓後並不會被保存下來。也不會被docker commit提交。

3.通過參數指定

如果用戶想要自定義容器的配置,可以在創建或啓動容器時利用下面的參數指定:

  1. 指定主機名-h HOSTNAME或者--hostname=HOSTNAME。設定容器的主機名,它會被寫到容器內的/etc/hostname和/etc/hosts。但這個主機名只有容器內能看到,在容器外部則看不到,既不會在docker ps中顯示,也不會在其他的容器的/etc/hosts看到。
  2. 記錄其他容器主機名--link=CONTAINER_NAME:ALIAS。選項會在創建容器的時候,添加一個所連接容器的主機名到容器內/etc/hosts文件中。這樣,新創建容器可以直接使用主機名來與所連接容器通信。
  3. 指定DNS服務器--dns=IP_ADDRESS。添加DNS服務器到容器的/etc/resolv.conf中,容器會用指定的服務器來解析所有不在/etc/hosts中的主機名。
  4. 指定DNS搜索域--dns-search=DOMAIN。設定容器的搜索域,當設定搜索域爲.example.com時,在搜索一個名爲host的主機時,DNS不僅搜索host,還會搜索host.example.com。

容器訪問控制

容器的訪問控制主要通過Linux上的iptables防火牆軟件來進行管理和實現。iptables是Linux系統流行的防火牆軟件,在大部分發行版中都自帶。

1.容器訪問外部網絡

我們知道容器默認指定了網關爲docker0網橋上的docker0內部接口。docker0內部接口同時也是宿主機的一個本地接口。因此,容器默認情況下是可以訪問到宿主機本地的。更進一步,容器要想通過宿主機訪問到外部網絡,需要宿主機進行轉發。

在宿主機Linux系統中,檢查轉發是否打開:

$ sudo sysctl net.ipv4.ip_forward

如果爲0,說明沒有開啓轉發,則需要手動打開:

$ sudo sysctl -w net.ipv4.ip_forward=1

更簡單的,在啓動Docker服務的時候設定--ip-forward=true,Docker服務會自動打開宿主機系統的轉發服務。

2.容器之間的訪問

容器之間相互訪問,需要兩方面的支持:

  1. 網絡拓撲是否已經連通。默認情況下,所有容器都會連接到docker0網橋上,這意味着默認情況下拓撲是互通的;
  2. 本地系統的防火牆軟件iptables是否允許訪問通過。這取決於防火牆的默認規則是允許(大部分情況)還是禁止。

下面分兩種情況介紹容器之間的訪問。

(1)訪問所有端口

當啓動Docker服務時候,默認會添加一條“允許”轉發策略到iptables的FORWARD鏈上。通過配置--icc=true|false(默認值爲true)參數可以控制默認的策略。

爲了安全考慮,可以在Docker配置文件中配置DOCKER_OPTS=--icc=false來默認禁止容器之間的相互訪問。

同時,如果啓動Docker服務時手動指定--iptables=false參數則不會修改宿主機系統上的iptables規則。

(2)訪問指定端口

在通過-icc=false禁止容器間相互訪問後,仍可以通過--link=CONTAINER_NAME:ALIAS選項來允許訪問指定容器的開放端口。

例如,在啓動Docker服務時,可以同時使用icc=false--iptables=true參數來配置容器間禁止訪問,並允許Docker自動修改系統中的iptables規則。

此時,系統中的iptables規則可能是類似如下規則,禁止所有轉發流量:

$ sudo iptables -nL
...
Chain FORWARD (policy ACCEPT)
target     prot opt source               destination
DROP       all  --  0.0.0.0/0            0.0.0.0/0
...

之後,啓動容器(docker run)時使用--link=CONTAINER_NAME:ALIAS選項。Docker會在iptable中爲兩個互聯容器分別添加一條ACCEPT規則,允許相互訪問開放的端口(取決於Dockerfile中的EXPOSE行)。

此時,iptables的規則可能是類似如下規則:

$ sudo iptables -nL
...
Chain FORWARD (policy ACCEPT)
target     prot opt source               destination
ACCEPT     tcp  --  172.17.0.2           172.17.0.3           tcp spt:80
ACCEPT     tcp  --  172.17.0.3           172.17.0.2           tcp dpt:80
DROP       all  --  0.0.0.0/0            0.0.0.0/0

--link=CONTAINER_NAME:ALIAS中的CONTAINER_NAME目前必須是Docker自動分配的容器名,或使用--name參數指定的名字。不能爲容器-h參數配置的主機名。

映射容器端口到宿主主機的實現

默認情況下,容器可以主動訪問到外部網絡的連接,但是外部網絡無法訪問到容器。

1.容器訪問外部實現

假設容器內部的網絡地址爲172.17.0.2,本地網絡地址爲10.0.2.2。容器要能訪問外部網絡,源地址不能爲172.17.0.2,需要進行源地址映射(Source NAT,SNAT),修改爲本地系統的IP地址10.0.2.2。映射是通過iptables的源地址僞裝操作實現的。

查看主機nat表上POSTROUTING鏈的規則。該鏈負責網包要離開主機前,改寫其源地址。

$ sudo iptables -t nat -nvL POSTROUTING
Chain POSTROUTING (policy ACCEPT 12 packets, 738 bytes)
pkts bytes target     prot opt in     out     source               destination
0     0 MASQUERADE  all  --  *      !docker0  172.17.0.0/16        0.0.0.0/0
...

其中,上述規則將所有源地址在172.17.0.0/16網段,且不是從docker0接口發出的流量(即從容器中出來的流量),動態僞裝爲從系統網卡發出。MASQUERADE行動跟傳統SNAT行動相比,好處是它能從網卡動態獲取地址。

2.外部訪問容器實現

容器允許外部訪問,可以在docker run時候通過-p或-P參數來啓用。

不管用那種辦法,其實也是在本地的iptable的nat表中添加相應的規則,將訪問外部IP地址的網包進行目標地址DNAT,將目標地址修改爲容器的IP地址。

以一個開放80端口的Web容器爲例,使用-P時,會自動映射本地49000~49900範文內的端口隨機端口到容器的80端口:

$ iptables -t nat -nvL
...
Chain PREROUTING (policy ACCEPT 236 packets, 33317 bytes)
pkts bytes target     prot opt in     out     source          destination
567 30236 DOCKER     all  --  *      *       0.0.0.0/0       0.0.0.0/0
ADDRTYPE match dst-type LOCAL
Chain DOCKER (2 references)
pkts bytes target     prot opt in     out     source          destination
0     0 DNAT       tcp  --  !docker0 *       0.0.0.0/0     0.0.0.0/0
tcp dpt:49153 to:172.17.0.2:80
...

 

可以看到,nat表中涉及兩條鏈,PREROUTING鏈負責包到達網絡接口時,改寫其目的地址。其中規則將所有流量都扔到DOCKER鏈。而DOCKER鏈中將所有不是從docker0進來的網包(意味着不是本地主機產生),將目標端口爲49153的,修改目標地址爲172.17.0.2,目標端口修改爲80。

使用-p 80:80時,與上面類似,只是本地端口也爲80:

$ iptables -t nat -nvL

 

...
Chain PREROUTING (policy ACCEPT 236 packets, 33317 bytes)
pkts bytes target     prot opt in     out     source          destination
567 30236 DOCKER     all  --  *      *       0.0.0.0/0       0.0.0.0/0
ADDRTYPE match dst-type LOCAL
Chain DOCKER (2 references)
pkts bytes target     prot opt in     out     source          destination
0     0 DNAT       tcp  --  !docker0 *       0.0.0.0/0     0.0.0.0/0
tcp dpt:80 to:172.17.0.2:80
...

 

有兩點需要注意:

  1. 這裏的規則映射了0.0.0.0,意味着將接受主機來自所有網絡接口上的流量。用戶可以通過-p IP:host_port:container_port或-p IP::port來指定綁定的外部網絡接口,以制定更嚴格的訪問規則;
  2. 如果希望映射永久綁定到某個固定的IP地址,可以在Docker配置文件/etc/default/docker中指定DOCKER_OPTS="--ip=IP_ADDRESS",之後重啓Docker服務即可生效。

 

配置docker0網橋

Docker服務默認會創建一個名稱爲docker0的Linux網橋(其上有一個docker0內部接口),它在內核層連通了其他的物理或虛擬網卡,這就將所有容器和本地主機都放到同一個物理網絡。用戶使用Docker創建多個自定義網絡時可能會出現多個容器網橋。

Docker默認指定了docker0接口的IP地址和子網掩碼,讓主機和容器之間可以通過網橋相互通信,它還給出了MTU(接口允許接收的最大傳輸單元),通常是1500字節,或宿主主機網絡路由上支持的默認值。這些值都可以在服務啓動的時候進行配置:

  1. --bip=CIDR——IP地址加掩碼格式,例如192.168.1.5/24;
  2. --mtu=BYTES——覆蓋默認的Docker mtu配置。

也可以在配置文件中配置DOCKER_OPTS,然後重啓服務。

由於目前Docker網橋是Linux網橋,用戶可以使用brctl show來查看網橋和端口連接信息:

$ sudo brctl show
bridge name     bridge id               STP enabled     interfaces
docker0         8000.3a1d7362b4ee       no              veth65f9 vethdda6

brctl命令如果系統中沒有自帶,可以使用sudo apt-get install bridge-utils來安裝(Debian、Ubuntu系列系統)。

每次創建一個新容器的時候,Docker從可用的地址段中選擇一個空閒的IP地址分配給容器的eth0端口。並且使用本地主機上docker0接口的IP作爲容器的默認網關:

$ docker run -i -t --rm base /bin/bash

$ ip addr show eth0

 

24: eth0: <broadcast,up,lower_up>mtu 1500 qdisc pfifo_fast state UP group
default qlen 1000
link/ether 32:6f:e0:35:57:91 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.3/16 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::306f:e0ff:fe35:5791/64 scope link
valid_lft forever preferred_lft forever

 

$ ip route
default via 172.17.42.1 dev eth0
172.17.0.0/16 dev eth0  proto kernel  scope link  src 172.17.0.3
$ exit

目前,Docker不支持在啓動容器時候指定IP地址。實際上,Linux網橋自身功能已經十分完備,也可以替換爲OpenvSwitch等功能更強大的網橋實現。

回到頂部

自定義網橋

除了默認的docker0網橋,用戶也可以指定網橋來連接各個容器。

在啓動Docker服務的時候,使用-b BRIDGE或--bridge=BRIDGE來指定使用的網橋。

如果服務已經運行,那需要先停止服務,並刪除舊的網橋:

$ sudo service docker stop

$ sudo ip link set dev docker0 down

$ sudo brctl delbr docker0

然後創建一個網橋bridge0:

$ sudo brctl addbr bridge0

$ sudo ip addr add 192.168.5.1/24 dev bridge0

$ sudo ip link set dev bridge0 up

查看確認網橋創建並啓動:

$ ip addr show bridge0
4: bridge0: <broadcast,multicast>mtu 1500 qdisc noop state UP group default
link/ether 66:38:d0:0d:76:18 brd ff:ff:ff:ff:ff:ff
inet 192.168.5.1/24 scope global bridge0
valid_lft forever preferred_lft forever

配置Docker服務,默認橋接到創建的網橋上:

$ echo 'DOCKER_OPTS="-b=bridge0"' >> /etc/default/docker

$ sudo service docker start

啓動Docker服務。新建一個容器,可以看到它已經橋接到了bridge0上。

可以繼續用brctl show命令查看橋接的信息。另外,在容器中可以使用ip addr和ip route命令來查看IP地址配置和路由信息。

使用OpenvSwitch網橋

Docker默認使用的是Linux自帶的網橋實現,實際上,OpenvSwitch項目作爲一個成熟的虛擬交換機實現,具備更豐富的功能。將來會有越來越多的容器支持OpenvSwitch作爲底層網橋實現。

1.環境

在Ubuntu 14.04系統中進行測試。操作流程也適用於RedHat/CentOS系列系統,但少數命令和配置文件可能略有差異。

2.安裝Docker

安裝最近版本的Docker並啓動服務。默認情況下,Docker服務會創建一個名爲docker0的Linux網橋,作爲連接容器的本地網橋。

可以通過如下命令查看:

$ sudo brctl show
bridge name     bridge id               STP enabled     interfaces
docker0         8000.000000000000       no

網橋docker0內部接口的默認地址可能爲172.17.42.1。

$ ifconfig docker0

 

docker0   Link encap:Ethernet  HWaddr 56:84:7a:fe:97:99
inet addr:172.17.42.1  Bcast:0.0.0.0  Mask:255.255.0.0
BROADCAST MULTICAST  MTU:1500  Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

 

3.安裝OpenvSwitch

通過如下命令安裝OpenvSwitch:

$ sudo aptitude install openvswitch-switch

測試添加一個網橋br0並查看:

$ sudo ovs-vsctl add-br br0

$ sudo ovs-vsctl show

4.配置容器連接到OpenvSwitch網橋

目前OpenvSwitch網橋還不能直接支持掛載容器,需要手動在OpenvSwitch網橋上創建虛擬網口並掛載到容器中。

(1)創建無網口容器

啓動一個ubuntu容器,並指定不創建網絡,後面我們手動添加網絡。較新版本的Docker默認不允許在容器內修改網絡配置,需要在run的時候指定參數--privileged=true:

$ docker run --net=none --privileged=true -it ubuntu:14.04 bash

root@298bbb17c244:/#

記住這裏容器的id爲298bbb17c244。

此時在容器內查看網絡信息,只能看到一個本地網卡lo:

root@298bbb17c244:/# ifconfig

 

lo        Link encap:Local Loopback
inet addr:127.0.0.1  Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING  MTU:65536  Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

 

(2)手動爲容器添加網絡

下載OpenvSwitch項目提供的支持Docker容器的輔助腳本ovs-docker:

$ wget https://github.com/openvswitch/ovs/raw/master/utilities/ovs-docker

$ sudo chmod a+x ovs-docker

爲容器添加網卡,並掛載到br0上,命令爲:

$ sudo ./ovs-docker add-port br0 eth0 298bbb17c244 --ipaddress=172.17.0.2/16

添加成功後,在容器內查看網絡信息,多了一個新添加的網卡eth0,對應添加的IP地址:

root@298bbb17c244:/# ifconfig

 

eth0      Link encap:Ethernet  HWaddr ae:3d:75:2c:18:ba
inet addr:172.17.0.2  Bcast:172.17.255.255  Mask:255.255.0.0
inet6 addr: fe80::ac3d:75ff:fe2c:18ba/64 Scope:Link
UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
RX packets:187 errors:0 dropped:2 overruns:0 frame:0
TX packets:11 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:33840 (33.8 KB)  TX bytes:1170 (1.1 KB)
lo        Link encap:Local Loopback
inet addr:127.0.0.1  Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING  MTU:65536  Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

 

在容器外,配置OpenvSwitch的網橋br0內部接口地址爲172.17.42.2/16(只要與所掛載容器IP在同一個子網內即可):

$ sudo ifconfig br0 172.17.42.2/16

(3)測試連通

經過上面步驟,容器已經連接到了網橋br0上了,拓撲如下所示:

容器(172.17.0.2/16)<-->br0

網橋<-->br0

內部端口(172.17.42.2/16)

此時,在容器內就可以測試是否連通到網橋br0上了:

root@298bbb17c244:/# ping 172.17.42.2

在容器內也可以配置默認網關爲br0接口地址:

root@298bbb17c244:/# route add default gw 172.17.42.2

另外,刪除該接口的命令爲:

$ sudo ./ovs-docker del-port br0 eth0<container_id>

實際上,Docker社區也已經討論對OpenvSwitch進行原生支持了。在Docker原生支持OpenvSwitch之前,用戶可以通過編寫腳本或更高級的工具來讓這一過程自動化。

 

創建一個點到點連接

默認情況下,Docker會將所有容器連接到由docker0提供的虛擬子網中。用戶有時候需要兩個容器之間可以直連通信,而不用通過主機網橋進行橋接。

解決辦法很簡單:創建一對peer接口,分別放到兩個容器中,配置成點到點鏈路類型即可。

下面這個過程我們將手動執行Docker配置容器網絡的大部分步驟。

首先啓動兩個容器:

$ docker run -i -t --rm --net=none base /bin/bash

$ docker run -i -t --rm --net=none base /bin/bash

找到進程號,然後創建網絡命名空間的跟蹤文件:

$ docker inspect -f '{{.State.Pid}}' 1f1f4c1f931a

2989

$ docker inspect -f '{{.State.Pid}}' 12e343489d2f

3004

$ sudo mkdir -p /var/run/netns

$ sudo ln -s /proc/2989/ns/net /var/run/netns/2989

$ sudo ln -s /proc/3004/ns/net /var/run/netns/3004

創建一對peer接口。

$ sudo ip link add A type veth peer name B

添加IP地址和路由信息:

$ sudo ip link set A netns 2989

$ sudo ip netns exec 2989 ip addr add 10.1.1.1/32 dev A

$ sudo ip netns exec 2989 ip link set A up

$ sudo ip netns exec 2989 ip route add 10.1.1.2/32 dev A

$ sudo ip link set B netns 3004

$ sudo ip netns exec 3004 ip addr add 10.1.1.2/32 dev B

$ sudo ip netns exec 3004 ip link set B up

$ sudo ip netns exec 3004 ip route add 10.1.1.1/32 dev B

現在這兩個容器就可以相互ping通,併成功建立連接。點到點鏈路不需要子網和子網掩碼。

此外,也可以不指定--net=none來創建點到點鏈路。這樣容器還可以通過原先的網絡來通信。

利用類似的辦法,可以創建一個只跟主機通信的容器。但是一般情況下,更推薦使用--icc=false來關閉容器之間的通信。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章