• docker在安装后会默认生成三种网络,none、bridge及host

长文讲述Docker的4大网络模型(host、container、none、bridge)_host、container

  • 我们在使用docker run创建Docker容器时,可以用--net选项指定容器的网络模式,Docker有以下4种网络模式:
    • host模式,使用--net=host指定
    • container模式,使用--net=container:NAME_or_ID指定
    • none模式,使用--net=none指定
    • bridge模式,使用--net=bridge指定,默认设置
一、host模式
  • 众所周知,Docker使用了Linux的Namespaces技术来进行资源隔离,如PID Namespace隔离进程,Mount Namespace隔离文件系统,Network Namespace隔离网络等。一个Network Namespace提供了一份独立的网络环境,包括网卡、路由、Iptable规则等都与其他的Network Namespace隔离。一个Docker容器一般会分配一个独立的Network Namespace
  • 但如果启动容器的时候使用host模式,那么这个容器将不会获得一个独立的Network Namespace,而是和宿主机共用一个Network Namespace。容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口
  • 例如,我们在10.10.101.105/24的机器上用host模式启动一个含有web应用的Docker容器,监听tcp80端口。当我们在容器中执行任何类似ifconfig命令查看网络环境时,看到的都是宿主机上的信息。而外界访问容器中的应用,则直接使用10.10.101.105:80即可,不用任何NAT转换,就如直接跑在宿主机中一样。但是,容器的其他方面,如文件系统、进程列表等还是和宿主机隔离的
  • 例如,下面运行一个host模式容器:
docker run -it --network=host busybox
二、container模式
  • 在理解了host模式后,这个模式也就好理解了。这个模式指定新创建的容器和已经存在的一个容器共享一个Network Namespace,而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的IP,而是和一个指定的容器共享IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。两个容器的进程可以通过lo网卡设备通信
三、none模式
  • 这个模式和前两个不同。在这种模式下,Docker容器拥有自己的Network Namespace,但是,并不为Docker容器进行任何网络配置。也就是说,这个Docker容器没有网卡、IP、路由等信息。需要我们自己为Docker容器添加网卡、配置IP等
  • 例如,下面运行一个none模式容器:
docker run -it --network=none busybox
四、bridge模式
  • bridge模式是Docker默认的网络设置,此模式会为每一个容器分配Network Namespace、设置IP等,并将一个主机上的Docker容器连接到一个虚拟网桥上。下面着重介绍一下此模式
  • 工作原理:
    • 当Docker server启动时,会在主机上创建一个名为docker0的虚拟网桥,此主机上启动的Docker容器会连接到这个虚拟网桥上。虚拟网桥的工作方式和物理交换机类似,这样主机上的所有容器就通过交换机连在了一个二层网络中
    • 接下来就要为容器分配IP了,Docker会从RFC1918所定义的私有IP网段中,选择一个和宿主机不同的IP地址和子网分配给docker0,连接到docker0的容器就从这个子网中选择一个未占用的IP使用。如一般Docker会使用172.17.0.0/16这个网段,并将172.17.42.1/16分配给docker0网桥(在主机上使用ifconfig命令是可以看到docker0的,可以认为它是网桥的管理接口,在宿主机上作为一块虚拟网卡使用)。单机环境下的网络拓扑如下,主机地址为10.10.101.105/24

长文讲述Docker的4大网络模型(host、container、none、bridge)_Docker的4大网络模型_02

长文讲述Docker的4大网络模型(host、container、none、bridge)_docker_03

宿主机的“docker0”接口(虚拟的以太网桥)

  • 在宿主机中安装完Docker之后,会在宿主机中创建一个新的网络接口,名为docker0
  • 在宿主机中输入下面的命令查看接口情况,如下所示:
    • 可以看到,docker0接口有符合RFC1918的私有IP地址,范围是172.16~172.30
    • 此docker0接口的地址是172.18.0.1,是这个Docker网络的网关地址,也是当前宿主机中所有Docker容器的网关地址
    • 备注:Docker会默认使用172.17.x.x作为子网地址。如果这个子网被占用了,Docker会在172.16~172.30这个范围内尝试创建别的子网(下图是我们的一台云服务器,其172.17.x.xIP被占用了,因此使用了172.18.x.x的地址)
ip a show docker0

长文讲述Docker的4大网络模型(host、container、none、bridge)_Docker的4大网络模型_04

  • 接口docker0是一个虚拟的以太网桥,用于连接容器和本地宿主机网络

宿主机的“vethxxx”接口

  • 在宿主机中每使用“docker run”命令运行一个容器,就会在宿主机中创建一个以“veth”开头的网络接口
  • 如下图所示:
    • 可以看到当前宿主机中运行着两个docker容器
    • 通过ifconfig命令可以查看到有两个以“veth”开头的网络接口
sudo docker ps

ifconfig

长文讲述Docker的4大网络模型(host、container、none、bridge)_docker_05

Docker容器的“eth0”接口

  • 容器创建成功之后,容器内部会创建一个名为eth0的接口,用于与宿主机之间进行通信
  • 例如,下面我们启动一个容器,并查看其网络接口。可以看到Docker给容器分配的IP地址为172.18.0.4
sudo docker run -t -i ubuntu /bin/bash

# 第一次创建容器,没有ifconfig、ip这样的命令,需要输入下面的命令安装
apt-get update
apt-get install net-tools

ifconfig

长文讲述Docker的4大网络模型(host、container、none、bridge)_host、container_06

  • 详情参阅下面的工作原理

Docker内部网络工作原理

  • 上面已经介绍了这么多的接口,我们来介绍一下Docker内部网络是如何工作的
  • 第一步:Docker每创建一个容器就会创建一组互联的网络接口,这组接口就像管道的两端(一端发送,另一端接收)
    • 在宿主机中叫“vethxxx”接口
    • 在Docker容器中叫eth0接口

长文讲述Docker的4大网络模型(host、container、none、bridge)_docker_05

长文讲述Docker的4大网络模型(host、container、none、bridge)_host、container_06

  • 第二步:然后宿主机中还有一个名为docker0的虚拟子网,这个子网由宿主机和所有Docker容器共享
    • 可以把docker0看成是虚拟网线,其一端连接宿主机的“veth”接口,另一端连接容器的eth0接口
    • 因此可以进行通信

长文讲述Docker的4大网络模型(host、container、none、bridge)_Docker的4大网络模型_04

路由演示案例

  • 上面我们介绍了工作原理,现在我们从容器内跟踪对外通信的路由,看看是如何建立连接的
  • 第一步:启动一个容器,在容器中输入下面的命令安装traceroute路由工具
apt-get -yqq update

apt-get install -yqq traceroute

长文讲述Docker的4大网络模型(host、container、none、bridge)_Docker的4大网络模型_10

  • 第二步:输入下面的命令让容器访问百度。可以看到容器第一跳的地址是宿主机网络上docker0接口的网络IP 172.18.0.1
traceroute baidu.com

长文讲述Docker的4大网络模型(host、container、none、bridge)_redis_11

Docker的防火墙规则和NAT配置

  • 不过Docker网络还有一部分配置才能允许建立连接:防火墙规则和NAT配置。这些配置允许Docker在宿主机网路和容器间路由
  • 在宿主机中输入下面的命令可以查看宿主机的IPTables NAT配置,如下所示:这里有几个值得注意的IPTables规则。首先,我们注意到,容器默认是无法访问的。从宿主网络与容器通信时,必须明确指定打开的端口
sudo iptables -t nat -L -n

长文讲述Docker的4大网络模型(host、container、none、bridge)_host、container_12

  • 下面我们以DNAT(即目标NAT)为例,这个规则把容器里的访问路路由到宿主机的32774端口上
  • 第一步:我们宿主机中当前有一个运行着redis服务的容器,如下所示:
    • 容器的4379端口映射到了宿主机的32774端口上

长文讲述Docker的4大网络模型(host、container、none、bridge)_docker_13

  • 第二步:查看redis容器的网络,命令如下:
    • 可以看到redis容器的IP地址为172.18.0.3
    • 并且使用了docker0接口作为网关地址
    • 容器的6379端口被映射到了本地宿主机的32774端口
# 查看所有配置
sudo docker inspect redis

# 过滤只查看IP地址
sudo docker inspect -f '{{ .NetworkSettings.IPAddress }}' redis

长文讲述Docker的4大网络模型(host、container、none、bridge)_docker_14

  • 第三步:因为容器的6379端口被映射到了本地宿主机的32774端口,所以我们可以在宿主机中通过下面的命令来访问容器中的redis服务
redis-cli -h 127.0.0.1 -p 32774

长文讲述Docker的4大网络模型(host、container、none、bridge)_host、container_15

  • 第四步:但是,因为运行在本地的Docker宿主机上,所以可以不使用映射后的端口,可以直接使用172.18.0.3和4379端口对容器的redis进行访问,如下所示:
redis-cli -h 172.18.0.3 -p 6379

长文讲述Docker的4大网络模型(host、container、none、bridge)_redis_16

这种联网方式不适用于实际开发

  • 这种联网方式有两大问题:
    • 第一:要在应用程序里对容器的IP地址做硬编码
    • 第二:如果重启容器,Docker可能会改变容器的IP地址
  • 因此,在实际开发中,如果容器提供服务,其重启后IP地址改变,那么就需要不断的更新,这种方式不是很灵活

一个完整的演示案例

  • 第一步:当前系统中没有运行任何容器,查看docker0网卡上没有任何网络设备
bridge link show dev docker0 | grep docker0

长文讲述Docker的4大网络模型(host、container、none、bridge)_Docker的4大网络模型_17

  • 第二步:运行一个busybox容器,之后发现一个新的网络接口veth1144baa state UP @if7挂载到了docker0,这个就是busybox的虚拟网卡
sudo docker run -itd --name busybox busybox

bridge link show | grep docker0

长文讲述Docker的4大网络模型(host、container、none、bridge)_redis_18

  • 第三步:但是查看busybox的网络配置却没有找到这个veth1144baa state UP @if7设备
sudo docker exec -it busybox ip l

长文讲述Docker的4大网络模型(host、container、none、bridge)_host、container_19

  • 这里的eth0与veth1144baa state UP @if7就是一对veth pair设备。可以把veth设备看做是一对网卡,一块网卡(eth0)在容器内,另一块(veth1144baa state UP @if7)挂载在docker0上。目的就是把容器的eth0挂到docker0上
  • 第四步:查看容器的网关,这个网关就是docker0的ip地址
sudo docker exec -it busybox ip route

sudo docker network inspect bridge | jq '.[0].IPAM.Config[0].Gateway'

长文讲述Docker的4大网络模型(host、container、none、bridge)_redis_20


  • 我是小董,V公众点击"笔记白嫖"解锁更多【Docker】资料内容。

长文讲述Docker的4大网络模型(host、container、none、bridge)_Docker的4大网络模型_21