学习资料:公众号cloudman
文章目录
none和host网络的适用场景
本章开始讨论Docker网络。
我们会首先学习Docker提供的几种原生网络,以及如何创建自定义网络。然后探讨容器之间如何通信,以及容器与外界如何交互。
Docker网络从覆盖范围可分为单个host上的容器网络和跨多个host的网络。
Docker安装时会自动在host上创建三个网络,我们可用docker network ls
命令查看:
none网络
什么都没有的网络。挂在这个网络下的容器除了lo,没有其他任何网卡。容器创建时,可以通过--network-none
指定使用none网络。
这样的网络的应用场景是什么呢?
封闭意味着隔离,一些对安全性要求高并且不需要联网的应用可以使用none网络。比如某个容器的唯一用途是生成随机密码,就可以放到none网络中避免密码被窃取。
host
大部分容器其实是需要网络的,连接到host网络的容器共享Docker host的网络栈,容器的网络配置与host完全一样。可以通过--network=host
指定host网络
在容器中可以看到所有的网卡,并且连hostname也是host的。host网络的使用场景又是什么呢?
直接使用Docker host的网络最大的好处就是性能,如果容器对网络传输效率有较高要求,则可以选择host网络。当然不便之处就是**牺牲一些灵活性,**比如要考虑端口冲突问题,Docker host上已经使用的端口就不能再用了。
Docker host的另一个用途是让容器可以直接配置host网络。比如某些跨host的网络解决方案,其本身也是以容器方式运行的,这些方案需要对网络进行配置,比如管理iptables。
学容器必须懂bridge网络
Docker安装时会创建一个命名为docker0的linux bridge。如果不指定--network
,创建的容器默认都会挂到docker0
上。
当前docker0
上没有任何其他网络设备,我们创建一个容器看看有什么变化。
一个新的网络接口veth28c57df
被挂到了docker0
上,veth28c57df
就是新创建容器的虚拟网卡
下面看一下容器的网络配置。
容器有一个网卡eth0@if34
。大家可能会问了,为什么不是veth28c57df
呢?
实际上eth0@if34
和veth28c57df
是一对veth pair。veth pair是一种成对出现的特殊网络设备,可以把它们想象成由一根虚拟网线连接起来的一对网卡,网卡的一头(eth0@if34)在容器中,另一头(veth28c57df)挂在网桥docker0
上,其效果就是将eth0@if34
也挂在了docker0
上。
我们还看到eth0@if34
已经配置了IP172.17.0.2
,为什么是这个网段呢?让我们通过docker network inspect bridges
看一下bridge网络的配置信息:
原来bridge网络配置的subset就是172.17.0.0/16,并且网关是172.17.0.1、这个网关就是docker0
.
当前容器网络拓扑结构如图所示
容器创建时,docker会从172.17.0.0/16中分配一个IP,这里16位的掩码保证有足够多的IP可以供容器使用。
如何自定义容器网络?
除了none,host,bridge这三个自动创建的网络,用户也可以根据业务需要创建user-defined网络。
Docker提供三种user-defined网络驱动:bridge、overlay和macvlan。overlay和macvlan用于创建跨主机的网络。
我们可通过bridge驱动创建类似前面默认的bridge网络,例如:
查看一下当前host的网络结构变化:
新增了一个网桥br-eaed97dc9a77
,这里eaed97dc9a77
正好是新建的bridge网络my_net
的短ID,执行docker network inspect
查看一下my_net
的配置信息。
这里172.18.0.0/16是Docker自动分配的IP网段。
我们也可以自己指定IP网段。
只需在创建网段时指定--subnet
和--gateway
参数
这里我们创建了新的bridge网络my_net2
,网段为172.22.16.0/24,网关为172.22.16.1。与前面一样,网关在my_net1
对应的网桥br-5d863e9f78b6
上:
容器要使用新的网络,需要在启动时通过--network
指定:
容器分配到的IP为172.22.16.2。
到目前为止,容器的IP都是docker自动从subnet中分配,我们能否指定一个静态IP呢?
- 可以,通过
--ip
指定
注:只有使用--subnet
创建的网络才能指定静态IP
当前docker host的网络拓扑结构:
理解容器之间的连通性
上图展示了两个busybox容器都挂在my_net2上,应该能够互通,验证:
可见同一网络中的容器、网关之间都是可以通信的。
my_net2
与默认bridge网络能通信吗?
从拓扑图可知,两个网络属于不同的网桥,应该不能通信,我们通过实验验证一下,让 busybox 容器 ping httpd 容器:
确实 ping 不通,符合预期。
“等等!不同的网络如果加上路由应该就可以通信了吧?”我已经听到有读者在建议了。
这是一个非常非常好的想法。
确实,如果 host 上对每个网络的都有一条路由,同时操作系统上打开了 ip forwarding,host 就成了一个路由器,挂接在不同网桥上的网络就能够相互通信。下面我们来看看 docker host 满不满足这些条件呢?
ip r
查看 host 上的路由表:
\# ip r
......
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
172.22.16.0/24 dev br-5d863e9f78b6 proto kernel scope link src 172.22.16.1
......
172.17.0.0/16 和 172.22.16.0/24 两个网络的路由都定义好了。再看看 ip forwarding:
\# sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1
ip forwarding 也已经启用了。
条件都满足,为什么不能通行呢?
我们还得看看 iptables:
\# iptables-save
......
-A DOCKER-ISOLATION -i br-5d863e9f78b6 -o docker0 -j DROP
-A DOCKER-ISOLATION -i docker0 -o br-5d863e9f78b6 -j DROP
......
原因就在这里了:iptables DROP 掉了网桥 docker0 与 br-5d863e9f78b6 之间双向的流量。
从规则的命名DOCKER-ISOLATION
可知docker在设计上就是要隔离不同的network。
那么接下来的问题是:怎么才能容busybox与httpd通信呢?
答案是:为httpd容器添加一块my_net2的网卡。这个可以通过docker network connect
命令实现
我们在 httpd 容器中查看一下网络配置:
容器中增加了一个网卡 eth1,分配了 my_net2 的 IP 172.22.16.3。现在 busybox 应该能够访问 httpd 了,验证一下:
busybox 能够 ping 到 httpd,并且可以访问 httpd 的 web 服务。当前网络结构如图所示:
容器间通信的三种方式
IP通信
从上一节的例子可以得出这样一个结论:两个容器要能通信,必须要有属于同一个网络的网卡。
满足这个条件后,容器就可以通过IP交互了。具体做法是在容器创建时通过--network
指定相应的网络,或者通过docker network connect
将现有容器加入到指定网络。
Docker DNS sERVER
通过IP访问容器虽然满足了通信的要求,但还是不够灵活。因为我们在部署应用之前可能无法确定IP,部署之后再指定要访问的IP会比较麻烦。对于这个问题,可以通过docker自带的DNS服务解决。
从Docker 1.10版本开始,docker daemon实现了一个内嵌的DNS server,使容器可以直接通过“容器名”通信。方法很简单,只要在容器启动时用--name
为容器命名就可以了。
下面启动两个容器bbox1和bbox2:
docker run -it --network=my_net2 --name=bbox1 busybox
docker run -it --network=my_net2 --name=bbox2 busybox
然后,bbox2就可以直接ping到bbox1了:
使用docker DNS有一个限制:只能在user-defined网络中使用。也就是说,默认的bridge网络是无法使用DNS的。
joined容器
joined容器是另一种实现容器间通信的方式。
joined容器非常特别,它可以使两个或多个容器共享一个网络栈,共享网卡和配置信息,joined容器之间可以通过127.0.0.1直接通信。请看下面的例子:
先创建一个httpd容器,名字为web:
然后创建busybox容器并通过--network=container:web
指定joined容器为web:
请注意busybox容器中的网络配置信息,下面我们查看一下web的网络:
看!busybox和web1的网卡mac地址与IP完全一样,它们共享了相同的网络栈。busybox可以直接用127.0.0.1访问web1的http服务。
joined容器非常适合以下场景:
- 不同容器中的程序希望通过loopback高效快速地通信,比如web server与app server
- 希望监控其他容器的网络流量,比如运行在独立容器中的网络监控程序。
容器如何访问外部世界?
前面我们已经解决了容器间通信的问题,接下来讨论容器如何与外部世界通信。这里涉及两个方向:
- 容器访问外部世界
- 外部世界访问容器
容器访问外部世界
在我们当前的实验环境下,docker host是可以访问外网的。
我们看一下容器是否也能访问外网呢?
可见,容器默认就能访问外网。
注意:这里外网指的是容器网络以外的网络环境,并非特指internet。
在容器内访问外网是怎么实现的呢?
busybox位于docker0这个私有bridge网络中(172.17.0.0/16),当busybox从容器向外ping时,数据包是怎么到达baidu.com的呢?
这里的关键是NAT,即网络地址转换。将ping包的源地址换成了host的IP,从而保证数据包能够到达外网。下面用一张图来说明这个过程:
- busybox发送ping包:172.0.0.2>www.baidu.com
- docker0收到包,发现是发送到外网的,交给NAT处理
- NAT将源地址转换成enp0s3的IP:10.0.2.15>www.baidu.com
- ping包从enp0s3发送出去,到达www.bing.com
通过NAT,docker实现了容器对外网的访问。
外部世界如何访问容器?
上节我们学习了容器如何访问外部网络,今天讨论另一个方向:外部网络如何访问到容器?
答案是:端口映射。
docker可将容器对外提供服务的端口映射到host的某个端口,外网通过该端口访问容器。容器启动时通过-p
参数映射窗口。
容器启动后,可通过docker ps
或者docker port
查看host映射的端口。在上面的例子中,httpd容器的80端口被映射到host 8242上,这样就可以通过<host ip>:<32773>
访问容器的web服务了。
除了映射动态端口,也可以在-p
中指定映射到host某个特定的端口,例如可将80端口映射到host的8081端口:
每一个映射的端口,host都会启动一个docker-proxy
进程来处理访问容器的流量:
以0.0.0.0:32773->80/tcp为例分析整个过程:
- docker-proxy监听host的32773端口
- 当curl访问10.0.2.15:32773时,docker-proxy转发给容器172.17.0.2:80
- httpd容器相应请求并返回结果
本章小结
本章学习了Docker的三种网络:none,host和bridge并讨论了它们的不同使用场景;然后我们实践了创建自定义网络;最后详细讨论了如何实现容器与容器之间,容器与外部网络之间的通信。