Docker网络

2022/03/07 Docker

Docker网络

Docker网络现状

2015年3月,Docker宣布收购SocketPlane,随后SocketPlane社区开始沉寂,一个新的Docker子项目“Libnetwork”开始酝酿。一个月后,Libnetwork在github上正式与开发者见面,预示着Docker开始在网络方面发力。

Libnetwork提出了新的容器网络模型(Container Network Model,简称CNM),定义了标准的API用于为容器配置网络,其底层可以适配各种网络驱动。CNM有三个重要的概念:

  • 沙盒。沙盒是一个隔离的网络运行环境,保存了容器网络栈的配置,包括了对网络接口、路由表和DNS配置的管理。在Linux平台上,沙盒是用Linux Network Namespace实现的,在其他平台上可能是不同的概念,如FreeBSD Jail。一个沙盒可以包括来自多个网络的多个Endpoint(端点)。
  • Endpoint。Endpoint将沙盒加入一个网络,Endpoint的实现可以是一对veth pair或者OVS内部端口,当前的Libnetwork使用的是veth pair。一个Endpoint只能隶属于一个沙盒及一个网络。通过给沙盒增加多个Endpoint可以将一个沙盒加入多个网络。
  • 网络。网络包括一组能互相通信的Endpoint。网络的实现可以是Linux bridge、vlan等。 从CNM的概念角度讲,Libnetwork的出现使得Docker具备了跨主机多子网的能力,同一个子网内的不同容器可以运行在不同主机上。比如,同属于192.168.0.0/24子网IP地址分别为192.168.0.1和192.168.0.2的容器可以位于不同的主机上且可以直接通信,而持有IP 192.168.1.1的容器即使与IP为192.168.0.1的容器处于同一主机也不能互通。 Libnetwork已经实现了五种驱动(driver):
  • bridge:Docker默认的容器网络驱动。Container通过一对veth pair连接到docker0网桥上,由Docker为容器动态分配IP及配置路由、防火墙规则等。
  • host:容器与主机共享同一Network Namespace,共享同一套网络协议栈、路由表及iptables规则等。容器与主机看到的是相同的网络视图。
  • null:容器内网络配置为空,需要用户手动为容器配置网络接口及路由等。
  • remote:Docker网络插件的实现。Remote driver使得Libnetwork可以通过HTTP RESTful API对接第三方的网络方案,类似SocketPlane的SDN方案只要实现了约定的HTTP URL处理函数及底层的网络接口配置方法,就可以替换Docker原生的网络实现。
  • overlay:Docker原生的跨主机多子网网络方案。主要通过使用Linux bridge和vxlan隧道实现,底层通过类似于etcd或consul的KV存储系统实现多机的信息同步。overlay驱动当前还未正式发布,但开发者可以通过编译实验版的Docker来尝试使用,Docker实验版同时提供了额外的network和service子命令来进行更灵活的网络操作,不过,需要内核版本>=3.16才可正常使用。

Docker网络初探

Linux平台下,Docker容器网络资源通过内核的Network Namespace机制实现隔离,不同的Network Namespace有各自的网络设备、协议栈、路由表、防火墙规则等,反之,同一Network Namespace下的进程共享同一网络视图。通过对Network Namespace的灵活操纵,Docker提供了五种容器网络模式,下面分别进行介绍。

  • none:不为容器配置任何网络功能。 在该模式下,需要以–net=none参数启动容器:
    $ docker run --net=none -ti ubuntu:latest ip addr show
    1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
      link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
      inet 127.0.0.1/8 scope host lo
          valid_lft forever preferred_lft forever
      inet6 ::1/128 scope host
          valid_lft forever preferred_lft forever
    

    可以看到Docker容器仅有一lo环回接口,用户使用–net=none启动容器之后,仍然可以手动为容器配置网络。

  • container:与另一个运行中的容器共享Network Namespace,共享相同的网络视图。 举个例子,首先以默认网络配置(bridge模式)启动一个容器,设置hostname为dockerNet,dns为8.8.4.4。
    $ docker run -h dockerNet --dns 8.8.4.4 -tid ubuntu:latest bash
    d25864df1a3bbdd40613552197bd1a965acaf7f3dcb2673d50c875d4a303a67f
    $ docker exec -ti d25864df1a3b bash
    root@dockerNet:/# ip addr show
    1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
          link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
          inet 127.0.0.1/8 scope host lo
              valid_lft forever preferred_lft forever
          inet6 ::1/128 scope host
              valid_lft forever preferred_lft forever
    1739: eth0: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
          link/ether 02:42:ac:11:00:01 brd ff:ff:ff:ff:ff:ff
          inet 172.17.0.1/16 scope global eth0
              valid_lft forever preferred_lft forever
          inet6 fe80::42:acff:fe11:1/64 scope link
              valid_lft forever preferred_lft forever
    root@dockerNet:/# cat /etc/resolv.conf
    nameserver 8.8.4.4
    root@dockerNet:/# exit
    exit
    

    然后以–net=container:d25864df1a3b方式启动另一个容器:

    $ docker run --net=container:d25864df1a3b -ti ubuntu:latest bash
    root@dockerNet:/# ip addr show
    1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
          link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
          inet 127.0.0.1/8 scope host lo
              valid_lft forever preferred_lft forever
          inet6 ::1/128 scope host
              valid_lft forever preferred_lft forever
    1739: eth0: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
          link/ether 02:42:ac:11:00:01 brd ff:ff:ff:ff:ff:ff
          inet 172.17.0.1/16 scope global eth0
              valid_lft forever preferred_lft forever
          inet6 fe80::42:acff:fe11:1/64 scope link
              valid_lft forever preferred_lft forever
    root@dockerNet:/# cat /etc/resolv.conf
    nameserver 8.8.4.4
    

    可以看到,使用–net=container:d25864df1a3b参数启动的容器,其IP地址、DNS、hostname都继承了容器d25864df1a3b。实质上两个容器是共享同一个Network Namespace的,自然网络配置也是完全相同的。

  • host:与主机共享Root Network Namespace,容器有完整的权限可以操纵主机的协议栈、路由表和防火墙等,所以被认为是不安全的。 相应的,host模式启动时需要指定–net=host参数。举个例子:
    $ docker run -ti --net=host ubuntu:latest bash
    root@darcy-HP:/# ip addr show
    1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
          link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
          inet 127.0.0.1/8 scope host lo
              valid_lft forever preferred_lft forever
          inet6 ::1/128 scope host
              valid_lft forever preferred_lft forever
    2: eth0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN
    group default qlen 1000
          link/ether 2c:41:38:9e:e4:d5 brd ff:ff:ff:ff:ff:ff
    3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP
    group default qlen 1000
          link/ether 00:1b:21:cc:ee:6d brd ff:ff:ff:ff:ff:ff
          inet 10.110.52.38/22 brd 10.110.55.255 scope global eth1
              valid_lft forever preferred_lft forever
          inet6 fe80::21b:21ff:fecc:ee6d/64 scope link
              valid_lft forever preferred_lft forever
    1642: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
    group default
          link/ether 22:f2:f3:18:62:5d brd ff:ff:ff:ff:ff:ff
          inet 172.17.42.1/16 scope global docker0
              valid_lft forever preferred_lft forever
          inet6 fe80::348e:71ff:fe44:2d41/64 scope link
              valid_lft forever preferred_lft forever
    

    host模式下,容器可以操纵主机的网络配置,这是危险的,除非万不得已,应该尽可能避免使用host模式。

  • bridge:Docker设计的NAT网络模型。 Docker daemon启动时会在主机创建一个Linux网桥(默认为docker0,可通过-b参数手动指定)。容器启动时,Docker会创建一对veth pair(虚拟网络接口)设备,veth设备的特点是成对存在,从一端进入的数据会同时出现在另一端。Docker会将一端挂载到docker0网桥上,另一端放入容器的Network Namespace内,从而实现容器与主机通信的目的。 在桥接模式下,Docker容器与Internet的通信,以及不同容器之间的通信,都是通过iptables规则控制的。

总之,Docker网络的初始化动作包括:创建docker0网桥、为docker0网桥新建子网及路由、创建相应的iptables规则等。

举例来说,初次启动Docker daemon后,用户可以观察到主机的网络接口、网桥、路由等信息发生了如下变化:

# docker daemon -D -s aufs --userland-proxy=false
...
$ brctl show
bridge name     bridge id     STP enabled     interfaces
docker0         8000.000000000000 no
# 当前没有容器在运行,因而网桥上没有网络接口
$ ip addr show docker0
1642: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default
        link/ether 36:8e:71:44:2d:41 brd ff:ff:ff:ff:ff:ff
        inet 172.17.42.1/16 scope global docker0
            valid_lft forever preferred_lft forever
        inet6 fe80::348e:71ff:fe44:2d41/64 scope link
            valid_lft forever preferred_lft forever
# 默认docker0分配了172.17.42.1/16的子网,容器以bridge网络模式运行时默认从这个子网分配IP。
$ route -n内核 IP 路由表目标          网关           子网掩码         标志   跃点    引用    使用    接口
0.0.0.0      10.110.52.1   0.0.0.0         UG    0      0      0      eth1
10.110.52.0  0.0.0.0       255.255.252.0   U     1      0      0      eth1
172.17.0.0   0.0.0.0       255.255.0.0     U     0      0      0      docker0

而主机的iptables规则增加较多,可等同于通过如下命令创建完成的:

# iptables --wait -t nat -I POSTROUTING -s 172.17.42.1/16 ! -o docker0 -j MASQUERADE
# iptables --wait -t nat -I POSTROUTING -m addrtype --src-type LOCAL -o docker0 -j MASQUERADE
# iptables --wait -D FORWARD -i docker0 -o docker0 -j DROP
# iptables --wait -A FORWARD -i docker0 -o docker0 -j ACCEPT
# iptables --wait -I FORWARD -i docker0 ! -o docker0 -j ACCEPT
# iptables --wait -I FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED
-j ACCEPT
# iptables --wait -t nat -n -L DOCKER
# iptables --wait -t nat -A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
# iptables --wait -t nat -A OUTPUT -m addrtype --dst-type LOCAL -j DOCKER
# iptables --wait -t filter -n -L DOCKER
# iptables --wait -t filter -C FORWARD -o docker0 -j DOCKER
# iptables --wait -I FORWARD -o docker0 -j DOCKER

用户可以通过使用iptables-vnL-t nat命令来查看iptables nat表或其他表(替换前述命令-t后表名)的内容。 Bridge模式是Docker默认的容器运行模式,以bridge模式启动的容器,默认会从172.17.42.1/16子网内分配IP。示例操作如下:

$ docker run -ti --net=bridge ubuntu:latest ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
        link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
        inet 127.0.0.1/8 scope host lo
                valid_lft forever preferred_lft forever
        inet6 ::1/128 scope host
                valid_lft forever preferred_lft forever
1743: eth0: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
        link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
        inet 172.17.0.3/16 scope global eth0
                valid_lft forever preferred_lft forever
        inet6 fe80::42:acff:fe11:3/64 scope link tentative
                valid_lft forever preferred_lft forever
  • overlay:Docker原生的跨主机多子网模型。 overlay网络模型比较复杂,底层需要类似consul或etcd的KV存储系统进行消息同步,核心是通过Linux网桥与vxlan隧道实现跨主机划分子网。

每创建一个网络,Docker会在主机上创建一个单独的沙盒,沙盒的实现实质上是一个Network Namespace。在沙盒中,Docker会创建名为br0的网桥,并在网桥上增加一个vxlan接口,每个网络占用一个vxlan ID,当前Docker创建vxlan隧道的ID范围为256~1000,因而最多可以创建745个网络。当添加一个容器到某一个网络上时,Docker会创建一对veth网卡设备,一端连接到此网络相关沙盒内的br0网桥上,另一端放入容器的沙盒内,并设置br0的IP地址作为容器内路由默认的网关地址,从而实现容器加入网络的目的。 用户随后可以通过如下命令创建网络并在网络上运行容器了。

$ docker network create -d overlay dev
$ docker network ls
NETWORK ID          NAME                TYPE
9101d162c6db        bridge              bridge
fcd0327f5104        dev                 overlay
f5f9c8723777        none                null
eb81445767e1        host                host
# bridge,null,host会随Docker启动创建默认网络,dev是上一条命令创建成功的。
$ docker run -tid --publish-service test.dev ubuntu:latest bash
$ docker service ls
SERVICE ID          NAME                NETWORK             CONTAINER
015ab0c2fb24        test                dev                 9738192f6c2c
$ docker exec -ti 9738192f6c2c ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default 
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
        valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
        valid_lft forever preferred_lft forever
39: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group
default
    link/ether 02:42:4b:e0:1f:23 brd ff:ff:ff:ff:ff:ff
    inet 172.21.0.1/16 scope global eth0
        valid_lft forever preferred_lft forever
    inet6 fe80::42:4bff:fee0:1f23/64 scope link 
        valid_lft forever preferred_lft forever

通过以上操作,成功创建了一个名为dev的overlay网络,并创建了一个运行在该网络上的容器9738192f6c2c,且为容器绑定了名为test的服务。容器的IP为172.21.0.1。

Docker网络相关参数

下面先来了解一下Docker网络相关参数的含义。

Docker daemon部分的网络参数说明如下:

$ docker daemon --help
A self-sufficient runtime for linux containers.
Options:
-b, --bridge=      Attach containers to a network bridge
# 指定Docker daemon使用的网桥,默认为"docker0"。若设置-b="none",则禁用Docker的网络功能
--bip=       Specify network bridge IP
# 指定docker0网桥的IP,注意--bip不能与-b同时使用
--default-gateway=      Container default gateway IPv4 address
# 设置容器默认的IPv4网关
--default-gateway-v6=      Container default gateway IPv6 address
# 设置容器默认的IPv6网关
--dns=[]      DNS server to use
# 设置容器内的DNS服务器地址
--dns-search=[]      DNS search domains to use
# 设置容器内的search domain,即域名解析时默认添加的域名后缀
--fixed-cidr=      IPv4 subnet for fixed IPs
# 容器网络模式为bridge时,会从此子网内分配IP,本参数设置的子网必须嵌套于docker0网桥所属
# 子网之内
--fixed-cidr-v6=      IPv6 subnet for fixed IPs
# 与--fixed-cidr相同,bridge模式下默认分配的IPv6子网地址
-H, --host=[]      Daemon socket(s) to connect to
# 指定Docker client与Docker daemon通信的socket地址,可以是tcp地址、unix socket地址
# 或socket文件描述符,可同时指定多个,例如:
# docker daemon -H tcp://10.110.48.32:10000 -H unix:///var/run/docker.sock
# 代表Docker daemon同时监听10.110.48.32:10000及本机/var/run/docker.sock文件
--icc=true      Enable inter-container communication
# 允许/禁止容器间通信,禁用icc依赖iptables规则,若--icc=false,则必须--iptables=true
--ip=0.0.0.0      Default IP when binding container ports
# 容器暴露端口时默认绑定的主机IP,默认为0.0.0.0
--ip-forward=true      Enable net.ipv4.ip_forward
# 使能IP转发功能,为true则向主机/proc/sys/net/ipv4/ip_forward写入1
--ip-masq=true      Enable IP masquerading
# 使能IP地址变形功能(NAT),只有--iptables=true才可生效
--iptables=true      Enable addition of iptables rules
# 使能iptables。若设置为false,则无法向iptables表格添加规则
--ipv6=false      Enable IPv6 networking
# 使能IPv6网络功能
--mtu=0      Set the containers network MTU
# 设置容器网络MTU(最大传输单元)
--userland-proxy=true      Use userland proxy for loopback traffic
# 当设置为true时,Docker会为每个映射到主机端口的容器端口启动一个docker-proxy进程用于
# 数据转发。实际上这会耗用大量CPU资源,Docker社区正在计划去除docker-proxy

Docker client部分的网络参数说明如下:

$ docker run --help
Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
Run a command in a new container
--add-host=[]      Add a custom host-to-IP mapping (host:ip)
# 在容器内的/etc/hosts文件内增加一行主机对IP的映射
--dns=[]      Set custom DNS servers
# 设置容器的DNS服务器
--dns-search=[]      Set custom DNS search domains
# 设置容器的DNS搜索域
--expose=[]      Expose a port or a range of ports
# 暴露容器的端口,而不映射到主机端口
-h, --hostname=      Container host name
# 设置容器的主机名
--link=[]      Add link to another container
# 链接到另一个容器,在本容器中可以通过容器ID或者容器名访问对方
--mac-address=      Container MAC address (e.g. 92:d0:c6:0a:29:33)
# 设置容器的mac地址
--net=bridge      Set the Network mode for the container
# 设置容器的网络运行模式,当前支持四种模式:bridge、none、host、container
-P, --publish-all=false      Publish all exposed ports to random ports
# 将容器所有暴露出的端口映射到主机随机端口
-p, --publish=[]      Publish a container's port(s) to the host
# 将容器一段范围内的端口映射主机指定的端口

Search

    微信好友

    博士的沙漏

    Table of Contents