Docker 網絡配置
摘要
當docker啟動時,它會在宿主機器上創建一個名為docker0的虛擬網絡接口。它會從RFC 1918定義的私有地址中隨機選擇一個主機不用的地址和子網掩碼,并將它分配給docker0。例如當我啟動docker幾分鐘后它選擇了172.17.42.1/16-一個16位的子網掩碼為主機和它的容器提供了65,534個ip地址。
注意: 本文討論了Docker的高級網絡配置和選項。通常你不會用到這些。如果你想查看一個較為簡單的Docker網絡介紹和容器概念介紹來著手,請參見Docker用戶指南.
但docker0并不是正常的網絡接口。它只是一個在綁定到這上面的其他網卡間自動轉發數據包的虛擬以太網橋。它可以使容器與主機相互通信。每次 Docker創建一個容器,它就會創建一對對等接口(peer interface),類似于一個管子的兩端-在這邊可以收到另一邊發送的數據包。Docker會將對等接口中的一個做為eth0接口連接到容器上,并使 用類似于vethAQI2QT這樣的惟一名稱來持有另一個,該名稱取決于主機的命名空間。通過將所有veth*接口綁定到docker0橋接網卡 上,Docker在主機和所有Docker容器間創建一個共享的虛擬子網。
本文其他部分將會講解使用Docker選項的所有方式,并且-在高級模式下-使用純linux網線配置命令來
調整,補充,或完全替代Docker的默認網絡配置。
Docker選項快速指南
這里有一份關于Docker網絡配置的命令行選項列表,省去您查找相關資料的麻煩。
一些網絡配置的命令行選項只能在服務器啟動時提供給Docker服務器。并且一旦啟動起來就無法改變。
一些網絡配置命令選項只能在啟動時提供給Docker服務器,并且在運行中不能改變:
-
-b BRIDGE或--bridge=BRIDGE— see 建立自己的網橋
-
--bip=CIDR— see 定制docker0
-
-H SOCKET...或--host=SOCKET...— 它看起來像是在設置容器的網絡,但實際卻恰恰相反:它告訴Docker服務器要接收命令的通道,例如“run container"和"stop container"。
-
--icc=true|false— see 容器間通信
-
--ip=IP_ADDRESS— see 綁定容器端口
-
--ip-forward=true|false— see 容器間通信
-
--iptables=true|false— see 容器間通信
-
--mtu=BYTES— see 定制docker0
有兩個網絡配置選項可以在啟動時或調用docker run時設置。當在啟動時設置它會成為docker run的默認值:
最后,一些網絡配置選項只能在調用docker run時指出,因為它們要為每個容器做特定的配置:
-
-h HOSTNAME或--hostname=HOSTNAME— see 配置DNS 和 Docker與容器連接原理
-
--net=bridge|none|container:NAME_or_ID|host— see Docker與容器連接原理
接下來的部分會對以上話題從易到難做出逐一解答。
配置DNS
怎樣為Docker提供的每一個容器進行主機名和DNS配置,而不必建立自定義鏡像并將主機名寫到里面?它的訣竅是覆蓋三個至關重要的在/etc下的容器內的虛擬文件,那幾個文件可以寫入新的信息。你可以在容器內部運行mount看到這個:
$$ mount.../dev/disk/by-uuid/1fec...ebdf on /etc/hostname type ext4 .../dev/disk/by-uuid/1fec...ebdf on /etc/hosts type ext4 ...tmpfs on /etc/resolv.conf type tmpfs ......
這樣的配置允許Docker去做聰明的事情,類似于當主機接收到新的DHCP配置之后,保持resolv.conf的數據到所有的容器中。 Docker怎樣維護在容器內的這些文件從Docker的一個版本到下一個版本的具體細節,你應該拋開這些單獨的文件本身并且使用下面的Docker選項 代替。
有四種不同的選項會影響容器守護進程的服務名稱。
1. -h HOSTNAME 或者 --hostname=HOSTNAME --設置容器的主機名,僅本機可見。這種方式是寫到/etc/hostname ,以及/etc/hosts 文件中,作為容器主機IP的別名,并且將顯示在容器的bash中。不過這種方式設置的主機名將不容易被容器之外可見。這將不會出現在 docker ps 或者 其他的容器的/etc/hosts 文件中。
2. --link=CONTAINER_NAME:ALIAS --使用這個選項去run一個容器將在此容器的/etc/hosts文件中增加一個主機名ALIAS,這個主機名是名為CONTAINER_NAME 的容器的IP地址的別名。這使得新容器的內部進程可以訪問主機名為ALIAS的容器而不用知道它的IP。--link= 關于這個選項的詳細討論請看: Communication between containers.
3. --dns=IP_ADDRESS --設置DNS服務器的IP地址,寫入到容器的/etc/resolv.conf文件中。當容器中的進程嘗試訪問不在/etc/hosts文件中的主機A 時,容器將以53端口連接到IP_ADDRESS這個DNS服務器去搜尋主機A的IP地址。
4.
--dns-search=DOMAIN --設置DNS服務器的搜索域,以防容器嘗試訪問不完整的主機名時從中檢索相應的IP。這是寫入到容器的
/etc/resolv.conf文件中的。當容器嘗試訪問主機
host,而DNS搜索域被設置為 example.com ,那么DNS將不僅去查尋host主機的IP,還去查詢host.example.com的
IP。
在docker中,如果啟動容器時缺少以上最后兩種選項設置時,將使得容器的/etc/resolv.conf文件看起來和宿主主機的/etc/resolv.conf文件一致。這些選項將修改默認的設置。
容器間通信
在操作系統層面上,決定兩個容器間的通信能否得到控制,有以下三個因素。
網絡拓撲邏輯是否連接上了容器的網絡接口。默認情況下Docker將把所有容器綁定到一個 singledocker0bridge,并為兩個容器間的包傳輸提供路徑。參見本文檔后續部分---其他可能的拓撲邏輯
主機是否要發送IP包?這由ip_forward系統參數控制。如果這個參數設為1,那么數據包只能在容器間傳輸。通常情況下,讓 Docker服務器使用它的默認設置 --ip-forward=true , Docker在啟動的時候會把ip_forwardsh. 要檢查設置或手動設置參數,可以這樣做:
# Usually not necessary: turning on forwarding,# on the host where your Docker server is running$ cat /proc/sys/net/ipv4/ip_forward0$ sudo echo 1 > /proc/sys/net/ipv4/ip_forward $ cat /proc/sys/net/ipv4/ip_forward1
iptables是否允許特殊連接?如果你把設置 --iptables=false,當守護進程啟動時,Docker不會改變你的系統iptables規則。另外,如果你保留默認設置 --icc=true,Docker服務器或向FORWARD鏈添加一個帶有全局ACCEPT策略的默認規則。如果不保留默認設置,系統會把策略設為 DROP.
幾乎所有人使用docker都希望ip_forward 是打開的,至少使容器間的通訊成為可能。但是否同意 --icc=true 或者更改為 --icc=false 使得iptables 可以保護容器以及宿主主機不被任意地端口掃描、避免被已經被滲透的容器所訪問,這是一個策略問題。(在ubuntu,是編輯/etc/default /docker文件中的DOCKER_OPTS參數,然后重啟docker服務)
如果你選擇最安全的設置 --icc=false ,那么當你想讓它們彼此提供服務的時候如何讓它們相互通訊?
答案是:使用前文提到的 --link=CONTAINER_NAME:ALIAS 選項。如果docker守護進程正在以 --icc=false 和 --iptables=true 參數運行,當以選項 --link= 執行 docker run 命令時,docker服務將插入一部分 iptables ACCEPT 規則使得新容器可以連接其他容器所暴露出來的端口(此端口指前文在 Dockerfile 中提到的EXPOSE這一行)。更多詳細文檔介紹請看:linking Docker containers。
注意: --link 選項中的 CONTAINER_NAME 的值必須是 docker自動分配的容器名稱,比如 stupefied_pare, 或者是在執行docker run 的時候用 --name= 指定的容器名稱. 這不能使一個docker無法識別的主機名。
你可以在你的Docker主機上運行iptables命令,來觀察FORWARD鏈是否有默認的ACCEPT或DROP策略
# When --icc=false, you should see a DROP rule:$ sudo iptables -L -n...Chain FORWARD (policy ACCEPT)target prot opt source destination DROP all -- 0.0.0.0/0 0.0.0.0/0...# When a --link= has been created under --icc=false,# you should see port-specific ACCEPT rules overriding# the subsequent DROP policy for all other packets:$ sudo iptables -L -n...Chain FORWARD (policy ACCEPT)target prot opt source destination ACCEPT tcp -- 172.17.0.2 172.17.0.3 tcp spt:80ACCEPT tcp -- 172.17.0.3 172.17.0.2 tcp dpt:80DROP all -- 0.0.0.0/0 0.0.0.0/0
注意: Docker的iptables規則完全顯示了容器相互間的原始IP地址,所以一個容器到另一個容器的連接,需要顯示地顯示出第一個容器的原始IP地址。
為主機綁定容器端口
默認情況下,Docker容器可以連接到外部區域,但外部區域不能連接到容器。在Docker啟動時,由于它在主機上創建了一個iptables偽裝規則,使得每一個輸出連接看起來都是由主機IP地址建立起來的。
# You can see that the Docker server creates a# masquerade rule that let containers connect# to IP addresses in the outside world:$ sudo iptables -t nat -L -n...Chain POSTROUTING (policy ACCEPT)target prot opt source destination MASQUERADE all -- 172.17.0.0/16 !172.17.0.0/16...
當調用docker run的時候,如果你想讓容器接受輸入連接,你需要提供特殊選項。這些選項的詳細說明在 Docker User Guide. 有兩種方法可以實現。
首先,你可以提供 -P 或者 --publish-all=true|false 選項參數來執行 docker run 命令,這將會識別所有在dockerfile中暴露的端口,并且隨機映射到 49000-49900 之間的主機端口。這看起來是一個很大的不便,當你要啟動一個新的容器時你需要知道那個主機端口已經被映射。
更方便的操作是使用 -p SPEC 或者 --publish=SPEC 選項,這兩個選項讓你明確的指定docker容器的端口映射到任意的主機端口中,不局限于49000-49900.
無論如何,你應該通過審查你的NAT表,去看看docker在你的網絡占做了什么。
# What your NAT rules might look like when Docker # is finished setting up a -P forward: $ iptables -t nat -L -n ...Chain DOCKER (2 references) target prot opt source destination DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:49153 to:172.17.0.2:80 # What your NAT rules might look like when Docker # is finished setting up a -p 80:80 forward: Chain DOCKER (2 references) target prot opt source destination DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.17.0.2:80
可以看到,docker暴露了這些容器的端口到通配IP地址:0.0.0.0 ,這個通配IP地址可以匹配宿主主機上任意一個可以進入的端口。如果你希望更多的限制,并且只允許容器服務通過特殊的宿主主機的外部網絡接口來相互聯系, 那么你有兩種選擇。當你執行 docker run 命令時,你可以使用 -p IP:host_port:container_port 或者 -p IP::port 來明確地綁定外部接口。
或者如果你希望dokcer永遠轉發到一個特殊的IP地址上,你可以編輯你的docker系統設置文件(ubuntu系統的設置方法為:編輯 /etc/default/docker文件,改寫DOCKER_OPTS參數),增加選項 --ip=IP_ADDRESS 。修改完之后記得重啟你的docker服務。
如果你希望更詳細的指導,請參考: Docker User Guide .
定制 docker0
默認地,docker服務會在linux內核新建一個網絡橋接docker0,使得物理主機和其他虛擬網絡接口之間可以傳遞發送數據包,因此,這表現如一個獨立的網絡。
docker0有一個IP地址和子網掩碼,使得物理主機可以從容器的橋接網絡接收和發送數據包。并且給這個橋接網絡一個MTU(最大傳輸單元)或者 說網絡接口允許的最大包長度-例如1,500 bytes 或者從docker的宿主主機上的網絡接口拷貝的數值。在服務啟動的時候兩者都是可配置的:
--bip=CIDR— 為docker0橋接網絡提供一個特殊的IP地址和一個子網掩碼, 使用標準的 CIDR 記法例如192.168.1.5/24.
--mtu=BYTES— 從寫docker0的最大數據包長度。
在ubuntu系統上,你可以增加以上的配置到 /etc/default/docker 文件中的DOCKER_OPTS參數中,然后重啟docker服務。
當你有一個或多個正常運行的容器時,你可以通過在主機上運行brct1
命令,觀察interfaces列的輸出,來確定Docker已經將這些容器正確地連接到docker0網橋。下面是一個連接了兩個不同容器的主機:
# Display bridge info$ sudo brctl show bridge name bridge id STP enabled interfaces docker0 8000.3a1d7362b4ee no veth65f9 vethdda6
如果你的Docker主機還沒安裝brct1
命令,那么你可以在Ubuntu上運行sudo apt-get install bridge-utils
來安裝它。
最后,每次新建一個容器的時候都會用到docker0 橋接網絡。每次在執行docker run命令新建一個容器的時候,docker從可利用的橋接網絡中隨機選擇一個未被使用的IP地址,以及使用橋接網絡的子網掩碼,用來配置容器 eth0網絡接口。docker宿主主機的IP地址被docker容器作為默認的網關。
# The network, as seen from a container$ sudo docker run -i -t --rm base /bin/bash $$ ip addr show eth024: 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 routedefault via 172.17.42.1 dev eth0172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.3$$ exit
記住docker的宿主主機無法轉發docker容器的數據包到因特網上,除非它的ip_forward 系統設置為1,詳情請看: Communication between containers 。
建立你自己的橋接網絡
如果你希望建立完整的自己的橋接網絡,你可以在啟動docker之前用 -b BRIDGE 或者 --bridge=BRIDGE選項參數高數docker使用你自己的橋接網絡。如果你已經用docker0啟動docker了,你需要停止docker服務然后移除docker0.
# Stopping Docker and removing docker0$ sudo service docker stop $ sudo ip link set dev docker0 down $ sudo brctl delbr docker0
然后,在啟動docker服務之前,新建你自己的橋接網絡,寫上你想要的配置。接下來我們新建一個簡單的橋接網絡,剛好用這些選項來定做docker0 ,這剛好足夠說明這個技術。
# Create our own bridge$ sudo brctl addbr bridge0 $ sudo ip addr add 192.168.5.1/24 dev bridge0 $ sudo ip link set dev bridge0 up# Confirming that our bridge is up and running$ ip addr show bridge04: 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# Tell Docker about it and restart (on Ubuntu)$ echo 'DOCKER_OPTS="-b=bridge0"' >> /etc/default/docker $ sudo service docker start
運行結構應該是docker服務成功啟動,已經準備好綁定容器到橋接網絡上。當核實好橋接網絡的配置之后,嘗試著新建一個容器,你將看到容器的IP地址是在你的新的橋接網絡范圍內的,這是docker自動檢測的。
正如前文所述,可以用 brctl show 命令查看,新增或者移除網絡接口,可以在docker容器中執行 ip addr 和 ip route 命令查看IP地址是否是從網橋IP段分配的,以及docker的宿主主機的IP是否被作為默認網關。
Docker 如何使容器連接到網絡
docker是正在發展中的,并會持續提升網絡配置的邏輯。當前命令行是很難滿足docker新建容器時所需要的網絡配置。
讓我們回顧一些基礎知識。
通訊的時候使用網際協議(IP),一個機器需要訪問至少一個網絡接口用來發送和接收包,路由表定義了通過接口可達IP地址范圍。網絡接口不一定非是 物理設備。實際上,在每一個Linux機器(和每個Docker容器內部)的lo回環接口都是有效的而且完全是虛擬的——Linux內核簡單地拷貝回環 (數據)包,直接從發送者的內存放入接收者的內存。
Docker使用特殊的虛擬接口讓容器在主機間通訊——成對的虛擬接口被叫做“peers”,它被鏈接到主機內核的內部,因此(數據)包能在他們之間傳輸。他們簡單創建,待會兒我們將會看到。
Docker配置容器的步驟是:
1.創建一對虛擬接口
2.在主Docker主機內部給它一個唯一的名稱,比如veth65f9,綁定它到docker0或者Docker使用的任何網橋上
3.讓其他的接口KX上網進入新的容器(已經提供了lo接口),在容器的獨立和唯一網絡接口命名空間內,重新命名它為更漂亮的名字eth0,名稱不要和其他的物理接口沖突。
4.在網橋的網絡地址訪問內給容器的eth0一個新的IP地址,設置它的缺省路由為Docker主機在網橋上擁有的IP地址。
這些步驟結束后,容器將立即擁有一個eth0(虛擬)網卡,并會發現它自己可以和其他的容器以及互聯網通訊。
你可以使用 --net= 這個選項來執行 docker run 啟動一個容器,這個選項有一下可選參數。
-
--net=bridge— 默認選項,用網橋的方式來連接docker容器。
-
--net=host— 高數docker跳過配置容器的獨立網絡棧。本質上來說,這個參數告訴docker不去打包容器的網絡層。當然,docker 容器的進程仍然被限制在它自己獨有的文件系統、進程列表以及其他資源中。一個快速命令 ip addr 將像你展示docker的網絡,它是建立在docker 宿主主機上的,有完整的權限去訪問宿主主機的網絡接口。注意這不意味著docker容器可以去重新配置宿主主機的網絡棧,重新配置是需要 --privaleged=true 這個選項參數的,但是這個選項參數會讓docker容器打開大量的端口以及其他的系統的超級管理權限的進程。這也會允許容器去訪問宿主主機的網絡服務,比 如 D-bus。這會使docker容器里的進程有有權限去做一些意想不到的事,比如重啟你的宿主主機。所以要謹慎使用這個選項參數。
-
--net=container:NAME_or_ID— 告訴docker讓這個新建的容器使用已有容器的網絡配置。這個新建的容器將配置新的自己的文件系統和進程列表以及其他資源限制,但是將共享這個指定的容 器的網絡IP地址以及端口號,使得這兩個容器可以通過 loopback接口相互訪問。
-
--net=none— 告訴docker為新建的容器建立一個網絡棧,但不對這個網絡棧進行任何配置,在這個文檔的最后將介紹如何讓你去建立自定義的網絡配置。
去了解以下這一步是非常必要的,如果你在建立容器的時候使用 --net=none 這個選項參數。以下是一些命令去去配置自定義網絡,就好像你讓docker完全去自己配置一樣。
# At one shell, start a container and# leave its shell idle and running$ sudo docker run -i -t --rm --net=none base /bin/bash root@63f36fc01b5f:/# # At another shell, learn the container process ID # and create its namespace entry in /var/run/netns/# for the "ip netns" command we will be using below$ sudo docker inspect -f '{{.State.Pid}}' 63f36fc01b5f2778$ pid=2778$ sudo mkdir -p /var/run/netns $ sudo ln -s /proc/$pid/ns/net /var/run/netns/$pid# Check the bridge's IP address and netmask$ ip addr show docker021: docker0: ...inet 172.17.42.1/16 scope global docker0...# Create a pair of "peer" interfaces A and B,# bind the A end to the bridge, and bring it up$ sudo ip link add A type veth peer name B $ sudo brctl addif docker0 A $ sudo ip link set A up# Place B inside the container's network namespace,# rename to eth0, and activate it with a free IP$ sudo ip link set B netns $pid $ sudo ip netns exec $pid ip link set dev B name eth0 $ sudo ip netns exec $pid ip link set eth0 up $ sudo ip netns exec $pid ip addr add 172.17.42.99/16 dev eth0 $ sudo ip netns exec $pid ip route add default via 172.17.42.1
到這一步你的容器應該可以正常運行網絡操作了。
當你最后退出shell以及清理掉這個容器的時候,這個容器的虛擬網絡 eth0 將在網絡接口A 被清除后被消除,也會自動在網橋 docker0 上銷毀。所以不用你執行其他的命令,所有的東西將被清理。當然,是幾乎所有的東西:
# Clean up dangling symlinks in /var/run/netnsfind -L /var/run/netns -type l -delete
還要注意上面的腳本使用了現代的ip命令行替代舊的棄用的封裝,類似ipconfig和route,這些老的命令行還是會一直呆在我們的容器內部工作。如果你很忙碌的話,ip addr命令行也可以只鍵入ip a。
總之,注意這個ip netns exec重要的命令行,它讓我們以root用戶進入內部并配置一個網絡命名空間。如果在容器內部運行,類似的命令行可能不會工作,因為安全容器化的部分是 Docker剝離容器的處理過程,這個過程要正確地配置自己的網絡。使用ip netns exec可以讓我們完成配置,還避免了運行容器自身--privileged=true的危險步驟。
工具和實例
在把自定義網絡拓撲邏輯分類成下面幾個部分之前,你應該關注一些外部工具盒關于配置的實例。下面就有兩個例子:
Jérôme Petazzoni 創建了一個pipework的shell腳本,幫助你在復雜場景下建立容器間連接: https://github.com/jpetazzo/pipework
Brandon Rhodes 為下一個版本的Python網絡編程基金會創建了整個的網絡拓撲邏輯,包括路由器NAT'd防火墻,和提供HTTP,SMTP,POP,IMAP,Telnet,SSH,FTP的服務器: https://github.com/brandon-rhodes/fopnp/tree/m/playground
兩個工具都使用網絡命令并和之前見到的版本很相似,在下面章節會看到。
建立點對點連接
缺省情況下, Docker通過docker0將所有的容器添加到虛擬子網中。你能夠按照Building your own bridge中的方法創建你自己的橋讓容器連接到不同的虛擬子網。啟動容器時使用命令docker run --net=none,然后使用shell命令添加容器到你自己的橋,方法見How Docker networks a container。
但是有時你想讓兩個特別的容器能夠直接通訊,不用綁定到主機的以太網橋上。
解決方案是簡單的。創建一對對等接口,將他們放到容器中,并將其配置為經典的點對點鏈接。兩個容器就能夠直接通訊了(當然要告訴每個容器對方的IP地址)。您可能會調整在上一節的指示去這樣的事情:
# Start up two containers in two terminal windows$ sudo docker run -i -t --rm --net=none base /bin/bash root@1f1f4c1f931a:/# $ sudo docker run -i -t --rm --net=none base /bin/bash root@12e343489d2f:/# # Learn the container process IDs # and create their namespace entries $ sudo docker inspect -f '{{.State.Pid}}' 1f1f4c1f931a 2989 $ sudo 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# Create the "peer" interfaces and hand them out$ sudo ip link add A type veth peer name B $ 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通以確定連接成功。點對點鏈接不依賴于子網或子網掩碼,但是ip route需要確認一些其他的單一IP地址是連接到了特定的網絡接口。
請注意點對點鏈接可以安全的和其他類型的網絡連接混合使用。如果你想用點對點鏈接替換容器的正常網絡連接,啟動的時候,不需要帶參數--net=none。
在Docker主機和容器之間創建點對點鏈接是這個模板最終的排列方式,它允許主機和有單一IP地址的容器通訊。除非你有很特別的網絡需求,讓你嘗試使用這樣的解決方案,正如我們前面探討的,使用--icc=false鎖定跨容器的通訊是更好的方案。