Docker 高級網絡配置
當 Docker 啟動時,會自動在宿主機上創建一個 docker0 虛擬網橋,實際上是Linux 的一個 bridge,可以理解為一個軟件交換機。它會在掛載到它的網口之間進行轉發。
[root@server ~]# ifconfig docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 172.17.0.1 netmask 255.255.0.0 broadcast 0.0.0.0
同時,Docker 隨機分配一個本地未占用的私有網絡(在RFC1918中定義)中的一個地址給 docker0接口。比如典型的 172.17.0.1,掩碼為255.255.0.0。此后啟動的容器內的網口也會自動分配一個網段(172.17.0.0/16)的地址。
當創建一個 Docker 容器的時候,同時會創建一個 veth pair 接口(當數據包發送到一個接口時,另外一個接口也可以接收相同的數據包)。這對接口一端在容器內,即 eth0;另一端在本地並被掛載到docker0 網橋,名稱以veth 開頭(例如 veth4c45933)。通過這種方式,主機可以跟容器通信,容器之間也可以相互通信。Docker 就創建了在主機和所有容器之間的一個虛擬共享網絡。
快速配置指南
Docker 網絡相關的命令列表
只有在Docker 服務啟動的時候才能配置,而且不能馬上生效的有:
-b BRIDGE or --bridge=BRIDGE --指定容器掛載的網橋 --bip=CIDR --定制docker0的掩碼 -H SOCKET... or --host=SOCKET... --Docker服務端接收命令的通道 --icc=true|false --是否支持容器之間進行通信 --ip-forward=true|false --請看下文容器之間的通信 --iptables=true|false --是否允許Docker添加iptables規則 --mtu=BYTES --容器網絡中的MTU
既可以在啟動服務時指定,也可以 Docker容器啟動(docker run)時候指定。在Docker 服務啟動的時候指定則會成為默認值,后面執行 docker run 時可以覆蓋設置的默認值:
--dns=IP_ADDRESS... --使用指定的DNS服務器 --dns-search=DOMAIN... --指定DNS搜索域
只有在 docker run 執行時使用,因為它是針對容器的特性內容:
-h HOSTNAME or --hostname=HOSTNAME --配置容器主機名 --link=CONTAINER_NAME:ALIAS --添加到另一個容器的連接 --net=bridge|none|container:NAME_or_ID|host --配置容器的橋接模式 -p SPEC or --publish=SPEC --映射容器端口到宿主主機 -P or --publish-all=true|false --映射容器所有端口到宿主主機
配置 DNS
Docker 沒有為每個容器專門定制鏡像,那么怎么自定義配置容器的主機名和DNS配置?秘訣就是它利用虛擬文件來掛載到容器的3個相關的配置文件。
在容器中使用mount命令可以看到掛載信息:
[root@server ~]# docker run --rm --name myCentos -it centos /bin/bash #創建一個執行完命令自動終止的容器 [root@fb589b253bba /]# mount ... /dev/mapper/cl-root on /etc/resolv.conf type xfs (rw,relatime,attr2,inode64,noquota) /dev/mapper/cl-root on /etc/hostname type xfs (rw,relatime,attr2,inode64,noquota) /dev/mapper/cl-root on /etc/hosts type xfs (rw,relatime,attr2,inode64,noquota) ...
這種機制可以讓宿主機DNS信息發生更新后,所有Docker容器的 dns 配置通過 /etc/resolv.conf 文件立刻得到更新。
如果想要手動指定容器的配置,可以利用下面的選項。
-h HOSTNAME or --hostname=HOSTNAME 設定容器的主機名,它會被寫到容器的/etc/hostname 和 /etc/hosts。 但它在容器外部看不到,既不會在docker ps中顯示,也不會在其它容器的 /etc/hosts看到 [root@server ~]# docker run --rm -h mydocker_test --name myCentos -it centos /bin/bash #創建一個指定 HOSTNAME的容器 [root@mydocker_test /]# cat /etc/hostname #查看hostname文件信息 mydocker_test [root@mydocker_test /]# cat /etc/hosts #查看hosts文件信息 ... 172.17.0.5 mydocker_test
--link=CONTAINER_NAME:ALIAS 選項會在創建容器的時候,添加一個其他容器 的主機名到/etc/hosts 文件中,讓新容器的進程可以使用主機名 ALIAS 就可以 連接它。 實例參考:https://www.cnblogs.com/yanjieli/p/10218842.html#autoid-7-6-1
--dns=IP_ADDRESS 添加DNS服務器到容器的 /etc/resolv.conf中,讓容器利用這個服務器來解析所有不在 /etc/hosts 中的主機名 [root@server ~]# docker run --rm --dns=114.114.114.114 --name myCentos -it centos /bin/bash #創建一個容器,並指定dns為114.114.114.114 [root@aa199cd14e2a /]# cat /etc/resolv.conf nameserver 114.114.114.114
--dns-search=DOMAIN 設定容器的搜索域,當設定搜索域為 .example.com時,再搜索一個名為 host的主機時,DNS不僅搜索host,還會搜索host.example.com。
注意:如果沒有--dns 和--dns-search選項時,Docker會默認用宿主機上的 /etc/resolv.conf 來配置容器。
容器訪問控制
容器的訪問控制,主要通過Linux上的 iptables 防火牆來進行管理和實現。
容器訪問外部網絡
容器要想訪問外部網絡,需要本地系統的轉發支持,在linux系統中,檢查轉發是否打開
[root@server ~]# sysctl net.ipv4.ip_forward net.ipv4.ip_forward = 1
如果為0,說明沒有開啟轉發,則需要手動打開
[root@server ~]# sysctl -w net.ipv4.ip_forward=1
如果在啟動Docker服務的時候指定 --ip-forward=true,Docker就會自動設定系統的 ip_forward 參數為1。
[root@server ~]# docker run --rm --name myCentos -it centos /bin/bash #啟動一個容器 [root@2bdfb90cb3e0 /]# ping qq.com #測試是否能夠訪問外部網絡 PING qq.com (180.163.26.39) 56(84) bytes of data. 64 bytes from 180.163.26.39 (180.163.26.39): icmp_seq=1 ttl=52 time=29.6 ms 64 bytes from 180.163.26.39 (180.163.26.39): icmp_seq=2 ttl=52 time=29.3 ms
容器之間訪問
容器之間互相訪問,需要兩方面的支持。
- 容器的網絡拓撲是否已經互聯。默認情況下,所有容器都會被連接到 docker0 網橋上
- 本地系統的 iptables 是否允許通過
默認情況下,容器之間是可以相互通信的
訪問所有的端口
當啟動Docker 服務時,默認會添加一條轉發策略到iptables 的FORWARD鏈上。策略為通過(ACCEPT)還是禁止(DROP)取決於配置 --icc=true 還是 --icc=false。如果手動指定 --iptables=false 則不會添加iptables 規則。
可見,默認情況下,不同容器之間是允許網絡互通的。如果是為了安全考慮,可以在/etc/default/docker 文件中配置 DOCKER_OPTS=--icc=false 來禁止它。
容器訪問外部網絡
容器所有到外部網絡的連接,源地址都會被NAT成本地系統的IP地址,這是使用iptables 的源地址偽裝操作實現的。
查看主機的NAT規則
[root@server ~]# iptables -t nat -nL #查看防火牆nat表的規則 ... Chain POSTROUTING (policy ACCEPT) target prot opt source destination MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0 ...
其中,上述規則將所有源地址在 172.17.0.0/16 網段,目標地址為其它網絡(外部網絡)的流量動態偽裝為從系統網卡發出。MASQUERADE 跟傳統的 SNAT的好處就是它能動態從網卡獲取地址。
外部網絡訪問容器
容器允許外部訪問,可以在docker run 時候通過 -p 或 -P 參數來啟用。不論用哪種,其實也是在本地的 iptables 的nat表中添加相應的規則。使用 -P 時:
[root@server ~]# iptables -t nat -nL #查看防火牆nat表的規則 ... Chain DOCKER (2 references) target prot opt source destination DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:32770 to:172.17.0.3:443 DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:32771 to:172.17.0.3:80 ...
使用 -p 88:80 時:
[root@server ~]# iptables -t nat -nL #查看防火牆規則 Chain DOCKER (2 references) target prot opt source destination DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:88 to:172.17.0.5:80
注意:這里規則映射了 0.0.0.0,意味着將接收主機來自所有的接口的流量。
配置 docker0 網橋
Docker 服務默認會創建一個 docker0 網橋(其實有一個 docker0 內部接口),它在內核層連通了其它的物理網卡或虛擬網卡,這就將所有容器和本地主機都放到同一個物理網絡。
Docker 默認指定了 docker0 接口的 IP 地址和子網掩碼,讓主機和容器之間可以通過網橋相互通信,它還給出了MTU(接口允許接收的最大傳輸單元),通常是1500Bytes,或宿主機網絡路由上支持的默認值,這些值都可以在服務啟動的時候進行配置。
--bip=CIDR --IP地址加掩碼格式,例如 192.168.1.2/24 --mtu=BYTES --覆蓋默認的 Docker mtu配置
也可以在配置文件中配置 DOCKER_OPTS, 然后重啟服務,由於目前Docker網橋是Linux網橋,可以使用 brctl show 來查看網橋和端口連接信息
[root@server ~]# brctl show #查看網橋和端口信息 bridge name bridge id STP enabled interfaces docker0 8000.0242d8da46c0 no veth07c5f5c veth0bed0f7
每次創建一個新容器的時候,Docker 從可用的地址段中選擇一個空閑的 IP 地址分配給容器的 eth0 端口。使用本地主機上 docker0 接口的IP 作為所有容器的默認網關
[root@server ~]# docker run -it --rm --name myCentos centos /bin/bash [root@4636797a8e7c /]# ifconfig eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 172.17.0.6 netmask 255.255.0.0 broadcast 172.17.255.255 ... [root@4636797a8e7c /]# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 172.17.0.1 0.0.0.0 UG 0 0 0 eth0 172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 eth0
自定義網橋
除了默認的 docker0 網橋,也可以指定網橋來連接各個容器
在啟動Docker 服務的時候, 使用 -b BRIDGE 或 --bridge=BRIDGE 來指定使用的網橋
(1)先創建網橋
[root@server ~]# systemctl stop docker #停止docker服務 [root@server ~]# ip link set dev docker0 down #停止docker0網橋 [root@server ~]# brctl delbr docker0 #刪除docker0網橋 [root@server ~]# brctl addbr bridge0 #新建bridge0網橋 [root@server ~]# ip addr add 192.168.2.1/24 dev bridge0 #綁定ip給bridge0網橋 [root@server ~]# ip link set dev bridge0 up #啟動bridge0網橋 [root@server ~]# brctl show #查看網橋信息 bridge name bridge id STP enabled interfaces bridge0 8000.000000000000 no virbr0 8000.525400caf93e yes virbr0-nic [root@server ~]# ifconfig bridge0 #查看bridge0網橋信息 bridge0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 192.168.2.1 netmask 255.255.255.0 broadcast 0.0.0.0
(2)配置Docker 服務
[root@server ~]# vim /lib/systemd/system/docker.service #由於新版本的沒有/etc/default/docker配置文件,so 需要自己添加。 ExecStart=/usr/bin/dockerd -H unix:// $DOCKER_OPTS #在ExecStart末尾添加 $DOCKER_OPTS EnvironmentFile=-/etc/default/docker #指定配置文件的路徑 [root@server ~]# vim /etc/default/docker #自定義編輯配置文件,寫入啟動指定網橋的網橋信息 DOCKER_OPTS="-b=bridge0" [root@server ~]# systemctl start docker #啟動docker服務 [root@server ~]# docker run --rm -ti --name Mycentos centos /bin/bash #創建一個容器 [root@0a13bd05faae /]# ifconfig #查看容器的ip地址,檢查是否橋接到birdge0上面 eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 192.168.2.2 netmask 255.255.255.0 broadcast 192.168.2.255 [root@0a13bd05faae /]# ping 192.168.2.1 #測試和網橋是否通 PING 192.168.2.1 (192.168.2.1) 56(84) bytes of data. 64 bytes from 192.168.2.1: icmp_seq=1 ttl=64 time=0.094 ms
說明:在老版本docker上面默認就有/etc/default/docker配置文件,直接編輯即可, 在新版本上面沒有,所以需要自己指定。
創建一個點到點的連接
默認情況下,Docker 會將所有的容器連接到由 docker0 提供的虛擬子網中。
如果我們需要兩個容器之間可以通信,而不通過宿主機網橋進行橋接。
解決辦法:創建一對 peer 接口,分別放到兩個容器中,配置成點對點鏈路類型即可。
(1)首先啟動2個容器:
[root@server ~]# docker run -i -t --rm --name myCentos01 --net=none centos /bin/bash #在第一個終端啟動第一個容器 [root@server ~]# docker run -i -t --rm --name myCentos02 --net=none centos /bin/bash #在第二個終端啟動第二個容器
(2)找到進程號,然后創建網絡命名空間的跟蹤文件(在第三個終端操作)
[root@server ~]# docker inspect -f '{{.State.Pid}}' myCentos01 #找到myCentos01的進程號 16614 [root@server ~]# docker inspect -f '{{.State.Pid}}' myCentos02 #找到myCentos02的進程號 16713 [root@server ~]# mkdir -p /var/run/netns [root@server ~]# ln -s /proc/16614/ns/net /var/run/netns/16614 [root@server ~]# ln -s /proc/16713/ns/net /var/run/netns/16713
(3)創建一對 peer 接口,然后配置路由(在第三個終端操作)
[root@server ~]# ip link add A type veth peer name B [root@server ~]# [root@server ~]# ip link set A netns 16614 [root@server ~]# ip netns exec 16614 ip addr add 10.1.1.1/32 dev A [root@server ~]# ip netns exec 16614 ip link set A up [root@server ~]# ip netns exec 16614 ip route add 10.1.1.2/32 dev A [root@server ~]# [root@server ~]# ip link set B netns 16713 [root@server ~]# ip netns exec 16713 ip addr add 10.1.1.2/32 dev B [root@server ~]# ip netns exec 16713 ip link set B up [root@server ~]# ip netns exec 16713 ip route add 10.1.1.1/32 dev B
(4)測試兩個容器之間的通信
[root@90709b31ef86 /]# ping 10.1.1.2 #在myCentos01上面測試與myCentos02之間的通信 PING 10.1.1.2 (10.1.1.2) 56(84) bytes of data. 64 bytes from 10.1.1.2: icmp_seq=1 ttl=64 time=0.075 ms [root@96af03d9ad15 /]# ping 10.1.1.1 #在myCentos02上面測試與myCentos01之間的通信 PING 10.1.1.1 (10.1.1.1) 56(84) bytes of data. 64 bytes from 10.1.1.1: icmp_seq=1 ttl=64 time=0.075 ms
通過測試,這兩個容器可以互相ping通,並成功建立連接。點到點鏈路不需要子網和子網掩碼。
此外,也可以不指定 --net=none 來創建點到點鏈路。這樣容器還可以通過原先的網絡來通信。