Docker的本地网络实现其实就是利用了Linux上的网络命名空间和虚拟网络设备(特别是veth pair)。 git
直观上看,要实现网络通讯,机器须要至少一个网络接口(物理接口或虚拟接口)与外界相通,并能够收发数据包;此外,若是不一样子网之间要进行通讯,须要额外的路由机制。 github
Docker中的网络接口默认都是虚拟的接口。虚拟接口的最大优点就是转发效率极高。这是由于Linux经过在内核中进行数据复制来实现虚拟接口之间的数据转发,即发送接口的发送缓存中的数据包将被直接复制到接收接口的接收缓存中,而无需经过外部物理网络设备进行交换。对于本地系统和容器内系统来看,虚拟接口跟一个正常的以太网卡相比并没有区别,只是它速度要快得多。 web
Docker容器网络就很好地利用了Linux虚拟网络技术,在本地主机和容器内分别建立一个虚拟接口,并让它们彼此连通(这样的一对接口叫作veth pair)。 docker
通常状况下,Docker建立一个容器的时候,会具体执行以下操做: centos
1.建立一对虚拟接口,分别放到本地主机和新容器的命名空间中; 缓存
2.本地主机一端的虚拟接口链接到默认的docker0网桥或指定网桥上,并具备一个以veth开头的惟一名字,如veth1234; 服务器
3.容器一端的虚拟接口将放到新建立的容器中,并修更名字做为eth0。这个接口只在容器的命名空间可见; 网络
4.从网桥可用地址段中获取一个空闲地址分配给容器的eth0(例如172.17.0.2/16),并配置默认路由网关为docker0网卡的内部接口docker0的IP地址(例如172.17.42.1/16)。 app
完成这些以后,容器就可使用它所能看到的eth0虚拟网卡来链接其余容器和访问外部网络。用户也能够经过docker network命令来手动管理网络。 工具
安装Docker时,它会自动建立三个网络,bridge(建立容器默认链接到此网络,也就是在不使用--network参数时)、 none 、host。还有之后一种自定义模式,自定义模式有三种:bridge、overlay、macvlan。
host:容器将不会虚拟出本身的网卡,配置本身的IP等,而是使用宿主机的IP和端口。
Container:建立的容器不会建立本身的网卡,配置本身的IP,而是和一个指定的容器共享IP、端口范围。
None:该模式关闭了容器的网络功能。
Bridge:此模式会为每个容器分配、设置IP等,并将容器链接到一个docker0虚拟网桥,经过docker0网桥以及Iptables nat表配置与宿主机通讯。
经过docker network ls能够查看docker网络:
当容器运行分别以none、host、bridge这三种模式的时候以下:
bridge用法也就是容器默认使用,也就是上面基本原理所讲到的。
建立network
# docker network create -d bridge --ip-range=192.168.1.0/24 --gateway=192.168.1.1 --subnet=192.168.1.0/24 bridge2
# docker network ls
建立两个容器指定ip并指定network
# docker run -it --network=bridge2 --ip=192.168.1.3 busybox
# docker run -it --network=bridge2 --ip=192.168.1.4 busybox
在使用docker run命令启动容器的时候,能够经过--net参数来指定容器的网络配置。
有5个可选值bridge、none、container、host和用户定义的网络:
--net=bridge:默认值,在Docker网桥docker0上为容器建立新的网络栈。
--net=none:让Docker将新容器放到隔离的网络栈中,可是不进行网络配置。以后,用户能够自行进行配置。
--net=container:NAME_or_ID:让Docker将新建容器的进程放到一个已存在容器的网络栈中,新容器进程有本身的文件系统、进程列表和资源限制,但会和已存在的容器共享IP地址和端口等网络资源,二者进程能够直接经过lo环回接口通讯。
--net=host:告诉Docker不要将容器网络放到隔离的命名空间中,即不要容器化容器内的网络。此时容器使用本地主机的网络,它拥有彻底的本地主机接口访问权限。容器进程能够跟主机其余root进程同样打开低范围的端口,能够访问本地网络服务,好比D-bus,还可让容器作一些影响整个主机系统的事情,好比重启主机。所以使用这个选项的时候要很是当心。若是进一步的使用--privileged=true参数,容器甚至会被容许直接配置主机的网络栈。
--net=user_defined_network:用户自行用network相关命令建立一个网络,经过这种方式将容器链接到指定的已建立网络上去。
有些命令选项只有在Docker服务启动的时候才能配置,并且不能立刻生效:
-b BRIDGE or--bridge=BRIDGE——指定容器挂载的网桥;
--bip=CIDR——定制docker0的掩码;
-H SOCKET...or--host=SOCKET...——Docker服务端接收命令的通道;
--icc=true|false——是否支持容器之间进行通讯;
--ip-forward=true|false——启用net.ipv4.ip_forward,即打开转发功能;
--iptables=true|false——禁止Docker添加iptables规则;
--mtu=BYTES——容器网络中的MTU。
下面2个命令选项既能够在启动服务时指定,也能够docker run时候指定。在Docker服务启动的时候指定则会成为默认值,后续执行docker run时能够覆盖设置的默认值。
--dns=IP_ADDRESS...——使用指定的DNS服务器;
--dns-search=DOMAIN...——指定DNS搜索域。
最后这些选项只能在docker run执行时使用,由于它是针对容器的特性内容:
-h HOSTNAME or--hostname=HOSTNAME——配置容器主机名;
--link=CONTAINER_NAME:ALIAS——添加到另外一个容器的链接;
--net=bridge|none|container:NAME_or_ID|host|user_defined_network——配置容器的桥接模式;
-p SPEC or--publish=SPEC——映射容器端口到宿主主机;
-P or--publish-all=true|false——映射容器全部端口到宿主主机。
Docker支持自定义容器的主机名和DNS配置。
1.相关配置文件
容器中主机名和DNS配置信息都是经过三个系统配置文件来维护的:/etc/resolv.conf、/etc/hostname和/etc/hosts。
启动一个容器,在容器中使用mount命令能够看到这三个文件挂载信息:
/etc/resolv.conf文件在建立容器时候,默认会与宿主机/etc/resolv.conf文件内容保持一致
/etc/hosts文件中默认只记录了容器自身的一些地址和名称:
/etc/hostname文件则记录了容器的主机名。
2.容器内修改配置文件
Docker 1.2.0开始支持在运行中的容器里直接编辑/etc/hosts,/etc/hostname和/etc/resolve.conf文件。可是这些修改是临时的,只在运行的容器中保留,容器终止或重启后并不会被保存下来。也不会被docker commit提交。
3.经过参数指定
若是用户想要自定义容器的配置,能够在建立或启动容器时利用下面的参数指定:
1)指定主机名-h HOSTNAME或者--hostname=HOSTNAME。设定容器的主机名,它会被写到容器内的/etc/hostname和/etc/hosts。但这个主机名只有容器内能看到,在容器外部则看不到,既不会在docker ps中显示,也不会在其余的容器的/etc/hosts看到。
2)记录其余容器主机名--link=CONTAINER_NAME:ALIAS。选项会在建立容器的时候,添加一个所链接容器的主机名到容器内/etc/hosts文件中。这样,新建立容器能够直接使用主机名来与所链接容器通讯。
3)指定DNS服务器--dns=IP_ADDRESS。添加DNS服务器到容器的/etc/resolv.conf中,容器会用指定的服务器来解析全部不在/etc/hosts中的主机名。
4)指定DNS搜索域--dns-search=DOMAIN。设定容器的搜索域,当设定搜索域为.example.com时,在搜索一个名为host的主机时,DNS不只搜索host,还会搜索host.example.com。
容器之间可经过 IP,Docker DNS Server 和joined 容器三种方式通讯。
IP 通讯
两个容器要能通讯,必需要有属于同一个网络的网卡。知足这个条件后,容器就能够经过 IP 交互了。具体作法是在容器建立时经过 --network 指定相应的网络,或者经过 docker network connect 将现有容器加入到指定网络。
Docker DNS Server
经过 IP 访问容器虽然知足了通讯的需求,但仍是不够灵活。由于咱们在部署应用以前可能没法肯定 IP,部署以后再指定要访问的 IP 会比较麻烦。对于这个问题,能够经过 docker 自带的 DNS 服务解决。
从 Docker 1.10 版本开始,docker daemon 实现了一个内嵌的 DNS server,使容器能够直接经过"容器名"通讯。方法很简单,只要在启动时用 --name 为容器命名就能够了。
下面启动两个容器 box1 和 box2,而且在上面定义的网络模式bridge2中:
docker run -it --network=bridge2 --name box1 busybox
docker run -it --network=bridge2 --name box2 busybox
box2和 box1能够互ping通
使用 docker DNS 有个限制:只能在 user-defined 网络中使用。也就是说,默认的 bridge 网络是没法使用 DNS 的。
joined 容器
joined 容器是另外一种实现容器间通讯的方式。它可使两个或多个容器共享一个网络栈,共享网卡和配置信息,joined 容器之间能够经过 127.0.0.1 直接通讯。例:
先建立一个http容器,名字为 box1
docker run -it --name box1 http
而后建立 busybox 容器并经过 --network=container:box1 指定 jointed 容器为 box1:
docker run -it --network=container:box1 busybox
box1 的网络:
busybox 和 box1 的网卡 mac 地址与 IP 彻底同样,它们共享了相同的网络栈。busybox 能够直接用 127.0.0.1 访问 box1 的 http 服务。
joined 容器很是适合如下场景:
不一样容器中的程序但愿经过 loopback 高效快速地通讯,好比 web server 与 app server。
但愿监控其余容器的网络流量,好比运行在独立容器中的网络监控程序。
容器的访问控制主要经过Linux上的iptables防火墙软件来进行管理和实现。iptables是Linux系统流行的防火墙软件,在大部分发行版中都自带。
咱们知道容器默认指定了网关为docker0网桥上的docker0内部接口。docker0内部接口同时也是宿主机的一个本地接口。所以,容器默认状况下是能够访问到宿主机本地的。更进一步,容器要想经过宿主机访问到外部网络,须要宿主机进行转发。
若是为0,则没有开启转发,则须要手动打开:
# sysctl -w net.ipv4.ip_forward=1
更简单的,在启动Docker服务的时候设定--ip-forward=true,Docker服务会自动打开宿主机系统的转发服务。
容器容许外部访问,能够在docker run时候经过-p或-P参数来启用。
无论用那种办法,其实也是在本地的iptable的nat表中添加相应的规则,将访问外部IP地址的网包进行目标地址DNAT,将目标地址修改成容器的IP地址。
以一个开放80端口的Web容器为例,使用-P时,会自动映射本地49000~49900范文内的端口随机端口到容器的80端口:
能够看到,nat表中涉及两条链,PREROUTING链负责包到达网络接口时,改写其目的地址。其中规则将全部流量都扔到DOCKER链。而DOCKER链中将全部不是从docker0进来的网包(意味着不是本地主机产生),将目标端口为49153的,修改目标地址为172.17.0.2,目标端口修改成80。
使用-p 80:80时,与上面相似,只是本地端口也为80:
有两点须要注意:
这里的规则映射了0.0.0.0,意味着将接受主机来自全部网络接口上的流量。用户能够经过-p IP:host_port:container_port或-p IP::port来指定绑定的外部网络接口,以制定更严格的访问规则;
若是但愿映射永久绑定到某个固定的IP地址,能够在Docker配置文件/etc/default/docker中指定DOCKER_OPTS="--ip=IP_ADDRESS",以后重启Docker服务便可生效。
Docker服务默认会建立一个名称为docker0的Linux网桥(其上有一个docker0内部接口),它在内核层连通了其余的物理或虚拟网卡,这就将全部容器和本地主机都放到同一个物理网络。用户使用Docker建立多个自定义网络时可能会出现多个容器网桥。
Docker默认指定了docker0接口的IP地址和子网掩码,让主机和容器之间能够经过网桥相互通讯,它还给出了MTU(接口容许接收的最大传输单元),一般是1500字节,或宿主主机网络路由上支持的默认值。这些值均可以在服务启动的时候进行配置:
--bip=CIDR——IP地址加掩码格式,例如192.168.1.5/24;
--mtu=BYTES——覆盖默认的Docker mtu配置。
也能够在配置文件中配置OPTIONS,而后重启服务。
因为目前Docker网桥是Linux网桥,用户可使用brctl show来查看网桥和端口链接信息。
brctl命令若是系统中没有自带,可使用 yum install bridge-utils安装(centos)
每次建立一个新容器的时候,Docker从可用的地址段中选择一个空闲的IP地址分配给容器的eth0端口。而且使用本地主机上docker0接口的IP做为容器的默认网关。
目前,Docker不支持在启动容器时候指定IP地址。实际上,Linux网桥自身功能已经十分完备,也能够替换为OpenvSwitch等功能更强大的网桥实现。
在上述docker网络模式已经讲到,使用docker network便可实现。
Docker默认使用的是Linux自带的网桥实现,实际上,OpenvSwitch项目做为一个成熟的虚拟交换机实现,具有更丰富的功能。未来会有愈来愈多的容器支持OpenvSwitch做为底层网桥实现。
1.安装Docker(centos 7)
安装并启动服务。默认Docker服务会建立一个名为docker0的Linux网桥,做为链接容器的本地网桥。
2.安装OpenvSwitch
# yum install -y openvswitch
测试添加一个网桥br0并查看:
# ovs-vsctl add-br br0
# ovs-vsctl show
3.配置容器链接到OpenvSwitch网桥
目前OpenvSwitch网桥还不能直接支持挂载容器,须要手动在OpenvSwitch网桥上建立虚拟网口并挂载到容器中。
(1)建立无网口容器
启动一个容器,并指定不建立网络,后面咱们手动添加网络。较新版本的Docker默认不容许在容器内修改网络配置,须要在run的时候指定参数--privileged=true:
记住这里容器的id。
(2)手动为容器添加网络
下载OpenvSwitch项目提供的支持Docker容器的辅助脚本ovs-docker:
# wget https://github.com/openvswitch/ovs/raw/master/utilities/ovs-docker
# chmod a+x ovs-docker
为容器添加网卡,并挂载到br0上,命令为:
#./ovs-docker add-port br0 eth0 00b9028e14aa --ipaddress=10.0.0.2/16
添加成功后,在容器内查看网络信息,多了一个新添加的网卡eth0,对应添加的IP地址。
在容器外,配置OpenvSwitch的网桥br0内部接口地址为10.0.1.2/16(只要与所挂载容器IP在同一个子网内便可):
# ifconfig br0 10.0.1.2/16
(3)测试连通
通过上面步骤,容器已经链接到了网桥br0上了,拓扑以下所示:
容器(10.0.7.2/16)<-->br0网桥<-->br0内部端口(10.0.1.2/16)
此时,在容器内就能够测试是否连通到网桥br0上了:
在容器内也能够配置默认网关为br0接口地址:
route add default gw GWIP
删除该接口的命令为:
# ./ovs-docker del-port br0 eth0<container_id>
删除名为br0的网桥:ovs-vsctl del-br br0
实际上,Docker社区也已经讨论对OpenvSwitch进行原生支持了。在Docker原生支持OpenvSwitch以前,用户能够经过编写脚本或更高级的工具来让这一过程自动化。