① 四、Docker网络揭秘
Docker 之所以功能这么强大,其实就是充分利用了Linux Kernel的特性:NameSpace、CGroups、UnionFileSystem。通过这些特性实现了资源隔离、限制与分层等。本文这次就来揭晓Docker中的容器是如何做到网络互通的。
两台机器如果要实现通信,其实就是通过底层的网卡进行数据传输,每个网卡都有一个唯一的MAC地址,网卡又会绑定一个ip地址,只要两台机器的网络可以互通,那么这两台机器就可以进行通信。
想要实现通信,就得有两个同一网段的网卡,两个网卡必须是可以 ping 通的。
Docker在安装成功后,会在宿主机创建一个docker0网卡,这个网卡就是负责容器与宿主机之间通信的桥梁。
通过Docker创建一个容器之后,会在宿主机再创建一个网卡,也就是上面的 veth3543ea3@if7 ,容器内也会创建一个网卡。
一般成对的网卡,网卡组件名称后面的数字是连续的,比如宿主机的 @if7 和容器内的 @if8 ,正是这成对的网卡,才实现了容器与宿主机之间的通信。这其实是利用 Linux Kernel 的特性 NameSpace 实现的网卡隔离,不同NameSpace下的网卡是独立的,就像Java程序中的 package 一样。
从上面的例子中看到容器与宿主机之间的通信好像并不是通过docker0网卡实现的?
其实这只是单容器的状态,可能看不出docker0的作用。用图来表示一下单容器的网卡通信情况。
通过docker生成的 eth0 和 veth 两个网卡实现同一网段内的通信,而这个网卡又是桥接在docker0网卡上的。
再看下有多个容器的情况。
两个容器之间可以互相通信的原因就是因为docker0的存在,因为它们的网卡都是桥接在docker0上,所以也就有了和另一个容器通信的桥。
我们来验证一下是不是这样!
这种网络连接方法我们称为Bridge,这也是docker中默认的网络模式。可以通过命令查看docker中的网络模式:
通过 docker network ls 命令查看到,docker提供了3种网络模式,brige模式我们已经知道了,那 host 和 none 又是什么意思呢?不妨来验证一下:
这种模式只会创建一个本地的环路网卡,无法与其他容器或宿主机进行通信。
在创建自己的network之前先来解释一下为什么要创建新的network。
我们用一个例子来演示一下不同容器之间的通信。
容器之间通过 ip 是可以正常访问的,但是有没有这种情况:如果一个容器出问题了,我们重启之后它的ip变了,那是不是其他用到这个容器的项目配置是不是都得改。
有没有可能直接通过容器名称来访问呢?来验证一下:
发现并不能 ping 通,但是可以使用别的手段来达到这个目的。
通过上面这种方式就可以做到以容器名来 ping 通其他容器,其实它就跟windows系统中在 hosts 文件里加了个映射是一样的。
可以看到创建自定义的 network 自动帮我们实现了这个功能,而且使用自定义的 network 也方便管理,不同业务类型的容器可以指定不同的 network。
不同的 network ip网段也不一样,这样也可以增加单机中可以创建的容器的数量。
在创建一个容器的时候,一般都需要讲容器需要暴露的端口映射到物理主机的相同端口或其他端口,因为在外网环境下是不方便直接连接到容器的,所以需要通过映射端口的方式,让外网访问宿主机的映射端口来访问容器。
如果想建立多个容器,势必需要端口映射,以满足不同容器使用相同端口的情况。
以上这些内容都是在单容器进行操作,容器之间通信也只是通过 docker0 实现的桥接模式。
如果要实现多机之间的docker通信,其实还是通过网卡,只不过需要其他的技术来实现了。
本章节就不在演示,到后面的章节再来分析!
② docker网络怎么配置/
在你的宿主机启动docker的时候会多出一个 docker0 的虚拟网卡,然后容器都通过这个和外面通信。容器和docker宿主机之间是NAT的方式共享网络的,也就是说宿主机可以访问的资源,容器也是可以直接访问,你要使用apt-get,你就要保证你的宿主机可以上外网,否则免谈。
③ 云计算Docker网络使用
在容器中运行的网络应用,需要让外部可以访问这些应用,我们可以通过
参数来指定端口映射。
使用命令运行nginx,-P指令随机映射一个端口映射容器内部端口
我们使用 docker ps 或者 docker container ls 可以看到,宿主机的49154端口被映射到了容器的80端口,此时通过访问宿主机的49154就可以访问nginx默认页面
语法格式:
OPTIONS注解:
首先我们先将我们之前运行的容器全部删除
我们可以使用docker link链接容器,避免容器重启ip地址变化的问题
格式如下:
我们进入mycentos2容器
查看容器的hosts,发现了别名webcentos的ip地址
ping mycentos1 容器的ip别名 webcentos ,发现容器可以ping通
docker创建以后默认使用的是Linux的 桥连接 ,在宿主机上虚拟创建了一个容器 网桥 (docker0),使用 ip addr 查看
Docker每启动一个容器就会根据 docker0 的网段分配给容器一个IP地址,这个网桥docker0是所有容器的默认网关。因为宿主机内容器都使用网桥docker0,所以容器可以使用IP进行通信。
Docker就是很好的利用了Linux虚拟网络技术,在宿主机和容器内分别创建一个虚拟接口,实现他们彼此联通.
网络模式配置说明 bridge模式--net=bridge 默认使用 ,在docke的默认网桥docker0上为容器创建网络 none模式--net=none不配置网络,用于稍后进入容器,自行配置网络 container模式--net=container:name/id容器可以和应一个容器共享network namespace,k8s的pod使用此模式 hots模式--net=host容器和宿主机共享Network namespace 用户自定义--net=createnet用户使用network相关指令创建网络
Docker的container模式是指,创建容器的时候,使用--net=container:name/id,指定创建的容器和已经有的容器共享一个Network Namespace。
docker网络的host模式容器和宿主机共享Network namespace
默认选择bridge模式,容器启动后通过网桥docker0获取ip地址
我们首先使用 docker network 创建一个新的docker网络
-d指定docker的网络模型,有 bridge、overlay ,然后overlay网络模型使用的是Swarm mode。
使用ping来证明mycentos6容器和mycentos5可以相互链接
同理在mycentos5可以和mycentos5相互链接
④ Docker容器网络-实现篇
前面介绍了: Docker容器网络-基础篇
前文说到容器网络对Linux虚拟化技术的依赖,这一篇章我们将一探究竟,看看Docker究竟是怎么做的。通常,Linux容器的网络是被隔离在它自己的Network Namespace中,其中就包括:网卡(Network Interface)、回环设备(Loopback Device)、路由表(Routing Table)和iptables规则。对于一个进程来说,这些要素,就构成了它发起和响应网络请求的基本环境。
我们在执行 docker run -d --name xxx 之后,进入容器内部:
并执行 ifconfig:
我们看到一张叫eth0的网卡,它正是一个Veth Pair设备在容器的这一端。
我们再通过 route 查看该容器的路由表:
我们可以看到这个eth0是这个容器的默认路由设备。我们也可以通过第二条路由规则,看到所有对 169.254.1.1/16 网段的请求都会交由eth0来处理。
而Veth Pair 设备的另一端,则在宿主机上,我们同样也可以通过查看宿主机的网络设备来查看它:
在宿主机上,容器对应的Veth Pair设备是一张虚拟网卡,我们再用 brctl show 命令查看网桥:
可以清楚的看到Veth Pair的一端 vethd08be47 就插在 docker0 上。
我现在执行docker run 启动两个容器,就会发现docker0上插入两个容器的 Veth Pair的一端。如果我们在一个容器内部互相ping另外一个容器的IP地址,是不是也能ping通?
容器1:
容器2:
从一个容器ping另外一个容器:
我们看到,在一个容器内部ping另外一个容器的ip,是可以ping通的。也就意味着,这两个容器是可以互相通信的。
我们不妨结合前文时所说的,理解下为什么一个容器能访问另一个容器?先简单看如一幅图:
当在容器1里访问容器2的地址,这个时候目的IP地址会匹配到容器1的第二条路由规则,这条路由规则的Gateway是0.0.0.0,意味着这是一条直连规则,也就是说凡是匹配到这个路由规则的请求,会直接通过eth0网卡,通过二层网络发往目的主机。而要通过二层网络到达容器2,就需要127.17.0.3对应的MAC地址。所以,容器1的网络协议栈就需要通过eth0网卡来发送一个ARP广播,通过IP找到MAC地址。
所谓ARP(Address Resolution Protocol),就是通过三层IP地址找到二层的MAC地址的协议。这里说到的eth0,就是Veth Pair的一端,另一端则插在了宿主机的docker0网桥上。eth0这样的虚拟网卡插在docker0上,也就意味着eth0变成docker0网桥的“从设备”。从设备会降级成docker0设备的端口,而调用网络协议栈处理数据包的资格全部交给docker0网桥。
所以,在收到ARP请求之后,docker0就会扮演二层交换机的角色,把ARP广播发给其它插在docker0网桥的虚拟网卡上,这样,127.17.0.3就会收到这个广播,并把其MAC地址返回给容器1。有了这个MAC地址,容器1的eth0的网卡就可以把数据包发送出去。这个数据包会经过Veth Pair在宿主机的另一端veth26cf2cc,直接交给docker0。
docker0转发的过程,就是继续扮演二层交换机,docker0根据数据包的目标MAC地址,在CAM表查到对应的端口为veth8762ad2,然后把数据包发往这个端口。而这个端口,就是容器2的Veth Pair在宿主机的另一端,这样,数据包就进入了容器2的Network Namespace,最终容器2将响应(Ping)返回给容器1。在真实的数据传递中,Linux内核Netfilter/Iptables也会参与其中,这里不再赘述。
CAM就是交换机通过MAC地址学习维护端口和MAC地址的对应表
这里介绍的容器间的通信方式就是docker中最常见的bridge模式,当然此外还有host模式、container模式、none模式等,对其它模式有兴趣的可以去阅读相关资料。
好了,这里不禁问个问题,到目前为止只是单主机内部的容器间通信,那跨主机网络呢?在Docker默认配置下,一台宿主机的docker0网桥是无法和其它宿主机连通的,它们之间没有任何关联,所以这些网桥上的容器,自然就没办法多主机之间互相通信。但是无论怎么变化,道理都是一样的,如果我们创建一个公共的网桥,是不是集群中所有容器都可以通过这个公共网桥去连接?
当然在正常的情况下,节点与节点的通信往往可以通过NAT的方式,但是,这个在互联网发展的今天,在容器化环境下未必适用。例如在向注册中心注册实例的时候,肯定会携带IP,在正常物理机内的应用当然没有问题,但是容器化环境却未必,容器内的IP很可能就是上文所说的172.17.0.2,多个节点都会存在这个IP,大概率这个IP是冲突的。
如果我们想避免这个问题,就会携带宿主机的IP和映射的端口去注册。但是这又带来一个问题,即容器内的应用去意识到这是一个容器,而非物理机,当在容器内,应用需要去拿容器所在的物理机的IP,当在容器外,应用需要去拿当前物理机的IP。显然,这并不是一个很好的设计,这需要应用去配合配置。所以,基于此,我们肯定要寻找其他的容器网络解决方案。
在上图这种容器网络中,我们需要在我们已有的主机网络上,通过软件构建一个覆盖在多个主机之上,且能把所有容器连通的虚拟网络。这种就是Overlay Network(覆盖网络)。
关于这些具体的网络解决方案,例如Flannel、Calico等,我会在后续篇幅继续陈述。