Add the advanced network chapter

This commit is contained in:
Baohua Yang 2014-09-05 15:50:54 +08:00
parent d40ca4cbcb
commit 3368017470
10 changed files with 303 additions and 0 deletions

View File

@ -0,0 +1,9 @@
#docker高级网络配置
当docker启动时会在主机上创建一个docker0的虚拟网卡。他随机挑选RFC1918私有网络中的一段地址给docker0。比如172.17.42.1/16,16位掩码的网段可以拥有65534个地址可以使用这对主机和容器来说应该足够了。
注意本章介绍docker的高级网络配置一般情况下你不需要知道这些也可以使docker正常工作。简单的网络配置和介绍请看第五章内容。
docker0 不是普通的网卡他是桥接到其他网卡的虚拟网卡容器使用它来和主机相互通信。当创建一个docker容器的时候它就创建了一个对接口当数据包发送到一个接口时另外一个接口也可以收到相同的数据包它们是绑在一起的一对孪生接口。这对接口在容器中那一端的的名字是eth0宿主主机端的会指定一个唯一的名字比如vethAQI2QT这样的名字这种接口名字不再主机的命名空间中。所有的veth*的接口都会桥接到docker0这样docker就创建了在主机和所有容器之间一个虚拟共享网络。
![Docker网络](../images/docker0.png)
接下来的部分将介绍在一些场景中docker所有的网络定制配置。linux的原生命令将调整、补充、甚至替换docker默认的网络配置。

View File

@ -0,0 +1,31 @@
##创建自己的桥接
如果希望完全使用自己的桥接设置可以在启动docker服务的时候使用 -b BRIDGE or --bridge=BRIDGE 来告诉docker使用你的网桥。如果服务已经启动旧的网桥还在使用中那需要先停止服务再删除旧的网桥
```
#停止旧网桥并删除
$ sudo service docker stop
$ sudo ip link set dev docker0 down
$ sudo brctl delbr docker0
```
然后在开启服务前,创建你自己希望的网桥接口,这里建立一个网桥的配置:
```
# 创建自己的网桥
$ 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桥接设置并启动docker服务在ubuntu上
$ echo 'DOCKER_OPTS="-b=bridge0"' >> /etc/default/docker
$ sudo service docker start
```
docker服务启动成功并绑定容器到新的网桥新建一个容器你会看到它的ip是我们的设置的新ip段docker会自动检测到它。用brctl
show可以看到容器启动或则停止后网桥的配置变化在容器中使用```ip addr```和```ip route```来查看ip地址配置和路由信息。

View File

@ -0,0 +1,39 @@
##容器之间的通信
判断2个容器之间是否能够通信在操作系统层面取决于3个因素
* 网络拓扑是否连接到容器的网络接口默认docker会将所有的容器连接到docker0这网桥来提供数据包通信。其他拓扑结构将在稍后的文档中详细介绍。
* 主机是否开启ip转发ip_forward参数为1的时候可以提供数据包转发。通常你只需要为docker 设定 --ip-forward=true,
docker 就会在服务启动的时候设定ip_forward参数为1。下面是手工检查并手工设定该参数的方法。
```
# Usually not necessary: turning on forwarding,
# on the host where your Docker server is running
$ cat /proc/sys/net/ipv4/ip_forward
0
$ sudo echo 1 > /proc/sys/net/ipv4/ip_forward
$ cat /proc/sys/net/ipv4/ip_forward
1
```
*你的iptables是否允许这条特殊的连接被建立当docker的设定--iptables=false时docker不会改变系统的iptables
设定,否则它会在--icc=true的时候添加一条默认的ACCEPT策略到 FORWARD链当—icc=false时策略为DROP。几乎所有的人都会开启ip_forward来启用容器间的通信。但是否要改变icc-true配置是一个战略问题。这样iptable就可以防止其他被感染容器对宿主主机的恶意端口扫描和访问。
当你选择更安全的设定--icc=false后如何保持你希望的容器之间通信呢
答案就是--link=CONTAINER_NAME:ALIAS选项在之前的dns服务设定中提及过。如果docker 使用icc=false and --iptables=true 2个参数当docker run使用--link=选型时docker会为2个容器在iptable中参数一对ACCEPT规则开放的端口取决与dockerfile中的EXPOSE行详见第五章。
注意:--link= 中的CONTAINER_NAME 必须是自动生成的docker名字比如stupefied_pare或者你用--name参数指定的名字主机名在--link中不会被识别。
你可以使用iptables命令来检查FORWARD链是ACCEPT 还是DROP
当--icc=false时默认规则应该是这样
```
$ 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
...
```
当添加了--link后ACCEPT规则被改写了添加了新的端口和IP规则
```
$ 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: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
```

View File

@ -0,0 +1,25 @@
##配置DNS
docker没有定制为每一个容器定制image是怎么提供容器的主机名和dns配置呢秘诀就是它用主机上的3个配置文件来覆盖容器的这3个文件在容器中使用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 ...
...
```
这种机制可以让宿主主机从dhcp更新dns信息后马上更新所有docker容器的dns配置。如果要保持docker中这些文件固定不变你可以不覆盖容器中的这些配置文件然后使用下面的选项来配置它们。
配置容器dns服务的方法
-h HOSTNAME or --hostname=HOSTNAME
设定容器的主机名,它会被写到/etc/hostname/etc/hosts中的ip地址自动写成分配的ip地址在/bin/bash中显示该主机名。但它不会在docker ps中显示也不会在其他的容器的/etc/hosts中显示。
--link=CONTAINER_NAME:ALIAS
这选项会在创建容器的时候添加一个其他容器CONTAINE_NAME的主机名到/etc/hosts文件中让新容器的进程可以使用主机名ALIAS就可以连接它。--link=会在容器之间的通信中更详细的介绍
--dns=IP_ADDRESS
添加dns服务器到容器的/etc/resolv,conf中让容器用这ip地址来解析所有不在/etc/hosts中的主机名。
--dns-search=DOMAIN
设定容器的搜索域,当设定搜索域为.example.com时会在搜索一个host主机名时dns不仅搜索host还会搜索host.example.com
注意如果没有上述最后2个选项docker会用主机上的/etc/resolv.conf来配置容器它是默认配置。

View File

@ -0,0 +1,31 @@
##定制docker0
docker服务默认会创建一个docker0接口它在linux内核层桥接所有物理或虚拟网卡这就将所有容器和主机接口都放到同一个物理网络。
Docker指定了docker0的ip地址和子网掩码让主机和容器之间可以通过网桥相互通信它还给出了MTU-接口允许接收的最大传输单元通常是1500bytes或宿主主机网络路由上支持的默认值这2个都需要在服务启动的时候配置。
* --bip=CIDR — 192.168.1.5/24.ip地址加掩码 使用这种格式
* --mtu=BYTES — 覆盖默认的docker mtu配置
你可以在配置文件中配置DOCKER_OPTS然后重启来改变这些参数。
```
# 当容器启动后你可以使用brctl来确认他们是否已经连接到docker0网桥
$ sudo brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.3a1d7362b4ee no veth65f9
vethdda6
```
如果brctl命令没安装的话在ubuntu中你可以使用apt-get install bridge-utils这个命令来安装
docker0 网桥设置会在每次创建新容器的时候被使用。docker从可用的地址段中选择一个空闲的ip地址给容器的eth0端口子网掩码使用网桥docker0的配置docker主机本身的ip作为容器的网关使用。
```
$ sudo 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
```
转发数据包需要在主机上设定ip_forward参数为1,上文介绍过。

View File

@ -0,0 +1,8 @@
##工具和示例
在介绍自定义网络拓扑之前,你可能会对一些外部工具和例子感兴趣:
https://github.com/jpetazzo/pipework
Jérôme Petazzoni 创建了一个叫pipework的shell脚本来帮助我们在复杂的场景中完成网络连接
https://github.com/brandon-rhodes/fopnp/tree/m/playground
Brandon Rhodes创建了一个完整的docker容器网络拓扑包含 nat 防火墙服务包括HTTP, SMTP, POP, IMAP, Telnet, SSH, and FTP:
工具使用的网络命令跟我们之前看到非常相似。

View File

@ -0,0 +1,23 @@
##快速配置指南
下面是一个跟docker网络相关的命令列表希望可以让你快速找到需要的信息。有些命令选项只有在docker服务启动的时候才可以执行而且不能马上生效。
* -b BRIDGE or --bridge=BRIDGE — 桥接配置
* --bip=CIDR — 定制docker0的掩码
* -H SOCKET... or --host=SOCKET... — 它告诉docker从哪个通道来接收run container stop
container这样的命令也是docker api的地址
* --icc=true|false — 请看下文容器之间的通信
* --ip-forward=true|false — 请看下文容器之间的通信
* --iptables=true|false — 请看下文容器之间的通信
* --mtu=BYTES —请看下文定制docker0
下面2个可以在docker服务启动和docker run执行的时候指定服务启动的时候指定则会为docker run设定默认值docker run 后面指定可以覆盖默认值。
* --dns=IP_ADDRESS... — 请看下文dns配置
* --dns-search=DOMAIN... — 请看下文dns配置
最后这些选项只有在docker run后执行因为它是针对容器的特性内容。
*-h HOSTNAME or --hostname=HOSTNAME — 主机名配置
*--link=CONTAINER_NAME:ALIAS — link系统
*--net=bridge|none|container:NAME_or_ID|host —桥接配置
*-p SPEC or --publish=SPEC — 映射容器端口到宿主主机
* -P or --publish-all=true|false — 映射容器端口到宿主主机

View File

@ -0,0 +1,60 @@
##Docker 如何连接到容器?
让我们回顾一些基础知识:
机器需要一个网络接口来发送和接受数据包路由表来定义如何到达哪些地址段。这里的网络接口可以不是物理接口。事实上每个linxu机器上的lo环回接口docker 容器中也有就是一个完全的linux内核虚拟接口它直接复制发送缓存中的数据包到接收缓存中。docker让宿主主机和容器使用特殊的虚拟接口来通信--通信的2端叫“peers“他们在主机内核中连接在一起所以能够相互通信。创建他们很简单前面介绍过了。
docker创建容器的步骤如下
* 创建一对虚拟接口
* 其中宿主主机一端使用一个名字比如veth65f9他是唯一的,另外一端桥接到默认的docker0,或其它你指定的桥接网卡。
* 主机上的veth65f9这种接口映射到新的新容器中的名称通常是eth0,在容器这个隔离的network namespace
中,它是唯一的,不会有其他接口名字和它冲突。
* 从主机桥接网卡的地址段中获取一个空闲地址给eth0使用并设定默认路由到桥接网卡。
* 完成这些之后容器就可以使用这eth0虚拟网卡来连接其他容器和其他网络。
你也可以为特殊的容器设定特定的参数在docker run的时候使用--net它有4个可选参数
* --net=bridge — .默认连接到docker0网桥。
* --net=host — 告诉docker不要将容器放到隔离的网络堆栈中。从本质上讲这个选项告诉docker
不要容器化容器的网络尽管容器还是有自己的文件系统、进程列表和资源限制。但使用ip addr命令这样命令就可以知道实际上此时的的容器处于和docker 宿主主机的一样的网络级别,它拥有完全的宿主主机接口访问权限。虽然它不允许容器重新配置主机的网络堆栈,除非--privileged=true — 但是容器进程可以跟其他root进程一样可以打开低数字的端口可以访问本地网络服务比如D-bus还可以让容器做一些意想不到的事情比如重启主机使用这个选项的时候要非常小心
* --net=container:NAME_or_ID —
告诉docker将新容器的进程放到一个已经存在的容器的网络堆栈中新容器进程有它自己的文件系统、进程列表和资源限制但它会和那个已经存在的容器共享ip地址和端口他们之间来可以通过环回接口通信。
* --net=none — 告诉docker将新容器放到自己的网络堆栈中但是不要配置它的网络,
类似于vmware的host-only。这可以让你创建任何自定义的配置本文最后一段将介绍 他们。
下面通过配置一个以--net=none启动的容器使他达到跟平常一样具有访问网络的权限。来介绍docker是如何连接到容器中的。
启动一个/bin/bash 指定--net=none
```
$ sudo docker run -i -t --rm --net=none base /bin/bash
root@63f36fc01b5f:/#
```
再开启一个新的终端查找这个容器的进程id然后创建它的命名空间后面的ip netns会用到
```$ sudo docker inspect -f '{{.State.Pid}}' 63f36fc01b5f
2778
$ pid=2778
$ sudo mkdir -p /var/run/netns
$ sudo ln -s /proc/$pid/ns/net /var/run/netns/$pid
```
检查桥接网卡的ip和子网掩码
```
$ ip addr show docker0
21: docker0: ...
inet 172.17.42.1/16 scope global docker0
...
```
创建一对”peer“接口A和B绑定A到网桥并启用它
```
$ sudo ip link add A type veth peer name B
$ sudo brctl addif docker0 A
$ sudo ip link set A up
```
将B放到容器的网络命名空间命名为eth0,配置一个空闲的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后docker清空容器容器的eth0随网络命名空间一起被摧毁A 接口也被自动从docker0取消注册。不用其他命令所有东西都被清理掉了
注意ip netns exec命令它可以让我们像root一样配置网络命名空间。但在容器内部无法使用因为统一的安全策略docker限制容器进程配置自己的网络。使用ip netns exec 可以让我们不用设置--privileged=true就可以完成一些可能带来危险的操作。

View File

@ -0,0 +1,37 @@
##映射一个容器端口到宿主主机
默认情况下容器可以建立到外部网络的连接但是外部网络无法连接到容器。所有到外部的连接源地址都会被伪装成宿主主机的ip地址iptables的 masquerading来做到这一点。
```
# 查看主机的masquerading规则
$ 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执行的时候就指定对应选项第五章详细介绍了2种方法
* 指定-P --publish-all=true|false 选项会映射dockerfile
中expose的所有端口主机端口在49000-49900中随机挑选。当你的另外一个容器需要学习这个端口时候很不方便。
* 指定-p SPEC或则 --publish=SPEC,可以指定任意端口从主机映射容器内部
不管用那种办法你可以通过查看iptable的 nat表来观察docker 在网络层做了什么操作。
```
#使用-P时
$ 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
#使用-p 80:80时
$ 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:80 to:172.17.0.2:80
```
注意:
* 这里看到docker映射了0.0.0.0.它接受主机上的所有接口地址。可以通过-p IP:host_port:container_port 或则 -p
IP::port 来指定主机上的ip、接口制定更严格的规则。
* 如果你希望永久改变绑定的主机ip地址可以 在dcoker 配置中指定--ip=IP_ADDRESS. 记得重启服务。

View File

@ -0,0 +1,40 @@
##创建一个点到点连接
默认docker会将所有容器连接到由docker0提供的虚拟子网你也可以使用自己创建的网桥。但如果你想要2个特殊的容器之间可以直连通信而不用去配置复杂的主机网卡桥接。
解决办法很简单创建一对接口把2个容器放到这对接口中配置成点到点链路类型。这2个容器就可以直接通信了。配置如下
```
# 在2个终端中启动2个容器
$ 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:/#
```
找到他们的process IDs ,然后创建他们的 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
```
创建"peer"接口,然后配置路由
```
$ 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
```
现在这2个容器就可以相互ping通并成功建立连接。点到点链路不需要子网和子网掩码使用ip route 来连接单个ip地址到指定的网络接口。
如果没有特殊需要你不需要指定--net=none来创建点到点链路。
还有一个办法就是创建一个只跟主机通信的容器,除非有特殊需求,你可以仅用--icc=false来限制主机间的通信。