Docker引入了一整套docker network子命令和跨主机网络支持。允许用户可以根据他们应用的拓扑结构创建虚拟网络并将容器接入其所对应的网络。
为了标准化网络的驱动开发步骤和支持多种网络驱动,Docker公司在libnetwork(网络库)中使用了CNM(Container Network Model)。
- Docker daemon通过调用libnetwork对外提供的API完成网络的创建和管理等功能。
- libnetwrok(网络库)使用了CNM来完成网络功能的提供。
- libnetwork(网络库)内置5种驱动,为libnetwork提供了不同类型的网络服务
- bridge 驱动
- host 驱动
- overlay 驱动
- remote 驱动
- null 驱动
- CNM:容器网络模型
- 定义了构建容器虚拟化网络的模型。
- 提供了可以用于开发多种网络驱动的标准化接口和组件。
Docker使用了Linux的Namespaces技术来进行资源隔离,如PID Namespace隔离进程,Mount Namespace 隔离文件系统,Network Namespace隔离网络等。
一个Network Namespace提供了一份独立的网络环境,包括网卡、路由、Iptable规则等都与其他的Network Namespace隔离。
一个Docker容器一般会分配一个独立的Network Namespace。也就是说不同的网络模式,都有自己的Network Namespace。
- 定义了构建容器虚拟化网络的模型。可以认为它是容器虚拟化网络协议,或者是Java中的上层接口
- 提供了可以用于开发多种网络驱动的标准化接口和组件。
- Libnetwork是Docker对CNM的一种实现,提供了Docker核心网络架构的全部功能
- 一个沙盒包含了一个容器网络栈的信息。
- 沙盒可以对容器的接口、路由和DNS设置等进行管理。
- 沙盒的实现可以是Linux netwrok namespace、FreeBSD jail或者类似的机制。
- 一个沙盒可以有多个端点和多个网络。
- 一个端点可以加入一个沙盒和一个网络。
- 端点的实现可以是veth pair、Open vSwitch内部端口或者相似的设备。
- 一个端点只可以属于一个网络并且只属于一个沙盒。
- 一个网络是一组可以直接互相联通的端点。
- 网络的实现可以是Linux bridge、VLAN等。
- 一个网络可以包含多个端点
libnetwork共有5种内置驱动:bridge驱动、host驱动、overlay驱动、remote驱动、null驱动
- Docker的默认驱动
- 使用这个驱动的时候,libnetwork将创建出来的Docker容器连接到Docker网桥上。
- 作为最常规的模式,bridge模式已经可以满足Docker容器最基本的使用需求了
3.1.1、优点
默认配置,不需要配
3.1.2、缺点
bridge 驱动 与外界通信使用NAT,增加了通信的复杂性,在复杂场景下使用会有诸多限制。
- 使用这种驱动的时候,libnetwork将不为Docker容器创建网络协议栈,即不会创建独立的network namespace。
- Docker容器和宿主机共同用一个network namespace,使用宿主机的网卡、IP和端口等信息。
- 除了网络方面,其他的如文件系统、进程列表等还是隔离的。
- 适用于对于容器集群规模不大的场景。
3.2.1、优点
- 可以直接使用宿主机的IP进行通信
- 很好地解决了容器与外界通信的地址转换问题,不存在虚拟化网络带来的额外性能负担
3.2.2、缺点
降低了容器与容器之间、容器与宿主机之间网络层面的隔离性,引起网络资源的竞争与冲突。
- 采用IETE标准的VXLAN方式,并且是VXLAN中被普遍认为最适合大规模的云计算虚拟化环境的SDN controller模式。
- 在使用过程中,需要一个额外的配置存储服务,例如Consul、etcd和zookeeper。还需要在启动Docker daemon的时候额外添加参数来指定所使用的配置存储服务地址。
这个驱动实际上并未做真正的网络服务实现,而是调用了用户自行实现的网络驱动插件,使libnetwork实现了驱动的可插件化,更好地满足了用户的多种需求。
用户只需要根据libnetwork提供的协议标准,实现其所要求的各个接口并向Docker daemon进行注册。
使用这种驱动的时候,Docker容器拥有自己的network namespace,但是并不为Docker容器进行任何网络配置。
也就是说,这个Docker容器除了network namespace自带的loopback网卡名,没有其他任何网卡、IP、路由等信息,需要用户为Docker容器添加网卡、配置IP等。
这种模式如果不进行特定的配置是无法正常使用的,但是优点也非常明显,它给了用户最大的自由度来自定义容器的网络环境。
在上图示例中,使用Docker默认的bridge驱动进行演示。
执行前,先清空所有docker容器和网络
创建2个网络
注意:Docker daemon会默认创建3种网络
- bridge
- host
- none
4.2.1、验证c1,c2,c3的网络
因为c1和c2都在backend网络中,所以两者可以连通。
因为c3和c2不在一个网络中,所以两个容器之间不能连通。
4.2.2、进入c2容器中查看此容器中的网卡及配置情况
可以看到,容器中只有一块以太网卡,其名称为eth0,并且配置了和网桥backend同在一个IP段的IP地址,这个网卡就是CNM模型中的端点
4.3.1、进入c2容器中查看此容器中的网卡及配置情况
发现多了一块名为eth1的以太网卡,并且其IP和网桥frontend同在一个IP段。
4.3.2、测试c2与c3的连通性
可以发现两者已经连通。
执行前,先清空所有docker容器和网络
此条路由表示所有目的IP地址为172.17.0.0/16的数据包从docker0网卡转发。
可以发现c4的默认网关正是宿主机的docker0网卡,通过测试, c4可以顺利访问外网和宿主机网络,因此表明c4的eth0网卡与宿主机的docker0网卡是相互连通的。
发现多了一块以"veth"开头的网卡,如vethe06c1bd
我们可以大胆猜测这块网卡肯定是veth设备了,而veth pair总是成对出现的。
veth pair通常用来连接两个network namespace
1. 一个(eth0)用来连接 容器c4
2. 一个(veth60b16bd)用来连接 宿主机的docker0
通过上面可以看出docker0就不只是一个简单的网卡设备了,而是一个网桥。
下图为Docker默认网络模式(bridge模式)下的网络环境拓扑图,创建了docker0网桥,并以eth pair连接各容器的网络,容器中的数据通过docker0网桥转发到eth0网卡上。
这里的网桥概念等同于交换机,为连在其上的设备转发数据帧。
网桥上的veth网卡设备相当于交换机上的端口,可以将多个容器或虚拟机连接在上面,这些端口工作在二层,所以是不需要配置IP信息的。
图中docker0网桥就为连在其上的容器转发数据帧,使得同一台宿主机上的Docker容器之间可以相互通信。
大家应该注意到docker0既然是二层设备,它上面怎么设置了IP呢?
docker0是普通的linux网桥,它是可以在上面配置IP的,可以认为其内部有一个可以用于配置IP信息的网卡接口(如同每一个Open vSwitch网桥都有一个同名的内部接口一样)。
在Docker的桥接网络模式中,docker0的IP地址作为容器的默认网关地址。
查看网桥
docker0网桥是在Docker daemon启动时自动创建的,其IP默认为172.17.0.1/16,之后创建的Docker容器都会在docker0子网的范围内选取一个未占用的IP使用,并连接到docker0网桥上。
Docker安装完成后,将默认在宿主机系统上增加一些iptables规则,以用于Docker容器和容器之间以及和外界的通信.
5.2.1、查看iptables规则
其中nat表中的POSTROUTING链有这么一条规则
参数说明
含义
将源地址为172.17.0.0/16的数据包(即Docker容器发出的数据),当不是从docker0网卡发出时做SNAT。
这样一来,从Docker容器访问外网的流量,在外部看来就是从宿主机上发出的,外部感觉不到Docker容器的存在。
那么,外界想到访问Docker容器的服务时该怎么办呢?我们启动一个简单的web服务容器,观察iptables规则有何变化。
5.3.1、启动一个 tomcat容器,将其8080端口映射到宿主机上的8080端口上
5.3.2、 查看iptabels规则,省略部分无用信息
可以看到,在nat、filter的Docker链中分别增加了一条规则,这两条规则将访问宿主机8080端口的流量转发到了172.17.0.3的8080端口上(真正提供服务的Docker容器IP和端口),所以外界访问Docker容器是通过iptables做DNAT(目的地址转换)实现的。
此外,Docker的forward规则默认允许所有的外部IP访问容器,可以通过在filter的DOCKER链上添加规则来对外部的IP访问做出限制,比如只允许源IP 192.168.0.0/16的数据包访问容器,需要添加如下规则:
不仅仅是与外界间通信,Docker容器之间互个通信也受到iptables规则限制。同一台宿主机上的Docker容器默认都连在docker0网桥上,它们属于一个子网,这是满足相互通信的第一步。
同时,Docker daemon会在filter的FORWARD链中增加一条ACCEPT的规则(–icc=true)
这是满足相互通信的第二步。
当Docker datemon启动参数–icc(icc参数表示是否允许容器间相互通信)设置为false时,以上规则会被设置为DROP,Docker容器间的相互通信就被禁止,这种情况下,想让两个容器通信就需要在docker run时使用 --link选项。
在Docker容器和外界通信的过程中,还涉及了数据包在多个网卡间的转发(如从docker0网卡转发到宿主机ens160网卡),这需要内核将ip-forward功能打开,即将ip_forward系统参数设1。Docker daemon启动的时候默认会将其设为1(–ip-forward=true),也可以通过命令手动设置:
同一个Docker镜像可以启动很多Docker容器,通过查看,它们的主机名并不一样,也即是说主机名并非是被写入镜像中的。
实际上容器中/etc/目录下有3个文件是容器启动后被虚拟文件覆盖的,分别是
在容器中运行mount命令可以查看:
这样能解决主机名的问题,同时也能让DNS及时更新(改变resolv.conf)。由于这些文件的维护方法随着Docker版本演进而不断变化,因此尽量不修改这些文件,而是通过Docker提供的参数进行相关设置,配置方式如下:
- -h HOSTNAME 或 --hostname=HOSTNAME
设置容器的主机名,此名称会写在/etc/hostname和/etc/hosts文件中,也会在容器的bash提示符看到。
- –dns=IP_ADDRESS…:
为容器配置DNS,写在/etc/resolv.conf中。
可以在docker daemon 启动的时候设置
也可以在docker run时设置
默认为8.8.8或8.8.4.4。
注意:对以上3个文件的修改不会被docker commit保存,也就是不会保存在镜像中,重启容器也会导致修改失效。另外,在不稳定的网络环境下使用需要特别注意DNS的设置。
- Host
- Bridge
- None
- Container
- 自定义网络
- 使用这种驱动的时候,libnetwork将不为Docker容器创建网络协议栈,即不会创建独立的network namespace。
- Docker容器和宿主机共同用一个network namespace,使用宿主机的网卡、IP和端口等信息。
- 除了网络方面,其他的如文件系统、进程列表等还是隔离的。
- 适用于对于容器集群规模不大的场景。
7.2.1、验证–容器使用宿主机的网络
宿主机网络
容器网络
- 为每一个容器分配、设置IP,并将容器连接到一个docker0虚拟网桥,通过docker0网桥以及Iptables nat表配置与宿主机通信。
- bridge网络代表docker0所有Docker安装中存在的网络
- 容器的默认网络
使用这种驱动的时候,Docker容器拥有自己的network namespace,但是并不为Docker容器进行任何网络配置。
也就是说,这个Docker容器除了network namespace自带的loopback网卡名,没有其他任何网卡、IP、路由等信息,需要用户为Docker容器添加网卡、配置IP等。
这种模式如果不进行特定的配置是无法正常使用的,但是优点也非常明显,它给了用户最大的自由度来自定义容器的网络环境。
- 新创建的容器和已经存在的一个容器共享一个Network Namespace 。
- 新创建的容器不会创建自己的网卡,配置自己的IP,而是和一个指定的容器共享IP、端口范围等。
- 两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。
- 两个容器的进程可以通过lo网卡设备通信。
自己定义网络,如