本篇的主要内容是关于docker daemon启动时网络设置的相关部分,在上一篇中已经简要 提到(Docker daemon启动流程) 。主要内容集中在InitDriver函数的解析上。

简介

InitDriver函数位于github.com/docker/docker/daemon/networkdriver/bridge/driver.go中。 主要包含参数解析,docker0的创建,iptables的设置等。

相关网络参数

前面两篇也已经提到了相关的网络参数,主要是以下几个:

  1. iptables

    是否启用iptables

  2. icc

    Inter-Container Communitaion,是否允许docker container之间以及与 host之间的的通信。

  3. ip-masq

    是否启用IP masquerading,用于源地址转换。

  4. bridge ip

    docker0指定IP

  5. CIDR

    限制给container分配的IP范围.

  6. ip

    container映射port时默认的绑定地址,默认为0.0.0.0.

  7. bridge

    使用已经存在的网桥而不是创建docker0.

这些参数由命令行参数传入,最终存储在Jobenv中,InitDriver即通过env来获取 相关的设置:

var (
    network        *net.IPNet
    enableIPTables = job.GetenvBool("EnableIptables")
    icc            = job.GetenvBool("InterContainerCommunication")
    ipMasq         = job.GetenvBool("EnableIpMasq")
    ipForward      = job.GetenvBool("EnableIpForward")
    bridgeIP       = job.Getenv("BridgeIP")
    fixedCIDR      = job.Getenv("FixedCIDR")
)

之后便是对ip参数进行设置:

defaultBindingIP  = net.ParseIP("0.0.0.0")

if defaultIP := job.Getenv("DefaultBindingIP"); defaultIP != "" {
    defaultBindingIP = net.ParseIP(defaultIP)
}

然后便是判断是否使用默认的网桥docker0:

bridgeIface = job.Getenv("BridgeIface")
usingDefaultBridge := false
if bridgeIface == "" {
	usingDefaultBridge = true
	bridgeIface = DefaultNetworkBridge
}

网桥设置

确定好将要使用的网桥后,便需要对其IP地址进行相关设定。首先看能不能获取到其IP地址:

addr, err := networkdriver.GetIfaceAddr(bridgeIface)

docker服务停止后docker0网桥并不删掉,所以如果不是第一次使用,都能成功获取 到ip。我们来看下首次创建docker0的情况:

if !usingDefaultBridge {
    return job.Error(err)
}

if err := configureBridge(bridgeIP); err != nil {
    return job.Error(err)
}
    
addr, err = networkdriver.GetIfaceAddr(bridgeIface)
if err != nil {
    return job.Error(err)
}
network = addr.(*net.IPNet)

如果要使用自己指定的网桥而且获取不到IP,那就只能当成错误返回了。如果是使用 docker0且获取不到IP,则就创建并且赋给它IP,configureBridge参数为bridgeIp, 即命令行里的-bip,一般都没有指定,默认值为空。

addrs = []string{
    "172.17.42.1/16",
    "10.0.42.1/16",  
    "10.1.42.1/16",
    "10.42.42.1/16",
    "172.16.42.1/24",
    "172.16.43.1/24",
    "172.16.44.1/24",
    "10.0.42.1/24",
    "10.0.43.1/24",
    "192.168.42.1/24",
    "192.168.43.1/24",
    "192.168.44.1/24",
}

for _, addr := range addrs {
    _, dockerNetwork, err := net.ParseCIDR(addr)
    if err != nil {
        return err
    }
    if err := networkdriver.CheckNameserverOverlaps(nameservers, dockerNetwork); err == nil {
        if err := networkdriver.CheckRouteOverlaps(dockerNetwork); err == nil {
            ifaceAddr = addr
            break
        } else {
            log.Debugf("%s %s", addr, err)
        }
    }
}

每次给docker0分配IP,都会从addrs一个一个地尝试,所以我们一般看到的运行中的 docker0的IP都是172.17.42.1/16。对addrs中的IP,要检测其与nameservers和路由 表是否有重叠。nameservers即是从系统的/etc/resolv.conf中读取到的列表,路由表是 类似于如下图的结果中的第一列:

有了IP之后,再次调用GetIfaceAddr,将获取到的地址赋给addr,后面的IP Tables的 设置要用到这个地址。

IPTables 设置

if enableIPTables {
    if err := setupIPTables(addr, icc, ipMasq); err != nil {
        return job.Error(err)
    }
}

下面的重点部分就是介绍setupIPTables函数的流程。因为所涉及到IPTables部分较多, 单看代码难以理顺,所以将主要结合程序运行时的日志信息来进行介绍。

IP Masquerade

关于IP Masquerade的介绍,因为个人英语水平有限,怕翻译的不准确,感兴趣可以先看下这个 简单的介绍What is IP Masquerade?。 简单来说就是用来做对包的源地址做地址转换(NAT)

如果ipmasq参数为真,则插入一条相应的rule(先检查是否存在,不存在则插入)

natArgs := []string{"POSTROUTING", "-t", "nat", "-s", addr.String(), "!", "-o", bridgeIface, "-j", "MASQUERADE"}

if !iptables.Exists(natArgs...) {
    if output, err := iptables.Raw(append([]string{"-I"}, natArgs...)...); err != nil {
        return fmt.Errorf("Unable to enable network bridge NAT: %s", err)
    } else if len(output) != 0 {
        return &iptables.ChainError{Chain: "POSTROUTING", Output: output}
    }
}

从日志中我们可以看到实际运行的命令是:

这条rules的作用是对于container向外发出的包做源地址转换操作.

Package Forward

  • Inter-Container Communitaion

如果启用Inter-Container Communitaion (icc为真),则实际执行的结果如下:

确保container之间包的转发的targetACCEPT

iptables.Raw(append([]string{"-D"}, dropArgs...)...)
if !iptables.Exists(acceptArgs...) {
    log.Debugf("Enable inter-container communication")
    if output, err := iptables.Raw(append([]string{"-I"}, acceptArgs...)...); err != nil {
        return fmt.Errorf("Unable to allow intercontainer communication: %s", err)
    } else if len(output) != 0 {
        return fmt.Errorf("Error enabling intercontainer communication: %s", output)
    }
}
  • Non-Intercontainer Outgoing Packets

对于non-intercontainer outgoing packets,也将其target设为ACCEPT:

outgoingArgs := []string{"FORWARD", "-i", bridgeIface, "!", "-o", bridgeIface, "-j", "ACCEPT"}
if !iptables.Exists(outgoingArgs...) {
	if output, err := iptables.Raw(append([]string{"-I"}, outgoingArgs...)...); err != nil {
		return fmt.Errorf("Unable to allow outgoing packets: %s", err)
	} else if len(output) != 0 {
		return &iptables.ChainError{Chain: "FORWARD outgoing", Output: output}
	}
}

实际执行的命令为: /sbin/iptables --wait -C FORWARD -i docker0 ! -o docker0 -j ACCEPT

iptables的参数中,-C代表检查,-D删除,-I插入。一般设置都是先检查,没有的话 再插入,所以一般日志中见到的都只有-C

  • Incoming Packets for Existing Connecting

Accept incoming packets for existing connections

existingArgs := []string{"FORWARD", "-o", bridgeIface, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"}

if !iptables.Exists(existingArgs...) {
    if output, err := iptables.Raw(append([]string{"-I"}, existingArgs...)...); err != nil {
        return fmt.Errorf("Unable to allow incoming packets: %s", err)
    } else if len(output) != 0 {
        return &iptables.ChainError{Chain: "FORWARD incoming", Output: output}
    }
}

实际执行的命令为: /sbin/iptables --wait -C FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEP

Docker Chain

以上两部分就是setIPTables的流程,主要是一些基本的网络设置。除此之外,还有一些针 对NAT表中docker chain的设置。

先从日志中看看实际执行的命令是什么:

(-F 删除一条chain中的所有规则,-X删除一条用户自定义的chain,-N创建一条用 户自定义的chain,-A在一条chain后面添加一条规则.)

首先是尝试删除docker chain中的规则及其本身:

if err := iptables.RemoveExistingChain("DOCKER"); err != nil {
    return job.Error(err)
}

然后添加新的空的docker chain

if enableIPTables {
    chain, err := iptables.NewChain("DOCKER", bridgeIface)
    if err != nil {
        return job.Error(err)
    }
    portmapper.SetIptablesChain(chain)
}

最终的nat的表的结果如下图所示:

当有container的端口暴露时,我们就可以看到其中会有新的rules添加进来:

Handlers

网络设置的最后一部分是一些handler的注册,具体见下面列表:

Name Funciton Description
allocate_interface Allocate Allocate a network interface
release_interface Release Release an interface for a select ip
allocate_port AllocatePort Allocate an external port and map it to the interface
link LinkContainers