Docker之十七: 高级网络功能

  • Docker 网络基本原理
  • Docker 的网络模式
  • Docker 支持的网络模式
  • Docker 默认的网络模式
  • HOST 模式
  • CONTAINER 模式
  • NONE 模式
  • 自定义网络模式
  • 网络启动与配置参数
  • 配置容器 DNS 和主机名
  • 容器内修改配置文件
  • 通过参数指定
  • 容器访问控制
  • 容器访问外部网络
  • 容器之间访问
  • 访问所有端口
  • 访问指定端口
  • 映射容器端口到宿主主机的实现
  • 容器访问外部实现
  • 外部访问容器实现
  • 配置容器网桥
  • 自定义网桥
  • 使用 OpenvSwitch 网桥
  • 创建一个点到点的连接


Docker 网络基本原理

Docker 的本地网络实现其实就是利用了 Linux 上的网络命名空间和虚拟网络设备(特别是 veth pair)。

要实现网络通信,需要至少一个物理或虚拟网络接口与外界相通,收发数据,如需在不同子网之间进行通信,还需要路由机制。

Docker 中的网络接口默认都是虚拟接口。其基本原理是:Linux 通过在内核中进行数据复制来实现虚拟接口之间的数据转发,即发送接口的发送缓存中的数据包将直接复制到接收接口的接受缓存中,而无需通过外部网络设备进行交换。所以虚拟接口的转发效率极高。

Docker 容器网络利用了 Linux 虚拟网络技术,它在本地主机和容器内分别建立一个虚拟接口 veth,并连通(这样的一对虚拟接口叫做 veth pair)。

docker geth docker geth 性能_网络

Docker 创建容器的时候,按照如下步骤操作:

  1. 创建一对虚拟接口,分别放到本地主机和新容器的命名空间中;
  2. 本地主机一端的虚拟接口连接到默认的 docker0 网桥或指定网桥上,并且有一个以 veth 开头的唯一名字,如 veth1234
  3. 容器一端的虚拟接口将放到新创建的容器中,并修改名字作为 eth0U。这个接口只在容器命名空间可见;
  4. 从网桥可用地址段中获取一个空闲的地址分配给容器的 eth0(例如 172.17.0.2/16),并配置默认路由网关为 docker0 网卡的内部接口 docker0 的 IP 地址(例如 172.17.42.1/16)。

这样,容器就可以使用它所能看到的 eth0 虚拟网卡来连接其他容器和访问外部网络。

Docker 的网络模式

Docker 支持的网络模式

docker容器的网络有五种模式:

  1. bridge 模式,–net=bridge (默认)
    这是 Dokcer 网络的默认设置,为容器创建独立的网络命名空间,容器具有独立的网卡等所有单独的网络栈,是最常用的使用方式。
    docker run 启动容器的时候,如果不加 –net 参数,就默认采用这种网络模式。安装完 Docker,系统会自动添加一个供 Docker 使用的网桥 docker0,我们创建一个新的容器时,容器通过 DHCP 获取一个与 docker0 同网段的 IP 地址,并默认连接到 docker0 网桥,以此实现容器与宿主机的网络互通。
  2. host 模式,–net=host
    这个模式下创建出来的容器,直接使用容器宿主机的网络命名空间。
    容器将不拥有自己独立的 Network Namespace,即没有独立的网络环境。它使用宿主机的 IP 和端口。
  3. none 模式,–net=none
    为容器创建独立网络命名空间,但不为它做任何网络配置,容器中只有 lo,用户可以在此基础上,对容器网络做任意定制。
    这个模式下,Dokcer 不为容器进行任何网络配置。需要我们自己为容器添加网卡,配置 IP
    因此,若想使用 pipework 配置 Docker 容器的 IP 地址,必须要在 none 模式下才可以。
  4. 其他容器模式(即 container 模式),–net=container:NAME_or_ID
    host 模式类似,只是容器将与指定的容器共享网络命名空间。
    这个模式就是指定一个已有的容器,共享该容器的 IP 和端口。除了网络方面两个容器共享,其他的如文件系统,进程等还是隔离开的。
  5. 用户自定义:Docker 1.9 版本以后新增的特性,允许容器使用第三方的网络实现或者创建单独的 bridge 网络,提供网络隔离能力。

Docker 默认的网络模式

bridge 模式是 Docker 默认的网络模式,也是开发者最常使用的网络模式。在这种模式下,Docker 为容器创建独立的网络栈,保证容器内的进程使用独立的网络环境,实现容器之间、容器与宿主机之间的网络栈隔离。同时,通过宿主机上的 docker0 网桥,容器可以与宿主机乃至外界进行网络通信。

其网络模型如下图所示:

docker geth docker geth 性能_docker_02


从上面的网络模型可以看出,容器从原理上是可以与宿主机乃至外界的其他机器通信的。

同一宿主机上,容器之间都是连接到 docker0 这个网桥上的,它可以作为虚拟交换机使容器可以相互通信。然而,由于宿主机的 IP 地址与容器 veth pairIP 地址均不在同一个网段,故仅仅依靠 veth pairnamespace 的技术,还不足以使宿主机以外的网络主动发现容器的存在。为了使外界可以访问容器中的进程,Docker 采用了端口绑定的方式,也就是通过 iptablesNAT,将宿主机上的端口端口流量转发到容器内的端口上。

# 使用 run 命令创建容器,并将宿主机的 3306 端口绑定到容器的 3306 端口
$ docker run -itd --name db -p 3306:3306 MySQL

# 宿主机上,通过下面命令可以查到一条 DNAT 规则
$ iptables -t nat -L -n
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:3306 to:172.17.0.5:3306

上面的 172.17.0.5 即为 bridge 模式下,创建的容器 IP

很明显,bridge 模式的容器与外界通信时,必定会占用宿主机上的端口,从而与宿主机竞争端口资源,对宿主机端口的管理会是一个比较大的问题。同时,由于容器与外界通信是基于三层上 iptables NAT,性能和效率上的损耗是可以预见的。

# 列出当前主机网桥
$ brctl show
bridge name     bridge id               STP enabled     interfaces
docker0         8000.0242369ab552       no              vethef56aac

# 查看当前 docker0 ip
$ ifconfig 
...
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        ether 02:42:36:9a:b5:52  txqueuelen 0  (Ethernet)
        RX packets 36626  bytes 1544566 (1.5 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 41832  bytes 142181730 (142.1 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
...

# 在容器运行时,每个容器都会分配一个特定的虚拟机口并桥接到docker0。
# 每个容器都会配置同 docker0 ip 相同网段的专用ip 地址
# docker0的IP地址被用于所有容器的默认网关。
$ docker ps
CONTAINER ID	IMAGE		 COMMAND	  			 CREATED		STATUS	 	PORTS	                            NAMES
b5f03e866505	mysql:latest "docker-entrypoint.s…"  3 hours ago Up 3 hours     0.0.0.0:3306->3306/tcp, 33060/tcp   mysql-test
$ docker inspect b5f03e866505 | grep IPAddress 
  "SecondaryIPAddresses": null,
  "IPAddress": "172.17.0.2",
           "IPAddress": "172.17.0.2",

# 宿主机的ip路由转发功能一定要打开,否则所创建的容器无法联网
# 查看宿主机网络转发是否开启 1,开启 0,关闭
$ cat /proc/sys/net/ipv4/ip_forward
1
# 或
$ sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1

HOST 模式

容器和宿主机共享同一个网络命名空间,换言之,容器的 IP 地址即为宿主机的 IP 地址。所以容器可以和宿主机一样,使用宿主机的任意网卡,实现和外界的通信。其网络模型可以参照下图:

docker geth docker geth 性能_linux_03


采用 host 模式的容器,可以直接使用宿主机的 IP 地址与外界进行通信,若宿主机具有公有 IP,那么容器也拥有这个公有 IP。同时容器内服务的端口也可以使用宿主机的端口,无需额外进行 NAT 转换,而且由于容器通信时,不再需要通过 linux bridge 等方式转发或数据包的拆封,性能上有很大优势。当然,这种模式也有劣势,主要包括以下几个方面:

  • 容器不再拥有隔离、独立的网络栈。容器会与宿主机竞争网络栈的使用,并且容器的崩溃就可能导致宿主机崩溃,在生产环境中,这种问题可能是不被允许的。
  • 容器内部将不再拥有所有的端口资源,因为一些端口已经被宿主机服务、bridge模式的容器端口绑定等其他服务占用掉了。

CONTAINER 模式

其他网络模式是 docker 中一种较为特别的网络的模式。在这个模式下的容器,会使用其他容器的网络命名空间,其网络隔离性会处于 bridge 桥接模式与 host 模式之间。当容器共享其他容器的网络命名空间,则在这两个容器之间不存在网络隔离,而他们又与宿主机以及除此之外其他的容器存在网络隔离。其网络模型可以参考下图:

docker geth docker geth 性能_docker geth_04


在这种模式下的容器可以通过 localhost 来同一网络命名空间下的其他容器,传输效率较高。而且这种模式还节约了一定数量的网络资源,但它并没有改变容器与外界通信的方式。在一些特殊的场景中非常有用,例如,kubernetespod 创建一个基础设施容器,同一 pod 下的其他容器都以其他容器模式共享这个基础设施容器的网络命名空间,相互之间以 localhost 访问,构成一个统一的整体。

NONE 模式

在这种模式下,容器有独立的网络栈,但不包含任何网络配置,只具有 lo 这个 loopback 网卡用于进程通信。也就是说,none模式为容器做了最少的网络设置,但是俗话说得好“少即是多”,在没有网络配置的情况下,通过第三方工具或者手工的方式,开发这任意定制容器的网络,提供了最高的灵活性。

自定义网络模式

在用户定义网络模式下,开发者可以使用任何 docker 支持的第三方网络 driver 来定制容器的网络。并且,docker 1.9 以上的版本默认自带了 bridgeoverlay 两种类型的自定义网络 driver。可以用于集成 calicoweaveopenvswitch 等第三方厂商的网络实现。 除了 docker 自带的 bridge driver,其他的几种 driver 都可以实现容器的跨主机通信。而基于 bdrige driver 的网络,docker 会自动为其创建 iptables 规则,保证与其他网络之间、与 docker0 之间的网络隔离。

网络启动与配置参数

Docker 服务启动时会首先在主机上自动创建一个 docker0 虚拟网桥,实际上是一个 Linux 网桥。网桥可以理解为一个软件交换机,负责挂载其上的接口之间进行包转发。

同时,Docker 随机分配一个本地未占用的私有网段(在RFC1918中定义)中的一个地址给 docker0 接口。比如典型的 172.17.0.0/16 网段,掩码为 255.255.0.0。此后启动的容器内的网口也会自动分配一个该网段的地址。

当创建一个 Docker 容器的时候,同时会创建了一对 veth pair 互联接口。当向任一个接口发送包时,另外一个接口自动收到相同的包。互联接口的一端位于容器内,即 eth0;另一端在本地并被挂载到 docker0 网桥,名称以 veth 开头(例如 vethAQI2QT)。通过这种方式主机可以与容器通信,容器之间也可以相互通信。如此一来,Docker 就创建了在主机和所有容器之间一个虚拟共享网络。

Docker 网络设置相关参数:

表1:只有在 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 规则

表2:可以在启动服务时指定,也可以 Docker 容器启动(使用 docker run 命令)时指定

参数

描述

–net=bridge

默认设置。为容器创建独立的网络命名空间,分配网卡、IP 地址等网络配置,并通过 veth 接口对将容器挂载到一个虚拟网桥(默认为 docker0)上

–net=none

为容器创建独立的网络命名空间,但不进行网络配置,即容器内没有创建网卡、IP 地址等

–net=container:NAME_or_ID

新创建的容器共享指定的已存在容器的网络命名空间,两个容器的网络配置共享,但其他资源(如进程空间、文件系统等)还是相互隔离的

–net=host

不为容器创建独立的网络命名空间,容器内看到的网络配置(网卡信息、路由表、iptables 规则等)均与主机保持一致。注意其他资源还是与主机隔离的

–net=user_defined_network

用户自行用 network 相关命令创建一个网络,同一个网络内的容器彼此可见,可以采用更多类型的网络插件

–mtu=BYTES

容器网络中的 MTU

–dns=IP_ADDRESS

使用指定的 DNS 服务器

–dns-opt=""

指定 DNS 选项

–dns-search=DOMAIN

指定 DNS 搜索域

-h HOSTNAME or --hostname=HOSTNAME

配置容器主机名

-ip=""

指定容器内接口的 IP 地址

–link=CONTAINER_NAME:ALIAS

添加到另一个容器的链接

–network-alias

容器在网络中的别名

-p SPEC or --publish=SPEC

映射容器端口到宿主机

-P or --publish-all=tue|false

映射容器所有端口到宿主机

配置容器 DNS 和主机名

Docker 服务启动会默认启用一个内嵌的 DNS 服务,来自动解析同一个网络中的容器主机名和地址,如无法解析,则通过容器内的 DNS 相关配置进行解析。用户可以通过命令选项自定义容器主机名和 DNS 配置。

容器内修改配置文件

容器中主机名和 DNS 配置信息可以通过三个系统配置文件来管理:/etc/resolv.conf/etc/hostname、和 /etc/hosts

启动一个容器,在容器中使用 mount 命令可以查看到三个文件挂载信息:

root@ubuntu:~# docker run -it ubuntu:latest /bin/bash
root@32460bbe8527:/# mount
...
/dev/sda5 on /etc/resolv.conf type ext4 (rw,relatime,errors=remount-ro)
/dev/sda5 on /etc/hostname type ext4 (rw,relatime,errors=remount-ro)
/dev/sda5 on /etc/hosts type ext4 (rw,relatime,errors=remount-ro)
...

Docker 启动容器时,会从宿主机上复制 /etc/resolv.conf ,并删除掉其中无法连接到的 DNS 服务器:

root@32460bbe8527:/# cat /etc/resolv.conf 
# This file is managed by man:systemd-resolved(8). Do not edit.
#
# This is a dynamic resolv.conf file for connecting local clients directly to
# all known uplink DNS servers. This file lists all configured search domains.
#
# Third party programs must not access this file directly, but only through the
# symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a different way,
# replace this symlink by a static file or a different symlink.
#
# See man:systemd-resolved.service(8) for details about the supported modes of
# operation for /etc/resolv.conf.

nameserver 192.168.194.2
search localdomain

/etc/hosts 文件中默认只记录容器自身的地址和名称:

root@32460bbe8527:/# cat /etc/hosts 
127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.3      32460bbe8527

/etc/hostname 文件则记录了容器的主机名:

root@32460bbe8527:/# cat /etc/hostname 
32460bbe8527

容器运行时,可以在运行中的容器里直接编辑 /etc/resolv.conf/etc/hostname、和 /etc/hosts 文件。但这些修改是零时的,只在运行的容器中保留,容器终止或重启后并不会被保存下来,也不会被 docker commit 提交。

通过参数指定

用户自定义容器的配置,可以在创建或启动容器时利用下面的参数指定,注意一般不推荐与 -net=host 一起使用,会破坏主机上的配置信息:

参数

说明

-h HOSTNAME or --hostname=HOSTNAME

设定容器主机名。容器主机名会被写到容器内的 /etc/hostname 和 /etc/hosts。但时这个主机名只有容器内能看到,在容器外部则看不到,即不会再 docker ps 中显示,也不会在其他容器的 /etc/hosts 中看到

–link=CONTAINER_NAME:ALIAS

记录其他容器主机名。在创建容器的时候,添加一个所连接容器的主机到容器内 /etc/hosts 文件中。这样,新建容器可以直接使用主机名与所连接容器通信

–dns=IP_ADDRESS

指定 DNS 服务器。添加 DNS 服务器到容器的 /etc/resolv.conf 中,容器会用指定的服务来解析所有不在 /etc/hosts 中的主机名

–dns-option list

指定 DNS 相关的选项

–dns-search=DOMAIN

指定 DNS 搜索域。设定容器的搜索域,当设定搜索域为 .example.com 时,在搜索一个名为 host 的主机时,DNS 不仅搜索 host,还会搜索 host.example.com

容器访问控制

容器的访问控制主要通过 Linux 上的 iptables 防火墙软件来进行管理和实现。

容器访问外部网络

容器默认指定了网关为 docker0 网桥的 docker0 内部接口。docker0 内部接口同时也是宿主机的一个本地接口。因此,容器默认情况下可以访问到宿主本地网络。如果容器要想通过宿主机访问到外部网络,则需要宿主机进行辅助转发。

在宿主机 Linux 系统中,检查转发是否打开:

# 检查转发是否打开
$ sudo sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1

# net.ipv4.ip_forward 如果为0,说明没有开启转发,则需要手动打开
$ sudo sysctl -w net.ipv4.ip_forward=1

Docker 服务启动时会默认开启 --ip-forward=true,自动配置宿主机系统的转发规则。

容器之间访问

容器之间相互访问需要两方面的支持:

  • 网络拓扑是否已经连通。默认情况下,所有容器都会连接到 docker0 网桥上,这意味着默认情况下拓扑是互通的;
  • 本地系统的防火墙软件 iptables 是否允许访问通过。这取决于防火墙的默认规则是允许(大部分情况)还是禁止。

访问所有端口

当启动 Docker 服务时候,默认会添加一条 ”允许“ 转发策略到 iptables 的 FORWARD 链上。通过配置 –icc=true|false (默认值为 true)参数可以控制默认的策略。

为了安全考虑,可以在 Docker 配置文件中配置 DOCKER_OPTS=–icc=false 来默认禁止容器之间的相互访问。

同时,如果启动 Docker 服务时手动指定 –iptables=false 参数,则不会修改宿主机系统上的 iptables 规则。

访问指定端口

在通过 -icc=false 禁止容器间相互访问后,仍可以通过 –link=CONTAINER_NAME:ALIAS 选项来允许访问指定容器的开放端口。

启动 Docker 服务时,可以同时使用 -icc=false --iptables=true 参数来配置容器间禁止访问,并允许 Docker 自动修改系统中的 iptables 规则。

# Docker 服务启动时指定 -icc=false --iptables=true,查看系统中 iptables 规则
$ sudo iptables -nL
...
Chain FORWARD (policy ACCEPT)
target		prot opt source			destination
DROP		all -- 0.0.0.0/0		0.0.0.0/0
...

之后,启动容器(docker run)时使用 --link=CONTAINER_NAME:ALIAS 选项。Docker 会在 iptable 中为两个容器互联分别添加一条 ACCEPT 规则,允许相互访问开放的端口(取决于 Dockerfile 中的 EXPOSE 行)。

# 此时查看 iptables 的规则
$ sudo iptables -nL
...
Chain FORWARD (policy ACCEPT)
target		prot opt source			destination
ACCEPT		tcp -- 172.17.0.2		172.17.0.3		tcp spt:80
ACCEPT		tcp -- 172.17.0.3		172.17.0.2		tcp spt:80
DROP		all -- 0.0.0.0/0		0.0.0.0/0
...

–link=CONTAINER_NAME:ALIAS 中的 CONTAINER_NAME 目前必须时 Docker 自动分配的容器名,或者使用 –name 参数指定的名字。不能为容器 -h 参数配置的主机名。

映射容器端口到宿主主机的实现

默认情况下,容器可以主动访问到外部网络的连接,但是外部网络无法访问到容器。

容器访问外部实现

假设容器内部的网络地址为 172.17.0.2,本地网络地址为 10.0.2.2。容器要能访问外部网络,源地址不能为 172.17.0.2,需要进行源地址映射(Source NAT,SNAT),修改为本地系统的 IP 地址10.0.2.2

映射是通过 iptables 的源地址伪装操作实现的。查看主机 nat 表上 POSTROUTING 链的规则。该链负责网包要离开主机前,改写其源地址:

$ sudo iptables -t nat -nvL POSTROUTING
Chain POSTROUTING (policy ACCEPT 12 packets, 738 bytes)
pkts bytes target	prot opt in		out		source			destination
...
0	 0	MASQUERADE 	all -- *	!docker0	172.17.0.0/16	0.0.0.0/0
...

其中,上述规则将所有源地址在 172.17.0.0/16 网段,且不是从 docker0 接口发出的流量 (即从容器中出来的流量),动态伪装为从系统网卡发出。MASQUERADE 行动与传统 SNAT 行动相比,好处是能动态地从网卡获取地址。

外部访问容器实现

容器允许外部访问,可以在 docker run 时候通过 -p-P 参数来启用。

不管用哪种办法,其实也是在本地的 iptablenat 表中添加相应的规则,将访问外部 IP 地址的包进行目标地址 DNAT,将目标地址修改为容器的 IP 地址。

以一个开放 80 端口的 Web 容器为例,使用 -P 时,会自动映射本地 49000~49900 范围内的随机端口到容器的 80 端口:

$ sudo iptables -t nat -nvL
Chain PREROUTING (policy ACCEPT 236 packets, 33317 bytes)
pkts bytes target	prot opt in		out		source			destination
...
576	 30236	DOCKER 	all -- *		*		0.0.0.0/0		0.0.0.0/0
			ADDRTYPE match dst-type LOCAL

Chain DOCKER (2 references)
pkts bytes target	prot opt in				out		source			destination
0	 0	   RETURN	all -- docker0			*		0.0.0.0/0		0.0.0.0/0
0	 0	   RETURN   all -- br-337120b7e82e	*		0.0.0.0/0		0.0.0.0/0
0 	 0     DNAT		tcp -- !docker0			*		0.0.0.0/0		0.0.0.0/0
tcp dpt:49153 to:172.17.0.2:80
...

可以看到,nat 表中涉及两条链:PREROUTING 链负责包到达网络接口时,改写其目的地址,其中规则将所有流量都转发到 DOCKER 链;而 DOCKERR 链将所有不是从 docker0 进来的包(意味着不是本地主机产生),同时目标端口为 49153 的修改其目标地址为 172.17.0.0,目标端口修改为 80

使用 -p 80:80 时,与上面类似,只是本地端口也为 80:

$ sudo iptables -t nat -nvL
Chain PREROUTING (policy ACCEPT 236 packets, 33317 bytes)
pkts bytes target	prot opt in		out		source			destination
...
576	 30236	DOCKER 	all -- *		*		0.0.0.0/0		0.0.0.0/0
			ADDRTYPE match dst-type LOCAL

Chain DOCKER (2 references)
pkts bytes target	prot opt in				out		source			destination
0 	 0     DNAT		tcp -- !docker0			*		0.0.0.0/0		0.0.0.0/0
tcp dpt:80 to:172.17.0.2:80
...

注意

  1. 规则映射地址为 0.0.0.0,意味着将接受主机来自所有网络接口的流量。用户可以通过 -p IP:host_port:container_port-p IP::port 来指定绑定的外部网络接口,以指定更严格的访问规则;
  2. 如果希望映射绑定到某个固定的宿主机 IP 地址,可以在 Docker 配置文件中指定 DOCKER_OPTS="–ip=IPADDRESS",之后重启 Docker 服务即可生效。

配置容器网桥

Docker 服务默认会创建一个名称为 docker0Linux 网桥(其上有一个 docker0 内部接口),它在内核层连通了其他的物理或虚拟网卡,这就将所有容器和本地主机都放到同一个物理网络。用户使用 Docker 创建多个自定义网络时可能会出现多个容器网桥。

Docker 默认指定了 docker0 接口的 IP 地址和子网掩码,让主机和容器之间可以通过网桥相互通信,它还给出了 MTU(接口允许接收的最大传输单元),通常是 1500B,或宿主主机网络路由上支持的默认值。这些值都可以在服务启动的时候进行配置:

参数

描述

–bip=CIDR

IP 地址加掩码格式,例如 192.168.1.5/24

–mtu=BYTES

覆盖默认的 Docker mtu

也可以在配置文件中配置 DOCKER_OPTS,然后重启服务。

# Linux 查看网桥和端口连接信息
$ sudo brctl show

每次创建一个新容器的时候,Docker 从可用的地址段中选择一个空闲的 IP 地址分配给容器的 eth0 端口,并且使用本地主机上 docker0 接口的 IP 作为容器的默认端口。

# 进入容器查看 eth0 信息
$ docker run -it --rm debain:stable bash
root@32460bbe8527:/# ip addr show eth0

目前 Docker 不支持在启动容器的时候指定 IP 地址。

容器默认使用 Linux 网桥,用户可用替换为 OpenvSwitch 等功能更强大的网桥实现,支持更多的软件定义网络特性。

自定义网桥

除了默认的 docker0 网桥,用户也可以指定其他网桥来连接各个容器。在启动 Docker 服务的时候,可使用 -b BRIDGE–bridge=BRIDGE 来指定使用的网桥。

查看默认 docker0 网桥:

$ sudo ip addr show docker0
6: docker0: <BROADCAST,MULTICAST> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:1c:1e:27:9a brd ff:ff:ff:ff:ff:ff

自定义网桥过程:

# Step1:停止 Docker 服务,删除旧网桥
sudo service docker stop
sudo ip link set dev docker0 down
sudo brctl delbr docker0

# Step2:创建一个新网桥
sudo brctl addbr bridge0
sudo ip addr 192.168.5.1/24 dev bridge0
sudo ip link set dev bridge0 up

# Step3:查看确认网桥创建并启动
sudo ip addr show bridge0

# Step4:配置 Docker 服务,默认桥接到创建的网桥上
echo 'DOCKER_OPTS="-b=bridge0"' >> /etc/default/docker

# Step5:启动 Docker 服务
sudo service dcker satrt

# Step6:新建容器
# 可以使用 brctl show 命令来查看桥接信息
# 在容器中可以使用 ip addr 和 ip route 命令来查看 IP 地址配置和路由信息

使用 OpenvSwitch 网桥

Docker 默认使用的是 Linux 自带的网桥实现,可以替换为使用功能更强大的 OpenvSwitch 虚拟交换机实现。

# Step1:安装 Docker,默认创建名为 docker0 的 Linux 网桥,作为连接容器的本地网桥
# 查看网桥
$ sudo brctl show
bridge name     bridge id               STP enabled     interfaces
...
docker0         8000.02421c1e279a       no              vethd99eb53
...
$ ifconfig docker0
docker0: flags=4098<BROADCAST,MULTICAST>  mtu 1500
        ether 02:42:1c:1e:27:9a  txqueuelen 0  (Ethernet)
        RX packets 6848  bytes 289216 (289.2 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 7523  bytes 27456857 (27.4 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
        
# Step2:安装 OpenvSwitch(根据平台类型属于不同的下载命令)
$ sudo aptitude install openvswitch
# 测试添加一个网桥 br0 并查看
$ sudo ovs-vsct1 add-br br0
$ sudo ovs-vsct1 show

# Step3:配置容器连接到 OpenvSwitch 网桥
# Step3-1:创建无网口容器,并记录容器 id
$ docker run --net=none --privileged=true -it debian:stable bash
root@298bbb17c244:/# 
# 容器 id 为 298bbb17c244

# Step3-2:手动为容器添加网络
# 下载 OpenvSwitch 项目提供的支持 Docker 容器的辅助脚本 ovs-docker
$ wget https://github.com/openvswitch/ovs/tree/master/utilities/ovs-docker
$ sudo chmod a+x ovs-docker
# 为容器添加网卡,并挂载到 br0 上
$ sudo ./ovs-docker add-port br0 eth0 298bbb17c244 --ipaddress=172.17.0.2/16
# 配置 OpenvSwitch 的网桥 br0 内部接口地址为 172.17.0.2/16(只要与所挂载容器 IP 在同一个子网内即可)
$ sudo ifconfig br0 172.17.0.1/16

# Step3-3:测试连通
root@298bbb17c244:/# ping 172.17.0.1
# 在容器内也可以配置默认网关为 br0 接口地址
root@298bbb17c244:/# rute add default gw 172.17.0.1
# 删除 br0 接口命令
$ sudo ./ovs-docker del-port bro eth0 298bbb17c244

创建一个点到点的连接

如何实现两个容器直接直接通信,而不用通过主机网桥进行桥接?方法很简单:创建一对 peer 接口,分别放到两个容器中,配置成点到点的链路类型即可。

Step1:启动两个容器

docker run -it --rm --net=none ubuntu /bin/bash
root@15142ca9af67:/#
$ docker run -it --rm --net=none ubuntu /bin/bash
root@4667b8c477dc:/#

Step2:找到进程号,然后创建网络命名空间的跟踪文件

$ docker inspect -f '{{.State.Pid}}' 15142ca9af67
9908
$ docker inspect -f '{{.State.Pid}}' 4667b8c477dc
9988
$ sudo mkdir -p /var/run/netns
$ sudo ln -s /proc/9908/ns/net /var/run/netns/9908
$ sudo ln -s /proc/9988/ns/net /var/run/netns/9988

Step3:创建一对 peer 接口

$ sudo ip link add A type veth peer name B

Step4:添加 IP 地址和路由信息

$ sudo ip link set A netns 9908
$ sudo ip netns exec 9908 ip addr add 10.1.1.1/32 dev A
$ sudo ip netns exec 9908 ip link set A up
$ sudo ip netns exec 9908 ip route add 10.1.1.2/32 dev A

$ sudo ip link set B netns 9988
$ sudo ip netns exec 9988 ip addr add 10.1.1.2/32 dev B
$ sudo ip netns exec 9988 ip link set B up
$ sudo ip netns exec 9988 ip route add 10.1.1.1/32 dev B

至此,这两个容器便可以相互 ping 通,并成功建立连接。点到点链路不需要子网和子网掩码。此外,也可以不指定 –net=none 来创建点到点链路。这样容器还可以通过原先的网络来通信。

利用类似的办法,可以创建一个只跟主机通信的容器。也可以使用 –icc=false 命令来关闭容器之间的通信。

ep4**:添加 IP 地址和路由信息

$ sudo ip link set A netns 9908
$ sudo ip netns exec 9908 ip addr add 10.1.1.1/32 dev A
$ sudo ip netns exec 9908 ip link set A up
$ sudo ip netns exec 9908 ip route add 10.1.1.2/32 dev A

$ sudo ip link set B netns 9988
$ sudo ip netns exec 9988 ip addr add 10.1.1.2/32 dev B
$ sudo ip netns exec 9988 ip link set B up
$ sudo ip netns exec 9988 ip route add 10.1.1.1/32 dev B

至此,这两个容器便可以相互 ping 通,并成功建立连接。点到点链路不需要子网和子网掩码。此外,也可以不指定 –net=none 来创建点到点链路。这样容器还可以通过原先的网络来通信。

利用类似的办法,可以创建一个只跟主机通信的容器。也可以使用 –icc=false 命令来关闭容器之间的通信。