--昨夜西风凋碧树,独上高楼,望尽天涯路

Docker网络可以分为单个host上的容器网络和跨多个host的网络。

Docker安装时默认在host上创建了三个网络:

dockercompose host模式 docker --host_docker

下面分别来介绍。

none网络

挂在这个网络下的容器,除了lo没有任何网卡。容器创建时,可以通过--network=none指定none网络。这是一个封闭的网络,一些对安全性要高并且不需要联网的应用可以使用none网络。

dockercompose host模式 docker --host_云技术_02

 

host网络

连接host网络的容器共享Docker host的网络栈,容器的网络配置与host完全一样。通过--network=host指定使用host网络:   

dockercompose host模式 docker --host_云技术_03

进入容器可以看到host的所有网卡,hostname都是Docker host的。

使用host网络的最大好处就是性能,对网络传输效率要求高可以选择host网络。缺点是会牺牲灵活性:比如要考虑端口冲突问题(Docker host上已经使用的端口就不能再用了)。

Docker host的另一个用途可以让容器直接配置host网络。

bridge网络

Docker安装时会创建一个网桥docker0。这是默认使用的网桥,使用其他的网络必须用--network指定

dockercompose host模式 docker --host_容器_04

通过brctl show查看,没有容器启动时:

dockercompose host模式 docker --host_镜像_05

启动容器,发现有个新的网络接口被挂到了docker0下,veth403af2c是该容器的虚拟网卡:

dockercompose host模式 docker --host_容器_06

 进入容器,查看网络配置:

dockercompose host模式 docker --host_镜像_07

查看容器的网络配置发现容器有一块网卡eth0@if41,和挂到docker0下面的网卡不一样。why?

实际上这两块网卡是一对veth pair。beth pair是一种成对出现的特殊网络设备,可以理解为由一根虚拟网线连接起来的一对网卡,网卡的一头(eth0@if41)在容器中,另一头(veth403af2c)在网桥docker0,相当于也就是eth0@if41挂在了docker0上。

我们还可以看到IP为172.17.0.2/16,通过docker network inspect bridge查看bridge网络的配置信息,发现,bridge网络配置的subnet为172.17.0.0/16,并且网关是172.17.0.1(这个网关就是docker0)。

dockercompose host模式 docker --host_docker_08

dockercompose host模式 docker --host_镜像_09

 容器创建时,docker会自动从172.17.0.0/16中分配一个IP。

user-defined网络

除了自动创建的网络,我们也可以根据业务需要创建user-defined网络。

Docker提供三种user-defined网络驱动:bridge、overlay和macvlan。overlay和macvlan用于创建跨主机的网络,下一章讨论。

通过bridge驱动创建类似前面默认的bridge网络:

dockercompose host模式 docker --host_云技术_10

查看host的网络结构发现新增了一个网桥br-834b932f1cc,834b932f1cc是新建bridge网络my_net的短id。

产看my_net的配置信息:

dockercompose host模式 docker --host_Docker_11

172.17.0.0/16是Docker自动分配的IP网段,也可以通过--subnet和--gateway参数自己指定IP网段:

dockercompose host模式 docker --host_docker_12

dockercompose host模式 docker --host_docker_13

 创建新的bridge网络my_net2,网段为172.22.16.0/24,网关为172.22.16.1。网关在my_net2对应的网桥br-e4fae5e8f365上:

dockercompose host模式 docker --host_Docker_14

 通过--network指定使用网络my_net2:

dockercompose host模式 docker --host_docker_15

容器自动分配到IP 172.22.16.2,我们也可以通过--ip指定一个静态ip:

dockercompose host模式 docker --host_云技术_16

注:只有使用--subnet创建的网络才能指定静态IP。

my_net创建时没有指定--subnet,如果指定,报错如下: 

dockercompose host模式 docker --host_Docker_17

 现在我们的环境为:

dockercompose host模式 docker --host_容器_18

发现有两个busybox挂到my_net2上,进入到容器互相ping,发现可以互通:

dockercompose host模式 docker --host_容器_19

dockercompose host模式 docker --host_Docker_20

得出结论:同一网络中的容器、网关之间可以通信。

现在我们尝试一下my_net2与默认bridge网络是否可以通信,进入到busybox容器pinghttpd容器:

dockercompose host模式 docker --host_Docker_21

ping不通。如何实现不同网络之间的通信呢?

解决方案:在不同的网络之间加上路由。如果host上对每个网络都有一条路由,同时操作系统上打开了ip forwarding,host就集成了一个路由器,挂接在不同网桥上的网络就能够相互通信。

首先在docker host下通过ip r查看路由表:

dockercompose host模式 docker --host_镜像_22

172.17.0.0和172.22.16.0这两个网络的路由都定义好了。在查看是否启动ip forwarding:

dockercompose host模式 docker --host_Docker_23

 已启动,最后查看iptables,发现iptables DROP掉了drocker0和br-e4fae5e6f365之间的双向流量:

dockercompose host模式 docker --host_镜像_24

docker在设计上就是要隔离不同的network。现在知道原因之后,我们只需要给httpd容器添加一块my_net2的网卡,就可以实现通信:

dockercompose host模式 docker --host_镜像_25

 现在busybox可以访问httpd了:

dockercompose host模式 docker --host_镜像_26

容器间通信

容器之间的通信可以通过IP、Docker DNS Server或joined容器三种方式通信。

(1)IP通信

两个容器必须要有属于同一个网络的网卡才能通过IP交互。

具体做法就是在容器创建时通过--network指定相应的网络,或者通过docker network connect将现有的容器加入到指定网络(前面已经讲过了)。

(2)Docker DNS Server

IP访问的弊端是不够灵活。在部署应用之前可能无法确定IP,部署之后在指定要访问的IP会比较麻烦,对于这个问题,可以通过docker自带的DNS服务解决。

Docker1.0版本开始,docker daemon就实现了一个内嵌的DNS Server,使容器可以直接通过容器名通信(前提是要查到默认的容器名或者是使用--name自己命名)。

先通过docker run -it --network=my_net2 --name bb1 busybox,创建一个名为bb1的容器,然后创建bb2通过名称交互:

dockercompose host模式 docker --host_云技术_27

docker DNS只能子啊user-defined网络中使用。也就是说,默认的bridge网络是无法使用DNS的。

创建一个bb3,然后进入bb4 ping bb3 ,发现ping不通:

dockercompose host模式 docker --host_云技术_28

 (3)joined容器

joined容器可以使两个或者多个容器共享一个网络栈,共享网卡和配置信息,joined容器之间可以通过127.0.0.1直接通信。

创建一个httpd容器:

docker run -d -it --name=web1 httpd

创建busybox容器并通过--network=container:web1指定joined容器为web1:

dockercompose host模式 docker --host_docker_29

 查看busybox和web1的哇改名卡mac地址和IP地址一摸一样,它们共享了相同的网络栈。

dockercompose host模式 docker --host_docker_30

 busybox可以直接使用127.0.0.1访问web1的http服务:

dockercompose host模式 docker --host_容器_31

 joined容器非常适合以下场景:

1.不同的容器中的程序希望通过loopback高校快速的通信,比如Web Server与App Server。

2.希望监控其他容器的网络流量,比如运行在独立容器中的网络监控程序。

 容器访问外网

注:这里的外网指的是容器网络以外的网络,并非特指Internet

(1)容器访问外网

现在Docker host是可以访问外网的:

dockercompose host模式 docker --host_容器_32

进入busybox发现容器也可以访问外网:

dockercompose host模式 docker --host_Docker_33

上面的busybox位于docker0这个私有的bridge网络中(172.17.0.0/16),当busybox从容器向外ping时会发生什么呢?

首先查看Docker Host的iptables规则:

dockercompose host模式 docker --host_docker_34

发现,当网桥收到来自172.17.0.0/16网段的外出包,把它交给MASQUEREAD处理。而MASQUEREAD的处理方式是将包的源地址替换成host的地址发送出去,即做了一次网络地址转换(NAT)。

下面我们通过tcpdump查看地址是如何转换的。先查看docker host的路由表:

dockercompose host模式 docker --host_docker_35

默认路由通过ens33发出去,所以我们同时监控ens33和docker0上的icmp(ping)数据包。

当busybox ping www.baidu.com时,tcpdump输出如下:

dockercompose host模式 docker --host_云技术_36

dockercompose host模式 docker --host_容器_37

docker0收到busybox的ping包,源地址为容器IP172.17.0.2,之后交给MASQUEREAD处理。

dockercompose host模式 docker --host_云技术_38

ping包的源地址变成了ens33的IP 192.168.xx.xxx。

总结一下这个过程:

1.busybox发送ping包:172.17.0.2>www.baidu.com

2.docker0收到包,发现说发送到外网的,交给NAT处理

3.NAT将源地址换成ens33的IP:192.168.xx.xxx>www.baidu.com

4.ping包从ens33发送出去,到达www.baidu.com

(2)外网访问容器

docker是通过端口映射的方式另外网访问容器的。

docker可将容器对外提供服务的端口映射到host的某个端口,外网通过该端口访问容器。容器启动时通过-p参数映射端口。

容器启动后可通过docker ps或者docker port查看host映射的端口,发现容器的80端口映射到了host的32768端口。

dockercompose host模式 docker --host_Docker_39

之后通过<host ip>:<映射端口>访问web服务 :

curl hostip:32768

除了映射动态端口,也可以通过-p指定映射到host某个特定端口:

dockercompose host模式 docker --host_Docker_40

每一个映射的端口,host都会启动一个docker-proxy进程来处理访问容器的流量:

dockercompose host模式 docker --host_docker_41

以32768端口分析:

1.docker-proxy监听host的32768端口

2.当curl访问hostip:32768时,docker-proxy转发给容器172.17.0.2:80

3.httpd容器响应请求并返回结果