容器和宿主机、容器之间以及夸主机容器如何通讯呢?这就需要使用到Docker网络。
在前面的介绍中我们在Dockerfile中通过EXPOSE参数来设置容器暴露的端口,让在docker run中使用-p来设置宿主机端口到容器端口的映射,这只是最简单的宿主机和容器通讯,同样使用宿主机IP:PORT方式可以让其他容器和该容器通讯,但是这样有个问题,首先应用程序需要对IP进行硬编码,其次容器每次重启IP都会变化,显然在生产环节中应该做到的尽可能的解耦,下面我们先看一下Docker网络的构成。
查看网络设置
启动docker服务就会产生一个docker0的虚拟网桥(多端口虚拟交换机)设备。veth*这个是启动一个容器就会产生一个这样的设备该设备与容器内的eth0虚拟网卡对应,这个veth*你可以理解为某个网桥上的接口,所以它只有MAC地址而没有IP地址,毕竟二层交换机网口是没有IP地址的,这个接口的另外一端就插在容器的网卡上,这个网卡有IP也有MAC(也可以理解为一根连在docker0上的网线,毕竟容器内的网卡和容器外的是一对,460就是容器外ID,459就是容器内网卡的ID)网桥在内核层连通了其他物理或者虚拟网卡。
下面看一下docker run命令中和网络有关的参数:
--dns=IP #指定DNS服务器 --dns-search=DOMAIN #指定搜索域 -h HOSTNAME #设置容器的主机名称 --link=容器名:别名 #启动该容器时与指定的容器进行链接,这样容器间可以通过名称来访问 -p #映射主机端口 --net=bridge #默认配置,为容器创建独立的网络命名空间,分配网卡、IP地址并通过veth接口 #将容器挂到docker0虚拟网桥上。 --net=none #为容器创建独立的网络命名空间,但不进行网络设置,容器没有网卡和IP --net=host #容器和宿主机共享网络设置,在容器中看到的网络信息都与宿主机一样,也就是 #不为容器创建独立的网络命名空间。 --net=user_defined_network #用户自行使用network创建一个网络,同一个网络内的容器彼 #此可见。类似于vmware中你可以创建多个网络通道比如vmnet1 #、vmnet2等。 --net=container:容器名称或者ID #表示该容器共享指定容器的网络命名空间。
容器的DNS配置:
容器中的主机名和DNS设置是通过/etc/resolv.conf、/etc/hostname和/etc/hosts三个文件来维护的,如下图:
/etc/resolv.conf文件在创建容器时,默认和宿主机上的一样;/etc/hosts文件默认只有一条容器自己的记录;/etc/hostname记录了容器自己的主机名。你可以直接修改容器的这三个文件,但是容器一旦重启就失效了。
所以在运行docker run的时候使用--dns=IP来在容器中的/etc/rsovle.conf文件之后添加额外的DNS服务器地址;
--hostname=HOSTNAME来指定主机名称。
容器的访问控制:
外部要想访问Docker容器或者Docker容器访问外部网络那么,下面的参数需要为1,表示启用IPV4的转发
sysctl net.ipv4.ip_forward
另外还需要注意一下几点:
另外默认情况下容器可以访问外部网络,但是外部网络无法访问容器
容器是通过SNAT方式出去并访问外部网络的
外部访问容器还取决于宿主机和容器内的防火墙,如果允许访问在在docker run中加入-P或者-p参数指定宿主机到容器的端口映射
流量进入到宿主机网卡,先进入到PREROUTING链中,然后将流量引入到DOCKER链,通过DNAT把32768流量修改地址为docker容器地址和端口。
libnetwork
这是Docker种的一个插件化网络功能。这个模型结构很简洁。它包括三个基于元素:
沙盒:代表一个容器,也可以理解为网络命名空间
接入点:代表可以挂载容器的接口,会分配IP地址
网络:可以连通多个接入点的子网
首先驱动注册自己的到网络控制器,网络控制器创建网络,然后在在网络上创建接入点,最后把容器连接到接入点上。删除过程则是反向操作,先把容器从接口上卸载,然后删除接入点,最后删除网络。目前支持的驱动类型有4种:
类型 | 说明 |
Null | 不提供网络服务,容器如果接入到这儿类型的网络接入点上,则没有网络连接 |
Briage | 网桥,根Docker0一样,使用传统的Linux网桥和Iptables来实现,通过NAT容器可以和宿主机以及宿主机以外进行通讯。 |
Overlay | 使用vxlan隧道实现跨主机通信,这个和软件定义网络中是一个概念,软件定义网络中也有多种实现方式其中就有vxlan,另外一个比较常用的是GRE,其实这些都是建立隧道。GRE是建立二层点到点隧道,vxlan可以看做是vlan的升级,因为传统vland通过打标签的方式来区分不同网络,但是标签有限最多4096个VLAN网络;而vxlan的标签是24位所以网络个数大大提升,另外它是L2 over UDP的形式,这样可以跨越三层。这种SDN在公有云平台使用较多因为规模大,多租户。 |
host | 主机模式,容器使用宿主机的网络命名空间,也就是使用宿主机网卡IP对外通信(顾名思义它们两个是同一IP)。但这种方式没有独立的网络协议栈,容器会和宿主机竞争使用网络协议栈,而且宿主机上的很多服务使用的端口在容器中就不能使用。 |
Remote | 扩展类型,预留给其他方案 |
通过上面的描述可以看到容器的这些网络类型和虚拟化里面的及其相似。下面说一下常用命令
列出网络:
docker network ls [options] # -f driver=NAME 列出特定驱动类型的网络
创建网络:
docker network create [options] NETWORK-NAME # -d 驱动类型 # --gateway IP 网关地址 # --internal 禁止外部对该网络访问 # --ip-range IP 分配IP地址的范围 # --subnet VALUE 设置子网掩码 # --ipam-driver STRING IP地址管理的插件类型 # --ipam-opt VALUE IP地址管理插件的选项 # --ipv6 是否支持IPV6 # --lable VALUE 为网络添加标签信息 # --o VALUE 网络驱动选项
看下面的例子
docker network create -d bridge --gateway 172.16.200.254 --subnet 172.16.200.0/24 vmnet01
我这里建立一个172.16.200.0的网络,并指定了默认网关的IP,使用网桥模式。
注意:如果不使用-d指定网络驱动类型,则默认为网桥。
查看内部细节
网桥驱动的一些选项:
选项 | 说明 |
com.docker.network.bridge.name | 网桥名称,就是在ifconfig中看到的,建议加上这个选项,否则网卡多了不容易识别。默认的docker0就是默认网络bridge的别名,针对docker0配置IP就相当于给虚拟交换机配置管理IP,同时这个IP也是容器网关地址。 |
com.docker.network.bridge.enable_ip_masquerade | 是否启用ip_masquerade,这是地址伪装,类似SNAT,但是有点区别,它用将发送数据的网卡IP替换源IP,容器每次启动IP地址都发生变化,那么如何确保我们用相同的地址或者域名访问这个容器呢?总不可能每次启动容器都修改iptables。使用ip_masquerade这样每次容器的IP发生变化,会自动获取IP并修改iptables。 |
com.docker.network.bridge.enable_icc | 该网桥是否允许容器间通信。默认网桥docker0的icc是true,所以也就是默认允许容器间通信。容器创建或者连接到网桥后就会使用网桥上配置的这些参数,如果该网桥的icc是false则,容器间无法通信,当仍然可以通过容器链接的方式设置容器间通信。 |
com.docker.network.bridge.host_binding_ipv4 | 绑定容器端口时的默认IP是什么,默认网桥docker0的配置是0.0.0.0也就是接受主机来自所有网络接口上的流量。 |
com.docker.network.driver.mtu | 设置容器的MTU |
com.docker.network.bridge.default_bridge | 是否为默认网桥,容器创建默认会使用的网桥,除非使用--net参数来设置连接到哪个网桥。 |
docker network -d -o "com.docker.network.bridge.name"="XX" |
注意:创建网桥网络如果不知道子网则会按照默认的172.16.0.0/16往后排17、18....。同时会为这个网桥设置一个IP通常是这个子网的第一个IP,那么ifconfig中看到的名字和创建网络是的名字不同,名字是随机的,所以这就是为什么需要使用上面的-o参数中的com.docker.network.bridge.name来指定一个名字。其实通过docker命令创建网络后台其实是创建的Linux网桥。
将容器连接到网络:
该命令是把正在运行的容器进行切换网络,如果是创建容器的话,需要在docker run命令中使用--net参数来指定网络,如果不指定,则使用默认网桥。
docker network connect [options] NETWORK-NAME CONTAINER # --ip IP 为容器手动分配一个地址,如果不指定则自动分配 # --alias VALUE 为容器添加一个别名 # --link VALUE 添加链接到另一个容器 # --link-local-ip VALUE 为容器添加一个链接地址
看下面的例子,我们就把一个正在运行的容器连接到我们上面创建的网络上。
docker network connect vmnet01 jspSrv01
连接到其他网络之后并不影响外网访问。
从网络中卸载容器:
docker network disconnect [options] NETWORK-NAME CONTAINER # -f 强制把容器从网络接口上卸载
看下面的例子,我们把刚才的容器从vmnet01上卸载
docker network disconnect -f vmnet01 jspSrv01
卸载后你发现还有网络信息,其实它是又回到默认网桥上了
删除一个网络:
当网络不存在接入点时,删除成功。
docker network rm NETWORK-NAME
查看网络内部:
docker network inspect [options] NETWORK-NAME # -f STRING 对指定字符进行格式化输出
跨主机网络通信:
跨主机通信的方式有三种:
容器使用host模式,直接使用host主机的IP,但是这样端口容易出现冲突。使用场景有限。
端口映射,也就是我们之前一直使用的,通过网桥模式的网络,通过DNAT来实现外部访问,但缺少灵活度。
直接路由,你可以使用默认的Docker0,也可以新建一个网桥。在Docker主机上添加一条静态路由实现。这个方案的问题是虽然跨了主机,但是不同主机上的容器必须连接到相同网桥这就意味着IP段一样,所以这就有很大局限性。
使用SDN方式,比如Flanneldocker1.9以后原生支持的Overlay网络或者Open vSwitch
下面我们就基于docker自带的overlay来实现,需要注意使用overlay网络有2中模式一个是swarm模式,一个是非swarm模式,在非swarm模式下使用则需要借助服务发现第三方组件。
docker实现跨主机通信需要的网络驱动类型为overlay,但是同时还需要一个键值型的服务发现和配置共享软件,比如Zookeeper、Doozerd、Etcd、Consul等。Zookeeper、Doozerd、Etcd在架构上很类似,只提供原始的键值存储,要求程序开发人员自己提供服务发现功能,而Consul则内置了服务发现,只要用户注册服务并通过DNS或HTTP接口执行服务发现即可,同时它还具有健康检查功能。我们这里使用Consul来作为服务发现工具。先说一下环境:
计算机名称 | IP | 功能 |
dockerothsrv | eth0:192.168.124.139 eth2:172.16.100.10 | Docker私有仓库、Consul服务 |
Docker01 | eth0:192.168.124.138 eth2:172.16.100.20 | Docker容器服务器 |
Docker02 | eth0:192.168.124.141 eth2:172.16.100.30 | Docker容器服务器 |
建立一个Consul服务:
这个你可以直接在系统中搭建,也可以运行一个容器来完成,我们这里通过运行容器来实现因为毕竟是讲跨主机通讯,我们在dockerothsrv服务器上安装
docker run -d -p 8500:8500 -h consul progrium/consul -server -bootstrap
配置docker主机:
这里就需要修改dockerd的配置文件,两台Docker服务器都增加,增加一些内容,如下图:
参数 | 说明 |
cluster-store | 指向键值存储的地址,在consul中注册,它得存储形式就是键值。 |
cluster-advertise | 这是一个主机网络接口或者IP地址加端口的组合,也就是本主机中dockerd实例在consul集群中的地址,远程dockerd服务连接本dockerd服务时所使用的值,也是Docker01和Docker02服务器互通的端口。它可以使用IP:PORT形式,也可以是INTERFACE:PORT形式。这个端口是你的dockerd服务以daemon形式运行时指定的端口。入上图tcp://0.0.0.0后面的5555。有些文档中你会看到2376或者2375这样的端口,这是因为Docker官方文档中写的是这个,因为dockerd默认是本地socket运行不接受网络远程连接,所以如果需要则需要指定-H tcp选项中的IP:PORT,2375是不加密端口,2376是加密端口。 |
重启dockerd服务
systemctl restart docker
查看一下
建立overlay驱动类型网络:
docker network create -d overlay oNet
你只需要在一台主机上建立,然后在另外一台主机上就可以看到。如下图:
说明:docker_gwbridge网络这是一个本地网桥,它会在2中情况下自动建立,一个是初始化或者加入一个swarm集群时;另外是
测试:
新建2个容器使用自动的oNet网络
Docker01
Docker02
查看一下网络详情
两个容器相互Ping一下看看
#关闭2台宿主机上的防火墙,否则通不了 systemctl stop firewalld
跨主机通信原理:
因为后面我们要查看网络命名空间,而docker的网络命名空间不是建立在/var/run/netns下,所以我们把docker的网络命名空间链接到那里,这样就可以通过ip命令进行查看
ln -s /var/run/docker/netns/ /var/run/netns #通过IP netns查看网络命名空间
通过命令查看容器内的网卡和宿主机上的哪个网卡是一对儿
ip addr docker exec aaa ethtool -S eth0/1
发现容器内的eth0也就是10.0.0.0网段的这个网卡没有关联到宿主机上的任何虚拟网卡,那它去哪里了?反而容器中eth2关联到496,而496走的是docker_gwbridge网桥,可这个网桥是172.18.0.0/16网段,那到底是如何和10.0.0.0网段通讯呢?我们查看一下命名空间?
至于是哪个命名空间你只能逐一查看,4-cd这个命名空间中是10.0.0.0网段,容器中的eth0关联到这个命名空间中的494上,而这个494则连接到了br0网桥上。其中vxlan1是VXLAN隧道端点,它是VXLAN网络的设备边缘,用于VXLAN报文非封包和解包,包括ARP请求报文和正常的VXLAN数据报文,封装好以后报文通过隧道向另一端的VTEP也就是VXLAN隧道端点发送,另一端的VTEP收到后解开报文。
通过下图可以看到这个命名空间的路由。
可以通过下面的命令查看vxlan1的VLANID是多少。需要知道每建立一个网络都有一个独立的网络命名空间。
ip netns exec 4-cd7565b196 ip -d link show vxlan1
两台主机上的vxlanX中的X不一样,但是同一个overlay网络中的vxlan的VLANID是一样的。看一下分解图:
通信过程是这样:
宿主机A的容器01 ping 10.0.0.4 通过该容器的eth0发送出去,并通过路由表得知发往br0,br0相当于虚拟交换机,如果目标主机在同一宿主机,则直接通过br0通信,如果不在则通过vxlan
br0收到请求会把请求交给vxlan1,这里你PING一下,然后通过下面的命令可以看出来
ip netns exec 网络命名空间 ip neigh
vxlan中保存有MAC地址表(docker守护进程通过gossip协议在concul数据库中学来的),并通过宿主机A的eth2发送出去
报文到达宿主机B,拆包发现是vxlan报文,获取IP,则交给它上面的vxlan设备(同一ID的设备)
宿主机B上vxlan拆包,交给br0,然后br0根据MAC表完成最后的投递。
overlay的不足:
由于overlay网络和宿主机默认网络不在同一网络下,所以为了解决和宿主机通讯问题,docker为宿主机和容器额外添加一个网卡,为宿主机添加的就是docker_gwbridge为容器添加的就是eth2,这两个网卡是一个IP段。但这样会造成使用上的不方便,容器间使用一套IP,外网访问容器使用另外一套IP
容器对外提供仍然需要通过端口绑定来实现,外界无法通过IP直接访问容器
overlay必须依赖docker进程和键值数据库来通信。
docker原生的overlay性能损耗比较大,生产中不建议使用
无论你采取什么方案,都需要面临一些问题:
跨主机通信(不同主机上的容器要可以相互通信而且还可以在整个IDC环境内被访问到)
容器漂移
跨主机容器IP分配,要避免IP冲突
网络性能对比:
由此看出Bridge的性能损耗在10%左右,docker原生overlay性能损耗最大。思科的Calico overlay性能几乎接近Bridge性能。
扩展只知识:
为什么需要服务发现?
写一个程序调用某个服务,如果这个服务在其他服务器上按照传统方法的话,我们要先得到那个服务器的IP以及那个服务所使用的端口,无论哪个服务所在服务器是物理机还是虚拟机都一样它们的IP地址相对静态,我们只需要把这个信息写在代码中或者代码程序所依赖的配置文件中即可。但现在的情况不一样了,尤其是云计算的微服务架构比如容器技术,服务运行在容器中,无论这个容器是在物理机还是在虚拟机总之这个获取这个容器的地址很麻烦,因为容器或者称该服务的网络地址是动态分配的,而且因为服务容量(运行该容器的实例个数)所以动态伸缩所以导致也无法用静态地址,那要想调用服务就需要使用新的方式进行服务发现。
服务发现有两种模式,客户端发现和服务器端发现,常用的是服务器端发现因为相对于客户端发现来说,服务器端发现把Service Registry抽离出来。Service Registry就是服务注册,服务实例会在这里进行注册,当不用的时候会注销。客户端通过负载均衡器向服务发起请求,负载均衡器在Service Registry中查找并将请求发送给可用服务实例。所有的服务发现工具中都有Service Registry这样一个角色,它是核心,它是一个K/V的数据库里面包含服务实例的网络地址。通常Service Registry必须具备高可用,而且要时刻保持更新。
服务发现的注册方式:有自注册和第三方注册
自注册:自己注册自己,其实就是服务实例自己去Service Registry中注册和注销,在必要的情况下还会发送心跳信息给Registry。优点是简单不需要其他组件、缺点是服务实例和Service Registry是耦合的,开发人员必须要为服务单独写注册代码。
第三方注册:顾名思义注册和注销不是由服务实例自己完成,是由一个叫做服务注册器来做,它对运行中的服务实例进行监听跟踪,当它发现服务可用时就会自动为其注册。优点是解耦,注册由统一组件完成,开发人员不用单独写注册器;缺点是部署环境中必须有注册器,如果没有就得自己来设置。
什么是Overlay?
overlay就是网络叠加,一个数据包或者帧封装在另外一个数据包或者帧里面。被封装的包转发到隧道另一端时进行解封装。Vxlan或者GRE/NVGRE都是一种叠加网络技术,属于2层overlay技术,思路就是将以太网报文放在某种隧道上传输,只是构建隧道的方式或者协议不同。隧道有2层和3层之分,2层常见的就是PPTP或者L2TP等。三层常见的GRE、IPsec等。
overlay是在现有网络之上构建虚拟网络,上层应用只和虚拟网络有关。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。