From 33680174700d9e63a4fb3d2ffa82ec917e3e925c Mon Sep 17 00:00:00 2001 From: Baohua Yang Date: Fri, 5 Sep 2014 15:50:54 +0800 Subject: [PATCH] Add the advanced network chapter --- advanced_network/README.md | 9 +++++ advanced_network/bridge.md | 31 ++++++++++++++++ advanced_network/communication.md | 39 ++++++++++++++++++++ advanced_network/dns.md | 25 +++++++++++++ advanced_network/docker0.md | 31 ++++++++++++++++ advanced_network/example.md | 8 +++++ advanced_network/fast_config.md | 23 ++++++++++++ advanced_network/how_connect.md | 60 +++++++++++++++++++++++++++++++ advanced_network/port_mapping.md | 37 +++++++++++++++++++ advanced_network/ptp.md | 40 +++++++++++++++++++++ 10 files changed, 303 insertions(+) diff --git a/advanced_network/README.md b/advanced_network/README.md index e69de29..6d9077a 100644 --- a/advanced_network/README.md +++ b/advanced_network/README.md @@ -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默认的网络配置。 \ No newline at end of file diff --git a/advanced_network/bridge.md b/advanced_network/bridge.md index e69de29..1060c85 100644 --- a/advanced_network/bridge.md +++ b/advanced_network/bridge.md @@ -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: 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地址配置和路由信息。 \ No newline at end of file diff --git a/advanced_network/communication.md b/advanced_network/communication.md index e69de29..b834134 100644 --- a/advanced_network/communication.md +++ b/advanced_network/communication.md @@ -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 +``` \ No newline at end of file diff --git a/advanced_network/dns.md b/advanced_network/dns.md index e69de29..a464a3c 100644 --- a/advanced_network/dns.md +++ b/advanced_network/dns.md @@ -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来配置容器,它是默认配置。 \ No newline at end of file diff --git a/advanced_network/docker0.md b/advanced_network/docker0.md index e69de29..95c749a 100644 --- a/advanced_network/docker0.md +++ b/advanced_network/docker0.md @@ -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: 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,上文介绍过。 \ No newline at end of file diff --git a/advanced_network/example.md b/advanced_network/example.md index e69de29..9606cc6 100644 --- a/advanced_network/example.md +++ b/advanced_network/example.md @@ -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: +工具使用的网络命令跟我们之前看到非常相似。 \ No newline at end of file diff --git a/advanced_network/fast_config.md b/advanced_network/fast_config.md index e69de29..e80c274 100644 --- a/advanced_network/fast_config.md +++ b/advanced_network/fast_config.md @@ -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 — 映射容器端口到宿主主机 \ No newline at end of file diff --git a/advanced_network/how_connect.md b/advanced_network/how_connect.md index e69de29..dd165dc 100644 --- a/advanced_network/how_connect.md +++ b/advanced_network/how_connect.md @@ -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就可以完成一些可能带来危险的操作。 \ No newline at end of file diff --git a/advanced_network/port_mapping.md b/advanced_network/port_mapping.md index e69de29..a70959b 100644 --- a/advanced_network/port_mapping.md +++ b/advanced_network/port_mapping.md @@ -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. 记得重启服务。 \ No newline at end of file diff --git a/advanced_network/ptp.md b/advanced_network/ptp.md index e69de29..e6c45d2 100644 --- a/advanced_network/ptp.md +++ b/advanced_network/ptp.md @@ -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来限制主机间的通信。 \ No newline at end of file