笔者在前文《Docker 网络之进阶篇》中介绍了 CNM(Container Network Model),并演示了 bridge 驱动下的 CNM 使用方式。为了深入理解 CNM 及最常用的 bridge 驱动,本文将探索 bridge 驱动的实现机制。
说明:本文的演示环境为 ubuntu 16.04。

dokcer0 网桥

在 Ubuntn 上安装 docker 后,宿主机上默认被创建了一个名为 docker0 的网卡,其 IP 为 172.17.0.1/16:

有了这个网卡,宿主机还会在内核的路由表中添加一条到达相应网络的静态路由记录:

这条路由信息表示所有目的 IP 为 172.17.0.0/16 的数据包都会从 docker0 网卡发出。接下来我们创建一个名为 mycon 的容器,并观察其网络配置:

在 mycon 容器内可以看到两块网卡:lo 和 eth0。其中 lo 是容器的回环网卡,eth0 是容器与外界通信的网卡,eth0 的 IP 信息为 172.17.0.2/16,和宿主机上的网卡 bridge0 在同一网段中。查看 mycon 的路由信息:

mycon 容器的默认网关正是宿主机的 docker0 网卡。通过 ping 命令测试与外网的连通性,此时容器 mycon 是可以连通外网的,这就说明 mycon 的 eth0 网卡与宿主机的 docker0 网卡是连通的。

下面我们来查看宿主机的网络设备:

发现多了一个以 "veth" 开头的网卡,这是一个 veth 设备。而 veth 设备总是成对出现的,那么与 veth7537a16 配对的就应该是 mycon 容器中的 eth0 了。既然 mycon 容器中的 eth0 是与 docker0 连通的,那么 veth7537a16 也应该是与 docker0 连通的。因此 docker0 并不是一个简单的网卡设备,而是一个网桥!下图展示了 docker bridge 网络模式的拓扑图:

事实上,docker 创建了 docker0 网桥,并以 veth pair 连接各个容器的网络,容器中的数据通过 eth0 发送到 docker0 网桥上,并由 docker0 网桥完成转发。这里网桥的概念等同于交换机,为连在其上的设备转发数据帧。网桥上的 veth 网卡设备相当于交换机上的端口,可以将多个容器连接在它们上面,这些端口工作在二层,所以是不需要配置 IP 信息的。上图中的 docker0 网桥就为连在其上的容器转发数据帧,使得同一台宿主机上的 docker 容器之间可以相互通信。既然 docker0 是二层设备,那么它为什么还需要 IP 呢?其实,docker0 是一个普通的 linux 网桥,是可以为它配置 IP 的,我们可以认为它的内部有一个可以用于配置 IP 的网卡。Docker0 的 IP 地址作为所连接的容器的默认网关地址

docker0 网桥是在 docker daemon 启动时自动创建的,其默认 IP 为 172.17.0.1/16,之后通过 bridge 驱动创建的容器都会在 docker0 的子网范围内选取一个未占用的 IP 使用,并连接到 docker0 网桥上。Docker daemon 提供了如下参数可以帮助用户自定义 docker0 的设置。

  • --bip=CIDR:设置 docker0 的 IP 地址和子网范围,使用 CIDR 格式,如 192.168.1.0/24。这个参数仅仅是配置 docker0 的,对用户自定义的网桥无效。
  • --fixed-cidr=CIDR:限制 docker 容器可以获取的 IP 地址范围。Docker 容器默认可以获取的 IP 范围为 docker 网桥的整个子网范围,此参数可以将其缩小到某个子网范围内,所以这个参数必须在 docker 网桥的子网范围内。
  • --mtu=BYTES:指定 docker0 网桥的最大传输单元(MTU)。

除了使用 docker0 网桥外,用户还可以使用自定义的网桥,然后通过 --bridge=BRIDGE 参数传递给 docker daemon。比如我们可以创建一个自定义网桥 br0:

$ sudo ip link add name br0 type bridge
$ sudo ifconfig br0 188.18.0.1

然后在启动 docker daemon 时设置参数 --bridge=br0 即可。

iptables 规则

在安装 docker 时,会默认在宿主机中添加一些 iptables 规则,用于 docker 容器之间已经容器与外界的通信。我们可以通过 iptables-save 命令查看到 nat 表上 POSTROUTING 链上的有这么一条规则:

-A POSTROUTING -s 172.17.0.0/ ! -o docker0 -j MASQUERADE

这条规则关系着 docker 容器与外界的通信,其含义是将不是从网卡 docker0 发出的且源地址为 172.17.0.0/16 的数据包(容器中发出的数据包)做 SNAT。这样一来,从 docker 容器中访问外网的流量,在外部看来就是从宿主机上发出的,外部感觉不到 docker 容器的存在。

当外界想要访问 docker 容器运行的服务时该怎么办呢?接下来我们将启动一个简单的 web 服务器:

$ docker run -d -p : --name=myweb ljfpower/nodedemo

然后观察 iptables 规则的变化:

$ sudo iptables-save

*nat
-A DOCKER ! -i docker0 -p tcp -m tcp --dport -j DNAT --to-destination 172.17.0.3:

*filter
-A DOCKER -d 172.17.0.3/ ! -i docker0 -o docker0 -p tcp -m tcp --dport -j ACCEPT

可以看到,在 nat 表和 filter 表中的 DOCKER 链中分别增加来一条规则,这两条规则将访问宿主机 3000 端口的请求转发到 172.17.0.3 的 3000 端口上(提供服务的 docker 容器的 IP 和端口),所以外界访问 docker 容器是通过 iptables 做 DNAT 实现的。
Docker 默认的 forward 规则允许所有的外部 IP 访问容器,我们可以通过在 filter 的 DOCKER 链上添加规则来对外部的 IP 访问做出限制,比如只允许源 IP 为 192.168.21.212(笔者是在局域网内演示的)的数据包访问容器,添加的规则如下:

$ sudo iptables -I DOCKER -i docker0 ! -s 192.168.21.212 -j DROP

不仅仅是与外界通信,docker 容器之间相互通信也受到 iptables 规则的限制。同一台宿主机上的 docker 容器默认都连在 docker0 网桥上,它们属于同一个子网,这是满足通信的第一步。同时,docker daemon 会在 filter 表的 FORWARD 链中增加一条  ACCEPT 的规则(--icc=true):

-A FORWARD -i docker0 -o docker0 -j ACCEPT

这是满足容器间相互通信的第二步。当 docker daemon 的启动参数 -icc(icc 参数表示是否允许容器间相互通信) 设置为 false 时,上面的规则被设置为 DROP,容器间的相互通信就被禁止了,这时如果想让两个容器通信就需要在 docker run 命令中使用 --link 选项。

在 docker 容器和外界的通信过程中,还涉及了数据包在多个网卡之间的转发(比如从 docker0 网卡到宿主机 eth0 网卡),这需要内核将 ip forward 功能打开,就是把内核参数 ip_forward 设置为 1。Docker daemon 在启动的时候会执行这个操作,我们可以通过下面的命令进行检查:

$ cat /proc/sys/net/ipv4/ip_forward

返回的结果为 1,表示内核的 ip forward 功能已经打开。

容器的 DNS 和主机名(hostname)

使用同一个 docker 镜像可以创建很多个 docker 容器,但是这些容器的 hostname 并不相同,也就是说 hostname 并没有被写入到镜像中。实际上容器中的 /etc 目录下有 3 个文件是在容器启动后被虚拟文件覆盖掉的,分别是 /etc/hostname、/etc/hosts 和 /etc/resolv.conf,通过在容器中运行 mount 命令可以看到它们:

# mount

/dev/mapper/ubuntu--vg-root on /etc/resolv.conf type ext4 (rw,relatime,errors=remount-ro,data=ordered)
/dev/mapper/ubuntu--vg-root on /etc/hostname type ext4 (rw,relatime,errors=remount-ro,data=ordered)
/dev/mapper/ubuntu--vg-root on /etc/hosts type ext4 (rw,relatime,errors=remount-ro,data=ordered)

这种方式既能解决主机名的问题,同时也能让 DNS 及时更新(改变 resolv.conf)。由于这些文件的维护方法会随着 docker 版本的升级而不断变化,所以尽量不要修改这些文件,而是通过 docker 提供的相关参数进行设置,其参数配置方式如下。

  • -h HOSTNAME 或者 --hostname=HOSTNAME:设置容器的 hostname,此名称会写入到 /etc/hostname 和 /etc/hosts 文件中,也可以在容器的 bash 提示符中看到。
  • --dns=IP_ADDRESS…:为容器配置 DNS,会被写入到 /etc/resolv.conf 文件中。

这两个参数都是针对容器的需要在创建容器时进行设置。比如下面的 demo:

$ docker run -it --name mycon -h lion --dns=8.8.8.8 ubuntu:14.04

总结

本文主要通过演示 docker0 网桥相关的功能来探索 docker 网络中的 bridge 驱动的实现机制。从本文中不难看出,linux 系统中,docker 的 bridge 驱动是依赖于系统的 ip forward 以及 iptables 等核心功能的。因此在学习 docker 的过程中,适当的补充 linux 相关的知识也是十分必要的!

参考:
《docker 容器与容器云》

Docker 网络之理解 bridge 驱动的更多相关文章

  1. Docker网络详细理解-容器网络互通

    这篇文章主要解决以下几个问题: 1.同一个网段的容器互相之间通过ip进行ping通 2.同一个网段的容器互相之间通过容器名,通过使用--link进行ping通,已放弃这种方法 3.同一个网段的容器互相 ...

  2. Docker 网络原理

    引言 学习docker网络,可以带着下面两个问题来探讨 容器之间可以相互访问的原理 容器暴露端口后,通过宿主机访问到容器内应用,并且对于访问端而言不用感知容器存在的原理 Docker 本身的技术依赖L ...

  3. 理解Docker(5):Docker 网络

    本系列文章将介绍 Docker的相关知识: (1)Docker 安装及基本用法 (2)Docker 镜像 (3)Docker 容器的隔离性 - 使用 Linux namespace 隔离容器的运行环境 ...

  4. 理解Docker :Docker 网络

    本系列文章将介绍 Docker的相关知识: (1)Docker 安装及基本用法 (2)Docker 镜像 (3)Docker 容器的隔离性 - 使用 Linux namespace 隔离容器的运行环境 ...

  5. bridge br0 docker 网络问题 Docker Container与Docker Host

    Docker学习笔记:Docker 网络配置 - docker ppt - docker中文社区http://www.docker.org.cn/dockerppt/111.html Bridge t ...

  6. docker网络之bridge

    建议阅读本文章之前了解一下文章,本文不作bridge的基本介绍 https://blog.csdn.net/u014027051/article/details/53908878/ http://wi ...

  7. Docker网络——单host网络

    前言 前面总结了Docker基础以及Docker存储相关知识,今天来总结一下Docker单主机网络的相关知识.毋庸置疑,网络绝对是任何系统的核心,他在Docker中也占有重要的作用.同样本文基于Clo ...

  8. Docker网络解决方案 - Calico部署记录

    简单来说,实现docker跨主机容器间通信,常用的第三方网络方案是Flannel,Weave,Calico:Flannel会为每个host分配一个subnet,容器从这个subnet中分配ip,这些i ...

  9. Docker网络解决方案 - Weave部署记录

    前面说到了Flannel的部署,今天这里说下Docker跨主机容器间网络通信的另一个工具Weave的使用.当容器分布在多个不同的主机上时,这些容器之间的相互通信变得复杂起来.容器在不同主机之间都使用的 ...

随机推荐

  1. DataTable的Merge\COPY\AcceptChange使用说明

    在C#内使用DataTable的Merge().Copy().AcceptChange().Clone()方法的用途如下: 1.Merge()可将两个不同的表结构的表进行合并,合并后新表的列为之前两表 ...

  2. 获取标准shell 命令的输出内容

    cmdline.h #include <iostream> #include <mutex> class Cmdline { private: Cmdline() = defa ...

  3. linux报错-bash: xhost: command not found

    本帖转自倔强小梦https://www.cnblogs.com/xphdbky/p/8243008.html 倔强小梦参考自:http://blog.csdn.net/csdnones/article ...

  4. python 错误记录

    class Func: d = dict() def __setitem__(self, key, value): # xxx object does not support item assignm ...

  5. win10 anaconda安装后使用报错“Original error was: DLL load failed: 找不到指定的模块”

    报错:Original error was: DLL load failed: 找不到指定的模块. 环境变量需要添加3个 然后就okay了.

  6. Linux 系统中的内部与外部命令

    linux中的命令大致可分为两类,内部命令和外部命令: 内部命令(builtin command):也称shell内嵌命令 外部命令(external command):存放在一个文件中,使用时需要去 ...

  7. ssh网上商城源码

    本人承诺源码免费,只是出于前期发布很多免费资源反而落不到好下场,总是被一些人说三道四.就算你去找到资源版本不符合你也运行不起来.如果想要资源加QQ1397617269,或者电话短信1395630164 ...

  8. Spark 异步Action

    异步不保序,但大作业执行时间后移. .set("spark.scheduler.mode", "FAIR") 公平调度,充分使用集群资源. Spark Doc ...

  9. 微信小程序开发-窗体设置

    "window": { "backgroundTextStyle": "light", "navigationBarBackgro ...

  10. nova boot from volume代码分析

    首先要创建一个bootable volume curl -i http://16.158.166.197:8776/v1/c24c59846a7f44538d958e7548cc74a3/volume ...