rewrite the advanced network chapter

pull/7/head
Baohua Yang 2014-09-18 17:38:20 +08:00
parent 424e07993e
commit 23d4e36c64
11 changed files with 130 additions and 163 deletions

View File

@ -32,19 +32,19 @@
* [数据卷](data_management/volume.md)
* [数据卷容器](data_management/container.md)
* [备份、恢复、迁移数据卷](data_management/management.md)
* [网络和互联](network/README.md)
* [端口映射](network/port_mapping.md)
* [基本网络功能](network/README.md)
* [外部访问容器](network/port_mapping.md)
* [容器互联](network/linking.md)
* [高级网络配置](advanced_network/README.md)
* [快速配置指南](advanced_network/fast_config.md)
* [快速配置指南](advanced_network/quick_guide.md)
* [配置DNS](advanced_network/dns.md)
* [容器之间的通信](advanced_network/communication.md)
* [映射一个容器端口到宿主主机](advanced_network/port_mapping.md)
* [定制docker0](advanced_network/docker0.md)
* [创建自己的桥接](advanced_network/bridge.md)
* [Docker 如何连接到容器](advanced_network/how_connect.md)
* [容器访问控制](advanced_network/access_control.md)
* [端口映射实现](advanced_network/port_mapping.md)
* [配置docker0](advanced_network/docker0.md)
* [自定义网桥](advanced_network/bridge.md)
* [工具和示例](advanced_network/example.md)
* [创建一个点到点连接](advanced_network/ptp.md)
* [编辑网络配置文件](advanced_network/config_file.md)
* [实例:创建一个点到点连接](advanced_network/ptp.md)
* [容器安全](container_security/README.md)
* [内核名字空间](container_security/kernel_ns.md)
* [控制组](container_security/control_group.md)

View File

@ -7,7 +7,6 @@
当创建一个Docker容器的时候同时会创建了一对`veth pair`接口(当数据包发送到一个接口时,另外一个接口也可以收到相同的数据包)。这对接口一端在容器内,即`eth0`;另一端在本地并被挂载到`docker0`网桥,名称以`veth`开头(例如`vethAQI2QT`。通过这种方式主机可以跟容器通信容器之间也可以相互通信。Docker就创建了在主机和所有容器之间一个虚拟共享网络。
![Docker网络](../_images/network.png)
接下来的部分将介绍在一些场景中docker所有的网络定制配置。linux的原生命令将调整、补充、甚至替换docker默认的网络配置。

View File

@ -0,0 +1,55 @@
## 容器访问控制
容器的访问控制主要通过Linux上的`iptables`防火墙来进行管理和实现。`iptables`是Linux上默认的防火墙软件在大部分发行版中都自带。
### 容器访问外部网络
容器要想访问外部网络需要本地系统的转发支持。在Linux系统中检查转发是否打开。
```
$sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1
```
如果为0说明没有开启转发则需要手动打开。
```
$sysctl -w net.ipv4.ip_forward=1
```
如果在启动Docker服务的时候设定`--ip-forward=true`, Docker就会自动设定系统的`ip_forward`参数为1。
### 容器之间访问
容器之间相互访问,需要两方面的支持。
* 容器的网络拓扑是否已经互联。默认情况下,所有容器都会被连接到`docker0`网桥上。
* 本地系统的防火墙软件 -- `iptables`是否允许通过。
#### 访问所有端口
当启动Docker服务时候默认会添加一条转发策略到iptables的FORWARD链上。策略为通过`ACCEPT`)还是禁止(`DROP`)取决于配置`--icc=true`(缺省值)还是`--icc=false`。当然,如果手动指定`--iptables=false`则不会添加`iptables`规则。
可见,默认情况下,不同容器之间是允许网络相互访问的。如果为了安全考虑,可以在`/etc/default/docker`文件中配置`DOCKER_OPTS=--icc=false`来禁止它。
#### 访问指定端口
在通过`-icc=false`关闭网络访问后,还可以通过`--link=CONTAINER_NAME:ALIAS`选项来访问容器的开放端口。
例如在启动Docker服务时可以同时使用`icc=false --iptables=true`2个参数来关闭允许相互的网络访问并让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`中为2个容器分别添加一条`ACCEPT`规则允许相互访问开放的端口取决于dockerfile中的EXPOSE行
当添加了`--link=CONTAINER_NAME:ALIAS`选项后,添加了`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`参数指定的名字。主机名则不会被识别。

View File

@ -1,31 +1,34 @@
##创建自己的桥接
如果希望完全使用自己的桥接设置可以在启动docker服务的时候使用 -b BRIDGE or --bridge=BRIDGE 来告诉docker使用你的网桥。如果服务已经启动旧的网桥还在使用中那需要先停止服务再删除旧的网桥
##自定义网桥
除了默认的`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服务默认桥接到创建的网桥上。
```
# 告诉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地址配置和路由信息。
启动Docker服务。
新建一个容器,可以看到它已经桥接到了`bridge0`上。
可以继续用`brctl show`命令查看桥接的信息。另外,在容器中可以使用`ip addr`和`ip route`命令来查看ip地址配置和路由信息。

View File

@ -1,41 +0,0 @@
##容器之间的通信
判断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

@ -1,19 +1,22 @@
##定制docker0
docker服务默认会创建一个docker0接口它在linux内核层桥接所有物理或虚拟网卡这就将所有容器和主机接口都放到同一个物理网络。
Docker指定了docker0的ip地址和子网掩码让主机和容器之间可以通过网桥相互通信它还给出了MTU-接口允许接收的最大传输单元通常是1500bytes或宿主主机网络路由上支持的默认值这2个都需要在服务启动的时候配置。
* --bip=CIDR — 192.168.1.5/24.ip地址加掩码 使用这种格式
* --mtu=BYTES — 覆盖默认的docker mtu配置
##配置docker0
Docker服务默认会创建一个`docker0`接口,它在内核层连通了其他的物理或虚拟网卡,这就将所有容器和本地主机都放到同一个物理网络。
你可以在配置文件中配置DOCKER_OPTS然后重启来改变这些参数。
Docker默认指定了`docker0`的IP地址和子网掩码让主机和容器之间可以通过网桥相互通信它还给出了MTU接口允许接收的最大传输单元通常是1500bytes或宿主主机网络路由上支持的默认值这2个都可以在服务启动的时候进行配置。
* --bip=CIDR -- IP地址加掩码格式例如192.168.1.5/24
* --mtu=BYTES -- 覆盖默认的Docker mtu配置
也可以在配置文件中配置DOCKER_OPTS然后重启服务。
由于目前Docker网桥是Linux网桥用户可以使用`brctl show`来查看网桥和端口连接信息。
```
# 当容器启动后你可以使用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作为容器的网关使用。
```
注:`brctl`命令在Debian、Ubuntu中可以使用`sudo apt-get install bridge-utils`来安装。
每次创建一个新容器的时候Docker从可用的地址段中选择一个空闲的ip地址分配给容器的eth0端口。Docker主机上接口`docker0`的IP作为所有容器的默认网关。
```
$ sudo docker run -i -t --rm base /bin/bash
$ ip addr show eth0
@ -28,4 +31,3 @@ 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

@ -1,8 +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:
工具使用的网络命令跟之前看到非常相似。
### pipework
Jérôme Petazzoni编写了一个叫[pipework](https://github.com/jpetazzo/pipework)的shell脚本可以帮助用户在比较复杂的场景中完成容器的连接。
### playground
Brandon Rhodes创建了一个提供完整的Docker容器网络拓扑管理的[Python库](https://github.com/brandon-rhodes/fopnp/tree/m/playground)包括路由、NAT防火墙以及一些提供HTTP, SMTP, POP, IMAP, Telnet, SSH, FTP的服务器。

View File

@ -1,63 +1 @@
##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

@ -1,37 +1,43 @@
##映射一个容器端口到宿主主机
##映射容器端口到宿主主机的实现
默认情况下容器可以建立到外部网络的连接但是外部网络无法连接到容器。所有到外部的连接源地址都会被伪装成宿主主机的ip地址iptables的 masquerading来做到这一点。
默认情况下,容器可以主动访问到外部网络的连接,但是外部网络无法访问到容器。
### 容器访问外部实现
容器所有到外部网络的连接源地址都会被NAT成本地系统的IP地址。这是使用`iptables`的源地址伪装操作实现的。
查看主机的NAT规则。
```
# 查看主机的masquerading规则
$ sudo iptables -t nat -L -n
$ sudo iptables -t nat -nL
...
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 172.17.0.0/16 !172.17.0.0/16
...
```
其中,上述规则将所有源地址在`172.17.0.0/16`网段目标地址为其他网段外部网络的流量动态伪装为从系统网卡发出。MASQUERADE跟传统SNAT的好处是它能动态从网卡获取地址。
当你希望容器接收外部连接时你需要在docker run执行的时候就指定对应选项第五章详细介绍了2种方法
* 指定-P --publish-all=true|false 选项会映射dockerfile
中expose的所有端口主机端口在49000-49900中随机挑选。当你的另外一个容器需要学习这个端口时候很不方便。
* 指定-p SPEC或则 --publish=SPEC,可以指定任意端口从主机映射容器内部
### 外部访问容器实现
不管用那种办法你可以通过查看iptable的 nat表来观察docker 在网络层做了什么操作。
容器允许外部访问,可以在`docker run`时候通过`-p`或`-P`参数来启用。
不管用那种办法,其实也是在本地的`iptable`的nat表中添加相应的规则。
使用`-P`时:
```
#使用-P时
$ iptables -t nat -L -n
$ 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:49153 to:172.17.0.2:80
#使用-p 80:80时
$ iptables -t nat -L -n
```
使用`-p 80:80`时:
```
$ 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: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. 记得重启服务
* 这里的规则映射了0.0.0.0,意味着将接受主机来自所有接口的流量。用户可以通过`-p IP:host_port:container_port`或`-p
IP::port`来指定允许访问容器的主机上的IP、接口等制定更严格的规则。
* 如果希望永久绑定到某个固定的IP地址可以在Docker 配置文件`/etc/default/docker`中指定`DOCKER_OPTS="--ip=IP_ADDRESS"`之后重启Docker服务即可生效

View File

@ -1,7 +1,11 @@
##创建一个点到点连接
默认Docker会将所有容器连接到由docker0提供的虚拟子网你也可以使用自己创建的网桥。但如果你想要2个特殊的容器之间可以直连通信而不用去配置复杂的主机网卡桥接
##示例:创建一个点到点连接
默认情况下Docker会将所有容器连接到由`docker0`提供的虚拟子网中
解决办法很简单创建一对接口把2个容器放到这对接口中配置成点到点链路类型。这2个容器就可以直接通信了。首先启动2个容器
用户有时候需要2个容器之间可以直连通信而不用通过主机网桥进行桥接。
解决办法很简单创建一对“peer”接口分别放到2个容器中配置成点到点链路类型即可。
首先启动2个容器
```
$ sudo docker run -i -t --rm --net=none base /bin/bash
root@1f1f4c1f931a:/#
@ -9,7 +13,7 @@ $ sudo docker run -i -t --rm --net=none base /bin/bash
root@12e343489d2f:/#
```
找到他们的进程号,然后创建他们的 namespace entries
找到进程号,然后创建网络名字空间的跟踪文件。
```
$ sudo docker inspect -f '{{.State.Pid}}' 1f1f4c1f931a
2989
@ -20,7 +24,7 @@ $ sudo ln -s /proc/2989/ns/net /var/run/netns/2989
$ sudo ln -s /proc/3004/ns/net /var/run/netns/3004
```
创建"peer"接口,然后配置路由
创建一对“peer”接口,然后配置路由
```
$ sudo ip link add A type veth peer name B
@ -34,7 +38,8 @@ $ 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`来创建点到点链路。
现在这2个容器就可以相互ping通并成功建立连接。点到点链路不需要子网和子网掩码。
还有一个办法就是创建一个只跟主机通信的容器,除非有特殊需求,你可以仅用`--icc=false`来限制主机间的通信。
此外,也可以不指定`--net=none`来创建点到点链路。这样容器还可以通过原先的网络来通信。
利用类似的办法,可以创建一个只跟主机通信的容器。但是一般情况下,更推荐使用`--icc=false`来关闭容器之间的通信。